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

如何用BouncyCastle实现X25519 ECDH结合HSalsa20生成NaCl共享密钥

问题:使用BouncyCastle实现libsodium认证加密时的HSalsa20处理问题

我尝试用BouncyCastle实现libsodium的「认证加密」方案,最初直接通过X25519密钥协商获取javax.crypto.SecretKey对象,代码如下:

public SecretKey generateSecretKey(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException {
    KeyAgreement keyAgreement = KeyAgreement.getInstance("X25519", new BouncyCastleProvider());
    keyAgreement.init(privateKey);
    keyAgreement.doPhase(publicKey, true);
    return keyAgreement.generateSecret("X25519");
}

随后将该secretKey传入org.bouncycastle.crypto.engines.XSalsa20Engine:

XSalsa20Engine xSalsa20Engine = new XSalsa20Engine();
xSalsa20Engine.init(true, new ParametersWithIV(new KeyParameter(secretKey), nonce));

但查阅《Cryptography in NaCl》文档及测试后发现此方式错误,文档描述:

In the next step, described in Section 7, Alice will convert this 32-byte shared
secret k into a 32-byte string HSalsa20(k, 0), which is then used to encrypt and
authenticate packets. Bob similarly uses HSalsa20(k, 0) to verify and decrypt
the packets. No other use is made of k. One can thus view HSalsa20(k, 0) as the
shared secret rather than k.

X25519交换得到的初始密钥必须先经过一轮HSalsa20(k,0)处理,得到最终的共享密钥才能用于加密。但BouncyCastle没有独立的HSalsa20实现,该算法逻辑隐含在(X)Salsa20Engine中,请问如何对初始SecretKey执行一轮HSalsa20处理?


解决方案

HSalsa20本质是Salsa20算法的核心变换,当输入16字节全0的IV,并取密钥流的前32字节时,结果就等价于HSalsa20(k,0)的计算结果。可以借助BouncyCastle的XSalsa20Engine间接实现,具体代码如下:

import org.bouncycastle.crypto.engines.XSalsa20Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import javax.crypto.SecretKey;

// 1. 提取X25519协商得到的原始32字节密钥材料
byte[] sharedSecretK = secretKey.getEncoded();

// 2. 准备HSalsa20要求的全0 16字节IV(对应文档中的"0")
byte[] zeroIV = new byte[16]; // 默认初始化就是全0

// 3. 初始化XSalsa20Engine,模拟HSalsa20计算
XSalsa20Engine salsaEngine = new XSalsa20Engine();
// 第一个参数true表示加密模式(这里只是生成密钥流,模式不影响结果)
salsaEngine.init(true, new ParametersWithIV(new KeyParameter(sharedSecretK), zeroIV));

// 4. 生成32字节的HSalsa20输出,这就是最终可用的共享密钥
byte[] finalSharedKey = new byte[32];
// 输入空字节数组,直接读取密钥流的前32字节
salsaEngine.processBytes(new byte[0], 0, 0, finalSharedKey, 0);

原理说明

BouncyCastle的XSalsa20Engine内部实现了Salsa20的核心变换逻辑,而HSalsa20正是这个核心变换的一个特定用法:使用16字节全0的nonce,输出32字节的结果。通过初始化引擎后读取前32字节密钥流,就能得到HSalsa20(k,0)的计算结果,完全符合NaCl文档的要求。

后续加密时,直接使用finalSharedKey作为XSalsa20的密钥即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 07:01:03