
edu.uiuc.ncsa.security.util.crypto.KeyUtil Maven / Gradle / Ivy
package edu.uiuc.ncsa.security.util.crypto;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.concurrent.ThreadLocalRandom;
/**
* A utility for doing certain security-related operations, such as creating key pairs, serializing them and deserializing them
* to PEM formats.
. In a nutshell you can
*
* - Generate key pairs
* - Read and write PKCS 8 format private keys
* - Read and write X509 encoded public keys
* - Write PKCS 1 private keys
*
* There is no call to read in a PKCS 1 format pem; these are private keys that start with
* -----BEGIN RSA PRIVATE KEY-----
. This requires laborious parsing of ASN 1 objects and so
* far there is not much of a need for it. Java much prefers the newer and more secure PKCS 8 format which you should
* use if possible.
* All methods are static and if you need something other than the defaults, set them before first use.
*
Created by Jeff Gaynor
* on Jun 15, 2010 at 4:51:25 PM
*/
public class KeyUtil {
public static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----";
public static final String END_RSA_PRIVATE_KEY = "-----END RSA PRIVATE KEY-----";
public static final String BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----";
public static final String END_PRIVATE_KEY = "-----END PRIVATE KEY-----";
public static final String BEGIN_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----";
public static final String END_PUBLIC_KEY = "-----END PUBLIC KEY-----";
/**
* Write a PEM format PKCS1 private using a writer.
*
* @param privateKey
* @param writer
* @throws IOException
*/
public static void toPKCS1PEM(PrivateKey privateKey, Writer writer) throws IOException {
String pem = toPKCS1PEM(privateKey);
writer.write(pem);
writer.flush();
}
/**
* Write PEM format PKCS 1 private key using a stream
* @param privateKey
* @param out
* @throws IOException
*/
public static void toPKCS1PEM(PrivateKey privateKey, OutputStream out) throws IOException {
PrintStream printStream = new PrintStream(out);
printStream.print(toPKCS1PEM(privateKey));
printStream.flush();
}
/**
* Take a private key and put it into PKCS 1 format. These are used, e.g., by OpenSSL.
*
* @return
* @throws IOException
*/
public static String toPKCS1PEM(PrivateKey privateKey) throws IOException {
byte[] bytes = privateKey.getEncoded();
return PEMFormatUtil.delimitBody(PEMFormatUtil.bytesToChunkedString(bytes), BEGIN_RSA_PRIVATE_KEY, END_RSA_PRIVATE_KEY);
}
/**
* Use a reader to ingest a PKCS 1 private key.
* @param reader
* @return
* @throws Exception
*/
public static PrivateKey fromPKCS1PEM(Reader reader) throws Exception {
return fromPKCS1PEM(PEMFormatUtil.readerToString(reader));
}
/**
* Read a PKCS 1 format pem and return the private key. Read the RSA spec
*
* @param pem
* @return
* @throws Exception
*/
public static PrivateKey fromPKCS1PEM(String pem) throws Exception {
byte[] bytes = PEMFormatUtil.getBodyBytes(pem, BEGIN_RSA_PRIVATE_KEY, END_RSA_PRIVATE_KEY);
DerInputStream derReader = new DerInputStream(bytes);
DerValue[] sequence = derReader.getSequence(0);
// skip the version at index 0
// Note that getting the big integers this way automatically corrects so that the result is always positive.
// We have do this manually in the JSONWebKeyUtil.
BigInteger modulus = sequence[1].getBigInteger();
BigInteger publicExp = sequence[2].getBigInteger();
BigInteger privateExp = sequence[3].getBigInteger();
BigInteger prime1 = sequence[4].getBigInteger();
BigInteger prime2 = sequence[5].getBigInteger();
BigInteger exp1 = sequence[6].getBigInteger();
BigInteger exp2 = sequence[7].getBigInteger();
BigInteger crtCoef = sequence[8].getBigInteger();
RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec =
new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(rsaPrivateCrtKeySpec);
}
/**
* Ingest a keypair that has been encoded in PKCS 1 format using a reader.
* @param r
* @return
* @throws Exception
*/
public static KeyPair keyPairFromPKCS1(Reader r) throws Exception {
return keyPairFromPKCS1(PEMFormatUtil.readerToString(r));
}
/**
* Verifies that a given keypair is correct, i.e., that the public and private keys
* are in fact paired correctly.
* N.B: A positive result means they are most likely correct. A failure
* means they most certainly do not match.
*
* @param keyPair
* @return
*/
public static boolean validateKeyPair(KeyPair keyPair) throws Exception {
return validateKeyPair(keyPair.getPublic(), keyPair.getPrivate());
}
/**
* See {@link #validateKeyPair(KeyPair)}
*
* @param publicKey
* @param privateKey
* @return
* @throws Exception
*/
public static boolean validateKeyPair(PublicKey publicKey, PrivateKey privateKey) throws Exception {
byte[] challenge = new byte[10000];
ThreadLocalRandom.current().nextBytes(challenge); // Get really random bytes
// sign using the private key
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(challenge);
byte[] signature = sig.sign();
// verify signature using the public key
sig.initVerify(publicKey);
sig.update(challenge);
return sig.verify(signature);
}
/**
* Read a PKCS 1 key in and generate the keypair from it.
*
* @param pem
* @return
* @throws Exception
*/
public static KeyPair keyPairFromPKCS1(String pem) throws Exception {
byte[] bytes = PEMFormatUtil.getBodyBytes(pem, BEGIN_RSA_PRIVATE_KEY, END_RSA_PRIVATE_KEY);
DerInputStream derReader = new DerInputStream(bytes);
DerValue[] seq = derReader.getSequence(0);
// skip version seq[0];
BigInteger modulus = seq[1].getBigInteger();
BigInteger publicExp = seq[2].getBigInteger();
BigInteger privateExp = seq[3].getBigInteger();
BigInteger prime1 = seq[4].getBigInteger();
BigInteger prime2 = seq[5].getBigInteger();
BigInteger exp1 = seq[6].getBigInteger();
BigInteger exp2 = seq[7].getBigInteger();
BigInteger crtCoef = seq[8].getBigInteger();
RSAPrivateCrtKeySpec keySpec =
new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExp);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
return new KeyPair(publicKey, privateKey);
}
/**
* Convert the public key to PEM format using a writer.
* @param publicKey
* @param writer
* @throws IOException
*/
public static void toX509PEM(PublicKey publicKey, Writer writer) throws IOException {
writer.write(toX509PEM(publicKey));
writer.flush();
}
/**
* Convert public key to PEM format, returning the result as a string.
* @param publicKey
* @return
*/
public static String toX509PEM(PublicKey publicKey) {
byte[] bytes = publicKey.getEncoded();
return PEMFormatUtil.delimitBody(PEMFormatUtil.bytesToChunkedString(bytes), BEGIN_PUBLIC_KEY, END_PUBLIC_KEY);
}
/**
* DER encoding for the private key.
*
* @param privateKey
* @return
*/
public static byte[] toDER(PrivateKey privateKey) {
return privateKey.getEncoded();
}
/**
* DER encode a public key.
* @param publicKey
* @return
*/
public static byte[] toDER(PublicKey publicKey) {
return publicKey.getEncoded();
}
/**
* DER encode the private key of a {@link KeyPair}.
* @param keyPair
* @return
*/
public static byte[] privateToDER(KeyPair keyPair) {
return toDER(keyPair.getPrivate());
}
/**
* DER encode the public key in a {@link KeyPair}.
* @param keyPair
* @return
*/
public static byte[] publicToDER(KeyPair keyPair) {
return toDER(keyPair.getPublic());
}
/**
* Decode a PKCS #8 encoded private key. OpenSSL, for instance, does not put out this format
* automatically. The standard command will generate a PEM file, e.g.,
*
* openssl genrsa -out privkey.pem 2048
*
* so you must convert it e.g., with the following command:
*
* openssl pkcs8 -topk8 -nocrypt -in privkey.pem -inform PEM -out privkey.der -outform DER
*
* The result is that you have two copies of the private key. The one ending with extension .der
* (which is binary) can be imported using this method. Doing this conversion in Java is well past the scope of this
* utility. If you use this on a key in the wrong format you will get an exception.
*
* @param encodedPrivate
* @return
*/
public static PrivateKey fromPKCS8DER(byte[] encodedPrivate) {
PKCS8EncodedKeySpec encodedPrivatePKCS8 = new PKCS8EncodedKeySpec(encodedPrivate);
try {
return getKeyFactory().generatePrivate(encodedPrivatePKCS8);
} catch (InvalidKeySpecException e) {
throw new CryptoException(e);
}
}
/**
* Convert a private key to PKCS 8 format, returning the resulting PEM as a string.
* @param privateKey
* @return
*/
public static String toPKCS8PEM(PrivateKey privateKey) {
return PEMFormatUtil.delimitBody(privateKey.getEncoded(), BEGIN_PRIVATE_KEY, END_PRIVATE_KEY);
}
/**
* Convert a private key to PKCS 8 format, sending the resulting PEM to a writer.
* @param privateKey
* @param writer
* @throws IOException
*/
public static void toPKCS8PEM(PrivateKey privateKey, Writer writer) throws IOException {
writer.write(toPKCS8PEM(privateKey));
writer.flush();
}
/**
* This takes the PEM encoding of a PKCS 8 format private key, strips the header and footer, converts
* to bytes then invokes {@link #fromPKCS8DER(byte[])}.
* You can get a PKCS #8 private key that is PEM encoded from open ssl e.g. with
*
* openssl pkcs8 -topk8 -nocrypt -in privkey.pem -inform PEM -out privkey-pkcs8.pem -outform PEM
*
*
* @param pem
* @return
*/
public static PrivateKey fromPKCS8PEM(String pem) {
return fromPKCS8DER(PEMFormatUtil.getBodyBytes(pem, BEGIN_PRIVATE_KEY, END_PRIVATE_KEY));
}
/**
* Public keys are encoded with the X509 public key spec.
*
* @param encodedPublic
* @return
*/
public static PublicKey fromX509PEM(String encodedPublic) {
return fromX509DER(PEMFormatUtil.getBodyBytes(encodedPublic, BEGIN_PUBLIC_KEY, END_PUBLIC_KEY));
}
/**
* Convert a DER encoded public key to a {@link PublicKey};
* @param encodedPublic
* @return
*/
public static PublicKey fromX509DER(byte[] encodedPublic) {
X509EncodedKeySpec x = new X509EncodedKeySpec(encodedPublic);
try {
return getKeyFactory().generatePublic(x);
} catch (InvalidKeySpecException e) {
throw new CryptoException(e);
}
}
/**
* Gets the key length (default is 2048 bits).
* @return
*/
public static int getKeyLength() {
return keyLength;
}
public static void setKeyLength(int length) {
keyLength = length;
}
static int keyLength = 2048;
/**
* Create and set the keypair generator for this suite using the current key algorithm (default is RSA).
* @return
*/
public static KeyPairGenerator getKeyPairGenerator() {
if (keyPairGenerator == null) {
try {
keyPairGenerator = KeyPairGenerator.getInstance(getKeyAlgorithm());
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(e);
}
keyPairGenerator.initialize(getKeyLength());
}
return keyPairGenerator;
}
/**
* If you have some specific keypair generator you need to use, you can set it here,
* @param generator
*/
public static void setKeyPairGenerator(KeyPairGenerator generator) {
keyPairGenerator = generator;
}
static KeyPairGenerator keyPairGenerator;
/**
* Generate a {@link KeyPair}
* @return
*/
public static KeyPair generateKeyPair() {
return getKeyPairGenerator().generateKeyPair();
}
/**
* Return the current key algorithm. Default is RSA.
* @return
*/
public static String getKeyAlgorithm() {
return keyAlgorithm;
}
public static void setKeyAlgorithm(String algorithm) {
keyAlgorithm = algorithm;
}
protected static String keyAlgorithm = "RSA";
protected static KeyFactory keyFactory;
/**
* Create and set key factory for this suite using the current key algorithm default is RSA.
* @return
*/
public static KeyFactory getKeyFactory() {
if (keyFactory == null) {
try {
keyFactory = KeyFactory.getInstance(getKeyAlgorithm());
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(e);
}
}
return keyFactory;
}
/**
* Create and set the key factory for this suite using the given algorithm.
* @param keyAlgorithm
* @return
*/
public static KeyFactory getKeyFactory(String keyAlgorithm) {
if (keyFactory == null) {
try {
keyFactory = KeyFactory.getInstance(keyAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(e);
}
}
return keyFactory;
}
/**
* Ingest a PKCS 8 format PEM via a reader.
* @param reader
* @return
* @throws IOException
*/
public static PrivateKey fromPKCS8PEM(Reader reader) throws IOException {
return fromPKCS8PEM(PEMFormatUtil.readerToString(reader));
}
/**
* Ingest the public key in an X 509 PEM via a reader.
* @param reader
* @return
* @throws IOException
*/
public static PublicKey fromX509PEM(Reader reader) throws IOException {
return fromX509PEM(PEMFormatUtil.readerToString(reader));
}
/**
* Generate a symmetric key. Note that the length is not bits
* but bytes. E.g to generate a symmetric key of 4096 bits you would call
*
* byte[] sKey = generateSKey(4096/8);
*
* @param length
* @return
*/
public static byte[] generateSKey(int length) {
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[length];
secureRandom.nextBytes(key);
return key;
}
public static void main(String[] args) {
setKeyAlgorithm("EC");
KeyPair keyPair = generateKeyPair();
System.out.println(keyPair);
}
}