一覧SC-11-1 · 鍵・秘密情報の寿命

乱数・ソルトと鍵導出

秘密にすべき入力と、結果の照合に一緒に保存するパラメーターを説明します。

説明

秘密値を作るときの乱数生成と、パスワードから鍵を導出する処理は目的が違います。saltは同じパスワードの結果を分ける公開値で、パスワード自体の推測しにくさを増やすものではありません。

再現できる疑似乱数とOSの乱数APIを比較し、公開試料パスワードからsaltと反復数を変えて鍵を導出します。最後に同じ鍵材料から用途ごとの鍵を分けます。

秘密にすべき入力と、結果の照合に一緒に保存するパラメーターを説明します。

1同じseedの疑似乱数を再現する

通常のrandomはシミュレーション向けです。同じseedを知っていれば出力を再現できます。

import random,secrets,hashlib from sc11 import derive a = random.Random(7)再現用のseedを固定する b = random.Random(7) print([a.randrange(256)1個目の生成器からバイト範囲の値を得る for _ in range(8)]) print([b.randrange(256) for _ in range(8)])

8個の値が一致します。見た目がばらばらでも、秘密の生成に適したAPIとは限りません。

2OSの乱数源を使うAPIを確認する

secretsはOSが提供する乱数源を使います。生成した値の見た目で強度を判定せず、用途に合うAPIを選びます。

sample = secrets.token_bytes(32)OSの乱数源から32バイトを取得する print(len(sample),len(sample.hex()))

32バイトは16進数表示で64桁です。この一回の生成や長さの確認は、乱数の品質を統計的に保証する試験ではありません。

3パスワードとsaltから鍵を導出する

PBKDF2-HMAC-SHA256を使います。反復数1000は計算の比較用の小さな値で、運用向けの設定値ではありません。

password = b"public-example-password" salt_a = b"account-A-salt" k1 = hashlib.pbkdf2_hmacパスワード用の反復導出を行う("sha256",password,salt_a,1000,32) print(k1.hex()) print(hashlib.pbkdf2_hmac("sha256",password,salt_a,1000,32) == k1)

同じ入力とパラメーターなら同じ32バイトになります。照合にはsalt、方式、反復数、出力長も必要です。

4別のsaltで同じパスワードを処理する

別のアカウントへ異なるsaltを割り当てた場合です。

k2 = hashlib.pbkdf2_hmac("sha256",password,b"account-B-salt"アカウントごとのsaltを変更する,1000,32) print(k2.hex(),k2 == k1)

結果は一致しません。同じパスワードのアカウント間で結果を共用しにくくしますが、個々のパスワード候補を試す攻撃は別に考える必要があります。

5反復数だけを変更する

saltとパスワードを保ち、反復数を2000へ変更します。

k3 = hashlib.pbkdf2_hmac("sha256",password,salt_a,2000,32反復数を増やし出力長は保つ) print(k3.hex(),k3 == k1)

反復数も結果に影響します。パラメーターを更新したときは、保存済みの値をどの設定で照合するか記録する必要があります。

6保存するパラメーターをまとめる

パスワードそのものではなく、方式・salt・反復数・導出値を記録する例です。

record =方式とパラメーターを保存する {"algorithm":"pbkdf2-sha256","salt":salt_a.hex(),"iterations":1000,"derived":k1.hex()} print(record) print(hashlib.pbkdf2_hmac("sha256",b"wrong-password"別の候補を同じ設定で処理する,bytes.fromhex(record["salt"]),record["iterations"],32).hex() == record["derived"])

別パスワードは一致しません。saltは照合に必要な公開情報で、秘密の代用品ではありません。実際の比較には一定時間の比較処理も使います。

7十分な鍵材料から用途別の鍵を作る

HKDFはここでは公開試料の32バイトを入力にします。パスワード推測の費用を増やす処理の代わりではありません。

material = bytes(range(32)) encryption = derive(material,b"application-salt",b"encryption"暗号化用の文脈を指定する) authentication = derive(material,b"application-salt",b"authentication"認証用の文脈を指定する) print(encryption.hex(),authentication.hex(),encryption == authentication)

用途ラベルで異なる鍵を導出できました。鍵を分けると、一つの鍵を複数の暗号処理へ不用意に流用する設計を避けられます。

まとめ

秘密の生成には用途に適した乱数APIを使います。

saltと費用パラメーターを導出値と一緒に管理します。

HKDFの用途分離と、パスワードの推測費用を増やす処理は別の役割です。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
import random,secrets,hashlib from sc11 import derive a = random.Random(7) b = random.Random(7) print([a.randrange(256) for _ in range(8)]) print([b.randrange(256) for _ in range(8)])
[165, 77, 202, 24, 37, 48, 187, 29]
[165, 77, 202, 24, 37, 48, 187, 29]
2
sample = secrets.token_bytes(32) print(len(sample),len(sample.hex()))
32 64
3
password = b"public-example-password" salt_a = b"account-A-salt" k1 = hashlib.pbkdf2_hmac("sha256",password,salt_a,1000,32) print(k1.hex()) print(hashlib.pbkdf2_hmac("sha256",password,salt_a,1000,32) == k1)
42e914f3359d32d6ea49a71c95376e43f512bc284c1e02971a39411206b5bb4f
True
4
k2 = hashlib.pbkdf2_hmac("sha256",password,b"account-B-salt",1000,32) print(k2.hex(),k2 == k1)
3dd7498f16793f1742d9f1494569ad77dfbd6a99eb6becec909104c2365b7c15 False
5
k3 = hashlib.pbkdf2_hmac("sha256",password,salt_a,2000,32) print(k3.hex(),k3 == k1)
81f7da3fe176bafaf77a4dbe9121eee856f9885306cc659383920a77b6048f28 False
6
record = {"algorithm":"pbkdf2-sha256","salt":salt_a.hex(),"iterations":1000,"derived":k1.hex()} print(record) print(hashlib.pbkdf2_hmac("sha256",b"wrong-password",bytes.fromhex(record["salt"]),record["iterations"],32).hex() == record["derived"])
{'algorithm': 'pbkdf2-sha256', 'salt': '6163636f756e742d412d73616c74', 'iterations': 1000, 'derived': '42e914f3359d32d6ea49a71c95376e43f512bc284c1e02971a39411206b5bb4f'}
False
7
material = bytes(range(32)) encryption = derive(material,b"application-salt",b"encryption") authentication = derive(material,b"application-salt",b"authentication") print(encryption.hex(),authentication.hex(),encryption == authentication)
f01b8efd4e4e7f5fad0a1e916070a0dc a95dac744b9ceecb3dec015e9de64587 False

演習完了