繁体   English   中英

共享 ECDH 密钥,浏览器 + NodeJS

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM