mirror of https://github.com/apache/nifi.git
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:
parent
d9f35b8974
commit
0c676b9633
|
@ -235,6 +235,12 @@ language governing permissions and limitations under the License. -->
|
||||||
<version>1.20.0-SNAPSHOT</version>
|
<version>1.20.0-SNAPSHOT</version>
|
||||||
<type>nar</type>
|
<type>nar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-cipher-nar</artifactId>
|
||||||
|
<version>1.20.0-SNAPSHOT</version>
|
||||||
|
<type>nar</type>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.nifi</groupId>
|
<groupId>org.apache.nifi</groupId>
|
||||||
<artifactId>nifi-distributed-cache-services-nar</artifactId>
|
<artifactId>nifi-distributed-cache-services-nar</artifactId>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,7 @@
|
||||||
<module>nifi-record-path</module>
|
<module>nifi-record-path</module>
|
||||||
<module>nifi-repository-encryption</module>
|
<module>nifi-repository-encryption</module>
|
||||||
<module>nifi-schema-utils</module>
|
<module>nifi-schema-utils</module>
|
||||||
|
<module>nifi-security-crypto-key</module>
|
||||||
<module>nifi-security-kerberos-api</module>
|
<module>nifi-security-kerberos-api</module>
|
||||||
<module>nifi-security-kerberos</module>
|
<module>nifi-security-kerberos</module>
|
||||||
<module>nifi-security-kms</module>
|
<module>nifi-security-kms</module>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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";
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -123,6 +123,7 @@
|
||||||
<module>nifi-iceberg-bundle</module>
|
<module>nifi-iceberg-bundle</module>
|
||||||
<module>nifi-jslt-bundle</module>
|
<module>nifi-jslt-bundle</module>
|
||||||
<module>nifi-iotdb-bundle</module>
|
<module>nifi-iotdb-bundle</module>
|
||||||
|
<module>nifi-cipher-bundle</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
Loading…
Reference in New Issue