DiffieHellman是一种经典的密钥交换算法,能够让两个通信方在不安全的公开信道中,各自生成私钥和公钥,通过交换公钥计算出相同的共享密钥,且该共享密钥无法被第三方通过截获的公钥推导出来。HTML5的Web Crypto API提供了原生的密码学能力,其中就包含DiffieHellman算法的实现,不需要开发者引入额外的第三方库就能完成密钥交换操作。

DiffieHellman密钥交换的核心原理
DiffieHellman算法基于离散对数问题的数学特性,核心流程分为三步:首先双方协商一致的公共参数,包括一个大素数p和它的一个原根g;然后双方各自生成自己的私钥a和b,计算对应的公钥A=g^a mod p和B=g^b mod p,交换公钥;最后双方用对方的公钥和自己的私钥计算共享密钥,一方计算B^a mod p,另一方计算A^b mod p,最终结果是相同的,这个相同的结果就是双方协商出的共享密钥。
HTML5中使用Web Crypto API实现DiffieHellman交换的步骤
1. 生成DiffieHellman公共参数
首先需要生成双方都认可的公共参数,也就是素数p和原根g,Web Crypto API中可以通过generateKey方法生成DH密钥对时同时生成对应的参数。
// 生成DiffieHellman公共参数,同时生成第一方的密钥对
async function generateDHKeyPair() {
// 生成2048位的DH密钥对,算法名称为ECDH时实际使用的是椭圆曲线DiffieHellman,这里用经典的DH参数需要指定算法为DH
// 注意:部分浏览器对经典DH支持有限,更推荐使用ECDH,这里以经典DH为例说明逻辑
const algorithm = {
name: "DH",
prime: new Uint8Array([0x01, 0x02, 0x03]), // 实际使用时需要生成合法的2048位大素数,这里仅为示例
generator: 2
};
try {
const keyPair = await window.crypto.subtle.generateKey(
algorithm,
true, // 是否可提取密钥
["deriveKey", "deriveBits"] // DH密钥的用途是派生共享密钥
);
return keyPair;
} catch (e) {
console.error("生成DH密钥对失败:", e);
}
}
2. 双方交换公钥
生成密钥对后,双方需要将自己的公钥导出为可传输的格式,发送给对方,同时接收对方的公钥并导入到本地。
// 导出公钥为JWK格式,方便传输
async function exportPublicKey(publicKey) {
const jwk = await window.crypto.subtle.exportKey("jwk", publicKey);
return jwk;
}
// 导入对方发来的公钥
async function importPublicKey(jwkPublicKey, dhParams) {
const publicKey = await window.crypto.subtle.importKey(
"jwk",
jwkPublicKey,
{
name: "DH",
prime: dhParams.prime,
generator: dhParams.generator
},
false,
[]
);
return publicKey;
}
3. 计算共享密钥
拿到对方的公钥后,结合自己的私钥,就可以调用deriveBits方法计算出共享的密钥比特序列,之后可以将这个比特序列作为对称加密算法的密钥使用。
// 用己方私钥和对方公钥计算共享密钥
async function deriveSharedSecret(privateKey, publicKey) {
// 派生128位的共享密钥比特
const sharedBits = await window.crypto.subtle.deriveBits(
{
name: "DH",
public: publicKey
},
privateKey,
128 // 派生128位,即16字节的密钥
);
// 将比特序列转换为十六进制字符串方便查看
const sharedKeyHex = Array.from(new Uint8Array(sharedBits))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
return sharedKeyHex;
}
完整示例:两端协商共享密钥
以下是一个简化的完整流程示例,模拟通信双方A和B完成密钥交换的过程:
async function runDHExchange() {
// 通信方A的操作
const dhParams = {
name: "DH",
prime: new Uint8Array([0x01, 0x02, 0x03]), // 实际场景需替换为合法大素数
generator: 2
};
const keyPairA = await window.crypto.subtle.generateKey(
dhParams,
true,
["deriveKey", "deriveBits"]
);
const publicKeyA_JWK = await exportPublicKey(keyPairA.publicKey);
// 通信方B的操作,使用A传来的参数生成自己的密钥对
const keyPairB = await window.crypto.subtle.generateKey(
dhParams,
true,
["deriveKey", "deriveBits"]
);
const publicKeyB_JWK = await exportPublicKey(keyPairB.publicKey);
// A导入B的公钥,计算共享密钥
const publicKeyB_A = await importPublicKey(publicKeyB_JWK, dhParams);
const sharedSecretA = await deriveSharedSecret(keyPairA.privateKey, publicKeyB_A);
// B导入A的公钥,计算共享密钥
const publicKeyA_B = await importPublicKey(publicKeyA_JWK, dhParams);
const sharedSecretB = await deriveSharedSecret(keyPairB.privateKey, publicKeyA_B);
console.log("A计算的共享密钥:", sharedSecretA);
console.log("B计算的共享密钥:", sharedSecretB);
console.log("两个共享密钥是否一致:", sharedSecretA === sharedSecretB);
}
// 执行交换流程
runDHExchange();
注意事项
- 经典DH算法在部分浏览器中的支持度不如ECDH(椭圆曲线DiffieHellman),实际开发中更推荐使用ECDH,只需要将算法名称改为
ECDH,参数指定为对应的椭圆曲线(如P-256)即可,流程完全一致。 - 生成的公共参数中的大素数需要符合密码学安全要求,不要使用示例中的简单数值,避免密钥被破解。
- 导出的公钥在传输过程中仍然需要注意防篡改,建议配合签名机制验证公钥的来源,避免中间人替换公钥发起攻击。
- 计算出的共享密钥不要直接使用,最好通过KDF(密钥派生函数)进一步处理后再作为对称加密的密钥,提升安全性。
HTML5DiffieHellman密钥交换Web_Crypto_API修改时间:2026-07-11 23:48:28