http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/NiFiLegacyCipherProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/NiFiLegacyCipherProvider.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/NiFiLegacyCipherProvider.java
new file mode 100644
index 0000000..6918fe2
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/NiFiLegacyCipherProvider.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.apache.nifi.stream.io.StreamUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Provides a cipher initialized with the original NiFi key derivation process 
for password-based encryption (MD5 @ 1000 iterations). This is not a secure
+ * {@link org.apache.nifi.security.util.KeyDerivationFunction} (KDF) and 
should no longer be used.
+ * It is provided only for backward-compatibility with legacy data. A strong 
KDF should be selected for any future use.
+ *
+ * @see BcryptCipherProvider
+ * @see ScryptCipherProvider
+ * @see PBKDF2CipherProvider
+ */
+@Deprecated
+public class NiFiLegacyCipherProvider extends OpenSSLPKCS5CipherProvider 
implements PBECipherProvider {
+    private static final Logger logger = 
LoggerFactory.getLogger(NiFiLegacyCipherProvider.class);
+
+    // Legacy magic number value
+    private static final int ITERATION_COUNT = 1000;
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived using the NiFi legacy code, based on @see 
org.apache.nifi.processors.standard.util.crypto
+     * .OpenSSLPKCS5CipherProvider#getCipher(java.lang.String, 
java.lang.String, java.lang.String, boolean) [essentially {@code MD5(password 
|| salt) * 1000 }].
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param keyLength        the desired key length in bits (ignored because 
OpenSSL ciphers provide key length in algorithm name)
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, int keyLength, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, new byte[0], keyLength, 
encryptMode);
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived using the NiFi legacy code, based on @see 
org.apache.nifi.processors.standard.util.crypto
+     * .OpenSSLPKCS5CipherProvider#getCipher(java.lang.String, 
java.lang.String, java.lang.String, byte[], boolean) [essentially {@code 
MD5(password || salt) * 1000 }].
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param keyLength        the desired key length in bits (ignored because 
OpenSSL ciphers provide key length in algorithm name)
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, int keyLength, boolean encryptMode) throws Exception {
+        try {
+            // This method is defined in the OpenSSL implementation and just 
uses a locally-overridden iteration count
+            return getInitializedCipher(encryptionMethod, password, salt, 
encryptMode);
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProcessException("Error initializing the cipher", e);
+        }
+    }
+
+    @Override
+    public byte[] readSalt(InputStream in) throws IOException, 
ProcessException {
+        if (in == null) {
+            throw new IllegalArgumentException("Cannot read salt from null 
InputStream");
+        }
+
+        // The first 16 bytes of the input stream are the salt
+        if (in.available() < getDefaultSaltLength()) {
+            throw new ProcessException("The cipher stream is too small to 
contain the salt");
+        }
+        byte[] salt = new byte[getDefaultSaltLength()];
+        StreamUtils.fillBuffer(in, salt);
+        return salt;
+    }
+
+    @Override
+    public void writeSalt(byte[] salt, OutputStream out) throws IOException {
+        if (out == null) {
+            throw new IllegalArgumentException("Cannot write salt to null 
OutputStream");
+        }
+        out.write(salt);
+    }
+
+    protected int getIterationCount() {
+        return ITERATION_COUNT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPKeyBasedEncryptor.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPKeyBasedEncryptor.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPKeyBasedEncryptor.java
new file mode 100644
index 0000000..f0f8631
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPKeyBasedEncryptor.java
@@ -0,0 +1,380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.processors.standard.EncryptContent;
+import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+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.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+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.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import 
org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import 
org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import 
org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.zip.Deflater;
+
+import static org.apache.nifi.processors.standard.util.PGPUtil.BLOCK_SIZE;
+import static org.apache.nifi.processors.standard.util.PGPUtil.BUFFER_SIZE;
+
+public class OpenPGPKeyBasedEncryptor implements Encryptor {
+    private static final Logger logger = 
LoggerFactory.getLogger(OpenPGPPasswordBasedEncryptor.class);
+
+    private String algorithm;
+    private String provider;
+    // TODO: This can hold either the secret or public keyring path
+    private String keyring;
+    private String userId;
+    private char[] passphrase;
+    private String filename;
+
+    public OpenPGPKeyBasedEncryptor(final String algorithm, final String 
provider, final String keyring, final String userId, final char[] passphrase, 
final String filename) {
+        this.algorithm = algorithm;
+        this.provider = provider;
+        this.keyring = keyring;
+        this.userId = userId;
+        this.passphrase = passphrase;
+        this.filename = filename;
+    }
+
+    @Override
+    public StreamCallback getEncryptionCallback() throws Exception {
+        return new OpenPGPEncryptCallback(algorithm, provider, keyring, 
userId, filename);
+    }
+
+    @Override
+    public StreamCallback getDecryptionCallback() throws Exception {
+        return new OpenPGPDecryptCallback(provider, keyring, passphrase);
+    }
+
+    /**
+     * Returns true if the passphrase is valid.
+     * <p>
+     * This is used in the EncryptContent custom validation to check if the 
passphrase can extract a private key from the secret key ring. After BC was 
upgraded from 1.46 to 1.53, the API changed
+     * so this is performed differently but the functionality is equivalent.
+     *
+     * @param provider the provider name
+     * @param secretKeyringFile the file path to the keyring
+     * @param passphrase        the passphrase
+     * @return true if the passphrase can successfully extract any private key
+     * @throws IOException             if there is a problem reading the 
keyring file
+     * @throws PGPException            if there is a problem 
parsing/extracting the private key
+     * @throws NoSuchProviderException if the provider is not available
+     */
+    public static boolean validateKeyring(String provider, String 
secretKeyringFile, char[] passphrase) throws IOException, PGPException, 
NoSuchProviderException {
+        try {
+            getDecryptedPrivateKey(provider, secretKeyringFile, passphrase);
+            return true;
+        } catch (Exception e) {
+            // If this point is reached, no private key could be extracted 
with the given passphrase
+            return false;
+        }
+    }
+
+    private static PGPPrivateKey getDecryptedPrivateKey(String provider, 
String secretKeyringFile, char[] passphrase) throws IOException, PGPException {
+        // TODO: Verify that key IDs cannot be 0
+        return getDecryptedPrivateKey(provider, secretKeyringFile, 0L, 
passphrase);
+    }
+
+    private static PGPPrivateKey getDecryptedPrivateKey(String provider, 
String secretKeyringFile, long keyId, char[] passphrase) throws IOException, 
PGPException {
+        // TODO: Reevaluate the mechanism for executing this task as 
performance can suffer here and only a specific key needs to be validated
+
+        // Read in from the secret keyring file
+        try (FileInputStream keyInputStream = new 
FileInputStream(secretKeyringFile)) {
+
+            // Form the SecretKeyRing collection (1.53 way with fingerprint 
calculator)
+            PGPSecretKeyRingCollection pgpSecretKeyRingCollection = new 
PGPSecretKeyRingCollection(keyInputStream, new BcKeyFingerprintCalculator());
+
+            // The decryptor is identical for all keys
+            final PBESecretKeyDecryptor decryptor = new 
JcePBESecretKeyDecryptorBuilder().setProvider(provider).build(passphrase);
+
+            // Iterate over all secret keyrings
+            Iterator<PGPSecretKeyRing> keyringIterator = 
pgpSecretKeyRingCollection.getKeyRings();
+            PGPSecretKeyRing keyRing;
+            PGPSecretKey secretKey;
+
+            while (keyringIterator.hasNext()) {
+                keyRing = keyringIterator.next();
+
+                // If keyId exists, get a specific secret key; else, iterate 
over all
+                if (keyId != 0) {
+                    secretKey = keyRing.getSecretKey(keyId);
+                    try {
+                        return secretKey.extractPrivateKey(decryptor);
+                    } catch (Exception e) {
+                        throw new PGPException("No private key available using 
passphrase", e);
+                    }
+                } else {
+                    Iterator<PGPSecretKey> keyIterator = 
keyRing.getSecretKeys();
+
+                    while (keyIterator.hasNext()) {
+                        secretKey = keyIterator.next();
+                        try {
+                            return secretKey.extractPrivateKey(decryptor);
+                        } catch (Exception e) {
+                            // TODO: Log (expected) failures?
+                        }
+                    }
+                }
+            }
+        }
+
+        // If this point is reached, no private key could be extracted with 
the given passphrase
+        throw new PGPException("No private key available using passphrase");
+    }
+
+    /*
+     * Get the public key for a specific user id from a keyring.
+     */
+    @SuppressWarnings("rawtypes")
+    public static PGPPublicKey getPublicKey(String userId, String 
publicKeyringFile) throws IOException, PGPException {
+        // TODO: Reevaluate the mechanism for executing this task as 
performance can suffer here and only a specific key needs to be validated
+
+        // Read in from the public keyring file
+        try (FileInputStream keyInputStream = new 
FileInputStream(publicKeyringFile)) {
+
+            // Form the PublicKeyRing collection (1.53 way with fingerprint 
calculator)
+            PGPPublicKeyRingCollection pgpPublicKeyRingCollection = new 
PGPPublicKeyRingCollection(keyInputStream, new BcKeyFingerprintCalculator());
+
+            // Iterate over all public keyrings
+            Iterator<PGPPublicKeyRing> iter = 
pgpPublicKeyRingCollection.getKeyRings();
+            PGPPublicKeyRing keyRing;
+            while (iter.hasNext()) {
+                keyRing = iter.next();
+
+                // Iterate over each public key in this keyring
+                Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
+                while (keyIter.hasNext()) {
+                    PGPPublicKey publicKey = keyIter.next();
+
+                    // Iterate over each userId attached to the public key
+                    Iterator userIdIterator = publicKey.getUserIDs();
+                    while (userIdIterator.hasNext()) {
+                        String id = (String) userIdIterator.next();
+                        if (userId.equalsIgnoreCase(id)) {
+                            return publicKey;
+                        }
+                    }
+                }
+            }
+        }
+
+        // If this point is reached, no public key could be extracted with the 
given userId
+        throw new PGPException("Could not find a public key with the given 
userId");
+    }
+
+    private static class OpenPGPDecryptCallback implements StreamCallback {
+
+        private String provider;
+        private String secretKeyringFile;
+        private char[] passphrase;
+
+        OpenPGPDecryptCallback(final String provider, final String 
secretKeyringFile, final char[] passphrase) {
+            this.provider = provider;
+            this.secretKeyringFile = secretKeyringFile;
+            this.passphrase = passphrase;
+        }
+
+        @Override
+        public void process(InputStream in, OutputStream out) throws 
IOException {
+            try (InputStream pgpin = PGPUtil.getDecoderStream(in)) {
+                PGPObjectFactory pgpFactory = new PGPObjectFactory(pgpin, new 
BcKeyFingerprintCalculator());
+
+                Object obj = pgpFactory.nextObject();
+                if (!(obj instanceof PGPEncryptedDataList)) {
+                    obj = pgpFactory.nextObject();
+                    if (!(obj instanceof PGPEncryptedDataList)) {
+                        throw new ProcessException("Invalid OpenPGP data");
+                    }
+                }
+                PGPEncryptedDataList encList = (PGPEncryptedDataList) obj;
+
+                try {
+                    PGPPrivateKey privateKey = null;
+                    PGPPublicKeyEncryptedData encData = null;
+
+                    // Find the secret key in the encrypted data
+                    Iterator it = encList.getEncryptedDataObjects();
+                    while (privateKey == null && it.hasNext()) {
+                        obj = it.next();
+                        if (!(obj instanceof PGPPublicKeyEncryptedData)) {
+                            throw new ProcessException("Invalid OpenPGP data");
+                        }
+                        encData = (PGPPublicKeyEncryptedData) obj;
+
+                        // Check each encrypted data object to see if it 
contains the key ID for the secret key -> private key
+                        try {
+                            privateKey = getDecryptedPrivateKey(provider, 
secretKeyringFile, encData.getKeyID(), passphrase);
+                        } catch (PGPException e) {
+                            // TODO: Log (expected) exception?
+                        }
+                    }
+                    if (privateKey == null) {
+                        throw new ProcessException("Secret keyring does not 
contain the key required to decrypt");
+                    }
+
+                    // Read in the encrypted data stream and decrypt it
+                    final PublicKeyDataDecryptorFactory dataDecryptor = new 
JcePublicKeyDataDecryptorFactoryBuilder().setProvider(provider).build(privateKey);
+                    try (InputStream clear = 
encData.getDataStream(dataDecryptor)) {
+                        // Create a plain object factory
+                        JcaPGPObjectFactory plainFact = new 
JcaPGPObjectFactory(clear);
+
+                        Object message = plainFact.nextObject();
+
+                        // Check the message type and act accordingly
+
+                        // If compressed, decompress
+                        if (message instanceof PGPCompressedData) {
+                            PGPCompressedData cData = (PGPCompressedData) 
message;
+                            JcaPGPObjectFactory pgpFact = new 
JcaPGPObjectFactory(cData.getDataStream());
+
+                            message = pgpFact.nextObject();
+                        }
+
+                        // If the message is literal data, read it and process 
to the out stream
+                        if (message instanceof PGPLiteralData) {
+                            PGPLiteralData literalData = (PGPLiteralData) 
message;
+
+                            try (InputStream lis = 
literalData.getInputStream()) {
+                                final byte[] buffer = new byte[BLOCK_SIZE];
+                                int len;
+                                while ((len = lis.read(buffer)) >= 0) {
+                                    out.write(buffer, 0, len);
+                                }
+                            }
+                        } else if (message instanceof PGPOnePassSignatureList) 
{
+                            // TODO: This is legacy code but should verify 
signature list here
+                            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 (encData.isIntegrityProtected()) {
+                            if (!encData.verify()) {
+                                throw new PGPException("Failed message 
integrity check");
+                            }
+                        } else {
+                            logger.warn("No message integrity check");
+                        }
+                    }
+                } catch (Exception e) {
+                    throw new ProcessException(e.getMessage());
+                }
+            }
+        }
+
+    }
+
+    private static class OpenPGPEncryptCallback implements StreamCallback {
+
+        private String algorithm;
+        private String provider;
+        private String publicKeyring;
+        private String userId;
+        private String filename;
+
+        OpenPGPEncryptCallback(final String algorithm, final String provider, 
final String keyring, final String userId, final String filename) {
+            this.algorithm = algorithm;
+            this.provider = provider;
+            this.publicKeyring = keyring;
+            this.userId = userId;
+            this.filename = filename;
+        }
+
+        @Override
+        public void process(InputStream in, OutputStream out) throws 
IOException {
+            PGPPublicKey publicKey;
+            final boolean isArmored = 
EncryptContent.isPGPArmoredAlgorithm(algorithm);
+
+            try {
+                publicKey = getPublicKey(userId, publicKeyring);
+            } catch (Exception e) {
+                throw new ProcessException("Invalid public keyring - " + 
e.getMessage());
+            }
+
+            try {
+                OutputStream output = out;
+                if (isArmored) {
+                    output = new ArmoredOutputStream(out);
+                }
+
+                try {
+                    // TODO: Refactor internal symmetric encryption algorithm 
to be customizable
+                    PGPEncryptedDataGenerator encryptedDataGenerator = new 
PGPEncryptedDataGenerator(
+                            new 
JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_128).setWithIntegrityPacket(true).setSecureRandom(new
 SecureRandom()).setProvider(provider));
+
+                    encryptedDataGenerator.addMethod(new 
JcePublicKeyKeyEncryptionMethodGenerator(publicKey).setProvider(provider));
+
+                    // TODO: Refactor shared encryption code to utility
+                    try (OutputStream encryptedOut = 
encryptedDataGenerator.open(output, new byte[BUFFER_SIZE])) {
+                        PGPCompressedDataGenerator compressedDataGenerator = 
new PGPCompressedDataGenerator(PGPCompressedData.ZIP, Deflater.BEST_SPEED);
+                        try (OutputStream compressedOut = 
compressedDataGenerator.open(encryptedOut, new byte[BUFFER_SIZE])) {
+                            PGPLiteralDataGenerator literalDataGenerator = new 
PGPLiteralDataGenerator();
+                            try (OutputStream literalOut = 
literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY, filename, new 
Date(), new byte[BUFFER_SIZE])) {
+
+                                final byte[] buffer = new byte[BLOCK_SIZE];
+                                int len;
+                                while ((len = in.read(buffer)) >= 0) {
+                                    literalOut.write(buffer, 0, len);
+                                }
+                            }
+                        }
+                    }
+                } finally {
+                    if (isArmored) {
+                        output.close();
+                    }
+                }
+            } catch (Exception e) {
+                throw new ProcessException(e.getMessage());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPPasswordBasedEncryptor.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPPasswordBasedEncryptor.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPPasswordBasedEncryptor.java
new file mode 100644
index 0000000..93e565a
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenPGPPasswordBasedEncryptor.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
+import 
org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import 
org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import 
org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static org.bouncycastle.openpgp.PGPUtil.getDecoderStream;
+
+public class OpenPGPPasswordBasedEncryptor implements Encryptor {
+    private static final Logger logger = 
LoggerFactory.getLogger(OpenPGPPasswordBasedEncryptor.class);
+
+    private String algorithm;
+    private String provider;
+    private char[] password;
+    private String filename;
+
+    public OpenPGPPasswordBasedEncryptor(final String algorithm, final String 
provider, final char[] passphrase, final String filename) {
+        this.algorithm = algorithm;
+        this.provider = provider;
+        this.password = passphrase;
+        this.filename = filename;
+    }
+
+    @Override
+    public StreamCallback getEncryptionCallback() throws Exception {
+        return new OpenPGPEncryptCallback(algorithm, provider, password, 
filename);
+    }
+
+    @Override
+    public StreamCallback getDecryptionCallback() throws Exception {
+        return new OpenPGPDecryptCallback(provider, password);
+    }
+
+    private static class OpenPGPDecryptCallback implements StreamCallback {
+
+        private String provider;
+        private char[] password;
+
+        OpenPGPDecryptCallback(final String provider, final char[] password) {
+            this.provider = provider;
+            this.password = password;
+        }
+
+        @Override
+        public void process(InputStream in, OutputStream out) throws 
IOException {
+            InputStream pgpin = getDecoderStream(in);
+            JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(pgpin);
+
+            Object obj = pgpFactory.nextObject();
+            if (!(obj instanceof PGPEncryptedDataList)) {
+                obj = pgpFactory.nextObject();
+                if (!(obj instanceof PGPEncryptedDataList)) {
+                    throw new ProcessException("Invalid OpenPGP data");
+                }
+            }
+            PGPEncryptedDataList encList = (PGPEncryptedDataList) obj;
+
+            obj = encList.get(0);
+            if (!(obj instanceof PGPPBEEncryptedData)) {
+                throw new ProcessException("Invalid OpenPGP data");
+            }
+            PGPPBEEncryptedData encryptedData = (PGPPBEEncryptedData) obj;
+
+            try {
+                final PGPDigestCalculatorProvider digestCalculatorProvider = 
new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build();
+                final PBEDataDecryptorFactory decryptorFactory = new 
JcePBEDataDecryptorFactoryBuilder(digestCalculatorProvider).setProvider(provider).build(password);
+                InputStream clear = 
encryptedData.getDataStream(decryptorFactory);
+
+                JcaPGPObjectFactory pgpObjectFactory = new 
JcaPGPObjectFactory(clear);
+
+                obj = pgpObjectFactory.nextObject();
+                if (obj instanceof PGPCompressedData) {
+                    PGPCompressedData compressedData = (PGPCompressedData) obj;
+                    pgpObjectFactory = new 
JcaPGPObjectFactory(compressedData.getDataStream());
+                    obj = pgpObjectFactory.nextObject();
+                }
+
+                PGPLiteralData literalData = (PGPLiteralData) obj;
+                InputStream plainIn = literalData.getInputStream();
+                final byte[] buffer = new 
byte[org.apache.nifi.processors.standard.util.PGPUtil.BLOCK_SIZE];
+                int len;
+                while ((len = plainIn.read(buffer)) >= 0) {
+                    out.write(buffer, 0, len);
+                }
+
+                if (encryptedData.isIntegrityProtected()) {
+                    if (!encryptedData.verify()) {
+                        throw new PGPException("Integrity check failed");
+                    }
+                } else {
+                    logger.warn("No message integrity check");
+                }
+            } catch (Exception e) {
+                throw new ProcessException(e.getMessage());
+            }
+        }
+    }
+
+    private static class OpenPGPEncryptCallback implements StreamCallback {
+
+        private String algorithm;
+        private String provider;
+        private char[] password;
+        private String filename;
+
+        OpenPGPEncryptCallback(final String algorithm, final String provider, 
final char[] password, final String filename) {
+            this.algorithm = algorithm;
+            this.provider = provider;
+            this.password = password;
+            this.filename = filename;
+        }
+
+        @Override
+        public void process(InputStream in, OutputStream out) throws 
IOException {
+            try {
+                PGPKeyEncryptionMethodGenerator encryptionMethodGenerator = 
new JcePBEKeyEncryptionMethodGenerator(password).setProvider(provider);
+                org.apache.nifi.processors.standard.util.PGPUtil.encrypt(in, 
out, algorithm, provider, PGPEncryptedData.AES_128, filename, 
encryptionMethodGenerator);
+            } catch (Exception e) {
+                throw new ProcessException(e.getMessage());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenSSLPKCS5CipherProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenSSLPKCS5CipherProvider.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenSSLPKCS5CipherProvider.java
new file mode 100644
index 0000000..049dbd8
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/OpenSSLPKCS5CipherProvider.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.apache.nifi.stream.io.StreamUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+public class OpenSSLPKCS5CipherProvider implements PBECipherProvider {
+    private static final Logger logger = 
LoggerFactory.getLogger(OpenSSLPKCS5CipherProvider.class);
+
+    // Legacy magic number value
+    private static final int ITERATION_COUNT = 0;
+    private static final int DEFAULT_SALT_LENGTH = 8;
+    private static final byte[] EMPTY_SALT = new byte[8];
+
+    private static final String OPENSSL_EVP_HEADER_MARKER = "Salted__";
+    private static final int OPENSSL_EVP_HEADER_SIZE = 8;
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived using the
+     * <a 
href="https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html";>OpenSSL
 EVP_BytesToKey proprietary KDF</a> [essentially {@code MD5(password || salt) 
}].
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param keyLength        the desired key length in bits (ignored because 
OpenSSL ciphers provide key length in algorithm name)
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, int keyLength, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, new byte[0], keyLength, 
encryptMode);
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived using the
+     * <a 
href="https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html";>OpenSSL
 EVP_BytesToKey proprietary KDF</a> [essentially {@code MD5(password || salt) 
}].
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param keyLength        the desired key length in bits (ignored because 
OpenSSL ciphers provide key length in algorithm name)
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, int keyLength, boolean encryptMode) throws Exception {
+        try {
+            return getInitializedCipher(encryptionMethod, password, salt, 
encryptMode);
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProcessException("Error initializing the cipher", e);
+        }
+    }
+
+    /**
+     * Convenience method without key length parameter. See {@link 
OpenSSLPKCS5CipherProvider#getCipher(EncryptionMethod, String, int, boolean)}
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, new byte[0], -1, 
encryptMode);
+    }
+
+    /**
+     * Convenience method without key length parameter. See {@link 
OpenSSLPKCS5CipherProvider#getCipher(EncryptionMethod, String, byte[], int, 
boolean)}
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, salt, -1, encryptMode);
+    }
+
+    protected Cipher getInitializedCipher(EncryptionMethod encryptionMethod, 
String password, byte[] salt, boolean encryptMode)
+            throws NoSuchAlgorithmException, NoSuchProviderException, 
InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        if (encryptionMethod == null) {
+            throw new IllegalArgumentException("The encryption method must be 
specified");
+        }
+
+        if (StringUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("Encryption with an empty 
password is not supported");
+        }
+
+        if (salt.length != DEFAULT_SALT_LENGTH && salt.length != 0) {
+            // This does not enforce ASCII encoding, just length
+            throw new IllegalArgumentException("Salt must be 8 bytes US-ASCII 
encoded or empty");
+        }
+
+        String algorithm = encryptionMethod.getAlgorithm();
+        String provider = encryptionMethod.getProvider();
+
+        // Initialize secret key from password
+        final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
+        final SecretKeyFactory factory = 
SecretKeyFactory.getInstance(algorithm, provider);
+        SecretKey tempKey = factory.generateSecret(pbeKeySpec);
+
+        final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 
getIterationCount());
+        Cipher cipher = Cipher.getInstance(algorithm, provider);
+        cipher.init(encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, 
tempKey, parameterSpec);
+        return cipher;
+    }
+
+    protected int getIterationCount() {
+        return ITERATION_COUNT;
+    }
+
+    @Override
+    public byte[] generateSalt() {
+        byte[] salt = new byte[getDefaultSaltLength()];
+        new SecureRandom().nextBytes(salt);
+        return salt;
+    }
+
+    @Override
+    public int getDefaultSaltLength() {
+        return DEFAULT_SALT_LENGTH;
+    }
+
+    /**
+     * Returns the salt provided as part of the cipher stream, or throws an 
exception if one cannot be detected.
+     *
+     * @param in the cipher InputStream
+     * @return the salt
+     */
+    @Override
+    public byte[] readSalt(InputStream in) throws IOException {
+        if (in == null) {
+            throw new IllegalArgumentException("Cannot read salt from null 
InputStream");
+        }
+
+        // The header and salt format is "Salted__salt x8b" in ASCII
+        byte[] salt = new byte[DEFAULT_SALT_LENGTH];
+
+        // Try to read the header and salt from the input
+        byte[] header = new byte[OPENSSL_EVP_HEADER_SIZE];
+
+        // Mark the stream in case there is no salt
+        in.mark(OPENSSL_EVP_HEADER_SIZE + 1);
+        StreamUtils.fillBuffer(in, header);
+
+        final byte[] headerMarkerBytes = 
OPENSSL_EVP_HEADER_MARKER.getBytes(StandardCharsets.US_ASCII);
+
+        if (!Arrays.equals(headerMarkerBytes, header)) {
+            // No salt present
+            salt = new byte[0];
+            // Reset the stream because we skipped 8 bytes of cipher text
+            in.reset();
+        }
+
+        StreamUtils.fillBuffer(in, salt);
+        return salt;
+    }
+
+    @Override
+    public void writeSalt(byte[] salt, OutputStream out) throws IOException {
+        if (out == null) {
+            throw new IllegalArgumentException("Cannot write salt to null 
OutputStream");
+        }
+
+        
out.write(OPENSSL_EVP_HEADER_MARKER.getBytes(StandardCharsets.US_ASCII));
+        out.write(salt);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBECipherProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBECipherProvider.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBECipherProvider.java
new file mode 100644
index 0000000..8677656
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBECipherProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.nifi.security.util.EncryptionMethod;
+
+import javax.crypto.Cipher;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public interface PBECipherProvider extends CipherProvider {
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived by the KDF of the implementation.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    Cipher getCipher(EncryptionMethod encryptionMethod, String password, int 
keyLength, boolean encryptMode) throws Exception;
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived by the KDF of the implementation.
+     * <p/>
+     * The IV can be retrieved by the calling method using {@link 
Cipher#getIV()}.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    Cipher getCipher(EncryptionMethod encryptionMethod, String password, 
byte[] salt, int keyLength, boolean encryptMode) throws Exception;
+
+    /**
+     * Returns a random salt suitable for this cipher provider.
+     *
+     * @return a random salt
+     * @see PBECipherProvider#getDefaultSaltLength()
+     */
+    byte[] generateSalt();
+
+    /**
+     * Returns the default salt length for this implementation.
+     *
+     * @return the default salt length in bytes
+     */
+    int getDefaultSaltLength();
+
+    /**
+     * Returns the salt provided as part of the cipher stream, or throws an 
exception if one cannot be detected.
+     *
+     * @param in the cipher InputStream
+     * @return the salt
+     */
+    byte[] readSalt(InputStream in) throws IOException;
+
+    /**
+     * Writes the salt provided as part of the cipher stream, or throws an 
exception if it cannot be written.
+     *
+     * @param salt the salt
+     * @param out  the cipher OutputStream
+     */
+    void writeSalt(byte[] salt, OutputStream out) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBKDF2CipherProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBKDF2CipherProvider.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBKDF2CipherProvider.java
new file mode 100644
index 0000000..748d77f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PBKDF2CipherProvider.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+
+public class PBKDF2CipherProvider extends RandomIVPBECipherProvider {
+    private static final Logger logger = 
LoggerFactory.getLogger(PBKDF2CipherProvider.class);
+    private static final int DEFAULT_SALT_LENGTH = 16;
+
+    private final int iterationCount;
+    private final Digest prf;
+
+    private static final String DEFAULT_PRF = "SHA-512";
+    /**
+     * This can be calculated automatically using the code {@see 
PBKDF2CipherProviderGroovyTest#calculateMinimumIterationCount} or manually 
updated by a maintainer
+     */
+    private static final int DEFAULT_ITERATION_COUNT = 160_000;
+
+    /**
+     * Instantiates a PBKDF2 cipher provider with the default number of 
iterations and the default PRF. Currently 128,000 iterations and SHA-512.
+     */
+    public PBKDF2CipherProvider() {
+        this(DEFAULT_PRF, DEFAULT_ITERATION_COUNT);
+    }
+
+    /**
+     * Instantiates a PBKDF2 cipher provider with the specified number of 
iterations and the specified PRF. Currently supports MD5, SHA1, SHA256, SHA384, 
and SHA512. Unknown PRFs will default to
+     * SHA512.
+     *
+     * @param prf            a String representation of the PRF name, e.g. 
"SHA256", "SHA-384" "sha_512"
+     * @param iterationCount the number of iterations
+     */
+    public PBKDF2CipherProvider(String prf, int iterationCount) {
+        this.iterationCount = iterationCount;
+        if (iterationCount < DEFAULT_ITERATION_COUNT) {
+            logger.warn("The provided iteration count {} is below the 
recommended minimum {}", iterationCount, DEFAULT_ITERATION_COUNT);
+        }
+        this.prf = resolvePRF(prf);
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key is 
derived by the KDF of the implementation. The IV is provided externally to 
allow for non-deterministic IVs, as IVs
+     * deterministically derived from the password are a potential 
vulnerability and compromise semantic security. See
+     * <a href="http://crypto.stackexchange.com/a/3970/12569";>Ilmari Karonen's 
answer on Crypto Stack Exchange</a>
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param iv               the IV
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, byte[] iv, int keyLength, boolean encryptMode) throws 
Exception {
+        try {
+            return getInitializedCipher(encryptionMethod, password, salt, iv, 
keyLength, encryptMode);
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProcessException("Error initializing the cipher", e);
+        }
+    }
+
+    @Override
+    Logger getLogger() {
+        return logger;
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived by the KDF of the implementation.
+     *
+     * The IV can be retrieved by the calling method using {@link 
Cipher#getIV()}.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, int keyLength, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, new byte[0], new byte[0], 
keyLength, encryptMode);
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived by the KDF of the implementation.
+     *
+     * The IV can be retrieved by the calling method using {@link 
Cipher#getIV()}.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, int keyLength, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, salt, new byte[0], 
keyLength, encryptMode);
+    }
+
+    protected Cipher getInitializedCipher(EncryptionMethod encryptionMethod, 
String password, byte[] salt, byte[] iv, int keyLength, boolean encryptMode) 
throws Exception {
+        if (encryptionMethod == null) {
+            throw new IllegalArgumentException("The encryption method must be 
specified");
+        }
+
+        if (!encryptionMethod.isCompatibleWithStrongKDFs()) {
+            throw new IllegalArgumentException(encryptionMethod.name() + " is 
not compatible with PBKDF2");
+        }
+
+        String algorithm = encryptionMethod.getAlgorithm();
+
+        final String cipherName = 
CipherUtility.parseCipherFromAlgorithm(algorithm);
+        if (!CipherUtility.isValidKeyLength(keyLength, cipherName)) {
+            throw new IllegalArgumentException(String.valueOf(keyLength) + " 
is not a valid key length for " + cipherName);
+        }
+
+        if (StringUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("Encryption with an empty 
password is not supported");
+        }
+
+        if (salt == null || salt.length < DEFAULT_SALT_LENGTH) {
+            throw new IllegalArgumentException("The salt must be at least " + 
DEFAULT_SALT_LENGTH + " bytes. To generate a salt, use 
PBKDF2CipherProvider#generateSalt()");
+        }
+
+        PKCS5S2ParametersGenerator gen = new 
PKCS5S2ParametersGenerator(this.prf);
+        gen.init(password.getBytes(StandardCharsets.UTF_8), salt, 
getIterationCount());
+        byte[] dk = ((KeyParameter) 
gen.generateDerivedParameters(keyLength)).getKey();
+        SecretKey tempKey = new SecretKeySpec(dk, algorithm);
+
+        KeyedCipherProvider keyedCipherProvider = new AESKeyedCipherProvider();
+        return keyedCipherProvider.getCipher(encryptionMethod, tempKey, iv, 
encryptMode);
+    }
+
+    @Override
+    public byte[] generateSalt() {
+        byte[] salt = new byte[DEFAULT_SALT_LENGTH];
+        new SecureRandom().nextBytes(salt);
+        return salt;
+    }
+
+    @Override
+    public int getDefaultSaltLength() {
+        return DEFAULT_SALT_LENGTH;
+    }
+
+    protected int getIterationCount() {
+        return iterationCount;
+    }
+
+    protected String getPRFName() {
+        if (prf != null) {
+            return prf.getAlgorithmName();
+        } else {
+            return "No PRF enabled";
+        }
+    }
+
+    private Digest resolvePRF(final String prf) {
+        if (StringUtils.isEmpty(prf)) {
+            throw new IllegalArgumentException("Cannot resolve empty PRF");
+        }
+        String formattedPRF = prf.toLowerCase().replaceAll("[\\W]+", "");
+        logger.debug("Resolved PRF {} to {}", prf, formattedPRF);
+        switch (formattedPRF) {
+            case "md5":
+                return new MD5Digest();
+            case "sha1":
+                return new SHA1Digest();
+            case "sha384":
+                return new SHA384Digest();
+            case "sha256":
+                return new SHA256Digest();
+            case "sha512":
+                return new SHA512Digest();
+            default:
+                logger.warn("Could not resolve PRF {}. Using default PRF {} 
instead", prf, DEFAULT_PRF);
+                return new SHA512Digest();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PasswordBasedEncryptor.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PasswordBasedEncryptor.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PasswordBasedEncryptor.java
new file mode 100644
index 0000000..ce81f07
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/PasswordBasedEncryptor.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.apache.nifi.security.util.KeyDerivationFunction;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.PBEKeySpec;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+
+public class PasswordBasedEncryptor implements Encryptor {
+
+    private EncryptionMethod encryptionMethod;
+    private PBEKeySpec password;
+    private KeyDerivationFunction kdf;
+
+    private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
+    private static final int MINIMUM_SAFE_PASSWORD_LENGTH = 10;
+
+    private static boolean isUnlimitedStrengthCryptographyEnabled;
+
+    // Evaluate an unlimited strength algorithm to determine if we support the 
capability we have on the system
+    static {
+        try {
+            isUnlimitedStrengthCryptographyEnabled = 
(Cipher.getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH);
+        } catch (NoSuchAlgorithmException e) {
+            // if there are issues with this, we default back to the value 
established
+            isUnlimitedStrengthCryptographyEnabled = false;
+        }
+    }
+
+    public PasswordBasedEncryptor(final EncryptionMethod encryptionMethod, 
final char[] password, KeyDerivationFunction kdf) {
+        super();
+        try {
+            if (encryptionMethod == null) {
+                throw new IllegalArgumentException("Cannot initialize 
password-based encryptor with null encryption method");
+            }
+            this.encryptionMethod = encryptionMethod;
+            if (kdf == null || kdf.equals(KeyDerivationFunction.NONE)) {
+                throw new IllegalArgumentException("Cannot initialize 
password-based encryptor with null KDF");
+            }
+            this.kdf = kdf;
+            if (password == null || password.length == 0) {
+                throw new IllegalArgumentException("Cannot initialize 
password-based encryptor with empty password");
+            }
+            this.password = new PBEKeySpec(password);
+        } catch (Exception e) {
+            throw new ProcessException(e);
+        }
+    }
+
+    public static int getMaxAllowedKeyLength(final String algorithm) {
+        if (StringUtils.isEmpty(algorithm)) {
+            return DEFAULT_MAX_ALLOWED_KEY_LENGTH;
+        }
+        String parsedCipher = 
CipherUtility.parseCipherFromAlgorithm(algorithm);
+        try {
+            return Cipher.getMaxAllowedKeyLength(parsedCipher);
+        } catch (NoSuchAlgorithmException e) {
+            // Default algorithm max key length on unmodified JRE
+            return DEFAULT_MAX_ALLOWED_KEY_LENGTH;
+        }
+    }
+
+    /**
+     * Returns a recommended minimum length for passwords. This can be 
modified over time and does not take full entropy calculations (patterns, 
character space, etc.) into account.
+     *
+     * @return the minimum safe password length
+     */
+    public static int getMinimumSafePasswordLength() {
+        return MINIMUM_SAFE_PASSWORD_LENGTH;
+    }
+
+    public static boolean supportsUnlimitedStrength() {
+        return isUnlimitedStrengthCryptographyEnabled;
+    }
+
+    @Override
+    public StreamCallback getEncryptionCallback() throws ProcessException {
+        return new EncryptCallback();
+    }
+
+    @Override
+    public StreamCallback getDecryptionCallback() throws ProcessException {
+        return new DecryptCallback();
+    }
+
+    private class DecryptCallback implements StreamCallback {
+
+        public DecryptCallback() {
+        }
+
+        @Override
+        public void process(final InputStream in, final OutputStream out) 
throws IOException {
+            // Initialize cipher provider
+            PBECipherProvider cipherProvider = (PBECipherProvider) 
CipherProviderFactory.getCipherProvider(kdf);
+
+            // Read salt
+            byte[] salt;
+            try {
+                salt = cipherProvider.readSalt(in);
+            } catch (final EOFException e) {
+                throw new ProcessException("Cannot decrypt because file size 
is smaller than salt size", e);
+            }
+
+            // Determine necessary key length
+            int keyLength = 
CipherUtility.parseKeyLengthFromAlgorithm(encryptionMethod.getAlgorithm());
+
+            // Generate cipher
+            try {
+                Cipher cipher;
+                // Read IV if necessary
+                if (cipherProvider instanceof RandomIVPBECipherProvider) {
+                    RandomIVPBECipherProvider rivpcp = 
(RandomIVPBECipherProvider) cipherProvider;
+                    byte[] iv = rivpcp.readIV(in);
+                    cipher = rivpcp.getCipher(encryptionMethod, new 
String(password.getPassword()), salt, iv, keyLength, false);
+                } else {
+                    cipher = cipherProvider.getCipher(encryptionMethod, new 
String(password.getPassword()), salt, keyLength, false);
+                }
+                CipherUtility.processStreams(cipher, in, out);
+            } catch (Exception e) {
+                throw new ProcessException(e);
+            }
+        }
+    }
+
+    private class EncryptCallback implements StreamCallback {
+
+        public EncryptCallback() {
+        }
+
+        @Override
+        public void process(final InputStream in, final OutputStream out) 
throws IOException {
+            // Initialize cipher provider
+            PBECipherProvider cipherProvider = (PBECipherProvider) 
CipherProviderFactory.getCipherProvider(kdf);
+
+            // Generate salt
+            byte[] salt = cipherProvider.generateSalt();
+
+            // Write to output stream
+            cipherProvider.writeSalt(salt, out);
+
+            // Determine necessary key length
+            int keyLength = 
CipherUtility.parseKeyLengthFromAlgorithm(encryptionMethod.getAlgorithm());
+
+            // Generate cipher
+            try {
+                Cipher cipher = cipherProvider.getCipher(encryptionMethod, new 
String(password.getPassword()), salt, keyLength, true);
+                // Write IV if necessary
+                if (cipherProvider instanceof RandomIVPBECipherProvider) {
+                    ((RandomIVPBECipherProvider) 
cipherProvider).writeIV(cipher.getIV(), out);
+                }
+                CipherUtility.processStreams(cipher, in, out);
+            } catch (Exception e) {
+                throw new ProcessException(e);
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/RandomIVPBECipherProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/RandomIVPBECipherProvider.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/RandomIVPBECipherProvider.java
new file mode 100644
index 0000000..903dfac
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/RandomIVPBECipherProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.slf4j.Logger;
+
+import javax.crypto.Cipher;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+public abstract class RandomIVPBECipherProvider implements PBECipherProvider {
+    static final byte[] SALT_DELIMITER = 
"NiFiSALT".getBytes(StandardCharsets.UTF_8);
+    static final int MAX_SALT_LIMIT = 128;
+    static final byte[] IV_DELIMITER = 
"NiFiIV".getBytes(StandardCharsets.UTF_8);
+    // This is 16 bytes for AES but can vary for other ciphers
+    static final int MAX_IV_LIMIT = 16;
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key is 
derived by the KDF of the implementation. The IV is provided externally to 
allow for non-deterministic IVs, as IVs
+     * deterministically derived from the password are a potential 
vulnerability and compromise semantic security. See
+     * <a href="http://crypto.stackexchange.com/a/3970/12569";>Ilmari Karonen's 
answer on Crypto Stack Exchange</a>
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the salt
+     * @param iv               the IV
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    abstract Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, byte[] iv, int keyLength, boolean encryptMode) throws 
Exception;
+
+    abstract Logger getLogger();
+
+    @Override
+    public byte[] readSalt(InputStream in) throws IOException, 
ProcessException {
+       return CipherUtility.readBytesFromInputStream(in, "salt", 
MAX_SALT_LIMIT, SALT_DELIMITER);
+    }
+
+    @Override
+    public void writeSalt(byte[] salt, OutputStream out) throws IOException {
+        CipherUtility.writeBytesToOutputStream(out, salt, "salt", 
SALT_DELIMITER);
+    }
+
+    public byte[] readIV(InputStream in) throws IOException, ProcessException {
+        return CipherUtility.readBytesFromInputStream(in, "IV", MAX_IV_LIMIT, 
IV_DELIMITER);
+    }
+
+    public void writeIV(byte[] iv, OutputStream out) throws IOException {
+        CipherUtility.writeBytesToOutputStream(out, iv, "IV", IV_DELIMITER);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/ScryptCipherProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/ScryptCipherProvider.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/ScryptCipherProvider.java
new file mode 100644
index 0000000..635b4ef
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/ScryptCipherProvider.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.standard.util.crypto;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processors.standard.util.crypto.scrypt.Scrypt;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ScryptCipherProvider extends RandomIVPBECipherProvider {
+    private static final Logger logger = 
LoggerFactory.getLogger(ScryptCipherProvider.class);
+
+    private final int n;
+    private final int r;
+    private final int p;
+    /**
+     * These values can be calculated automatically using the code {@see 
ScryptCipherProviderGroovyTest#calculateMinimumParameters} or manually updated 
by a maintainer
+     */
+    private static final int DEFAULT_N = Double.valueOf(Math.pow(2, 
14)).intValue();
+    private static final int DEFAULT_R = 8;
+    private static final int DEFAULT_P = 1;
+
+    private static final Pattern SCRYPT_SALT_FORMAT = 
Pattern.compile("^\\$s0\\$[a-f0-9]{5,16}\\$[\\w\\/\\.]{12,44}");
+    private static final Pattern MCRYPT_SALT_FORMAT = 
Pattern.compile("^\\$\\d+\\$\\d+\\$\\d+\\$[a-f0-9]{16,64}");
+
+    /**
+     * Instantiates a Scrypt cipher provider with the default parameters 
N=2^14, r=8, p=1.
+     */
+    public ScryptCipherProvider() {
+        this(DEFAULT_N, DEFAULT_R, DEFAULT_P);
+    }
+
+    /**
+     * Instantiates a Scrypt cipher provider with the specified N, r, p values.
+     *
+     * @param n the number of iterations
+     * @param r the block size in bytes
+     * @param p the parallelization factor
+     */
+    public ScryptCipherProvider(int n, int r, int p) {
+        this.n = n;
+        this.r = r;
+        this.p = p;
+        if (n < DEFAULT_N) {
+            logger.warn("The provided iteration count {} is below the 
recommended minimum {}", n, DEFAULT_N);
+        }
+        if (r < DEFAULT_R) {
+            logger.warn("The provided block size {} is below the recommended 
minimum {}", r, DEFAULT_R);
+        }
+        if (p < DEFAULT_P) {
+            logger.warn("The provided parallelization factor {} is below the 
recommended minimum {}", p, DEFAULT_P);
+        }
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key is 
derived by the KDF of the implementation. The IV is provided externally to 
allow for non-deterministic IVs, as IVs
+     * deterministically derived from the password are a potential 
vulnerability and compromise semantic security. See
+     * <a href="http://crypto.stackexchange.com/a/3970/12569";>Ilmari Karonen's 
answer on Crypto Stack Exchange</a>
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the complete salt (e.g. {@code 
"$2a$10$gUVbkVzp79H8YaCOsCVZNu".getBytes(StandardCharsets.UTF_8)})
+     * @param iv               the IV
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, byte[] iv, int keyLength, boolean encryptMode) throws 
Exception {
+        try {
+            return getInitializedCipher(encryptionMethod, password, salt, iv, 
keyLength, encryptMode);
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProcessException("Error initializing the cipher", e);
+        }
+    }
+
+    @Override
+    Logger getLogger() {
+        return logger;
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived by the KDF of the implementation.
+     * <p/>
+     * This method is deprecated because while Scrypt could generate a random 
salt to use, it would not be returned to the caller of this method and future 
derivations would fail. Provide a valid
+     * salt generated by {@link ScryptCipherProvider#generateSalt()}.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     * @deprecated Provide a salt parameter using {@link 
ScryptCipherProvider#getCipher(EncryptionMethod, String, byte[], int, boolean)}
+     */
+    @Deprecated
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, int keyLength, boolean encryptMode) throws Exception {
+        throw new UnsupportedOperationException("The cipher cannot be 
initialized without a valid salt. Use ScryptCipherProvider#generateSalt() to 
generate a valid salt");
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and 
IV if necessary) are derived by the KDF of the implementation.
+     *
+     * The IV can be retrieved by the calling method using {@link 
Cipher#getIV()}.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the complete salt (e.g. {@code 
"$s0$20101$gUVbkVzp79H8YaCOsCVZNu".getBytes(StandardCharsets.UTF_8)})
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String 
password, byte[] salt, int keyLength, boolean encryptMode) throws Exception {
+        return getCipher(encryptionMethod, password, salt, new byte[0], 
keyLength, encryptMode);
+    }
+
+    protected Cipher getInitializedCipher(EncryptionMethod encryptionMethod, 
String password, byte[] salt, byte[] iv, int keyLength, boolean encryptMode) 
throws Exception {
+        if (encryptionMethod == null) {
+            throw new IllegalArgumentException("The encryption method must be 
specified");
+        }
+        if (!encryptionMethod.isCompatibleWithStrongKDFs()) {
+            throw new IllegalArgumentException(encryptionMethod.name() + " is 
not compatible with Scrypt");
+        }
+
+        if (StringUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("Encryption with an empty 
password is not supported");
+        }
+
+        String algorithm = encryptionMethod.getAlgorithm();
+
+        final String cipherName = 
CipherUtility.parseCipherFromAlgorithm(algorithm);
+        if (!CipherUtility.isValidKeyLength(keyLength, cipherName)) {
+            throw new IllegalArgumentException(String.valueOf(keyLength) + " 
is not a valid key length for " + cipherName);
+        }
+
+        String scryptSalt = formatSaltForScrypt(salt);
+        List<Integer> params = new ArrayList<>(3);
+        byte[] rawSalt = new byte[Scrypt.getDefaultSaltLength()];
+
+        parseSalt(scryptSalt, rawSalt, params);
+
+        String hash = Scrypt.scrypt(password, rawSalt, params.get(0), 
params.get(1), params.get(2), keyLength);
+
+        // Split out the derived key from the hash and form a key object
+        final String[] hashComponents = hash.split("\\$");
+        final int HASH_INDEX = 4;
+        if (hashComponents.length < HASH_INDEX) {
+            throw new ProcessException("There was an error generating a scrypt 
hash -- the resulting hash was not properly formatted");
+        }
+        byte[] keyBytes = Base64.decodeBase64(hashComponents[HASH_INDEX]);
+        SecretKey tempKey = new SecretKeySpec(keyBytes, algorithm);
+
+        KeyedCipherProvider keyedCipherProvider = new AESKeyedCipherProvider();
+        return keyedCipherProvider.getCipher(encryptionMethod, tempKey, iv, 
encryptMode);
+    }
+
+    private void parseSalt(String scryptSalt, byte[] rawSalt, List<Integer> 
params) {
+        if (StringUtils.isEmpty(scryptSalt)) {
+            throw new IllegalArgumentException("Cannot parse empty salt");
+        }
+
+        /** Salt format is $s0$params$saltB64 where params is encoded 
according to
+         *  {@link Scrypt#parseParameters(String)}*/
+        final String[] saltComponents = scryptSalt.split("\\$");
+        if (saltComponents.length < 4) {
+            throw new IllegalArgumentException("Could not parse salt");
+        }
+        byte[] salt = Base64.decodeBase64(saltComponents[3]);
+        if (rawSalt.length < salt.length) {
+            byte[] tempBytes = new byte[salt.length];
+            System.arraycopy(rawSalt, 0, tempBytes, 0, rawSalt.length);
+            rawSalt = tempBytes;
+        }
+        System.arraycopy(salt, 0, rawSalt, 0, salt.length);
+
+        if (params == null) {
+            params = new ArrayList<>(3);
+        }
+        params.addAll(Scrypt.parseParameters(saltComponents[2]));
+    }
+
+    /**
+     * Formats the salt into a string which Scrypt can understand containing 
the N, r, p values along with the salt value. If the provided salt contains all 
values, the response will be unchanged.
+     * If it only contains the raw salt value, the resulting return value will 
also include the current instance version, N, r, and p.
+     *
+     * @param salt the provided salt
+     * @return the properly-formatted and complete salt
+     */
+    private String formatSaltForScrypt(byte[] salt) {
+        if (salt == null || salt.length == 0) {
+            throw new IllegalArgumentException("The salt cannot be empty. To 
generate a salt, use ScryptCipherProvider#generateSalt()");
+        }
+
+        String saltString = new String(salt, StandardCharsets.UTF_8);
+        Matcher matcher = SCRYPT_SALT_FORMAT.matcher(saltString);
+
+        if (matcher.find()) {
+            return saltString;
+        } else {
+            if (saltString.startsWith("$")) {
+                logger.warn("Salt starts with $ but is not valid scrypt salt");
+                matcher = MCRYPT_SALT_FORMAT.matcher(saltString);
+                if (matcher.find()) {
+                    logger.warn("The salt appears to be of the modified mcrypt 
format. Use ScryptCipherProvider#translateSalt(mcryptSalt) to form a valid 
salt");
+                    return translateSalt(saltString);
+                }
+
+                logger.info("Salt is not modified mcrypt format");
+            }
+            logger.info("Treating as raw salt bytes");
+
+            // Ensure the length of the salt
+            int saltLength = salt.length;
+            if (saltLength < 8 || saltLength > 32) {
+                throw new IllegalArgumentException("The raw salt must be 
between 8 and 32 bytes");
+            }
+            return Scrypt.formatSalt(salt, n, r, p);
+        }
+    }
+
+    /**
+     * Translates a salt from the mcrypt format {@code $n$r$p$salt_hex} to the 
Java scrypt format {@code $s0$params$saltBase64}.
+     *
+     * @param mcryptSalt the mcrypt-formatted salt string
+     * @return the formatted salt to use with Java Scrypt
+     */
+    public String translateSalt(String mcryptSalt) {
+        if (StringUtils.isEmpty(mcryptSalt)) {
+            throw new IllegalArgumentException("Cannot translate empty salt");
+        }
+
+        // Format should be $n$r$p$saltHex
+        Matcher matcher = MCRYPT_SALT_FORMAT.matcher(mcryptSalt);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Salt is not valid mcrypt 
format of $n$r$p$saltHex");
+        }
+
+        String[] components = mcryptSalt.split("\\$");
+        try {
+            return 
Scrypt.formatSalt(Hex.decodeHex(components[4].toCharArray()), 
Integer.valueOf(components[1]), Integer.valueOf(components[2]), 
Integer.valueOf(components[3]));
+        } catch (DecoderException e) {
+            final String msg = "Mcrypt salt was not properly hex-encoded";
+            logger.warn(msg);
+            throw new IllegalArgumentException(msg);
+        }
+    }
+
+    @Override
+    public byte[] generateSalt() {
+        byte[] salt = new byte[Scrypt.getDefaultSaltLength()];
+        new SecureRandom().nextBytes(salt);
+        return Scrypt.formatSalt(salt, n, r, 
p).getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public int getDefaultSaltLength() {
+        return Scrypt.getDefaultSaltLength();
+    }
+
+    protected int getN() {
+        return n;
+    }
+
+    protected int getR() {
+        return r;
+    }
+
+    protected int getP() {
+        return p;
+    }
+}

Reply via email to