Difference between revisions of "AES Java Implementation"

From Second Life Wiki
Jump to navigation Jump to search
Line 279: Line 279:
import java.security.InvalidKeyException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;


import javax.crypto.BadPaddingException;
import javax.crypto.BadPaddingException;
Line 290: Line 292:
  * <p>
  * <p>
  * This is a class designed to process AES messages sent from the LSL
  * This is a class designed to process AES messages sent from the LSL
  * implementation of AES which can be found here:<br/> <a
  * implementation of AES which can be found here:<br/>
  * href="https://wiki.secondlife.com/wiki/AES_LSL_Implementation">https://wiki.secondlife.com/wiki/AES_LSL_Implementation</a>
* <a
  * href="https://wiki.secondlife.com/wiki/AES_LSL_Implementation">https://wiki
* .secondlife.com/wiki/AES_LSL_Implementation</a>
  * </p>
  * </p>
  * <p>
  * <p>
Line 307: Line 311:
/** Our currently set block-cipher mode */
/** Our currently set block-cipher mode */
protected LSLAESCryptoMode mode = LSLAESCryptoMode.CBC;
protected LSLAESCryptoMode mode = LSLAESCryptoMode.CBC;
/** Used to detect when a new {@link Cipher} Is needed. */
protected boolean modeChanged = false;


/** Out currently set padding mode */
/** Our currently set padding mode */
protected LSLAESCryptoPad pad = LSLAESCryptoPad.NoPadding;
protected LSLAESCryptoPad pad = LSLAESCryptoPad.NONE;
/** Our currently set pad-size */
protected int padSize = 512;


/** The currently loaded key */
/** The currently loaded key */
Line 318: Line 326:
/** The currently active cipher */
/** The currently active cipher */
protected Cipher cipher = null;
protected Cipher cipher = null;
/** A random class for secure random operations. */
protected Random random = new SecureRandom();


/**
/**
Line 326: Line 337:
* @param pad
* @param pad
*            the padding scheme to use
*            the padding scheme to use
* @param padSize
*            the block-size to use when padding. Must be a non-zero,
*            positive value that is a multiple of 128.
* @param hexKey
* @param hexKey
*            the key to start with (represented as hexadecimal string)
*            the key to start with (represented as hexadecimal string)
Line 339: Line 353:
public LSLAESCrypto(
public LSLAESCrypto(
final LSLAESCryptoMode mode,
final LSLAESCryptoMode mode,
final LSLAESCryptoPad pad,
final LSLAESCryptoPad pad,
final String hexKey,
final int padSize,
final String hexIV)
final String hexKey,
final String hexIV)
throws NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
NoSuchPaddingException {
NoSuchPaddingException {
this.init(mode, pad, hexKey, hexIV);
this.init(mode, pad, padSize, hexKey, hexIV);
}
}


Line 365: Line 380:
public String decrypt(final String base64ciphertext)
public String decrypt(final String base64ciphertext)
throws InvalidKeyException,
throws InvalidKeyException,
InvalidAlgorithmParameterException,
InvalidAlgorithmParameterException,
IllegalBlockSizeException,
IllegalBlockSizeException,
BadPaddingException {
BadPaddingException {
if (this.modeChanged) try {
this.createCipher();
} catch (final Exception e) { /* Do nothing */}
 
this.cipher.init(Cipher.DECRYPT_MODE, this.keySpec, this.ivSpec);
this.cipher.init(Cipher.DECRYPT_MODE, this.keySpec, this.ivSpec);
return new String(
return new String(this.cipher.doFinal(Base64Coder
this.cipher.doFinal(Base64Coder.decode(base64ciphertext)));
.decode(base64ciphertext)));
}
}


Line 390: Line 409:
public String encrypt(final String text)
public String encrypt(final String text)
throws IllegalBlockSizeException,
throws IllegalBlockSizeException,
BadPaddingException,
BadPaddingException,
InvalidKeyException,
InvalidKeyException,
InvalidAlgorithmParameterException {
InvalidAlgorithmParameterException {
if (this.modeChanged) try {
this.createCipher();
} catch (final Exception e) { /* Do nothing */}
 
this.cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, this.ivSpec);
this.cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, this.ivSpec);
return new String(
 
Base64Coder.encode(this.cipher.doFinal(text.getBytes())));
byte[] data = text.getBytes();
int bits = data.length * 8;
 
/* Apply padding */
LSLAESCryptoPad padding = this.pad;
if (padding == LSLAESCryptoPad.NONE) {
if (this.mode == LSLAESCryptoMode.CFB) { return Base64Coder
.encodeString(this.cipher.doFinal(data), 0, bits); }
padding = LSLAESCryptoPad.RBT;
}
 
int blockSize = this.padSize;
if (padding == LSLAESCryptoPad.RBT) blockSize = 128;
 
final int blocks = bits / blockSize;
int extra = bits % blockSize;
 
if (padding == LSLAESCryptoPad.RBT) {
if (extra > 0) {
/*
* This scheme takes the last encrypted block, encrypts it
* again, and XORs it with any leftover data, maintaining
* data-length. If input is less than a block in size, then the
* current input-vector is used.
*/
int bytes = extra / 8;
if ((bytes * 8) < extra) ++bytes;
 
// Grab leftover bytes
final byte[] t = new byte[bytes];
if (bytes > 0)
System.arraycopy(data, data.length - bytes, t, 0, bytes);
 
// Encrypt all other data.
byte[] lb;
if (blocks < 1) {
// If not enough for a block, double-encrypt IV.
data = new byte[0];
lb =
this.cipher.doFinal(this.cipher.doFinal(this.ivSpec
.getIV()));
} else {
// If there are blocks, then double-encrypt final one.
data = this.cipher.doFinal(data, 0, data.length - bytes);
lb = this.cipher.doFinal(data, data.length - 16, 16);
}
 
// XOR lb with t.
for (int i = 0; i < t.length; ++i)
t[i] ^= lb[i];
 
lb = new byte[data.length + t.length];
System.arraycopy(data, 0, lb, 0, data.length);
System.arraycopy(t, 0, lb, data.length, t.length);
 
return Base64Coder.encodeString(lb);
}
return Base64Coder.encodeString(this.cipher.doFinal(data), 0, bits);
}
 
// Padding schemes that add bytes until block-boundary is reached.
extra = blockSize - extra;
 
if (padding == LSLAESCryptoPad.NULLS_SAFE) {
++bits;
final int bytes = bits / 8;
final int bit = bytes % 8;
 
if (bytes < data.length) data[bytes] |= (1 << (8 - bit));
else {
final byte[] t = new byte[data.length + 1];
System.arraycopy(data, 0, t, 0, data.length);
t[data.length] = (byte) 0x80;
data = t;
}
 
if ((--extra) < 0) extra += blockSize;
padding = LSLAESCryptoPad.NULLS;
}
 
int bytes = extra / 8;
if (bytes <= 0) {
if (padding == LSLAESCryptoPad.NULLS)
return Base64Coder.encodeString(
this.cipher.doFinal(data),
0,
bits);
 
bytes = blockSize / 8;
extra += blockSize;
}
 
bits += extra;
final byte[] t = new byte[data.length + bytes];
int i = data.length;
System.arraycopy(data, 0, t, 0, data.length);
data = t;
 
for (; i < data.length; ++i) {
byte b = 0;
if ((i >= (data.length - 4)) && (padding != LSLAESCryptoPad.NULLS)) b =
(byte) bytes;
else if (padding == LSLAESCryptoPad.RANDOM)
b = (byte) this.random.nextInt(256);
 
data[i] = b;
}
 
return Base64Coder.encodeString(this.cipher.doFinal(data), 0, bits);
}
}


Line 406: Line 537:
* @param pad
* @param pad
*            the padding scheme to use
*            the padding scheme to use
* @param padSize
*            the block-size to use when padding. Must be a non-zero,
*            positive value that is a multiple of 128.
* @param hexKey
* @param hexKey
*            the key to use as a hexadecimal string
*            the key to use as a hexadecimal string
Line 418: Line 552:
final LSLAESCryptoMode mode,
final LSLAESCryptoMode mode,
final LSLAESCryptoPad pad,
final LSLAESCryptoPad pad,
final int padSize,
final String hexKey,
final String hexKey,
final String hexIV)
final String hexIV)
throws NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
NoSuchPaddingException {
NoSuchPaddingException {
if ((mode == null) || (pad == null) || (hexKey == null)
if ((mode == null) || (pad == null) || (hexKey == null) ||
|| (hexIV == null))
(hexIV == null))
throw new IllegalArgumentException("No arguments may be null");
throw new IllegalArgumentException("No arguments may be null");


this.setMode(mode);
this.setMode(mode);
this.setPad(pad);
this.setPad(pad, padSize);
this.setKey(hexKey);
this.setKey(hexKey);
this.setInputVector(hexIV);
this.setInputVector(hexIV);
this.random.nextInt();


this.createCipher();
this.createCipher();
Line 471: Line 608:


this.mode = mode;
this.mode = mode;
this.modeChanged = true;
}
}


Line 480: Line 618:
*/
*/
public void setPad(final LSLAESCryptoPad pad) {
public void setPad(final LSLAESCryptoPad pad) {
this.setPad(pad, this.padSize);
}
/**
* Sets the padding scheme of this implementation
*
* @param pad
*            the padding scheme to use
* @param padSize
*            the block-size to use when padding. Must be a non-zero,
*            positive value that is a multiple of 128.
*/
public void setPad(final LSLAESCryptoPad pad, final int padSize) {
if (pad == null)
if (pad == null)
throw new IllegalArgumentException("Pad may not be null!");
throw new IllegalArgumentException("Pad may not be null!");
if ((padSize <= 0) || ((padSize % 128) > 0))
throw new IllegalArgumentException(
"Pad size may not be less than zero, and must be a multiple of 128");


this.pad = pad;
this.pad = pad;
this.padSize = padSize;
}
}


Line 496: Line 651:
protected void createCipher()
protected void createCipher()
throws NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
NoSuchPaddingException {
NoSuchPaddingException {
this.cipher = Cipher.getInstance("AES/" + this.mode + "/" + this.pad);
this.cipher = Cipher.getInstance("AES/" + this.mode + "/NoPadding");
}
}


Line 510: Line 665:
/** Defines padding schemes compatible with LSL */
/** Defines padding schemes compatible with LSL */
public enum LSLAESCryptoPad {
public enum LSLAESCryptoPad {
/** No padding. Doesn't work with CBC. */
/** Performs no padding, will switch to RBT if mode is CBC. */
NoPadding,
NONE,
/** Equivalent to PAD_ZEROES <b>or</b> PAD_RANDOM in LSL */
/**
ISO10126Padding;
* Enables CFB mode temporarily for the final complete block, and
* combines with data. This preserves data-length.
*/
RBT,
/**
* Adds null-bytes to the end of the data until it is of correct-size.
* This is an padding scheme (may result in loss of null-bytes from
* original data).
*/
NULLS,
/**
* Same as NULLS, except that it first appends a single '1' bit to the
* data before padding.
*/
NULLS_SAFE,
/**
* Appends null-bytes to the data until one word from block-size, final
* word is then populated with bytes describing the number of padding
* bytes added.
*/
ZEROES,
/**
* Same as ZEROES, except that random-bytes are used in place of
* null-bytes.
*/
RANDOM;
}
}
}</java>
}</java>

Revision as of 07:38, 20 August 2009

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

<java>package lslAESCrypto;

/**

* A Base64 Encoder/Decoder.

*

* This class is used to encode and decode data in Base64 format as described in * RFC 1521. *

*/

public class Base64Coder { /** Mapping table from 6-bits to Base64 characters. */ private static char[] BITS_TO_BASE64_CHAR = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

/** Mapping table from Base64 characters to 6-bits. */ private static byte[] BASE64_CHAR_TO_BITS = new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, };

/** * 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) throws IllegalArgumentException { int len = in.length; if (len % 4 != 0) throw new IllegalArgumentException( "Length of Base64 encoded input string is not a multiple of 4.");

// Ignore trailing equals while ((len > 0) && (in[len - 1] == '=')) --len;

final byte[] bytes = new byte[(len * 3) / 4]; int o = 0;

for (int i = 0; i < len;) { try { final char c0 = in[i++]; final char c1 = in[i++]; final char c2 = (i < len) ? in[i++] : 'A'; final char c3 = (i < len) ? in[i++] : 'A';

if ((c0 > 127) || (c1 > 127) || (c2 > 127) || (c3 > 127)) throw new IllegalArgumentException( "Invalid base64 character");

final byte b0 = Base64Coder.BASE64_CHAR_TO_BITS[c0]; final byte b1 = Base64Coder.BASE64_CHAR_TO_BITS[c1]; final byte b2 = Base64Coder.BASE64_CHAR_TO_BITS[c2]; final byte b3 = Base64Coder.BASE64_CHAR_TO_BITS[c3];

if ((b0 < 0) || (b1 < 0) || (b2 < 0) || (b3 < 0)) throw new IllegalArgumentException( "Invalid base64 character");

bytes[o++] = (byte) ((b0 << 2) | (b1 >>> 4)); if (o < bytes.length) { bytes[o++] = (byte) (((b1 & 0xF) << 4) | (b2 >>> 2)); if (o < bytes.length) bytes[o++] = (byte) (((b2 & 0x3) << 6) | b3); } } catch (final ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("Invalid base64 character"); } }

return bytes; }

/** * 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, 0, 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 offset * the offset into the array at which to begin reading. * @param bits * number of bits to process from in. * @return A character array with the Base64 encoded data. */ public static char[] encode( final byte[] in, final int offset, final int bits) { int length = bits / 8; if ((length * 8) < bits) ++length;

final char[] chars = new char[((length + 2) / 3) * 4]; final int out = ((length * 4) + 2) / 3;

int mask = ~(-1 << (8 - (bits % 8))) | ~(-1 << (bits % 8)); if (mask == 0) mask = 0xFF;

int o = 0; final int end = length + offset; for (int i = offset; i < end;) { final int b0 = ((i + 1) == end) ? in[i++] & mask : in[i++] & 0xFF; final int b1 = (i < length) ? (((i + 1) == end) ? in[i++] & mask : in[i++] & 0xFF) : 0; final int b2 = (i < length) ? (((i + 1) == end) ? in[i++] & mask : in[i++] & 0xFF) : 0;

final int i0 = (b0 >>> 2); final int i1 = (((b0 & 0x3) << 4) | (b1 >>> 4)); final int i2 = (((b1 & 0xF) << 2) | (b2 >>> 6)); final int i3 = b2 & 0x3F;

chars[o++] = Base64Coder.BITS_TO_BASE64_CHAR[i0]; chars[o++] = Base64Coder.BITS_TO_BASE64_CHAR[i1]; chars[o] = (o < out) ? Base64Coder.BITS_TO_BASE64_CHAR[i2] : '='; ++o; chars[o] = (o < out) ? Base64Coder.BITS_TO_BASE64_CHAR[i3] : '='; ++o; }

return chars; }

/** * Produces a base64 string from the provided byte-array. * * @param bytes * the byte-array to read-from. * @return the base64 encoded string produced. */ public static String encodeString(final byte[] bytes) { return Base64Coder.encodeString(bytes, 0, bytes.length); }

/** * Produces a base64 string from the provided byte-array slice. * * @param bytes * the byte-array to read-from. * @param offset * the offset into the array at which to begin reading. * @param bits * number of bits to process from bytes. * @return the base64 encoded string produced. */ public static String encodeString( final byte[] bytes, final int offset, final int bits) { return new String(Base64Coder.encode(bytes, offset, bits)); }

/** * 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() { /* Blocking constructor */}

}</java>

HexCoder

<java>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; } } </java>

Class

<java>package lslAESCrypto;

import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random;

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;

/**

*

* This is a class designed to process AES messages sent from the LSL * implementation of AES which can be found here:
* <a * href="https://wiki.secondlife.com/wiki/AES_LSL_Implementation">https://wiki * .secondlife.com/wiki/AES_LSL_Implementation</a> *

*

* 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. *

* 
* @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; /** Used to detect when a new {@link Cipher} Is needed. */ protected boolean modeChanged = false;

/** Our currently set padding mode */ protected LSLAESCryptoPad pad = LSLAESCryptoPad.NONE; /** Our currently set pad-size */ protected int padSize = 512;

/** 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;

/** A random class for secure random operations. */ protected Random random = new SecureRandom();

/** * 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 padSize * the block-size to use when padding. Must be a non-zero, * positive value that is a multiple of 128. * @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 int padSize, final String hexKey, final String hexIV) throws NoSuchAlgorithmException, NoSuchPaddingException { this.init(mode, pad, padSize, 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 { if (this.modeChanged) try { this.createCipher(); } catch (final Exception e) { /* Do nothing */}

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 { if (this.modeChanged) try { this.createCipher(); } catch (final Exception e) { /* Do nothing */}

this.cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, this.ivSpec);

byte[] data = text.getBytes(); int bits = data.length * 8;

/* Apply padding */ LSLAESCryptoPad padding = this.pad; if (padding == LSLAESCryptoPad.NONE) { if (this.mode == LSLAESCryptoMode.CFB) { return Base64Coder .encodeString(this.cipher.doFinal(data), 0, bits); } padding = LSLAESCryptoPad.RBT; }

int blockSize = this.padSize; if (padding == LSLAESCryptoPad.RBT) blockSize = 128;

final int blocks = bits / blockSize; int extra = bits % blockSize;

if (padding == LSLAESCryptoPad.RBT) { if (extra > 0) { /* * This scheme takes the last encrypted block, encrypts it * again, and XORs it with any leftover data, maintaining * data-length. If input is less than a block in size, then the * current input-vector is used. */ int bytes = extra / 8; if ((bytes * 8) < extra) ++bytes;

// Grab leftover bytes final byte[] t = new byte[bytes]; if (bytes > 0) System.arraycopy(data, data.length - bytes, t, 0, bytes);

// Encrypt all other data. byte[] lb; if (blocks < 1) { // If not enough for a block, double-encrypt IV. data = new byte[0]; lb = this.cipher.doFinal(this.cipher.doFinal(this.ivSpec .getIV())); } else { // If there are blocks, then double-encrypt final one. data = this.cipher.doFinal(data, 0, data.length - bytes); lb = this.cipher.doFinal(data, data.length - 16, 16); }

// XOR lb with t. for (int i = 0; i < t.length; ++i) t[i] ^= lb[i];

lb = new byte[data.length + t.length]; System.arraycopy(data, 0, lb, 0, data.length); System.arraycopy(t, 0, lb, data.length, t.length);

return Base64Coder.encodeString(lb); } return Base64Coder.encodeString(this.cipher.doFinal(data), 0, bits); }

// Padding schemes that add bytes until block-boundary is reached. extra = blockSize - extra;

if (padding == LSLAESCryptoPad.NULLS_SAFE) { ++bits; final int bytes = bits / 8; final int bit = bytes % 8;

if (bytes < data.length) data[bytes] |= (1 << (8 - bit)); else { final byte[] t = new byte[data.length + 1]; System.arraycopy(data, 0, t, 0, data.length); t[data.length] = (byte) 0x80; data = t; }

if ((--extra) < 0) extra += blockSize; padding = LSLAESCryptoPad.NULLS; }

int bytes = extra / 8; if (bytes <= 0) { if (padding == LSLAESCryptoPad.NULLS) return Base64Coder.encodeString( this.cipher.doFinal(data), 0, bits);

bytes = blockSize / 8; extra += blockSize; }

bits += extra; final byte[] t = new byte[data.length + bytes]; int i = data.length; System.arraycopy(data, 0, t, 0, data.length); data = t;

for (; i < data.length; ++i) { byte b = 0; if ((i >= (data.length - 4)) && (padding != LSLAESCryptoPad.NULLS)) b = (byte) bytes; else if (padding == LSLAESCryptoPad.RANDOM) b = (byte) this.random.nextInt(256);

data[i] = b; }

return Base64Coder.encodeString(this.cipher.doFinal(data), 0, bits); }

/** * 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 padSize * the block-size to use when padding. Must be a non-zero, * positive value that is a multiple of 128. * @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 int padSize, 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, padSize); this.setKey(hexKey); this.setInputVector(hexIV);

this.random.nextInt();

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; this.modeChanged = true; }

/** * Sets the padding scheme of this implementation * * @param pad * the padding scheme to use */ public void setPad(final LSLAESCryptoPad pad) { this.setPad(pad, this.padSize); }

/** * Sets the padding scheme of this implementation * * @param pad * the padding scheme to use * @param padSize * the block-size to use when padding. Must be a non-zero, * positive value that is a multiple of 128. */ public void setPad(final LSLAESCryptoPad pad, final int padSize) { if (pad == null) throw new IllegalArgumentException("Pad may not be null!"); if ((padSize <= 0) || ((padSize % 128) > 0)) throw new IllegalArgumentException( "Pad size may not be less than zero, and must be a multiple of 128");

this.pad = pad; this.padSize = padSize; }

/** * 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 + "/NoPadding"); }

/** 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 { /** Performs no padding, will switch to RBT if mode is CBC. */ NONE, /** * Enables CFB mode temporarily for the final complete block, and * combines with data. This preserves data-length. */ RBT, /** * Adds null-bytes to the end of the data until it is of correct-size. * This is an padding scheme (may result in loss of null-bytes from * original data). */ NULLS, /** * Same as NULLS, except that it first appends a single '1' bit to the * data before padding. */ NULLS_SAFE, /** * Appends null-bytes to the data until one word from block-size, final * word is then populated with bytes describing the number of padding * bytes added. */ ZEROES, /** * Same as ZEROES, except that random-bytes are used in place of * null-bytes. */ RANDOM; } }</java>

Examples

Encryption

<java>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)); } }</java>

Decryption

<java>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)); } }</java>