如何修复非交互式Bob-Alice Diffie-Hellman密钥交换解密错误?
问题
我刚入门密码学与网络安全领域,正尝试复现Bob与Alice的Diffie-Hellman密钥交换,目标是实现该协议的非交互式版本:
- Alice需完成的操作:
- 获取Bob的公钥(pkBob);
- 利用该公钥的公开参数生成DH密钥对(skAlice, pkAlice);
- 结合pkBob与自身私钥推导会话密钥K;
- 使用认证密码算法加密目标消息;
- 生成包含pkAlice和加密密文的"cryptogram"文件。
- Bob需完成的操作:
- 通过自身私钥(skBob)与pkAlice推导会话密钥K;
- 使用K解密密文。
我已编写如下Node.js代码实现上述流程:
const crypto = require("crypto") const fs = require("fs") function serializeKey(key) { return key.toString("base64") } function deserializeKey(pem, type) { return Buffer.from(pem, "base64") } function generateDHKeys(user) { const dh = crypto.createDiffieHellman(2048) const privateKey = dh.generateKeys() const publicKey = dh.getPublicKey() const publicKeyPEM = serializeKey(publicKey) const privateKeyPEM = serializeKey(privateKey) fs.writeFileSync(`${user}.pk`, publicKeyPEM, "utf8") fs.writeFileSync(`${user}.sk`, privateKeyPEM, "utf8") } function getSharedSecret(myPrivateKeyPEM, theirPublicKeyPEM) { const dh = crypto.createDiffieHellman(2048) dh.setPrivateKey(deserializeKey(myPrivateKeyPEM)) const theirPublicKey = deserializeKey(theirPublicKeyPEM) const secret = dh.computeSecret(theirPublicKey) return secret } function encryptWithAESGCM(secret, message) { const iv = crypto.randomBytes(12) // AES-GCM recommends 12-byte IVs const cipher = crypto.createCipheriv("aes-256-gcm", secret.slice(0, 32), iv) let encrypted = cipher.update(message, "utf8", "hex") encrypted += cipher.final("hex") const authTag = cipher.getAuthTag().toString("hex") return { encrypted, iv: iv.toString("hex"), authTag } } function decryptWithAESGCM(secret, encryptedData) { const decipher = crypto.createDecipheriv("aes-256-gcm", secret.slice(0, 32), Buffer.from(encryptedData.iv, "hex")) decipher.setAuthTag(Buffer.from(encryptedData.authTag, "hex")) let decrypted = decipher.update(encryptedData.encrypted, "hex", "utf8") decrypted += decipher.final("utf8") return decrypted } function encryptMessage(user, filePath) { const myPrivateKeyPEM = fs.readFileSync(`${user}.sk`, "utf8") const theirPublicKeyPEM = fs.readFileSync(`${user}.pk`, "utf8") const secret = getSharedSecret(myPrivateKeyPEM, theirPublicKeyPEM) const message = fs.readFileSync(filePath, "utf8") const { encrypted, iv, authTag } = encryptWithAESGCM(secret, message) const outputFilePath = `${filePath}.enc` fs.writeFileSync(outputFilePath, JSON.stringify({ encrypted, iv, authTag })) } function decryptMessage(user, encryptedFilePath) { const myPrivateKeyPEM = fs.readFileSync(`${user}.sk`, "utf8") const theirPublicKeyPEM = fs.readFileSync(`${user}.pk`, "utf8") const secret = getSharedSecret(myPrivateKeyPEM, theirPublicKeyPEM) const encryptedData = JSON.parse(fs.readFileSync(encryptedFilePath, "utf8")) const decryptedMessage = decryptWithAESGCM(secret, encryptedData) const outputFilePath = `${encryptedFilePath}.dec` fs.writeFileSync(outputFilePath, decryptedMessage, "utf8") } function main() { const args = process.argv.slice(2) const operation = args[0] switch (operation) { case "set": generateDHKeys(args[1]) break case "enc": encryptMessage(args[1], args[2]) break case "dec": decryptMessage(args[1], args[2]) break default: console.log("Unknown Operation:", operation) } } main()
操作指令:
node diffie.js set bob -> Creates Bob's keys node diffie.js set alice -> Creates Alice's keys node diffie.js enc bob test_file -> Encrypts test_file node diffie.js dec bob test_file.enc -> Decrypts test_file.enc
目前我能成功生成Bob和Alice的密钥,并加密含简单消息的测试文件,但解密时持续报错,推测是加密处理存在问题。当前使用AES-GCM算法,核心需求是成功实现Diffie-Hellman密钥交换,而非特定密码算法。请问该如何调整解决此问题?
解决方案
核心问题分析
你的代码存在三个关键错误,直接导致DH密钥交换失败、共享密钥不匹配:
- DH参数不统一:每次调用
crypto.createDiffieHellman(2048)都会生成新的素数(p)和生成元(g),Alice和Bob的DH参数完全不同,无法计算出相同的共享密钥。 - 公钥读取逻辑错误:加密/解密时读取的是自身公钥,而非对方的公钥(比如Alice加密应该读取Bob的公钥,而不是自己的)。
- 未按协议要求包含发送方公钥:生成的加密文件里没有包含Alice的公钥,Bob无法用它来计算共享密钥。
修改后的完整代码
const crypto = require("crypto") const fs = require("fs") // 序列化/反序列化工具函数 function serializeKey(key) { return key.toString("base64") } function deserializeKey(pem) { return Buffer.from(pem, "base64") } // 生成DH密钥对,同时保存公共参数(p和g) function generateDHKeys(user) { const dh = crypto.createDiffieHellman(2048) const privateKey = dh.generateKeys() const publicKey = dh.getPublicKey() // 保存公共参数(p和g),确保双方使用同一套参数 const params = { p: serializeKey(dh.getPrime()), g: serializeKey(dh.getGenerator()) } fs.writeFileSync(`${user}.params`, JSON.stringify(params), "utf8") // 保存公私钥 fs.writeFileSync(`${user}.pk`, serializeKey(publicKey), "utf8") fs.writeFileSync(`${user}.sk`, serializeKey(privateKey), "utf8") } // 加载对方的DH参数,初始化DH实例 function initDHFromParams(paramsPath) { const params = JSON.parse(fs.readFileSync(paramsPath, "utf8")) const dh = crypto.createDiffieHellman(deserializeKey(params.p), deserializeKey(params.g)) return dh } // 计算共享密钥 function getSharedSecret(myPrivateKeyPEM, theirPublicKeyPEM, paramsPath) { const dh = initDHFromParams(paramsPath) dh.setPrivateKey(deserializeKey(myPrivateKeyPEM)) const theirPublicKey = deserializeKey(theirPublicKeyPEM) return dh.computeSecret(theirPublicKey) } // AES-GCM加密(保留原逻辑,仅确保密钥来源正确) function encryptWithAESGCM(secret, message) { const iv = crypto.randomBytes(12) const cipher = crypto.createCipheriv("aes-256-gcm", secret.slice(0, 32), iv) let encrypted = cipher.update(message, "utf8", "hex") encrypted += cipher.final("hex") const authTag = cipher.getAuthTag().toString("hex") return { encrypted, iv: iv.toString("hex"), authTag } } // AES-GCM解密 function decryptWithAESGCM(secret, encryptedData) { const decipher = crypto.createDecipheriv( "aes-256-gcm", secret.slice(0, 32), Buffer.from(encryptedData.iv, "hex") ) decipher.setAuthTag(Buffer.from(encryptedData.authTag, "hex")) let decrypted = decipher.update(encryptedData.encrypted, "hex", "utf8") decrypted += decipher.final("utf8") return decrypted } // Alice加密逻辑:需要传入接收方(Bob)的参数路径和公钥路径 function encryptMessage(sender, receiver, filePath) { // 加载发送方自己的私钥 const myPrivateKeyPEM = fs.readFileSync(`${sender}.sk`, "utf8") // 加载接收方的公钥和DH参数 const theirPublicKeyPEM = fs.readFileSync(`${receiver}.pk`, "utf8") const paramsPath = `${receiver}.params` // 计算共享密钥 const secret = getSharedSecret(myPrivateKeyPEM, theirPublicKeyPEM, paramsPath) // 加密消息 const message = fs.readFileSync(filePath, "utf8") const encryptedData = encryptWithAESGCM(secret, message) // 加入发送方公钥,生成cryptogram文件 const myPublicKeyPEM = fs.readFileSync(`${sender}.pk`, "utf8") const cryptogram = { senderPk: myPublicKeyPEM, ...encryptedData } const outputFilePath = `${filePath}.cryptogram` fs.writeFileSync(outputFilePath, JSON.stringify(cryptogram), "utf8") } // Bob解密逻辑:传入自己的参数路径,读取加密文件中的发送方公钥 function decryptMessage(receiver, cryptogramPath) { // 加载接收方自己的私钥和DH参数 const myPrivateKeyPEM = fs.readFileSync(`${receiver}.sk`, "utf8") const paramsPath = `${receiver}.params` // 读取加密文件中的发送方公钥和加密数据 const cryptogram = JSON.parse(fs.readFileSync(cryptogramPath, "utf8")) const senderPublicKeyPEM = cryptogram.senderPk const encryptedData = { encrypted: cryptogram.encrypted, iv: cryptogram.iv, authTag: cryptogram.authTag } // 计算共享密钥 const secret = getSharedSecret(myPrivateKeyPEM, senderPublicKeyPEM, paramsPath) // 解密消息 const decryptedMessage = decryptWithAESGCM(secret, encryptedData) const outputFilePath = `${cryptogramPath}.dec` fs.writeFileSync(outputFilePath, decryptedMessage, "utf8") } // 主函数调整参数逻辑 function main() { const args = process.argv.slice(2) const operation = args[0] switch (operation) { case "set": generateDHKeys(args[1]) break // 加密指令:node diffie.js enc alice bob test_file case "enc": encryptMessage(args[1], args[2], args[3]) break // 解密指令:node diffie.js dec bob test_file.cryptogram case "dec": decryptMessage(args[1], args[2]) break default: console.log("Unknown Operation. Use:") console.log(" set <user> - Generate DH keys and params for user") console.log(" enc <sender> <receiver> <file> - Encrypt file as sender to receiver") console.log(" dec <receiver> <cryptogram> - Decrypt cryptogram as receiver") } } main()
正确操作流程
- 生成Bob的密钥和参数:
node diffie.js set bob - 生成Alice的密钥和参数:
node diffie.js set alice - Alice加密消息给Bob:
会生成node diffie.js enc alice bob test_filetest_file.cryptogram,包含Alice的公钥和加密数据。 - Bob解密消息:
会生成node diffie.js dec bob test_file.cryptogramtest_file.cryptogram.dec,即原始明文。
关键修改说明
- 统一DH参数:生成密钥时同时保存素数p和生成元g,双方使用同一套参数初始化DH实例,确保共享密钥计算一致。
- 修正公钥读取逻辑:加密时读取接收方的公钥,解密时从加密文件中读取发送方的公钥。
- 符合协议要求:加密文件包含发送方公钥,满足非交互式DH交换的要求。
内容的提问来源于stack exchange,提问作者Rohac
相关产品推荐
相关产品推荐

