All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.craftercms.commons.crypto.PGPUtils Maven / Gradle / Ivy

/*
 * Copyright (C) 2007-2019 Crafter Software Corporation. All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package org.craftercms.commons.crypto;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.RSAPrivateCrtKey;
import java.util.Date;
import java.util.Iterator;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.RSASecretBCPGKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;

/**
 * Utility class to perform encryption and decryption using PGP keys.
 */
public abstract class PGPUtils {

    public static final String ALGORITHM = "RSA";
    public static final String PROVIDER = "BC";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * Creates a private/public PGP key pair.
     * @param length length in bytes for the keys
     * @param identity name used for the keys
     * @param password passphrase used for the private key
     * @param privateKeyStream stream to receive the encoded private key
     * @param publicKeyStream stream to receive the encoded public key
     * @throws NoSuchProviderException if there is an error with the security provider
     * @throws NoSuchAlgorithmException is there is an error with the security provider
     * @throws PGPException if there is an error creating the keys
     * @throws IOException if there is an error writing to the streams
     */
    public static void createKeyPair(int length, String identity, char[] password, OutputStream privateKeyStream,
                                     OutputStream
                                         publicKeyStream) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
        SecureRandom random = SecureRandom.getInstanceStrong();
        keyPairGenerator.initialize(length, random);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        PGPPublicKey publicKey = new JcaPGPKeyConverter().getPGPPublicKey(PGPPublicKey.RSA_GENERAL, keyPair
            .getPublic(), new Date());
        RSAPrivateCrtKey privateCrtKey = (RSAPrivateCrtKey) keyPair.getPrivate();
        RSASecretBCPGKey secretBCPGKey = new RSASecretBCPGKey(privateCrtKey.getPrivateExponent(), privateCrtKey
            .getPrimeP(), privateCrtKey.getPrimeQ());
        PGPPrivateKey privateKey = new PGPPrivateKey(publicKey.getKeyID(), publicKey.getPublicKeyPacket(),
            secretBCPGKey);
        PGPKeyPair pgpKeyPair = new PGPKeyPair(publicKey, privateKey);
        PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder().build().get
            (HashAlgorithmTags.SHA1);
        PGPSecretKey secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, pgpKeyPair, identity,
            calculator, null, null, new JcaPGPContentSignerBuilder(pgpKeyPair.getPublicKey().getAlgorithm(),
            HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, calculator)
            .setProvider(PROVIDER).build(password));


        try(ArmoredOutputStream privateArm = new ArmoredOutputStream(privateKeyStream);
            ArmoredOutputStream publicArm = new ArmoredOutputStream(publicKeyStream)) {
            secretKey.encode(privateArm);
            secretKey.getPublicKey().encode(publicArm);
        }
    }

    /**
     * Extracts the PGP public key from an encoded stream.
     * @param content stream to extract the key
     * @return key object
     * @throws IOException if there is an error reading the stream
     * @throws PGPException if the public key cannot be extracted
     */
    public static PGPPublicKey getPublicKey(InputStream content) throws Exception {
        InputStream in = PGPUtil.getDecoderStream(content);
        PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator());
        PGPPublicKey key = null;
        Iterator keyRings = keyRingCollection.getKeyRings();
        while(key == null && keyRings.hasNext()) {
            PGPPublicKeyRing keyRing = keyRings.next();
            Iterator keys = keyRing.getPublicKeys();
            while(key == null && keys.hasNext()) {
                PGPPublicKey current = keys.next();
                if(current.isEncryptionKey()) {
                    key = current;
                }
            }
        }
        return key;
    }

    /**
     * Performs encryption on a single file using a PGP public key.
     * @param path file to be encrypted
     * @param publicKeyStream stream providing the encoded public key
     * @param targetStream stream to receive the encrypted data
     * @throws IOException if there is an error reading or writing from the streams
     * @throws PGPException if the encryption process fails
     */
    public static void encrypt(Path path, InputStream publicKeyStream, OutputStream targetStream) throws Exception {
        PGPPublicKey publicKey = getPublicKey(publicKeyStream);

        try(ByteArrayOutputStream compressed = new ByteArrayOutputStream();
            ArmoredOutputStream armOut = new ArmoredOutputStream(targetStream)) {
            PGPCompressedDataGenerator compressedGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
            PGPUtil.writeFileToLiteralData(compressedGenerator.open(compressed), PGPLiteralData.BINARY, path.toFile());
            compressedGenerator.close();

            JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
                .setWithIntegrityPacket(true).setSecureRandom(new SecureRandom()).setProvider(PROVIDER);
            PGPEncryptedDataGenerator dataGenerator = new PGPEncryptedDataGenerator(encryptorBuilder);
            JcePublicKeyKeyEncryptionMethodGenerator methodGenerator = new JcePublicKeyKeyEncryptionMethodGenerator
                (publicKey).setProvider(PROVIDER).setSecureRandom(new SecureRandom());
            dataGenerator.addMethod(methodGenerator);

            byte[] compressedData = compressed.toByteArray();
            OutputStream encryptedOut = dataGenerator.open(armOut, compressedData.length);
            encryptedOut.write(compressedData);
            encryptedOut.close();
        }
    }

    /**
     * Performs decryption of a given stream using a PGP private key.
     * @param encryptedStream stream providing the encrypted data
     * @param targetStream stream to receive the decrypted data
     * @param privateKeyStream stream providing the encoded PGP private key
     * @param password passphrase for the private key
     * @throws IOException if there is an error reading or writing from the streams
     * @throws PGPException if the decryption process fails
     */
    @SuppressWarnings("rawtypes")
    public static void decrypt(InputStream encryptedStream, OutputStream targetStream, InputStream
        privateKeyStream, char[] password) throws Exception {

        BcKeyFingerprintCalculator calculator = new BcKeyFingerprintCalculator();
        PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(encryptedStream), calculator);
        PGPEncryptedDataList dataList;
        Object object = factory.nextObject();
        if(object instanceof PGPEncryptedDataList) {
            dataList = (PGPEncryptedDataList) object;
        } else {
            dataList = (PGPEncryptedDataList) factory.nextObject();
        }
        Iterator objects = dataList.getEncryptedDataObjects();
        PGPPrivateKey privateKey = null;
        PGPPublicKeyEncryptedData data = null;
        while(privateKey == null && objects.hasNext()) {
            data = (PGPPublicKeyEncryptedData) objects.next();
            privateKey = findSecretKey(privateKeyStream, data.getKeyID(), password);
        }
        if(privateKey == null) {
            throw new IllegalArgumentException("Secret key for message not found.");
        }

        decryptData(privateKey, data, calculator, targetStream);
    }

    /**
     * Extracts the PGP private key from an encoded stream.
     * @param keyStream stream providing the encoded private key
     * @param keyId id of the secret key to extract
     * @param password passphrase for the secret key
     * @return the private key object
     * @throws IOException if there is an error reading from the stream
     * @throws PGPException if the secret key cannot be extracted
     */
    protected static PGPPrivateKey findSecretKey(InputStream keyStream, long keyId, char[] password) throws Exception {
        PGPSecretKeyRingCollection keyRings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyStream), new
            BcKeyFingerprintCalculator());
        PGPSecretKey secretKey = keyRings.getSecretKey(keyId);
        if(secretKey == null) {
            return null;
        }
        PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder(
            new JcaPGPDigestCalculatorProviderBuilder().setProvider(PROVIDER).build())
            .setProvider(PROVIDER).build(password);
        return secretKey.extractPrivateKey(decryptor);
    }

    /**
     * Performs the decryption of the given data.
     * @param privateKey PGP Private Key to decrypt
     * @param data encrypted data
     * @param calculator instance of {@link BcKeyFingerprintCalculator}
     * @param targetStream stream to receive the decrypted data
     * @throws PGPException if the decryption process fails
     * @throws IOException if the stream write operation fails
     */
    protected static void decryptData(final PGPPrivateKey privateKey, final PGPPublicKeyEncryptedData data,
                                      final BcKeyFingerprintCalculator calculator, final OutputStream targetStream)
        throws PGPException, IOException {
        PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder().setProvider
            (PROVIDER).setContentProvider(PROVIDER).build(privateKey);

        InputStream content = data.getDataStream(decryptorFactory);

        PGPObjectFactory plainFactory = new PGPObjectFactory(content, calculator);

        Object message = plainFactory.nextObject();

        if(message instanceof PGPCompressedData) {
            PGPCompressedData compressedData = (PGPCompressedData) message;
            PGPObjectFactory compressedFactory = new PGPObjectFactory(compressedData.getDataStream(), calculator);
            message = compressedFactory.nextObject();
        }

        if(message instanceof PGPLiteralData) {
            PGPLiteralData literalData = (PGPLiteralData) message;
            try(InputStream literalStream = literalData.getInputStream()) {
                IOUtils.copy(literalStream, targetStream);
            }
        } else if(message instanceof PGPOnePassSignatureList) {
            throw new PGPException("Encrypted message contains a signed message - not literal data.");
        } else {
            throw new PGPException("Message is not a simple encrypted file - type unknown.");
        }

        if(data.isIntegrityProtected() && !data.verify()) {
            throw new PGPException("Message failed integrity check");
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy