/* See LICENSE for licensing and NOTICE for copyright. */ package org.cryptacular.util; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.math.BigInteger; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.DSAParameters; import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; import org.bouncycastle.crypto.params.DSAPublicKeyParameters; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.signers.DSASigner; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.RSADigestSigner; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.cryptacular.EncodingException; import org.cryptacular.StreamException; import org.cryptacular.adapter.Converter; import org.cryptacular.asn.OpenSSLPrivateKeyDecoder; import org.cryptacular.asn.PKCS8PrivateKeyDecoder; import org.cryptacular.asn.PublicKeyDecoder; /** * Utility methods for public/private key pairs used for asymmetric encryption. * * @author Middleware Services */ public final class KeyPairUtil { /** Data used to verify key pairs. */ private static final byte[] SIGN_BYTES = ByteUtil.toBytes("Mr. Watson--come here--I want to see you."); /** Private constructor of utility class. */ private KeyPairUtil() {} /** * Gets the length in bits of a public key where key size is dependent on the particulars of the algorithm. * * <ul> * <li>DSA - length of p</li> * <li>EC - length of p for prime fields, m for binary fields</li> * <li>RSA - length of modulus</li> * </ul> * * @param pubKey Public key. * * @return Size of the key in bits. */ public static int length(final PublicKey pubKey) { final int size; if (pubKey instanceof DSAPublicKey) { size = ((DSAPublicKey) pubKey).getParams().getP().bitLength(); } else if (pubKey instanceof RSAPublicKey) { size = ((RSAPublicKey) pubKey).getModulus().bitLength(); } else if (pubKey instanceof ECPublicKey) { size = ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize(); } else { throw new IllegalArgumentException(pubKey + " not supported."); } return size; } /** * Gets the length in bits of a private key where key size is dependent on the particulars of the algorithm. * * <ul> * <li>DSA - length of q in bits</li> * <li>EC - length of p for prime fields, m for binary fields</li> * <li>RSA - modulus length in bits</li> * </ul> * * @param privKey Private key. * * @return Size of the key in bits. */ public static int length(final PrivateKey privKey) { final int size; if (privKey instanceof DSAPrivateKey) { size = ((DSAPrivateKey) privKey).getParams().getQ().bitLength(); } else if (privKey instanceof RSAPrivateKey) { size = ((RSAPrivateKey) privKey).getModulus().bitLength(); } else if (privKey instanceof ECPrivateKey) { size = ((ECPrivateKey) privKey).getParams().getCurve().getField().getFieldSize(); } else { throw new IllegalArgumentException(privKey + " not supported."); } return size; } /** * Determines whether the given public and private keys form a proper key pair by computing and verifying a digital * signature with the keys. * * @param pubKey DSA, RSA or EC public key. * @param privKey DSA, RSA, or EC private key. * * @return True if the keys form a functioning keypair, false otherwise. Errors during signature verification are * treated as false. * * @throws org.cryptacular.CryptoException on key validation errors. */ public static boolean isKeyPair(final PublicKey pubKey, final PrivateKey privKey) throws org.cryptacular.CryptoException { final String alg = pubKey.getAlgorithm(); if (!alg.equals(privKey.getAlgorithm())) { return false; } // Dispatch onto the algorithm-specific method final boolean result; switch (alg) { case "DSA": result = isKeyPair((DSAPublicKey) pubKey, (DSAPrivateKey) privKey); break; case "RSA": result = isKeyPair((RSAPublicKey) pubKey, (RSAPrivateKey) privKey); break; case "EC": result = isKeyPair((ECPublicKey) pubKey, (ECPrivateKey) privKey); break; default: throw new IllegalArgumentException(alg + " not supported."); } return result; } /** * Determines whether the given DSA public and private keys form a proper key pair by computing and verifying a * digital signature with the keys. * * @param pubKey DSA public key. * @param privKey DSA private key. * * @return True if the keys form a functioning keypair, false otherwise. Errors during signature verification are * treated as false. * * @throws org.cryptacular.CryptoException on key validation errors. */ public static boolean isKeyPair(final DSAPublicKey pubKey, final DSAPrivateKey privKey) throws org.cryptacular.CryptoException { final DSASigner signer = new DSASigner(); final DSAParameters params = new DSAParameters( privKey.getParams().getP(), privKey.getParams().getQ(), privKey.getParams().getG()); try { signer.init(true, new DSAPrivateKeyParameters(privKey.getX(), params)); final BigInteger[] sig = signer.generateSignature(SIGN_BYTES); signer.init(false, new DSAPublicKeyParameters(pubKey.getY(), params)); return signer.verifySignature(SIGN_BYTES, sig[0], sig[1]); } catch (RuntimeException e) { throw new org.cryptacular.CryptoException("Signature computation error", e); } } /** * Determines whether the given RSA public and private keys form a proper key pair by computing and verifying a * digital signature with the keys. * * @param pubKey RSA public key. * @param privKey RSA private key. * * @return True if the keys form a functioning keypair, false otherwise. Errors during signature verification are * treated as false. * * @throws org.cryptacular.CryptoException on key validation errors. */ public static boolean isKeyPair(final RSAPublicKey pubKey, final RSAPrivateKey privKey) throws org.cryptacular.CryptoException { final RSADigestSigner signer = new RSADigestSigner(new SHA256Digest()); try { signer.init(true, new RSAKeyParameters(true, privKey.getModulus(), privKey.getPrivateExponent())); signer.update(SIGN_BYTES, 0, SIGN_BYTES.length); final byte[] sig = signer.generateSignature(); signer.init(false, new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent())); signer.update(SIGN_BYTES, 0, SIGN_BYTES.length); return signer.verifySignature(sig); } catch (Exception e) { throw new org.cryptacular.CryptoException("Signature computation error", e); } } /** * Determines whether the given EC public and private keys form a proper key pair by computing and verifying a digital * signature with the keys. * * @param pubKey EC public key. * @param privKey EC private key. * * @return True if the keys form a functioning keypair, false otherwise. Errors during signature verification are * treated as false. * * @throws org.cryptacular.CryptoException on key validation errors. */ public static boolean isKeyPair(final ECPublicKey pubKey, final ECPrivateKey privKey) throws org.cryptacular.CryptoException { final ECDSASigner signer = new ECDSASigner(); try { signer.init(true, ECUtil.generatePrivateKeyParameter(privKey)); final BigInteger[] sig = signer.generateSignature(SIGN_BYTES); signer.init(false, ECUtil.generatePublicKeyParameter(pubKey)); return signer.verifySignature(SIGN_BYTES, sig[0], sig[1]); } catch (Exception e) { throw new org.cryptacular.CryptoException("Signature computation error", e); } } /** * Reads an encoded private key from a file at the given path. Both PKCS#8 and OpenSSL "traditional" formats are * supported in DER or PEM encoding. See {@link #decodePrivateKey(byte[])} for supported asymmetric algorithms. * * @param path Path to private key file. * * @return Private key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors reading data from file. */ public static PrivateKey readPrivateKey(final String path) throws EncodingException, StreamException { return readPrivateKey(new File(path)); } /** * Reads an encoded private key from a file. Both PKCS#8 and OpenSSL "traditional" formats are supported in DER or PEM * encoding. See {@link #decodePrivateKey(byte[])} for supported asymmetric algorithms. * * @param file Private key file. * * @return Private key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors reading data from file. */ public static PrivateKey readPrivateKey(final File file) throws EncodingException, StreamException { try { return readPrivateKey(new FileInputStream(file)); } catch (FileNotFoundException e) { throw new StreamException("File not found: " + file); } } /** * Reads an encoded private key from an input stream. Both PKCS#8 and OpenSSL "traditional" formats are supported in * DER or PEM encoding. See {@link #decodePrivateKey(byte[])} for supported asymmetric algorithms. The {@link * InputStream} parameter is closed by this method. * * @param in Input stream containing private key data. * * @return Private key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors reading data from file. */ public static PrivateKey readPrivateKey(final InputStream in) throws EncodingException, StreamException { return decodePrivateKey(StreamUtil.readAll(in)); } /** * Reads an encrypted private key from a file at the given path. Both PKCS#8 and OpenSSL "traditional" formats are * supported in DER or PEM encoding. See {@link #decodePrivateKey(byte[])} for supported asymmetric algorithms. * * @param path Path to private key file. * @param password Password used to encrypt private key. * * @return Private key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors. */ public static PrivateKey readPrivateKey(final String path, final char[] password) throws EncodingException, StreamException { return readPrivateKey(new File(path), password); } /** * Reads an encrypted private key from a file. Both PKCS#8 and OpenSSL "traditional" formats are supported in DER or * PEM encoding. See {@link #decodePrivateKey(byte[])} for supported asymmetric algorithms. * * @param file Private key file. * @param password Password used to encrypt private key. * * @return Private key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors. */ public static PrivateKey readPrivateKey(final File file, final char[] password) throws EncodingException, StreamException { try { return readPrivateKey(new FileInputStream(file), password); } catch (FileNotFoundException e) { throw new StreamException("File not found: " + file); } } /** * Reads an encrypted private key from an input stream. Both PKCS#8 and OpenSSL "traditional" formats are supported in * DER or PEM encoding. See {@link #decodePrivateKey(byte[])} for supported asymmetric algorithms. The {@link * InputStream} parameter is closed by this method. * * @param in Input stream containing private key data. * @param password Password used to encrypt private key. * * @return Private key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors. */ public static PrivateKey readPrivateKey(final InputStream in, final char[] password) throws EncodingException, StreamException { return decodePrivateKey(StreamUtil.readAll(in), password); } /** * Decodes an encoded private key in either PKCS#8 or OpenSSL "traditional" format in either DER or PEM encoding. Keys * from the following asymmetric algorithms are supported: * * <ul> * <li>DSA</li> * <li>RSA</li> * <li>Elliptic curve</li> * </ul> * * @param encodedKey Encoded private key data. * * @return Private key. * * @throws EncodingException on key encoding errors. */ public static PrivateKey decodePrivateKey(final byte[] encodedKey) throws EncodingException { return decodePrivateKey(encodedKey, null); } /** * Decodes an encrypted private key. The following formats are supported: * * <ul> * <li>DER or PEM encoded PKCS#8 format</li> * <li>PEM encoded OpenSSL "traditional" format</li> * </ul> * * <p>Keys from the following asymmetric algorithms are supported:</p> * * <ul> * <li>DSA</li> * <li>RSA</li> * <li>Elliptic curve</li> * </ul> * * @param encryptedKey Encrypted private key data. * @param password Password used to encrypt private key. * * @return Private key. * * @throws EncodingException on key encoding errors. */ public static PrivateKey decodePrivateKey(final byte[] encryptedKey, final char[] password) throws EncodingException { AsymmetricKeyParameter key; try { final PKCS8PrivateKeyDecoder decoder = new PKCS8PrivateKeyDecoder(); key = decoder.decode(encryptedKey, password); } catch (RuntimeException e) { final OpenSSLPrivateKeyDecoder decoder = new OpenSSLPrivateKeyDecoder(); key = decoder.decode(encryptedKey, password); } return Converter.convertPrivateKey(key); } /** * Reads a DER or PEM-encoded public key from a file. * * @param path Path to DER or PEM-encoded public key file. * * @return Public key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors. */ public static PublicKey readPublicKey(final String path) throws EncodingException, StreamException { return readPublicKey(new File(path)); } /** * Reads a DER or PEM-encoded public key from a file. * * @param file DER or PEM-encoded public key file. * * @return Public key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors. */ public static PublicKey readPublicKey(final File file) throws EncodingException, StreamException { try { return readPublicKey(new FileInputStream(file)); } catch (FileNotFoundException e) { throw new StreamException("File not found: " + file); } } /** * Reads a DER or PEM-encoded public key from data in the given stream. The {@link InputStream} parameter is closed by * this method. * * @param in Input stream containing an encoded key. * * @return Public key. * * @throws EncodingException on key encoding errors. * @throws StreamException on IO errors. */ public static PublicKey readPublicKey(final InputStream in) throws EncodingException, StreamException { return decodePublicKey(StreamUtil.readAll(in)); } /** * Decodes public keys formatted in an X.509 SubjectPublicKeyInfo structure in either PEM or DER encoding. * * @param encoded Encoded public key bytes. * * @return Public key. * * @throws EncodingException on key encoding errors. */ public static PublicKey decodePublicKey(final byte[] encoded) throws EncodingException { return Converter.convertPublicKey(new PublicKeyDecoder().decode(encoded)); } }