AES Java Implementation
Revision as of 07:43, 15 September 2008 by Haravikk Mistral (talk | contribs) (New page: = Description = The following is a simple Java example of AES encryption and decryption, compatible with the LSL AES Engine by [[User:Haravikk Mistral|Haravikk M...)
Description
The following is a simple Java example of AES encryption and decryption, compatible with the LSL AES Engine by Haravikk Mistral.
Required Classes
Base64Coder
package lslAESCrypto; /** * A Base64 Encoder/Decoder. * <p> * This class is used to encode and decode data in Base64 format as described in * RFC 1521. * <p> * This is "Open Source" software and released under the <a * href="http://www.gnu.org/licenses/lgpl.html">GNU/LGPL</a> license.<br> * It is provided "as is" without warranty of any kind.<br> * Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland.<br> * Home page: <a href="http://www.source-code.biz">www.source-code.biz</a> */ public class Base64Coder { /** Mapping table from 6-bit nibbles to Base64 characters. */ private static char[] map1 = new char[64]; /** Mapping table from Base64 characters to 6-bit nibbles. */ private static byte[] map2 = new byte[128]; static { int i = 0; for (char c = 'A'; c <= 'Z'; c++) Base64Coder.map1[i++] = c; for (char c = 'a'; c <= 'z'; c++) Base64Coder.map1[i++] = c; for (char c = '0'; c <= '9'; c++) Base64Coder.map1[i++] = c; Base64Coder.map1[i++] = '+'; Base64Coder.map1[i++] = '/'; for (i = 0; i < Base64Coder.map2.length; i++) Base64Coder.map2[i] = -1; for (i = 0; i < 64; i++) Base64Coder.map2[Base64Coder.map1[i]] = (byte) i; } /** * Decodes a byte array from Base64 format. No blanks or line breaks are * allowed within the Base64 encoded data. * * @param in * a character array containing the Base64 encoded data. * @return An array containing the decoded data bytes. * @throws IllegalArgumentException * if the input is not valid Base64 encoded data. */ public static byte[] decode(final char[] in) { int iLen = in.length; if (iLen % 4 != 0) throw new IllegalArgumentException( "Length of Base64 encoded input string is not a multiple of 4."); while ((iLen > 0) && (in[iLen - 1] == '=')) iLen--; final int oLen = (iLen * 3) / 4; final byte[] out = new byte[oLen]; int ip = 0; int op = 0; while (ip < iLen) { final int i0 = in[ip++]; final int i1 = in[ip++]; final int i2 = ip < iLen ? in[ip++] : 'A'; final int i3 = ip < iLen ? in[ip++] : 'A'; if ((i0 > 127) || (i1 > 127) || (i2 > 127) || (i3 > 127)) throw new IllegalArgumentException( "Illegal character in Base64 encoded data."); final int b0 = Base64Coder.map2[i0]; final int b1 = Base64Coder.map2[i1]; final int b2 = Base64Coder.map2[i2]; final int b3 = Base64Coder.map2[i3]; if ((b0 < 0) || (b1 < 0) || (b2 < 0) || (b3 < 0)) throw new IllegalArgumentException( "Illegal character in Base64 encoded data."); final int o0 = (b0 << 2) | (b1 >>> 4); final int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); final int o2 = ((b2 & 3) << 6) | b3; out[op++] = (byte) o0; if (op < oLen) out[op++] = (byte) o1; if (op < oLen) out[op++] = (byte) o2; } return out; } /** * Decodes a byte array from Base64 format. * * @param s * a Base64 String to be decoded. * @return An array containing the decoded data bytes. * @throws IllegalArgumentException * if the input is not valid Base64 encoded data. */ public static byte[] decode(final String s) { return Base64Coder.decode(s.toCharArray()); } /** * Decodes a string from Base64 format. * * @param s * a Base64 String to be decoded. * @return A String containing the decoded data. * @throws IllegalArgumentException * if the input is not valid Base64 encoded data. */ public static String decodeString(final String s) { return new String(Base64Coder.decode(s)); } /** * Encodes a byte array into Base64 format. No blanks or line breaks are * inserted. * * @param in * an array containing the data bytes to be encoded. * @return A character array with the Base64 encoded data. */ public static char[] encode(final byte[] in) { return Base64Coder.encode(in, in.length); } /** * Encodes a byte array into Base64 format. No blanks or line breaks are * inserted. * * @param in * an array containing the data bytes to be encoded. * @param iLen * number of bytes to process in <code>in</code>. * @return A character array with the Base64 encoded data. */ public static char[] encode(final byte[] in, final int iLen) { final int oDataLen = (iLen * 4 + 2) / 3; // output length without // padding final int oLen = ((iLen + 2) / 3) * 4; // output length including // padding final char[] out = new char[oLen]; int ip = 0; int op = 0; while (ip < iLen) { final int i0 = in[ip++] & 0xff; final int i1 = ip < iLen ? in[ip++] & 0xff : 0; final int i2 = ip < iLen ? in[ip++] & 0xff : 0; final int o0 = i0 >>> 2; final int o1 = ((i0 & 3) << 4) | (i1 >>> 4); final int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); final int o3 = i2 & 0x3F; out[op++] = Base64Coder.map1[o0]; out[op++] = Base64Coder.map1[o1]; out[op] = op < oDataLen ? Base64Coder.map1[o2] : '='; op++; out[op] = op < oDataLen ? Base64Coder.map1[o3] : '='; op++; } return out; } /** * Encodes a string into Base64 format. No blanks or line breaks are * inserted. * * @param s * a String to be encoded. * @return A String with the Base64 encoded data. */ public static String encodeString(final String s) { return new String(Base64Coder.encode(s.getBytes())); } /** Dummy constructor. */ private Base64Coder() {} }
HexCoder
package lslAESCrypto; /** * The following is a simple set of static methods for converting from hex to * bytes and vice-versa * * @author Haravikk Mistral * @date Sep 15, 2008, 3:26:42 PM * @version 1.0 */ public class HexCoder { /** * Quick converts bytes to hex-characters * * @param bytes * the byte-array to convert * @return the hex-representation */ public static String bytesToHex(final byte[] bytes) { final StringBuffer s = new StringBuffer(bytes.length * 2); for (int i = 0; i < bytes.length; ++i) { s.append(Character.forDigit((bytes[i] >> 4) & 0xF, 16)); s.append(Character.forDigit(bytes[i] & 0xF, 16)); } return s.toString(); } /** * Quickly converts hex-characters to bytes * * @param s * the hex-string * @return the bytes represented */ public static byte[] hexToBytes(final String s) { final byte[] bytes = new byte[s.length() / 2]; for (int i = 0; i < bytes.length; ++i) bytes[i] = (byte) Integer.parseInt( s.substring(2 * i, (2 * i) + 2), 16); return bytes; } }
Class
package lslAESCrypto; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * <p> * This is a class designed to process AES messages sent from the LSL * implementation of AES which can be found here:<br/> <a * href="https://wiki.secondlife.com/wiki/AES_LSL_Implementation">https://wiki.secondlife.com/wiki/AES_LSL_Implementation</a> * </p> * <p> * This Java class will be updated to support the same modes of operation as the * LSL implementation. It currently assumes that keys and input-vectors are * processed as hex-strings, and that text is received as plain-text, while * ciphertext will be handled as base64 strings. * </p> * * @author Haravikk * @date Sep 15, 2008, 4:18:48 PM * @version 1.0 */ public class LSLAESCrypto { /** Our currently set block-cipher mode */ protected LSLAESCryptoMode mode = LSLAESCryptoMode.CBC; /** Out currently set padding mode */ protected LSLAESCryptoPad pad = LSLAESCryptoPad.NoPadding; /** The currently loaded key */ protected SecretKeySpec keySpec = null; /** The currently loaded input-vector */ protected IvParameterSpec ivSpec = null; /** The currently active cipher */ protected Cipher cipher = null; /** * Creates an instance of an LSL compatible AES handler. * * @param mode * the cipher-block mode of operation * @param pad * the padding scheme to use * @param hexKey * the key to start with (represented as hexadecimal string) * @param hexIV * the input vector to start with (represented as hexadecimal * string) * @throws NoSuchAlgorithmException * if the AES algorithm is not supported by the current JVM * @throws NoSuchPaddingException * if the padding scheme chosen is not supported by the current * JVM */ public LSLAESCrypto( final LSLAESCryptoMode mode, final LSLAESCryptoPad pad, final String hexKey, final String hexIV) throws NoSuchAlgorithmException, NoSuchPaddingException { this.init(mode, pad, hexKey, hexIV); } /** * Decrypts a base64 ciphertext into plain-text * * @param base64ciphertext * the ciphertext to decrypt * @return the plain-text that was originally encrypted * @throws InvalidKeyException * if the currently loaded key is not valid * @throws InvalidAlgorithmParameterException * if the AES algorithm is not supported by the current JVM * @throws IllegalBlockSizeException * if the ciphertext is somehow unreadable (bad base64 * conversion) * @throws BadPaddingException * if the chosen mode of operation requires padded data */ public String decrypt(final String base64ciphertext) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { this.cipher.init(Cipher.DECRYPT_MODE, this.keySpec, this.ivSpec); return new String( this.cipher.doFinal(Base64Coder.decode(base64ciphertext))); } /** * Encrypts plain-text into a base64 string * * @param text * the plain-text to encrypt * @return the base64 ciphertext produced * @throws IllegalBlockSizeException * if the plain text is somehow invalid * @throws BadPaddingException * if the chosen mode of operation requires padded data * @throws InvalidKeyException * if the currently loaded key is invalid * @throws InvalidAlgorithmParameterException * if the AES algorithm is not supported by the current JVM */ public String encrypt(final String text) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { this.cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, this.ivSpec); return new String( Base64Coder.encode(this.cipher.doFinal(text.getBytes()))); } /** * Initialises this AES instance with a mode, pad, key, and input vector in * a single operation * * @param mode * the cipher-block mode of operation * @param pad * the padding scheme to use * @param hexKey * the key to use as a hexadecimal string * @param hexIV * the input-vector to use as a hexadecimal string * @throws NoSuchAlgorithmException * if the AES algorithm is not supported by the current JVM * @throws NoSuchPaddingException * if the padding method is not supported by the current JVM */ public void init( final LSLAESCryptoMode mode, final LSLAESCryptoPad pad, final String hexKey, final String hexIV) throws NoSuchAlgorithmException, NoSuchPaddingException { if ((mode == null) || (pad == null) || (hexKey == null) || (hexIV == null)) throw new IllegalArgumentException("No arguments may be null"); this.setMode(mode); this.setPad(pad); this.setKey(hexKey); this.setInputVector(hexIV); this.createCipher(); } /** * Sets the input-vector for this engine to use * * @param hexIV * a hexadecimal input-vector to use */ public void setInputVector(final String hexIV) { if (hexIV == null) throw new IllegalArgumentException("Input-vector may not be null!"); this.ivSpec = new IvParameterSpec(HexCoder.hexToBytes(hexIV)); } /** * Sets the key for this engine to use * * @param hexKey * a hexadecimal key to use */ public void setKey(final String hexKey) { if (hexKey == null) throw new IllegalArgumentException("Key may not be null!"); this.keySpec = new SecretKeySpec(HexCoder.hexToBytes(hexKey), "AES"); } /** * Sets the mode of this implementation * * @param mode * the mode to set */ public void setMode(final LSLAESCryptoMode mode) { if (mode == null) throw new IllegalArgumentException("Mode may not be null!"); this.mode = mode; } /** * Sets the padding scheme of this implementation * * @param pad * the padding scheme to use */ public void setPad(final LSLAESCryptoPad pad) { if (pad == null) throw new IllegalArgumentException("Pad may not be null!"); this.pad = pad; } /** * Creates a new cipher instance for processing * * @throws NoSuchPaddingException * if the padding scheme set is invalid * @throws NoSuchAlgorithmException * if AES is not supported by this JVM */ protected void createCipher() throws NoSuchAlgorithmException, NoSuchPaddingException { this.cipher = Cipher.getInstance("AES/" + this.mode + "/" + this.pad); } /** Defines modes of operation combatible with LSL */ public enum LSLAESCryptoMode { /** Cipher-Block-Chaining mode */ CBC, /** Cipher FeedBack mode */ CFB; } /** Defines padding schemes compatible with LSL */ public enum LSLAESCryptoPad { /** No padding. Doesn't work with CBC. */ NoPadding, /** Equivalent to PAD_ZEROES <b>or</b> PAD_RANDOM in LSL */ ISO10126Padding; } }
Examples
Encryption
import lslAESCrypto.LSLAESCrypto; import lslAESCrypto.LSLAESCrypto.LSLAESCryptoMode; import lslAESCrypto.LSLAESCrypto.LSLAESCryptoPad; /** */ public class ExampleEncrypt { /** * @param args * @throws Exception */ public static void main(final String[] args) throws Exception { final String myKey = "1234567890ABCDEF0123456789ABCDEF"; final String myIV = "89ABCDEF0123456789ABCDEF01234567"; final String myMsg = "Hello world! I am a lovely message waiting to be encrypted!"; final LSLAESCrypto aes = new LSLAESCrypto( LSLAESCryptoMode.CFB, LSLAESCryptoPad.NoPadding, myKey, myIV); System.out.println(aes.encrypt(myMsg)); } }
Decryption
import lslAESCrypto.LSLAESCrypto; import lslAESCrypto.LSLAESCrypto.LSLAESCryptoMode; import lslAESCrypto.LSLAESCrypto.LSLAESCryptoPad; /** */ public class ExampleDecrypt { /** * @param args * @throws Exception */ public static void main(final String[] args) throws Exception { final String myKey = "1234567890ABCDEF0123456789ABCDEF"; final String myIV = "89ABCDEF0123456789ABCDEF01234567"; final String myMsg = "Mdn6jGTwRPMOKTYTTdDKGm9KScz26LIz96KVOGAeMw3hpwByPfa07PDRHxRW4TIh5dmu5LlhKpTQChi="; final LSLAESCrypto aes = new LSLAESCrypto( LSLAESCryptoMode.CFB, LSLAESCryptoPad.NoPadding, myKey, myIV); System.out.println(aes.decrypt(myMsg)); } }