From d574a2fc71ad43f8bc025ea9cc23ca718bc66570 Mon Sep 17 00:00:00 2001 From: "Chou, Joseph" Date: Thu, 7 Mar 2019 11:49:23 -0500 Subject: ONAP password encryption tool Migrate ECOMP Policy password encryption tool to ONAP Issue-ID: POLICY-1561 Change-Id: I9020efb7698b95c36c4ebff842a318bf8beefc69 Signed-off-by: Joseph Chou --- utils/pom.xml | 4 + .../policy/common/utils/security/CryptoUtils.java | 259 +++++++++++++++++++++ .../common/utils/security/CryptoUtilsTest.java | 120 ++++++++++ 3 files changed, 383 insertions(+) create mode 100644 utils/src/main/java/org/onap/policy/common/utils/security/CryptoUtils.java create mode 100644 utils/src/test/java/org/onap/policy/common/utils/security/CryptoUtilsTest.java diff --git a/utils/pom.xml b/utils/pom.xml index 3263b7b9..3faf9121 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -40,6 +40,10 @@ org.apache.commons commons-lang3 + + com.google.guava + guava + junit junit 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 00000000..ade8b467 --- /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 + * + *

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 00000000..fd3daee8 --- /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 -- cgit 1.2.3-korg