为什么 secp256k1 privateKeys 在 nodejs 中不总是 32 字节?
Why are secp256k1 privateKeys not always 32 bytes in nodejs?
当我 运行 遇到一些生成的私钥长度并不总是 32 字节的问题时,我正在使用节点的加密模块生成大量 secp256k1 密钥。我写了一个测试脚本,它清楚地表明这种情况经常发生。
这是什么原因,是否有修复方法,或者我是否必须检查长度然后重新生成直到我得到 32 个字节?
这是重现问题的测试脚本:
const { createECDH, ECDH } = require("crypto");
const privateLens = {};
const publicLens = {};
for(let i = 0; i < 10000; i++){
const ecdh = createECDH("secp256k1");
ecdh.generateKeys();
const privateKey = ecdh.getPrivateKey("hex");
const publicKey = ecdh.getPublicKey("hex");
privateLens[privateKey.length+""] = (privateLens[privateKey.length+""] || 0) + 1;
publicLens[publicKey.length+""] = (publicLens[publicKey.length+""] || 0) + 1;
}
console.log(privateLens);
console.log(publicLens);
(多次运行的)输出如下所示:
% node test.js
{ '62': 32, '64': 9968 }
{ '130': 10000 }
% node test.js
{ '62': 40, '64': 9960 }
{ '130': 10000 }
% node test.js
{ '62': 39, '64': 9961 }
{ '130': 10000 }
我只是不明白...如果我用 base64 对其进行编码,它的长度始终相同,但将其解码回缓冲区时,某些键再次显示 31 个字节。
谢谢,非常感谢任何见解!
对于 EC 密码术,密钥在字节上不是完全随机的,它是 [1, N)
范围内的随机 数字 ,其中 N 是曲线的阶数。现在通常生成的数字将与 256 位顺序处于相同的范围内。尤其如此,因为 N 已(故意)选择为 非常 接近 2^256,即 secp256k1 的高位全部设置为 1。
但是,大约每 256 次,所选私钥 s 的前几位仍然全部设置为零。这意味着它需要 31 个或更少的字节而不是 32 个字节。一旦达到 65536,它甚至会是 30 个字节而不是 32 个字节,等等。一旦超过 40 亿次(小规模),它甚至会使用 29 个字节。
Base64 使用一个字符代表 6 位,不包括开销。然而,通常它一次只将 3 个字节的块编码为 4 个字符(可能包括用 =
个字符填充)。这意味着 32 个字节将占用 ceil(32 / 3) * 4 = 44
个字节。现在从 ceil(31 / 3) * 4 = 44
开始,您将不会注意到任何事情。然而,每 65536 次你就会得到 ceil(30 / 3) * 4 = 40
。在那之后,达到 36 个字符变得极不可能(尽管从密码学上来说, 小得可以忽略不计 ,“只是”在 2^48 次中出现一次——我想有些彩票做得更糟)...
所以不,您不必重新生成密钥 - 对于算法来说,它们毕竟是完全有效的。对于私钥,您通常没有太多兼容性要求,但通常您会尝试将此类密钥编码为静态大小(32 字节,可能在左侧使用 00
值字节)。将它们重新编码为静态大小的密钥可能是个好主意...
当我 运行 遇到一些生成的私钥长度并不总是 32 字节的问题时,我正在使用节点的加密模块生成大量 secp256k1 密钥。我写了一个测试脚本,它清楚地表明这种情况经常发生。
这是什么原因,是否有修复方法,或者我是否必须检查长度然后重新生成直到我得到 32 个字节?
这是重现问题的测试脚本:
const { createECDH, ECDH } = require("crypto");
const privateLens = {};
const publicLens = {};
for(let i = 0; i < 10000; i++){
const ecdh = createECDH("secp256k1");
ecdh.generateKeys();
const privateKey = ecdh.getPrivateKey("hex");
const publicKey = ecdh.getPublicKey("hex");
privateLens[privateKey.length+""] = (privateLens[privateKey.length+""] || 0) + 1;
publicLens[publicKey.length+""] = (publicLens[publicKey.length+""] || 0) + 1;
}
console.log(privateLens);
console.log(publicLens);
(多次运行的)输出如下所示:
% node test.js
{ '62': 32, '64': 9968 }
{ '130': 10000 }
% node test.js
{ '62': 40, '64': 9960 }
{ '130': 10000 }
% node test.js
{ '62': 39, '64': 9961 }
{ '130': 10000 }
我只是不明白...如果我用 base64 对其进行编码,它的长度始终相同,但将其解码回缓冲区时,某些键再次显示 31 个字节。
谢谢,非常感谢任何见解!
对于 EC 密码术,密钥在字节上不是完全随机的,它是 [1, N)
范围内的随机 数字 ,其中 N 是曲线的阶数。现在通常生成的数字将与 256 位顺序处于相同的范围内。尤其如此,因为 N 已(故意)选择为 非常 接近 2^256,即 secp256k1 的高位全部设置为 1。
但是,大约每 256 次,所选私钥 s 的前几位仍然全部设置为零。这意味着它需要 31 个或更少的字节而不是 32 个字节。一旦达到 65536,它甚至会是 30 个字节而不是 32 个字节,等等。一旦超过 40 亿次(小规模),它甚至会使用 29 个字节。
Base64 使用一个字符代表 6 位,不包括开销。然而,通常它一次只将 3 个字节的块编码为 4 个字符(可能包括用 =
个字符填充)。这意味着 32 个字节将占用 ceil(32 / 3) * 4 = 44
个字节。现在从 ceil(31 / 3) * 4 = 44
开始,您将不会注意到任何事情。然而,每 65536 次你就会得到 ceil(30 / 3) * 4 = 40
。在那之后,达到 36 个字符变得极不可能(尽管从密码学上来说, 小得可以忽略不计 ,“只是”在 2^48 次中出现一次——我想有些彩票做得更糟)...
所以不,您不必重新生成密钥 - 对于算法来说,它们毕竟是完全有效的。对于私钥,您通常没有太多兼容性要求,但通常您会尝试将此类密钥编码为静态大小(32 字节,可能在左侧使用 00
值字节)。将它们重新编码为静态大小的密钥可能是个好主意...