org.jsecurity.crypto.BlowfishCipher.java Source code

Java tutorial

Introduction

Here is the source code for org.jsecurity.crypto.BlowfishCipher.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.jsecurity.crypto;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jsecurity.codec.Base64;
import org.jsecurity.codec.CodecSupport;

import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * JSecurity's default symmetric block Cipher using the Blowfish algorithm.  As it is a symmetric Cipher, it uses the
 * same <tt>Key</tt> to both encrypt and decrypt data.  If one is not provided via the {@link #setKey setKey} method,
 * a default one will be used, BUT NOTE:
 *
 * <p>Because JSecurity is an open-source project, if anyone knew that you were using JSecurity's default
 * <code>Key</code>, they could download/view the source, and with enough effort, reconstruct the <code>Key</code>
 * and decode encrypted data at will.
 *
 * <p>JSecurity only really uses Ciphers to encrypt user ids and session ids, so if that information is not critical
 * to you and you think the default key still makes things 'sufficiently difficult', then you can ignore this issue.
 *
 * <p>However, if you do feel this constitutes sensitive information, it is recommended that you provide your own
 * <tt>Key</tt> via the {@link #setKey setKey} method to a Key known only to your application,
 * guaranteeing that no third party can decrypt your data.  If you want to know how to do this, you can browse this
 * class's source code for the {@link #generateNewKey()} method to see how we created our default.  Then you can
 * duplicate the same in your environment and set the result on an instance of this class via the
 * <code>setKey</code> method.
 *
 * @author Les Hazlewood
 * @author Jeremy Haile
 * @since 0.9
 */
public class BlowfishCipher implements Cipher {

    /**
     * The JDK Crypto Cipher algorithm to use for this class, equal to &quot;Blowfish&quot;.
     */
    private static final String ALGORITHM = "Blowfish";

    /**
     * The JDK Crypto Transformation string to use for this class, equal to {@link #ALGORITHM ALGORITHM} + &quot;/ECB/PKCS5Padding&quot;;
     */
    private static final String TRANSFORMATION_STRING = ALGORITHM + "/ECB/PKCS5Padding";

    //The following KEY_BYTES String was created by running
    //System.out.println( Base64.encode( generateNewKey().getEncoded() ) ); and copying-n-pasting the output here.
    //You should run the same and set the resulting output as a property of this class instead of using
    //JSecurity's default Key for proper security.
    private static final byte[] KEY_BYTES = Base64.decode("jJ9Kg1BAevbvhSg3vBfwfQ==");
    private static final Key DEFAULT_CIPHER_KEY = new SecretKeySpec(KEY_BYTES, ALGORITHM);

    /**
     * Internal private log instance.
     */
    private static final Log log = LogFactory.getLog(BlowfishCipher.class);

    /**
     * The key to use by default, can be overridden by calling {@link #setKey(java.security.Key)}.
     */
    private Key key = DEFAULT_CIPHER_KEY;

    /**
     * Default no argument constructor that uses an internal default {@link #getKey() key} to use during
     * encryption and decryption.  For propery security, you should definitely supply your own key by using the
     * {@link #setKey(java.security.Key) setKey(Key)} method.
     */
    public BlowfishCipher() {
    }

    /**
     * Returns the default {@link Key Key} to use for symmetric encryption and decryption if one is not specified during
     * encryption/decryption.  For truly secure applications,
     * you should always specify your own key via the {@link #setKey(java.security.Key) setKey} method.
     * @return the {@link Key Key} to use for symmetric encryption and decryption.
     * @see #encrypt(byte[], byte[])
     * @see #decrypt(byte[], byte[]) 
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the internal default {@link Key Key} to use for symmetric encryption and decryption if one is not
     * specified during encryption/decryption.   For truly secure applications, you should always specify your own
     * key via this method.
     * @param key the key to use for symmetric encryption and decryption.
     * @see #encrypt(byte[], byte[])
     * @see #decrypt(byte[], byte[])
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Encrypts the specified raw byte array.  If the <code>key</code> argument is null, the internal default
     * {@link #getKey() key} will be used to encrypt the byte array.
     */
    public byte[] encrypt(byte[] raw, byte[] key) {
        byte[] encrypted = crypt(raw, javax.crypto.Cipher.ENCRYPT_MODE, key);
        if (log.isTraceEnabled()) {
            log.trace("Incoming byte array of size " + (raw != null ? raw.length : 0) + ".  Encrypted "
                    + "byte array is size " + (encrypted != null ? encrypted.length : 0));
        }
        return encrypted;
    }

    /**
     * Decrypts the specified already-encrypted byte array.  If the <code>key</code> argument is null, the internal default
     * {@link #getKey() key} will be used to encrypt the byte array.
     */
    public byte[] decrypt(byte[] encrypted, byte[] key) {
        if (log.isTraceEnabled()) {
            log.trace("Attempting to decrypt incoming byte array of length "
                    + (encrypted != null ? encrypted.length : 0));
        }
        return crypt(encrypted, javax.crypto.Cipher.DECRYPT_MODE, key);
    }

    /**
     * Returns a new {@link javax.crypto.Cipher Cipher} instance to use for encryption/decryption operations, based on
     * the {@link #TRANSFORMATION_STRING TRANSFORMATION_STRING} constant.
     * @return a new Cipher instance.
     * @throws IllegalStateException if a new Cipher instance cannot be constructed based on the
     * {@link #TRANSFORMATION_STRING TRANSFORMATION_STRING} constant.
     */
    protected javax.crypto.Cipher newCipherInstance() throws IllegalStateException {
        try {
            return javax.crypto.Cipher.getInstance(TRANSFORMATION_STRING);
        } catch (Exception e) {
            String msg = "Unable to acquire a Java JCE Cipher instance using " + javax.crypto.Cipher.class.getName()
                    + ".getInstance( \"" + TRANSFORMATION_STRING + "\" ). "
                    + "Blowfish under this configuration is required for the " + getClass().getName()
                    + " instance to function.";
            throw new IllegalStateException(msg, e);
        }
    }

    /**
     * Initializes the JDK Cipher with the specified mode and key.  This is primarily a utility method to catch any
     * potential {@link InvalidKeyException InvalidKeyException} that might arise.
     *
     * @param cipher the JDK Cipher to {@link javax.crypto.Cipher#init(int, java.security.Key) init}.
     * @param mode the Cipher mode
     * @param key the Cipher's Key
     */
    protected void init(javax.crypto.Cipher cipher, int mode, java.security.Key key) {
        try {
            cipher.init(mode, key);
        } catch (InvalidKeyException e) {
            String msg = "Unable to init cipher.";
            throw new IllegalStateException(msg, e);
        }
    }

    /**
     * Calls the {@link javax.crypto.Cipher#doFinal(byte[]) doFinal(bytes)} method, propagating any exception that
     * might arise in an {@link IllegalStateException IllegalStateException}
     * @param cipher the JDK Cipher to finalize (perform the actual cryption)
     * @param bytes the bytes to crypt
     * @return the resulting crypted byte array.
     */
    protected byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) {
        try {
            return cipher.doFinal(bytes);
        } catch (Exception e) {
            String msg = "Unable to crypt bytes with cipher [" + cipher + "].";
            throw new IllegalStateException(msg, e);
        }
    }

    /**
     * Calls the {@link #init(javax.crypto.Cipher, int, java.security.Key)} and then
     * {@link #crypt(javax.crypto.Cipher, byte[])}.  Ensures that the key is never null by using the
     * {@link #getKey() default key} if the method argument key is <code>null</code>.
     * @param bytes the bytes to crypt
     * @param mode the JDK Cipher mode
     * @param key the key to use to do the cryption.  If <code>null</code> the {@link #getKey() default key} will be used.
     * @return the resulting crypted byte array
     */
    protected byte[] crypt(byte[] bytes, int mode, byte[] key) {
        javax.crypto.Cipher cipher = newCipherInstance();

        java.security.Key jdkKey;
        if (key == null) {
            jdkKey = getKey();
        } else {
            jdkKey = new SecretKeySpec(key, ALGORITHM);
        }

        init(cipher, mode, jdkKey);
        return crypt(cipher, bytes);
    }

    /**
     * Generates a new {@link Key Key} suitable for this Cipher by calling
     * {@link #generateNewKey() generateNewKey(128)} (uses a 128 bit size by default).
     * @return a new {@link Key Key}, 128 bits in length.
     */
    public static Key generateNewKey() {
        return generateNewKey(128);
    }

    /**
     * Generates a new {@link Key Key} of the specified size suitable for this Cipher
     * (based on the {@link #ALGORITHM ALGORITHM} using the JDK {@link KeyGenerator KeyGenerator}.
     * @param keyBitSize the bit size of the key to create
     * @return the created key suitable for use with this Cipher.
     */
    public static Key generateNewKey(int keyBitSize) {
        KeyGenerator kg;
        try {
            kg = KeyGenerator.getInstance(ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            String msg = "Unable to acquire " + ALGORITHM + " algorithm.  This is required to function.";
            throw new IllegalStateException(msg, e);
        }
        kg.init(keyBitSize);
        return kg.generateKey();
    }

    /**
     * Simple test main method to ensure functionality is correct.  Should really be moved to a proper test case.
     * @param unused ignored
     * @throws Exception if anything unexpected happens.
     */
    public static void main(String[] unused) throws Exception {

        Cipher cipher = new BlowfishCipher();

        String[] cleartext = new String[] { "Hello, this is a test.",
                "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." };

        for (String clear : cleartext) {
            byte[] cleartextBytes = CodecSupport.toBytes(clear);
            System.out.println("Clear text: [" + clear + "]");
            System.out.println("Clear text base64: [" + Base64.encodeToString(cleartextBytes) + "]");

            byte[] encrypted = cipher.encrypt(cleartextBytes, null);
            String encryptedBase64 = Base64.encodeToString(encrypted);
            System.out.println("Encrypted base64: [" + encryptedBase64 + "]");

            byte[] decrypted = cipher.decrypt(Base64.decode(encryptedBase64), null);
            String decryptedString = CodecSupport.toString(decrypted);

            System.out.println("Arrays equal? " + Arrays.equals(cleartextBytes, decrypted));

            System.out.println("Decrypted text: [" + decryptedString + "]");
            System.out.println("Decrypted text base64: [" + Base64.encodeToString(decrypted) + "]");
        }
    }
}