Merge "ONAP password encryption tool"
diff --git a/utils/pom.xml b/utils/pom.xml
index 84fe462..f75a60b 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -46,6 +46,10 @@
             <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/utils/src/main/java/org/onap/policy/common/utils/security/CryptoUtils.java b/utils/src/main/java/org/onap/policy/common/utils/security/CryptoUtils.java
new file mode 100644
index 0000000..ade8b46
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/security/CryptoUtils.java
@@ -0,0 +1,259 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.security;
+
+import com.google.common.base.Charsets;
+
+import java.security.GeneralSecurityException;
+
+import java.security.SecureRandom;
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AES Encryption Utilities.
+ */
+public class CryptoUtils {
+    private static Logger logger = LoggerFactory.getLogger(CryptoUtils.class);
+
+    /**
+     * Definition of encryption algorithm.
+     */
+    private static final String ALGORITHM = "AES";
+
+    /**
+     * Detailed definition of encryption algorithm.
+     */
+    private static final String ALGORITHM_DETAILS = ALGORITHM + "/CBC/PKCS5PADDING";
+
+    private static final int IV_BLOCK_SIZE_IN_BITS = 128;
+
+    /**
+     * An Initial Vector of 16 Bytes, so 32 Hexadecimal Chars.
+     */
+    private static final int IV_BLOCK_SIZE_IN_BYTES = IV_BLOCK_SIZE_IN_BITS / 8;
+
+    private static int validSize = (2 * IV_BLOCK_SIZE_IN_BYTES) + 4;
+
+    private SecretKeySpec secretKeySpec;
+
+    private static final String RANDOM_NUMBER_GENERATOR = "SHA1PRNG";
+
+    /**
+     * This method is used as the main entry point when testing.
+     *
+     */
+    public static void main(String[] args) {
+        if (args.length == 3) {
+            if (args[0].equals("enc")) {
+                String encryptedValue = encrypt(args[1], args[2]);
+                logger.info("original value: " + args[1] + " encrypted value: " + encryptedValue);
+            } else if (args[0].equals("dec")) {
+                String decryptedValue = decrypt(args[1], args[2]);
+                logger.info("original value: " + args[1] + " decrypted value: " + decryptedValue);
+            } else {
+                logger.info("Unknown request: " + args[0]);
+            }
+        } else {
+            logger.info("Usage  : CryptoUtils enc/dec password   secretKey");
+            logger.info("Example: CryptoUtils enc     HelloWorld 1234");
+            logger.info("Example: CryptoUtils dec     enc:112233 1234");
+        }
+    }
+
+    public CryptoUtils(SecretKeySpec secretKeySpec) {
+        this.secretKeySpec = secretKeySpec;
+    }
+
+    /**
+     * CryptoUtils - encryption tool constructor.
+     * @param secretKey
+     *  AES supports 128, 192 or 256-bit long key size, it can be plain text or generated with key generator
+     */
+    public CryptoUtils(String secretKey) {
+        this.secretKeySpec = readSecretKeySpec(secretKey);
+    }
+
+    /**
+     * Encrypt a value based on the Policy Encryption Key.
+     * Equivalent openssl command: echo -n "123456" | openssl aes-128-cbc -e -K PrivateHexkey
+     * -iv 16BytesIV | xxd -u -g100
+     * 
+     * <p>Final result is to put in properties file is: IV + Outcome of openssl command
+     *
+     * @param value
+     *  The plain text string
+     * @return The encrypted String
+     * @throws GeneralSecurityException
+     *  In case of issue with the encryption
+     */
+    public String encrypt(String value) throws GeneralSecurityException {
+        return encryptValue(value, secretKeySpec);
+    }
+
+    /**
+     * Encrypt a value based on the Policy Encryption Key.
+     *
+     * @param value
+     *  The plain text string
+     * @param secretKey
+     *  The secret key
+     * @return The encrypted String
+     */
+    public static String encrypt(String value, String secretKey) {
+        SecretKeySpec keySpec = readSecretKeySpec(secretKey);
+        return encryptValue(value, keySpec);
+    }
+
+    private static String encryptValue(String value, SecretKeySpec keySpec) {
+        if (value == null || value.isEmpty()) {
+            logger.debug("Empty/null value passed in for decryption");
+            return value;
+        }
+        if (isEncrypted(value)) {
+            return value;
+        }
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM_DETAILS);
+            byte[] iv = new byte[IV_BLOCK_SIZE_IN_BYTES];
+            SecureRandom.getInstance(RANDOM_NUMBER_GENERATOR).nextBytes(iv);
+            IvParameterSpec ivspec = new IvParameterSpec(iv);
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
+
+            return "enc:" + DatatypeConverter.printBase64Binary(
+                    ArrayUtils.addAll(iv, cipher.doFinal(value.getBytes(Charsets.UTF_8))));
+        } catch (Exception e) {
+            logger.error("Could not encrypt value - exception: ", e);
+            return value;
+        }
+    }
+
+    /**
+     * Decrypt a value based on the Policy Encryption Key if string begin with 'enc:'.
+     * Equivalent openssl command: echo -n 'Encrypted string' | xxd -r -ps | openssl aes-128-cbc -d
+     * -K PrivateHexKey -iv 16BytesIVFromEncryptedString
+     *
+     * @param value
+     *  The encrypted string that must be decrypted using the Policy Encryption Key
+     * @return The String decrypted if string begin with 'enc:'
+     * @throws GeneralSecurityException
+     *   In case of issue with the encryption
+     */
+    public String decrypt(String value) throws GeneralSecurityException {
+        return decryptValue(value, secretKeySpec);
+    }
+
+    /**
+     * Decrypt a value based on the Policy Encryption Key if string begin with 'enc:'.
+     *
+     * @param value
+     *  The encrypted string that must be decrypted using the Policy Encryption Key
+     * @param secretKey
+     *  The secret key
+     * @return The String decrypted if string begin with 'enc:'
+     */
+    public static String decrypt(String value, String secretKey) {
+        SecretKeySpec keySpec = readSecretKeySpec(secretKey);
+        if (keySpec != null) {
+            return decryptValue(value, keySpec);
+        } else {
+            return value;
+        }
+    }
+
+    private static String decryptValue(String value, SecretKeySpec keySpec) {
+        if (value == null || value.isEmpty() || !isEncrypted(value)) {
+            return value;
+        }
+        if (value.length() < validSize) {
+            throw new IllegalArgumentException("Invalid size on input value");
+        }
+        try {
+            String pureValue = value.substring(4);
+            byte[] encryptedValue = DatatypeConverter.parseBase64Binary(pureValue);
+
+            Cipher cipher = Cipher.getInstance(ALGORITHM_DETAILS);
+            IvParameterSpec ivspec = new IvParameterSpec(
+                    ArrayUtils.subarray(encryptedValue, 0, IV_BLOCK_SIZE_IN_BYTES));
+            byte[] realData = ArrayUtils.subarray(encryptedValue, IV_BLOCK_SIZE_IN_BYTES, encryptedValue.length);
+
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivspec);
+            byte[] decrypted = cipher.doFinal(realData);
+            return new String(decrypted, Charsets.UTF_8);
+        } catch (Exception e) {
+            logger.error("Could not decrypt value - exception: ", e);
+        }
+        return value;
+    }
+
+    /**
+     * Method used to generate the SecretKeySpec from a Hex String.
+     *
+     * @param keyString
+     *            The key as a string in base64 String
+     * @return The SecretKeySpec created
+     * @throws DecoderException
+     *             In case of issues with the decoding of base64 String
+     */
+    private static SecretKeySpec getSecretKeySpec(String keyString) {
+        byte[] key = DatatypeConverter.parseBase64Binary(keyString);
+        return new SecretKeySpec(key, ALGORITHM);
+    }
+
+    /**
+     * Get Secret Key Spec from user provided secret key string.
+     *
+     * @param secretKey
+     *            user provided secretKey String
+     * @return SecretKeySpec secret key spec read from getSecretKeySpec
+     */
+    private static SecretKeySpec readSecretKeySpec(String secretKey) {
+        if (secretKey != null && !secretKey.isEmpty()) {
+            SecretKeySpec keySpec = null;
+            try {
+                keySpec = getSecretKeySpec(secretKey);
+                return keySpec;
+            } catch (Exception e) {
+                logger.error("Invalid key - exception: ", e);
+                return null;
+            }
+        } else {
+            logger.error("Secretkey can not be null or empty");
+            return null;
+        }
+    }
+    /**
+     * Check if string is encrypted by verify if string prefix with 'enc:'.
+     *
+     * @param value
+     *  The encrypted string or plain text value
+     * @return boolean value indicate if string prefix with enc: or not
+     */
+    public static Boolean isEncrypted(String value) {
+        return (value != null && value.startsWith("enc:"));
+    }
+}
\ No newline at end of file
diff --git a/utils/src/test/java/org/onap/policy/common/utils/security/CryptoUtilsTest.java b/utils/src/test/java/org/onap/policy/common/utils/security/CryptoUtilsTest.java
new file mode 100644
index 0000000..fd3daee
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/security/CryptoUtilsTest.java
@@ -0,0 +1,120 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.security;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.security.GeneralSecurityException;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Unit test for simple App.
+ */
+
+public class CryptoUtilsTest {
+    private static Logger logger = LoggerFactory.getLogger(CryptoUtilsTest.class);
+    private final String pass = "HelloWorld";
+    private final String secretKey = "12345678901234567890123456789012";
+    private final String encryptedPass = "enc:8XxseP5W5ODxzPrReNKd9JBYLv0iiAzy9BHnMKau5yg=";
+
+    @Test
+    public void testEncrypt() throws GeneralSecurityException {
+        logger.info("testEncrypt:");
+        CryptoUtils cryptoUtils = new CryptoUtils(secretKey);
+        String encryptedValue = cryptoUtils.encrypt(pass);
+        logger.info("original value : " + pass + "  encrypted value: " + encryptedValue);
+
+        String decryptedValue = cryptoUtils.decrypt(encryptedValue);
+        logger.info("encrypted value: " + encryptedValue + "  decrypted value : " + decryptedValue);
+        assertEquals(pass, decryptedValue);
+    }
+
+    @Test
+    public void testDecrypt() throws GeneralSecurityException {
+        logger.info("testDecrypt:");
+        CryptoUtils cryptoUtils = new CryptoUtils(secretKey);
+        String decryptedValue = cryptoUtils.decrypt(encryptedPass);
+        logger.info("encrypted value: " + encryptedPass + "  decrypted value : " + decryptedValue);
+        assertEquals(pass, decryptedValue);
+    }
+
+    @Test
+    public void testStaticEncrypt() {
+        logger.info("testStaticEncrypt:");
+        String encryptedValue = CryptoUtils.encrypt(pass, secretKey);
+        logger.info("original value : " + pass + "  encrypted value: " + encryptedValue);
+
+        String decryptedValue = CryptoUtils.decrypt(encryptedValue, secretKey);
+        logger.info("encrypted value: " + encryptedValue + "  decrypted value : " + decryptedValue);
+        assertEquals(pass, decryptedValue);
+    }
+
+    @Test
+    public void testStaticDecrypt() {
+        logger.info("testStaticDecrypt:");
+        String decryptedValue = CryptoUtils.decrypt(encryptedPass, secretKey);
+        logger.info("encrypted value: " + encryptedPass + "  decrypted value : " + decryptedValue);
+        assertEquals(pass, decryptedValue);
+    }
+
+    @Test
+    public void testBadInputs() {
+        String badKey = CryptoUtils.encrypt(pass, "test");
+        assertEquals(pass, badKey);
+
+        String badDecrypt = CryptoUtils.decrypt(encryptedPass, "");
+        assertEquals(encryptedPass, badDecrypt);
+
+        String emptyValue = CryptoUtils.encrypt(new String(), secretKey);
+        assertEquals("", emptyValue);
+
+        String emptyDecrypt = CryptoUtils.decrypt(new String(), secretKey);
+        assertEquals("", emptyDecrypt);
+
+        String nullValue = CryptoUtils.encrypt(null, secretKey);
+        assertNull(nullValue);
+
+        String nullDecrypt = CryptoUtils.decrypt(null, secretKey);
+        assertNull(nullDecrypt);
+    }
+
+    @Test
+    public void testAll() {
+        logger.info("testAll:");
+        String encryptedValue = CryptoUtils.encrypt(pass, secretKey);
+        logger.info("original value : " + pass + "  encrypted value: " + encryptedValue);
+
+        String encryptedAgain = CryptoUtils.encrypt(encryptedValue, secretKey);
+
+        assertEquals(encryptedValue, encryptedAgain);
+
+        String decryptedValue = CryptoUtils.decrypt(encryptedAgain, secretKey);
+        logger.info("encrypted value: " + encryptedAgain + "  decrypted value : " + decryptedValue);
+        assertEquals(pass, decryptedValue);
+
+        String decryptedAgain = CryptoUtils.decrypt(decryptedValue, secretKey);
+        assertEquals(decryptedValue, decryptedAgain);
+    }
+}
\ No newline at end of file