PgpKeyPairGenerator.java
package com.hsbc.baas;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Calendar;
public class PgpKeyPairGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(PgpKeyPairGenerator.class);
private PgpKeyPairGenerator() {
}
// Implementation from https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/sun/security/jca/JCAUtil.java
// https://www.baeldung.com/java-secure-random
private static volatile SecureRandom secureRandom;
private static final Object LOCK = PgpKeyPairGenerator.class;
/**
* Get a SecureRandom instance. This method should me used by JDK
* internal code in favor of calling "new SecureRandom()". That needs to
* iterate through the provider table to find the default SecureRandom
* implementation, which is fairly inefficient.
*/
public static SecureRandom getSecureRandom() {
// we use double checked locking to minimize synchronization
// works because we use a volatile reference
SecureRandom r = secureRandom;
if (r == null) {
synchronized (LOCK) {
r = secureRandom;
if (r == null) {
r = new SecureRandom();
secureRandom = r;
}
}
}
return r;
}
/**
* creates and initializes a PGP Key Ring Generator
*
* @param userId the user id to use
* @param password the password used for the private key
* @param keySize the key size used for the keys
* @return the initialized key ring generator or null if something goes wrong
*/
private static PGPKeyRingGenerator createKeyRingGenerator(String userId, String password, int keySize) {
LOGGER.trace("createKeyRingGenerator(String, String, int)");
LOGGER.trace("User ID: {}, Password: {}, Key Size: {}", userId, password == null ? "not set" : "********", keySize);
PGPKeyRingGenerator generator = null;
try {
LOGGER.debug("Creating RSA key pair generator");
// Type: RSA or DSA
RSAKeyPairGenerator generator1 = new RSAKeyPairGenerator();
// Size: 2048 or 4096 (for RSA); 2048 or 3072 (for DSA)
generator1.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), getSecureRandom(), keySize, 12));
boolean isCritical = true;
//Key lifespan: between 1 day -27 months
LocalDateTime datePgpKey = LocalDateTime.now().plusMonths(12);
long validSeconds = Duration.between(LocalDateTime.now(), datePgpKey).getSeconds();
LOGGER.debug("Generating Signature Key Properties");
PGPSignatureSubpacketGenerator signatureSubpacketGenerator = new PGPSignatureSubpacketGenerator();
signatureSubpacketGenerator.setKeyExpirationTime(isCritical, validSeconds);
signatureSubpacketGenerator.setKeyFlags(false, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER
| KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
//Hash algorithm: SHA-2 (SHA-224, SHA-256, SHA-512, SHA-384)
int[] hashingAlgos = {HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512};
//Ciphers and key length: IDEA (128 bits), AES (128 bits and more),
int[] symmetricKeys = {SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.CAST5, SymmetricKeyAlgorithmTags.TRIPLE_DES};
signatureSubpacketGenerator.setPreferredSymmetricAlgorithms(false, symmetricKeys);
signatureSubpacketGenerator.setPreferredHashAlgorithms(false, hashingAlgos);
signatureSubpacketGenerator.setPreferredCompressionAlgorithms(false,
new int[]{CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.ZIP});
LOGGER.debug("Generating Encyption Key Properties");
PGPSignatureSubpacketGenerator encryptionSubpacketGenerator = new PGPSignatureSubpacketGenerator();
encryptionSubpacketGenerator.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
encryptionSubpacketGenerator.setPreferredCompressionAlgorithms(false,
new int[]{CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.ZIP});
encryptionSubpacketGenerator.setPreferredHashAlgorithms(false,
hashingAlgos);
encryptionSubpacketGenerator.setPreferredSymmetricAlgorithms(false, symmetricKeys);
encryptionSubpacketGenerator.setKeyExpirationTime(isCritical, validSeconds);
encryptionSubpacketGenerator.setKeyFlags(true, KeyFlags.SIGN_DATA);
encryptionSubpacketGenerator.setSignerUserID(true, userId);
LOGGER.debug("Generating Signing Key Pair");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -2);
BcPGPKeyPair signingKeyPair = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, generator1.generateKeyPair(), calendar.getTime());
LOGGER.debug("Generating Encyption Key Pair");
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
generator = new PGPKeyRingGenerator(PGPSignature.DEFAULT_CERTIFICATION, signingKeyPair, userId,
new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1), signatureSubpacketGenerator.generate(),
encryptionSubpacketGenerator.generate(), new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, HashAlgorithmTags.SHA256),
new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc)
.setProvider(new BouncyCastleProvider()).build(password.toCharArray()));
} catch (PGPException e) {
LOGGER.error("{}", e.getMessage());
generator = null;
}
return generator;
}
public static boolean generateKeyPair(String userId, String password, int keySize, OutputStream publicKey, OutputStream secrectKey) {
LOGGER.trace("generateKeyPair(String, String, int, OutputStream, OutputStream)");
LOGGER.trace("User ID: {}, Password: {}, Key Size: {}, Public Key: {}, Secret Key: {}", userId, password == null ?
"not set" : "********", keySize, publicKey == null ? "not set" : "set", secrectKey == null ? "not set" : "set");
boolean result = true;
LOGGER.debug("Generating key ring generator");
PGPKeyRingGenerator keyRingGenerator = createKeyRingGenerator(userId, password, keySize);
LOGGER.debug("Generating public key ring");
PGPPublicKeyRing publicKeyRing = keyRingGenerator.generatePublicKeyRing();
LOGGER.debug("Generating secret key ring");
PGPSecretKeyRing secretKeyRing = keyRingGenerator.generateSecretKeyRing();
LOGGER.debug("Wrapping public key target stream in ArmoredOutputStream");
try (OutputStream targetStream = new ArmoredOutputStream(publicKey)) {
LOGGER.info("Saving public key ring to public target");
publicKeyRing.encode(targetStream);
} catch (IOException e) {
LOGGER.error("{}", e.getMessage());
result &= false;
}
LOGGER.debug("Wrapping secret key target stream in ArmoredOutputStream");
try (OutputStream targetStream = new ArmoredOutputStream(secrectKey)) {
LOGGER.debug("Create secret key ring collection");
PGPSecretKeyRingCollection secretKeyRingCollection = new PGPSecretKeyRingCollection(Arrays.asList(secretKeyRing));
LOGGER.info("Saving secret key ring to secret key target");
secretKeyRingCollection.encode(targetStream);
} catch (IOException e) {
LOGGER.error("{}", e.getMessage());
result &= false;
}
return result;
}
}