Example usage for org.bouncycastle.openpgp PGPSignatureGenerator generateOnePassVersion

List of usage examples for org.bouncycastle.openpgp PGPSignatureGenerator generateOnePassVersion

Introduction

In this page you can find the example usage for org.bouncycastle.openpgp PGPSignatureGenerator generateOnePassVersion.

Prototype

public PGPOnePassSignature generateOnePassVersion(boolean isNested) throws PGPException 

Source Link

Document

Return the one pass header associated with the current signature.

Usage

From source file:org.apache.camel.converter.crypto.PGPDataFormat.java

License:Apache License

protected PGPSignatureGenerator createSignatureGenerator(Exchange exchange, OutputStream out)
        throws IOException, PGPException, NoSuchProviderException, NoSuchAlgorithmException {

    String sigKeyFileName = findSignatureKeyFileName(exchange);
    String sigKeyUserid = findSignatureKeyUserid(exchange);
    String sigKeyPassword = findSignatureKeyPassword(exchange);
    byte[] sigKeyRing = findSignatureKeyRing(exchange);

    if ((sigKeyFileName == null && sigKeyRing == null) || sigKeyUserid == null || sigKeyPassword == null) {
        return null;
    }//from   w  w  w.  jav  a2s.  co  m

    PGPSecretKey sigSecretKey = PGPDataFormatUtil.findSecretKey(exchange.getContext(), sigKeyFileName,
            sigKeyRing, sigKeyPassword, sigKeyUserid, getProvider());
    if (sigSecretKey == null) {
        throw new IllegalArgumentException(String.format(
                "Cannot PGP encrypt message. No secret key found for User ID %s. Either add a key with this User ID to the secret keyring or change the configured User ID.",
                sigKeyUserid));
    }

    PGPPrivateKey sigPrivateKey = sigSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder()
            .setProvider(getProvider()).build(sigKeyPassword.toCharArray()));
    if (sigPrivateKey == null) {
        // this exception will never happen
        throw new IllegalArgumentException("Signature private key is null, cannot proceed");
    }

    PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
    spGen.setSignerUserID(false, sigKeyUserid);

    int algorithm = sigSecretKey.getPublicKey().getAlgorithm();
    PGPSignatureGenerator sigGen = new PGPSignatureGenerator(
            new JcaPGPContentSignerBuilder(algorithm, findHashAlgorithm(exchange)).setProvider(getProvider()));
    sigGen.init(PGPSignature.BINARY_DOCUMENT, sigPrivateKey);
    sigGen.setHashedSubpackets(spGen.generate());
    sigGen.generateOnePassVersion(false).encode(out);
    return sigGen;
}

From source file:org.apache.camel.converter.crypto.PGPKeyAccessDataFormat.java

License:Apache License

protected List<PGPSignatureGenerator> createSignatureGenerator(Exchange exchange, OutputStream out)
        throws Exception {

    if (secretKeyAccessor == null) {
        return null;
    }/*w w w .j a va2  s.  c  o m*/

    List<String> sigKeyUserids = determineSignaturenUserIds(exchange);
    List<PGPSecretKeyAndPrivateKeyAndUserId> sigSecretKeysWithPrivateKeyAndUserId = secretKeyAccessor
            .getSignerKeys(exchange, sigKeyUserids);
    if (sigSecretKeysWithPrivateKeyAndUserId.isEmpty()) {
        return null;
    }

    exchange.getOut().setHeader(NUMBER_OF_SIGNING_KEYS,
            Integer.valueOf(sigSecretKeysWithPrivateKeyAndUserId.size()));

    List<PGPSignatureGenerator> sigGens = new ArrayList<PGPSignatureGenerator>();
    for (PGPSecretKeyAndPrivateKeyAndUserId sigSecretKeyWithPrivateKeyAndUserId : sigSecretKeysWithPrivateKeyAndUserId) {
        PGPPrivateKey sigPrivateKey = sigSecretKeyWithPrivateKeyAndUserId.getPrivateKey();

        PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
        spGen.setSignerUserID(false, sigSecretKeyWithPrivateKeyAndUserId.getUserId());

        int algorithm = sigSecretKeyWithPrivateKeyAndUserId.getSecretKey().getPublicKey().getAlgorithm();
        PGPSignatureGenerator sigGen = new PGPSignatureGenerator(
                new JcaPGPContentSignerBuilder(algorithm, findHashAlgorithm(exchange))
                        .setProvider(getProvider()));
        sigGen.init(PGPSignature.BINARY_DOCUMENT, sigPrivateKey);
        sigGen.setHashedSubpackets(spGen.generate());
        sigGen.generateOnePassVersion(false).encode(out);
        sigGens.add(sigGen);
    }
    return sigGens;
}

From source file:org.brownsocks.payments.gateways.enets.pgp.BCPGPProvider.java

@Override
public String signAndEncrypt(String message) throws IOException {

    try {/*from   w w w.  j a  va 2 s  .  c  o  m*/
        /* Final < Armored < Crypted < Clear PGP */
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ArmoredOutputStream armoredOutput = new ArmoredOutputStream(out);
        PGPEncryptedDataGenerator crypter = new PGPEncryptedDataGenerator(PGPEncryptedDataGenerator.S2K_SHA1,
                new SecureRandom(), _provider);
        crypter.addMethod(getRemotePublicKey());
        BCPGOutputStream pgpOut = new BCPGOutputStream(crypter.open(armoredOutput, new byte[512]));

        /* Prepare for signing */
        PGPSignatureGenerator signer = new PGPSignatureGenerator(getSigningPublicKey().getAlgorithm(),
                PGPUtil.SHA1, _provider);
        signer.initSign(PGPSignature.BINARY_DOCUMENT, getSigningPrivateKey());

        /* Output the standard header */
        signer.generateOnePassVersion(false).encode(pgpOut);

        /* Output the literal data */
        PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(true);
        literalDataGenerator.open(pgpOut, 'b', "bar", message.getBytes().length, new Date())
                .write(message.getBytes());

        /* Calculate signature and output it */
        signer.update(message.getBytes());
        signer.generate().encode(pgpOut);

        pgpOut.close();
        armoredOutput.close();
        out.close();

        byte[] result = out.toByteArray();

        // brain dead UMAPI adds an extra base64 encoding on top of the ASCII armored string. Go figure.
        return new String(Base64.encode(result));

    } catch (PGPException pgpException) {
        throw new IOException("PGP subsystem problem.", pgpException);

    } catch (NoSuchAlgorithmException noSuchAlgorithmException) {
        throw new IOException("Missing algorithm. Are you running a compatible JVM/Bouncycastle version?",
                noSuchAlgorithmException);

    } catch (SignatureException signatureException) {
        throw new IOException("PGP subsystem problem.", signatureException);

    } catch (NoSuchProviderException noSuchProviderException) {
        throw new IOException("Missing provider. Are you running a compatible JVM/Bouncycastle version?",
                noSuchProviderException);

    }

}

From source file:org.kontalk.crypto.Coder.java

License:Open Source License

/**
 * Creates encrypted and signed message body.
 * Errors that may occur are saved to the message.
 * @param message/*from  ww w.j av a 2s  .  c  om*/
 * @return the encrypted and signed text.
 */
public static Optional<byte[]> processOutMessage(OutMessage message) {
    if (message.getCoderStatus().getEncryption() != Encryption.DECRYPTED) {
        LOGGER.warning("message does not want to be encrypted");
        return Optional.empty();
    }

    LOGGER.info("encrypting message...");

    // get keys
    KeysResult keys = getKeys(message.getUser());
    if (keys.myKey == null || keys.otherKey == null) {
        message.setSecurityErrors(keys.errors);
        return Optional.empty();
    }

    // secure the message against the most basic attacks using Message/CPIM
    String from = keys.myKey.getUserId();
    String to = keys.otherKey.userID + "; ";
    String mime = "text/plain";
    // TODO encrypt more possible content
    String text = message.getContent().getPlainText();
    CPIMMessage cpim = new CPIMMessage(from, to, new Date(), mime, text);
    byte[] plainText;
    try {
        plainText = cpim.toByteArray();
    } catch (UnsupportedEncodingException ex) {
        LOGGER.log(Level.WARNING, "UTF-8 not supported", ex);
        plainText = cpim.toString().getBytes();
    }

    // setup data encryptor & generator
    BcPGPDataEncryptorBuilder encryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_192);
    encryptor.setWithIntegrityPacket(true);
    encryptor.setSecureRandom(new SecureRandom());

    // add public key recipients
    PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(encryptor);
    //for (PGPPublicKey rcpt : mRecipients)
    encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(keys.otherKey.encryptKey));

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ByteArrayInputStream in = new ByteArrayInputStream(plainText);
    try { // catch all io and pgp exceptions

        OutputStream encryptedOut = encGen.open(out, new byte[BUFFER_SIZE]);

        // setup compressed data generator
        PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
        OutputStream compressedOut = compGen.open(encryptedOut, new byte[BUFFER_SIZE]);

        // setup signature generator
        int algo = keys.myKey.getPublicEncryptionKey().getAlgorithm();
        PGPSignatureGenerator sigGen = new PGPSignatureGenerator(
                new BcPGPContentSignerBuilder(algo, HashAlgorithmTags.SHA1));
        sigGen.init(PGPSignature.BINARY_DOCUMENT, keys.myKey.getPrivateEncryptionKey());

        PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
        spGen.setSignerUserID(false, keys.myKey.getUserId());
        sigGen.setUnhashedSubpackets(spGen.generate());

        sigGen.generateOnePassVersion(false).encode(compressedOut);

        // Initialize literal data generator
        PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
        OutputStream literalOut = literalGen.open(compressedOut, PGPLiteralData.BINARY, "", new Date(),
                new byte[BUFFER_SIZE]);

        // read the "in" stream, compress, encrypt and write to the "out" stream
        // this must be done if clear data is bigger than the buffer size
        // but there are other ways to optimize...
        byte[] buf = new byte[BUFFER_SIZE];
        int len;
        while ((len = in.read(buf)) > 0) {
            literalOut.write(buf, 0, len);
            try {
                sigGen.update(buf, 0, len);
            } catch (SignatureException ex) {
                LOGGER.log(Level.WARNING, "can't read data for signature", ex);
                message.setSecurityErrors(EnumSet.of(Error.INVALID_SIGNATURE_DATA));
                return Optional.empty();
            }
        }

        in.close();
        literalGen.close();

        // generate the signature, compress, encrypt and write to the "out" stream
        try {
            sigGen.generate().encode(compressedOut);
        } catch (SignatureException ex) {
            LOGGER.log(Level.WARNING, "can't create signature", ex);
            message.setSecurityErrors(EnumSet.of(Error.INVALID_SIGNATURE_DATA));
            return Optional.empty();
        }
        compGen.close();
        encGen.close();

    } catch (IOException | PGPException ex) {
        LOGGER.log(Level.WARNING, "can't encrypt message", ex);
        message.setSecurityErrors(EnumSet.of(Error.UNKNOWN_ERROR));
        return Optional.empty();
    }

    LOGGER.info("encryption successful");
    return Optional.of(out.toByteArray());
}

From source file:org.opentestsystem.delivery.testreg.transformer.GPGEncryptor.java

License:Open Source License

/**
 * Uses the Legion of the Bouncy Castle (aka BouncyCastle) PGP API to encrypt, compress, and sign the input.
 * //from   w  w w  .  ja v a 2 s. c  o  m
 * The configured landing zone public key is used to encrypt the input. Only the landing zone private key will be
 * able to decrypt.
 * 
 * The configured test registration private key is used to sign the input. This can be verified by the landing zone
 * to prove that this specific test registration instance created the data.
 * 
 * @param input
 *            A byte array
 * @return A byte array comprised of a PGP/GPG compatible binary encrypted and signed output
 */
@Transformer
public Message<File> encryptStream(final File input, final @Header("dwBatchUuid") String dwBatchUuid,
        final @Header("fileSuffix") String fileSuffix, final @Header("recordsSent") int recordsSent,
        final @Header("tempPaths") List<Path> tempPaths,
        final @Header("dwConfigType") DwConfigType dwConfigType) {

    String debugPrefix = dwConfigType + " DW Config: ";

    long curTime = System.currentTimeMillis();
    File tmpEncFile;

    try {

        PGPPublicKey landingZonePubKey = findLandingZonePublicKey(dwConfigType);
        PGPSecretKey testRegSecretKey = findTestRegSecretKey();

        PGPPrivateKey testRegPrivateKey = testRegSecretKey.extractPrivateKey(
                new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(getPassphrase()));

        // ////////////////////
        // setup encryptor

        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256).setWithIntegrityPacket(true)
                        .setSecureRandom(new SecureRandom()).setProvider("BC"));

        encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(landingZonePubKey).setProvider("BC"));

        // This outputstream, encryptedSignedOutputStream is the ultimate target that will contain the encrypted and
        // signed output
        Path tempEncPath = Files.createTempFile(DwBatchHandler.DW_ENC_TMP_PREFIX,
                (dwConfigType == DwConfigType.SBAC ? DwBatchHandler.SBAC_DW_NAME : DwBatchHandler.LOCAL_DW_NAME)
                        + fileSuffix);
        tempPaths.add(tempEncPath);
        tmpEncFile = tempEncPath.toFile();
        FileOutputStream encryptedSignedOutputStream = new FileOutputStream(tmpEncFile);

        LOGGER.debug(debugPrefix + "Created temp encrypted output file " + tmpEncFile.getAbsolutePath());

        OutputStream encryptOutStream = encGen.open(encryptedSignedOutputStream, new byte[BUFFER_SIZE]);

        // ////////////////////////////
        // setup data compression

        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
        OutputStream compressOutStream = comData.open(encryptOutStream);

        // /////////////////////
        // sign encrypted file with test reg private key

        // create a signature generator
        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
                new JcaPGPContentSignerBuilder(testRegSecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1)
                        .setProvider("BC"));
        signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, testRegPrivateKey);

        @SuppressWarnings("unchecked")
        Iterator<String> it = testRegSecretKey.getPublicKey().getUserIDs();

        if (it.hasNext()) {
            PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
            spGen.setSignerUserID(false, it.next());
            signatureGenerator.setHashedSubpackets(spGen.generate());
        }

        // setup signature generator to encode the contents of the compressed output stream
        signatureGenerator.generateOnePassVersion(false).encode(compressOutStream);

        // create a PGP Literal Data Generator and open it to wrap the compression output stream
        PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();

        OutputStream signedOutStream = lGen.open(compressOutStream, PGPLiteralData.BINARY, "",
                new java.util.Date(), new byte[BUFFER_SIZE]);

        // create an input stream out of the input bytes
        FileInputStream clearInputStream = new FileInputStream(input);

        // read the input and write all data to the signing output stream, also update the signature
        // generator with the same input data
        byte[] buf = new byte[BUFFER_SIZE];
        int len;
        while ((len = clearInputStream.read(buf)) > 0) {
            signedOutStream.write(buf, 0, len);
            signatureGenerator.update(buf, 0, len);
        }

        // close everything and generate the final signature
        signedOutStream.close();
        lGen.close();
        signatureGenerator.generate().encode(compressOutStream);
        compressOutStream.close();
        comData.close();
        encryptOutStream.close();
        encGen.close();
        clearInputStream.close();

        encryptedSignedOutputStream.close();

    } catch (IOException | PGPException | SignatureException e) {
        throw new GPGEncryptionException(debugPrefix + "Failure to encrypt and sign input", e);
    }

    LOGGER.debug(debugPrefix + "Generated encrypted data in " + (System.currentTimeMillis() - curTime));

    return MessageBuilder.withPayload(tmpEncFile).setHeader("dwBatchUuid", dwBatchUuid)
            .setHeader("fileSuffix", fileSuffix).setHeader("recordsSent", recordsSent)
            .setHeader("tempPaths", tempPaths).setHeader("dwConfigType", dwConfigType).build();
}

From source file:org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation.java

License:Open Source License

/**
 * Signs and/or encrypts data based on parameters of class
 *//*w  w w .j a v a 2  s  .co m*/
private PgpSignEncryptResult executeInternal(PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput,
        InputData inputData, OutputStream outputStream) {

    int indent = 0;
    OperationLog log = new OperationLog();

    log.add(LogType.MSG_PSE, indent);
    indent += 1;

    PgpSignEncryptData data = input.getData();
    boolean enableSignature = data.getSignatureMasterKeyId() != Constants.key.none;
    boolean enableEncryption = ((data.getEncryptionMasterKeyIds() != null
            && data.getEncryptionMasterKeyIds().length > 0) || data.getSymmetricPassphrase() != null);
    boolean enableCompression = (data.getCompressionAlgorithm() != CompressionAlgorithmTags.UNCOMPRESSED);

    Log.d(Constants.TAG,
            "enableSignature:" + enableSignature + "\nenableEncryption:" + enableEncryption
                    + "\nenableCompression:" + enableCompression + "\nenableAsciiArmorOutput:"
                    + data.isEnableAsciiArmorOutput() + "\nisHiddenRecipients:" + data.isHiddenRecipients());

    // add additional key id to encryption ids (mostly to do self-encryption)
    if (enableEncryption && data.getAdditionalEncryptId() != Constants.key.none) {
        data.setEncryptionMasterKeyIds(
                Arrays.copyOf(data.getEncryptionMasterKeyIds(), data.getEncryptionMasterKeyIds().length + 1));
        data.getEncryptionMasterKeyIds()[data.getEncryptionMasterKeyIds().length - 1] = data
                .getAdditionalEncryptId();
    }

    ArmoredOutputStream armorOut = null;
    OutputStream out;
    if (data.isEnableAsciiArmorOutput()) {
        armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
        if (data.getVersionHeader() != null) {
            armorOut.setHeader("Version", data.getVersionHeader());
        }
        // if we have a charset, put it in the header
        if (data.getCharset() != null) {
            armorOut.setHeader("Charset", data.getCharset());
        }
        // add proprietary header to indicate that this is a key backup
        if (data.isAddBackupHeader()) {
            armorOut.setHeader("BackupVersion", "2");
        }
        out = armorOut;
    } else {
        out = outputStream;
    }

    /* Get keys for signature generation for later usage */
    CanonicalizedSecretKey signingKey = null;
    if (enableSignature) {

        updateProgress(R.string.progress_extracting_signature_key, 0, 100);

        try {
            long signingMasterKeyId = data.getSignatureMasterKeyId();
            long signingSubKeyId = data.getSignatureSubKeyId();

            CanonicalizedSecretKeyRing signingKeyRing = mProviderHelper
                    .getCanonicalizedSecretKeyRing(signingMasterKeyId);
            signingKey = signingKeyRing.getSecretKey(data.getSignatureSubKeyId());

            // Make sure key is not expired or revoked
            if (signingKeyRing.isExpired() || signingKeyRing.isRevoked() || signingKey.isExpired()
                    || signingKey.isRevoked()) {
                log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent);
                return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
            }

            // Make sure we are allowed to sign here!
            if (!signingKey.canSign()) {
                log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
                return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
            }

            switch (mProviderHelper.getCachedPublicKeyRing(signingMasterKeyId)
                    .getSecretKeyType(signingSubKeyId)) {
            case DIVERT_TO_CARD:
            case PASSPHRASE_EMPTY: {
                if (!signingKey.unlock(new Passphrase())) {
                    throw new AssertionError(
                            "PASSPHRASE_EMPTY/DIVERT_TO_CARD keyphrase not unlocked with empty passphrase."
                                    + " This is a programming error!");
                }
                break;
            }

            case PIN:
            case PATTERN:
            case PASSPHRASE: {
                Passphrase localPassphrase = cryptoInput.getPassphrase();
                if (localPassphrase == null) {
                    try {
                        localPassphrase = getCachedPassphrase(signingMasterKeyId, signingKey.getKeyId());
                    } catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
                    }
                }
                if (localPassphrase == null) {
                    log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
                    return new PgpSignEncryptResult(log,
                            RequiredInputParcel.createRequiredSignPassphrase(signingMasterKeyId,
                                    signingKey.getKeyId(), cryptoInput.getSignatureTime()),
                            cryptoInput);
                }
                if (!signingKey.unlock(localPassphrase)) {
                    log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
                    return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
                }
                break;
            }

            case GNU_DUMMY: {
                log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
                return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
            }
            default: {
                throw new AssertionError("Unhandled SecretKeyType! (should not happen)");
            }

            }

        } catch (ProviderHelper.NotFoundException e) {
            log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
            return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
        } catch (PgpGeneralException e) {
            log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
            return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
        }

        // Use requested hash algo
        int requestedAlgorithm = data.getSignatureHashAlgorithm();
        if (requestedAlgorithm == PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) {
            data.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM);
        }
    }
    updateProgress(R.string.progress_preparing_streams, 2, 100);

    /* Initialize PGPEncryptedDataGenerator for later usage */
    PGPEncryptedDataGenerator cPk = null;
    if (enableEncryption) {

        // Use requested encryption algo
        int algo = data.getSymmetricEncryptionAlgorithm();
        if (algo == PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) {
            algo = PgpSecurityConstants.DEFAULT_SYMMETRIC_ALGORITHM;
        }
        JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder(algo)
                .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
                .setWithIntegrityPacket(data.isIntegrityProtected());

        cPk = new PGPEncryptedDataGenerator(encryptorBuilder);

        if (data.getSymmetricPassphrase() != null) {
            // Symmetric encryption
            log.add(LogType.MSG_PSE_SYMMETRIC, indent);

            JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = new JcePBEKeyEncryptionMethodGenerator(
                    data.getSymmetricPassphrase().getCharArray());
            cPk.addMethod(symmetricEncryptionGenerator);
        } else {
            log.add(LogType.MSG_PSE_ASYMMETRIC, indent);

            // Asymmetric encryption
            for (long id : data.getEncryptionMasterKeyIds()) {
                try {
                    CanonicalizedPublicKeyRing keyRing = mProviderHelper
                            .getCanonicalizedPublicKeyRing(KeyRings.buildUnifiedKeyRingUri(id));
                    Set<Long> encryptSubKeyIds = keyRing.getEncryptIds();
                    for (Long subKeyId : encryptSubKeyIds) {
                        CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId);
                        cPk.addMethod(key.getPubKeyEncryptionGenerator(data.isHiddenRecipients()));
                        log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
                                KeyFormattingUtils.convertKeyIdToHex(subKeyId));
                    }
                    if (encryptSubKeyIds.isEmpty()) {
                        log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id));
                        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
                    }
                    // Make sure key is not expired or revoked
                    if (keyRing.isExpired() || keyRing.isRevoked()) {
                        log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent);
                        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
                    }
                } catch (ProviderHelper.NotFoundException e) {
                    log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id));
                    return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
                }
            }
        }
    }

    /* Initialize signature generator object for later usage */
    PGPSignatureGenerator signatureGenerator = null;
    if (enableSignature) {
        updateProgress(R.string.progress_preparing_signature, 4, 100);

        try {
            boolean cleartext = data.isCleartextSignature() && data.isEnableAsciiArmorOutput()
                    && !enableEncryption;
            signatureGenerator = signingKey.getDataSignatureGenerator(data.getSignatureHashAlgorithm(),
                    cleartext, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
        } catch (PgpGeneralException e) {
            log.add(LogType.MSG_PSE_ERROR_NFC, indent);
            return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
        }
    }

    ProgressScaler progressScaler = new ProgressScaler(mProgressable, 8, 95, 100);
    PGPCompressedDataGenerator compressGen = null;
    OutputStream pOut;
    OutputStream encryptionOut = null;
    BCPGOutputStream bcpgOut;

    ByteArrayOutputStream detachedByteOut = null;
    ArmoredOutputStream detachedArmorOut = null;
    BCPGOutputStream detachedBcpgOut = null;

    long opTime, startTime = System.currentTimeMillis();

    try {

        if (enableEncryption) {
            /* actual encryption */
            updateProgress(R.string.progress_encrypting, 8, 100);
            log.add(enableSignature ? LogType.MSG_PSE_SIGCRYPTING : LogType.MSG_PSE_ENCRYPTING, indent);
            indent += 1;

            encryptionOut = cPk.open(out, new byte[1 << 16]);

            if (enableCompression) {
                log.add(LogType.MSG_PSE_COMPRESSING, indent);

                // Use preferred compression algo
                int algo = data.getCompressionAlgorithm();
                if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
                    algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
                }
                compressGen = new PGPCompressedDataGenerator(algo);
                bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
            } else {
                bcpgOut = new BCPGOutputStream(encryptionOut);
            }

            if (enableSignature) {
                signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
            }

            PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
            char literalDataFormatTag;
            if (data.isCleartextSignature()) {
                literalDataFormatTag = PGPLiteralData.UTF8;
            } else {
                literalDataFormatTag = PGPLiteralData.BINARY;
            }
            pOut = literalGen.open(bcpgOut, literalDataFormatTag, inputData.getOriginalFilename(), new Date(),
                    new byte[1 << 16]);

            long alreadyWritten = 0;
            int length;
            byte[] buffer = new byte[1 << 16];
            InputStream in = new BufferedInputStream(inputData.getInputStream());
            while ((length = in.read(buffer)) > 0) {
                pOut.write(buffer, 0, length);

                // update signature buffer if signature is requested
                if (enableSignature) {
                    signatureGenerator.update(buffer, 0, length);
                }

                alreadyWritten += length;
                if (inputData.getSize() > 0) {
                    long progress = 100 * alreadyWritten / inputData.getSize();
                    progressScaler.setProgress((int) progress, 100);
                }
            }

            literalGen.close();
            indent -= 1;

        } else if (enableSignature && data.isCleartextSignature() && data.isEnableAsciiArmorOutput()) {
            /* cleartext signature: sign-only of ascii text */

            updateProgress(R.string.progress_signing, 8, 100);
            log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent);

            // write -----BEGIN PGP SIGNED MESSAGE-----
            armorOut.beginClearText(data.getSignatureHashAlgorithm());

            InputStream in = new BufferedInputStream(inputData.getInputStream());
            final BufferedReader reader = new BufferedReader(new InputStreamReader(in));

            // update signature buffer with first line
            processLine(reader.readLine(), armorOut, signatureGenerator);

            // TODO: progress: fake annealing?
            while (true) {
                String line = reader.readLine();

                // end cleartext signature with newline, see http://tools.ietf.org/html/rfc4880#section-7
                if (line == null) {
                    armorOut.write(NEW_LINE);
                    break;
                }

                armorOut.write(NEW_LINE);

                // update signature buffer with input line
                signatureGenerator.update(NEW_LINE);
                processLine(line, armorOut, signatureGenerator);
            }

            armorOut.endClearText();

            pOut = new BCPGOutputStream(armorOut);
        } else if (enableSignature && data.isDetachedSignature()) {
            /* detached signature */

            updateProgress(R.string.progress_signing, 8, 100);
            log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);

            InputStream in = new BufferedInputStream(inputData.getInputStream());

            // handle output stream separately for detached signatures
            detachedByteOut = new ByteArrayOutputStream();
            OutputStream detachedOut = detachedByteOut;
            if (data.isEnableAsciiArmorOutput()) {
                detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
                if (data.getVersionHeader() != null) {
                    detachedArmorOut.setHeader("Version", data.getVersionHeader());
                }

                detachedOut = detachedArmorOut;
            }
            detachedBcpgOut = new BCPGOutputStream(detachedOut);

            long alreadyWritten = 0;
            int length;
            byte[] buffer = new byte[1 << 16];
            while ((length = in.read(buffer)) > 0) {
                // no output stream is written, no changed to original data!

                signatureGenerator.update(buffer, 0, length);

                alreadyWritten += length;
                if (inputData.getSize() > 0) {
                    long progress = 100 * alreadyWritten / inputData.getSize();
                    progressScaler.setProgress((int) progress, 100);
                }
            }

            pOut = null;
        } else if (enableSignature && !data.isCleartextSignature() && !data.isDetachedSignature()) {
            /* sign-only binary (files/data stream) */

            updateProgress(R.string.progress_signing, 8, 100);
            log.add(LogType.MSG_PSE_SIGNING, indent);

            InputStream in = new BufferedInputStream(inputData.getInputStream());

            if (enableCompression) {
                // Use preferred compression algo
                int algo = data.getCompressionAlgorithm();
                if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
                    algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
                }

                compressGen = new PGPCompressedDataGenerator(algo);
                bcpgOut = new BCPGOutputStream(compressGen.open(out));
            } else {
                bcpgOut = new BCPGOutputStream(out);
            }

            signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);

            PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
            pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, inputData.getOriginalFilename(), new Date(),
                    new byte[1 << 16]);

            long alreadyWritten = 0;
            int length;
            byte[] buffer = new byte[1 << 16];
            while ((length = in.read(buffer)) > 0) {
                pOut.write(buffer, 0, length);

                signatureGenerator.update(buffer, 0, length);

                alreadyWritten += length;
                if (inputData.getSize() > 0) {
                    long progress = 100 * alreadyWritten / inputData.getSize();
                    progressScaler.setProgress((int) progress, 100);
                }
            }

            literalGen.close();
        } else {
            throw new AssertionError("cannot clearsign in non-ascii armored text, this is a bug!");
        }

        if (enableSignature) {
            updateProgress(R.string.progress_generating_signature, 95, 100);
            try {
                if (detachedBcpgOut != null) {
                    signatureGenerator.generate().encode(detachedBcpgOut);
                } else {
                    signatureGenerator.generate().encode(pOut);
                }
            } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
                // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
                log.add(LogType.MSG_PSE_PENDING_NFC, indent);
                return new PgpSignEncryptResult(log,
                        RequiredInputParcel.createSecurityTokenSignOperation(
                                signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), e.hashToSign,
                                e.hashAlgo, cryptoInput.getSignatureTime()),
                        cryptoInput);
            }
        }

        opTime = System.currentTimeMillis() - startTime;
        Log.d(Constants.TAG, "sign/encrypt time taken: " + String.format("%.2f", opTime / 1000.0) + "s");

        // closing outputs
        // NOTE: closing needs to be done in the correct order!
        if (encryptionOut != null) {
            if (compressGen != null) {
                compressGen.close();
            }

            encryptionOut.close();
        }
        // Note: Closing ArmoredOutputStream does not close the underlying stream
        if (armorOut != null) {
            armorOut.close();
        }
        // Note: Closing ArmoredOutputStream does not close the underlying stream
        if (detachedArmorOut != null) {
            detachedArmorOut.close();
        }
        // Also closes detachedBcpgOut
        if (detachedByteOut != null) {
            detachedByteOut.close();
        }
        if (out != null) {
            out.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }

    } catch (SignatureException e) {
        log.add(LogType.MSG_PSE_ERROR_SIG, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
    } catch (PGPException e) {
        log.add(LogType.MSG_PSE_ERROR_PGP, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
    } catch (IOException e) {
        log.add(LogType.MSG_PSE_ERROR_IO, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
    }

    updateProgress(R.string.progress_done, 100, 100);

    log.add(LogType.MSG_PSE_OK, indent);
    PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log);
    result.mOperationTime = opTime;
    if (detachedByteOut != null) {
        try {
            detachedByteOut.flush();
            detachedByteOut.close();
        } catch (IOException e) {
            // silently catch
        }
        result.setDetachedSignature(detachedByteOut.toByteArray());
        try {
            String digestName = PGPUtil.getDigestName(data.getSignatureHashAlgorithm());
            // construct micalg parameter according to https://tools.ietf.org/html/rfc3156#section-5
            result.setMicAlgDigestName("pgp-" + digestName.toLowerCase());
        } catch (PGPException e) {
            Log.e(Constants.TAG, "error setting micalg parameter!", e);
        }
    }
    return result;
}