源码:
之前使用GO已经可以实现秘钥交换了,这里使用浏览器发送数据,与后端服务实现秘钥交换,记录一下实现的基本函数:
// 生成密钥对,并保存到全局变量中async function createDHPair() {// 生成新的ECDH密钥对const keyPair = await window.crypto.subtle.generateKey({name: "ECDH",namedCurve: "P-256"},true, // 允许导出私钥["deriveKey", "deriveBits"] // 导出私钥的权限);return keyPair;};async function getPublicKey(keyPair) {const publicKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);return publicKey;}async function getPublicKeyRaw(keyPair) {const publicKey = await crypto.subtle.exportKey("raw", keyPair.publicKey);return publicKey;}// async function getPrivateKey(keyPair) {// const publicKey = await crypto.subtle.exportKey("pkcs8", keyPair.publicKey);// return publicKey;// }async function calculateSharedSecret(privateKey, publicKey) {// 执行Diffie-Hellman密钥交换以计算共享密钥const sharedSecret = await window.crypto.subtle.deriveBits({name: "ECDH",namedCurve: "P-256",public: publicKey // 使用对方的公钥},privateKey, // 使用自己的私钥256,);return new Uint8Array(sharedSecret);}
getPublicKeyRaw
函数使用了 crypto.subtle.exportKey
方法来将公钥导出为原始数据格式。这里的 "raw" 参数指示将密钥导出为原始字节序列。
与之相比,通常使用的其他导出类型包括 "spki"(SubjectPublicKeyInfo)和 "pkcs8"(Private-Key Information Syntax Standard #8)。这两种类型是 ASN.1 格式的密钥编码格式,用于在不同系统之间交换和存储密钥。"spki" 用于导出公钥,"pkcs8" 用于导出私钥。
区别在于:
-
"raw" 格式:将密钥直接导出为原始的字节序列,没有进行任何格式化。这种格式可能比较简单,但也更加直接。
-
"spki" 格式:导出的是 SubjectPublicKeyInfo 结构,包含了公钥的算法标识和公钥本身。这种格式通常用于在 X.509 证书中传输和存储公钥。
-
"pkcs8" 格式:导出的是 Private-Key Information Syntax Standard #8 结构,包含了私钥的算法标识和私钥本身。这种格式通常用于在 PKCS#8 格式的私钥文件中存储私钥。
测试的代码如下:
async function test1(){const keyPair1 = await createDHPair();const keyPair2 = await createDHPair();console.log("s1:", keyPair1);console.log("s2:", keyPair2);const publicKey1 = await getPublicKey(keyPair1);const publicKey2 = await getPublicKey(keyPair2);console.log("public1:", publicKey1);console.log("public2:", publicKey2);const publicKey11 = await getPublicKeyRaw(keyPair1);console.log("public1:", publicKey11);// const privateKey1 = await getPrivateKey(keyPair1);// const privateKey2 = await getPrivateKey(keyPair2);//// console.log("private1:", privateKey1);// console.log("private2:", privateKey2);// 计算共享密钥const sharedSecret1 = await calculateSharedSecret(keyPair1.privateKey, keyPair2.publicKey);const sharedSecret2 = await calculateSharedSecret(keyPair2.privateKey, keyPair1.publicKey);// sharedSecret1 和 sharedSecret2 应该是相同的(如果计算正确的话)console.log("Shared secret 1:", sharedSecret1);console.log("Shared secret 2:", sharedSecret2);}
备注:
1)公钥可以导出,私钥不允许导出,所以只能生成秘钥之后,保存共享密钥,用指纹来标记;
2)具体的使用raw导出无法与GO等交互,需要按照标准约定来导出和导入,也就是spki方式;
具体能用的代码参考项目文件。https://github.com/robinfoxnan/BirdTalkServer/blob/main/server/js/wsclient.js