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

me.sniggle.pgp.crypt.PGPMessageEncryptor Maven / Gradle / Ivy

The newest version!
package me.sniggle.pgp.crypt;

import me.sniggle.pgp.crypt.internal.BasePGPCommon;
import me.sniggle.pgp.crypt.internal.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;

/**
 * The the library dependent implementation of a MessageEncryptor
 *
 * @author iulius
 */
public class PGPMessageEncryptor extends BasePGPCommon implements MessageEncryptor {

  private static final Logger LOGGER = LoggerFactory.getLogger(PGPMessageEncryptor.class);

  public PGPMessageEncryptor() {
  }

  /**
   * encrypts and if possible (secret key and password provided) signs the target stream
   *
   * @param pgpSecretKey
   *    the secret key
   * @param password
   *    the password for the private key
   * @param inputDataName
   *    the name of the data
   * @param inputData
   *    the plain input data
   * @param encryptedDataStream
   *    the encrypted data stream
   * @throws PGPException
   * @throws IOException
   */
  private void encryptAndSign(PGPSecretKey pgpSecretKey, String password, String inputDataName, InputStream inputData, OutputStream encryptedDataStream) throws PGPException, IOException {
    LOGGER.trace("encryptAndSign(PGPSecretKey, String, String, InputStream, OutputStream)");
    LOGGER.trace("Secret Key: {}, Password: {}, Input Name: {}, Input Data: {}, Output Data: {}", pgpSecretKey == null ? "not set" : "set", password == null ? "not set" : "********", inputDataName, inputData == null ? "not set" : "set", encryptedDataStream == null ? "not set" : "set");
    PGPSignatureGenerator pgpSignatureGenerator = null;

    PGPPrivateKey signingKey = null;
    if (pgpSecretKey != null) {
      LOGGER.info("Retrieving signing key from secret key");
      signingKey = findPrivateKey(pgpSecretKey, password);
    }

    LOGGER.debug("Wrapping target stream in compressed data stream");
    PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(getCompressionAlgorithm());
    try ( OutputStream compressedDataStream = new BCPGOutputStream(compressedDataGenerator.open(encryptedDataStream)) ) {

      if (signingKey != null) {
        LOGGER.info("Preparing message signing");
        pgpSignatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(signingKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA256));
        pgpSignatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signingKey);
        pgpSignatureGenerator.generateOnePassVersion(false).encode(compressedDataStream);
      } else {
        LOGGER.info("No signing key provided. Encrypted data will be unsigned!");
      }

      PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(false);
      LOGGER.debug("Wrapping compressed data stream in literal data stream");
      try (OutputStream literalDataOutputStream = literalDataGenerator.open(compressedDataStream, PGPLiteralDataGenerator.BINARY, inputDataName, new Date(), new byte[4096])) {

        IOUtils.StreamHandler streamHandler = null;
        if( signingKey != null ) {
          final PGPSignatureGenerator callbackGenerator = pgpSignatureGenerator;
          streamHandler = new IOUtils.StreamHandler() {
            @Override
            public void handleStreamBuffer(byte[] buffer, int offset, int length) throws IOException {
              callbackGenerator.update(buffer, offset, length);
            }
          };
        }

        LOGGER.info("Encrypting data and saving to target stream");
        IOUtils.copy(inputData, literalDataOutputStream, new byte[4096], streamHandler);

        literalDataGenerator.close();
      }
      if (signingKey != null) {
        LOGGER.info("Generating data signature");
        pgpSignatureGenerator.generate().encode(compressedDataStream);
      }
      compressedDataGenerator.close();
    }
  }

  /**
   * accessor to the encryption algorithm constant to use, based of #isUnlimitedEncryptionStrength()
   *
   * @return the appropriate algorithm constant
   */
  protected  int getEncryptionAlgorithm() {
    return (isUnlimitedEncryptionStrength()) ? PGPEncryptedData.AES_256 : PGPEncryptedData.AES_128;
  }

  /**
   * @see MessageEncryptor#encrypt(InputStream, String, InputStream, OutputStream)
   *
   * @param publicKeyOfRecipient
   *    the public key stream of the message recipient
   * @param inputDataName
   *    the (file)name of the input data
   * @param plainInputData
   *    the input data stream
   * @param target
   *    the encrypted (ascii-armored) target stream
   * @return
   */
  @Override
  public boolean encrypt(InputStream publicKeyOfRecipient, String inputDataName, InputStream plainInputData, OutputStream target) {
    LOGGER.trace("encrypt(InputStream, String, InputStream, OutputStream)");
    LOGGER.trace("Public Key: {}, Input Name: {}, Input Data: {}, Output: {}",publicKeyOfRecipient == null ? "not set" : "set", inputDataName, plainInputData == null ? "not set" : "set", target == null ? "not set" : "set");
    return encrypt(publicKeyOfRecipient, null, null, null, inputDataName, plainInputData, target);
  }

  /**
   * @see MessageEncryptor#encrypt(InputStream, InputStream, String, String, String, InputStream, OutputStream)
   *
   * @param publicKeyOfRecipient
   *    the public key stream of the message recipient
   * @param privateKeyOfSender
   *    the private key stream of the message sender
   * @param userIdOfSender
   *    the user id of the message sender
   * @param passwordOfSendersPrivateKey
   *    the password for the private key of the sender
   * @param inputDataName
   *    the (file)name of the input data
   * @param plainInputData
   *    the input data stream
   * @param target
   *    the encrypted (ascii-armored) target stream
   * @return
   */
  @Override
  public boolean encrypt(InputStream publicKeyOfRecipient, InputStream privateKeyOfSender, String userIdOfSender, String passwordOfSendersPrivateKey, String inputDataName, InputStream plainInputData, OutputStream target) {
    LOGGER.trace("encrypt(InputStream, InputStream, String, String, String, InputStream, OutputStream)");
    LOGGER.trace("Public Key: {}, Private Key: {}, User ID: {}, Password: {}, Input Name: {}, Input Data: {}, Output: {}",
        publicKeyOfRecipient == null ? "not set" : "set", privateKeyOfSender == null ? "not set" : "set", userIdOfSender, passwordOfSendersPrivateKey == null ? "not set" : "********", inputDataName, plainInputData == null ? "not set" : "set", target == null ? "not set" : "set");
    boolean result = true;
    LOGGER.debug("Reading public key");
    PGPPublicKey pgpPublicKey = findPublicKey(publicKeyOfRecipient, new KeyFilter() {
      @Override
      public boolean accept(PGPPublicKey pgpKey) {
        return pgpKey.isEncryptionKey() && !pgpKey.isMasterKey();
      }
    });
    if( pgpPublicKey != null ) {
      LOGGER.debug("Wrapping target stream in ArmoredOutputStream");
      try( OutputStream wrappedTargetStream = new ArmoredOutputStream(target) ) {
        BcPGPDataEncryptorBuilder encryptorBuilder = new BcPGPDataEncryptorBuilder(getEncryptionAlgorithm());
        LOGGER.debug("Enabling integrity packet");
        encryptorBuilder.setWithIntegrityPacket(true);
        LOGGER.debug("Creating encrypted data generator");
        PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(encryptorBuilder);
        encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(pgpPublicKey));
        PGPSecretKey pgpSecretKey = null;
        if( privateKeyOfSender != null ) {
          LOGGER.debug("Looking up secret key");
          pgpSecretKey = findSecretKey(privateKeyOfSender, userIdOfSender);
        } else {
          LOGGER.info("No private key provided -> No signing of encrypted data");
        }
        LOGGER.debug("Wrapping target stream in encrypted output stream");
        try( OutputStream encryptedDataStream = encryptedDataGenerator.open(wrappedTargetStream, new byte[4096]) ) {
          LOGGER.info("Encrypting and optionally signing of input data");
          encryptAndSign(pgpSecretKey, passwordOfSendersPrivateKey, inputDataName, plainInputData, encryptedDataStream);
        }
      } catch (IOException | PGPException e) {
        LOGGER.error("{}", e.getMessage());
        result &= false;
      }
    } else {
      LOGGER.error("No public key found for encryption!");
      result &= false;
    }
    return result;
  }

  /**
   * @see MessageEncryptor#decrypt(String, InputStream, InputStream, OutputStream)
   *
   * @param passwordOfReceiversPrivateKey
   *    the password for the receiver's private key
   * @param privateKeyOfReceiver
   *    the receiver's private key
   * @param encryptedData
   *    the encrypted data
   * @param target
   *    the plain data stream
   * @return
   */
  @Override
  public boolean decrypt(String passwordOfReceiversPrivateKey, InputStream privateKeyOfReceiver, InputStream encryptedData, OutputStream target) {
    LOGGER.trace("decrypt(String, InputStream, InputStream, OutputStream)");
    LOGGER.trace("Password: {}, Private Key: {}, Encrypted Data: {}, Output: {}",
        passwordOfReceiversPrivateKey == null ? "not set" : "********", privateKeyOfReceiver == null ? "not set" : "set", encryptedData == null ? "not set" : "set", target == null ? "not set" : "set");
    return decrypt(passwordOfReceiversPrivateKey, privateKeyOfReceiver, null, encryptedData, target);
  }

  /**
   * @see MessageEncryptor#decrypt(String, InputStream, InputStream, InputStream, OutputStream)
   *
   * @param passwordOfReceiversPrivateKey
   *    the password of the receivers private key
   * @param privateKeyOfReceiver
   *    the receiver's private key
   * @param publicKeyOfSender
   *    the sender's public key
   * @param encryptedData
   *    the encrypted data
   * @param target
   *    the plain data stream
   * @return
   */
  @Override
  public boolean decrypt(String passwordOfReceiversPrivateKey, InputStream privateKeyOfReceiver, InputStream publicKeyOfSender, InputStream encryptedData, OutputStream target) {
    LOGGER.trace("decrypt(String, InputStream, InputStream, InputStream, OutputStream)");
    LOGGER.trace("Password: {}, Private Key: {}, Public Key: {}, Encrypted Data: {}, Output: {}",
        passwordOfReceiversPrivateKey == null ? "not set" : "set", privateKeyOfReceiver == null ? "not set" : "set", publicKeyOfSender == null ? "not set" : "set",
        encryptedData == null ? "not set" : "set", target == null ? "not set" : "set");
    boolean result = true;
    try {
      PGPPublicKeyRingCollection publicKeyRingCollection = null;
      if( publicKeyOfSender != null ) {
        LOGGER.debug("Wrapping public key in ArmoredInputStream");
        try( InputStream armoredInputStream = new ArmoredInputStream(publicKeyOfSender) ) {
          publicKeyRingCollection = new PGPPublicKeyRingCollection(armoredInputStream, new BcKeyFingerprintCalculator());
        }
      }
      LOGGER.debug("Retrieving DecoderStream from encrypted input");
      try( InputStream in = PGPUtil.getDecoderStream(encryptedData) ) {
        LOGGER.debug("Create PGP Object factory");

        PGPObjectFactory objectFactory = new PGPObjectFactory(in, new BcKeyFingerprintCalculator());
        PGPEncryptedDataList dataList;

        LOGGER.debug("Retrieve EncryptedDataList");
        Object firstObject = objectFactory.nextObject();
        if( firstObject instanceof PGPEncryptedDataList ) {
          dataList = (PGPEncryptedDataList)firstObject;
        } else {
          dataList = (PGPEncryptedDataList)objectFactory.nextObject();
        }

        PGPPrivateKey pgpPrivateKey = null;
        PGPEncryptedData pgpEncryptedData = null;
        LOGGER.debug("Iterating over encrypted data objects");
        Iterator iterator = dataList.getEncryptedDataObjects();
        while( pgpPrivateKey == null && iterator.hasNext() ) {
          pgpEncryptedData = iterator.next();
          LOGGER.debug("Looking up private key");
          pgpPrivateKey = findPrivateKey(privateKeyOfReceiver, ((PGPPublicKeyEncryptedData) pgpEncryptedData).getKeyID(), passwordOfReceiversPrivateKey);
        }
        PublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory = new BcPublicKeyDataDecryptorFactory(pgpPrivateKey);
        LOGGER.debug("Retrieving data stream from encrypted data");
        try( InputStream clearText = ((PGPPublicKeyEncryptedData)pgpEncryptedData).getDataStream(publicKeyDataDecryptorFactory)) {
          PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(clearText, new BcKeyFingerprintCalculator());
          Object message;
          PGPCompressedData compressedData;
          PGPOnePassSignatureList onePassSignatureList = null;
          PGPOnePassSignature onePassSignature = null;
          PGPLiteralData literalData;
          PGPSignatureList signatures = null;
          PGPPublicKey pgpPublicKey = null;

          while( (message = pgpObjectFactory.nextObject()) != null ) {
            if( message instanceof PGPCompressedData ) {
              compressedData = (PGPCompressedData) message;
              LOGGER.debug("Compressed data block found, creating new object factory with compressed data stream");
              pgpObjectFactory = new PGPObjectFactory(compressedData.getDataStream(), new BcKeyFingerprintCalculator());
            }
            if( message instanceof PGPLiteralData ) {
              literalData = (PGPLiteralData) message;
              LOGGER.debug("Reading literal data stream");
              try( InputStream literalDataStream = literalData.getInputStream() ) {
                IOUtils.StreamHandler streamHandler = null;
                if( onePassSignature != null ) {
                  final PGPOnePassSignature callbackSignature = onePassSignature;
                  streamHandler = new IOUtils.StreamHandler() {
                    @Override
                    public void handleStreamBuffer(byte[] buffer, int offset, int length) throws IOException {
                      callbackSignature.update(buffer, offset, length);
                    }
                  };
                }
                IOUtils.copy(literalDataStream, target, new byte[4096], streamHandler);
              }
            } else if( message instanceof PGPOnePassSignatureList ) {
              onePassSignatureList = (PGPOnePassSignatureList)message;
              if( publicKeyOfSender != null ) {
                LOGGER.info("Public key provided -> verifying message signature");
                onePassSignature = onePassSignatureList.get(0);
                pgpPublicKey = publicKeyRingCollection.getPublicKey(onePassSignature.getKeyID());
                onePassSignature.init(new BcPGPContentVerifierBuilderProvider(), pgpPublicKey);
              }
            } else if( message instanceof  PGPSignatureList){
              LOGGER.info("Signature List found for verification");
              signatures = (PGPSignatureList)message;
            }
          }

          LOGGER.debug("Iterating over signature list");
          for( int i = 0; onePassSignatureList != null && i < onePassSignatureList.size(); i++ ) {
            if( pgpPublicKey != null && signatures != null ) {
              LOGGER.info("Verifying signatures");
              PGPSignature signature = signatures.get(i);
              if( onePassSignature.verify(signature) ) {
                LOGGER.info("Signature verified");
                String userId = null;
                Iterator it = pgpPublicKey.getUserIDs();
                while (it.hasNext()) {
                  userId = it.next();
                  LOGGER.info("Signed by {}", userId);
                }
              } else {
                LOGGER.warn("Signature verification failed");
                result &= false;
              }
            }
          }


          if( pgpEncryptedData.isIntegrityProtected() ) {
            LOGGER.info("Performing integrity check on encrypted data");
            if( pgpEncryptedData.verify() ) {
              LOGGER.info("Data integrity verified");
            } else {
              LOGGER.warn("Data integrity verification failed");
              result &= false;
            }
          }
        }
      }
    } catch (IOException | PGPException e) {
      LOGGER.error("{}", e.getMessage());
      result &= false;
    }
    return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy