You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复非交互式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密钥交换失败、共享密钥不匹配:

  1. DH参数不统一:每次调用crypto.createDiffieHellman(2048)都会生成新的素数(p)和生成元(g),Alice和Bob的DH参数完全不同,无法计算出相同的共享密钥。
  2. 公钥读取逻辑错误:加密/解密时读取的是自身公钥,而非对方的公钥(比如Alice加密应该读取Bob的公钥,而不是自己的)。
  3. 未按协议要求包含发送方公钥:生成的加密文件里没有包含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()

正确操作流程

  1. 生成Bob的密钥和参数:
    node diffie.js set bob
    
  2. 生成Alice的密钥和参数:
    node diffie.js set alice
    
  3. Alice加密消息给Bob:
    node diffie.js enc alice bob test_file
    
    会生成test_file.cryptogram,包含Alice的公钥和加密数据。
  4. Bob解密消息:
    node diffie.js dec bob test_file.cryptogram
    
    会生成test_file.cryptogram.dec,即原始明文。

关键修改说明

  • 统一DH参数:生成密钥时同时保存素数p和生成元g,双方使用同一套参数初始化DH实例,确保共享密钥计算一致。
  • 修正公钥读取逻辑:加密时读取接收方的公钥,解密时从加密文件中读取发送方的公钥。
  • 符合协议要求:加密文件包含发送方公钥,满足非交互式DH交换的要求。

内容的提问来源于stack exchange,提问作者Rohac

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:07:04