greyp9 commented on code in PR #6821:
URL: https://github.com/apache/nifi/pull/6821#discussion_r1088327831


##########
nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContentEncoded.java:
##########
@@ -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 DecryptContentEncoded 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;

Review Comment:
   
https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@nifi.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to