NIFI-11022 Added DecryptContent Compatibility Processors

- Added nifi-cipher-bundle with nifi-cipher-nar for new Processors
- Added DecryptContentCompatibilityMode Processor supporting PKCS5 and PKCS12 Password-Based Encryption Schemes
- Added DecryptContentEncoded Processor supporting NiFi Key Derivation Functions and associated formatting
- Added nifi-security-crypt-key module with Key Derivation Functions and Parameter Readers
- Added Additional Details documentation for Processors

This closes #6821
Signed-off-by: Paul Grey <greyp@apache.org>
This commit is contained in:
exceptionfactory 2022-12-27 11:01:58 -06:00 committed by Paul Grey
parent d9f35b8974
commit 0c676b9633
No known key found for this signature in database
GPG Key ID: 8DDF32B9C7EE39D0
58 changed files with 4605 additions and 0 deletions

View File

@ -235,6 +235,12 @@ language governing permissions and limitations under the License. -->
<version>1.20.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-cipher-nar</artifactId>
<version>1.20.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-distributed-cache-services-nar</artifactId>

View File

@ -0,0 +1,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--
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.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-commons</artifactId>
<version>1.20.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-security-crypto-key</artifactId>
<description>Cryptographic key derivation function components with minimal dependencies</description>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,31 @@
/*
* 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.crypto.key;
import java.security.Key;
/**
* Derived Key extension of standard Key supporting serialized representation
*/
public interface DerivedKey extends Key {
/**
* Get serialized representation of derived key including parameters when applicable to a particular function algorithm
*
* @return Serialized representation of derived key
*/
String getSerialized();
}

View File

@ -0,0 +1,29 @@
/*
* 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.crypto.key;
/**
* Derived Key Parameter Specification with properties for implementing algorithms
*/
public interface DerivedKeyParameterSpec {
/**
* Get cryptographic salt that algorithms use for key derivation
*
* @return Cryptographic salt bytes
*/
byte[] getSalt();
}

View File

@ -0,0 +1,30 @@
/*
* 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.crypto.key;
/**
* Abstraction for reading Derived Key Parameter Specifications from serialized parameters
*/
public interface DerivedKeyParameterSpecReader<T extends DerivedKeyParameterSpec> {
/**
* Read serialized parameters and return Derived Key Parameter Specification
*
* @param serializedParameters Serialized parameters
* @return Derived Key Parameter Specification
*/
T read(byte[] serializedParameters);
}

View File

@ -0,0 +1,30 @@
/*
* 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.crypto.key;
/**
* Provider abstraction for Key Derivation Function implementations
*/
public interface DerivedKeyProvider<T extends DerivedKeyParameterSpec> {
/**
* Get Derived Key using configured properties
*
* @param derivedKeySpec Derived Key Specification
* @return Derived Key
*/
DerivedKey getDerivedKey(DerivedKeySpec<T> derivedKeySpec);
}

View File

@ -0,0 +1,50 @@
/*
* 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.crypto.key;
/**
* Derived Key Specification with password and parameters for implementing algorithms
*/
public interface DerivedKeySpec<T extends DerivedKeyParameterSpec> {
/**
* Get password characters that algorithms use for key derivation
*
* @return Password characters
*/
char[] getPassword();
/**
* Get length requested for derived key in bytes
*
* @return Length of derived key in bytes
*/
int getDerivedKeyLength();
/**
* Get cipher algorithm for which the derived key will be used
*
* @return Cipher algorithm
*/
String getAlgorithm();
/**
* Get parameter specification
*
* @return Parameter specification
*/
T getParameterSpec();
}

View File

@ -0,0 +1,48 @@
/*
* 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.crypto.key;
import javax.crypto.spec.SecretKeySpec;
/**
* Derived Secret Key containing encoded key bytes and serialized representation of key with parameters
*/
public class DerivedSecretKey extends SecretKeySpec implements DerivedKey {
private final String serialized;
/**
* Derived Secret Key constructor with encoded key and serialized representation
*
* @param key Encoded key bytes
* @param algorithm Cipher algorithm for which the key will be used
* @param serialized Serialized representation of the key with parameters depending on derivation function
*/
public DerivedSecretKey(final byte[] key, final String algorithm, final String serialized) {
super(key, algorithm);
this.serialized = serialized;
}
/**
* Get serialized key with parameters
*
* @return Serialized key with parameters
*/
@Override
public String getSerialized() {
return serialized;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.crypto.key;
import java.util.Objects;
/**
* Standard implementation of Derived Key Specification with required properties
*/
public class StandardDerivedKeySpec<T extends DerivedKeyParameterSpec> implements DerivedKeySpec<T> {
private final char[] password;
private final int derivedKeyLength;
private final String algorithm;
private final T parameterSpec;
public StandardDerivedKeySpec(
final char[] password,
final int derivedKeyLength,
final String algorithm,
final T parameterSpec
) {
this.password = Objects.requireNonNull(password, "Password required");
this.derivedKeyLength = derivedKeyLength;
this.algorithm = Objects.requireNonNull(algorithm, "Algorithm required");
this.parameterSpec = Objects.requireNonNull(parameterSpec, "Parameter Specification required");
}
@Override
public char[] getPassword() {
return password;
}
@Override
public int getDerivedKeyLength() {
return derivedKeyLength;
}
@Override
public String getAlgorithm() {
return algorithm;
}
@Override
public T getParameterSpec() {
return parameterSpec;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.crypto.key.argon2;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
/**
* Argon2 key derivation function parameter specification
*/
public class Argon2DerivedKeyParameterSpec implements DerivedKeyParameterSpec {
private final int memory;
private final int iterations;
private final int parallelism;
private final byte[] salt;
/**
* Argon2 Parameter Specification constructor with required properties
*
* @param memory Size of memory in kilobytes for processing
* @param iterations Number of iterations to perform
* @param parallelism Number of threads for parallel processing
* @param salt Array of random salt bytes
*/
public Argon2DerivedKeyParameterSpec(
final int memory,
final int iterations,
final int parallelism,
final byte[] salt
) {
this.memory = memory;
this.iterations = iterations;
this.parallelism = parallelism;
this.salt = salt;
}
@Override
public byte[] getSalt() {
return salt;
}
public int getMemory() {
return memory;
}
public int getIterations() {
return iterations;
}
public int getParallelism() {
return parallelism;
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.crypto.key.argon2;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Argon2 implementation reads a US-ASCII string of bytes using the Password-Hashing-Competition String Format specification
*/
public class Argon2DerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<Argon2DerivedKeyParameterSpec> {
/** Argon2id hybrid with version 1.3 and 22 character Base64 encoded salt with optional trailing hash parameter */
private static final Pattern PHC_STRING_FORMAT = Pattern.compile("^\\$argon2id\\$v=19\\$m=(\\d+),t=(\\d+),p=(\\d+)\\$([\\w/+]{22})(\\$[\\w/+]+)?$");
private static final int MEMORY_GROUP = 1;
private static final int ITERATIONS_GROUP = 2;
private static final int PARALLELISM_GROUP = 3;
private static final int SALT_GROUP = 4;
private static final Charset PARAMETERS_CHARACTER_SET = StandardCharsets.US_ASCII;
private static final Base64.Decoder decoder = Base64.getDecoder();
/**
* Read serialized parameters parsed from US-ASCII string of bytes
*
* @param serializedParameters Serialized parameters
* @return Argon2 Parameter Specification
*/
@Override
public Argon2DerivedKeyParameterSpec read(final byte[] serializedParameters) {
Objects.requireNonNull(serializedParameters, "Parameters required");
final String parameters = new String(serializedParameters, PARAMETERS_CHARACTER_SET);
final Matcher matcher = PHC_STRING_FORMAT.matcher(parameters);
if (matcher.matches()) {
final String memoryGroup = matcher.group(MEMORY_GROUP);
final String iterationsGroup = matcher.group(ITERATIONS_GROUP);
final String parallelismGroup = matcher.group(PARALLELISM_GROUP);
final String saltGroup = matcher.group(SALT_GROUP);
final int memory = Integer.parseInt(memoryGroup);
final int iterations = Integer.parseInt(iterationsGroup);
final int parallelism = Integer.parseInt(parallelismGroup);
final byte[] salt = decoder.decode(saltGroup);
return new Argon2DerivedKeyParameterSpec(
memory,
iterations,
parallelism,
salt
);
} else {
final String message = String.format("Argon2 serialized parameters [%s] format not matched", parameters);
throw new IllegalArgumentException(message);
}
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.crypto.key.argon2;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.DerivedSecretKey;
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import java.util.Base64;
/**
* Argon2 implementation of Derived Key Provider based on Bouncy Castle Argon2 components described in RFC 9016
*/
public class Argon2DerivedKeyProvider implements DerivedKeyProvider<Argon2DerivedKeyParameterSpec> {
private static final int HYBRID_VERSION = Argon2Parameters.ARGON2_id;
private static final int CURRENT_VERSION = Argon2Parameters.ARGON2_VERSION_13;
private static final String SERIALIZED_FORMAT = "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s";
private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
/**
* Get Derived Key using Argon2id version 1.3 and provided specification
*
* @param derivedKeySpec Derived Key Specification
* @return Derived Secret Key
*/
@Override
public DerivedKey getDerivedKey(final DerivedKeySpec<Argon2DerivedKeyParameterSpec> derivedKeySpec) {
final Argon2DerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
final Argon2Parameters parameters = getParameters(parameterSpec);
final Argon2BytesGenerator generator = new Argon2BytesGenerator();
generator.init(parameters);
final int derivedKeyLength = derivedKeySpec.getDerivedKeyLength();
final byte[] derivedKeyBytes = new byte[derivedKeyLength];
final char[] password = derivedKeySpec.getPassword();
generator.generateBytes(password, derivedKeyBytes);
final String serialized = getSerialized(derivedKeyBytes, parameterSpec);
return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
}
private Argon2Parameters getParameters(final Argon2DerivedKeyParameterSpec parameterSpec) {
return new Argon2Parameters.Builder(HYBRID_VERSION)
.withVersion(CURRENT_VERSION)
.withMemoryAsKB(parameterSpec.getMemory())
.withIterations(parameterSpec.getIterations())
.withParallelism(parameterSpec.getParallelism())
.withSalt(parameterSpec.getSalt())
.build();
}
private String getSerialized(final byte[] derivedKeyBytes, final Argon2DerivedKeyParameterSpec parameterSpec) {
final String derivedKeyEncoded = encoder.encodeToString(derivedKeyBytes);
final String saltEncoded = encoder.encodeToString(parameterSpec.getSalt());
return String.format(
SERIALIZED_FORMAT,
CURRENT_VERSION,
parameterSpec.getMemory(),
parameterSpec.getIterations(),
parameterSpec.getParallelism(),
saltEncoded,
derivedKeyEncoded
);
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.crypto.key.bcrypt;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Base64 Decoder translates from bcrypt Base64 characters to RFC 4648 characters
*/
class BcryptBase64Decoder {
/** Alphabet of shared characters following common ordering */
private static final String SHARED_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
/** bcrypt alphabet defined according to the OpenBSD bcrypt function beginning with control characters */
private static final String BCRYPT_ALPHABET = String.format("./%s", SHARED_ALPHABET);
/** Standard alphabet defined according to RFC 4648 ending with control characters */
private static final String STANDARD_ALPHABET = String.format("%s+/", SHARED_ALPHABET);
private static final Map<Character, Character> BCRYPT_STANDARD_CHARACTERS = new LinkedHashMap<>();
private static final Base64.Decoder decoder = Base64.getDecoder();
static {
final char[] bcryptCharacters = BCRYPT_ALPHABET.toCharArray();
final char[] standardCharacters = STANDARD_ALPHABET.toCharArray();
for (int i = 0; i < bcryptCharacters.length; i++) {
final char bcryptCharacter = bcryptCharacters[i];
final char standardCharacter = standardCharacters[i];
BCRYPT_STANDARD_CHARACTERS.put(bcryptCharacter, standardCharacter);
}
}
/**
* Decode bcrypt Base64 encoded ASCII characters to support reading salt and hash strings
*
* @param encoded ASCII string of characters encoded using bcrypt Base64 characters
* @return Decoded bytes
*/
static byte[] decode(final String encoded) {
final int encodedLength = encoded.length();
final byte[] converted = new byte[encodedLength];
for (int i = 0; i < encodedLength; i++) {
final char encodedCharacter = encoded.charAt(i);
final char standardCharacter = getStandardCharacter(encodedCharacter);
converted[i] = (byte) standardCharacter;
}
return decoder.decode(converted);
}
private static char getStandardCharacter(final char encodedCharacter) {
final Character standardCharacter = BCRYPT_STANDARD_CHARACTERS.get(encodedCharacter);
if (standardCharacter == null) {
final String message = String.format("Encoded character [%c] not supported for bcrypt Base64 decoding", encodedCharacter);
throw new IllegalArgumentException(message);
}
return standardCharacter;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.crypto.key.bcrypt;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
/**
* bcrypt key derivation function parameter specification
*/
public class BcryptDerivedKeyParameterSpec implements DerivedKeyParameterSpec {
private final int cost;
private final byte[] salt;
/**
* bcrypt Parameter Specification constructor with required properties
*
* @param cost Cost parameter
* @param salt Array of random salt bytes
*/
public BcryptDerivedKeyParameterSpec(
final int cost,
final byte[] salt
) {
this.cost = cost;
this.salt = salt;
}
@Override
public byte[] getSalt() {
return salt;
}
public int getCost() {
return cost;
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.crypto.key.bcrypt;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* bcrypt implementation reads a US-ASCII string of bytes using the Modular Crypt Format specification as added in NiFi 0.5.0
*/
public class BcryptDerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<BcryptDerivedKeyParameterSpec> {
/** Modular Crypt Format containing the cost as a zero-padded number with a trailing bcrypt-Base64 encoded 16 byte salt and optional hash */
private static final Pattern MODULAR_CRYPT_FORMAT = Pattern.compile("^\\$2[abxy]\\$(\\d{2})\\$([\\w/.]{22})([\\w/.]{31})?$");
private static final int COST_GROUP = 1;
private static final int SALT_GROUP = 2;
private static final Charset PARAMETERS_CHARACTER_SET = StandardCharsets.US_ASCII;
/**
* Read serialized parameters parsed from US-ASCII string of bytes
*
* @param serializedParameters Serialized parameters
* @return bcrypt Parameter Specification
*/
@Override
public BcryptDerivedKeyParameterSpec read(final byte[] serializedParameters) {
Objects.requireNonNull(serializedParameters, "Parameters required");
final String parameters = new String(serializedParameters, PARAMETERS_CHARACTER_SET);
final Matcher matcher = MODULAR_CRYPT_FORMAT.matcher(parameters);
if (matcher.matches()) {
final String costGroup = matcher.group(COST_GROUP);
final String saltGroup = matcher.group(SALT_GROUP);
final int cost = Integer.parseInt(costGroup);
final byte[] salt = BcryptBase64Decoder.decode(saltGroup);
return new BcryptDerivedKeyParameterSpec(cost, salt);
} else {
final String message = String.format("bcrypt serialized parameters [%s] format not matched", parameters);
throw new IllegalArgumentException(message);
}
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.crypto.key.bcrypt;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.DerivedSecretKey;
import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* bcrypt implementation of Derived Key Provider based on Bouncy Castle bcrypt components with SHA-512 digest for derived key
*/
public class BcryptDerivedKeyProvider implements DerivedKeyProvider<BcryptDerivedKeyParameterSpec> {
private static final Charset SERIALIZED_CHARACTER_SET = StandardCharsets.US_ASCII;
private static final int SERIALIZED_HASH_START_INDEX = 29;
private static final String DIGEST_ALGORITHM = "SHA-512";
private static final String BCRYPT_VERSION = "2a";
/**
* Get Derived Key using bcrypt version 2a and provided specification with SHA-512 digest of encoded raw hash bytes
*
* @param derivedKeySpec Derived Key Specification
* @return Derived Secret Key
*/
@Override
public DerivedKey getDerivedKey(final DerivedKeySpec<BcryptDerivedKeyParameterSpec> derivedKeySpec) {
final String serialized = getHashMessage(derivedKeySpec);
final byte[] hashMessage = serialized.getBytes(SERIALIZED_CHARACTER_SET);
final byte[] encodedRawHash = Arrays.copyOfRange(hashMessage, SERIALIZED_HASH_START_INDEX, hashMessage.length);
final byte[] derivedKeyBytes = getDerivedKeyBytes(encodedRawHash, derivedKeySpec.getDerivedKeyLength());
return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
}
private String getHashMessage(final DerivedKeySpec<BcryptDerivedKeyParameterSpec> derivedKeySpec) {
final BcryptDerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
final int cost = parameterSpec.getCost();
final byte[] salt = parameterSpec.getSalt();
final char[] password = derivedKeySpec.getPassword();
return OpenBSDBCrypt.generate(BCRYPT_VERSION, password, salt, cost);
}
private byte[] getDerivedKeyBytes(final byte[] hash, final int derivedKeyLength) {
final MessageDigest messageDigest = getMessageDigest();
final byte[] digested = messageDigest.digest(hash);
return Arrays.copyOf(digested, derivedKeyLength);
}
private MessageDigest getMessageDigest() {
try {
return MessageDigest.getInstance(DIGEST_ALGORITHM);
} catch (final NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(DIGEST_ALGORITHM, e);
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.crypto.key.detection;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpecReader;
import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpecReader;
import org.apache.nifi.security.crypto.key.io.ByteBufferSearch;
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpecReader;
import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpecReader;
import java.nio.ByteBuffer;
/**
* Delegating implementation capable of selecting a reader based on detected header bytes of serialized parameters
*/
public class DetectedDerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<DerivedKeyParameterSpec> {
/** Argon2id header as implemented in NiFi 1.12.0 */
private static final byte[] ARGON2_ID_DELIMITER = {'$', 'a', 'r', 'g', 'o', 'n', '2', 'i', 'd', '$'};
/** bcrypt 2a header as implemented in NiFi 0.5.0 */
private static final byte[] BCRYPT_2A_DELIMITER = {'$', '2', 'a', '$'};
/** scrypt header as implemented in NiFi 0.5.0 */
private static final byte[] SCRYPT_DELIMITER = {'$', 's', '0', '$'};
private static final Argon2DerivedKeyParameterSpecReader argon2Reader = new Argon2DerivedKeyParameterSpecReader();
private static final BcryptDerivedKeyParameterSpecReader bcryptReader = new BcryptDerivedKeyParameterSpecReader();
private static final ScryptDerivedKeyParameterSpecReader scryptReader = new ScryptDerivedKeyParameterSpecReader();
private static final Pbkdf2DerivedKeyParameterSpecReader pbkdf2Reader = new Pbkdf2DerivedKeyParameterSpecReader();
/**
* Read Parameter Specification selects a Reader based on header bytes defaulting to PBKDF2 in absence of a matched pattern
*
* @param serializedParameters Serialized parameters
* @return Derived Key Parameter Specification read from serialized parameters
*/
@Override
public DerivedKeyParameterSpec read(final byte[] serializedParameters) {
final ByteBuffer buffer = ByteBuffer.wrap(serializedParameters);
final DerivedKeyParameterSpecReader<? extends DerivedKeyParameterSpec> reader = getReader(buffer);
return reader.read(serializedParameters);
}
private DerivedKeyParameterSpecReader<? extends DerivedKeyParameterSpec> getReader(final ByteBuffer buffer) {
final DerivedKeyParameterSpecReader<? extends DerivedKeyParameterSpec> reader;
if (ByteBufferSearch.indexOf(buffer, ARGON2_ID_DELIMITER) == 0) {
reader = argon2Reader;
} else if (ByteBufferSearch.indexOf(buffer, BCRYPT_2A_DELIMITER) == 0) {
reader = bcryptReader;
} else if (ByteBufferSearch.indexOf(buffer, SCRYPT_DELIMITER) == 0) {
reader = scryptReader;
} else {
reader = pbkdf2Reader;
}
return reader;
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.crypto.key.detection;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyProvider;
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyProvider;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* Provider delegating to configured implementations based on Parameter Specification class
*/
public class DetectedDerivedKeyProvider implements DerivedKeyProvider<DerivedKeyParameterSpec> {
private static final Map<Class<? extends DerivedKeyParameterSpec>, DerivedKeyProvider<? extends DerivedKeyParameterSpec>> providers = new LinkedHashMap<>();
static {
providers.put(Argon2DerivedKeyParameterSpec.class, new Argon2DerivedKeyProvider());
providers.put(BcryptDerivedKeyParameterSpec.class, new BcryptDerivedKeyProvider());
providers.put(ScryptDerivedKeyParameterSpec.class, new ScryptDerivedKeyProvider());
providers.put(Pbkdf2DerivedKeyParameterSpec.class, new Pbkdf2DerivedKeyProvider());
}
/**
* Get Derived Key using implementation selected based on assignable Parameter Specification class mapped to Provider
*
* @param derivedKeySpec Derived Key Specification
* @return Derived Key
*/
@Override
public DerivedKey getDerivedKey(final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec) {
Objects.requireNonNull(derivedKeySpec, "Specification required");
final Class<? extends DerivedKeyParameterSpec> parameterSpecClass = derivedKeySpec.getParameterSpec().getClass();
final DerivedKeyProvider<DerivedKeyParameterSpec> derivedKeyProvider = findProvider(parameterSpecClass);
return derivedKeyProvider.getDerivedKey(derivedKeySpec);
}
@SuppressWarnings("unchecked")
private DerivedKeyProvider<DerivedKeyParameterSpec> findProvider(final Class<? extends DerivedKeyParameterSpec> parameterSpecClass) {
final Class<? extends DerivedKeyParameterSpec> foundSpecClass = providers.keySet()
.stream()
.filter(specClass -> specClass.isAssignableFrom(parameterSpecClass))
.findFirst()
.orElseThrow(() -> new UnsupportedOperationException(String.format("Parameter Specification [%s] not supported", parameterSpecClass)));
final DerivedKeyProvider<? extends DerivedKeyParameterSpec> derivedKeyProvider = providers.get(foundSpecClass);
return (DerivedKeyProvider<DerivedKeyParameterSpec>) derivedKeyProvider;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.crypto.key.io;
import java.nio.ByteBuffer;
/**
* Byte Buffer Search utilities
*/
public class ByteBufferSearch {
private static final int END_OF_FILE = -1;
/**
* Get starting index of delimiter in buffer
*
* @param buffer Byte Buffer to be searched
* @param delimiter Delimiter
* @return Starting index of delimiter or -1 when not found
*/
public static int indexOf(final ByteBuffer buffer, final byte[] delimiter) {
final int bufferSearchLength = buffer.limit() - delimiter.length + 1;
bufferSearch:
for (int i = 0; i < bufferSearchLength; i++) {
for (int j = 0; j < delimiter.length; j++) {
final int bufferIndex = i + j;
final byte indexByte = buffer.get(bufferIndex);
final byte delimiterByte = delimiter[j];
if (indexByte != delimiterByte) {
continue bufferSearch;
}
}
return i;
}
return END_OF_FILE;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.crypto.key.pbkdf2;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
/**
* PBKDF2 key derivation function parameter specification
*/
public class Pbkdf2DerivedKeyParameterSpec implements DerivedKeyParameterSpec {
private final int iterations;
private final byte[] salt;
/**
* PBKDF2 Parameter Specification constructor with required properties
*
* @param iterations Cost parameter
* @param salt Array of random salt bytes
*/
public Pbkdf2DerivedKeyParameterSpec(
final int iterations,
final byte[] salt
) {
this.iterations = iterations;
this.salt = salt;
}
@Override
public byte[] getSalt() {
return salt;
}
public int getIterations() {
return iterations;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.crypto.key.pbkdf2;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
import java.util.Objects;
/**
* PBKDF2 implementation uses the serialized parameters as the salt with a hard-coded number of iterations as defined in NiFi 0.5.0
*/
public class Pbkdf2DerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<Pbkdf2DerivedKeyParameterSpec> {
protected static final int VERSION_0_5_0_ITERATIONS = 160000;
/**
* Read serialized parameters and return as salt bytes with 160,000 iterations as defined in NiFi 0.5.0
*
* @param serializedParameters Serialized parameters
* @return PBKDF2 Parameter Specification
*/
@Override
public Pbkdf2DerivedKeyParameterSpec read(final byte[] serializedParameters) {
Objects.requireNonNull(serializedParameters, "Parameters required");
return new Pbkdf2DerivedKeyParameterSpec(VERSION_0_5_0_ITERATIONS, serializedParameters);
}
}

View File

@ -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.security.crypto.key.pbkdf2;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.DerivedSecretKey;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* PBKDF2 implementation of Derived Key Provider based on Bouncy Castle components with HMAC SHA-512 pseudorandom function
*/
public class Pbkdf2DerivedKeyProvider implements DerivedKeyProvider<Pbkdf2DerivedKeyParameterSpec> {
private static final Charset PASSWORD_CHARACTER_SET = StandardCharsets.UTF_8;
private static final int BITS = 8;
private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
/**
* Get Derived Key using PBKDF2 with HMAC SHA-512 and provided specification
*
* @param derivedKeySpec Derived Key Specification
* @return Derived Secret Key
*/
@Override
public DerivedKey getDerivedKey(final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec) {
final byte[] derivedKeyBytes = getDerivedKeyBytes(derivedKeySpec);
final String serialized = encoder.encodeToString(derivedKeyBytes);
return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
}
private byte[] getDerivedKeyBytes(final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec) {
final Digest digest = new SHA512Digest();
final PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(digest);
final byte[] password = new String(derivedKeySpec.getPassword()).getBytes(PASSWORD_CHARACTER_SET);
final Pbkdf2DerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
final byte[] salt = parameterSpec.getSalt();
final int iterations = parameterSpec.getIterations();
generator.init(password, salt, iterations);
final int derivedKeyLengthBits = derivedKeySpec.getDerivedKeyLength() * BITS;
final CipherParameters cipherParameters = generator.generateDerivedParameters(derivedKeyLengthBits);
final KeyParameter keyParameter = (KeyParameter) cipherParameters;
return keyParameter.getKey();
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.crypto.key.scrypt;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
/**
* scrypt key derivation function parameter specification
*/
public class ScryptDerivedKeyParameterSpec implements DerivedKeyParameterSpec {
private final int cost;
private final int blockSize;
private final int parallelization;
private final byte[] salt;
/**
* scrypt Parameter Specification constructor with required properties
*
* @param cost CPU and memory cost parameter
* @param blockSize Block size parameter
* @param parallelization Parallelization parameter
* @param salt Array of random salt bytes
*/
public ScryptDerivedKeyParameterSpec(
final int cost,
final int blockSize,
final int parallelization,
final byte[] salt
) {
this.cost = cost;
this.blockSize = blockSize;
this.parallelization = parallelization;
this.salt = salt;
}
@Override
public byte[] getSalt() {
return salt;
}
public int getCost() {
return cost;
}
public int getBlockSize() {
return blockSize;
}
public int getParallelization() {
return parallelization;
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.crypto.key.scrypt;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* scrypt implementation reads a US-ASCII string of bytes using a Modular Crypt Format defined according to the com.lambdaworks:scrypt library
*/
public class ScryptDerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<ScryptDerivedKeyParameterSpec> {
/** Modular Crypt Format containing the parameters encoded as a 32-bit hexadecimal number with a trailing Base64 encoded salt and optional hash */
private static final Pattern MODULAR_CRYPT_FORMAT = Pattern.compile("^\\$s0\\$([a-f0-9]{5,})\\$([\\w/=+]{11,64})\\$?([\\w/=+]{1,256})?$");
private static final int PARAMETERS_GROUP = 1;
private static final int SALT_GROUP = 2;
private static final Charset PARAMETERS_CHARACTER_SET = StandardCharsets.US_ASCII;
private static final int HEXADECIMAL_RADIX = 16;
private static final int LOG_BASE_2 = 2;
private static final int COST_BITS = 16;
private static final int SIZE_BITS = 8;
private static final int SIXTEEN_BIT_SHIFT = 0xffff;
private static final int EIGHT_BIT_SHIFT = 0xff;
private static final Base64.Decoder decoder = Base64.getDecoder();
@Override
public ScryptDerivedKeyParameterSpec read(final byte[] serializedParameters) {
Objects.requireNonNull(serializedParameters, "Parameters required");
final String parameters = new String(serializedParameters, PARAMETERS_CHARACTER_SET);
final Matcher matcher = MODULAR_CRYPT_FORMAT.matcher(parameters);
if (matcher.matches()) {
final String parametersGroup = matcher.group(PARAMETERS_GROUP);
final String saltGroup = matcher.group(SALT_GROUP);
return readParameters(parametersGroup, saltGroup);
} else {
final String message = String.format("scrypt serialized parameters [%s] format not matched", parameters);
throw new IllegalArgumentException(message);
}
}
private ScryptDerivedKeyParameterSpec readParameters(final String parametersEncoded, final String saltEncoded) {
final long parameters = Long.parseLong(parametersEncoded, HEXADECIMAL_RADIX);
final long costExponent = parameters >> COST_BITS & SIXTEEN_BIT_SHIFT;
final int cost = (int) Math.pow(LOG_BASE_2, costExponent);
final int blockSize = (int) parameters >> SIZE_BITS & EIGHT_BIT_SHIFT;
final int parallelization = (int) parameters & EIGHT_BIT_SHIFT;
final byte[] salt = decoder.decode(saltEncoded);
return new ScryptDerivedKeyParameterSpec(cost, blockSize, parallelization, salt);
}
}

View File

@ -0,0 +1,96 @@
/*
* 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.crypto.key.scrypt;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.DerivedSecretKey;
import org.bouncycastle.crypto.generators.SCrypt;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* scrypt implementation of Derived Key Provider based on Bouncy Castle scrypt components described in RFC 7914
*/
public class ScryptDerivedKeyProvider implements DerivedKeyProvider<ScryptDerivedKeyParameterSpec> {
private static final String SERIALIZED_FORMAT = "$s0$%s$%s$%s";
private static final int HEXADECIMAL_RADIX = 16;
private static final int LOG_BASE_2 = 2;
private static final int COST_BITS = 16;
private static final int SIZE_BITS = 8;
private static final Charset PASSWORD_CHARACTER_SET = StandardCharsets.UTF_8;
private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
/**
* Get Derived Key using scrypt and provided specification
*
* @param derivedKeySpec Derived Key Specification
* @return Derived Secret Key
*/
@Override
public DerivedKey getDerivedKey(final DerivedKeySpec<ScryptDerivedKeyParameterSpec> derivedKeySpec) {
final byte[] password = new String(derivedKeySpec.getPassword()).getBytes(PASSWORD_CHARACTER_SET);
final ScryptDerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
final byte[] salt = parameterSpec.getSalt();
final int cost = parameterSpec.getCost();
final int blockSize = parameterSpec.getBlockSize();
final int parallelization = parameterSpec.getParallelization();
final int derivedKeyLength = derivedKeySpec.getDerivedKeyLength();
final byte[] derivedKeyBytes = SCrypt.generate(password, salt, cost, blockSize, parallelization, derivedKeyLength);
final String serialized = getSerialized(derivedKeyBytes, parameterSpec);
return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
}
private String getSerialized(final byte[] derivedKeyBytes, final ScryptDerivedKeyParameterSpec parameterSpec) {
final String parametersEncoded = getParametersEncoded(parameterSpec);
final String derivedKeyEncoded = encoder.encodeToString(derivedKeyBytes);
final String saltEncoded = encoder.encodeToString(parameterSpec.getSalt());
return String.format(
SERIALIZED_FORMAT,
parametersEncoded,
saltEncoded,
derivedKeyEncoded
);
}
private String getParametersEncoded(final ScryptDerivedKeyParameterSpec parameterSpec) {
final long cost = log2(parameterSpec.getCost()) << COST_BITS;
final long blockSize = parameterSpec.getBlockSize() << SIZE_BITS;
final long parallelization = parameterSpec.getParallelization();
final long parameters = cost | blockSize | parallelization;
return Long.toString(parameters, HEXADECIMAL_RADIX);
}
private long log2(final int number) {
final double log = Math.log(number);
final double logBase2 = Math.log(LOG_BASE_2);
final double log2 = log / logBase2;
return Math.round(log2);
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.crypto.key.argon2;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
class Argon2DerivedKeyParameterSpecReaderTest {
private static final byte[] STRING_PARAMETERS = String.class.getSimpleName().getBytes(StandardCharsets.US_ASCII);
private static final int MEMORY = 65536;
private static final int ITERATIONS = 2;
private static final int PARALLELISM = 1;
private static final String ARGON2_SALT_STRING = "QXJnb24yU2FsdFN0cmluZw";
private static final String PARAMETERS = String.format("$argon2id$v=19$m=%d,t=%d,p=%d$%s", MEMORY, ITERATIONS, PARALLELISM, ARGON2_SALT_STRING);
private static final String PARAMETERS_HASH = String.format("%s$6LOmoOXYJV0tXBJtxtD1Mg", PARAMETERS);
Base64.Decoder decoder = Base64.getDecoder();
Argon2DerivedKeyParameterSpecReader reader;
@BeforeEach
void setReader() {
reader = new Argon2DerivedKeyParameterSpecReader();
}
@Test
void testReadException() {
assertThrows(IllegalArgumentException.class, () -> reader.read(STRING_PARAMETERS));
}
@Test
void testRead() {
final byte[] serializedParameters = PARAMETERS.getBytes(StandardCharsets.US_ASCII);
final Argon2DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertParameterSpecEquals(parameterSpec);
}
@Test
void testReadHash() {
final byte[] serializedParameters = PARAMETERS_HASH.getBytes(StandardCharsets.US_ASCII);
final Argon2DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertParameterSpecEquals(parameterSpec);
}
private void assertParameterSpecEquals(final Argon2DerivedKeyParameterSpec parameterSpec) {
assertNotNull(parameterSpec);
assertEquals(MEMORY, parameterSpec.getMemory());
assertEquals(ITERATIONS, parameterSpec.getIterations());
assertEquals(PARALLELISM, parameterSpec.getParallelism());
final byte[] salt = decoder.decode(ARGON2_SALT_STRING);
assertArrayEquals(salt, parameterSpec.getSalt());
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.crypto.key.argon2;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class Argon2DerivedKeyProviderTest {
private static final char[] PASSWORD = String.class.getSimpleName().toCharArray();
private static final int DERIVED_KEY_LENGTH = 16;
private static final String ALGORITHM = "AES";
private static final int MEMORY = 65536;
private static final int ITERATIONS = 2;
private static final int PARALLELISM = 1;
private static final String ARGON2_SALT_STRING = "QXJnb24yU2FsdFN0cmluZw";
private static final String PARAMETERS = String.format("$argon2id$v=19$m=%d,t=%d,p=%d$%s", MEMORY, ITERATIONS, PARALLELISM, ARGON2_SALT_STRING);
private static final String HASH = "6LOmoOXYJV0tXBJtxtD1Mg";
private static final String SERIALIZED = String.format("%s$%s", PARAMETERS, HASH);
Base64.Decoder decoder = Base64.getDecoder();
Argon2DerivedKeyProvider provider;
@BeforeEach
void setReader() {
provider = new Argon2DerivedKeyProvider();
}
@Test
void testGetDerivedKey() {
final byte[] salt = decoder.decode(ARGON2_SALT_STRING);
final Argon2DerivedKeyParameterSpec parameterSpec = new Argon2DerivedKeyParameterSpec(MEMORY, ITERATIONS, PARALLELISM, salt);
final DerivedKeySpec<Argon2DerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
PASSWORD,
DERIVED_KEY_LENGTH,
ALGORITHM,
parameterSpec
);
final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
assertNotNull(derivedKey);
assertEquals(ALGORITHM, derivedKey.getAlgorithm());
assertEquals(SERIALIZED, derivedKey.getSerialized());
final byte[] hashBytes = decoder.decode(HASH);
assertArrayEquals(hashBytes, derivedKey.getEncoded());
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.crypto.key.bcrypt;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
class BcryptDerivedKeyParameterSpecReaderTest {
private static final byte[] STRING_PARAMETERS = String.class.getSimpleName().getBytes(StandardCharsets.US_ASCII);
private static final int COST = 12;
private static final String SALT_ENCODED = "R9h/cIPz0gi.URNNX3kh2O";
private static final String HASH = "PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW";
private static final String SERIALIZED = String.format("$2a$%d$%s", COST, SALT_ENCODED);
private static final String SERIALIZED_HASH = String.format("%s%s", SERIALIZED, HASH);
BcryptDerivedKeyParameterSpecReader reader;
@BeforeEach
void setReader() {
reader = new BcryptDerivedKeyParameterSpecReader();
}
@Test
void testReadException() {
assertThrows(IllegalArgumentException.class, () -> reader.read(STRING_PARAMETERS));
}
@Test
void testRead() {
final byte[] serializedParameters = SERIALIZED.getBytes(StandardCharsets.US_ASCII);
final BcryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertParameterSpecEquals(parameterSpec);
}
@Test
void testReadHash() {
final byte[] serializedParameters = SERIALIZED_HASH.getBytes(StandardCharsets.US_ASCII);
final BcryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertParameterSpecEquals(parameterSpec);
}
private void assertParameterSpecEquals(final BcryptDerivedKeyParameterSpec parameterSpec) {
assertNotNull(parameterSpec);
assertEquals(COST, parameterSpec.getCost());
final byte[] salt = BcryptBase64Decoder.decode(SALT_ENCODED);
assertArrayEquals(salt, parameterSpec.getSalt());
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.crypto.key.bcrypt;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class BcryptDerivedKeyProviderTest {
private static final String PASSWORD = "abc123xyz";
private static final int COST = 12;
private static final String SALT_ENCODED = "R9h/cIPz0gi.URNNX3kh2O";
private static final String HASH = "PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW";
private static final String SERIALIZED = String.format("$2a$%d$%s%s", COST, SALT_ENCODED, HASH);
private static final Charset SERIALIZED_CHARACTER_SET = StandardCharsets.US_ASCII;
private static final int DERIVED_KEY_LENGTH = 16;
private static final String ALGORITHM = "AES";
private static final String DIGEST_ALGORITHM = "SHA-512";
BcryptDerivedKeyProvider provider;
@BeforeEach
void setProvider() {
provider = new BcryptDerivedKeyProvider();
}
@Test
void testGetDerivedKey() {
final byte[] salt = BcryptBase64Decoder.decode(SALT_ENCODED);
final BcryptDerivedKeyParameterSpec parameterSpec = new BcryptDerivedKeyParameterSpec(COST, salt);
final DerivedKeySpec<BcryptDerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
PASSWORD.toCharArray(),
DERIVED_KEY_LENGTH,
ALGORITHM,
parameterSpec
);
final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
assertNotNull(derivedKey);
assertEquals(ALGORITHM, derivedKey.getAlgorithm());
assertEquals(SERIALIZED, derivedKey.getSerialized());
final byte[] encodedHashBytes = HASH.getBytes(SERIALIZED_CHARACTER_SET);
final byte[] digestedDerivedKey = getDerivedKeyBytes(encodedHashBytes);
assertArrayEquals(digestedDerivedKey, derivedKey.getEncoded());
}
private byte[] getDerivedKeyBytes(final byte[] hash) {
final MessageDigest messageDigest = getMessageDigest();
final byte[] digested = messageDigest.digest(hash);
return Arrays.copyOf(digested, DERIVED_KEY_LENGTH);
}
private MessageDigest getMessageDigest() {
try {
return MessageDigest.getInstance(DIGEST_ALGORITHM);
} catch (final NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(DIGEST_ALGORITHM, e);
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.crypto.key.detection;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
class DetectedDerivedKeyParameterSpecReaderTest {
private static final String UNSPECIFIED_PARAMETERS = "unspecified";
private static final String ARGON2_PARAMETERS = "$argon2id$v=19$m=65536,t=2,p=1$QXJnb24yU2FsdFN0cmluZw";
private static final String BCRYPT_PARAMETERS = "$2a$12$R9h/cIPz0gi.URNNX3kh2O";
private static final String SCRYPT_PARAMETERS = "$s0$e0801$epIxT/h6HbbwHaehFnh/bw";
DetectedDerivedKeyParameterSpecReader reader;
@BeforeEach
void setReader() {
reader = new DetectedDerivedKeyParameterSpecReader();
}
@Test
void testArgon2() {
assertParameterSpecInstanceOf(Argon2DerivedKeyParameterSpec.class, ARGON2_PARAMETERS);
}
@Test
void testBcrypt() {
assertParameterSpecInstanceOf(BcryptDerivedKeyParameterSpec.class, BCRYPT_PARAMETERS);
}
@Test
void testScrypt() {
assertParameterSpecInstanceOf(ScryptDerivedKeyParameterSpec.class, SCRYPT_PARAMETERS);
}
@Test
void testPbkdf2() {
assertParameterSpecInstanceOf(Pbkdf2DerivedKeyParameterSpec.class, UNSPECIFIED_PARAMETERS);
}
private void assertParameterSpecInstanceOf(final Class<? extends DerivedKeyParameterSpec> parameterSpecClass, final String parameters) {
final byte[] serializedParameters = parameters.getBytes(StandardCharsets.UTF_8);
final DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertInstanceOf(parameterSpecClass, parameterSpec);
}
}

View File

@ -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.security.crypto.key.detection;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class DetectedDerivedKeyProviderTest {
private static final char[] PASSWORD = {'w', 'o', 'r', 'd'};
private static final int DERIVED_KEY_LENGTH = 16;
private static final String ALGORITHM = "AES";
private static final byte[] SALT = {'N', 'i', 'F', 'i', 'S', 'A', 'L', 'T'};
private static final int MINIMUM_ITERATIONS = 1;
private static final int MINIMUM_ARGON2_MEMORY = 2;
private static final int MINIMUM_PARALLELISM = 1;
private static final int MINIMUM_BCRYPT_COST = 4;
private static final byte[] BCRYPT_SALT = {'S', 'I', 'X', 'T', 'E', 'E', 'N', 'B', 'Y', 'T', 'E', 'S', 'S', 'A', 'L', 'T'};
private static final int MINIMUM_SCRYPT_COST = 2;
private static final int SCRYPT_BLOCK_SIZE = 16;
@Mock
DerivedKeyParameterSpec unsupportedDerivedKeyParameterSpec;
DetectedDerivedKeyProvider provider;
@BeforeEach
void setProvider() {
provider = new DetectedDerivedKeyProvider();
}
@Test
void testArgon2() {
final Argon2DerivedKeyParameterSpec parameterSpec = new Argon2DerivedKeyParameterSpec(MINIMUM_ARGON2_MEMORY, MINIMUM_ITERATIONS, MINIMUM_PARALLELISM, SALT);
assertDerivedKey(parameterSpec);
}
@Test
void testBcrypt() {
final BcryptDerivedKeyParameterSpec parameterSpec = new BcryptDerivedKeyParameterSpec(MINIMUM_BCRYPT_COST, BCRYPT_SALT);
assertDerivedKey(parameterSpec);
}
@Test
void testPbkdf2() {
final Pbkdf2DerivedKeyParameterSpec parameterSpec = new Pbkdf2DerivedKeyParameterSpec(MINIMUM_ITERATIONS, SALT);
assertDerivedKey(parameterSpec);
}
@Test
void testScrypt() {
final ScryptDerivedKeyParameterSpec parameterSpec = new ScryptDerivedKeyParameterSpec(MINIMUM_SCRYPT_COST, SCRYPT_BLOCK_SIZE, MINIMUM_PARALLELISM, SALT);
assertDerivedKey(parameterSpec);
}
@Test
void testUnsupported() {
final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec = getDerivedKeySpec(unsupportedDerivedKeyParameterSpec);
assertThrows(UnsupportedOperationException.class, () -> provider.getDerivedKey(derivedKeySpec));
}
private <T extends DerivedKeyParameterSpec> DerivedKeySpec<T> getDerivedKeySpec(final T parameterSpec) {
return new StandardDerivedKeySpec<>(PASSWORD, DERIVED_KEY_LENGTH, ALGORITHM, parameterSpec);
}
private <T extends DerivedKeyParameterSpec> void assertDerivedKey(final T parameterSpec) {
final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec = getDerivedKeySpec(parameterSpec);
final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
assertNotNull(derivedKey);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.crypto.key.pbkdf2;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class Pbkdf2DerivedKeyParameterSpecReaderTest {
private static final String STRING_PARAMETERS = String.class.getName();
Pbkdf2DerivedKeyParameterSpecReader reader;
@BeforeEach
void setReader() {
reader = new Pbkdf2DerivedKeyParameterSpecReader();
}
@Test
void testRead() {
final byte[] serializedParameters = STRING_PARAMETERS.getBytes(StandardCharsets.US_ASCII);
final Pbkdf2DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertNotNull(parameterSpec);
assertEquals(Pbkdf2DerivedKeyParameterSpecReader.VERSION_0_5_0_ITERATIONS, parameterSpec.getIterations());
assertArrayEquals(serializedParameters, parameterSpec.getSalt());
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.crypto.key.pbkdf2;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class Pbkdf2DerivedKeyProviderTest {
private static final char[] PASSWORD = String.class.getSimpleName().toCharArray();
private static final byte[] SALT = String.class.getName().getBytes(StandardCharsets.UTF_8);
private static final int DERIVED_KEY_LENGTH = 16;
private static final String SERIALIZED = "yeoZx0KZR9IzcuaOWOJbFg";
private static final String ALGORITHM = "AES";
private static final int ITERATIONS = 160000;
private static final Base64.Decoder decoder = Base64.getDecoder();
Pbkdf2DerivedKeyProvider provider;
@BeforeEach
void setReader() {
provider = new Pbkdf2DerivedKeyProvider();
}
@Test
void testGetDerivedKey() {
final Pbkdf2DerivedKeyParameterSpec parameterSpec = new Pbkdf2DerivedKeyParameterSpec(ITERATIONS, SALT);
final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
PASSWORD,
DERIVED_KEY_LENGTH,
ALGORITHM,
parameterSpec
);
final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
assertNotNull(derivedKey);
assertEquals(ALGORITHM, derivedKey.getAlgorithm());
assertEquals(SERIALIZED, derivedKey.getSerialized());
final byte[] decoded = decoder.decode(SERIALIZED);
assertArrayEquals(decoded, derivedKey.getEncoded());
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.crypto.key.scrypt;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ScryptDerivedKeyParameterSpecReaderTest {
private static final byte[] STRING_PARAMETERS = String.class.getSimpleName().getBytes(StandardCharsets.US_ASCII);
private static final int COST = 16384;
private static final int BLOCK_SIZE = 8;
private static final int PARALLELIZATION = 1;
private static final String SALT_ENCODED = "epIxT/h6HbbwHaehFnh/bw";
private static final String HASH = "7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0";
private static final String SERIALIZED = String.format("$s0$e0801$%s", SALT_ENCODED);
private static final String SERIALIZED_HASH = String.format("%s$%s", SERIALIZED, HASH);
private static final Base64.Decoder decoder = Base64.getDecoder();
ScryptDerivedKeyParameterSpecReader reader;
@BeforeEach
void setReader() {
reader = new ScryptDerivedKeyParameterSpecReader();
}
@Test
void testReadException() {
assertThrows(IllegalArgumentException.class, () -> reader.read(STRING_PARAMETERS));
}
@Test
void testRead() {
final byte[] serializedParameters = SERIALIZED.getBytes(StandardCharsets.US_ASCII);
final ScryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertParameterSpecEquals(parameterSpec);
}
@Test
void testReadHash() {
final byte[] serializedParameters = SERIALIZED_HASH.getBytes(StandardCharsets.US_ASCII);
final ScryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
assertParameterSpecEquals(parameterSpec);
}
private void assertParameterSpecEquals(final ScryptDerivedKeyParameterSpec parameterSpec) {
assertNotNull(parameterSpec);
assertEquals(COST, parameterSpec.getCost());
assertEquals(BLOCK_SIZE, parameterSpec.getBlockSize());
assertEquals(PARALLELIZATION, parameterSpec.getParallelization());
final byte[] salt = decoder.decode(SALT_ENCODED);
assertArrayEquals(salt, parameterSpec.getSalt());
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.crypto.key.scrypt;
import org.apache.nifi.security.crypto.key.DerivedKey;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class ScryptDerivedKeyProviderTest {
private static final String PASSWORD = "secret";
private static final int COST = 16384;
private static final int BLOCK_SIZE = 8;
private static final int PARALLELIZATION = 1;
private static final String SALT_ENCODED = "epIxT/h6HbbwHaehFnh/bw";
private static final String HASH = "7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0";
private static final String SERIALIZED = String.format("$s0$e0801$%s$%s", SALT_ENCODED, HASH);
private static final int DERIVED_KEY_LENGTH = 32;
private static final String ALGORITHM = "AES";
private static final Base64.Decoder decoder = Base64.getDecoder();
ScryptDerivedKeyProvider provider;
@BeforeEach
void setProvider() {
provider = new ScryptDerivedKeyProvider();
}
@Test
void testGetDerivedKey() {
final byte[] salt = decoder.decode(SALT_ENCODED);
final ScryptDerivedKeyParameterSpec parameterSpec = new ScryptDerivedKeyParameterSpec(COST, BLOCK_SIZE, PARALLELIZATION, salt);
final DerivedKeySpec<ScryptDerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
PASSWORD.toCharArray(),
DERIVED_KEY_LENGTH,
ALGORITHM,
parameterSpec
);
final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
assertNotNull(derivedKey);
assertEquals(ALGORITHM, derivedKey.getAlgorithm());
assertEquals(SERIALIZED, derivedKey.getSerialized());
final byte[] hashBytes = decoder.decode(HASH);
assertArrayEquals(hashBytes, derivedKey.getEncoded());
}
}

View File

@ -53,6 +53,7 @@
<module>nifi-record-path</module>
<module>nifi-repository-encryption</module>
<module>nifi-schema-utils</module>
<module>nifi-security-crypto-key</module>
<module>nifi-security-kerberos-api</module>
<module>nifi-security-kerberos</module>
<module>nifi-security-kms</module>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-cipher-bundle</artifactId>
<version>1.20.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-cipher-nar</artifactId>
<packaging>nar</packaging>
<properties>
<maven.javadoc.skip>true</maven.javadoc.skip>
<source.skip>true</source.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-cipher-processors</artifactId>
<version>1.20.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-services-api-nar</artifactId>
<version>1.20.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-cipher-bundle</artifactId>
<version>1.20.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-cipher-processors</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
<version>1.20.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-crypto-key</artifactId>
<version>1.20.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,29 @@
/*
* 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.cipher;
/**
* Cipher FlowFile Attribute Key
*/
class CipherAttributeKey {
static final String PBE_SCHEME = "pbe.scheme";
static final String PBE_SYMMETRIC_CIPHER = "pbe.symmetric.cipher";
static final String PBE_DIGEST_ALGORITHM = "pbe.digest.algorithm";
}

View File

@ -0,0 +1,32 @@
/*
* 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.cipher;
/**
* Cipher Exception for categorizing cryptographic failures
*/
public class CipherException extends RuntimeException {
/**
* Cipher Exception constructor with message and cause
*
* @param message Exception message
* @param cause Exception cause
*/
public CipherException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,364 @@
/*
* 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.cipher;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.SideEffectFree;
import org.apache.nifi.annotation.behavior.SupportsBatching;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmMode;
import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmPadding;
import org.apache.nifi.processors.cipher.algorithm.SymmetricCipher;
import org.apache.nifi.processors.cipher.encoded.EncodedDelimiter;
import org.apache.nifi.processors.cipher.encoded.KeySpecificationFormat;
import org.apache.nifi.processors.cipher.io.DecryptStreamCallback;
import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
import org.apache.nifi.security.crypto.key.detection.DetectedDerivedKeyParameterSpecReader;
import org.apache.nifi.security.crypto.key.detection.DetectedDerivedKeyProvider;
import org.apache.nifi.security.crypto.key.io.ByteBufferSearch;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SideEffectFree
@SupportsBatching
@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
@Tags({"cryptography", "decipher", "decrypt", "AES", "Argon2", "bcrypt", "scrypt", "PBKDF2"})
@CapabilityDescription("Decrypt content encrypted with AES and encoded according conventions added in NiFi 0.5.0 for the EncryptContent Processor. " +
"The Processor reads the first 256 bytes to determine the presence of a cryptographic salt based on finding the 'NiFiSALT' delimiter. " +
"The salt is not present for content encrypted with a raw hexadecimal key. " +
"The Processor determines the presence of the initialization vector based on finding the 'NiFiIV' delimiter." +
"The salt format indicates the Key Derivation Function that the Processor uses to generate a secret key based on a configured password. " +
"The Processor derives keys with a size of 128 bits according to the conventions implemented in NiFi 0.5.0."
)
public class DecryptContent extends AbstractProcessor {
static final PropertyDescriptor CIPHER_ALGORITHM_MODE = new PropertyDescriptor.Builder()
.name("cipher-algorithm-mode")
.displayName("Cipher Algorithm Mode")
.description("Block cipher mode of operation for decryption using the Advanced Encryption Standard")
.required(true)
.allowableValues(CipherAlgorithmMode.class)
.defaultValue(CipherAlgorithmMode.GCM.getValue())
.build();
static final PropertyDescriptor CIPHER_ALGORITHM_PADDING = new PropertyDescriptor.Builder()
.name("cipher-algorithm-padding")
.displayName("Cipher Algorithm Padding")
.description("Padding specification used in cipher operation for decryption using the Advanced Encryption Standard")
.required(true)
.allowableValues(CipherAlgorithmPadding.class)
.defaultValue(CipherAlgorithmPadding.NO_PADDING.getValue())
.build();
static final PropertyDescriptor KEY_SPECIFICATION_FORMAT = new PropertyDescriptor.Builder()
.name("key-specification-format")
.displayName("Key Specification Format")
.description("Format describing the configured Key Specification")
.required(true)
.allowableValues(KeySpecificationFormat.class)
.defaultValue(KeySpecificationFormat.PASSWORD.getValue())
.build();
static final PropertyDescriptor KEY_SPECIFICATION = new PropertyDescriptor.Builder()
.name("key-specification")
.displayName("Key Specification")
.description("Specification providing the raw secret key or a password from which to derive a secret key")
.required(true)
.sensitive(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
static final Relationship SUCCESS = new Relationship.Builder()
.name("success")
.description("Decryption succeeded")
.build();
static final Relationship FAILURE = new Relationship.Builder()
.name("failure")
.description("Decryption failed")
.build();
private static final List<PropertyDescriptor> DESCRIPTORS = Collections.unmodifiableList(Arrays.asList(
CIPHER_ALGORITHM_MODE,
CIPHER_ALGORITHM_PADDING,
KEY_SPECIFICATION_FORMAT,
KEY_SPECIFICATION
));
private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
SUCCESS,
FAILURE
)));
private static final SymmetricCipher SYMMETRIC_CIPHER = SymmetricCipher.AES;
private static final String TRANSFORMATION_FORMAT = "%s/%s/%s";
/**
* Get Supported Property Descriptors
*
* @return Processor Property Descriptors
*/
@Override
public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return DESCRIPTORS;
}
/**
* Get Relationships
*
* @return Processor Relationships
*/
@Override
public Set<Relationship> getRelationships() {
return RELATIONSHIPS;
}
/**
* On Trigger decrypts Flow File contents using configured properties
*
* @param context Process Context
* @param session Process Session
*/
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) {
FlowFile flowFile = session.get();
if (flowFile == null) {
return;
}
final String specificationFormat = context.getProperty(KEY_SPECIFICATION_FORMAT).getValue();
final KeySpecificationFormat keySpecificationFormat = KeySpecificationFormat.valueOf(specificationFormat);
final String cipherTransformation = getCipherTransformation(context);
final Cipher cipher = getCipher(cipherTransformation);
final String algorithmMode = context.getProperty(CIPHER_ALGORITHM_MODE).getValue();
final CipherAlgorithmMode cipherAlgorithmMode = CipherAlgorithmMode.valueOf(algorithmMode);
final KeySpec keySpec = getKeySpec(context, keySpecificationFormat);
final StreamCallback callback = new DecryptCallback(cipher, cipherAlgorithmMode, keySpec);
try {
flowFile = session.write(flowFile, callback);
getLogger().debug("Decryption completed using [{}] {}", cipherTransformation, flowFile);
session.transfer(flowFile, SUCCESS);
} catch (final RuntimeException e) {
getLogger().error("Decryption failed using [{}] {}", cipherTransformation, flowFile, e);
session.transfer(flowFile, FAILURE);
}
}
private String getCipherTransformation(final ProcessContext context) {
final String algorithmMode = context.getProperty(CIPHER_ALGORITHM_MODE).getValue();
final String algorithmPadding = context.getProperty(CIPHER_ALGORITHM_PADDING).getValue();
return String.format(TRANSFORMATION_FORMAT, SYMMETRIC_CIPHER.getValue(), algorithmMode, algorithmPadding);
}
private Cipher getCipher(final String transformation) {
try {
return Cipher.getInstance(transformation);
} catch (final GeneralSecurityException e) {
final String message = String.format("Cipher [%s] not found", transformation);
throw new CipherException(message, e);
}
}
private KeySpec getKeySpec(final ProcessContext context, final KeySpecificationFormat keySpecificationFormat) {
final KeySpec keySpec;
final String keySpecification = context.getProperty(KEY_SPECIFICATION).getValue();
if (KeySpecificationFormat.RAW == keySpecificationFormat) {
final byte[] decodedKey = Hex.decode(keySpecification);
keySpec = new SecretKeySpec(decodedKey, SYMMETRIC_CIPHER.getValue());
} else {
final char[] password = keySpecification.toCharArray();
keySpec = new PBEKeySpec(password);
}
return keySpec;
}
private interface SerializedParameterSpec {
byte[] getParameters();
}
private static class GCMSerializedParameterSpec extends GCMParameterSpec implements SerializedParameterSpec {
private static final int GCM_TAG_LENGTH_BITS = 128;
private final byte[] parameters;
private GCMSerializedParameterSpec(final byte[] iv, final byte[] parameters) {
super(GCM_TAG_LENGTH_BITS, iv);
this.parameters = parameters;
}
@Override
public byte[] getParameters() {
return parameters;
}
}
private static class IvSerializedParameterSpec extends IvParameterSpec implements SerializedParameterSpec {
private final byte[] parameters;
private IvSerializedParameterSpec(final byte[] iv, final byte[] parameters) {
super(iv);
this.parameters = parameters;
}
@Override
public byte[] getParameters() {
return parameters;
}
}
private static class DecryptCallback extends DecryptStreamCallback {
private static final int PARAMETERS_BUFFER_LENGTH = 256;
private static final int DERIVED_KEY_LENGTH_BYTES = 16;
private static final int END_OF_FILE = -1;
private static final byte[] EMPTY_BYTES = {};
private static final DetectedDerivedKeyParameterSpecReader parameterSpecReader = new DetectedDerivedKeyParameterSpecReader();
private static final DetectedDerivedKeyProvider derivedKeyProvider = new DetectedDerivedKeyProvider();
private final CipherAlgorithmMode cipherAlgorithmMode;
private final KeySpec keySpec;
private DecryptCallback(
final Cipher cipher,
final CipherAlgorithmMode cipherAlgorithmMode,
final KeySpec keySpec
) {
super(cipher, PARAMETERS_BUFFER_LENGTH);
this.cipherAlgorithmMode = cipherAlgorithmMode;
this.keySpec = keySpec;
}
/**
* Read Algorithm Parameters including optional salt bytes for derived keys and required initialization vector bytes
*
* @param parameterBuffer Buffer of parameter bytes
* @return Algorithm Parameters Specification based on Cipher Algorithm Mode and parsed bytes
*/
@Override
protected AlgorithmParameterSpec readAlgorithmParameterSpec(final ByteBuffer parameterBuffer) {
final AlgorithmParameterSpec spec;
final byte[] salt = readDelimitedBytes(parameterBuffer, EncodedDelimiter.SALT);
final byte[] iv = readDelimitedBytes(parameterBuffer, EncodedDelimiter.IV);
if (CipherAlgorithmMode.GCM == cipherAlgorithmMode) {
spec = new GCMSerializedParameterSpec(iv, salt);
} else {
spec = new IvSerializedParameterSpec(iv, salt);
}
return spec;
}
/**
* Get Secret Key for cipher operations using either configured hexadecimal key or key derived from password and parameters
*
* @param algorithmParameterSpec Algorithm Parameters Specification
* @return Secret Key for decryption
*/
@Override
protected Key getKey(final AlgorithmParameterSpec algorithmParameterSpec) {
final Key key;
if (keySpec instanceof SecretKeySpec) {
key = (SecretKeySpec) keySpec;
} else if (algorithmParameterSpec instanceof SerializedParameterSpec) {
final SerializedParameterSpec serializedParameterSpec = (SerializedParameterSpec) algorithmParameterSpec;
final byte[] parameters = serializedParameterSpec.getParameters();
key = getDerivedKey(parameters);
} else {
final String message = String.format("Key Derivation Function Parameters not provided [%s]", algorithmParameterSpec.getClass());
throw new IllegalArgumentException(message);
}
return key;
}
private Key getDerivedKey(final byte[] parameters) {
final DerivedKeyParameterSpec derivedKeyParameterSpec = parameterSpecReader.read(parameters);
final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec = getDerivedKeySpec(derivedKeyParameterSpec);
return derivedKeyProvider.getDerivedKey(derivedKeySpec);
}
private DerivedKeySpec<DerivedKeyParameterSpec> getDerivedKeySpec(final DerivedKeyParameterSpec parameterSpec) {
final PBEKeySpec pbeKeySpec = (PBEKeySpec) keySpec;
final char[] password = pbeKeySpec.getPassword();
return new StandardDerivedKeySpec<>(
password,
DERIVED_KEY_LENGTH_BYTES,
SYMMETRIC_CIPHER.getValue(),
parameterSpec
);
}
private byte[] readDelimitedBytes(final ByteBuffer buffer, final EncodedDelimiter encodedDelimiter) {
final byte[] delimiter = encodedDelimiter.getDelimiter();
final int delimiterIndex = ByteBufferSearch.indexOf(buffer, delimiter);
final byte[] delimitedBytes;
if (delimiterIndex == END_OF_FILE) {
delimitedBytes = EMPTY_BYTES;
} else {
final int delimitedLength = delimiterIndex - buffer.position();
delimitedBytes = new byte[delimitedLength];
buffer.get(delimitedBytes);
final int newPosition = delimiterIndex + delimiter.length;
buffer.position(newPosition);
}
return delimitedBytes;
}
}
}

View File

@ -0,0 +1,295 @@
/*
* 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.cipher;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.SideEffectFree;
import org.apache.nifi.annotation.behavior.SupportsBatching;
import org.apache.nifi.annotation.behavior.WritesAttribute;
import org.apache.nifi.annotation.behavior.WritesAttributes;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeEncryptionScheme;
import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeKeyDerivationStrategy;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.processors.cipher.io.DecryptStreamCallback;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@SideEffectFree
@SupportsBatching
@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
@Tags({"cryptography", "decipher", "decrypt", "Jasypt", "OpenSSL", "PKCS5", "PBES1"})
@CapabilityDescription("Decrypt content using password-based encryption schemes with legacy algorithms supporting historical compatibility modes.")
@WritesAttributes({
@WritesAttribute(attribute = CipherAttributeKey.PBE_SCHEME, description = "Password-Based Encryption Scheme"),
@WritesAttribute(attribute = CipherAttributeKey.PBE_SYMMETRIC_CIPHER, description = "Password-Based Encryption Block Cipher"),
@WritesAttribute(attribute = CipherAttributeKey.PBE_DIGEST_ALGORITHM, description = "Password-Based Encryption Digest Algorithm"),
})
public class DecryptContentCompatibility extends AbstractProcessor {
static final PropertyDescriptor ENCRYPTION_SCHEME = new PropertyDescriptor.Builder()
.name("encryption-scheme")
.displayName("Encryption Scheme")
.description("Password-Based Encryption Scheme including PBES1 described in RFC 8018, and others defined according to PKCS12 and Bouncy Castle implementations")
.required(true)
.allowableValues(CompatibilityModeEncryptionScheme.class)
.build();
static final PropertyDescriptor KEY_DERIVATION_STRATEGY = new PropertyDescriptor.Builder()
.name("key-derivation-strategy")
.displayName("Key Derivation Strategy")
.description("Strategy for reading salt from encoded contents and deriving the decryption key according to the number of function iterations")
.required(true)
.allowableValues(CompatibilityModeKeyDerivationStrategy.class)
.build();
static final PropertyDescriptor PASSWORD = new PropertyDescriptor.Builder()
.name("password")
.displayName("Password")
.description("Password required for Password-Based Encryption Schemes")
.required(true)
.sensitive(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
static final Relationship SUCCESS = new Relationship.Builder()
.name("success")
.description("Decryption succeeded")
.build();
static final Relationship FAILURE = new Relationship.Builder()
.name("failure")
.description("Decryption failed")
.build();
private static final List<PropertyDescriptor> DESCRIPTORS = Collections.unmodifiableList(Arrays.asList(
ENCRYPTION_SCHEME,
KEY_DERIVATION_STRATEGY,
PASSWORD
));
private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
SUCCESS,
FAILURE
)));
private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
/**
* Get Supported Property Descriptors
*
* @return Processor Property Descriptors
*/
@Override
public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return DESCRIPTORS;
}
/**
* Get Relationships
*
* @return Processor Relationships
*/
@Override
public Set<Relationship> getRelationships() {
return RELATIONSHIPS;
}
/**
* On Trigger decrypts Flow File contents using configured properties
*
* @param context Process Context
* @param session Process Session
*/
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) {
FlowFile flowFile = session.get();
if (flowFile == null) {
return;
}
final String scheme = context.getProperty(ENCRYPTION_SCHEME).getValue();
final CompatibilityModeEncryptionScheme encryptionScheme = getEncryptionScheme(scheme);
final Cipher cipher = getCipher(scheme);
final char[] password = context.getProperty(PASSWORD).getValue().toCharArray();
final PBEKeySpec keySpec = new PBEKeySpec(password);
final String strategy = context.getProperty(KEY_DERIVATION_STRATEGY).getValue();
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.valueOf(strategy);
final StreamCallback callback = new DecryptCallback(cipher, keySpec, keyDerivationStrategy);
final Map<String, String> attributes = new LinkedHashMap<>();
attributes.put(CipherAttributeKey.PBE_SCHEME, encryptionScheme.getValue());
attributes.put(CipherAttributeKey.PBE_SYMMETRIC_CIPHER, encryptionScheme.getSymmetricCipher().getValue());
attributes.put(CipherAttributeKey.PBE_DIGEST_ALGORITHM, encryptionScheme.getDigestAlgorithm().getValue());
try {
flowFile = session.write(flowFile, callback);
flowFile = session.putAllAttributes(flowFile, attributes);
getLogger().debug("Decryption completed using [{}] {}", scheme, flowFile);
session.transfer(flowFile, SUCCESS);
} catch (final RuntimeException e) {
getLogger().error("Decryption failed using [{}] {}", scheme, flowFile, e);
session.transfer(flowFile, FAILURE);
}
}
private CompatibilityModeEncryptionScheme getEncryptionScheme(final String scheme) {
final Optional<CompatibilityModeEncryptionScheme> encryptionSchemeFound = Arrays.stream(CompatibilityModeEncryptionScheme.values())
.filter(encryptionScheme -> encryptionScheme.getValue().equals(scheme))
.findFirst();
return encryptionSchemeFound.orElseThrow(() -> new IllegalArgumentException(String.format("Encryption Scheme [%s] not found", scheme)));
}
private Cipher getCipher(final String transformation) {
try {
return Cipher.getInstance(transformation, BOUNCY_CASTLE_PROVIDER);
} catch (final GeneralSecurityException e) {
final String message = String.format("Cipher [%s] not found", transformation);
throw new CipherException(message, e);
}
}
private static class DecryptCallback extends DecryptStreamCallback {
private static final byte[] EMPTY_SALT = {};
private static final int BLOCK_SIZE_UNDEFINED = 0;
private final String cipherAlgorithm;
private final int cipherBlockSize;
private final PBEKeySpec keySpec;
private final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy;
private DecryptCallback(
final Cipher cipher,
final PBEKeySpec keySpec,
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy
) {
super(cipher, keyDerivationStrategy.getSaltBufferLength());
this.cipherAlgorithm = cipher.getAlgorithm();
this.cipherBlockSize = cipher.getBlockSize();
this.keySpec = keySpec;
this.keyDerivationStrategy = keyDerivationStrategy;
}
/**
* Get Secret Key generated based on configured key specification
*
* @param algorithmParameterSpec Algorithm Parameters Specification not used
* @return Secret Key
*/
@Override
protected Key getKey(final AlgorithmParameterSpec algorithmParameterSpec) {
final SecretKeyFactory secretKeyFactory = getSecretKeyFactory();
try {
return secretKeyFactory.generateSecret(keySpec);
} catch (final InvalidKeySpecException e) {
final String message = String.format("Generate Secret Key Algorithm [%s] invalid specification", cipherAlgorithm);
throw new CipherException(message, e);
}
}
/**
* Read salt bytes based on configured key derivation strategy
*
* @param parameterBuffer Buffer of parameter bytes
* @return Password-Based Encryption specification with salt and configured iterations
*/
@Override
protected AlgorithmParameterSpec readAlgorithmParameterSpec(final ByteBuffer parameterBuffer) {
final byte[] salt = readSalt(parameterBuffer);
return new PBEParameterSpec(salt, keyDerivationStrategy.getIterations());
}
private byte[] readSalt(final ByteBuffer byteBuffer) {
final byte[] salt;
if (CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY == keyDerivationStrategy) {
salt = readSaltOpenSsl(byteBuffer);
} else {
salt = readSaltStandard(byteBuffer);
}
return salt;
}
private byte[] readSaltOpenSsl(final ByteBuffer byteBuffer) {
final byte[] salt;
final int saltHeaderLength = keyDerivationStrategy.getSaltHeader().length;
final byte[] saltHeader = new byte[saltHeaderLength];
byteBuffer.get(saltHeader);
if (MessageDigest.isEqual(keyDerivationStrategy.getSaltHeader(), saltHeader)) {
salt = new byte[keyDerivationStrategy.getSaltStandardLength()];
byteBuffer.get(salt);
} else {
salt = EMPTY_SALT;
byteBuffer.rewind();
}
return salt;
}
private byte[] readSaltStandard(final ByteBuffer byteBuffer) {
final int saltLength = cipherBlockSize == BLOCK_SIZE_UNDEFINED ? keyDerivationStrategy.getSaltStandardLength() : cipherBlockSize;
final byte[] salt = new byte[saltLength];
byteBuffer.get(salt);
return salt;
}
private SecretKeyFactory getSecretKeyFactory() {
try {
return SecretKeyFactory.getInstance(cipherAlgorithm, BOUNCY_CASTLE_PROVIDER);
} catch (final NoSuchAlgorithmException e) {
final String message = String.format("Secret Key Factory Algorithm [%s] not found", cipherAlgorithm);
throw new CipherException(message, e);
}
}
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.cipher.algorithm;
import org.apache.nifi.components.DescribedValue;
/**
* Cipher Algorithm Modes as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
*/
public enum CipherAlgorithmMode implements DescribedValue {
CBC("Cipher Blocking Chaining Mode"),
CTR("Counter Mode"),
GCM("Galois/Counter Mode supporting Authenticated Encryption with Associated Data");
private final String description;
CipherAlgorithmMode(final String description) {
this.description = description;
}
@Override
public String getValue() {
return name();
}
@Override
public String getDisplayName() {
return name();
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.cipher.algorithm;
import org.apache.nifi.components.DescribedValue;
/**
* Cipher Algorithm Paddings as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
*/
public enum CipherAlgorithmPadding implements DescribedValue {
/** No Padding */
NO_PADDING("NoPadding"),
/** PKCS5 Padding described in RFC 8018 */
PKCS5_PADDING("PKCS5Padding");
private final String padding;
CipherAlgorithmPadding(final String padding) {
this.padding = padding;
}
@Override
public String getValue() {
return padding;
}
@Override
public String getDisplayName() {
return padding;
}
@Override
public String getDescription() {
return padding;
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.cipher.algorithm;
import org.apache.nifi.components.DescribedValue;
/**
* Digest Algorithm as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
*/
public enum DigestAlgorithm implements DescribedValue {
MD5("MD5", "Message Digest Algorithm 5"),
SHA1("SHA-1", "Secure Hash Algorithm 1"),
SHA256("SHA-256", "Secure Hash Algorithm 2 with 256 bits");
private String name;
private String description;
DigestAlgorithm(final String name, final String description) {
this.name = name;
this.description = description;
}
@Override
public String getValue() {
return name;
}
@Override
public String getDisplayName() {
return name;
}
@Override
public String getDescription() {
return name;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.cipher.algorithm;
import org.apache.nifi.components.DescribedValue;
/**
* Symmetric Cipher Algorithm Names as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
*/
public enum SymmetricCipher implements DescribedValue {
AES("AES", "Advanced Encryption Standard defined in FIPS 197"),
DES("DES", "Data Encryption Standard defined in FIPS 46-3 and withdrawn in 2005"),
DESEDE("DESede", "Triple Data Encryption Standard also known as 3DES and deprecated in 2023"),
RC2("RC2", "RSA Rivest Cipher 2 defined in RFC 2268"),
RC4("RC4", "RSA Rivest Cipher 4"),
TWOFISH("TWOFISH", "Twofish Block Cipher");
private final String name;
private final String description;
SymmetricCipher(final String name, final String description) {
this.name = name;
this.description = description;
}
@Override
public String getValue() {
return name;
}
@Override
public String getDisplayName() {
return name;
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,216 @@
/*
* 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.cipher.compatibility;
import org.apache.nifi.processors.cipher.algorithm.SymmetricCipher;
import org.apache.nifi.processors.cipher.algorithm.DigestAlgorithm;
import org.apache.nifi.components.DescribedValue;
import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.AES;
import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.DES;
import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.DESEDE;
import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.RC2;
import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.RC4;
import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.TWOFISH;
/**
* Compatibility Mode Encryption Schemes supporting decryption using legacy algorithms such as PBES1 defined in RFC 8018 Section 6.1
*/
public enum CompatibilityModeEncryptionScheme implements DescribedValue {
PBE_WITH_MD5_AND_AES_CBC_128(
"PBEWITHMD5AND128BITAES-CBC-OPENSSL",
DigestAlgorithm.MD5,
AES,
"PKCS12 with MD5 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 128 bit keys."
),
PBE_WITH_MD5_AND_AES_CBC_192(
"PBEWITHMD5AND192BITAES-CBC-OPENSSL",
DigestAlgorithm.MD5,
AES,
"PKCS12 with MD5 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 192 bit keys."
),
PBE_WITH_MD5_AND_AES_CBC_256(
"PBEWITHMD5AND256BITAES-CBC-OPENSSL",
DigestAlgorithm.MD5,
AES,
"PKCS12 with MD5 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 256 bit keys."
),
PBE_WITH_MD5_AND_DES(
"PBEWITHMD5ANDDES",
DigestAlgorithm.MD5,
DES,
"PKCS5 Scheme 1 with MD5 digest and Data Encryption Standard 64 bit keys. OID 1.2.840.113549.1.5.3"
),
PBE_WITH_MD5_AND_RC2(
"PBEWITHMD5ANDRC2",
DigestAlgorithm.MD5,
RC2,
"PKCS Scheme 1 with MD5 digest and Rivest Cipher 2. OID 1.2.840.113549.1.5.6"
),
PBE_WITH_SHA1_AND_AES_CBC_128(
"PBEWITHSHAAND128BITAES-CBC-BC",
DigestAlgorithm.SHA1,
AES,
"PKCS12 with SHA-1 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 128 bit keys."
),
PBE_WITH_SHA1_AND_AES_CBC_192(
"PBEWITHSHAAND192BITAES-CBC-BC",
DigestAlgorithm.SHA1,
AES,
"PKCS12 with SHA-1 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 192 bit keys."
),
PBE_WITH_SHA1_AND_AES_CBC_256(
"PBEWITHSHAAND256BITAES-CBC-BC",
DigestAlgorithm.SHA1,
AES,
"PKCS12 with SHA-1 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 256 bit keys."
),
PBE_WITH_SHA1_AND_DES(
"PBEWITHSHA1ANDDES",
DigestAlgorithm.SHA1,
DES,
"PKCS5 Scheme 1 with SHA-1 digest and Data Encryption Standard. OID 1.2.840.113549.1.5.10"
),
PBE_WITH_SHA1_AND_DESEDE_128(
"PBEWITHSHAAND2-KEYTRIPLEDES-CBC",
DigestAlgorithm.SHA1,
DESEDE,
"PKCS12 with SHA-1 digest and Triple Data Encryption Standard 128 bit keys. OID 1.2.840.113549.1.12.1.4"
),
PBE_WITH_SHA1_AND_DESEDE_192(
"PBEWITHSHAAND3-KEYTRIPLEDES-CBC",
DigestAlgorithm.SHA1,
DESEDE,
"PKCS12 with SHA-1 digest and Triple Data Encryption Standard 192 bit keys. OID 1.2.840.113549.1.12.1.3"
),
PBE_WITH_SHA1_AND_RC2(
"PBEWITHSHA1ANDRC2",
DigestAlgorithm.SHA1,
RC2,
"PKCS5 Scheme 1 with SHA-1 digest and Rivest Cipher 2. OID 1.2.840.113549.1.5.11"
),
PBE_WITH_SHA1_AND_RC2_128(
"PBEWITHSHAAND128BITRC2-CBC",
DigestAlgorithm.SHA1,
RC2,
"PKCS12 with SHA-1 digest and Rivest Cipher 2 128 bit keys. OID 1.2.840.113549.1.12.1.5"
),
PBE_WITH_SHA1_AND_RC2_40(
"PBEWITHSHAAND40BITRC2-CBC",
DigestAlgorithm.SHA1,
RC2,
"PKCS12 with SHA-1 digest and Rivest Cipher 2 40 bit keys. OID 1.2.840.113549.1.12.1.6"
),
PBE_WITH_SHA1_AND_RC4_128(
"PBEWITHSHAAND128BITRC4",
DigestAlgorithm.SHA1,
RC4,
"PKCS12 with SHA-1 digest and Rivest Cipher 4 128 bit keys. OID 1.2.840.113549.1.12.1.1"
),
PBE_WITH_SHA1_AND_RC4_40(
"PBEWITHSHAAND40BITRC4",
DigestAlgorithm.SHA1,
RC4,
"PKCS12 with SHA-1 digest and Rivest Cipher 4 40 bit keys. OID 1.2.840.113549.1.12.1.2"
),
PBE_WITH_SHA1_AND_TWOFISH(
"PBEWITHSHAANDTWOFISH-CBC",
DigestAlgorithm.SHA1,
TWOFISH,
"PKCS12 with SHA-1 digest and Twofish in Cipher Block Chaining mode using 256 bit keys."
),
PBE_WITH_SHA256_AND_AES_CBC_128(
"PBEWITHSHA256AND128BITAES-CBC-BC",
DigestAlgorithm.SHA256,
AES,
"PKCS12 with SHA-256 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 128 bit keys."
),
PBE_WITH_SHA256_AND_AES_CBC_192(
"PBEWITHSHA256AND192BITAES-CBC-BC",
DigestAlgorithm.SHA256,
AES,
"PKCS12 with SHA-256 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 192 bit keys."
),
PBE_WITH_SHA256_AND_AES_CBC_256(
"PBEWITHSHA256AND256BITAES-CBC-BC",
DigestAlgorithm.SHA256,
AES,
"PKCS12 with SHA-256 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 256 bit keys."
);
private final String algorithm;
private final String description;
private final DigestAlgorithm digestAlgorithm;
private final SymmetricCipher symmetricCipher;
CompatibilityModeEncryptionScheme(
final String algorithm,
final DigestAlgorithm digestAlgorithm,
final SymmetricCipher symmetricCipher,
final String description
) {
this.algorithm = algorithm;
this.description = description;
this.digestAlgorithm = digestAlgorithm;
this.symmetricCipher = symmetricCipher;
}
@Override
public String getValue() {
return algorithm;
}
@Override
public String getDisplayName() {
return name();
}
@Override
public String getDescription() {
return description;
}
public DigestAlgorithm getDigestAlgorithm() {
return digestAlgorithm;
}
public SymmetricCipher getSymmetricCipher() {
return symmetricCipher;
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.cipher.compatibility;
import org.apache.nifi.components.DescribedValue;
import java.nio.charset.StandardCharsets;
/**
* Compatibility Mode Key Derivation Strategy supporting decryption with legacy schemes
*/
public enum CompatibilityModeKeyDerivationStrategy implements DescribedValue {
/** Strategy based on OpenSSL EVP_BytesToKey function */
OPENSSL_EVP_BYTES_TO_KEY(
"OpenSSL Envelope BytesToKey using a digest algorithm with one iteration and optional salt of eight bytes",
0,
8,
16,
"Salted__".getBytes(StandardCharsets.US_ASCII)
),
/** Strategy based on default configuration of org.jasypt.encryption.pbe.StandardPBEByteEncryptor */
JASYPT_STANDARD(
"Jasypt Java Simplified Encryption using a digest algorithm with 1000 iterations and required salt of eight or sixteen bytes",
1000,
8,
16,
new byte[0]
);
private final String description;
private final int iterations;
private final byte[] saltHeader;
private final int saltStandardLength;
private final int saltBufferLength;
CompatibilityModeKeyDerivationStrategy(
final String description,
final int iterations,
final int saltStandardLength,
final int saltBufferLength,
final byte[] saltHeader
) {
this.description = description;
this.iterations = iterations;
this.saltStandardLength = saltStandardLength;
this.saltBufferLength = saltBufferLength;
this.saltHeader = saltHeader;
}
@Override
public String getValue() {
return name();
}
@Override
public String getDisplayName() {
return name();
}
@Override
public String getDescription() {
return description;
}
public int getIterations() {
return iterations;
}
public int getSaltStandardLength() {
return saltStandardLength;
}
public int getSaltBufferLength() {
return saltBufferLength;
}
public byte[] getSaltHeader() {
return saltHeader;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.cipher.encoded;
import org.apache.nifi.components.DescribedValue;
import java.nio.charset.StandardCharsets;
/**
* Delimiters for content encoded according to conventions added in NiFi 0.5.0
*/
public enum EncodedDelimiter implements DescribedValue {
SALT("NiFiSALT", "Eight bytes appended to a stream after the salt bytes according to NiFi 0.5.0 conventions"),
IV("NiFiIV", "Six bytes appended to a stream after the initialization vector bytes according to NiFi 0.5.0 conventions");
private final byte[] delimiter;
private final String description;
EncodedDelimiter(
final String delimiterEncoded,
final String description
) {
this.delimiter = delimiterEncoded.getBytes(StandardCharsets.UTF_8);
this.description = description;
}
@Override
public String getValue() {
return name();
}
@Override
public String getDisplayName() {
return name();
}
@Override
public String getDescription() {
return description;
}
public byte[] getDelimiter() {
return delimiter;
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.cipher.encoded;
import org.apache.nifi.components.DescribedValue;
/**
* Key Specification Format supporting raw and derived secret keys
*/
public enum KeySpecificationFormat implements DescribedValue {
RAW("Raw secret key provided as a hexadecimal string"),
PASSWORD("Password string for use with a Key Derivation Function to produce a secret key");
private final String description;
KeySpecificationFormat(final String description) {
this.description = description;
}
@Override
public String getValue() {
return name();
}
@Override
public String getDisplayName() {
return name();
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.cipher.io;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processors.cipher.CipherException;
import javax.crypto.Cipher;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
/**
* Decrypt Stream Callback implements shared methods for cipher operations and parameter buffer processing
*/
public abstract class DecryptStreamCallback implements StreamCallback {
private static final int BUFFER_LENGTH = 4096;
private static final int BUFFER_INPUT_OFFSET = 0;
private static final int END_OF_FILE = -1;
private final Cipher cipher;
private final int parameterBufferLength;
public DecryptStreamCallback(
final Cipher cipher,
final int parameterBufferLength
) {
this.cipher = cipher;
this.parameterBufferLength = parameterBufferLength;
}
/**
* Process streams with parsed algorithm parameters and cipher initialization
*
* @param inputStream Stream of encrypted bytes
* @param outputStream Stream of decrypted bytes
* @throws IOException Thrown on read or write failures
*/
@Override
public void process(final InputStream inputStream, final OutputStream outputStream) throws IOException {
final ByteBuffer parameterBuffer = readParameterBuffer(inputStream);
final AlgorithmParameterSpec algorithmParameterSpec = readAlgorithmParameterSpec(parameterBuffer);
final Key key = getKey(algorithmParameterSpec);
initCipher(key, algorithmParameterSpec);
processBuffer(parameterBuffer, outputStream);
processStream(inputStream, outputStream);
}
/**
* Read Cipher Algorithm Parameters from initial buffer of parameter bytes
*
* @param parameterBuffer Buffer of parameter bytes
* @return Algorithm Parameters Specification
*/
protected abstract AlgorithmParameterSpec readAlgorithmParameterSpec(final ByteBuffer parameterBuffer);
/**
* Get Key for Cipher operations using Algorithm Parameters when needed
*
* @param algorithmParameterSpec Algorithm Parameters Specification
* @return Key for decryption processing
*/
protected abstract Key getKey(final AlgorithmParameterSpec algorithmParameterSpec);
private void initCipher(final Key key, final AlgorithmParameterSpec algorithmParameterSpec) {
try {
cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameterSpec);
} catch (final GeneralSecurityException e) {
final String message = String.format("Cipher [%s] initialization failed", cipher.getAlgorithm());
throw new CipherException(message, e);
}
}
private ByteBuffer readParameterBuffer(final InputStream inputStream) throws IOException {
final byte[] buffer = new byte[parameterBufferLength];
final int read = inputStream.read(buffer);
if (read == END_OF_FILE) {
throw new EOFException("Read parameters buffer failed");
}
return ByteBuffer.wrap(buffer, BUFFER_INPUT_OFFSET, read);
}
private void processBuffer(final ByteBuffer byteBuffer, final OutputStream outputStream) throws IOException {
if (byteBuffer.hasRemaining()) {
final int remaining = byteBuffer.remaining();
final byte[] buffer = new byte[remaining];
byteBuffer.get(buffer);
processBytes(buffer, remaining, outputStream);
}
}
private void processBytes(final byte[] buffer, final int length, final OutputStream outputStream) throws IOException {
final byte[] deciphered = cipher.update(buffer, BUFFER_INPUT_OFFSET, length);
if (deciphered != null) {
outputStream.write(deciphered);
}
}
private void processStream(final InputStream inputStream, final OutputStream outputStream) throws IOException {
final byte[] buffer = new byte[BUFFER_LENGTH];
int read;
while ((read = inputStream.read(buffer)) != END_OF_FILE) {
processBytes(buffer, read, outputStream);
}
try {
final byte[] deciphered = cipher.doFinal();
outputStream.write(deciphered);
} catch (final GeneralSecurityException e) {
final String message = String.format("Cipher [%s] verification failed", cipher.getAlgorithm());
throw new CipherException(message, e);
}
}
}

View File

@ -0,0 +1,16 @@
# 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.
org.apache.nifi.processors.cipher.DecryptContentCompatibility
org.apache.nifi.processors.cipher.DecryptContent

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<!--
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.
-->
<head>
<meta charset="UTF-8">
<title>DecryptContent</title>
<link rel="stylesheet" href="../../../../../css/component-usage.css" type="text/css" />
</head>
<body>
<h2>Summary</h2>
<p>
This Processor supports decryption using the Advanced Encryption Standard algorithm with modern key derivation
functions and formats defined in the EncryptContent Processor.
Decryption processing is capable of selecting the necessary key derivation function based on reading the header
bytes of input files. The Processor supports password-based decryption or key-based decryption depending on
configured properties.
</p>
<h2>Configuration</h2>
<p>
This Processor can be configured to decrypt information encrypted using the EncryptContent Processor. Successful
decryption requires selecting matching algorithm and password properties.
</p>
<h3>Cipher Algorithm Property Values</h3>
<table>
<tbody>
<tr>
<th>Cipher Algorithm Mode</th>
<th>Cipher Algorithm Padding</th>
<th>EncryptContent Encryption Algorithm</th>
</tr>
<tr>
<td>CBC</td>
<td>NoPadding</td>
<td>AES_CBC_NO_PADDING</td>
</tr>
<tr>
<td>CBC</td>
<td>PKCS5Padding</td>
<td>AES_CBC</td>
</tr>
<tr>
<td>CTR</td>
<td>NoPadding</td>
<td>AES_CTR</td>
</tr>
<tr>
<td>GCM</td>
<td>NoPadding</td>
<td>AES_GCM</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<!--
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.
-->
<head>
<meta charset="UTF-8">
<title>DecryptContentCompatibility</title>
<link rel="stylesheet" href="../../../../../css/component-usage.css" type="text/css" />
</head>
<body>
<h2>Summary</h2>
<p>
This Processor supports decryption using legacy formats and Password-Based Encryption Algorithms.
<a href="https://www.rfc-editor.org/rfc/rfc8018" target="_blank">RFC 8018</a> defines the Password-Based Cryptography
Specification Version 2.1, including several of the supported encryption schemes described in Section 6.1
as PBES1. According to the specification, PBES1 is not recommended for new applications, and this Processor
exists for the purpose of providing compatibility with historical information. The supported key derivation
strategies align with implementations in OpenSSL and the Java Simplified Encryption library.
</p>
<h2>Configuration</h2>
<p>
This Processor can be configured to decrypt information encrypted using the EncryptContent Processor. Successful
decryption requires selecting matching algorithm and password properties.
</p>
<h3>Key Derivation Property Values</h3>
<table>
<tbody>
<tr>
<th>DecryptContentCompatibility</th>
<th>EncryptContent</th>
</tr>
<tr>
<td>JASYPT_STANDARD</td>
<td>NiFi Legacy KDF</td>
</tr>
<tr>
<td>OPENSSL_EVP_BYTES_TO_KEY</td>
<td>OpenSSL EVP_BytesToKey</td>
</tr>
</tbody>
</table>
<h3>Encryption Scheme Property Values</h3>
<table>
<tbody>
<tr>
<th>DecryptContentCompatibility</th>
<th>EncryptContent</th>
</tr>
<tr>
<td>PBE_WITH_MD5_AND_AES_CBC_128</td>
<td>MD5_128AES</td>
</tr>
<tr>
<td>PBE_WITH_MD5_AND_AES_CBC_192</td>
<td>MD5_192AES</td>
</tr>
<tr>
<td>PBE_WITH_MD5_AND_AES_CBC_256</td>
<td>MD5_256AES</td>
</tr>
<tr>
<td>PBE_WITH_MD5_AND_DES</td>
<td>MD5_DES</td>
</tr>
<tr>
<td>PBE_WITH_MD5_AND_RC2</td>
<td>MD5_RC2</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_AES_CBC_128</td>
<td>SHA_128AES</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_AES_CBC_192</td>
<td>SHA_192AES</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_AES_CBC_256</td>
<td>SHA_256AES</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_DES</td>
<td>SHA1_DES</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_DESEDE_128</td>
<td>SHA_2KEYTRIPLEDES</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_DESEDE_192</td>
<td>SHA_3KEYTRIPLEDES</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_RC2</td>
<td>SHA1_RC2</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_RC2_40</td>
<td>SHA_40RC2</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_RC2_128</td>
<td>SHA_128RC2</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_RC4_40</td>
<td>SHA_40RC4</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_RC4_128</td>
<td>SHA_128RC4</td>
</tr>
<tr>
<td>PBE_WITH_SHA1_AND_TWOFISH</td>
<td>SHA_TWOFISH</td>
</tr>
<tr>
<td>PBE_WITH_SHA256_AND_AES_CBC_128</td>
<td>SHA256_128AES</td>
</tr>
<tr>
<td>PBE_WITH_SHA256_AND_AES_CBC_192</td>
<td>SHA256_192AES</td>
</tr>
<tr>
<td>PBE_WITH_SHA256_AND_AES_CBC_256</td>
<td>SHA256_256AES</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,255 @@
/*
* 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.cipher;
import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeEncryptionScheme;
import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeKeyDerivationStrategy;
import org.apache.nifi.util.LogMessage;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertTrue;
class DecryptContentCompatibilityTest {
private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
private static final String PASSWORD = "password";
private static final byte[] RAW = DecryptContentCompatibility.class.getSimpleName().getBytes(StandardCharsets.UTF_8);
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final CompatibilityModeEncryptionScheme SIMPLE_ENCRYPTION_SCHEME = CompatibilityModeEncryptionScheme.PBE_WITH_MD5_AND_DES;
private static final byte[] EMPTY_SALT = {};
private static final byte[] OPENSSL_SALT = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
private static final int BLOCK_SIZE_UNDEFINED = 0;
private static final byte[] WORD = {'W', 'O', 'R', 'D'};
private static final int WORD_COUNT = 2048;
TestRunner runner;
@BeforeEach
void setRunner() {
runner = TestRunners.newTestRunner(DecryptContentCompatibility.class);
}
@ParameterizedTest
@EnumSource(CompatibilityModeEncryptionScheme.class)
void testRunKeyDerivationOpenSslUnsalted(final CompatibilityModeEncryptionScheme encryptionScheme) throws Exception {
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY;
runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, encryptionScheme.getValue());
runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
final Cipher cipher = getCipher(encryptionScheme);
final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, EMPTY_SALT, RAW);
assertDecrypted(RAW, encrypted, encryptionScheme);
}
@ParameterizedTest
@EnumSource(CompatibilityModeEncryptionScheme.class)
void testRunKeyDerivationOpenSslSalted(final CompatibilityModeEncryptionScheme encryptionScheme) throws Exception {
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY;
runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, encryptionScheme.getValue());
runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
final Cipher cipher = getCipher(encryptionScheme);
final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, OPENSSL_SALT, RAW);
assertDecrypted(RAW, encrypted, encryptionScheme);
}
@Test
void testRunKeyDerivationOpenSslSaltedStream() throws Exception {
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY;
runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, SIMPLE_ENCRYPTION_SCHEME.getValue());
runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
final Cipher cipher = getCipher(SIMPLE_ENCRYPTION_SCHEME);
final byte[] words = getWords();
final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, OPENSSL_SALT, words);
assertDecrypted(words, encrypted, SIMPLE_ENCRYPTION_SCHEME);
}
@ParameterizedTest
@EnumSource(CompatibilityModeEncryptionScheme.class)
void testRunKeyDerivationJasyptStandard(final CompatibilityModeEncryptionScheme encryptionScheme) throws Exception {
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD;
runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, encryptionScheme.getValue());
runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
final Cipher cipher = getCipher(encryptionScheme);
final byte[] salt = getSalt(cipher, keyDerivationStrategy);
final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, salt, RAW);
assertDecrypted(RAW, encrypted, encryptionScheme);
}
@Test
void testRunKeyDerivationJasyptStandardSaltMissing() throws Exception {
setSimpleEncryptionScheme();
final Cipher cipher = getCipher(SIMPLE_ENCRYPTION_SCHEME);
final byte[] encrypted = getEncrypted(cipher, CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD, EMPTY_SALT, RAW);
runner.enqueue(encrypted);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.FAILURE);
assertErrorLogged();
}
@Test
void testRunKeyDerivationJasyptStandardFileEnd() {
setSimpleEncryptionScheme();
runner.enqueue(EMPTY_SALT);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.FAILURE);
assertErrorLogged();
}
@Test
void testRunKeyDerivationJasyptStandardSaltLengthMissing() {
setSimpleEncryptionScheme();
runner.enqueue(WORD);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.FAILURE);
assertErrorLogged();
}
private void setSimpleEncryptionScheme() {
runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD.getValue());
runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, SIMPLE_ENCRYPTION_SCHEME.getValue());
runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
}
private void assertErrorLogged() {
final List<LogMessage> errorMessages = runner.getLogger().getErrorMessages();
final Optional<LogMessage> firstErrorMessage = errorMessages.stream().findFirst();
assertTrue(firstErrorMessage.isPresent());
final LogMessage errorLogMessage = firstErrorMessage.get();
final String message = errorLogMessage.getMsg();
assertTrue(message.contains(SIMPLE_ENCRYPTION_SCHEME.getValue()));
}
private void assertDecrypted(final byte[] expected, final byte[] encrypted, final CompatibilityModeEncryptionScheme encryptionScheme) throws IOException {
runner.enqueue(encrypted);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.SUCCESS);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(DecryptContentCompatibility.SUCCESS).get(0);
flowFile.assertContentEquals(expected);
flowFile.assertAttributeEquals(CipherAttributeKey.PBE_SCHEME, encryptionScheme.getValue());
flowFile.assertAttributeEquals(CipherAttributeKey.PBE_SYMMETRIC_CIPHER, encryptionScheme.getSymmetricCipher().getValue());
flowFile.assertAttributeEquals(CipherAttributeKey.PBE_DIGEST_ALGORITHM, encryptionScheme.getDigestAlgorithm().getValue());
}
private byte[] getSalt(final Cipher cipher, final CompatibilityModeKeyDerivationStrategy strategy) {
final int blockSize = cipher.getBlockSize();
final int saltLength = blockSize == BLOCK_SIZE_UNDEFINED ? strategy.getSaltStandardLength() : blockSize;
final byte[] salt = new byte[saltLength];
SECURE_RANDOM.nextBytes(salt);
return salt;
}
private byte[] getEncrypted(
final Cipher cipher,
final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy,
final byte[] salt,
final byte[] raw
) throws GeneralSecurityException, IOException {
initCipher(cipher, keyDerivationStrategy, salt);
final byte[] encrypted = cipher.doFinal(raw);
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
if (CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD == keyDerivationStrategy || salt.length == 0) {
outputStream.write(salt);
} else {
outputStream.write(keyDerivationStrategy.getSaltHeader());
outputStream.write(salt);
}
outputStream.write(encrypted);
return outputStream.toByteArray();
}
private Cipher getCipher(final CompatibilityModeEncryptionScheme encryptionScheme) throws GeneralSecurityException {
final String algorithm = encryptionScheme.getValue();
return Cipher.getInstance(algorithm, BOUNCY_CASTLE_PROVIDER);
}
private void initCipher(final Cipher cipher, final CompatibilityModeKeyDerivationStrategy strategy, final byte[] salt) throws GeneralSecurityException {
final String algorithm = cipher.getAlgorithm();
final Key key = getKey(algorithm);
final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, strategy.getIterations());
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
}
private Key getKey(final String algorithm) throws GeneralSecurityException {
final SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, BOUNCY_CASTLE_PROVIDER);
final PBEKeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray());
return secretKeyFactory.generateSecret(keySpec);
}
private byte[] getWords() throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (int i = 0; i < WORD_COUNT; i++) {
outputStream.write(WORD);
}
return outputStream.toByteArray();
}
}

View File

@ -0,0 +1,236 @@
/*
* 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.cipher;
import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmMode;
import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmPadding;
import org.apache.nifi.processors.cipher.encoded.EncodedDelimiter;
import org.apache.nifi.processors.cipher.encoded.KeySpecificationFormat;
import org.apache.nifi.util.LogMessage;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.bouncycastle.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertTrue;
class DecryptContentTest {
private static final byte[] WORD = {'W', 'O', 'R', 'D'};
private static final byte[] INITIALIZATION_VECTOR = {'I', 'N', 'I', 'T', 'I', 'A', 'L', 'I', 'Z', 'A', 'T', 'I', 'O', 'N', 'I', 'V'};
private static final String HEXADECIMAL_KEY_SPECIFICATION = "0123456789abcdef0123456789abcdef";
private static final String SECRET_KEY_SPECIFICATION = "ContentEncrypted";
private static final String UNENCRYPTED = "unencrypted";
private static final String ARGON2_PARAMETERS = "$argon2id$v=19$m=65536,t=5,p=8$OCXIIeGgaovfpBZXCDxGDg";
private static final String ARGON2_IV_ENCODED = "veGFZ+EWI0qShgT5AUJ+Qg==";
private static final String ARGON2_CIPHERED_ENCODED = "I32fSCCeZEkKFwP04MtnK9rbL283yXBTis4T";
private static final String BCRYPT_PARAMETERS = "$2a$12$JhHkHwk6ojTSWn9seyQV2O";
private static final String BCRYPT_IV_ENCODED = "8bDMMSPI+dKfNL3iC3hBow==";
private static final String BCRYPT_CIPHERED_ENCODED = "IeNNMCulpLoehwKg/A0e5dhIztA6fpqtBENl";
private static final String PBKDF2_SALT_ENCODED = "yoHJ1TbaLMc9qpDNAhV5bQ==";
private static final String PBKDF2_IV_ENCODED = "tgN6TnR6EbXnjKUBT4mq6Q==";
private static final String PBKDF2_CIPHERED_ENCODED = "++5mqUPqtG4bXNwJ7ruq4cSWIncOpFhiQRDR";
private static final String SCRYPT_PARAMETERS = "$s0$e0801$RWV0Tnr5u0fbQ3oPsHG9FA";
private static final String SCRYPT_IV_ENCODED = "oZxuUWK5le9eCkkckKoLhw==";
private static final String SCRYPT_CIPHERED_ENCODED = "Z4de2Bo+Zs5tpcgSp8jasXuea8tJl+vj6wqh";
private static final String HEXADECIMAL_KEY_IV_ENCODED = "z5sz+mXn3GdEWq/qvFUbvw==";
private static final String HEXADECIMAL_KEY_CIPHERED_ENCODED = "GtpE6cPs8zSYO2U=";
private static final Base64.Decoder decoder = Base64.getDecoder();
TestRunner runner;
@BeforeEach
void setRunner() {
runner = TestRunners.newTestRunner(DecryptContent.class);
}
@Test
void testRunInitializationVectorNotFound() {
runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.PASSWORD.getValue());
runner.setProperty(DecryptContent.KEY_SPECIFICATION, SECRET_KEY_SPECIFICATION);
runner.enqueue(WORD);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContent.FAILURE);
assertErrorLogged();
}
@Test
void testRunInputNotFound() {
runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.RAW.getValue());
runner.setProperty(DecryptContent.KEY_SPECIFICATION, HEXADECIMAL_KEY_SPECIFICATION);
final byte[] bytes = Arrays.concatenate(INITIALIZATION_VECTOR, EncodedDelimiter.IV.getDelimiter());
runner.enqueue(bytes);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContent.FAILURE);
assertErrorLogged();
}
@Test
void testRunInputInvalid() {
runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.RAW.getValue());
runner.setProperty(DecryptContent.KEY_SPECIFICATION, HEXADECIMAL_KEY_SPECIFICATION);
final byte[] bytes = Arrays.concatenate(INITIALIZATION_VECTOR, EncodedDelimiter.IV.getDelimiter(), INITIALIZATION_VECTOR);
runner.enqueue(bytes);
runner.run();
runner.assertAllFlowFilesTransferred(DecryptContent.FAILURE);
assertErrorLogged();
}
@Test
void testRunAesGcmNoPaddingPasswordArgon2() throws IOException {
setGcmNoPaddingPassword();
final byte[] encryptedBytes = getEncryptedBytes(ARGON2_PARAMETERS.getBytes(StandardCharsets.UTF_8), ARGON2_IV_ENCODED, ARGON2_CIPHERED_ENCODED);
runner.enqueue(encryptedBytes);
runner.run();
assertDecryptedSuccess();
}
@Test
void testRunAesGcmNoPaddingPasswordBcrypt() throws IOException {
setGcmNoPaddingPassword();
final byte[] encryptedBytes = getEncryptedBytes(BCRYPT_PARAMETERS.getBytes(StandardCharsets.UTF_8), BCRYPT_IV_ENCODED, BCRYPT_CIPHERED_ENCODED);
runner.enqueue(encryptedBytes);
runner.run();
assertDecryptedSuccess();
}
@Test
void testRunAesGcmNoPaddingPasswordPbkdf2() throws IOException {
setGcmNoPaddingPassword();
final byte[] encryptedBytes = getEncryptedBytes(decoder.decode(PBKDF2_SALT_ENCODED), PBKDF2_IV_ENCODED, PBKDF2_CIPHERED_ENCODED);
runner.enqueue(encryptedBytes);
runner.run();
assertDecryptedSuccess();
}
@Test
void testRunAesGcmNoPaddingPasswordScrypt() throws IOException {
setGcmNoPaddingPassword();
final byte[] encryptedBytes = getEncryptedBytes(SCRYPT_PARAMETERS.getBytes(StandardCharsets.UTF_8), SCRYPT_IV_ENCODED, SCRYPT_CIPHERED_ENCODED);
runner.enqueue(encryptedBytes);
runner.run();
assertDecryptedSuccess();
}
@Test
void testRunAesCtrNoPaddingRaw() throws IOException {
runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.RAW.getValue());
runner.setProperty(DecryptContent.KEY_SPECIFICATION, HEXADECIMAL_KEY_SPECIFICATION);
runner.setProperty(DecryptContent.CIPHER_ALGORITHM_MODE, CipherAlgorithmMode.CTR.getValue());
runner.setProperty(DecryptContent.CIPHER_ALGORITHM_PADDING, CipherAlgorithmPadding.NO_PADDING.getValue());
final byte[] encryptedBytes = getHexadecimalKeyEncryptedBytes();
runner.enqueue(encryptedBytes);
runner.run();
assertDecryptedSuccess();
}
private void setGcmNoPaddingPassword() {
runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.PASSWORD.getValue());
runner.setProperty(DecryptContent.KEY_SPECIFICATION, SECRET_KEY_SPECIFICATION);
runner.setProperty(DecryptContent.CIPHER_ALGORITHM_MODE, CipherAlgorithmMode.GCM.getValue());
runner.setProperty(DecryptContent.CIPHER_ALGORITHM_PADDING, CipherAlgorithmPadding.NO_PADDING.getValue());
}
private void assertDecryptedSuccess() {
runner.assertAllFlowFilesTransferred(DecryptContent.SUCCESS);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(DecryptContent.SUCCESS).get(0);
flowFile.assertContentEquals(UNENCRYPTED);
}
private void assertErrorLogged() {
final List<LogMessage> errorMessages = runner.getLogger().getErrorMessages();
final Optional<LogMessage> firstErrorMessage = errorMessages.stream().findFirst();
assertTrue(firstErrorMessage.isPresent());
}
private byte[] getEncryptedBytes(
final byte[] serializedParameters,
final String initializationVectorEncoded,
final String cipheredEncoded
) throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(serializedParameters);
outputStream.write(EncodedDelimiter.SALT.getDelimiter());
final byte[] initializationVector = decoder.decode(initializationVectorEncoded);
outputStream.write(initializationVector);
outputStream.write(EncodedDelimiter.IV.getDelimiter());
final byte[] ciphered = decoder.decode(cipheredEncoded);
outputStream.write(ciphered);
return outputStream.toByteArray();
}
private byte[] getHexadecimalKeyEncryptedBytes() throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final byte[] initializationVector = decoder.decode(HEXADECIMAL_KEY_IV_ENCODED);
outputStream.write(initializationVector);
outputStream.write(EncodedDelimiter.IV.getDelimiter());
final byte[] ciphered = decoder.decode(HEXADECIMAL_KEY_CIPHERED_ENCODED);
outputStream.write(ciphered);
return outputStream.toByteArray();
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-nar-bundles</artifactId>
<version>1.20.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-cipher-bundle</artifactId>
<packaging>pom</packaging>
<modules>
<module>nifi-cipher-processors</module>
<module>nifi-cipher-nar</module>
</modules>
</project>

View File

@ -123,6 +123,7 @@
<module>nifi-iceberg-bundle</module>
<module>nifi-jslt-bundle</module>
<module>nifi-iotdb-bundle</module>
<module>nifi-cipher-bundle</module>
</modules>
<build>