為什麼大多數時候 X25519 私鑰根據使用的功能略有不匹配,但公鑰總是匹配(兩者的種子相同)?
我正試圖從 a
seed
到 aSigningKey
,並獲得一個PrivateKey
(加密密鑰)。我正在使用 NaCl / libsodium。我創建了下面的程式碼,結果很有趣。結果
pk1.private_key
並pk2.private_key
匹配大約 3% 的時間。然而,公鑰匹配 100%,所有生成都以相同的seed
. 這裡發生了什麼?
- 我
pk1
通過使用獲得PrivateKey.from_seed(seed)
- 我
pk2
通過使用獲得SigningKey(seed).to_curve25519_private_key()
不匹配的範例(它們接近但不相等):
# 1st byte mismatch by 0x01 seed: f8d9e54a23971beebf2552c1a50ade6150cd051321398394f515e8d4b1ba0404 priv1: c1fd4612ee8ef24d295210a277e196e6bb4a9ae6b93f98d93f197860fe5dc048 priv2: c0fd4612ee8ef24d295210a277e196e6bb4a9ae6b93f98d93f197860fe5dc048 # 1st byte mismatch by 0x03 seed: d612a66f92ee2f42ab1f7ea9a712a47c815843d21fc988b1d202459f235b6410 priv1: f33d5e80bb556333e2961c9868b1dc7e548836ee56808689ca022f1a19fe86bb priv2: f03d5e80bb556333e2961c9868b1dc7e548836ee56808689ca022f1a19fe867b # 1st byte mismatch by 0x01, last byte mismatch by 0x40 seed: 10b7e1c66cf08005a22289158a088e028160f892dc6c20d43025be4690aaed85 priv1: 194898f65d117579d50e80a9b7e07bd048bfd1300d55561dac9dfaed4ef02109 priv2: 184898f65d117579d50e80a9b7e07bd048bfd1300d55561dac9dfaed4ef02149
from nacl.signing import SigningKey from nacl.public import PrivateKey, PublicKey, Box, SealedBox from nacl.bindings import crypto_sign_SEEDBYTES from nacl.utils import StringFixer, random def run(debug=False): seed = random(crypto_sign_SEEDBYTES) pk1 = PrivateKey.from_seed(seed) pk2 = SigningKey(seed).to_curve25519_private_key() if debug: print('seed: ', seed.hex()) print('priv1: ', pk1._private_key.hex()) print('priv2: ', pk2._private_key.hex()) print('pub1: ', bytes(pk1.public_key).hex()) print('pub2: ', bytes(pk2.public_key).hex()) return seed, pk1, pk2 runs = 10000 private_key_match = 0 public_key_match = 0 both_match = 0 for i in range(runs): if i % 500 == 0: print(i, 'of', runs) seed, pk1, pk2 = run() x = pk1._private_key == pk2._private_key y = bytes(pk1.public_key) == bytes(pk2.public_key) if x: private_key_match += 1 if y: public_key_match += 1 if x and y: both_match += 1 print('private key match:', private_key_match) print('public key match: ', public_key_match) print('both match: ', both_match)
在數學上,Curve25519私鑰是 $ \mathbb{F}_{2^{255}-19} $ , 即整數模 $ 2^{255}-19 $ . 這自然可以表示為之間的整數 $ 0 $ 和 $ 2^{255}-19-1 $ ,它本身可以用 255 位來表示。由於實際電腦使用 8 位字節,因此最小的實際表示是 32 字節字元串。
有一種表示 Curve25519 私鑰的標準格式,在RFC 7748 §5中有描述。這種格式編碼之間的整數 $ 0 $ 和 $ 2^{255}-19-1 $ 作為小端順序的 32 字節(256 位)字元串。
一個 32 字節的字元串可以表示範圍內的數字 $ [0,2^{256}-1] $ , 大於 $ [0,2^{255}-19-1] $ . 此外,由於各種原因,並非該範圍內的所有數字 $ [0,2^{255}-19-1] $ 很好(參見Curve25519 關鍵結構和Genkin 等人的“May the Fourth Be With You: A Microarchitectural Side Channel Attack on several Real-World Applications of Curve25519”)。約束是:
- 數字必須介於 $ 2^{254} $ 和 $ 2^{255}-19 $ ,所以 256 位數的最高有效兩位必須是 0 和 1。
- 該數字必須是 8 的倍數,因此最低有效三位必須為 0。
RFC 7748 §5 規定,在生成私鑰時,必須採用隨機(或偽隨機,視情況而定)32 字節字元串,並強制上述五位為其強制值。Curve25519 的實現將私鑰作為 32 字節字元串形式的輸入,必須在開始對密鑰表示的數字進行計算之前應用此遮罩。
nacl.public.PrivateKey.from_seed
返回原始的 32 字節字元串。nacl.signing.SigningKey(seed)
執行屏蔽,因此to_curve25519_private_key
以規範的屏蔽形式導出值。遮罩涉及 5 位,所以給定一個隨機種子,有一個 $ 1/2^5 = 1/32 \approx 3% $ 有可能它已經具有這 5 位的正確值。