jeffaschenk.tomcat.zuul.util.KeyFileUtils.java Source code

Java tutorial

Introduction

Here is the source code for jeffaschenk.tomcat.zuul.util.KeyFileUtils.java

Source

package jeffaschenk.tomcat.zuul.util;

/*
 * Copyright (C) 2011 www.itcsolutions.eu
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1, or (at your
 * option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 *
 */

/**
 *
 * @author Catalin - www.itcsolutions.eu
 * @version 2011
 * original package: eu.itcsolutions.bc.aes;
 */

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import java.io.*;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;

/**
 * initial Implementation of AES
 * Bouncy Castle API installed as a JCA provider
 * CBC mode for encryption and decryption
 *
 * @author Catalin Boja
 * @author Jeff Schenk
 */

public class KeyFileUtils {

    // The default block size
    public static int blockSize = 16;

    Cipher encryptCipher = null;
    Cipher decryptCipher = null;

    // Buffer used to transport the bytes from one stream to another
    byte[] buf = new byte[blockSize]; //input buffer
    byte[] obuf = new byte[512]; //output buffer

    // The key
    byte[] key = null;
    // The initialization vector needed by the CBC mode
    byte[] IV = null;

    public KeyFileUtils() {
        //for a 192 key you must install the unrestricted policy files
        //  from the JCE/JDK downloads page
        // Key Must be divisible by 16 evenly.
        key = "SECRET_1SECRET_20001010204060201".getBytes();
        //default IV value initialized with 0
        IV = new byte[blockSize];
    }

    public KeyFileUtils(String pass, byte[] iv) {
        //get the key and the IV
        key = pass.getBytes();
        IV = new byte[blockSize];
        System.arraycopy(iv, 0, IV, 0, iv.length);
    }

    public KeyFileUtils(byte[] pass, byte[] iv) {
        //get the key and the IV
        key = new byte[pass.length];
        System.arraycopy(pass, 0, key, 0, pass.length);
        IV = new byte[blockSize];
        System.arraycopy(iv, 0, IV, 0, iv.length);
    }

    public void InitCiphers() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        //1. create the cipher using Bouncy Castle Provider
        encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", new BouncyCastleProvider());
        //2. create the key
        SecretKey keyValue = new SecretKeySpec(key, "AES");
        //3. create the IV
        AlgorithmParameterSpec IVspec = new IvParameterSpec(IV);
        //4. init the cipher
        encryptCipher.init(Cipher.ENCRYPT_MODE, keyValue, IVspec);

        //1 create the cipher
        decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", new BouncyCastleProvider());
        //2. the key is already created
        //3. the IV is already created
        //4. init the cipher
        decryptCipher.init(Cipher.DECRYPT_MODE, keyValue, IVspec);
    }

    public void ResetCiphers() {
        encryptCipher = null;
        decryptCipher = null;
    }

    public void CBCEncrypt(InputStream fis, OutputStream fos)
            throws IOException, ShortBufferException, IllegalBlockSizeException, BadPaddingException {
        //optionally put the IV at the beginning of the cipher file
        //fos.write(IV, 0, IV.length);

        byte[] buffer = new byte[blockSize];
        int noBytes = 0;
        byte[] cipherBlock = new byte[encryptCipher.getOutputSize(buffer.length)];
        int cipherBytes;
        while ((noBytes = fis.read(buffer)) != -1) {
            cipherBytes = encryptCipher.update(buffer, 0, noBytes, cipherBlock);
            fos.write(cipherBlock, 0, cipherBytes);
        }
        //always call doFinal
        cipherBytes = encryptCipher.doFinal(cipherBlock, 0);
        fos.write(cipherBlock, 0, cipherBytes);

        //close the files
        fos.close();
        fis.close();
    }

    public byte[] CBCDecrypt(InputStream fis)
            throws IOException, ShortBufferException, IllegalBlockSizeException, BadPaddingException {
        // get the IV from the file
        // DO NOT FORGET TO reinit the cipher with the IV
        //fis.read(IV,0,IV.length);
        //this.InitCiphers();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            byte[] buffer = new byte[blockSize];
            int noBytes = 0;
            byte[] cipherBlock = new byte[decryptCipher.getOutputSize(buffer.length)];
            int cipherBytes;
            while ((noBytes = fis.read(buffer)) != -1) {
                cipherBytes = decryptCipher.update(buffer, 0, noBytes, cipherBlock);
                baos.write(cipherBlock, 0, cipherBytes);
            }
            // always call doFinal
            cipherBytes = decryptCipher.doFinal(cipherBlock, 0);
            baos.write(cipherBlock, 0, cipherBytes);

            // return the decrypted Password / Key Phrase
            return baos.toByteArray();
        } finally {
            //close the input file
            fis.close();
            baos.close();
        }

    }

    /**
     * Helper method to Obtain the Validated Password from the Command Line.
     *
     * @param prompt - Display Prompt
     * @return char[] - Character Array of Validated/Confirmed Entered Data.
     */
    private static char[] obtainValidatedPassword(String prompt) {
        char[] initialPassword = readPassword(prompt);
        // Now prompt to confirm entered Password...
        char[] confirmedPassword = readPassword("Please Confirm " + prompt);
        if (Arrays.equals(initialPassword, confirmedPassword)) {
            Arrays.fill(confirmedPassword, ' ');
            return initialPassword;
        } else {
            Arrays.fill(initialPassword, ' ');
            Arrays.fill(confirmedPassword, ' ');
            System.out.println("Passwords do not Match, unable to continue!");
            return null;
        }
    }

    /**
     * Helper method to read a Password from the command line,
     * hiding input if in true terminal and not IDE.
     *
     * @param prompt - Display Prompt
     * @return char[] - Character Array of Entered Data.
     */
    private static char[] readPassword(String prompt) {
        System.out.print(prompt);
        if (System.console() != null) {
            return System.console().readPassword();
        } else {
            try {
                return new BufferedReader(new InputStreamReader(System.in)).readLine().toCharArray();
            } catch (IOException ioe) {
                return null;
            }
        }
    }

    /**
     * Utility Main to provide capabilities to generate a protected KeyFile for use with Zuul
     *
     * @param args - Incoming program Arguments
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        // Initialize our Ciphers
        KeyFileUtils keyFileUtils = new KeyFileUtils();
        keyFileUtils.InitCiphers();

        // Assume we our sole purpose in life is to prompt for a Password, which is the same,
        // ensure we have an argument to be out filename.
        if ((args == null) || (args.length != 1)) {
            System.out.println("Invalid number of Arguments specified!");
            System.out.println(
                    "Usage: " + keyFileUtils.getClass().getName() + " <Fully Qualified Output Key Filename>");
            return;
        }

        // Validate Argument as a file.
        File outputKeyFile = new File(args[0]);
        if (outputKeyFile.exists()) {
            // Prompt to overwrite...
            char[] answer = readPassword("Specified Key File:[" + outputKeyFile + "], exists. Overwrite? (y/n)? ");
            if ((answer == null) || (answer.length == 0)
                    || (!String.valueOf(answer).toUpperCase().startsWith("Y"))) {
                System.out.println(
                        "Existing Key File:[" + outputKeyFile + "], exists. Will not be Overwritten, Done.");
                return;
            } else {
                outputKeyFile.delete();
            }
        }

        // Prompt Operator for the Zuul Encryption Password to be encrypted in a file and saved for later access.
        // Prompt to overwrite...
        char[] password = obtainValidatedPassword("Enter Zuul Encryption Password: ");
        if (password == null) {
            System.out.println("Unable to obtain Password, Done.");
            return;
        }

        // Create our Input and Output Streams for the encryption of the Password.
        InputStream is = IOUtils.toInputStream(String.valueOf(password), "UTF-8");
        FileOutputStream fos = new FileOutputStream(outputKeyFile);

        // Encrypt
        keyFileUtils.CBCEncrypt(is, fos);

        // Now lets us Validate the file.
        keyFileUtils.ResetCiphers();
        keyFileUtils = new KeyFileUtils();
        keyFileUtils.InitCiphers();

        // Obtain the Decrypted Contents
        byte[] obtainedDecryptedData = keyFileUtils.CBCDecrypt(new FileInputStream(outputKeyFile));

        // Compare to validate...
        boolean valid = String.valueOf(password).equals(new String(obtainedDecryptedData));
        Arrays.fill(password, ' ');
        Arrays.fill(obtainedDecryptedData, (byte) 0x00);
        if (valid) {
            System.out.println("Key File:[" + outputKeyFile + "], validated and available for use, Done.");
            return;
        } else {
            System.out.println("Key File:[" + outputKeyFile + "], Did not Validate, very bad!");
            return;
        }
    } // End of Main.
}