Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.trilead.ssh2.crypto.dh;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
Expand All @@ -9,19 +8,18 @@
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.XECPrivateKeySpec;
import java.security.spec.XECPublicKeySpec;

import javax.crypto.KeyAgreement;

/**
* X25519 provider implementation using platform-native APIs (Java 11+/Android API 33+).
* X25519 provider implementation using platform-native APIs (Java 11+/Android
* API 33+).
*/
public class PlatformX25519Provider implements X25519Provider {
private static final String ALGORITHM = "X25519";
private static final NamedParameterSpec X25519_SPEC = new NamedParameterSpec(ALGORITHM);

private final KeyPairGenerator keyPairGenerator;
private final KeyFactory keyFactory;
Expand All @@ -48,18 +46,22 @@ public byte[] publicFromPrivate(byte[] privateKey) throws InvalidKeyException {
return pubKeyBytes;
}

private static final byte[] BASE_POINT = new byte[KEY_SIZE];
static {
BASE_POINT[0] = 9;
}

private void computePublicFromPrivate(byte[] privateKey, byte[] publicKey) throws InvalidKeyException {
try {
PrivateKey privKey = createPrivateKey(privateKey);
KeyAgreement ka = KeyAgreement.getInstance(ALGORITHM);
ka.init(privKey);

XECPublicKeySpec basePointSpec = new XECPublicKeySpec(X25519_SPEC, BigInteger.valueOf(9));
PublicKey basePoint = keyFactory.generatePublic(basePointSpec);
PublicKey basePoint = createPublicKey(BASE_POINT);
ka.doPhase(basePoint, true);
byte[] result = ka.generateSecret();
System.arraycopy(result, 0, publicKey, 0, KEY_SIZE);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
} catch (NoSuchAlgorithmException e) {
throw new InvalidKeyException("X25519 not available", e);
}
}
Expand All @@ -81,12 +83,19 @@ public byte[] computeSharedSecret(byte[] privateKey, byte[] publicKey) throws In
}

private static final byte[] PKCS8_PREFIX = {
0x30, 0x2e, // SEQUENCE (46 bytes)
0x02, 0x01, 0x00, // INTEGER 0 (version)
0x30, 0x05, // SEQUENCE (5 bytes)
0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519)
0x04, 0x22, // OCTET STRING (34 bytes)
0x04, 0x20 // OCTET STRING (32 bytes) - key follows
0x30, 0x2e, // SEQUENCE (46 bytes)
0x02, 0x01, 0x00, // INTEGER 0 (version)
0x30, 0x05, // SEQUENCE (5 bytes)
0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519)
0x04, 0x22, // OCTET STRING (34 bytes)
0x04, 0x20 // OCTET STRING (32 bytes) - key follows
};

private static final byte[] X509_PREFIX = {
0x30, 0x2a, // SEQUENCE (42 bytes)
0x30, 0x05, // SEQUENCE (5 bytes)
0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519)
0x03, 0x21, 0x00 // BIT STRING (33 bytes, 0 unused bits)
};

private PrivateKey createPrivateKey(byte[] keyBytes) throws InvalidKeyException {
Expand All @@ -103,8 +112,10 @@ private PrivateKey createPrivateKey(byte[] keyBytes) throws InvalidKeyException

private PublicKey createPublicKey(byte[] keyBytes) throws InvalidKeyException {
try {
BigInteger u = decodeLittleEndian(keyBytes);
XECPublicKeySpec spec = new XECPublicKeySpec(X25519_SPEC, u);
byte[] x509 = new byte[X509_PREFIX.length + KEY_SIZE];
System.arraycopy(X509_PREFIX, 0, x509, 0, X509_PREFIX.length);
System.arraycopy(keyBytes, 0, x509, X509_PREFIX.length, KEY_SIZE);
X509EncodedKeySpec spec = new X509EncodedKeySpec(x509);
return keyFactory.generatePublic(spec);
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException("Invalid public key", e);
Expand All @@ -129,11 +140,4 @@ private byte[] extractPrivateKeyBytes(PrivateKey privateKey) {
}
}

private static BigInteger decodeLittleEndian(byte[] bytes) {
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
reversed[i] = bytes[bytes.length - 1 - i];
}
return new BigInteger(1, reversed);
}
}
Loading