org.jmrtd.Util Maven / Gradle / Ivy
/*
* JMRTD - A Java API for accessing machine readable travel documents.
*
* Copyright (C) 2006 - 2016 The JMRTD team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id: Util.java 1635 2016-09-22 13:06:34Z martijno $
*/
package org.jmrtd;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECField;
import java.security.spec.ECFieldF2m;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.DHParameter;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X962NamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.DHParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.jmrtd.lds.SecurityInfo;
import org.jmrtd.lds.icao.MRZInfo;
import net.sf.scuba.tlv.TLVOutputStream;
import net.sf.scuba.util.Hex;
/* FIXME: Move some of these to specific protocol classes. */
/**
* Some static helper functions. Mostly dealing with low-level crypto.
*
* @deprecated The visibility of this class will be changed to package.
*
* @author The JMRTD team ([email protected])
*
* @version $Revision: 1635 $
*/
public class Util {
private static final Logger LOGGER = Logger.getLogger("org.jmrtd");
/** Mode for KDF. */
public static final int
ENC_MODE = 1,
MAC_MODE = 2,
PACE_MODE = 3;
private static final Provider BC_PROVIDER = JMRTDSecurityProvider.getBouncyCastleProvider();
private Util() {
}
/**
* Derives the ENC or MAC key for BAC from the keySeed.
*
* @param keySeed the key seed.
* @param mode either ENC_MODE
or MAC_MODE
*
* @return the key
*
* @throws GeneralSecurityException on security error
*/
public static SecretKey deriveKey(byte[] keySeed, int mode) throws GeneralSecurityException {
return deriveKey(keySeed, "DESede", 128, mode);
}
/**
* Derives the ENC or MAC key for BAC or PACE
*
* @param keySeed the key seed.
* @param cipherAlgName either AES or DESede
* @param keyLength key length in bits
* @param mode either {@code ENC_MODE}, {@code MAC_MODE}, or {@code PACE_MODE}
*
* @return the key.
*
* @throws GeneralSecurityException on security error
*/
public static SecretKey deriveKey(byte[] keySeed, String cipherAlgName, int keyLength, int mode) throws GeneralSecurityException {
return deriveKey(keySeed, cipherAlgName, keyLength, null, mode);
}
/**
* Derives a shared key.
*
* @param keySeed the shared secret, as octets
* @param cipherAlg in Java mnemonic notation (for example "DESede", "AES")
* @param keyLength length in bits
* @param nonce optional nonce or null
* @param counter counter or mode
*
* @return the derived key
*
* @throws GeneralSecurityException if something went wrong
*/
public static SecretKey deriveKey(byte[] keySeed, String cipherAlg, int keyLength, byte[] nonce, int counter) throws GeneralSecurityException {
String digestAlg = inferDigestAlgorithmFromCipherAlgorithmForKeyDerivation(cipherAlg, keyLength);
LOGGER.info("DEBUG: key derivation uses digestAlg = " + digestAlg);
MessageDigest digest = MessageDigest.getInstance(digestAlg);
digest.reset();
digest.update(keySeed);
if (nonce != null) {
digest.update(nonce);
}
digest.update(new byte[] { 0x00, 0x00, 0x00, (byte)counter });
byte[] hashResult = digest.digest();
byte[] keyBytes = null;
if ("DESede".equalsIgnoreCase(cipherAlg) || "3DES".equalsIgnoreCase(cipherAlg)) {
/* TR-SAC 1.01, 4.2.1. */
switch(keyLength) {
case 112: /* Fall through. */
case 128:
keyBytes = new byte[24];
System.arraycopy(hashResult, 0, keyBytes, 0, 8); /* E (octets 1 to 8) */
System.arraycopy(hashResult, 8, keyBytes, 8, 8); /* D (octets 9 to 16) */
System.arraycopy(hashResult, 0, keyBytes, 16, 8); /* E (again octets 1 to 8, i.e. 112-bit 3DES key) */
break;
default:
throw new IllegalArgumentException("KDF can only use DESede with 128-bit key length");
}
} else if ("AES".equalsIgnoreCase(cipherAlg) || cipherAlg.startsWith("AES")) {
/* TR-SAC 1.01, 4.2.2. */
switch(keyLength) {
case 128:
keyBytes = new byte[16]; /* NOTE: 128 = 16 * 8 */
System.arraycopy(hashResult, 0, keyBytes, 0, 16);
break;
case 192:
keyBytes = new byte[24]; /* NOTE: 192 = 24 * 8 */
System.arraycopy(hashResult, 0, keyBytes, 0, 24);
break;
case 256:
keyBytes = new byte[32]; /* NOTE: 256 = 32 * 8 */
System.arraycopy(hashResult, 0, keyBytes, 0, 32);
break;
default:
throw new IllegalArgumentException("KDF can only use AES with 128-bit, 192-bit key or 256-bit length, found: " + keyLength + "-bit key length");
}
}
return new SecretKeySpec(keyBytes, cipherAlg);
}
/**
* Computes the static key seed, based on information from the MRZ.
*
* @param documentNumber a string containing the document number
* @param dateOfBirth a string containing the date of birth (YYMMDD)
* @param dateOfExpiry a string containing the date of expiry (YYMMDD)
* @param digestAlg a Java mnemonic algorithm string to indicate the digest algorithm (typically SHA-1)
* @param doTruncate whether to truncate the resulting output to 16 bytes
*
* @return a byte array of length 16 containing the key seed
*
* @throws GeneralSecurityException on security error
*/
public static byte[] computeKeySeed(String documentNumber, String dateOfBirth, String dateOfExpiry, String digestAlg, boolean doTruncate) throws GeneralSecurityException {
/* Check digits... */
byte[] documentNumberCheckDigit = { (byte)MRZInfo.checkDigit(documentNumber) };
byte[] dateOfBirthCheckDigit = { (byte)MRZInfo.checkDigit(dateOfBirth) };
byte[] dateOfExpiryCheckDigit = { (byte)MRZInfo.checkDigit(dateOfExpiry) };
MessageDigest shaDigest = MessageDigest.getInstance(digestAlg);
shaDigest.update(getBytes(documentNumber));
shaDigest.update(documentNumberCheckDigit);
shaDigest.update(getBytes(dateOfBirth));
shaDigest.update(dateOfBirthCheckDigit);
shaDigest.update(getBytes(dateOfExpiry));
shaDigest.update(dateOfExpiryCheckDigit);
byte[] hash = shaDigest.digest();
if (doTruncate) {
/* FIXME: truncate to 16 byte only for BAC with 3DES. Also for PACE and/or AES? -- MO */
byte[] keySeed = new byte[16];
System.arraycopy(hash, 0, keySeed, 0, 16);
return keySeed;
} else {
return hash;
}
}
/**
* Pads the input in
according to ISO9797-1 padding method 2,
* using the given block size.
*
* @param in input
* @param blockSize the block size
*
* @return padded bytes
*/
public static byte[] pad(/*@ non_null */ byte[] in, int blockSize) {
return pad(in, 0, in.length, blockSize);
}
/**
* Pads the input {@code in} indicated by {@code offset} and {@code length}
* according to ISO9797-1 padding method 2, using the given block size in {@code blockSize}.
*
* @param in input
* @param offset the offset
* @param length the length
* @param blockSize the block size
*
* @return padded bytes
*/
public static byte[] pad(/*@ non_null */ byte[] in, int offset, int length, int blockSize) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(in, offset, length);
out.write((byte)0x80);
while (out.size() % blockSize != 0) {
out.write((byte)0x00);
}
return out.toByteArray();
}
/**
* Unpads the input {@code in} according to ISO9797-1 padding method 2.
*
* @param in the input
*
* @return the unpadded bytes
*/
public static byte[] unpad(byte[] in) throws BadPaddingException {
int i = in.length - 1;
while (i >= 0 && in[i] == 0x00) {
i--;
}
if ((in[i] & 0xFF) != 0x80) {
throw new BadPaddingException("Expected constant 0x80, found 0x" + Integer.toHexString((in[i] & 0x000000FF)) + "\nDEBUG: in = " + Hex.bytesToHexString(in) + ", index = " + i);
}
byte[] out = new byte[i];
System.arraycopy(in, 0, out, 0, i);
return out;
}
/**
* Recovers the M1 part of the message sent back by the AA protocol
* (INTERNAL AUTHENTICATE command). The algorithm is described in
* ISO 9796-2:2002 9.3.
*
* Based on code by Ronny ([email protected]) who presumably ripped this
* from Bouncy Castle.
*
* @param digestLength should be 20
* @param plaintext response from card, already 'decrypted' (using the
* AA public key)
*
* @return the m1 part of the message
*/
public static byte[] recoverMessage(int digestLength, byte[] plaintext) {
if (plaintext == null || plaintext.length < 1) {
throw new IllegalArgumentException("Plaintext too short to recover message");
}
/* Header. */
if (((plaintext[0] & 0xC0) ^ 0x40) != 0) {
/*
* First two bits (working from left to right) should be '01'.
* NOTE: 0xC0 = 1100 0000, 0x40 = 0100 0000.
*/
throw new NumberFormatException("Could not get M1");
}
if ((plaintext[0] & 0x20) == 0) {
/* Third bit (working from left to right) should be '1' for partial recovery. */
throw new NumberFormatException("Could not get M1");
}
/* Trailer. */
if (((plaintext[plaintext.length - 1] & 0xF) ^ 0xC) != 0) {
/*
* Trailer.
* NOTE: 0xF = 0000 1111, 0xC = 0000 1100.
*/
throw new NumberFormatException("Could not get M1");
}
if (((plaintext[plaintext.length - 1] & 0xFF) ^ 0xBC) != 0) {
/* NOTE: 0xBC = 1011 1100. */
throw new NumberFormatException("Could not get M1");
}
/* Padding to the left of M1, find out how long. */
int paddingLength = 0;
for (; paddingLength < plaintext.length; paddingLength++) {
// 0x0A = 0000 1010
if (((plaintext[paddingLength] & 0x0F) ^ 0x0A) == 0) {
break;
}
}
int messageOffset = paddingLength + 1;
int paddedMessageLength = plaintext.length - 1 - digestLength;
int messageLength = paddedMessageLength - messageOffset;
/* There must be at least one byte of message string. */
if (messageLength <= 0) {
throw new NumberFormatException("Could not get M1");
}
/* TODO: If we contain the whole message as well, check the hash of that. */
byte[] recoveredMessage = new byte[messageLength];
System.arraycopy(plaintext, messageOffset, recoveredMessage, 0, messageLength);
return recoveredMessage;
}
/**
* For ECDSA the EAC 1.11 specification requires the signature to be stripped down from any ASN.1 wrappers, as so.
*
* @param signedData signed data
* @param keySize key size
*
* @return signature without wrappers
*
* @throws IOException on error
*/
public static byte[] getRawECDSASignature(byte[] signedData, int keySize) throws IOException {
ASN1InputStream asn1In = new ASN1InputStream(signedData);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ASN1Sequence obj = (ASN1Sequence)asn1In.readObject();
Enumeration e = obj.getObjects();
while (e.hasMoreElements()) {
ASN1Integer i = (ASN1Integer)e.nextElement();
byte[] t = i.getValue().toByteArray();
t = alignKeyDataToSize(t, keySize);
out.write(t);
}
out.flush();
return out.toByteArray();
} finally {
asn1In.close();
out.close();
}
}
public static byte[] alignKeyDataToSize(byte[] keyData, int size) {
byte[] result = new byte[size];
if (keyData.length < size) { size = keyData.length; }
System.arraycopy(keyData, keyData.length - size, result, result.length - size, size);
return result;
}
/**
* Converts an integer to an octet string.
* Based on BSI TR 03111 Section 3.1.2.
*
* @param val positive integer
* @param length length
*
* @return octet string
*/
public static byte[] i2os(BigInteger val, int length) {
BigInteger base = BigInteger.valueOf(256);
byte[] result = new byte[length];
for (int i = 0; i < length; i++) {
BigInteger remainder = val.mod(base);
val = val.divide(base);
result[length - 1 - i] = (byte)remainder.intValue();
}
return result;
}
/**
* Converts an integer to an octet string.
*
* @param val positive integer
* @return octet string
*/
public static byte[] i2os(BigInteger val) {
/* FIXME: Quick hack. What if val < 0? -- MO */
/* Do something with: int sizeInBytes = val.bitLength() / Byte.SIZE; */
int sizeInNibbles = val.toString(16).length();
if (sizeInNibbles % 2 != 0) { sizeInNibbles++; }
int length = sizeInNibbles / 2;
return i2os(val, length);
}
/**
* Converts an octet string to an integer.
* Based on BSI TR 03111 Section 3.1.2.
*
* @param bytes octet string
*
* @return positive integer
*/
public static BigInteger os2i(byte[] bytes) {
if (bytes == null) { throw new IllegalArgumentException(); }
return os2i(bytes, 0, bytes.length);
}
/**
* Converts an octet string to an integer.
* Based on BSI TR 03111 Section 3.1.2.
*
* @param bytes octet string
* @param offset offset of octet string
* @param length length of octet string
*
* @return positive integer
*/
public static BigInteger os2i(byte[] bytes, int offset, int length) {
if (bytes == null) { throw new IllegalArgumentException(); }
BigInteger result = BigInteger.ZERO;
BigInteger base = BigInteger.valueOf(256);
for (int i = offset; i < offset + length; i++) {
result = result.multiply(base);
result = result.add(BigInteger.valueOf(bytes[i] & 0xFF));
}
return result;
}
/**
* Convert an octet string to field element via OS2FE as specified in BSI TR-03111.
*
* @param bytes octet string
* @param p modulus
*
* @return positive integer
*/
public static BigInteger os2fe(byte[] bytes, BigInteger p) {
return Util.os2i(bytes).mod(p);
}
/* Best effort. FIXME: test and improve. -- MO */
/**
* Infers a digest algorithm mnemonic from a signature algorithm mnemonic.
*
* @param signatureAlgorithm a signature algorithm
* @return a digest algorithm, or null if inference failed
*/
public static String inferDigestAlgorithmFromSignatureAlgorithm(String signatureAlgorithm) {
if (signatureAlgorithm == null) { throw new IllegalArgumentException(); }
String digestAlgorithm = null;
String signatureAlgorithmToUppercase = signatureAlgorithm.toUpperCase();
if (signatureAlgorithmToUppercase.contains("WITH")) {
String[] components = signatureAlgorithmToUppercase.split("WITH");
digestAlgorithm = components[0];
}
if ("SHA1".equalsIgnoreCase(digestAlgorithm)) { digestAlgorithm = "SHA-1"; }
if ("SHA224".equalsIgnoreCase(digestAlgorithm)) { digestAlgorithm = "SHA-224"; }
if ("SHA256".equalsIgnoreCase(digestAlgorithm)) { digestAlgorithm = "SHA-256"; }
if ("SHA384".equalsIgnoreCase(digestAlgorithm)) { digestAlgorithm = "SHA-384"; }
if ("SHA512".equalsIgnoreCase(digestAlgorithm)) { digestAlgorithm = "SHA-512"; }
return digestAlgorithm;
}
public static String inferDigestAlgorithmFromCipherAlgorithmForKeyDerivation(String cipherAlg, int keyLength) {
if (cipherAlg == null) { throw new IllegalArgumentException(); }
if ("DESede".equals(cipherAlg) || "AES-128".equals(cipherAlg)) { return "SHA-1"; }
if ("AES".equals(cipherAlg) && keyLength == 128) { return "SHA-1"; }
if ("AES-256".equals(cipherAlg) || "AES-192".equals(cipherAlg)) { return "SHA-256"; }
if ("AES".equals(cipherAlg) && (keyLength == 192 || keyLength == 256)) { return "SHA-256"; }
throw new IllegalArgumentException("Unsupported cipher algorithm or key length \"" + cipherAlg + "\", " + keyLength);
}
public static DHParameterSpec toExplicitDHParameterSpec(DHParameters params) {
BigInteger p = params.getP();
BigInteger generator = params.getG();
int order = (int)params.getL();
return new DHParameterSpec(p, generator, order);
}
/**
* The public key algorithm (like RSA or) with some extra information (like 1024 bits).
*
* @param publicKey a public key
*
* @return the algorithm
*/
public static String getDetailedPublicKeyAlgorithm(PublicKey publicKey) {
String algorithm = publicKey.getAlgorithm();
if (publicKey instanceof RSAPublicKey) {
RSAPublicKey rsaPublicKey = (RSAPublicKey)publicKey;
int bitLength = rsaPublicKey.getModulus().bitLength();
algorithm += " [" + bitLength + " bit]";
} else if (publicKey instanceof ECPublicKey) {
ECPublicKey ecPublicKey = (ECPublicKey)publicKey;
ECParameterSpec ecParams = ecPublicKey.getParams();
String name = getCurveName(ecParams);
if (name != null) {
algorithm += " [" + name + "]";
}
}
return algorithm;
}
public static String getDetailedPrivateKeyAlgorithm(PrivateKey privateKey) {
String algorithm = privateKey.getAlgorithm();
if (privateKey instanceof RSAPrivateKey) {
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey)privateKey;
int bitLength = rsaPrivateKey.getModulus().bitLength();
algorithm += " [" + bitLength + " bit]";
} else if (privateKey instanceof ECPrivateKey) {
ECPrivateKey ecPrivateKey = (ECPrivateKey)privateKey;
ECParameterSpec ecParams = ecPrivateKey.getParams();
String name = getCurveName(ecParams);
if (name != null) {
algorithm += " [" + name + "]";
}
}
return algorithm;
}
/**
* Gets the curve name if known (or {@code null}).
*
* @param params an specification of the curve
*
* @return the name
*/
public static String getCurveName(ECParameterSpec params) {
org.bouncycastle.jce.spec.ECNamedCurveSpec namedECParams = toNamedCurveSpec(params);
if (namedECParams == null) { return null; }
return namedECParams.getName();
}
public static ECParameterSpec toExplicitECParameterSpec(ECNamedCurveParameterSpec parameterSpec) {
return toExplicitECParameterSpec(toECNamedCurveSpec(parameterSpec));
}
/**
* Translates (named) curve spec to JCA compliant explicit param spec.
*
* @param params an EC parameter spec, possibly named
*
* @return another spec not name based
*/
public static ECParameterSpec toExplicitECParameterSpec(ECParameterSpec params) {
try {
ECPoint g = params.getGenerator();
BigInteger n = params.getOrder(); // Order, order
int h = params.getCofactor(); // co-factor
EllipticCurve curve = params.getCurve();
BigInteger a = curve.getA();
BigInteger b = curve.getB();
ECField field = curve.getField();
if (field instanceof ECFieldFp) {
BigInteger p = ((ECFieldFp)field).getP();
ECField resultField = new ECFieldFp(p);
EllipticCurve resultCurve = new EllipticCurve(resultField, a, b);
ECParameterSpec resultParams = new ECParameterSpec(resultCurve, g, n, h);
return resultParams;
} else if (field instanceof ECFieldF2m) {
int m = ((ECFieldF2m)field).getM();
ECField resultField = new ECFieldF2m(m);
EllipticCurve resultCurve = new EllipticCurve(resultField, a, b);
ECParameterSpec resultParams = new ECParameterSpec(resultCurve, g, n, h);
return resultParams;
} else {
LOGGER.warning("Could not make named EC param spec explicit");
return params;
}
} catch (Exception e) {
LOGGER.warning("Could not make named EC param spec explicit");
return params;
}
}
private static org.bouncycastle.jce.spec.ECNamedCurveSpec toNamedCurveSpec(ECParameterSpec ecParamSpec) {
if (ecParamSpec == null) { return null; }
if (ecParamSpec instanceof org.bouncycastle.jce.spec.ECNamedCurveSpec) { return (org.bouncycastle.jce.spec.ECNamedCurveSpec)ecParamSpec; }
@SuppressWarnings("unchecked")
List names = (List)Collections.list(ECNamedCurveTable.getNames());
List namedSpecs = new ArrayList();
for (String name: names) {
org.bouncycastle.jce.spec.ECNamedCurveSpec namedSpec = toECNamedCurveSpec(ECNamedCurveTable.getParameterSpec(name));
if (namedSpec.getCurve().equals(ecParamSpec.getCurve())
&& namedSpec.getGenerator().equals(ecParamSpec.getGenerator())
&& namedSpec.getOrder().equals(ecParamSpec.getOrder())
&& namedSpec.getCofactor() == ecParamSpec.getCofactor()
) {
namedSpecs.add(namedSpec);
}
}
if (namedSpecs.size() == 0) {
// throw new IllegalArgumentException("No named curve found");
return null;
} else if (namedSpecs.size() == 1) {
return namedSpecs.get(0);
} else {
return namedSpecs.get(0);
}
}
/**
* Translates internal BC named curve spec to BC provided JCA compliant named curve spec.
*
* @param namedParamSpec a named EC parameter spec
*
* @return a JCA compliant named EC parameter spec
*/
public static org.bouncycastle.jce.spec.ECNamedCurveSpec toECNamedCurveSpec(org.bouncycastle.jce.spec.ECNamedCurveParameterSpec namedParamSpec) {
String name = namedParamSpec.getName();
org.bouncycastle.math.ec.ECCurve curve = namedParamSpec.getCurve();
org.bouncycastle.math.ec.ECPoint generator = namedParamSpec.getG();
BigInteger order = namedParamSpec.getN();
BigInteger coFactor = namedParamSpec.getH();
byte[] seed = namedParamSpec.getSeed();
return new org.bouncycastle.jce.spec.ECNamedCurveSpec(name, curve, generator, order, coFactor, seed);
}
/*
* NOTE: Woj, I moved this here from DG14File, seemed more appropriate here. -- MO
* FIXME: Do we still need this now that we have reconstructPublicKey? -- MO
*
* Woj says: Here we need to some hocus-pokus, the EAC specification require for
* all the key information to include the domain parameters explicitly. This is
* not what Bouncy Castle does by default. But we first have to check if this is
* the case.
*/
public static SubjectPublicKeyInfo toSubjectPublicKeyInfo(PublicKey publicKey) {
try {
String algorithm = publicKey.getAlgorithm();
if ("EC".equals(algorithm) || "ECDH".equals(algorithm) || (publicKey instanceof ECPublicKey)) {
ASN1InputStream asn1In = new ASN1InputStream(publicKey.getEncoded());
SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo((ASN1Sequence)asn1In.readObject());
asn1In.close();
AlgorithmIdentifier algorithmIdentifier = subjectPublicKeyInfo.getAlgorithm();
String algOID = algorithmIdentifier.getAlgorithm().getId();
if (!SecurityInfo.ID_EC_PUBLIC_KEY.equals(algOID)) {
throw new IllegalStateException("Was expecting id-ecPublicKey (" + SecurityInfo.ID_EC_PUBLIC_KEY_TYPE + "), found " + algOID);
}
ASN1Primitive derEncodedParams = algorithmIdentifier.getParameters().toASN1Primitive();
X9ECParameters params = null;
if (derEncodedParams instanceof ASN1ObjectIdentifier) {
ASN1ObjectIdentifier paramsOID = (ASN1ObjectIdentifier)derEncodedParams;
/* It's a named curve from X9.62. */
params = X962NamedCurves.getByOID(paramsOID);
if (params == null) { throw new IllegalStateException("Could not find X9.62 named curve for OID " + paramsOID.getId()); }
/* Reconstruct the parameters. */
org.bouncycastle.math.ec.ECPoint generator = params.getG();
org.bouncycastle.math.ec.ECCurve curve = generator.getCurve();
generator = curve.createPoint(generator.getX().toBigInteger(), generator.getY().toBigInteger(), false);
params = new X9ECParameters(params.getCurve(), generator, params.getN(), params.getH(), params.getSeed());
} else {
/* It's not a named curve, we can just return the decoded public key info. */
return subjectPublicKeyInfo;
}
if (publicKey instanceof org.bouncycastle.jce.interfaces.ECPublicKey) {
org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = (org.bouncycastle.jce.interfaces.ECPublicKey)publicKey;
AlgorithmIdentifier id = new AlgorithmIdentifier(subjectPublicKeyInfo.getAlgorithm().getAlgorithm(), params.toASN1Primitive());
org.bouncycastle.math.ec.ECPoint q = ecPublicKey.getQ();
/* FIXME: investigate the compressed versus uncompressed point issue. What is allowed in TR03110? -- MO */
// In case we would like to compress the point:
// p = p.getCurve().createPoint(p.getX().toBigInteger(), p.getY().toBigInteger(), true);
subjectPublicKeyInfo = new SubjectPublicKeyInfo(id, q.getEncoded());
return subjectPublicKeyInfo;
} else {
return subjectPublicKeyInfo;
}
} else if ("DH".equals(algorithm) || (publicKey instanceof DHPublicKey)) {
DHPublicKey dhPublicKey = (DHPublicKey)publicKey;
DHParameterSpec dhSpec = dhPublicKey.getParams();
return new SubjectPublicKeyInfo(
new AlgorithmIdentifier(EACObjectIdentifiers.id_PK_DH,
new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL()).toASN1Primitive()),
new ASN1Integer(dhPublicKey.getY()));
} else {
throw new IllegalArgumentException("Unrecognized key type, found " + publicKey.getAlgorithm() + ", should be DH or ECDH");
}
} catch (Exception e) {
LOGGER.severe("Exception: " + e.getMessage());
return null;
}
}
public static PublicKey toPublicKey(SubjectPublicKeyInfo subjectPublicKeyInfo) {
try {
byte[] encodedPublicKeyInfoBytes = subjectPublicKeyInfo.getEncoded(ASN1Encoding.DER);
KeySpec keySpec = new X509EncodedKeySpec(encodedPublicKeyInfoBytes);
try {
KeyFactory factory = KeyFactory.getInstance("DH");
return factory.generatePublic(keySpec);
} catch (GeneralSecurityException gse) {
KeyFactory factory = KeyFactory.getInstance("EC", BC_PROVIDER);
return factory.generatePublic(keySpec);
}
} catch (GeneralSecurityException gse2) {
LOGGER.severe("Exception: " + gse2.getMessage());
return null;
} catch (Exception ioe) {
LOGGER.severe("Exception: " + ioe.getMessage());
return null;
}
}
/**
* Reconstructs the public key to use explicit domain params for EC public keys
*
* @param publicKey the public key
*
* @return the same public key (if not EC or error), or a reconstructed one (if EC)
*/
public static PublicKey reconstructPublicKey(PublicKey publicKey) {
if (!(publicKey instanceof ECPublicKey)) { return publicKey; }
try {
ECPublicKey ecPublicKey = (ECPublicKey)publicKey;
ECPoint w = ecPublicKey.getW();
ECParameterSpec params = ecPublicKey.getParams();
params = toExplicitECParameterSpec(params);
if (params == null) { return publicKey; }
ECPublicKeySpec explicitPublicKeySpec = new ECPublicKeySpec(w, params);
return KeyFactory.getInstance("EC", BC_PROVIDER).generatePublic(explicitPublicKeySpec);
} catch (Exception e) {
LOGGER.warning("Could not make public key param spec explicit");
return publicKey;
}
}
/**
* Based on TR-SAC 1.01 4.5.1 and 4.5.2.
*
* For signing authentication token, not for sending to smart card.
* Assumes context is known.
*
* @param oid object identifier
* @param publicKey public key
*
* @return encoded public key data object for signing as authentication token
*
* @throws InvalidKeyException when public key is not DH or EC
*/
public static byte[] encodePublicKeyDataObject(String oid, PublicKey publicKey) throws InvalidKeyException {
return encodePublicKeyDataObject(oid, publicKey, true);
}
/**
* Based on TR-SAC 1.01 4.5.1 and 4.5.2.
*
* For signing authentication token, not for sending to smart card.
*
* @param oid object identifier
* @param publicKey public key
* @param isContextKnown whether context of public key is known to receiver (we will not include domain parameters in that case).
*
* @return encoded public key data object for signing as authentication token
*
* @throws InvalidKeyException when public key is not DH or EC
*/
public static byte[] encodePublicKeyDataObject(String oid, PublicKey publicKey, boolean isContextKnown) throws InvalidKeyException {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
TLVOutputStream tlvOut = new TLVOutputStream(bOut);
try {
tlvOut.writeTag(0x7F49); // FIXME: constant for 7F49 */
if (publicKey instanceof DHPublicKey) {
DHPublicKey dhPublicKey = (DHPublicKey)publicKey;
DHParameterSpec params = dhPublicKey.getParams();
BigInteger p = params.getP();
int l = params.getL();
BigInteger generator = params.getG();
BigInteger y = dhPublicKey.getY();
tlvOut.write(new ASN1ObjectIdentifier(oid).getEncoded()); /* Object Identifier, NOTE: encoding already contains 0x06 tag */
if (!isContextKnown) {
tlvOut.writeTag(0x81); tlvOut.writeValue(i2os(p)); /* p: Prime modulus */
tlvOut.writeTag(0x82); tlvOut.writeValue(i2os(BigInteger.valueOf(l))); /* q: Order of the subgroup */
tlvOut.writeTag(0x83); tlvOut.writeValue(i2os(generator)); /* Generator */
}
tlvOut.writeTag(0x84); tlvOut.writeValue(i2os(y)); /* y: Public value */
} else if (publicKey instanceof ECPublicKey) {
ECPublicKey ecPublicKey = (ECPublicKey)publicKey;
ECParameterSpec params = ecPublicKey.getParams();
BigInteger p = getPrime(params);
EllipticCurve curve = params.getCurve();
BigInteger a = curve.getA();
BigInteger b = curve.getB();
ECPoint generator = params.getGenerator();
BigInteger order = params.getOrder();
int coFactor = params.getCofactor();
ECPoint publicPoint = ecPublicKey.getW();
tlvOut.write(new ASN1ObjectIdentifier(oid).getEncoded()); /* Object Identifier, NOTE: encoding already contains 0x06 tag */
if (!isContextKnown) {
tlvOut.writeTag(0x81); tlvOut.writeValue(i2os(p)); /* Prime modulus */
tlvOut.writeTag(0x82); tlvOut.writeValue(i2os(a)); /* First coefficient */
tlvOut.writeTag(0x83); tlvOut.writeValue(i2os(b)); /* Second coefficient */
BigInteger affineX = generator.getAffineX();
BigInteger affineY = generator.getAffineY();
tlvOut.writeTag(0x84); tlvOut.write(i2os(affineX)); tlvOut.write(i2os(affineY)); tlvOut.writeValueEnd(); /* Base point, FIXME: correct encoding? */
tlvOut.writeTag(0x85); tlvOut.writeValue(i2os(order)); /* Order of the base point */
}
tlvOut.writeTag(0x86); tlvOut.writeValue(ecPoint2OS(publicPoint)); /* Public point */
if (!isContextKnown) {
tlvOut.writeTag(0x87); tlvOut.writeValue(i2os(BigInteger.valueOf(coFactor))); /* Cofactor */
}
} else {
throw new InvalidKeyException("Unsupported public key: " + publicKey.getClass().getCanonicalName());
}
tlvOut.writeValueEnd(); /* 7F49 */
tlvOut.flush();
tlvOut.close();
} catch (IOException ioe) {
LOGGER.severe("Exception: " + ioe.getMessage());
throw new IllegalStateException("Error in encoding public key");
}
return bOut.toByteArray();
}
/*
* FIXME: how can we be sure coords are uncompressed?
*/
/**
* Write uncompressed coordinates (for EC) or public value (DH).
*
* @param publicKey public key
*
* @return encoding for smart card
*
* @throws InvalidKeyException if the key type is not EC or DH
*/
public static byte[] encodePublicKeyForSmartCard(PublicKey publicKey) throws InvalidKeyException {
if (publicKey == null) {
throw new IllegalArgumentException("Cannot encode null public key");
}
if (publicKey instanceof ECPublicKey) {
ECPublicKey ecPublicKey = (ECPublicKey)publicKey;
try {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
bOut.write(Util.ecPoint2OS(ecPublicKey.getW()));
byte[] encodedPublicKey = bOut.toByteArray();
bOut.close();
return encodedPublicKey;
} catch (IOException ioe) {
/* NOTE: Should never happen, we're writing to a ByteArrayOutputStream. */
throw new IllegalStateException("Internal error writing to memory: " + ioe.getMessage());
}
} else if (publicKey instanceof DHPublicKey) {
DHPublicKey dhPublicKey = (DHPublicKey)publicKey;
return Util.i2os(dhPublicKey.getY());
} else {
throw new InvalidKeyException("Unsupported public key: " + publicKey.getClass().getCanonicalName());
}
}
public static ECPoint os2ECPoint(byte[] encodedECPoint) {
try {
DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(encodedECPoint));
int b = dataIn.read();
if (b != 0x04) {
throw new IllegalArgumentException("Expected encoded ECPoint to start with 0x04");
}
int length = (encodedECPoint.length - 1) / 2;
byte[] xCoordBytes = new byte[length];
byte[] yCoordBytes = new byte[length];
dataIn.readFully(xCoordBytes);
dataIn.readFully(yCoordBytes);
dataIn.close();
BigInteger x = Util.os2i(xCoordBytes);
BigInteger y = Util.os2i(yCoordBytes);
return new ECPoint(x, y);
} catch (IOException ioe) {
LOGGER.log(Level.SEVERE, "Exception", ioe);
throw new IllegalArgumentException(ioe.getMessage());
}
}
/**
* Encode an EC point (for use as public key value).
* Prefixes a {@code 0x04} (without a length).
*
* @param point an EC Point
*
* @return an octet string
*/
public static byte[] ecPoint2OS(ECPoint point) {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
BigInteger x = point.getAffineX();
BigInteger y = point.getAffineY();
try {
bOut.write(0x04);
bOut.write(i2os(x));
bOut.write(i2os(y));
bOut.close();
} catch (IOException ioe) {
throw new IllegalStateException(ioe.getMessage());
}
return bOut.toByteArray();
}
public static PublicKey decodePublicKeyFromSmartCard(byte[] encodedPublicKey, AlgorithmParameterSpec params) {
if (params == null) { throw new IllegalArgumentException("Params cannot be null"); }
try {
if (params instanceof ECParameterSpec) {
ECPoint w = os2ECPoint(encodedPublicKey);
ECParameterSpec ecParams = (ECParameterSpec)params;
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(new ECPublicKeySpec(w, ecParams));
} else if (params instanceof DHParameterSpec) {
DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(encodedPublicKey));
int b = dataIn.read();
if (b != 0x04) { throw new IllegalArgumentException("Expected encoded public key to start with 0x04"); }
int length = encodedPublicKey.length - 1;
byte[] publicValue = new byte[length];
dataIn.readFully(publicValue);
dataIn.close();
BigInteger y = Util.os2i(publicValue);
KeyFactory kf = KeyFactory.getInstance("DH");
DHParameterSpec dhParams = (DHParameterSpec)params;
return kf.generatePublic(new DHPublicKeySpec(y, dhParams.getP(), dhParams.getG()));
}
throw new IllegalArgumentException("Expected ECParameterSpec or DHParameterSpec, found " + params.getClass().getCanonicalName());
} catch (IOException ioe) {
LOGGER.severe("Exception: " + ioe.getMessage());
throw new IllegalArgumentException(ioe.getMessage());
} catch (GeneralSecurityException gse) {
LOGGER.severe("Exception: " + gse.getMessage());
throw new IllegalArgumentException(gse.getMessage());
}
}
/**
* Infer an EAC object identifier for an EC or DH public key.
*
* @param publicKey a public key
*
* @return either ID_PK_ECDH or ID_PK_DH
*/
public static String inferProtocolIdentifier(PublicKey publicKey) {
String algorithm = publicKey.getAlgorithm();
if ("EC".equals(algorithm) || "ECDH".equals(algorithm)) {
return SecurityInfo.ID_PK_ECDH;
} else if ("DH".equals(algorithm)) {
return SecurityInfo.ID_PK_DH;
} else {
throw new IllegalArgumentException("Wrong key type. Was expecting ECDH or DH public key.");
}
}
/**
* Maps nonce for generic mapping case.
*
* @param nonceS the nonce
* @param sharedSecretH the shared secret as opaque byte array
* @param params the key agreement algorithm parameter specification
*
* @return the new algorithm parameters
*
* @deprecated Looks like computing Y coord in ECDH case is buggy. Use DH or ECDH variant directly.
*/
public static AlgorithmParameterSpec mapNonceGM(byte[] nonceS, byte[] sharedSecretH, AlgorithmParameterSpec params) {
if (params == null) { throw new IllegalArgumentException("Unsupported parameters for mapping nonce"); }
if (params instanceof ECParameterSpec) {
ECParameterSpec ecParams = (ECParameterSpec)params;
BigInteger affineX = os2i(sharedSecretH);
BigInteger affineY = computeAffineY(affineX, ecParams); /* FIXME: Y coord is wrong about 50% of the time (when tested against Morpho applet). */
ECPoint sharedSecretPointH = new ECPoint(affineX, affineY);
return mapNonceGMWithECDH(os2i(nonceS), sharedSecretPointH, ecParams);
} else if (params instanceof DHParameterSpec) {
DHParameterSpec dhParams = (DHParameterSpec)params;
return mapNonceGMWithDH(os2i(nonceS), os2i(sharedSecretH), dhParams);
} else {
throw new IllegalArgumentException("Unsupported parameters for mapping nonce, expected ECParameterSpec or DHParameterSpec, found " + params.getClass().getCanonicalName());
}
}
public static AlgorithmParameterSpec mapNonceIM(byte[] nonceS, byte[] nonceT, byte[] sharedSecretH, AlgorithmParameterSpec params) {
/* FIXME: work in progress. */
return null;
}
public static ECParameterSpec mapNonceGMWithECDH(BigInteger nonceS, ECPoint sharedSecretPointH, ECParameterSpec params) {
/*
* D~ = (p, a, b, G~, n, h) where G~ = [s]G + H
*/
ECPoint generator = params.getGenerator();
EllipticCurve curve = params.getCurve();
BigInteger a = curve.getA();
BigInteger b = curve.getB();
ECFieldFp field = (ECFieldFp)curve.getField();
BigInteger p = field.getP();
BigInteger order = params.getOrder();
int cofactor = params.getCofactor();
ECPoint ephemeralGenerator = add(multiply(nonceS, generator, params), sharedSecretPointH, params);
if (!toBouncyCastleECPoint(ephemeralGenerator, params).isValid()) {
LOGGER.info("ephemeralGenerator is not a valid point");
}
return new ECParameterSpec(new EllipticCurve(new ECFieldFp(p), a, b), ephemeralGenerator, order, cofactor);
}
public static DHParameterSpec mapNonceGMWithDH(BigInteger nonceS, BigInteger sharedSecretH, DHParameterSpec params) {
// g~ = g^s * h
BigInteger p = params.getP();
BigInteger generator = params.getG();
BigInteger ephemeralGenerator = generator.modPow(nonceS, p).multiply(sharedSecretH).mod(p);
return new DHParameterSpec(p, ephemeralGenerator, params.getL());
}
private static ECPoint add(ECPoint x, ECPoint y, ECParameterSpec params) {
org.bouncycastle.math.ec.ECPoint bcX = toBouncyCastleECPoint(x, params);
org.bouncycastle.math.ec.ECPoint bcY = toBouncyCastleECPoint(y, params);
org.bouncycastle.math.ec.ECPoint bcSum = bcX.add(bcY);
return fromBouncyCastleECPoint(bcSum);
}
public static ECPoint multiply(BigInteger s, ECPoint point, ECParameterSpec params) {
org.bouncycastle.math.ec.ECPoint bcPoint = toBouncyCastleECPoint(point, params);
org.bouncycastle.math.ec.ECPoint bcProd = bcPoint.multiply(s);
return fromBouncyCastleECPoint(bcProd);
}
private static byte[] getBytes(String str) {
byte[] bytes = str.getBytes();
try {
bytes = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException use) {
/* NOTE: unlikely. */
LOGGER.severe("Exception: " + use.getMessage());
}
return bytes;
}
public static BigInteger getPrime(AlgorithmParameterSpec params) {
if (params == null) {
throw new IllegalArgumentException("Parameters null");
}
if (params instanceof DHParameterSpec) {
return ((DHParameterSpec)params).getP();
} else if (params instanceof ECParameterSpec) {
EllipticCurve curve = ((ECParameterSpec)params).getCurve();
ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) {
throw new IllegalStateException("Was expecting prime field of type ECFieldFp, found " + field.getClass().getCanonicalName());
}
return ((ECFieldFp)field).getP();
} else {
throw new IllegalArgumentException("Unsupported agreement algorithm, was expecting DHParameterSpec or ECParameterSpec, found " + params.getClass().getCanonicalName());
}
}
public static byte[] wrapDO(byte tag, byte[] data) {
if (data == null) {
throw new IllegalArgumentException("Data to wrap is null");
}
byte[] result = new byte[data.length + 2];
result[0] = tag;
result[1] = (byte)data.length;
System.arraycopy(data, 0, result, 2, data.length);
return result;
}
public static byte[] unwrapDO(byte expectedTag, byte[] wrappedData) {
if (wrappedData == null || wrappedData.length < 2) {
throw new IllegalArgumentException("Wrapped data is null or length < 2");
}
byte actualTag = wrappedData[0];
if (actualTag != expectedTag) {
throw new IllegalArgumentException("Expected tag " + Integer.toHexString(expectedTag) + ", found tag " + Integer.toHexString(actualTag));
}
byte[] result = new byte[wrappedData.length - 2];
System.arraycopy(wrappedData, 2, result, 0, result.length);
return result;
}
public static String inferKeyAgreementAlgorithm(PublicKey publicKey) {
if (publicKey instanceof ECPublicKey) {
return "ECDH";
} else if (publicKey instanceof DHPublicKey) {
return "DH";
} else {
throw new IllegalArgumentException("Unsupported public key: " + publicKey);
}
}
/**
* This just solves the curve equation for y.
*
* @param affineX the x coord of a point on the curve
* @param params EC parameters for curve over Fp
* @return the corresponding y coord
*/
public static BigInteger computeAffineY(BigInteger affineX, ECParameterSpec params) {
ECCurve bcCurve = toBouncyCastleECCurve(params);
ECFieldElement a = bcCurve.getA();
ECFieldElement b = bcCurve.getB();
ECFieldElement x = bcCurve.fromBigInteger(affineX);
LOGGER.info("DEBUG: x.bitLength = " + x.bitLength());
ECFieldElement y = x.multiply(x).add(a).multiply(x).add(b).sqrt();
LOGGER.info("DEBUG: y.bitLength = " + y.bitLength());
return y.toBigInteger();
}
private static org.bouncycastle.math.ec.ECPoint toBouncyCastleECPoint(ECPoint point, ECParameterSpec params) {
org.bouncycastle.math.ec.ECCurve bcCurve = toBouncyCastleECCurve(params);
return bcCurve.createPoint(point.getAffineX(), point.getAffineY(), false);
// return new org.bouncycastle.math.ec.ECPoint.Fp(bcCurve, bcCurve.fromBigInteger(point.getAffineX()), bcCurve.fromBigInteger(point.getAffineY()));
}
public static ECPoint fromBouncyCastleECPoint(org.bouncycastle.math.ec.ECPoint point) {
point = point.normalize();
if (!point.isValid()) { LOGGER.warning("point not valid"); }
return new ECPoint(point.getAffineXCoord().toBigInteger(), point.getAffineYCoord().toBigInteger());
}
public static boolean isValid(ECPoint ecPoint, ECParameterSpec params) {
org.bouncycastle.math.ec.ECPoint bcPoint = toBouncyCastleECPoint(ecPoint, params);
return bcPoint.isValid();
}
public static ECPoint normalize(ECPoint ecPoint, ECParameterSpec params) {
org.bouncycastle.math.ec.ECPoint bcPoint = toBouncyCastleECPoint(ecPoint, params);
bcPoint = bcPoint.normalize();
return fromBouncyCastleECPoint(bcPoint);
}
private static ECCurve toBouncyCastleECCurve(ECParameterSpec params) {
EllipticCurve curve = params.getCurve();
ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) {
throw new IllegalArgumentException("Only prime field supported (for now), found " + field.getClass().getCanonicalName());
}
int coFactor = params.getCofactor();
BigInteger order = params.getOrder();
BigInteger a = curve.getA();
BigInteger b = curve.getB();
BigInteger p = getPrime(params);
return new ECCurve.Fp(p, a, b, order, BigInteger.valueOf(coFactor));
}
public static ECPublicKeyParameters toBouncyECPublicKeyParameters(ECPublicKey publicKey) {
ECParameterSpec ecParams = publicKey.getParams();
org.bouncycastle.math.ec.ECPoint q = toBouncyCastleECPoint(publicKey.getW(), ecParams);
return new ECPublicKeyParameters(q, toBouncyECDomainParameters(ecParams));
}
public static ECPrivateKeyParameters toBouncyECPrivateKeyParameters(ECPrivateKey privateKey) {
BigInteger d = privateKey.getS();
ECDomainParameters ecParams = toBouncyECDomainParameters(privateKey.getParams());
return new ECPrivateKeyParameters(d, ecParams);
}
public static ECDomainParameters toBouncyECDomainParameters(ECParameterSpec params) {
ECCurve curve = toBouncyCastleECCurve(params);
org.bouncycastle.math.ec.ECPoint g = toBouncyCastleECPoint(params.getGenerator(), params);
BigInteger n = params.getOrder();
BigInteger h = BigInteger.valueOf(params.getCofactor());
byte[] seed = params.getCurve().getSeed();
return new ECDomainParameters(curve, g, n, h, seed);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy