跨平台端到端加密(Swift&Node):共享密钥不一致问题排查
ECDH跨平台密钥派生不一致问题解决
问题现象
生成两组ECDH公私钥并导出为PEM格式,分别导入Swift(CryptoKit)和JS(Web Crypto)后,双方派生的共享密钥不一致,导致无法互相解密。已确认密钥导入后导出与原PEM一致,问题出在共享密钥派生环节。
原JS代码及输出
async function deriveKey(publicKey, privateKey) { return await crypto.subtle.deriveKey( { name: "ECDH", public: publicKey }, privateKey, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); } let sharedKey1 = await deriveKey(publicKey2, privateKey); let exportedKey = await crypto.subtle.exportKey('raw', sharedKey1); let keyString = Buffer.from(exportedKey).toString('base64'); console.log(keyString);
输出:1vF4AK9IqDDHNZ86zxt5zavx3h+V7AFCfpBU5Yv8Zro=
原Swift代码及输出
func deriveSymmetricKey(privateKey: P256.KeyAgreement.PrivateKey, publicKey: P256.KeyAgreement.PublicKey) throws -> SymmetricKey { let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: publicKey) let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey( using: SHA256.self, salt: Data(), sharedInfo: Data(), outputByteCount: 32 ) return symmetricKey } let sharedKey1 = try deriveSymmetricKey(privateKey: privateKey, publicKey: publicKey2) let keyData = sharedKey1.withUnsafeBytes { Data(Array($0)) } let keyString = keyData.base64EncodedString() print(keyString)
输出:SeQZEg38dcfRl8+5LIwiiJXABJnOMuv3srlrIDZ0wQc=
问题原因
Web Crypto的deriveKey方法从ECDH派生AES密钥时,底层默认使用HKDF,但未显式指定哈希算法、salt和sharedInfo参数,导致其使用的HKDF参数与Swift端不一致:
- Swift端明确使用SHA256哈希算法、空salt、空sharedInfo
- 原JS代码未指定HKDF参数,Web Crypto会使用默认值(不同环境可能有差异,且大概率与Swift端不匹配)
修正方案
调整JS代码,显式使用HKDF派生密钥,确保所有参数与Swift端完全一致:
修正后的JS代码
async function deriveSharedKey(publicKey, privateKey) { // 先获取ECDH原始共享秘密 const sharedSecret = await crypto.subtle.deriveBits( { name: "ECDH", public: publicKey }, privateKey, 256 // P256曲线的共享秘密长度为256位 ); // 用HKDF派生对称密钥,参数与Swift严格对齐 return await crypto.subtle.deriveKey( { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(), // 空salt匹配Swift设置 info: new Uint8Array() // 空sharedInfo匹配Swift设置 }, { name: "HKDF", raw: sharedSecret }, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); } let sharedKey1 = await deriveSharedKey(publicKey2, privateKey); let exportedKey = await crypto.subtle.exportKey('raw', sharedKey1); let keyString = Buffer.from(exportedKey).toString('base64'); console.log(keyString);
验证说明
修正后,JS端派生的共享密钥Base64字符串会与Swift端完全一致,此时双方使用AES-GCM加密解密即可互相兼容。
内容的提问来源于stack exchange,提问作者eskimo
相关产品推荐
相关产品推荐

