org.axiom_tools.crypto.Symmetric.java Source code

Java tutorial

Introduction

Here is the source code for org.axiom_tools.crypto.Symmetric.java

Source

/**
 * Copyright 2014,2015 Nikolas Boyd.
 *
 * 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 org.axiom_tools.crypto;

import java.util.HashMap;
import java.security.Key;
import java.security.Security;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.axiom_tools.context.SpringContext;

/**
 * Symmetrically encrypts and decrypts data under AES.
 *
 * <h4>Symmetric Responsibilities:</h4>
 * <ul>
 * <li>knows which cryptographer supports a given kind of usage</li>
 * <li>knows an AES initialization vector</li>
 * <li>knows an AES key</li>
 * <li>encrypts clear text data under the configured IV and key</li>
 * <li>decrypts cypher data under the same IV and key</li>
 * </ul>
 *
 * <h4>Client Responsibilities:</h4>
 * <ul>
 * <li>properly configure an instance of this class with IV and key</li>
 * </ul>
 */
public class Symmetric {

    private static final String ConfigurationFile = "cryptographers.xml";
    private static final HashMap<String, String> CryptographerMap = new HashMap<>();

    /**
     * Returns the cryptographer configured to handle usage of a given kind.
     *
     * @param usageName identifies a kind of usage
     * @return a Symmetric, or null if none was configured for the supplied usageName
     */
    public static Symmetric getCryptographer(String usageName) {
        String cryptName = CryptographerMap.get(usageName);
        if (cryptName == null) {
            return null;
        }
        return Symmetric.named(cryptName);
    }

    /**
     * Initializes mappings from usage names to cryptographer names.
     */
    public static class Mapper {

        /**
         * Initializes the name mappings.
         *
         * @param mapping a formatted description of the mappings
         */
        public void setMapElements(String mapping) {
            String[] parts = mapping.split(Separator);
            for (String part : parts) {
                String[] map = part.trim().split(Equals);
                String[] terms = map[1].trim().split(Comma);
                for (String term : terms) {
                    CryptographerMap.put(term.trim(), map[0].trim());
                }
                getLogger().info("registered cryptographer " + part.trim());
            }
        }

        private Logger getLogger() {
            return LoggerFactory.getLogger(getClass());
        }
    }

    static {
        Security.addProvider(new BouncyCastleProvider());
        SpringContext.named(ConfigurationFile).getBean(Mapper.class);
    }

    private static final String Empty = "";
    private static final String Comma = ",";
    private static final String Equals = "=";
    private static final String Separator = ";";
    private static final String Blank = " ";
    private static final String Pad = Hex.encodeHexString(Blank.getBytes());

    private static final String Algorithm = "AES";
    private static final String Transform = Algorithm + "/CBC/NoPadding";
    private static final String Encoding = "UTF-8";

    private static final int BlockSize = 16;
    private static final int ByteNibbles = 2;
    private static final int VectorSize = BlockSize * ByteNibbles;
    private static final byte[] EmptyBuffer = {};

    private String seedValue;
    private String keyValue;

    /**
     * Constructs a new Symmetric.
     */
    public Symmetric() {
    }

    /**
     * Returns a configured cryptographer.
     *
     * @param configuredName a configured cryptographer name.
     * @return a Symmetric, or null
     */
    public static Symmetric named(String configuredName) {
        return SpringContext.named(ConfigurationFile).getBean(Symmetric.class, configuredName);
    }

    /**
     * Returns a new Symmetric.
     *
     * @param seedValue an AES initialization vector (32 hex digits)
     * @return a new Symmetric
     */
    public static Symmetric withSeed(String seedValue) {
        Symmetric result = new Symmetric();
        result.seedValue = checkLength(seedValue, BadSeed);
        return result;
    }

    /**
     * Sets the AES key.
     *
     * @param keyValue an AES key (32 hex digits).
     * @return this Symmetric
     */
    public Symmetric withKey(String keyValue) {
        this.keyValue = checkLength(keyValue, BadKey);
        return this;
    }

    /**
     * Encrypts clear text into a hex string.
     *
     * @param clearText clear text data
     * @return a hex string containing cypher data, or empty
     */
    public String encryptAsHex(String clearText) {
        return Hex.encodeHexString(encrypt(clearText));
    }

    /**
     * Encrypts clear text data.
     *
     * @param clearText clear text data
     * @return a buffer containing cypher data, or empty
     */
    public byte[] encrypt(String clearText) {
        try {
            return encryptBytes(clearText.getBytes(Encoding));
        } catch (Exception e) {
            getLogger().error(e.getMessage(), e);
            return EmptyBuffer;
        }
    }

    /**
     * Encrypts clear text data.
     *
     * @param clearData a buffer containing the clear text data
     * @return a buffer containing cypher data, or empty
     */
    public byte[] encryptBytes(byte[] clearData) {
        if (clearData == null) {
            return EmptyBuffer;
        }
        if (clearData.length == 0) {
            return EmptyBuffer;
        }

        try {
            clearData = normalize(clearData);
            return buildEncrypter().doFinal(clearData);
        } catch (Exception e) {
            getLogger().error(e.getMessage(), e);
            return EmptyBuffer;
        }
    }

    /**
     * Decrypts cypher text encoded as hex.
     *
     * @param cypherText a hex string that contains cypher data
     * @return a clear text result, or empty
     */
    public String decryptFromHex(String cypherText) {
        try {
            return decrypt(Hex.decodeHex(cypherText.toCharArray()));
        } catch (Exception e) {
            getLogger().error(e.getMessage(), e);
            return Empty;
        }
    }

    /**
     * Decrypts cypher text data.
     *
     * @param cypherData a buffer that contains cypher data
     * @return a buffer containing a clear text result, or empty
     */
    public byte[] decryptBytes(byte[] cypherData) {
        if (cypherData == null) {
            return EmptyBuffer;
        }
        if (cypherData.length == 0) {
            return EmptyBuffer;
        }

        try {
            return buildDecrypter().doFinal(cypherData);
        } catch (Exception e) {
            getLogger().error(e.getMessage(), e);
            return EmptyBuffer;
        }
    }

    /**
     * Decrypts cypher text data.
     *
     * @param cypherData a buffer that contains cypher data
     * @return a clear text result, or empty
     */
    public String decrypt(byte[] cypherData) {
        try {
            return new String(buildDecrypter().doFinal(cypherData), Encoding).trim();
        } catch (Exception e) {
            getLogger().error(e.getMessage(), e);
            return Empty;
        }
    }

    /**
     * A seed value.
     *
     * @return a seedValue
     */
    public String getSeedValue() {
        return this.seedValue;
    }

    /**
     * A seed value.
     *
     * @param seedValue a seedValue
     */
    public void setSeedValue(String seedValue) {
        this.seedValue = seedValue;
    }

    /**
     * A key value.
     *
     * @return a keyValue
     */
    public String getKeyValue() {
        return this.keyValue;
    }

    /**
     * A key value.
     *
     * @param keyValue a keyValue
     */
    public void setKeyValue(String keyValue) {
        this.keyValue = keyValue;
    }

    /**
     * Normalizes a buffer to the length needed for encryption.
     *
     * @param clearData a buffer containing clear text data
     * @return a buffer containing padded clear text data
     * @throws Exception if raised during conversion
     */
    private static byte[] normalize(byte[] clearData) throws Exception {
        int length = clearData.length;
        int extra = length % BlockSize;
        if (extra < 1) {
            return clearData;
        }

        String hex = Hex.encodeHexString(clearData);
        int padding = BlockSize - extra;
        while (padding-- > 0) {
            hex += Pad;
        }
        return Hex.decodeHex(hex.toCharArray());
    }

    private Cipher buildEncrypter() throws Exception {
        Cipher result = getCipher();
        result.init(Cipher.ENCRYPT_MODE, buildKey(), buildSeed());
        return result;
    }

    private Cipher buildDecrypter() throws Exception {
        Cipher result = getCipher();
        result.init(Cipher.DECRYPT_MODE, buildKey(), buildSeed());
        return result;
    }

    private Cipher getCipher() throws Exception {
        return Cipher.getInstance(Transform, BouncyCastleProvider.PROVIDER_NAME);
    }

    private IvParameterSpec buildSeed() throws Exception {
        return new IvParameterSpec(Hex.decodeHex(getSeedValue().toCharArray()));
    }

    private Key buildKey() throws Exception {
        return new SecretKeySpec(Hex.decodeHex(getKeyValue().toCharArray()), Algorithm);
    }

    @SuppressWarnings("unused")
    private static String checkLength(String value, String failMessage) {
        String result = StringUtils.defaultString(value).trim();
        if (result.length() != VectorSize) {
            throw new IllegalArgumentException(failMessage);
        }

        try {
            // test hex conversion
            byte[] bytes = Hex.decodeHex(result.toCharArray());
            int size = bytes.length;
        } catch (Exception e) {
            throw new IllegalArgumentException(failMessage);
        }

        return result;
    }

    private Logger getLogger() {
        return LoggerFactory.getLogger(getClass());
    }

    private static final String BadSeed = "seed value must be a hex value of length " + VectorSize + " digits";
    private static final String BadKey = "key value must be a hex value of length " + VectorSize + " digits";

} // Symmetric