Java tutorial
/* * 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 "Blowfish". */ private static final String ALGORITHM = "Blowfish"; /** * The JDK Crypto Transformation string to use for this class, equal to {@link #ALGORITHM ALGORITHM} + "/ECB/PKCS5Padding"; */ 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) + "]"); } } }