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;
    }
}