org.apache.shindig.common.crypto.Crypto.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.shindig.common.crypto.Crypto.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.shindig.common.crypto;

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

import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.SecureRandom;

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

/**
 * Cryptographic utility functions.
 */
public final class Crypto {

    /** 
     * Use this random number generator instead of creating your own.  This is
     * thread-safe.
     */
    public static final SecureRandom RAND = new SecureRandom();

    /**
     * HMAC algorithm to use
     */
    private final static String HMAC_TYPE = "HMACSHA1";

    /** 
     * minimum safe length for hmac keys (this is good practice, but not 
     * actually a requirement of the algorithm
     */
    private final static int MIN_HMAC_KEY_LEN = 8;

    /**
     * Encryption algorithm to use
     */
    private final static String CIPHER_TYPE = "AES/CBC/PKCS5Padding";

    private final static String CIPHER_KEY_TYPE = "AES";

    /**
     * Use keys of this length for encryption operations
     */
    public final static int CIPHER_KEY_LEN = 16;

    private static final int CIPHER_BLOCK_SIZE = 16;

    /**
     * Length of HMAC SHA1 output
     */
    public final static int HMAC_SHA1_LEN = 20;

    private final static char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

    // everything is static, no instantiating this class
    private Crypto() {
    }

    /**
     * Gets a hex encoded random string.
     * 
     * @param numBytes number of bytes of randomness.
     */
    public static String getRandomString(int numBytes) {
        return new String(Hex.encodeHex(getRandomBytes(numBytes)));
    }

    /**
     * @return a random string of digits of the specified length.
     */
    public static String getRandomDigits(int len) {
        byte[] random = getRandomBytes(len);
        StringBuilder out = new StringBuilder(len);
        for (int i = 0; i < len; ++i) {
            out.append(DIGITS[Math.abs(random[i] % DIGITS.length)]);
        }
        return out.toString();
    }

    /**
     * Returns strong random bytes.
     * 
     * @param numBytes number of bytes of randomness
     */
    public static byte[] getRandomBytes(int numBytes) {
        byte[] out = new byte[numBytes];
        RAND.nextBytes(out);
        return out;
    }

    /**
     * HMAC sha1
     * 
     * @param key the key must be at least 8 bytes in length.
     * @param in byte array to HMAC.
     * @return the hash
     * 
     * @throws GeneralSecurityException
     */
    public static byte[] hmacSha1(byte[] key, byte[] in) throws GeneralSecurityException {
        if (key.length < MIN_HMAC_KEY_LEN) {
            throw new GeneralSecurityException("HMAC key should be at least " + MIN_HMAC_KEY_LEN + " bytes.");
        }
        Mac hmac = Mac.getInstance(HMAC_TYPE);
        Key hmacKey = new SecretKeySpec(key, HMAC_TYPE);
        hmac.init(hmacKey);
        hmac.update(in);
        return hmac.doFinal();
    }

    /**
     * Verifies an HMAC SHA1 hash.  Throws if the verification fails.
     * 
     * @param key
     * @param in
     * @param expected
     * @throws GeneralSecurityException
     */
    public static void hmacSha1Verify(byte[] key, byte[] in, byte[] expected) throws GeneralSecurityException {
        Mac hmac = Mac.getInstance(HMAC_TYPE);
        Key hmacKey = new SecretKeySpec(key, HMAC_TYPE);
        hmac.init(hmacKey);
        hmac.update(in);
        byte actual[] = hmac.doFinal();
        if (actual.length != expected.length) {
            throw new GeneralSecurityException("HMAC verification failure");
        }
        for (int i = 0; i < actual.length; i++) {
            if (actual[i] != expected[i]) {
                throw new GeneralSecurityException("HMAC verification failure");
            }
        }
    }

    /**
     * AES-128-CBC encryption.  The IV is returned as the first 16 bytes
     * of the cipher text.
     * 
     * @param key
     * @param plain
     * 
     * @return the IV and cipher text
     * 
     * @throws GeneralSecurityException
     */
    public static byte[] aes128cbcEncrypt(byte[] key, byte[] plain) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
        byte iv[] = getRandomBytes(cipher.getBlockSize());
        return concat(iv, aes128cbcEncryptWithIV(key, iv, plain));
    }

    /**
     * AES-128-CBC encryption with a given IV.
     *
     * @param key
     * @param iv
     * @param plain
     *
     * @return the cipher text
     *
     * @throws GeneralSecurityException
     */
    public static byte[] aes128cbcEncryptWithIV(byte[] key, byte[] iv, byte[] plain)
            throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
        Key cipherKey = new SecretKeySpec(key, CIPHER_KEY_TYPE);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec);
        return cipher.doFinal(plain);
    }

    /**
     * AES-128-CBC decryption.  The IV is assumed to be the first 16 bytes
     * of the cipher text.
     * 
     * @param key
     * @param cipherText
     * 
     * @return the plain text
     * 
     * @throws GeneralSecurityException
     */
    public static byte[] aes128cbcDecrypt(byte[] key, byte[] cipherText) throws GeneralSecurityException {
        byte iv[] = new byte[CIPHER_BLOCK_SIZE];
        System.arraycopy(cipherText, 0, iv, 0, iv.length);
        return aes128cbcDecryptWithIv(key, iv, cipherText, iv.length);
    }

    /**
     * AES-128-CBC decryption with a particular IV.
     * 
     * @param key decryption key
     * @param iv initial vector for decryption
     * @param cipherText cipher text to decrypt
     * @param offset offset into cipher text to begin decryption
     * 
     * @return the plain text
     * 
     * @throws GeneralSecurityException
     */
    public static byte[] aes128cbcDecryptWithIv(byte[] key, byte[] iv, byte[] cipherText, int offset)
            throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
        Key cipherKey = new SecretKeySpec(key, CIPHER_KEY_TYPE);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, cipherKey, ivSpec);
        return cipher.doFinal(cipherText, offset, cipherText.length - offset);
    }

    /**
     * Concatenate two byte arrays.
     */
    public static byte[] concat(byte[] a, byte[] b) {
        byte[] out = new byte[a.length + b.length];
        int cursor = 0;
        System.arraycopy(a, 0, out, cursor, a.length);
        cursor += a.length;
        System.arraycopy(b, 0, out, cursor, b.length);
        return out;
    }
}