com.ccstats.crypto.AESWorker.java Source code

Java tutorial

Introduction

Here is the source code for com.ccstats.crypto.AESWorker.java

Source

/**
 * Copyright (c) 2016 Ahmed Sakr
 *
 * 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.
 */

package com.ccstats.crypto;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;

/**
 *
 * @author Ahmed Sakr
 * @since February 28, 2016.
 */
public class AESWorker {

    // the default value the worker will assume wit be 256 bits for the key length.
    private int keyLength = 256;

    /**
     * Returns the running key length of this worker instance.
     *
     * @return an integer key length value
     */
    public int getKeyLength() {
        return this.keyLength;
    }

    /**
     * Sets the running key length of this worker instance. Any value bigger than
     * the maximum policy will be automatically reduced to the maximum policy.
     * IMPORTANT: Value must be power of two.
     *
     * @param keyLength the new key length in bits
     */
    public void setKeyLength(int keyLength) {
        this.keyLength = keyLength;
    }

    /**
     * Through the power of the advanced encryption standard, a plaintext will be encrypted with a parameter-specified
     * password, an extra protective layer (salt), and a specified key length. Make sure to acquire the salt and ivBytes
     * as they are necessary for decrypting the encrypted result.
     *
     * Firstly, The password is obtained and instantly overridden with the hashed version of the password, allowing
     * for stronger security as the plaintext password will not be used. Second, an arbitrary salt is securely
     * generated. Finally, the encryption standard is carried out and the encrypted text is obtained.
     *
     * @param password the password as a char array.
     * @param text The plaintext bytes to be encrypted.
     *
     * @return The Encrypted text in hexadecimal format.
     */
    public char[] encrypt(char[] password, byte[] text)
            throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
            InvalidParameterSpecException, BadPaddingException, IllegalBlockSizeException {

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        if (Cipher.getMaxAllowedKeyLength("AES") < this.keyLength) {
            this.keyLength = Cipher.getMaxAllowedKeyLength("AES");
            System.err.printf(
                    "WARNING: YOUR MAXIMUM AES KEY LENGTH POLICY IS %d BITS. KEY LENGTH LIMITED TO %d BITS.\n",
                    this.keyLength, this.keyLength);
        }

        // hash the password and acquire a securely and randomly generated salt
        password = hash(new String(password).getBytes(StandardCharsets.UTF_8));
        byte[] salt = new byte[20];
        new SecureRandom().nextBytes(salt);

        // acquire the key
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        PBEKeySpec spec = new PBEKeySpec(password, salt, 16384, this.keyLength);
        SecretKey key = factory.generateSecret(spec);
        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

        // init the cipher and process the encryption
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        AlgorithmParameters ap = cipher.getParameters();
        byte[] ivBytes = ap.getParameterSpec(IvParameterSpec.class).getIV();
        byte[] result = cipher.doFinal(text);

        return Hex.encodeHex(mergeByteArrays(ivBytes, result, salt));
    }

    /**
     * An override of the encrypt method with a default keyLength value of 256-bits.
     *
     * @param password The password used to encrypt the text.
     * @param text The text to be encrypted using the advanced encryption standard.
     *
     * @return The bytes of encrypted text.
     */
    public char[] encrypt(String password, String text)
            throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
            InvalidParameterSpecException, BadPaddingException, IllegalBlockSizeException {
        return encrypt(password.toCharArray(), text.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * Decrypting text that is encrypted by the advanced encryption standard.
     *
     * @param password The char array containing of the plaintext password
     * @param encryptedBlock The Encrypted text to be targeted and decrypted.
     *
     * @return The decrypted byte array of the encrypted text.
     */
    public byte[] decrypt(char[] password, char[] encryptedBlock)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException,
            BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, DecoderException {

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        if (Cipher.getMaxAllowedKeyLength("AES") < this.keyLength) {
            this.keyLength = Cipher.getMaxAllowedKeyLength("AES");
            System.err.printf(
                    "WARNING: YOUR MAXIMUM AES KEY LENGTH POLICY IS %d BITS. KEY LENGTH LIMITED TO %d BITS.\n",
                    this.keyLength, this.keyLength);
        }

        // hash the password with the MD5 function and decode the encryptedBlock
        password = hash(new String(password).getBytes(StandardCharsets.UTF_8));
        byte[] decoded = Hex.decodeHex(encryptedBlock);

        // The decoded byte array has the IV, encryptedText, and salt bytes stored in that order.
        // The IV bytes are of length 16 and salt is of length 20.
        byte[] encryptedText = new byte[decoded.length - 36], ivBytes = new byte[16], salt = new byte[20];

        // The decoded bytes are ordered in the following form: ivBytes + encryptedText + saltBytes.
        // Extract the bytes into their corresponding array.
        System.arraycopy(decoded, 0, ivBytes, 0, ivBytes.length);
        System.arraycopy(decoded, ivBytes.length, encryptedText, 0, encryptedText.length);
        System.arraycopy(decoded, decoded.length - salt.length, salt, 0, salt.length);

        // generate the key from the acquired data
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        PBEKeySpec spec = new PBEKeySpec(password, salt, 16384, this.keyLength);
        SecretKey key = factory.generateSecret(spec);
        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

        // finally, attempt to decrypt the encryptedText
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));
        return cipher.doFinal(encryptedText);
    }

    /**
     * An override of the decrypt method with the ability to provide the password and encryptedText as String
     * objects, and a default value of 256-bits for the AES Key length value.
     *
     * @param password The plaintext password as a String.
     * @param encryptedText The encrypted text as a String.
     *
     * @return The decrypted byte array of the encrypted text.
     */
    public byte[] decrypt(String password, String encryptedText) throws NoSuchPaddingException, DecoderException,
            InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException,
            BadPaddingException, InvalidKeyException, InvalidKeySpecException {
        return decrypt(password.toCharArray(), encryptedText.toCharArray());
    }

    /**
     * Hashes the plain password to provide a more secure experience.
     *
     * @param password the bytes of the plaintext password.
     *
     * @return The hashed password's characters in an array.
     */
    private char[] hash(byte[] password) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.reset();
        md.update(password);

        return Hex.encodeHex(md.digest());
    }

    /**
     * Merges all the byte[] varargs.
     *
     * @param arrays The byte[] varargs
     *
     * @return The master byte[] containing all the byte arrays.
     */
    private byte[] mergeByteArrays(byte[]... arrays) {
        int capacity = 0;
        for (byte[] arr : arrays) {
            capacity += arr.length;
        }

        byte[] result = new byte[capacity];
        int index = 0;
        for (byte[] array : arrays) {
            System.arraycopy(array, 0, result, index, array.length);
            index += array.length;
        }

        return result;
    }

}