http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java
new file mode 100644
index 0000000..2aeae3d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java
@@ -0,0 +1,510 @@
+/*
+ * 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.security.util.crypto.scrypt;
+
+import static java.lang.Integer.MAX_VALUE;
+import static java.lang.System.arraycopy;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.security.util.crypto.CipherUtility;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Copyright (C) 2011 - Will Glozer. All rights reserved.
+ *
+ * Taken from Will Glozer's port of Colin Percival's C implementation. Glozer's project located at https://github.com/wg/scrypt was released under the ASF
+ * 2.0 license and has not been updated since May 25, 2013 and there are outstanding issues which have been patched in this version.
+ *
+ * An implementation of the scrypt
+ * key derivation function.
+ *
+ * Allows for hashing passwords using the
+ * scrypt key derivation function
+ * and comparing a plain text password to a hashed one.
+ */
+public class Scrypt {
+ private static final Logger logger = LoggerFactory.getLogger(Scrypt.class);
+
+ private static final int DEFAULT_SALT_LENGTH = 16;
+
+ /**
+ * Hash the supplied plaintext password and generate output in the format described
+ * below:
+ *
+ * The hashed output is an
+ * extended implementation of the Modular Crypt Format that also includes the scrypt
+ * algorithm parameters.
+ *
+ * Format: $s0$PARAMS$SALT$KEY
.
+ *
+ *
+ * - PARAMS
- 32-bit hex integer containing log2(N) (16 bits), r (8 bits), and p (8 bits)
+ * - SALT
- base64-encoded salt
+ * - KEY
- base64-encoded derived key
+ *
+ *
+ * s0
identifies version 0 of the scrypt format, using a 128-bit salt and 256-bit derived key.
+ *
+ * This method generates a 16 byte random salt internally.
+ *
+ * @param password password
+ * @param n CPU cost parameter
+ * @param r memory cost parameter
+ * @param p parallelization parameter
+ * @param dkLen the desired key length in bits
+ * @return the hashed password
+ */
+ public static String scrypt(String password, int n, int r, int p, int dkLen) {
+ byte[] salt = new byte[DEFAULT_SALT_LENGTH];
+ new SecureRandom().nextBytes(salt);
+
+ return scrypt(password, salt, n, r, p, dkLen);
+ }
+
+ /**
+ * Hash the supplied plaintext password and generate output in the format described
+ * in {@link Scrypt#scrypt(String, int, int, int, int)}.
+ *
+ * @param password password
+ * @param salt the raw salt (16 bytes)
+ * @param n CPU cost parameter
+ * @param r memory cost parameter
+ * @param p parallelization parameter
+ * @param dkLen the desired key length in bits
+ * @return the hashed password
+ */
+ public static String scrypt(String password, byte[] salt, int n, int r, int p, int dkLen) {
+ try {
+ byte[] derived = deriveScryptKey(password.getBytes(StandardCharsets.UTF_8), salt, n, r, p, dkLen);
+
+ return formatHash(salt, n, r, p, derived);
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
+ }
+ }
+
+ public static String formatSalt(byte[] salt, int n, int r, int p) {
+ String params = encodeParams(n, r, p);
+
+ StringBuilder sb = new StringBuilder((salt.length) * 2);
+ sb.append("$s0$").append(params).append('$');
+ sb.append(CipherUtility.encodeBase64NoPadding(salt));
+
+ return sb.toString();
+ }
+
+ private static String encodeParams(int n, int r, int p) {
+ return Long.toString(log2(n) << 16L | r << 8 | p, 16);
+ }
+
+ private static String formatHash(byte[] salt, int n, int r, int p, byte[] derived) {
+ StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
+ sb.append(formatSalt(salt, n, r, p)).append('$');
+ sb.append(CipherUtility.encodeBase64NoPadding(derived));
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the expected memory cost of the provided parameters in bytes.
+ *
+ * @param n the N value, iterations >= 2
+ * @param r the r value, block size >= 1
+ * @param p the p value, parallelization factor >= 1
+ * @return the memory cost in bytes
+ */
+ public static int calculateExpectedMemory(int n, int r, int p) {
+ return 128 * r * n + 128 * r * p;
+ }
+
+ /**
+ * Compare the supplied plaintext password to a hashed password.
+ *
+ * @param password plaintext password
+ * @param hashed scrypt hashed password
+ * @return true if password matches hashed value
+ */
+ public static boolean check(String password, String hashed) {
+ try {
+ if (StringUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("Password cannot be empty");
+ }
+
+ if (StringUtils.isEmpty(hashed)) {
+ throw new IllegalArgumentException("Hash cannot be empty");
+ }
+
+ String[] parts = hashed.split("\\$");
+
+ if (parts.length != 5 || !parts[1].equals("s0")) {
+ throw new IllegalArgumentException("Hash is not properly formatted");
+ }
+
+ List splitParams = parseParameters(parts[2]);
+ int n = splitParams.get(0);
+ int r = splitParams.get(1);
+ int p = splitParams.get(2);
+
+ byte[] salt = Base64.decodeBase64(parts[3]);
+ byte[] derived0 = Base64.decodeBase64(parts[4]);
+
+ // Previously this was hard-coded to 32 bits but the publicly-available scrypt methods accept arbitrary bit lengths
+ int hashLength = derived0.length * 8;
+ byte[] derived1 = deriveScryptKey(password.getBytes(StandardCharsets.UTF_8), salt, n, r, p, hashLength);
+
+ if (derived0.length != derived1.length) return false;
+
+ int result = 0;
+ for (int i = 0; i < derived0.length; i++) {
+ result |= derived0[i] ^ derived1[i];
+ }
+ return result == 0;
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
+ }
+ }
+
+ /**
+ * Parses the individual values from the encoded params value in the modified-mcrypt format for the salt & hash.
+ *
+ * Example:
+ *
+ * Hash: $s0$e0801$epIxT/h6HbbwHaehFnh/bw$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0
+ * Params: e0801
+ *
+ * N = 16384
+ * r = 8
+ * p = 1
+ *
+ * @param encodedParams the String representation of the second section of the mcrypt format hash
+ * @return a list containing N, r, p
+ */
+ public static List parseParameters(String encodedParams) {
+ long params = Long.parseLong(encodedParams, 16);
+
+ List paramsList = new ArrayList<>(3);
+
+ // Parse N, r, p from encoded value and add to return list
+ paramsList.add((int) Math.pow(2, params >> 16 & 0xffff));
+ paramsList.add((int) params >> 8 & 0xff);
+ paramsList.add((int) params & 0xff);
+
+ return paramsList;
+ }
+
+ private static int log2(int n) {
+ int log = 0;
+ if ((n & 0xffff0000) != 0) {
+ n >>>= 16;
+ log = 16;
+ }
+ if (n >= 256) {
+ n >>>= 8;
+ log += 8;
+ }
+ if (n >= 16) {
+ n >>>= 4;
+ log += 4;
+ }
+ if (n >= 4) {
+ n >>>= 2;
+ log += 2;
+ }
+ return log + (n >>> 1);
+ }
+
+ /**
+ * Implementation of the scrypt KDF.
+ *
+ * @param password password
+ * @param salt salt
+ * @param n CPU cost parameter
+ * @param r memory cost parameter
+ * @param p parallelization parameter
+ * @param dkLen intended length of the derived key in bits
+ * @return the derived key
+ * @throws GeneralSecurityException when HMAC_SHA256 is not available
+ */
+ protected static byte[] deriveScryptKey(byte[] password, byte[] salt, int n, int r, int p, int dkLen) throws GeneralSecurityException {
+ if (n < 2 || (n & (n - 1)) != 0) {
+ throw new IllegalArgumentException("N must be a power of 2 greater than 1");
+ }
+
+ if (r < 1) {
+ throw new IllegalArgumentException("Parameter r must be 1 or greater");
+ }
+
+ if (p < 1) {
+ throw new IllegalArgumentException("Parameter p must be 1 or greater");
+ }
+
+ if (n > MAX_VALUE / 128 / r) {
+ throw new IllegalArgumentException("Parameter N is too large");
+ }
+
+ // Must be enforced before r check
+ if (p > MAX_VALUE / 128) {
+ throw new IllegalArgumentException("Parameter p is too large");
+ }
+
+ if (r > MAX_VALUE / 128 / p) {
+ throw new IllegalArgumentException("Parameter r is too large");
+ }
+
+ if (password == null || password.length == 0) {
+ throw new IllegalArgumentException("Password cannot be empty");
+ }
+
+ int saltLength = salt == null ? 0 : salt.length;
+ if (salt == null || saltLength == 0) {
+ // Do not enforce this check here. According to the scrypt spec, the salt can be empty. However, in the user-facing ScryptCipherProvider, enforce an arbitrary check to avoid empty salts
+ logger.warn("An empty salt was used for scrypt key derivation");
+// throw new IllegalArgumentException("Salt cannot be empty");
+ // as the Exception is not being thrown, prevent NPE if salt is null by setting it to empty array
+ if( salt == null ) salt = new byte[]{};
+ }
+
+ if (saltLength < 8 || saltLength > 32) {
+ // Do not enforce this check here. According to the scrypt spec, the salt can be empty. However, in the user-facing ScryptCipherProvider, enforce an arbitrary check of [8..32] bytes
+ logger.warn("A salt of length {} was used for scrypt key derivation", saltLength);
+// throw new IllegalArgumentException("Salt must be between 8 and 32 bytes");
+ }
+
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(new SecretKeySpec(password, "HmacSHA256"));
+
+ byte[] b = new byte[128 * r * p];
+ byte[] xy = new byte[256 * r];
+ byte[] v = new byte[128 * r * n];
+ int i;
+
+ pbkdf2(mac, salt, 1, b, p * 128 * r);
+
+ for (i = 0; i < p; i++) {
+ smix(b, i * 128 * r, r, n, v, xy);
+ }
+
+ byte[] dk = new byte[dkLen / 8];
+ pbkdf2(mac, b, 1, dk, dkLen / 8);
+ return dk;
+ }
+
+ /**
+ * Implementation of PBKDF2 (RFC2898).
+ *
+ * @param alg the HMAC algorithm to use
+ * @param p the password
+ * @param s the salt
+ * @param c the iteration count
+ * @param dkLen the intended length, in octets, of the derived key
+ * @return The derived key
+ */
+ private static byte[] pbkdf2(String alg, byte[] p, byte[] s, int c, int dkLen) throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(alg);
+ mac.init(new SecretKeySpec(p, alg));
+ byte[] dk = new byte[dkLen];
+ pbkdf2(mac, s, c, dk, dkLen);
+ return dk;
+ }
+
+ /**
+ * Implementation of PBKDF2 (RFC2898).
+ *
+ * @param mac the pre-initialized {@link Mac} instance to use
+ * @param s the salt
+ * @param c the iteration count
+ * @param dk the byte array that derived key will be placed in
+ * @param dkLen the intended length, in octets, of the derived key
+ * @throws GeneralSecurityException if the key length is too long
+ */
+ private static void pbkdf2(Mac mac, byte[] s, int c, byte[] dk, int dkLen) throws GeneralSecurityException {
+ int hLen = mac.getMacLength();
+
+ if (dkLen > (Math.pow(2, 32) - 1) * hLen) {
+ throw new GeneralSecurityException("Requested key length too long");
+ }
+
+ byte[] U = new byte[hLen];
+ byte[] T = new byte[hLen];
+ byte[] block1 = new byte[s.length + 4];
+
+ int l = (int) Math.ceil((double) dkLen / hLen);
+ int r = dkLen - (l - 1) * hLen;
+
+ arraycopy(s, 0, block1, 0, s.length);
+
+ for (int i = 1; i <= l; i++) {
+ block1[s.length + 0] = (byte) (i >> 24 & 0xff);
+ block1[s.length + 1] = (byte) (i >> 16 & 0xff);
+ block1[s.length + 2] = (byte) (i >> 8 & 0xff);
+ block1[s.length + 3] = (byte) (i >> 0 & 0xff);
+
+ mac.update(block1);
+ mac.doFinal(U, 0);
+ arraycopy(U, 0, T, 0, hLen);
+
+ for (int j = 1; j < c; j++) {
+ mac.update(U);
+ mac.doFinal(U, 0);
+
+ for (int k = 0; k < hLen; k++) {
+ T[k] ^= U[k];
+ }
+ }
+
+ arraycopy(T, 0, dk, (i - 1) * hLen, (i == l ? r : hLen));
+ }
+ }
+
+ private static void smix(byte[] b, int bi, int r, int n, byte[] v, byte[] xy) {
+ int xi = 0;
+ int yi = 128 * r;
+ int i;
+
+ arraycopy(b, bi, xy, xi, 128 * r);
+
+ for (i = 0; i < n; i++) {
+ arraycopy(xy, xi, v, i * (128 * r), 128 * r);
+ blockmix_salsa8(xy, xi, yi, r);
+ }
+
+ for (i = 0; i < n; i++) {
+ int j = integerify(xy, xi, r) & (n - 1);
+ blockxor(v, j * (128 * r), xy, xi, 128 * r);
+ blockmix_salsa8(xy, xi, yi, r);
+ }
+
+ arraycopy(xy, xi, b, bi, 128 * r);
+ }
+
+ private static void blockmix_salsa8(byte[] by, int bi, int yi, int r) {
+ byte[] X = new byte[64];
+ int i;
+
+ arraycopy(by, bi + (2 * r - 1) * 64, X, 0, 64);
+
+ for (i = 0; i < 2 * r; i++) {
+ blockxor(by, i * 64, X, 0, 64);
+ salsa20_8(X);
+ arraycopy(X, 0, by, yi + (i * 64), 64);
+ }
+
+ for (i = 0; i < r; i++) {
+ arraycopy(by, yi + (i * 2) * 64, by, bi + (i * 64), 64);
+ }
+
+ for (i = 0; i < r; i++) {
+ arraycopy(by, yi + (i * 2 + 1) * 64, by, bi + (i + r) * 64, 64);
+ }
+ }
+
+ private static int r(int a, int b) {
+ return (a << b) | (a >>> (32 - b));
+ }
+
+ private static void salsa20_8(byte[] b) {
+ int[] b32 = new int[16];
+ int[] x = new int[16];
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ b32[i] = (b[i * 4 + 0] & 0xff) << 0;
+ b32[i] |= (b[i * 4 + 1] & 0xff) << 8;
+ b32[i] |= (b[i * 4 + 2] & 0xff) << 16;
+ b32[i] |= (b[i * 4 + 3] & 0xff) << 24;
+ }
+
+ arraycopy(b32, 0, x, 0, 16);
+
+ for (i = 8; i > 0; i -= 2) {
+ x[4] ^= r(x[0] + x[12], 7);
+ x[8] ^= r(x[4] + x[0], 9);
+ x[12] ^= r(x[8] + x[4], 13);
+ x[0] ^= r(x[12] + x[8], 18);
+ x[9] ^= r(x[5] + x[1], 7);
+ x[13] ^= r(x[9] + x[5], 9);
+ x[1] ^= r(x[13] + x[9], 13);
+ x[5] ^= r(x[1] + x[13], 18);
+ x[14] ^= r(x[10] + x[6], 7);
+ x[2] ^= r(x[14] + x[10], 9);
+ x[6] ^= r(x[2] + x[14], 13);
+ x[10] ^= r(x[6] + x[2], 18);
+ x[3] ^= r(x[15] + x[11], 7);
+ x[7] ^= r(x[3] + x[15], 9);
+ x[11] ^= r(x[7] + x[3], 13);
+ x[15] ^= r(x[11] + x[7], 18);
+ x[1] ^= r(x[0] + x[3], 7);
+ x[2] ^= r(x[1] + x[0], 9);
+ x[3] ^= r(x[2] + x[1], 13);
+ x[0] ^= r(x[3] + x[2], 18);
+ x[6] ^= r(x[5] + x[4], 7);
+ x[7] ^= r(x[6] + x[5], 9);
+ x[4] ^= r(x[7] + x[6], 13);
+ x[5] ^= r(x[4] + x[7], 18);
+ x[11] ^= r(x[10] + x[9], 7);
+ x[8] ^= r(x[11] + x[10], 9);
+ x[9] ^= r(x[8] + x[11], 13);
+ x[10] ^= r(x[9] + x[8], 18);
+ x[12] ^= r(x[15] + x[14], 7);
+ x[13] ^= r(x[12] + x[15], 9);
+ x[14] ^= r(x[13] + x[12], 13);
+ x[15] ^= r(x[14] + x[13], 18);
+ }
+
+ for (i = 0; i < 16; ++i) b32[i] = x[i] + b32[i];
+
+ for (i = 0; i < 16; i++) {
+ b[i * 4 + 0] = (byte) (b32[i] >> 0 & 0xff);
+ b[i * 4 + 1] = (byte) (b32[i] >> 8 & 0xff);
+ b[i * 4 + 2] = (byte) (b32[i] >> 16 & 0xff);
+ b[i * 4 + 3] = (byte) (b32[i] >> 24 & 0xff);
+ }
+ }
+
+ private static void blockxor(byte[] s, int si, byte[] d, int di, int len) {
+ for (int i = 0; i < len; i++) {
+ d[di + i] ^= s[si + i];
+ }
+ }
+
+ private static int integerify(byte[] b, int bi, int r) {
+ int n;
+
+ bi += (2 * r - 1) * 64;
+
+ n = (b[bi + 0] & 0xff) << 0;
+ n |= (b[bi + 1] & 0xff) << 8;
+ n |= (b[bi + 2] & 0xff) << 16;
+ n |= (b[bi + 3] & 0xff) << 24;
+
+ return n;
+ }
+
+ public static int getDefaultSaltLength() {
+ return DEFAULT_SALT_LENGTH;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestEncryptContentGroovy.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestEncryptContentGroovy.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestEncryptContentGroovy.groovy
index e3de6b0..f940640 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestEncryptContentGroovy.groovy
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestEncryptContentGroovy.groovy
@@ -17,10 +17,10 @@
package org.apache.nifi.processors.standard
import org.apache.nifi.components.ValidationResult
-import org.apache.nifi.processors.standard.util.crypto.CipherUtility
-import org.apache.nifi.processors.standard.util.crypto.PasswordBasedEncryptor
import org.apache.nifi.security.util.EncryptionMethod
import org.apache.nifi.security.util.KeyDerivationFunction
+import org.apache.nifi.security.util.crypto.CipherUtility
+import org.apache.nifi.security.util.crypto.PasswordBasedEncryptor
import org.apache.nifi.util.MockFlowFile
import org.apache.nifi.util.MockProcessContext
import org.apache.nifi.util.TestRunner
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProviderGroovyTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProviderGroovyTest.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProviderGroovyTest.groovy
deleted file mode 100644
index 0596d7d..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProviderGroovyTest.groovy
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * 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.binary.Hex
-import org.apache.nifi.security.util.EncryptionMethod
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.*
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import javax.crypto.SecretKey
-import javax.crypto.spec.SecretKeySpec
-import java.security.SecureRandom
-import java.security.Security
-
-import static groovy.test.GroovyAssert.shouldFail
-
-@RunWith(JUnit4.class)
-public class AESKeyedCipherProviderGroovyTest {
- private static final Logger logger = LoggerFactory.getLogger(AESKeyedCipherProviderGroovyTest.class)
-
- private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210"
-
- private static final List keyedEncryptionMethods = EncryptionMethod.values().findAll { it.keyedCipher }
-
- private static final SecretKey key = new SecretKeySpec(Hex.decodeHex(KEY_HEX as char[]), "AES")
-
- @BeforeClass
- public static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
- @Test
- public void testGetCipherShouldBeInternallyConsistent() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final String plaintext = "This is a plaintext message."
-
- // Act
- for (EncryptionMethod em : keyedEncryptionMethods) {
- logger.info("Using algorithm: ${em.getAlgorithm()}")
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, key, true)
- byte[] iv = cipher.getIV()
- logger.info("IV: ${Hex.encodeHexString(iv)}")
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"))
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}")
-
- cipher = cipherProvider.getCipher(em, key, iv, false)
- byte[] recoveredBytes = cipher.doFinal(cipherBytes)
- String recovered = new String(recoveredBytes, "UTF-8")
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert plaintext.equals(recovered)
- }
- }
-
- @Test
- public void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final String plaintext = "This is a plaintext message."
-
- // Act
- keyedEncryptionMethods.each { EncryptionMethod em ->
- logger.info("Using algorithm: ${em.getAlgorithm()}")
- byte[] iv = cipherProvider.generateIV()
- logger.info("IV: ${Hex.encodeHexString(iv)}")
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, key, iv, true)
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"))
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}")
-
- cipher = cipherProvider.getCipher(em, key, iv, false)
- byte[] recoveredBytes = cipher.doFinal(cipherBytes)
- String recovered = new String(recoveredBytes, "UTF-8")
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert plaintext.equals(recovered)
- }
- }
-
- @Test
- public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception {
- // Arrange
- Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.",
- PasswordBasedEncryptor.supportsUnlimitedStrength())
-
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
- final List LONG_KEY_LENGTHS = [192, 256]
-
- final String plaintext = "This is a plaintext message."
-
- SecureRandom secureRandom = new SecureRandom()
-
- // Act
- keyedEncryptionMethods.each { EncryptionMethod em ->
- // Re-use the same IV for the different length keys to ensure the encryption is different
- byte[] iv = cipherProvider.generateIV()
- logger.info("IV: ${Hex.encodeHexString(iv)}")
-
- LONG_KEY_LENGTHS.each { int keyLength ->
- logger.info("Using algorithm: ${em.getAlgorithm()} with key length ${keyLength}")
-
- // Generate a key
- byte[] keyBytes = new byte[keyLength / 8]
- secureRandom.nextBytes(keyBytes)
- SecretKey localKey = new SecretKeySpec(keyBytes, "AES")
- logger.info("Key: ${Hex.encodeHexString(keyBytes)} ${keyBytes.length}")
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, localKey, iv, true)
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"))
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}")
-
- cipher = cipherProvider.getCipher(em, localKey, iv, false)
- byte[] recoveredBytes = cipher.doFinal(cipherBytes)
- String recovered = new String(recoveredBytes, "UTF-8")
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert plaintext.equals(recovered)
- }
- }
- }
-
- @Test
- public void testShouldRejectEmptyKey() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
-
- // Act
- def msg = shouldFail(IllegalArgumentException) {
- cipherProvider.getCipher(encryptionMethod, null, true)
- }
-
- // Assert
- assert msg =~ "The key must be specified"
- }
-
- @Test
- public void testShouldRejectIncorrectLengthKey() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- SecretKey localKey = new SecretKeySpec(Hex.decodeHex("0123456789ABCDEF" as char[]), "AES")
- assert ![128, 192, 256].contains(localKey.encoded.length)
-
- final EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
-
- // Act
- def msg = shouldFail(IllegalArgumentException) {
- cipherProvider.getCipher(encryptionMethod, localKey, true)
- }
-
- // Assert
- assert msg =~ "The key must be of length \\[128, 192, 256\\]"
- }
-
- @Test
- public void testShouldRejectEmptyEncryptionMethod() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- // Act
- def msg = shouldFail(IllegalArgumentException) {
- cipherProvider.getCipher(null, key, true)
- }
-
- // Assert
- assert msg =~ "The encryption method must be specified"
- }
-
- @Test
- public void testShouldRejectUnsupportedEncryptionMethod() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final EncryptionMethod encryptionMethod = EncryptionMethod.MD5_128AES
-
- // Act
- def msg = shouldFail(IllegalArgumentException) {
- cipherProvider.getCipher(encryptionMethod, key, true)
- }
-
- // Assert
- assert msg =~ " requires a PBECipherProvider"
- }
-
- @Test
- public void testGetCipherShouldSupportExternalCompatibility() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final String PLAINTEXT = "This is a plaintext message."
-
- // These values can be generated by running `$ ./openssl_aes.rb` in the terminal
- final byte[] IV = Hex.decodeHex("e0bc8cc7fbc0bdfdc184dc22ce2fcb5b" as char[])
- final byte[] LOCAL_KEY = Hex.decodeHex("c72943d27c3e5a276169c5998a779117" as char[])
- final String CIPHER_TEXT = "a2725ea55c7dd717664d044cab0f0b5f763653e322c27df21954f5be394efb1b"
- byte[] cipherBytes = Hex.decodeHex(CIPHER_TEXT as char[])
-
- SecretKey localKey = new SecretKeySpec(LOCAL_KEY, "AES")
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}")
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}")
-
- // Act
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, localKey, IV, false)
- byte[] recoveredBytes = cipher.doFinal(cipherBytes)
- String recovered = new String(recoveredBytes, "UTF-8")
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert PLAINTEXT.equals(recovered)
- }
-
- @Test
- public void testGetCipherForDecryptShouldRequireIV() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final String plaintext = "This is a plaintext message."
-
- // Act
- keyedEncryptionMethods.each { EncryptionMethod em ->
- logger.info("Using algorithm: ${em.getAlgorithm()}")
- byte[] iv = cipherProvider.generateIV()
- logger.info("IV: ${Hex.encodeHexString(iv)}")
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, key, iv, true)
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"))
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}")
-
- def msg = shouldFail(IllegalArgumentException) {
- cipher = cipherProvider.getCipher(em, key, false)
- }
-
- // Assert
- assert msg =~ "Cannot decrypt without a valid IV"
- }
- }
-
- @Test
- public void testGetCipherShouldRejectInvalidIVLengths() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- final def INVALID_IVS = (0..15).collect { int length -> new byte[length] }
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
-
- // Act
- INVALID_IVS.each { byte[] badIV ->
- logger.info("IV: ${Hex.encodeHexString(badIV)} ${badIV.length}")
-
- // Encrypt should print a warning about the bad IV but overwrite it
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, key, badIV, true)
-
- // Decrypt should fail
- def msg = shouldFail(IllegalArgumentException) {
- cipher = cipherProvider.getCipher(encryptionMethod, key, badIV, false)
- }
- logger.expected(msg)
-
- // Assert
- assert msg =~ "Cannot decrypt without a valid IV"
- }
- }
-
- @Test
- public void testGetCipherShouldRejectEmptyIV() throws Exception {
- // Arrange
- KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider()
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
-
- byte[] badIV = [0x00 as byte] * 16 as byte[]
-
- // Act
- logger.info("IV: ${Hex.encodeHexString(badIV)} ${badIV.length}")
-
- // Encrypt should print a warning about the bad IV but overwrite it
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, key, badIV, true)
- logger.info("IV after encrypt: ${Hex.encodeHexString(cipher.getIV())}")
-
- // Decrypt should fail
- def msg = shouldFail(IllegalArgumentException) {
- cipher = cipherProvider.getCipher(encryptionMethod, key, badIV, false)
- }
- logger.expected(msg)
-
- // Assert
- assert msg =~ "Cannot decrypt without a valid IV"
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProviderGroovyTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProviderGroovyTest.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProviderGroovyTest.groovy
deleted file mode 100644
index 396e3b2..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProviderGroovyTest.groovy
+++ /dev/null
@@ -1,539 +0,0 @@
-/*
- * 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.binary.Base64
-import org.apache.commons.codec.binary.Hex
-import org.apache.nifi.processors.standard.util.crypto.bcrypt.BCrypt
-import org.apache.nifi.security.util.EncryptionMethod
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.security.Security
-
-import static groovy.test.GroovyAssert.shouldFail
-import static org.junit.Assert.assertTrue
-
-@RunWith(JUnit4.class)
-public class BcryptCipherProviderGroovyTest {
- private static final Logger logger = LoggerFactory.getLogger(BcryptCipherProviderGroovyTest.class);
-
- private static List strongKDFEncryptionMethods
-
- private static final int DEFAULT_KEY_LENGTH = 128;
- public static final String MICROBENCHMARK = "microbenchmark"
- private static ArrayList AES_KEY_LENGTHS
-
- @BeforeClass
- public static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider());
-
- strongKDFEncryptionMethods = EncryptionMethod.values().findAll { it.isCompatibleWithStrongKDFs() }
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
-
- if (PasswordBasedEncryptor.supportsUnlimitedStrength()) {
- AES_KEY_LENGTHS = [128, 192, 256]
- } else {
- AES_KEY_LENGTHS = [128]
- }
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
-
- }
-
- @Test
- public void testGetCipherShouldBeInternallyConsistent() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PASSWORD = "shortPassword";
- final byte[] SALT = cipherProvider.generateSalt()
-
- final String plaintext = "This is a plaintext message.";
-
- // Act
- for (EncryptionMethod em : strongKDFEncryptionMethods) {
- logger.info("Using algorithm: ${em.getAlgorithm()}");
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true);
- byte[] iv = cipher.getIV();
- logger.info("IV: ${Hex.encodeHexString(iv)}")
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"));
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false);
- byte[] recoveredBytes = cipher.doFinal(cipherBytes);
- String recovered = new String(recoveredBytes, "UTF-8");
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert plaintext.equals(recovered);
- }
- }
-
- @Test
- public void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PASSWORD = "shortPassword";
- final byte[] SALT = cipherProvider.generateSalt()
- final byte[] IV = Hex.decodeHex("01" * 16 as char[]);
-
- final String plaintext = "This is a plaintext message.";
-
- // Act
- for (EncryptionMethod em : strongKDFEncryptionMethods) {
- logger.info("Using algorithm: ${em.getAlgorithm()}");
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true);
- logger.info("IV: ${Hex.encodeHexString(IV)}")
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"));
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false);
- byte[] recoveredBytes = cipher.doFinal(cipherBytes);
- String recovered = new String(recoveredBytes, "UTF-8");
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert plaintext.equals(recovered);
- }
- }
-
- @Test
- public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception {
- // Arrange
- Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.",
- PasswordBasedEncryptor.supportsUnlimitedStrength());
-
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PASSWORD = "shortPassword";
- final byte[] SALT = cipherProvider.generateSalt()
-
- final int LONG_KEY_LENGTH = 256
-
- final String plaintext = "This is a plaintext message.";
-
- // Act
- for (EncryptionMethod em : strongKDFEncryptionMethods) {
- logger.info("Using algorithm: ${em.getAlgorithm()}");
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true);
- byte[] iv = cipher.getIV();
- logger.info("IV: ${Hex.encodeHexString(iv)}")
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"));
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false);
- byte[] recoveredBytes = cipher.doFinal(cipherBytes);
- String recovered = new String(recoveredBytes, "UTF-8");
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert plaintext.equals(recovered);
- }
- }
-
- @Test
- public void testHashPWShouldMatchTestVectors() {
- // Arrange
- final String PASSWORD = 'abcdefghijklmnopqrstuvwxyz'
- final String SALT = '$2a$10$fVH8e28OQRj9tqiDXs1e1u'
- final String EXPECTED_HASH = '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'
-// final int WORK_FACTOR = 10
-
- // Act
- String calculatedHash = BCrypt.hashpw(PASSWORD, SALT)
- logger.info("Generated ${calculatedHash}")
-
- // Assert
- assert calculatedHash == EXPECTED_HASH
- }
-
- @Test
- public void testGetCipherShouldSupportExternalCompatibility() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PLAINTEXT = "This is a plaintext message.";
- final String PASSWORD = "thisIsABadPassword";
-
- // These values can be generated by running `$ ./openssl_bcrypt` in the terminal
- final byte[] SALT = Hex.decodeHex("81455b915ce9efd1fc61a08eb0255936" as char[]);
- final byte[] IV = Hex.decodeHex("41a51e0150df6a1f72826b36c6371f3f" as char[]);
-
- // $v2$w2$base64_salt_22__base64_hash_31
- final String FULL_HASH = "\$2a\$10\$gUVbkVzp79H8YaCOsCVZNuz/d759nrMKzjuviaS5/WdcKHzqngGKi"
- logger.info("Full Hash: ${FULL_HASH}")
- final String HASH = FULL_HASH[-31..-1]
- logger.info(" Hash: ${HASH.padLeft(60, " ")}")
- logger.info(" B64 Salt: ${CipherUtility.encodeBase64NoPadding(SALT).padLeft(29, " ")}")
-
- String extractedSalt = FULL_HASH[7..<29]
- logger.info("Extracted Salt: ${extractedSalt}")
- String extractedSaltHex = Hex.encodeHexString(Base64.decodeBase64(extractedSalt))
- logger.info("Extracted Salt (hex): ${extractedSaltHex}")
- logger.info(" Expected Salt (hex): ${Hex.encodeHexString(SALT)}")
-
- final String CIPHER_TEXT = "3a226ba2b3c8fe559acb806620001246db289375ba8075a68573478b56a69f15"
- byte[] cipherBytes = Hex.decodeHex(CIPHER_TEXT as char[])
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}");
- logger.info("External cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- // Sanity check
- Cipher rubyCipher = Cipher.getInstance(encryptionMethod.algorithm, "BC")
- def rubyKey = new SecretKeySpec(Hex.decodeHex("724cd9e1b0b9e87c7f7e7d7b270bca07" as char[]), "AES")
- def ivSpec = new IvParameterSpec(IV)
- rubyCipher.init(Cipher.ENCRYPT_MODE, rubyKey, ivSpec)
- byte[] rubyCipherBytes = rubyCipher.doFinal(PLAINTEXT.bytes)
- logger.info("Expected cipher text: ${Hex.encodeHexString(rubyCipherBytes)}")
- rubyCipher.init(Cipher.DECRYPT_MODE, rubyKey, ivSpec)
- assert rubyCipher.doFinal(rubyCipherBytes) == PLAINTEXT.bytes
- assert rubyCipher.doFinal(cipherBytes) == PLAINTEXT.bytes
- logger.sanity("Decrypted external cipher text and generated cipher text successfully")
-
- // Sanity for hash generation
- final String FULL_SALT = FULL_HASH[0..<29]
- logger.sanity("Salt from external: ${FULL_SALT}")
- String generatedHash = BCrypt.hashpw(PASSWORD, FULL_SALT)
- logger.sanity("Generated hash: ${generatedHash}")
- assert generatedHash == FULL_HASH
-
- // Act
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, FULL_SALT.bytes, IV, DEFAULT_KEY_LENGTH, false);
- byte[] recoveredBytes = cipher.doFinal(cipherBytes);
- String recovered = new String(recoveredBytes, "UTF-8");
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert PLAINTEXT.equals(recovered);
- }
-
- @Test
- public void testGetCipherShouldHandleFullSalt() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PLAINTEXT = "This is a plaintext message.";
- final String PASSWORD = "thisIsABadPassword";
-
- // These values can be generated by running `$ ./openssl_bcrypt.rb` in the terminal
- final byte[] IV = Hex.decodeHex("41a51e0150df6a1f72826b36c6371f3f" as char[]);
-
- // $v2$w2$base64_salt_22__base64_hash_31
- final String FULL_HASH = "\$2a\$10\$gUVbkVzp79H8YaCOsCVZNuz/d759nrMKzjuviaS5/WdcKHzqngGKi"
- logger.info("Full Hash: ${FULL_HASH}")
- final String FULL_SALT = FULL_HASH[0..<29]
- logger.info(" Salt: ${FULL_SALT}")
- final String HASH = FULL_HASH[-31..-1]
- logger.info(" Hash: ${HASH.padLeft(60, " ")}")
-
- String extractedSalt = FULL_HASH[7..<29]
- logger.info("Extracted Salt: ${extractedSalt}")
- String extractedSaltHex = Hex.encodeHexString(Base64.decodeBase64(extractedSalt))
- logger.info("Extracted Salt (hex): ${extractedSaltHex}")
-
- final String CIPHER_TEXT = "3a226ba2b3c8fe559acb806620001246db289375ba8075a68573478b56a69f15"
- byte[] cipherBytes = Hex.decodeHex(CIPHER_TEXT as char[])
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}");
- logger.info("External cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- // Act
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, FULL_SALT.bytes, IV, DEFAULT_KEY_LENGTH, false);
- byte[] recoveredBytes = cipher.doFinal(cipherBytes);
- String recovered = new String(recoveredBytes, "UTF-8");
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert PLAINTEXT.equals(recovered);
- }
-
- @Test
- public void testGetCipherShouldHandleUnformedSalt() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PASSWORD = "thisIsABadPassword";
-
- final def INVALID_SALTS = ['$ab$00$acbdefghijklmnopqrstuv', 'bad_salt', '$3a$11$', 'x', '$2a$10$']
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}");
-
- // Act
- INVALID_SALTS.each { String salt ->
- logger.info("Checking salt ${salt}")
-
- def msg = shouldFail(IllegalArgumentException) {
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true);
- }
-
- // Assert
- assert msg =~ "The salt must be of the format \\\$2a\\\$10\\\$gUVbkVzp79H8YaCOsCVZNu\\. To generate a salt, use BcryptCipherProvider#generateSalt"
- }
- }
-
- String bytesToBitString(byte[] bytes) {
- bytes.collect {
- String.format("%8s", Integer.toBinaryString(it & 0xFF)).replace(' ', '0')
- }.join("")
- }
-
- String spaceString(String input, int blockSize = 4) {
- input.collect { it.padLeft(blockSize, " ") }.join("")
- }
-
- @Test
- public void testGetCipherShouldRejectEmptySalt() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PASSWORD = "thisIsABadPassword";
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}");
-
- // Two different errors -- one explaining the no-salt method is not supported, and the other for an empty byte[] passed
-
- // Act
- def msg = shouldFail(IllegalArgumentException) {
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, new byte[0], DEFAULT_KEY_LENGTH, true);
- }
- logger.expected(msg)
-
- // Assert
- assert msg =~ "The salt cannot be empty\\. To generate a salt, use BcryptCipherProvider#generateSalt"
- }
-
- @Test
- public void testGetCipherForDecryptShouldRequireIV() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4)
-
- final String PASSWORD = "shortPassword";
- final byte[] SALT = cipherProvider.generateSalt()
- final byte[] IV = Hex.decodeHex("00" * 16 as char[]);
-
- final String plaintext = "This is a plaintext message.";
-
- // Act
- for (EncryptionMethod em : strongKDFEncryptionMethods) {
- logger.info("Using algorithm: ${em.getAlgorithm()}");
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true);
- logger.info("IV: ${Hex.encodeHexString(IV)}")
-
- byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8"));
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- def msg = shouldFail(IllegalArgumentException) {
- cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false);
- }
-
- // Assert
- assert msg =~ "Cannot decrypt without a valid IV"
- }
- }
-
- @Test
- public void testGetCipherShouldAcceptValidKeyLengths() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4);
-
- final String PASSWORD = "shortPassword";
- final byte[] SALT = cipherProvider.generateSalt()
- final byte[] IV = Hex.decodeHex("01" * 16 as char[]);
-
- final String PLAINTEXT = "This is a plaintext message.";
-
- // Currently only AES ciphers are compatible with Bcrypt, so redundant to test all algorithms
- final def VALID_KEY_LENGTHS = AES_KEY_LENGTHS
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
-
- // Act
- VALID_KEY_LENGTHS.each { int keyLength ->
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()} with key length ${keyLength}")
-
- // Initialize a cipher for encryption
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true);
- logger.info("IV: ${Hex.encodeHexString(IV)}")
-
- byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8"));
- logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}");
-
- cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false);
- byte[] recoveredBytes = cipher.doFinal(cipherBytes);
- String recovered = new String(recoveredBytes, "UTF-8");
- logger.info("Recovered: ${recovered}")
-
- // Assert
- assert PLAINTEXT.equals(recovered);
- }
- }
-
- @Test
- public void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4);
-
- final String PASSWORD = "shortPassword";
- final byte[] SALT = cipherProvider.generateSalt()
- final byte[] IV = Hex.decodeHex("00" * 16 as char[]);
-
- final String PLAINTEXT = "This is a plaintext message.";
-
- // Currently only AES ciphers are compatible with Bcrypt, so redundant to test all algorithms
- final def INVALID_KEY_LENGTHS = [-1, 40, 64, 112, 512]
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
-
- // Act
- INVALID_KEY_LENGTHS.each { int keyLength ->
- logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()} with key length ${keyLength}")
-
- // Initialize a cipher for encryption
- def msg = shouldFail(IllegalArgumentException) {
- Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true);
- }
-
- // Assert
- assert msg =~ "${keyLength} is not a valid key length for AES"
- }
- }
-
- @Test
- public void testGenerateSaltShouldUseProvidedWorkFactor() throws Exception {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(11);
- int workFactor = cipherProvider.getWorkFactor()
-
- // Act
- final byte[] saltBytes = cipherProvider.generateSalt()
- String salt = new String(saltBytes)
- logger.info("Salt: ${salt}")
-
- // Assert
- assert salt =~ /^\$2[axy]\$\d{2}\$/
- assert salt.contains("\$${workFactor}\$")
- }
-
- @Ignore("This test can be run on a specific machine to evaluate if the default work factor is sufficient")
- @Test
- public void testDefaultConstructorShouldProvideStrongWorkFactor() {
- // Arrange
- RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider();
-
- // Values taken from http://wildlyinaccurate.com/bcrypt-choosing-a-work-factor/ and http://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt
-
- // Calculate the work factor to reach 500 ms
- int minimumWorkFactor = calculateMinimumWorkFactor()
- logger.info("Determined minimum safe work factor to be ${minimumWorkFactor}")
-
- // Act
- int workFactor = cipherProvider.getWorkFactor()
- logger.info("Default work factor ${workFactor}")
-
- // Assert
- assertTrue("The default work factor for BcryptCipherProvider is too weak. Please update the default value to a stronger level.", workFactor >= minimumWorkFactor)
- }
-
- /**
- * Returns the work factor required for a derivation to exceed 500 ms on this machine. Code adapted from http://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt
- *
- * @return the minimum bcrypt work factor
- */
- private static int calculateMinimumWorkFactor() {
- // High start-up cost, so run multiple times for better benchmarking
- final int RUNS = 10
-
- // Benchmark using a work factor of 5 (the second-lowest allowed)
- int workFactor = 5
-
- String salt = BCrypt.gensalt(workFactor)
-
- // Run once to prime the system
- double duration = time {
- BCrypt.hashpw(MICROBENCHMARK, salt)
- }
- logger.info("First run of work factor ${workFactor} took ${duration} ms (ignored)")
-
- def durations = []
-
- RUNS.times { int i ->
- duration = time {
- BCrypt.hashpw(MICROBENCHMARK, salt)
- }
- logger.info("Work factor ${workFactor} took ${duration} ms")
- durations << duration
- }
-
- duration = durations.sum() / durations.size()
- logger.info("Work factor ${workFactor} averaged ${duration} ms")
-
- // Increasing the work factor by 1 would double the run time
- // Keep increasing N until the estimated duration is over 500 ms
- while (duration < 500) {
- workFactor += 1
- duration *= 2
- }
-
- logger.info("Returning work factor ${workFactor} for ${duration} ms")
-
- return workFactor
- }
-
- private static double time(Closure c) {
- long start = System.nanoTime()
- c.call()
- long end = System.nanoTime()
- return (end - start) / 1_000_000.0
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactoryGroovyTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactoryGroovyTest.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactoryGroovyTest.groovy
deleted file mode 100644
index be8d5f4..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactoryGroovyTest.groovy
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.KeyDerivationFunction
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class CipherProviderFactoryGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(CipherProviderFactoryGroovyTest.class)
-
- private static final Map EXPECTED_CIPHER_PROVIDERS = [
- (KeyDerivationFunction.BCRYPT) : BcryptCipherProvider.class,
- (KeyDerivationFunction.NIFI_LEGACY) : NiFiLegacyCipherProvider.class,
- (KeyDerivationFunction.NONE) : AESKeyedCipherProvider.class,
- (KeyDerivationFunction.OPENSSL_EVP_BYTES_TO_KEY): OpenSSLPKCS5CipherProvider.class,
- (KeyDerivationFunction.PBKDF2) : PBKDF2CipherProvider.class,
- (KeyDerivationFunction.SCRYPT) : ScryptCipherProvider.class
- ]
-
- @BeforeClass
- public static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
- @Test
- public void testGetCipherProviderShouldResolveRegisteredKDFs() {
- // Arrange
-
- // Act
- KeyDerivationFunction.values().each { KeyDerivationFunction kdf ->
- logger.info("Expected: ${kdf.name} -> ${EXPECTED_CIPHER_PROVIDERS.get(kdf).simpleName}")
- CipherProvider cp = CipherProviderFactory.getCipherProvider(kdf)
- logger.info("Resolved: ${kdf.name} -> ${cp.class.simpleName}")
-
- // Assert
- assert cp.class == (EXPECTED_CIPHER_PROVIDERS.get(kdf))
- }
- }
-
- @Ignore("Cannot mock enum using Groovy map coercion")
- @Test
- public void testGetCipherProviderShouldHandleUnregisteredKDFs() {
- // Arrange
-
- // Can't mock this; see http://stackoverflow.com/questions/5323505/mocking-java-enum-to-add-a-value-to-test-fail-case
- KeyDerivationFunction invalidKDF = [name: "Unregistered", description: "Not a registered KDF"] as KeyDerivationFunction
- logger.info("Expected: ${invalidKDF.name} -> error")
-
- // Act
- def msg = shouldFail(IllegalArgumentException) {
- CipherProvider cp = CipherProviderFactory.getCipherProvider(invalidKDF)
- logger.info("Resolved: ${invalidKDF.name} -> ${cp.class.simpleName}")
- }
- logger.expected(msg)
-
- // Assert
- assert msg =~ "No cipher provider registered for ${invalidKDF.name}"
- }
-}
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherUtilityGroovyTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherUtilityGroovyTest.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherUtilityGroovyTest.groovy
deleted file mode 100644
index 6a6a958..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/CipherUtilityGroovyTest.groovy
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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 org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class CipherUtilityGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(CipherUtilityGroovyTest.class)
-
- // TripleDES must precede DES for automatic grouping precedence
- private static final List CIPHERS = ["AES", "TRIPLEDES", "DES", "RC2", "RC4", "RC5", "TWOFISH"]
- private static final List SYMMETRIC_ALGORITHMS = EncryptionMethod.values().findAll { it.algorithm.startsWith("PBE") || it.algorithm.startsWith("AES") }*.algorithm
- private static final Map> ALGORITHMS_MAPPED_BY_CIPHER = SYMMETRIC_ALGORITHMS.groupBy { String algorithm -> CIPHERS.find { algorithm.contains(it) } }
-
- // Manually mapped as of 01/19/16 0.5.0
- private static final Map> ALGORITHMS_MAPPED_BY_KEY_LENGTH = [
- (40) : ["PBEWITHSHAAND40BITRC2-CBC",
- "PBEWITHSHAAND40BITRC4"],
- (64) : ["PBEWITHMD5ANDDES",
- "PBEWITHSHA1ANDDES"],
- (112): ["PBEWITHSHAAND2-KEYTRIPLEDES-CBC",
- "PBEWITHSHAAND3-KEYTRIPLEDES-CBC"],
- (128): ["PBEWITHMD5AND128BITAES-CBC-OPENSSL",
- "PBEWITHMD5ANDRC2",
- "PBEWITHSHA1ANDRC2",
- "PBEWITHSHA256AND128BITAES-CBC-BC",
- "PBEWITHSHAAND128BITAES-CBC-BC",
- "PBEWITHSHAAND128BITRC2-CBC",
- "PBEWITHSHAAND128BITRC4",
- "PBEWITHSHAANDTWOFISH-CBC",
- "AES/CBC/PKCS7Padding",
- "AES/CTR/NoPadding",
- "AES/GCM/NoPadding"],
- (192): ["PBEWITHMD5AND192BITAES-CBC-OPENSSL",
- "PBEWITHSHA256AND192BITAES-CBC-BC",
- "PBEWITHSHAAND192BITAES-CBC-BC",
- "AES/CBC/PKCS7Padding",
- "AES/CTR/NoPadding",
- "AES/GCM/NoPadding"],
- (256): ["PBEWITHMD5AND256BITAES-CBC-OPENSSL",
- "PBEWITHSHA256AND256BITAES-CBC-BC",
- "PBEWITHSHAAND256BITAES-CBC-BC",
- "AES/CBC/PKCS7Padding",
- "AES/CTR/NoPadding",
- "AES/GCM/NoPadding"]
- ]
-
- @BeforeClass
- static void setUpOnce() {
- Security.addProvider(new BouncyCastleProvider());
-
- // Fix because TRIPLEDES -> DESede
- def tripleDESAlgorithms = ALGORITHMS_MAPPED_BY_CIPHER.remove("TRIPLEDES")
- ALGORITHMS_MAPPED_BY_CIPHER.put("DESede", tripleDESAlgorithms)
-
- logger.info("Mapped algorithms: ${ALGORITHMS_MAPPED_BY_CIPHER}")
- }
-
- @Before
- void setUp() throws Exception {
-
- }
-
- @After
- void tearDown() throws Exception {
-
- }
-
- @Test
- void testShouldParseCipherFromAlgorithm() {
- // Arrange
- final def EXPECTED_ALGORITHMS = ALGORITHMS_MAPPED_BY_CIPHER
-
- // Act
- SYMMETRIC_ALGORITHMS.each { String algorithm ->
- String cipher = CipherUtility.parseCipherFromAlgorithm(algorithm)
- logger.info("Extracted ${cipher} from ${algorithm}")
-
- // Assert
- assert EXPECTED_ALGORITHMS.get(cipher).contains(algorithm)
- }
- }
-
- @Test
- void testShouldParseKeyLengthFromAlgorithm() {
- // Arrange
- final def EXPECTED_ALGORITHMS = ALGORITHMS_MAPPED_BY_KEY_LENGTH
-
- // Act
- SYMMETRIC_ALGORITHMS.each { String algorithm ->
- int keyLength = CipherUtility.parseKeyLengthFromAlgorithm(algorithm)
- logger.info("Extracted ${keyLength} from ${algorithm}")
-
- // Assert
- assert EXPECTED_ALGORITHMS.get(keyLength).contains(algorithm)
- }
- }
-
- @Test
- void testShouldDetermineValidKeyLength() {
- // Arrange
-
- // Act
- ALGORITHMS_MAPPED_BY_KEY_LENGTH.each { int keyLength, List algorithms ->
- algorithms.each { String algorithm ->
- logger.info("Checking ${keyLength} for ${algorithm}")
-
- // Assert
- assert CipherUtility.isValidKeyLength(keyLength, CipherUtility.parseCipherFromAlgorithm(algorithm))
- }
- }
- }
-
- @Test
- void testShouldDetermineInvalidKeyLength() {
- // Arrange
-
- // Act
- ALGORITHMS_MAPPED_BY_KEY_LENGTH.each { int keyLength, List algorithms ->
- algorithms.each { String algorithm ->
- def invalidKeyLengths = [-1, 0, 1]
- if (algorithm =~ "RC\\d") {
- invalidKeyLengths += [39, 2049]
- } else {
- invalidKeyLengths += keyLength + 1
- }
- logger.info("Checking ${invalidKeyLengths.join(", ")} for ${algorithm}")
-
- // Assert
- invalidKeyLengths.each { int invalidKeyLength ->
- assert !CipherUtility.isValidKeyLength(invalidKeyLength, CipherUtility.parseCipherFromAlgorithm(algorithm))
- }
- }
- }
- }
-
- @Test
- void testShouldDetermineValidKeyLengthForAlgorithm() {
- // Arrange
-
- // Act
- ALGORITHMS_MAPPED_BY_KEY_LENGTH.each { int keyLength, List algorithms ->
- algorithms.each { String algorithm ->
- logger.info("Checking ${keyLength} for ${algorithm}")
-
- // Assert
- assert CipherUtility.isValidKeyLengthForAlgorithm(keyLength, algorithm)
- }
- }
- }
-
- @Test
- void testShouldDetermineInvalidKeyLengthForAlgorithm() {
- // Arrange
-
- // Act
- ALGORITHMS_MAPPED_BY_KEY_LENGTH.each { int keyLength, List algorithms ->
- algorithms.each { String algorithm ->
- def invalidKeyLengths = [-1, 0, 1]
- if (algorithm =~ "RC\\d") {
- invalidKeyLengths += [39, 2049]
- } else {
- invalidKeyLengths += keyLength + 1
- }
- logger.info("Checking ${invalidKeyLengths.join(", ")} for ${algorithm}")
-
- // Assert
- invalidKeyLengths.each { int invalidKeyLength ->
- assert !CipherUtility.isValidKeyLengthForAlgorithm(invalidKeyLength, algorithm)
- }
- }
- }
-
- // Extra hard-coded checks
- String algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC"
- int invalidKeyLength = 192
- logger.info("Checking ${invalidKeyLength} for ${algorithm}")
- assert !CipherUtility.isValidKeyLengthForAlgorithm(invalidKeyLength, algorithm)
- }
-
- @Test
- void testShouldGetValidKeyLengthsForAlgorithm() {
- // Arrange
-
- def rcKeyLengths = (40..2048).asList()
- def CIPHER_KEY_SIZES = [
- AES : [128, 192, 256],
- DES : [56, 64],
- DESede : [56, 64, 112, 128, 168, 192],
- RC2 : rcKeyLengths,
- RC4 : rcKeyLengths,
- RC5 : rcKeyLengths,
- TWOFISH: [128, 192, 256]
- ]
-
- def SINGLE_KEY_SIZE_ALGORITHMS = EncryptionMethod.values()*.algorithm.findAll { CipherUtility.parseActualKeyLengthFromAlgorithm(it) != -1 }
- logger.info("Single key size algorithms: ${SINGLE_KEY_SIZE_ALGORITHMS}")
- def MULTIPLE_KEY_SIZE_ALGORITHMS = EncryptionMethod.values()*.algorithm - SINGLE_KEY_SIZE_ALGORITHMS
- MULTIPLE_KEY_SIZE_ALGORITHMS.removeAll { it.contains("PGP") }
- logger.info("Multiple key size algorithms: ${MULTIPLE_KEY_SIZE_ALGORITHMS}")
-
- // Act
- SINGLE_KEY_SIZE_ALGORITHMS.each { String algorithm ->
- def EXPECTED_KEY_SIZES = [CipherUtility.parseKeyLengthFromAlgorithm(algorithm)]
-
- def validKeySizes = CipherUtility.getValidKeyLengthsForAlgorithm(algorithm)
- logger.info("Checking ${algorithm} ${validKeySizes} against expected ${EXPECTED_KEY_SIZES}")
-
- // Assert
- assert validKeySizes == EXPECTED_KEY_SIZES
- }
-
- // Act
- MULTIPLE_KEY_SIZE_ALGORITHMS.each { String algorithm ->
- String cipher = CipherUtility.parseCipherFromAlgorithm(algorithm)
- def EXPECTED_KEY_SIZES = CIPHER_KEY_SIZES[cipher]
-
- def validKeySizes = CipherUtility.getValidKeyLengthsForAlgorithm(algorithm)
- logger.info("Checking ${algorithm} ${validKeySizes} against expected ${EXPECTED_KEY_SIZES}")
-
- // Assert
- assert validKeySizes == EXPECTED_KEY_SIZES
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d242076/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptorGroovyTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptorGroovyTest.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptorGroovyTest.groovy
deleted file mode 100644
index 8e78778..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptorGroovyTest.groovy
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.binary.Hex
-import org.apache.nifi.processor.io.StreamCallback
-import org.apache.nifi.security.util.EncryptionMethod
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Test
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.SecretKey
-import javax.crypto.spec.SecretKeySpec
-import java.security.Security
-
-public class KeyedEncryptorGroovyTest {
- private static final Logger logger = LoggerFactory.getLogger(KeyedEncryptorGroovyTest.class)
-
- private static final String TEST_RESOURCES_PREFIX = "src/test/resources/TestEncryptContent/"
- private static final File plainFile = new File("${TEST_RESOURCES_PREFIX}/plain.txt")
- private static final File encryptedFile = new File("${TEST_RESOURCES_PREFIX}/unsalted_128_raw.asc")
-
- private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210"
- private static final SecretKey KEY = new SecretKeySpec(Hex.decodeHex(KEY_HEX as char[]), "AES")
-
- @BeforeClass
- public static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
- @Test
- public void testShouldEncryptAndDecrypt() throws Exception {
- // Arrange
- final String PLAINTEXT = "This is a plaintext message."
- logger.info("Plaintext: {}", PLAINTEXT)
- InputStream plainStream = new ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
-
- OutputStream cipherStream = new ByteArrayOutputStream()
- OutputStream recoveredStream = new ByteArrayOutputStream()
-
- EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- logger.info("Using ${encryptionMethod.name()}")
-
- // Act
- KeyedEncryptor encryptor = new KeyedEncryptor(encryptionMethod, KEY)
-
- StreamCallback encryptionCallback = encryptor.getEncryptionCallback()
- StreamCallback decryptionCallback = encryptor.getDecryptionCallback()
-
- encryptionCallback.process(plainStream, cipherStream)
-
- final byte[] cipherBytes = ((ByteArrayOutputStream) cipherStream).toByteArray()
- logger.info("Encrypted: {}", Hex.encodeHexString(cipherBytes))
- InputStream cipherInputStream = new ByteArrayInputStream(cipherBytes)
- decryptionCallback.process(cipherInputStream, recoveredStream)
-
- // Assert
- byte[] recoveredBytes = ((ByteArrayOutputStream) recoveredStream).toByteArray()
- String recovered = new String(recoveredBytes, "UTF-8")
- logger.info("Recovered: {}\n\n", recovered)
- assert PLAINTEXT.equals(recovered)
- }
-
- @Test
- public void testShouldDecryptOpenSSLUnsaltedCipherTextWithKnownIV() throws Exception {
- // Arrange
- final String PLAINTEXT = new File("${TEST_RESOURCES_PREFIX}/plain.txt").text
- logger.info("Plaintext: {}", PLAINTEXT)
- byte[] cipherBytes = new File("${TEST_RESOURCES_PREFIX}/unsalted_128_raw.enc").bytes
-
- final String keyHex = "711E85689CE7AFF6F410AEA43ABC5446"
- final String ivHex = "842F685B84879B2E00F977C22B9E9A7D"
-
- InputStream cipherStream = new ByteArrayInputStream(cipherBytes)
- OutputStream recoveredStream = new ByteArrayOutputStream()
-
- final EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
- KeyedEncryptor encryptor = new KeyedEncryptor(encryptionMethod, new SecretKeySpec(Hex.decodeHex(keyHex as char[]), "AES"), Hex.decodeHex(ivHex as char[]))
-
- StreamCallback decryptionCallback = encryptor.getDecryptionCallback()
- logger.info("Cipher bytes: ${Hex.encodeHexString(cipherBytes)}")
-
- // Act
- decryptionCallback.process(cipherStream, recoveredStream)
-
- // Assert
- byte[] recoveredBytes = ((ByteArrayOutputStream) recoveredStream).toByteArray()
- String recovered = new String(recoveredBytes, "UTF-8")
- logger.info("Recovered: {}", recovered)
- assert PLAINTEXT.equals(recovered)
- }
-}
\ No newline at end of file