[英]Shared ECDH Secret, Browser + NodeJS
我正在尝试使用椭圆曲线 Diffie-Hellman 密钥在浏览器和 NodeJS 之间创建共享密钥。 如果我将浏览器公钥导出为raw
,一切正常,但我需要将密钥导出为spki
,然后 NodeJS 会为此生气。
在浏览器中,我这样做:
async function generateDHKeys() {
const key_ECDH = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveKey'],
);
const publicKeyData = await window.crypto.subtle.exportKey(
'spki',
key_ECDH.publicKey,
);
const publicKeyBytes = new Uint8Array(publicKeyData);
publicKeyB64 = btoa(String.fromCharCode.apply(null, publicKeyBytes));
const privateKeyData = await window.crypto.subtle.exportKey(
'pkcs8',
key_ECDH.privateKey,
);
const privateKeyBytes = new Uint8Array(privateKeyData);
privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
privateKeyBytes.fill(0);
return { publicKeyB64, privateKeyB64 };
}
const {publicKeyB64} = await generateDHKeys();
所以,现在我已经导出了公钥并将其转换为 Base64。 然后我将它发送到 NodeJS 服务器,并尝试创建一个共享密钥:
在 NodeJS 中,我这样做:
export function generateDHKeys(foreignPublicKeyB64) {
const ecdh = crypto.createECDH("prime256v1");
ecdh.generateKeys();
const publicKeyB64 = ecdh.getPublicKey("base64");
const privateKeyB64 = ecdh.getPrivateKey("base64");
const sharedSecretB64 = ecdh.computeSecret(foreignPublicKeyB64, "base64", "base64");
const sharedSecretHashB64 = crypto
.createHash("sha256")
.update(sharedSecretB64, "base64")
.digest("base64");
return { publicKeyB64, privateKeyB64, sharedSecretB64, sharedSecretHashB64 };
}
我收到一条错误消息,提示“公钥对指定曲线无效”。
但是,如果在浏览器代码中我将密钥导出为raw
(而不是spki
)它可以工作......
如何在浏览器中将公钥导出为spki
,然后在 NodeJS 中使用它生成共享密钥? 或者,如何将 Base64 SPKI 公钥转换为 Node 中的原始密钥?
编辑发现 Node v15.0.0+ 确实支持 Browser Crypto API,这意味着我的浏览器 JS 可以简单地复制并在 Node 上下文中运行。 而不是像在浏览器中那样访问window.crypto.subtle
,在 Node 应用程序中,我可以像这样导入微妙的模块:
const { subtle } = require("crypto").webcrypto;
但是...正如@Topaco 指出的那样,从 Node v16.2.0 开始,此 API 仍处于试验阶段,可能会发生变化。 有关其他信息和文档链接,请参阅@Topaco 的答案。
据我所知,NodeJS 加密模块不支持 ECDH 上下文中公钥的 X.509/SPKI 格式,而只支持原始密钥。 但是,可以从 X.509/SPKI 密钥派生原始密钥。
使用 WebCrypto 代码生成的 X.509/SPKI 密钥封装了原始(更准确地说是未压缩)密钥 0x04 + <x> + <y>,该密钥在末尾进行了本地化。 对于 P-256 aka prime256v1,最后 65 个字节对应于原始密钥。 对于不同的 P-256 键,前部是相同的。
这样,在 NodeJS 代码中,P-256 的原始密钥可以确定为 X.509/SPKI 密钥的最后 65 个字节。
同样,X.509/SPKI 密钥的前面部分可以与 NodeJS 代码生成的原始密钥连接,从而将原始密钥转换为 X.509/SPKI 格式。
用于此的 NodeJS 代码是:
// Convert the SPKI key of the WebCrypto side into the raw format
var webcryptoSpkiB64 = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPF2r2yyMp/PykPZEt6v8WFAvnrf5FsI3UnpEYsbKo7UKVKB8k2hfxxhjKw8p9nulNaRo472hTcEqsSbsGcr5Dg==';
var webcryptoRawB64 = Buffer.from(webcryptoSpkiB64, 'base64').slice(-65).toString('base64'); // the last 65 bytes
// Calculate the shared secret for the NodeJS side
var { publicKeyB64, privateKeyB64, sharedSecretB64, sharedSecretHashB64 } = generateDHKeys(webcryptoRawB64);
// Convert the raw key of the NodeJS side into the SPKI format
var nodejsSpkiB64 = Buffer.concat([
Buffer.from(webcryptoSpkiB64, 'base64').slice(0, -65), // all bytes except the last 65
Buffer.from(publicKeyB64, 'base64')]
).toString('base64');
console.log("Shared secret:", sharedSecretB64);
console.log("SPKI:", nodejsSpkiB64); // will be sent to the WebCrypto side and used there to calculate the shared secret
其中generateDHKeys()
是问题中发布的 function。
编辑:正如 OP 的评论中所述,WebCrypto API 现在是 NodeJS 的一部分,因此通过 NodeJS 中的 WebCrypto API 在 ECDH 的上下文中也支持 X.509/SPKI 密钥。 不过需要指出的是,当前 NodeJS 版本v16.0.2中的 WebCrypto API 稳定性为 1 级(Experimental)。 这意味着非向后兼容的更改或删除是可能的。 此外,当前的LTS版本 ( v14.17.0 ) 不包括 WebCrypto API。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.