P-521 (Web Crypto Api) / secp521r1 (NodeJS Crypto) 的 ECDH 生成略有不同的共享密钥
ECDH for P-521 (Web Crypto Api) / secp521r1 (NodeJS Crypto) generate a slightly different shared secret
我已经从 NodeJS
生成了 public 和私钥对 ECDH
function _genPrivateKey(curveName = "secp384r1", encoding = "hex") {
const private_0 = crypto.createECDH(curveName);
private_0.generateKeys();
return private_0.getPrivateKey().toString(encoding);
}
BOB 私钥
9d0c809d692c83c7d8d1355205dd78e679066fd9c15d12cdea3e1103b041873765264351e8939083b876d89d423301d8486a956da455ddbdc4a91ef60af7dd2325
BOB PUBLIC KEY
[hex]
0400d7a342e89f501e1cd8224e2463ef1ad057c9b64bf45c1d3627cc1f06c055c80f75c2013c27b63dc984b467ecfc5202cf9a126ef1f1487e92b9acfb52abaeb7022e01f4259918ca34f442214a31d1bad329f9be1c67ce98af6621f622e44887264e856ee8c664a51e56f24008c932ee1cb5514c02d03ba27f6b6a1cd0aa0f8eac261250
[jwk] {
key_ops: [ 'deriveKey' ],
ext: true,
kty: 'EC',
x:'ANejQuifUB4c2CJOJGPvGtBXybZL9FwdNifMHwbAVcgPdcIBPCe2PcmEtGfs_FICz5oSbvHxSH6Suaz7UquutwIu',
y:'AfQlmRjKNPRCIUox0brTKfm-HGfOmK9mIfYi5EiHJk6FbujGZKUeVvJACMky7hy1UUwC0Duif2tqHNCqD46sJhJQ',
crv: 'P-521'
}
来自网页的 和 Alice
的密钥 Web Crypto API
const generateAlicesKeyPair = window.crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-521"
},
false,
["deriveBits"]
);
爱丽丝PUBLIC钥匙
04000eefa90c3de22e79e6742f807806a603059d16afaa9f1bc69f420050aae100d0006e6510fe17a8f6767fe1e69bada039175ef5a375e30af4085e4315cf7527655f00ed9a39552a5f9170cc7626c1f4584d0e6de17870e336bcc5b6e251e3ea2c7cd9633e1afe2f9aee5f9a7445d38218c20695cc7ba2a462b67ce39a060e6464133609
当我尝试导出 shared key
时,一件奇怪的事情发生了,密钥的末尾有不同的位。
NodeJS:
function _getSharedSecret(privateKey, publicKey, curveName = "secp521r1", encoding = "hex") {
const private_0 = crypto.createECDH(curveName);
private_0.setPrivateKey(privateKey, encoding);
const _sharedSecret = private_0.computeSecret(publicKey, encoding);
return _sharedSecret
};
网络加密API
const sharedSecret = await window.crypto.subtle.deriveBits({
name: "ECDH",
namedCurve: "P-521",
public: publicKey
},
privateKey,
521
);
Results:
//NodeJS:
0089b99212c9348a6fd3aa78225a773a90ef45f57bbd10dc86d8e52fa26662c550b56d2368ee358ab240ceec10191b6cdd7d09bb0a8763ea48a487a5676ebdf7af[eb]
//WebCryptoAPI:
0089b99212c9348a6fd3aa78225a773a90ef45f57bbd10dc86d8e52fa26662c550b56d2368ee358ab240ceec10191b6cdd7d09bb0a8763ea48a487a5676ebdf7af[80]
这只发生在曲线 P-521/secp521r1 而不是曲线 P-256/secp256r1 和 P-384/secp384r1
检查第三个库(Python、Cryptography 库)表明 NodeJS 结果是正确的(即以 0xEB
结尾的那个) ).
P-521的X和Y坐标用66字节表示(521位=65字节+1位)(s.). The shared secret is the X coordinate of a curve point (s. here)因此也有66字节=528位。此值将在 WebCrypto API 实现中指定 deriveBits()
作为共享秘密的长度。
如果您指定 521 位,则仅考虑最高位(为 0xEB
设置),其余位设置为 0
,结果值为 0x80
.
以下代码说明了这一点(请注意该脚本在 Firefox 下不 运行,这可能是一个错误):
(async () => {
await getSharedSecret(521);
await getSharedSecret(528);
})();
async function getSharedSecret(bits) {
var bobPrivateKeyJwk = {
kty: "EC",
crv: "P-521",
x:'ANejQuifUB4c2CJOJGPvGtBXybZL9FwdNifMHwbAVcgPdcIBPCe2PcmEtGfs_FICz5oSbvHxSH6Suaz7UquutwIu',
y:'AfQlmRjKNPRCIUox0brTKfm-HGfOmK9mIfYi5EiHJk6FbujGZKUeVvJACMky7hy1UUwC0Duif2tqHNCqD46sJhJQ',
d: "AJ0MgJ1pLIPH2NE1UgXdeOZ5Bm_ZwV0Szeo-EQOwQYc3ZSZDUeiTkIO4dtidQjMB2EhqlW2kVd29xKke9gr33SMl",
ext: true,
}
var alicePublicKeyBuffer = typedArray('04000eefa90c3de22e79e6742f807806a603059d16afaa9f1bc69f420050aae100d0006e6510fe17a8f6767fe1e69bada039175ef5a375e30af4085e4315cf7527655f00ed9a39552a5f9170cc7626c1f4584d0e6de17870e336bcc5b6e251e3ea2c7cd9633e1afe2f9aee5f9a7445d38218c20695cc7ba2a462b67ce39a060e6464133609');
var privateKey = await window.crypto.subtle.importKey(
"jwk",
bobPrivateKeyJwk,
{ name: "ECDH", namedCurve: "P-521" },
true,
["deriveKey", "deriveBits"]
);
var publicKey = await window.crypto.subtle.importKey(
"raw",
alicePublicKeyBuffer.buffer,
{ name: "ECDH", namedCurve: "P-521"},
true,
[]
);
var sharedSecret = await window.crypto.subtle.deriveBits(
{ name: "ECDH", namedCurve: "P-521", public: publicKey },
privateKey,
bits
);
console.log("Bob's shared secret:\n", buf2hex(sharedSecret).replace(/(.{48})/g,'\n'));
};
function typedArray(hex) {
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { // from:
return parseInt(h, 16)
}))
}
function buf2hex(buffer) {
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); // from:
}
曲线 P-256 和 P-384 不会出现问题,因为素数域已经是 8 的倍数(分别为 32 字节和 48 字节)。
我已经从 NodeJS
生成了 public 和私钥对ECDH
function _genPrivateKey(curveName = "secp384r1", encoding = "hex") {
const private_0 = crypto.createECDH(curveName);
private_0.generateKeys();
return private_0.getPrivateKey().toString(encoding);
}
BOB 私钥
9d0c809d692c83c7d8d1355205dd78e679066fd9c15d12cdea3e1103b041873765264351e8939083b876d89d423301d8486a956da455ddbdc4a91ef60af7dd2325
BOB PUBLIC KEY
[hex]
0400d7a342e89f501e1cd8224e2463ef1ad057c9b64bf45c1d3627cc1f06c055c80f75c2013c27b63dc984b467ecfc5202cf9a126ef1f1487e92b9acfb52abaeb7022e01f4259918ca34f442214a31d1bad329f9be1c67ce98af6621f622e44887264e856ee8c664a51e56f24008c932ee1cb5514c02d03ba27f6b6a1cd0aa0f8eac261250
[jwk] {
key_ops: [ 'deriveKey' ],
ext: true,
kty: 'EC',
x:'ANejQuifUB4c2CJOJGPvGtBXybZL9FwdNifMHwbAVcgPdcIBPCe2PcmEtGfs_FICz5oSbvHxSH6Suaz7UquutwIu',
y:'AfQlmRjKNPRCIUox0brTKfm-HGfOmK9mIfYi5EiHJk6FbujGZKUeVvJACMky7hy1UUwC0Duif2tqHNCqD46sJhJQ',
crv: 'P-521'
}
来自网页的 和 Alice
的密钥 Web Crypto API
const generateAlicesKeyPair = window.crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-521"
},
false,
["deriveBits"]
);
爱丽丝PUBLIC钥匙
04000eefa90c3de22e79e6742f807806a603059d16afaa9f1bc69f420050aae100d0006e6510fe17a8f6767fe1e69bada039175ef5a375e30af4085e4315cf7527655f00ed9a39552a5f9170cc7626c1f4584d0e6de17870e336bcc5b6e251e3ea2c7cd9633e1afe2f9aee5f9a7445d38218c20695cc7ba2a462b67ce39a060e6464133609
当我尝试导出 shared key
时,一件奇怪的事情发生了,密钥的末尾有不同的位。
NodeJS:
function _getSharedSecret(privateKey, publicKey, curveName = "secp521r1", encoding = "hex") {
const private_0 = crypto.createECDH(curveName);
private_0.setPrivateKey(privateKey, encoding);
const _sharedSecret = private_0.computeSecret(publicKey, encoding);
return _sharedSecret
};
网络加密API
const sharedSecret = await window.crypto.subtle.deriveBits({
name: "ECDH",
namedCurve: "P-521",
public: publicKey
},
privateKey,
521
);
Results:
//NodeJS:
0089b99212c9348a6fd3aa78225a773a90ef45f57bbd10dc86d8e52fa26662c550b56d2368ee358ab240ceec10191b6cdd7d09bb0a8763ea48a487a5676ebdf7af[eb]
//WebCryptoAPI:
0089b99212c9348a6fd3aa78225a773a90ef45f57bbd10dc86d8e52fa26662c550b56d2368ee358ab240ceec10191b6cdd7d09bb0a8763ea48a487a5676ebdf7af[80]
这只发生在曲线 P-521/secp521r1 而不是曲线 P-256/secp256r1 和 P-384/secp384r1
检查第三个库(Python、Cryptography 库)表明 NodeJS 结果是正确的(即以 0xEB
结尾的那个) ).
P-521的X和Y坐标用66字节表示(521位=65字节+1位)(s.deriveBits()
作为共享秘密的长度。
如果您指定 521 位,则仅考虑最高位(为 0xEB
设置),其余位设置为 0
,结果值为 0x80
.
以下代码说明了这一点(请注意该脚本在 Firefox 下不 运行,这可能是一个错误):
(async () => {
await getSharedSecret(521);
await getSharedSecret(528);
})();
async function getSharedSecret(bits) {
var bobPrivateKeyJwk = {
kty: "EC",
crv: "P-521",
x:'ANejQuifUB4c2CJOJGPvGtBXybZL9FwdNifMHwbAVcgPdcIBPCe2PcmEtGfs_FICz5oSbvHxSH6Suaz7UquutwIu',
y:'AfQlmRjKNPRCIUox0brTKfm-HGfOmK9mIfYi5EiHJk6FbujGZKUeVvJACMky7hy1UUwC0Duif2tqHNCqD46sJhJQ',
d: "AJ0MgJ1pLIPH2NE1UgXdeOZ5Bm_ZwV0Szeo-EQOwQYc3ZSZDUeiTkIO4dtidQjMB2EhqlW2kVd29xKke9gr33SMl",
ext: true,
}
var alicePublicKeyBuffer = typedArray('04000eefa90c3de22e79e6742f807806a603059d16afaa9f1bc69f420050aae100d0006e6510fe17a8f6767fe1e69bada039175ef5a375e30af4085e4315cf7527655f00ed9a39552a5f9170cc7626c1f4584d0e6de17870e336bcc5b6e251e3ea2c7cd9633e1afe2f9aee5f9a7445d38218c20695cc7ba2a462b67ce39a060e6464133609');
var privateKey = await window.crypto.subtle.importKey(
"jwk",
bobPrivateKeyJwk,
{ name: "ECDH", namedCurve: "P-521" },
true,
["deriveKey", "deriveBits"]
);
var publicKey = await window.crypto.subtle.importKey(
"raw",
alicePublicKeyBuffer.buffer,
{ name: "ECDH", namedCurve: "P-521"},
true,
[]
);
var sharedSecret = await window.crypto.subtle.deriveBits(
{ name: "ECDH", namedCurve: "P-521", public: publicKey },
privateKey,
bits
);
console.log("Bob's shared secret:\n", buf2hex(sharedSecret).replace(/(.{48})/g,'\n'));
};
function typedArray(hex) {
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { // from:
return parseInt(h, 16)
}))
}
function buf2hex(buffer) {
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); // from:
}
曲线 P-256 和 P-384 不会出现问题,因为素数域已经是 8 的倍数(分别为 32 字节和 48 字节)。