繁体   English   中英

Java 11 Curve25519 实现不像 Signal 的库

[英]Java 11 Curve25519 Implementation doesn't behave as Signal's libary

在 Java 11 中引入了 curve25519 内置实现。 由于我对此一无所知,并且最近才发现它,因此我正在使用Signal 的库 这是我在切换到 Java 11 的实现之前的代码:

private final Curve25519 CURVE_25519 = Curve25519.getInstance(Curve25519.JAVA);

public Curve25519KeyPair calculateRandomKeyPair() {
    return CURVE_25519.generateKeyPair();
}

public byte[] calculateSharedSecret(byte[] publicKey, byte[] privateKey) {
    return CURVE_25519.calculateAgreement(publicKey, privateKey);
}

这是我现在的代码:

private final String XDH = "XDH";
private final String CURVE = "X25519";

@SneakyThrows
public KeyPair calculateRandomKeyPair() {
    return KeyPairGenerator.getInstance(CURVE).generateKeyPair();
}

@SneakyThrows
public byte[] calculateSharedSecret(byte[] publicKeyBytes, XECPrivateKey privateKey) {
    var paramSpec = new NamedParameterSpec(CURVE);
    var keyFactory = KeyFactory.getInstance(XDH);

    var publicKeySpec = new XECPublicKeySpec(paramSpec, new BigInteger(publicKeyBytes));
    var publicKey = keyFactory.generatePublic(publicKeySpec);

    var keyAgreement = KeyAgreement.getInstance(XDH);
    keyAgreement.init(privateKey);
    keyAgreement.doPhase(publicKey, true);
    return keyAgreement.generateSecret();
}

显然,第二个实现不起作用,而第一个实现。 最初,我认为我做错了什么,所以我阅读了文档并检查了类似的答案,但是,由于我没有发现任何有用的信息,我决定进一步挖掘并尝试检查 Signal 的库和 Java 是否生成相同的公钥给私人的。 为此,我编写了以下代码段:

import org.whispersystems.curve25519.Curve25519;
import sun.security.ec.XECOperations;
import sun.security.ec.XECParameters;

import java.security.InvalidAlgorithmParameterException;
import java.security.spec.NamedParameterSpec;
import java.util.Arrays;

private static boolean generateJava11KeyPair() throws InvalidAlgorithmParameterException {
    var signalKeyPair = Curve25519.getInstance(Curve25519.JAVA).generateKeyPair();
    
    var signalPublicKey = signalKeyPair.getPublicKey();
    var params = XECParameters.get(InvalidAlgorithmParameterException::new, NamedParameterSpec.X25519);
    var ops = new XECOperations(params);
    var javaPublicKey = ops.computePublic(signalKeyPair.getPrivateKey().clone()).toByteArray();
    
    return Arrays.equals(signalPublicKey, javaPublicKey);
}

(Java 实现之后用于计算公钥的代码是从 sun.security.ec.XDHKeyPairGenerator 中提取的)

此方法打印 false,这意味着这两个实现实际上的行为方式不同。 在这一点上,我想知道这是一个 Java 错误还是我遗漏了什么。 提前致谢。

Bernstein 等人为 X25519(和 X448)公钥和私钥定义的编码是无符号固定长度 little-endian,而BigInteger.toByteArray()返回并被 ctor BigInteger(byte[])接受的表示是双补变长大端。 由于 255 位四舍五入到 32 字节,而备用位始终为零(对于 XDH),因此可以忽略符号差异,但其他的很重要。

JCA 确实使接口 class XECPrivateKey返回,并且相应的Spec接受,这些 forms,但对于XECPublicKey[Spec]它使用BigInteger 它确实使用 Bernstein forms 一致地用于(分别)由Key.getEncoded() ) 返回并被KeyFactory接受的“X509”和“PKCS8”编码,但这些元数据具有仅 XDH(或仅 Bernstein XDH)的元数据-and-EdDSA) 系统,如 X3DH 不使用。

AFAICS 你的选择是

  • 需要时,字节反转和(零)填充代码中的 JCA 公共值,或者
  • 使用Key.getEncoded()并解析特定于算法的部分,或者反过来构建算法通用结构以作为X509EncodedKeySpec传递给KeyFactory.getInstance("Xblah")

过去曾针对其他算法询问过第二种方法:“传统”(X9 风格)EC——尤其是比特币和相关硬币的 secp256k1,它通常只使用没有元数据的原始 X9/SECG 数据——和RSA,其中一些系统使用原始 PKCS1 格式(这里的私钥比公钥更常见); 如果您愿意,我可以找到一些几乎重复的内容来说明该方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM