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>
|
||||
<type>nar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-cipher-nar</artifactId>
|
||||
<version>1.20.0-SNAPSHOT</version>
|
||||
<type>nar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-distributed-cache-services-nar</artifactId>
|
||||
|
|
|
@ -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-repository-encryption</module>
|
||||
<module>nifi-schema-utils</module>
|
||||
<module>nifi-security-crypto-key</module>
|
||||
<module>nifi-security-kerberos-api</module>
|
||||
<module>nifi-security-kerberos</module>
|
||||
<module>nifi-security-kms</module>
|
||||
|
|
|
@ -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-jslt-bundle</module>
|
||||
<module>nifi-iotdb-bundle</module>
|
||||
<module>nifi-cipher-bundle</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
|
Loading…
Reference in New Issue