public static int getMaximumPasswordLengthForAlgorithmOnLimitedStrengthCrypto(EncryptionMethod encryptionMethod) { if (encryptionMethod == null) { throw new IllegalArgumentException("Cannot evaluate an empty encryption method algorithm"); } return MAX_PASSWORD_LENGTH_BY_ALGORITHM.getOrDefault(encryptionMethod.getAlgorithm(), -1); }
private int calculateSaltLength(EncryptionMethod encryptionMethod) { try { Cipher cipher = Cipher.getInstance(encryptionMethod.getAlgorithm(), encryptionMethod.getProvider()); return cipher.getBlockSize() > 0 ? cipher.getBlockSize() : getDefaultSaltLength(); } catch (Exception e) { logger.warn("Encountered exception determining salt length from encryption method {}", encryptionMethod.getAlgorithm(), e); final int defaultSaltLength = getDefaultSaltLength(); logger.warn("Returning default length: {} bytes", defaultSaltLength); return defaultSaltLength; } }
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"); } validateSalt(encryptionMethod, salt); 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; }
public KeyedEncryptor(final EncryptionMethod encryptionMethod, final byte[] keyBytes, final byte[] iv) { super(); try { if (encryptionMethod == null) { throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with null encryption method"); } if (!encryptionMethod.isKeyedCipher()) { throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with encryption method " + encryptionMethod.name()); } this.encryptionMethod = encryptionMethod; if (keyBytes == null || keyBytes.length == 0) { throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with empty key"); } if (!CipherUtility.isValidKeyLengthForAlgorithm(keyBytes.length * 8, encryptionMethod.getAlgorithm())) { throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with key of length " + keyBytes.length); } String cipherName = CipherUtility.parseCipherFromAlgorithm(encryptionMethod.getAlgorithm()); this.key = new SecretKeySpec(keyBytes, cipherName); this.iv = iv; } catch (Exception e) { throw new ProcessException(e); } }
String algorithm = encryptionMethod.getAlgorithm(); String provider = encryptionMethod.getProvider();
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 Bcrypt"); } if (StringUtils.isEmpty(password)) { throw new IllegalArgumentException("Encryption with an empty password is not supported"); } String algorithm = encryptionMethod.getAlgorithm(); String provider = encryptionMethod.getProvider(); 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 bcryptSalt = formatSaltForBcrypt(salt); String hash = BCrypt.hashpw(password, bcryptSalt); /* The SHA-512 hash is required in order to derive a key longer than 184 bits (the resulting size of the Bcrypt hash) and ensuring the avalanche effect causes higher key entropy (if all derived keys follow a consistent pattern, it weakens the strength of the encryption) */ MessageDigest digest = MessageDigest.getInstance("SHA-512", provider); byte[] dk = digest.digest(hash.getBytes(StandardCharsets.UTF_8)); dk = Arrays.copyOf(dk, keyLength / 8); SecretKey tempKey = new SecretKeySpec(dk, algorithm); KeyedCipherProvider keyedCipherProvider = new AESKeyedCipherProvider(); return keyedCipherProvider.getCipher(encryptionMethod, tempKey, iv, encryptMode); }
String algorithm = encryptionMethod.getAlgorithm();
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); }
@Test public void testShouldDecryptExternalFile() throws Exception { // Arrange byte[] plainBytes = Files.readAllBytes(Paths.get(plainFile.getPath())); final String PLAINTEXT = new String(plainBytes, "UTF-8"); InputStream cipherStream = new FileInputStream(encryptedFile); OutputStream recoveredStream = new ByteArrayOutputStream(); // No file, just streams String filename = encryptedFile.getName(); OpenPGPPasswordBasedEncryptor encryptor = new OpenPGPPasswordBasedEncryptor(EncryptionMethod.PGP.getAlgorithm(), EncryptionMethod.PGP.getProvider(), LEGACY_PASSWORD.toCharArray(), filename); StreamCallback decryptionCallback = encryptor.getDecryptionCallback(); // Act decryptionCallback.process(cipherStream, recoveredStream); // Assert byte[] recoveredBytes = ((ByteArrayOutputStream) recoveredStream).toByteArray(); String recovered = new String(recoveredBytes, "UTF-8"); logger.info("Recovered: {}", recovered); Assert.assertEquals("Recovered text", PLAINTEXT, recovered); } }
@Test public void testShouldDecryptExternalFile() throws Exception { // Arrange byte[] plainBytes = Files.readAllBytes(Paths.get(plainFile.getPath())); final String PLAINTEXT = new String(plainBytes, "UTF-8"); InputStream cipherStream = new FileInputStream(unsignedFile); OutputStream recoveredStream = new ByteArrayOutputStream(); // No file, just streams String filename = unsignedFile.getName(); OpenPGPKeyBasedEncryptor encryptor = new OpenPGPKeyBasedEncryptor( EncryptionMethod.PGP.getAlgorithm(), EncryptionMethod.PGP.getProvider(), SECRET_KEYRING_PATH, USER_ID, PASSWORD.toCharArray(), filename); StreamCallback decryptionCallback = encryptor.getDecryptionCallback(); // Act decryptionCallback.process(cipherStream, recoveredStream); // Assert byte[] recoveredBytes = ((ByteArrayOutputStream) recoveredStream).toByteArray(); String recovered = new String(recoveredBytes, "UTF-8"); logger.info("Recovered: {}", recovered); Assert.assertEquals("Recovered text", PLAINTEXT, recovered); } }
@Test public void testShouldDetermineMaxKeySizeForAlgorithms() throws IOException { // Arrange final String AES_ALGORITHM = EncryptionMethod.MD5_256AES.getAlgorithm(); final String DES_ALGORITHM = EncryptionMethod.MD5_DES.getAlgorithm(); final int AES_MAX_LENGTH = PasswordBasedEncryptor.supportsUnlimitedStrength() ? Integer.MAX_VALUE : 128; final int DES_MAX_LENGTH = PasswordBasedEncryptor.supportsUnlimitedStrength() ? Integer.MAX_VALUE : 64; // Act int determinedAESMaxLength = PasswordBasedEncryptor.getMaxAllowedKeyLength(AES_ALGORITHM); int determinedTDESMaxLength = PasswordBasedEncryptor.getMaxAllowedKeyLength(DES_ALGORITHM); // Assert assert determinedAESMaxLength == AES_MAX_LENGTH; assert determinedTDESMaxLength == DES_MAX_LENGTH; }
@Test public void testShouldEncryptAndDecrypt() throws Exception { // Arrange final String PLAINTEXT = "This is a plaintext message."; logger.info("Plaintext: {}", PLAINTEXT); InputStream plainStream = new java.io.ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8")); OutputStream cipherStream = new ByteArrayOutputStream(); OutputStream recoveredStream = new ByteArrayOutputStream(); // No file, just streams String filename = "tempFile.txt"; OpenPGPPasswordBasedEncryptor encryptor = new OpenPGPPasswordBasedEncryptor(EncryptionMethod.PGP.getAlgorithm(), EncryptionMethod.PGP.getProvider(), PASSWORD.toCharArray(), filename); StreamCallback encryptionCallback = encryptor.getEncryptionCallback(); StreamCallback decryptionCallback = encryptor.getDecryptionCallback(); // Act 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: {}", recovered); assert PLAINTEXT.equals(recovered); }
@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(); // No file, just streams String filename = "tempFile.txt"; // Encryptor does not require password OpenPGPKeyBasedEncryptor encryptor = new OpenPGPKeyBasedEncryptor( EncryptionMethod.PGP.getAlgorithm(), EncryptionMethod.PGP.getProvider(), PUBLIC_KEYRING_PATH, USER_ID, new char[0], filename); StreamCallback encryptionCallback = encryptor.getEncryptionCallback(); OpenPGPKeyBasedEncryptor decryptor = new OpenPGPKeyBasedEncryptor( EncryptionMethod.PGP.getAlgorithm(), EncryptionMethod.PGP.getProvider(), SECRET_KEYRING_PATH, USER_ID, PASSWORD.toCharArray(), filename); StreamCallback decryptionCallback = decryptor.getDecryptionCallback(); // Act 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: {}", recovered); assert PLAINTEXT.equals(recovered); }
if (encryptionMethod.isUnlimitedStrength()) { validationResults.add(new ValidationResult.Builder().subject(ENCRYPTION_ALGORITHM.getName()) .explanation(encryptionMethod.name() + " (" + encryptionMethod.getAlgorithm() + ") is not supported by this JVM due to lacking JCE Unlimited " + "Strength Jurisdiction Policy files. See Admin Guide.").build()); .explanation(RAW_KEY_HEX.getDisplayName() + " is required when using algorithm " + encryptionMethod.getAlgorithm() + ". See Admin Guide.").build()); } else { byte[] keyBytes = new byte[0]; " due to lacking JCE Unlimited Strength Jurisdiction Policy files. See Admin Guide.").build()); if (!CipherUtility.isValidKeyLengthForAlgorithm(keyBytes.length * 8, encryptionMethod.getAlgorithm())) { List<Integer> validKeyLengths = CipherUtility.getValidKeyLengthsForAlgorithm(encryptionMethod.getAlgorithm()); validationResults.add(new ValidationResult.Builder().subject(RAW_KEY_HEX.getName()) .explanation("Key must be valid length [" + StringUtils.join(validKeyLengths, ", ") + "]. See Admin Guide.").build()); validationResults.add(new ValidationResult.Builder().subject(KEY_DERIVATION_FUNCTION.getName()) .explanation(KEY_DERIVATION_FUNCTION.getDisplayName() + " is required to be " + StringUtils.join(kdfsForKeyedCipher, ", ") + " when using algorithm " + encryptionMethod.getAlgorithm()).build());
.explanation(PASSWORD.getDisplayName() + " is required when using algorithm " + encryptionMethod.getAlgorithm()).build()); return validationResults; .explanation(encryptionMethod.name() + " (" + encryptionMethod.getAlgorithm() + ") is not supported by this JVM due to lacking JCE Unlimited " + "Strength Jurisdiction Policy files. See Admin Guide.").build()); validationResults.add(new ValidationResult.Builder().subject(displayName) .explanation(displayName + " is required to be " + StringUtils.join(kdfsForPBECipher, ", ") + " when using algorithm " + encryptionMethod.getAlgorithm() + ". See Admin Guide.").build());
int keyLength = CipherUtility.parseKeyLengthFromAlgorithm(encryptionMethod.getAlgorithm());
@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; // NiFi legacy code determined the salt length based on the cipher block size if (cipherProvider instanceof org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider) { salt = ((org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider) cipherProvider).generateSalt(encryptionMethod); } else { 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); } } }
@Override protected Collection<ValidationResult> customValidate(final ValidationContext context) { final List<ValidationResult> validationResults = new ArrayList<>(super.customValidate(context)); final String methodValue = context.getProperty(ENCRYPTION_ALGORITHM).getValue(); final EncryptionMethod encryptionMethod = EncryptionMethod.valueOf(methodValue); final String algorithm = encryptionMethod.getAlgorithm(); final String password = context.getProperty(PASSWORD).getValue(); final KeyDerivationFunction kdf = KeyDerivationFunction.valueOf(context.getProperty(KEY_DERIVATION_FUNCTION).getValue()); final String keyHex = context.getProperty(RAW_KEY_HEX).getValue(); if (isPGPAlgorithm(algorithm)) { final boolean encrypt = context.getProperty(MODE).getValue().equalsIgnoreCase(ENCRYPT_MODE); final String publicKeyring = context.getProperty(PUBLIC_KEYRING).getValue(); final String publicUserId = context.getProperty(PUBLIC_KEY_USERID).getValue(); final String privateKeyring = context.getProperty(PRIVATE_KEYRING).getValue(); final String privateKeyringPassphrase = context.getProperty(PRIVATE_KEYRING_PASSPHRASE).evaluateAttributeExpressions().getValue(); validationResults.addAll(validatePGP(encryptionMethod, password, encrypt, publicKeyring, publicUserId, privateKeyring, privateKeyringPassphrase)); } else { // Not PGP if (encryptionMethod.isKeyedCipher()) { // Raw key validationResults.addAll(validateKeyed(encryptionMethod, kdf, keyHex)); } else { // PBE boolean allowWeakCrypto = context.getProperty(ALLOW_WEAK_CRYPTO).getValue().equalsIgnoreCase(WEAK_CRYPTO_ALLOWED_NAME); validationResults.addAll(validatePBE(encryptionMethod, kdf, password, allowWeakCrypto)); } } return validationResults; }
.explanation(encryptionMethod.getAlgorithm() + " encryption without a " + PASSWORD.getDisplayName() + " requires both " + PUBLIC_KEYRING.getDisplayName() + " and " + PUBLIC_KEY_USERID.getDisplayName()) .build()); .explanation(encryptionMethod.getAlgorithm() + " decryption without a " + PASSWORD.getDisplayName() + " requires both " + PRIVATE_KEYRING.getDisplayName() + " and " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName()) .build());
final EncryptionMethod encryptionMethod = EncryptionMethod.valueOf(method); final String providerName = encryptionMethod.getProvider(); final String algorithm = encryptionMethod.getAlgorithm(); final String password = context.getProperty(PASSWORD).getValue(); final KeyDerivationFunction kdf = KeyDerivationFunction.valueOf(context.getProperty(KEY_DERIVATION_FUNCTION).getValue());