Coverage Summary for Class: AESUtil (org.kitodo.production.security)

Class Class, % Method, % Line, %
AESUtil 100% (1/1) 83,3% (5/6) 91,9% (34/37)


 /*
  * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
  *
  * This file is part of the Kitodo project.
  *
  * It is licensed under GNU General Public License version 3 or later.
  *
  * For the full copyright and license information, please read the
  * GPL3-License.txt file that was distributed with this source code.
  */
 
 package org.kitodo.production.security;
 
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Objects;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 public class AESUtil {
 
     private static final Logger logger = LogManager.getLogger(AESUtil.class);
 
     /*
      * DO NOT CHANGE! Identifier for is encryption check and secret key generation.
      * If changed are made, encrypted data cannot be restored.
      */
     private static final String SALT_PREFIX = "KITODO";
 
     private static final int SALT_LENGTH = 16;
 
     private static final int IV_LENGTH = 16;
 
     private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
 
     /**
      * Encrypts a value by the secret using AES/CBC/PKCS5Padding algorithm.
      *
      * <p>
      * Function generates salt to convert secret to AES-256 secret key and makes
      * simple secrets more complex. In addition, function generates initialization
      * vector (iv) to made encrypted value different when original value is the
      * same. Salt and iv will be become one with the cipher text and result is
      * converted to base64. As pseudocode the structure is as follows: BASE64( SALT(
      * SALT_PREFIX + RANDOM ) + CIPHER TEXT + IV ( RANDOM ) )
      * </p>
      *
      * @param value
      *            The value to encrypt
      * @param secret
      *            The secret from config properties
      * @return The encrypted value as base64 string.
      */
     public static String encrypt(String value, String secret)
             throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
             InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {
         if (Objects.isNull(value)) {
             return StringUtils.EMPTY;
         }
 
         // generate salt
         byte[] salt = new byte[SALT_LENGTH];
         new SecureRandom().nextBytes(salt);
         System.arraycopy(SALT_PREFIX.getBytes(), 0, salt, 0, SALT_PREFIX.getBytes().length);
 
         // generate iv
         byte[] iv = new byte[IV_LENGTH];
         new SecureRandom().nextBytes(iv);
 
         Cipher cipher = Cipher.getInstance(ALGORITHM);
         cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secret, salt), new IvParameterSpec(iv));
         byte[] cipherText = cipher.doFinal(value.getBytes());
 
         // prefix cipher text with salt and attach iv to the end
         byte[] cipherCombined = new byte[SALT_LENGTH + cipherText.length + IV_LENGTH];
 
         System.arraycopy(salt, 0, cipherCombined, 0, SALT_LENGTH);
         System.arraycopy(cipherText, 0, cipherCombined, SALT_LENGTH, cipherText.length);
         System.arraycopy(iv, 0, cipherCombined, SALT_LENGTH + cipherText.length, iv.length);
 
         return Base64.getEncoder().encodeToString(cipherCombined);
     }
 
     /**
      * Checks if value is encrypted.
      *
      * <p>
      * Function checks if value starts with defined salt prefix after base64
      * decoding.
      * </p>
      *
      * @param potentialEncryptedValue
      *            The value to be checked.
      * @return boolean true if value is encrypted using encrypt function
      */
     public static boolean isEncrypted(String potentialEncryptedValue) {
         try {
             if (Objects.nonNull(potentialEncryptedValue)) {
                 byte[] cipherCombined = Base64.getDecoder().decode(potentialEncryptedValue);
                 byte[] saltPrefix = Arrays.copyOfRange(cipherCombined, 0, SALT_PREFIX.getBytes().length);
                 // check if cipher combined has salt prefix
                 return Arrays.equals(saltPrefix, SALT_PREFIX.getBytes());
             }
         } catch (IllegalArgumentException e) {
             logger.debug("Value to encrypt is not a valid base64 string.");
         }
         return false;
     }
 
     /**
      * Decrypts encrypted value by the secret using AES/CBC/PKCS5Padding algorithm.
      * 
      * <p>
      * Function splits encrypted value salt, cipher text and iv after base64
      * decoding. Salt and secret will be used to build AES-256 secret key. With the
      * help of the iv and the secret key, the cipher text is decrypted and returned.
      * </p>
      *
      * @param encryptValue
      *            The encrypted value
      * @param secret
      *            The secret from config properties
      * @return The decrypted value.
      */
     public static String decrypt(String encryptValue, String secret)
             throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
             InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {
         byte[] cipherCombined = Base64.getDecoder().decode(encryptValue);
 
         // get cipherText and iv from cipherCombined
         byte[] salt = Arrays.copyOfRange(cipherCombined, 0, SALT_LENGTH);
         byte[] cipherText = Arrays.copyOfRange(cipherCombined, SALT_LENGTH, cipherCombined.length - IV_LENGTH);
         byte[] iv = Arrays.copyOfRange(cipherCombined, cipherCombined.length - IV_LENGTH, cipherCombined.length);
 
         Cipher cipher = Cipher.getInstance(ALGORITHM);
         cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secret, salt), new IvParameterSpec(iv));
         byte[] value = cipher.doFinal(cipherText);
         return new String(value);
     }
 
     private static SecretKey getSecretKey(String secret, byte[] salt)
             throws InvalidKeySpecException, NoSuchAlgorithmException {
         KeySpec spec = new PBEKeySpec(secret.toCharArray(), salt, 65536, 256); // AES-256
         SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
         byte[] key = f.generateSecret(spec).getEncoded();
         return new SecretKeySpec(key, "AES");
     }
 }