[英]Java 11 Curve25519 Implementation doesn't behave as Signal's libary
In Java 11 a curve25519 built-in implementation was introduced.在 Java 11 中引入了 curve25519 内置实现。 As I had no idea of this, and only discovered it recently, I was using a library from Signal .
由于我对此一无所知,并且最近才发现它,因此我正在使用Signal 的库。 This was my code before I switched to Java 11's implementation:
这是我在切换到 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);
}
And this is my code now:这是我现在的代码:
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();
}
Obviously, the second implementation doesn't work while the first one does.显然,第二个实现不起作用,而第一个实现。 Initially, I thought I was doing something wrong so I read the documentation and checked similar answers, though, as I didn't find anything helpful, I decided to dig further and tried to check if both Signal's library and Java generate the same public key given the private one.
最初,我认为我做错了什么,所以我阅读了文档并检查了类似的答案,但是,由于我没有发现任何有用的信息,我决定进一步挖掘并尝试检查 Signal 的库和 Java 是否生成相同的公钥给私人的。 To do this I wrote this snippet:
为此,我编写了以下代码段:
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);
}
(the code used to calculate the public key following Java's implementation was extracted from sun.security.ec.XDHKeyPairGenerator) (Java 实现之后用于计算公钥的代码是从 sun.security.ec.XDHKeyPairGenerator 中提取的)
This method prints false, which means that the two implementations actually don't behave in the same way.此方法打印 false,这意味着这两个实现实际上的行为方式不同。 At this point, I'm wondering if this is a Java bug or if I'm missing something.
在这一点上,我想知道这是一个 Java 错误还是我遗漏了什么。 Thanks in advance.
提前致谢。
The encoding defined by Bernstein et al for X25519 (and X448) keys both public and private is unsigned fixed-length little-endian, while the representation returned by BigInteger.toByteArray()
and accepted by ctor BigInteger(byte[])
is twos-complement variable-length big-endian. Bernstein 等人为 X25519(和 X448)公钥和私钥定义的编码是无符号固定长度 little-endian,而
BigInteger.toByteArray()
返回并被 ctor BigInteger(byte[])
接受的表示是双补变长大端。 Since 255 bits rounds up to 32 bytes with a spare bit that is always zero (for XDH) the signedness difference can be ignored there, but the others matter.由于 255 位四舍五入到 32 字节,而备用位始终为零(对于 XDH),因此可以忽略符号差异,但其他的很重要。
JCA did make the inteface class XECPrivateKey
return, and the corresponding Spec
accept, these forms, but for XECPublicKey[Spec]
it uses BigInteger
. JCA 确实使接口 class
XECPrivateKey
返回,并且相应的Spec
接受,这些 forms,但对于XECPublicKey[Spec]
它使用BigInteger
。 It does use the Bernstein forms consistently for (both) the "X509" and "PKCS8" encodings (respectively) returned by Key.getEncoded()
and accepted by a KeyFactory
, but those have metadata that XDH-only (or Bernstein-only XDH-and-EdDSA) systems like X3DH don't use.它确实使用 Bernstein forms 一致地用于(分别)由
Key.getEncoded()
) 返回并被KeyFactory
接受的“X509”和“PKCS8”编码,但这些元数据具有仅 XDH(或仅 Bernstein XDH)的元数据-and-EdDSA) 系统,如 X3DH 不使用。
AFAICS your choices are AFAICS 你的选择是
Key.getEncoded()
and parse the algorithm-specific part or conversely build the algorithm-generic structure to pass as X509EncodedKeySpec
to KeyFactory.getInstance("Xblah")
.Key.getEncoded()
并解析特定于算法的部分,或者反过来构建算法通用结构以作为X509EncodedKeySpec
传递给KeyFactory.getInstance("Xblah")
。 The second approach has been asked about in the past for other algorithms: 'traditional' (X9-style) EC -- especially secp256k1 for bitcoin and related coins, which generally use only the raw-X9/SECG data with no metadata -- and RSA where a few systems use the raw-PKCS1 formats (here more commonly for privatekey than publickey);过去曾针对其他算法询问过第二种方法:“传统”(X9 风格)EC——尤其是比特币和相关硬币的 secp256k1,它通常只使用没有元数据的原始 X9/SECG 数据——和RSA,其中一些系统使用原始 PKCS1 格式(这里的私钥比公钥更常见); if you want I can find some near-duplicates to illustrate the approach.
如果您愿意,我可以找到一些几乎重复的内容来说明该方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.