add support for x25519 and secp256r1 keys

This commit is contained in:
Craig Raw 2024-10-30 13:04:20 +02:00
parent 35bebe13bc
commit dba1a9a2be
3 changed files with 186 additions and 0 deletions

View file

@ -4,6 +4,8 @@ import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.protocol.ProtocolException; import com.sparrowwallet.drongo.protocol.ProtocolException;
import com.sparrowwallet.drongo.protocol.Ripemd160; import com.sparrowwallet.drongo.protocol.Ripemd160;
import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Sha256Hash;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
@ -15,6 +17,9 @@ import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*; import java.util.*;
public class Utils { public class Utils {
@ -338,6 +343,21 @@ public class Utils {
return Sha256Hash.hash(buffer.array()); return Sha256Hash.hash(buffer.array());
} }
public static byte[] getRawKeyBytesFromPKCS8(PrivateKey pkcs8Key) {
try {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Key.getEncoded());
PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(keySpec.getEncoded());
return privateKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded();
} catch(IOException e) {
throw new IllegalArgumentException("Error parsing private key", e);
}
}
public static byte[] getRawKeyBytesFromX509(PublicKey x509Key) {
SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(x509Key.getEncoded());
return spki.getPublicKeyData().getBytes();
}
public static class LexicographicByteArrayComparator implements Comparator<byte[]> { public static class LexicographicByteArrayComparator implements Comparator<byte[]> {
@Override @Override
public int compare(byte[] left, byte[] right) { public int compare(byte[] left, byte[] right) {

View file

@ -0,0 +1,39 @@
package com.sparrowwallet.drongo.crypto;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
public class Secp256r1Key {
private static final X9ECParameters CURVE_PARAMS = ECNamedCurveTable.getByName("P-256");
public static final ECDomainParameters CURVE;
private final ECPoint point;
static {
CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
}
public Secp256r1Key(byte[] publicKeyBytes) {
this.point = CURVE.getCurve().decodePoint(publicKeyBytes);
}
public boolean verify(byte[] challenge, byte[] challengeSignature) {
ECDSASigner signer = new ECDSASigner();
signer.init(false, new ECPublicKeyParameters(point, CURVE));
int halfLength = challengeSignature.length / 2;
byte[] r = new byte[halfLength];
byte[] s = new byte[halfLength];
System.arraycopy(challengeSignature, 0, r, 0, halfLength);
System.arraycopy(challengeSignature, halfLength, s, 0, halfLength);
return signer.verifySignature(challenge, new BigInteger(1, r), new BigInteger(1, s));
}
}

View file

@ -0,0 +1,127 @@
package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.X25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.XECPrivateKey;
import java.security.interfaces.XECPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Optional;
public class X25519Key {
private KeyPair keyPair;
private final AlgorithmParameterSpec ecSpec;
public X25519Key() {
try {
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("X25519");
this.keyPair = keyPairGenerator.generateKeyPair();
this.ecSpec = keyPairGenerator.generateKeyPair().getPrivate().getParams();
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public X25519Key(byte[] priv) {
this();
X25519PrivateKeyParameters privateKeyParams = new X25519PrivateKeyParameters(priv, 0);
X25519PublicKeyParameters publicKeyParams = privateKeyParams.generatePublicKey();
PrivateKey privateKey = new BouncyCastlePrivateKey(privateKeyParams);
PublicKey publicKey = new BouncyCastlePublicKey(publicKeyParams);
this.keyPair = new KeyPair(publicKey, privateKey);
}
public KeyPair getKeyPair() {
return keyPair;
}
public byte[] getRawPrivateKeyBytes() {
return Utils.getRawKeyBytesFromPKCS8(keyPair.getPrivate());
}
public byte[] getRawPublicKeyBytes() {
return Utils.getRawKeyBytesFromX509(keyPair.getPublic());
}
public class BouncyCastlePrivateKey implements XECPrivateKey {
private final X25519PrivateKeyParameters privateKeyParams;
BouncyCastlePrivateKey(X25519PrivateKeyParameters privateKeyParams) {
this.privateKeyParams = privateKeyParams;
}
@Override
public String getAlgorithm() {
return "X25519";
}
@Override
public String getFormat() {
return "RAW";
}
@Override
public byte[] getEncoded() {
return privateKeyParams.getEncoded();
}
@Override
public Optional<byte[]> getScalar() {
return Optional.of(getEncoded());
}
@Override
public AlgorithmParameterSpec getParams() {
return ecSpec;
}
}
public class BouncyCastlePublicKey implements XECPublicKey {
private final X25519PublicKeyParameters publicKeyParams;
BouncyCastlePublicKey(X25519PublicKeyParameters publicKeyParams) {
this.publicKeyParams = publicKeyParams;
}
@Override
public String getAlgorithm() {
return "X25519";
}
@Override
public String getFormat() {
return "X.509";
}
@Override
public byte[] getEncoded() {
try {
ASN1ObjectIdentifier algOid = new ASN1ObjectIdentifier("1.3.101.110");
AlgorithmIdentifier algId = new AlgorithmIdentifier(algOid);
SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algId, publicKeyParams.getEncoded());
return spki.getEncoded();
} catch(IOException e) {
throw new RuntimeException(e);
}
}
@Override
public BigInteger getU() {
return new BigInteger(1, publicKeyParams.getEncoded());
}
@Override
public AlgorithmParameterSpec getParams() {
return ecSpec;
}
}
}