Difference between revisions of "AES Java Implementation"
Jump to navigation
Jump to search
m (language tags to <source>) |
|||
(3 intermediate revisions by 2 users not shown) | |||
Line 4: | Line 4: | ||
= Required Classes = | = Required Classes = | ||
== Base64Coder == | == Base64Coder == | ||
<java>package lslAESCrypto; | <source lang="java">package lslAESCrypto; | ||
/** | /** | ||
* A Base64 Encoder/Decoder. | * A Base64 Encoder/Decoder. | ||
Line 10: | Line 11: | ||
* This class is used to encode and decode data in Base64 format as described in | * This class is used to encode and decode data in Base64 format as described in | ||
* RFC 1521. | * RFC 1521. | ||
* <p | * </p> | ||
*/ | */ | ||
public class Base64Coder { | public class Base64Coder { | ||
/** Mapping table from 6- | /** Mapping table from 6-bits to Base64 characters. */ | ||
private static | private static char[] BITS_TO_BASE64_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- | '/' }; | ||
private static | |||
/** Mapping table from Base64 characters to 6-bits. */ | |||
private static byte[] BASE64_CHAR_TO_BITS = | |||
{ -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, }; | |||
/** | /** | ||
Line 45: | Line 44: | ||
* if the input is not valid Base64 encoded data. | * if the input is not valid Base64 encoded data. | ||
*/ | */ | ||
public static byte[] decode(final char[] in) { | public static byte[] decode(final char[] in) | ||
int | throws IllegalArgumentException { | ||
if ( | int len = in.length; | ||
if (len % 4 != 0) | |||
throw new IllegalArgumentException( | throw new IllegalArgumentException( | ||
"Length of Base64 encoded input string is not a multiple of 4."); | "Length of Base64 encoded input string is not a multiple of 4."); | ||
while ( | |||
// Ignore trailing equals | |||
while (len > 0 && in[len - 1] == '=') | |||
final byte[] | --len; | ||
int | |||
int | final byte[] bytes = new byte[(len * 3) / 4]; | ||
int o = 0; | |||
final | |||
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 | |||
return bytes; | |||
} | } | ||
Line 116: | Line 127: | ||
*/ | */ | ||
public static char[] encode(final byte[] in) { | public static char[] encode(final byte[] in) { | ||
return Base64Coder.encode(in, in.length); | return Base64Coder.encode(in, 0, in.length); | ||
} | } | ||
Line 125: | Line 136: | ||
* @param in | * @param in | ||
* an array containing the data bytes to be encoded. | * an array containing the data bytes to be encoded. | ||
* @param | * @param offset | ||
* number of | * the offset into the array at which to begin reading. | ||
* @param bits | |||
* number of <b>bits</b> to process from <code>in</code>. | |||
* @return A character array with the Base64 encoded data. | * @return A character array with the Base64 encoded data. | ||
*/ | */ | ||
public static char[] encode(final byte[] in, final int | public static char[] encode( | ||
final byte[] in, | |||
final int offset, | |||
final int | final int bits) { | ||
int length = bits / 8; | |||
if ((length * 8) < bits) ++length; | |||
int | |||
int | final char[] chars = new char[((length + 2) / 3) * 4]; | ||
final int out = ((length * 4) + 2) / 3; | |||
final int | |||
final int | int mask = ~(-1 << (8 - (bits % 8))) | ~(-1 << (bits % 8)); | ||
final int | if (mask == 0) mask = 0xFF; | ||
final int | |||
final int | int o = 0; | ||
final int | final int end = length + offset; | ||
final int | 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 | |||
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 <b>bits</b> to process from <code>bytes</code>. | |||
* @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)); | |||
} | } | ||
Line 168: | Line 224: | ||
/** Dummy constructor. */ | /** Dummy constructor. */ | ||
private Base64Coder() {} | private Base64Coder() { /* Blocking constructor */} | ||
}</ | }</source> | ||
== HexCoder == | == HexCoder == | ||
<java>package lslAESCrypto; | <source lang="java">package lslAESCrypto; | ||
/** | /** | ||
* The following is a simple set of static methods for converting from hex to | * The following is a simple set of static methods for converting from hex to | ||
Line 215: | Line 271: | ||
} | } | ||
} | } | ||
</ | </source> | ||
= Class = | = Class = | ||
<java>package lslAESCrypto; | <source lang="java">package lslAESCrypto; | ||
import java.security.InvalidAlgorithmParameterException; | import java.security.InvalidAlgorithmParameterException; | ||
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 234: | 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 251: | 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; | |||
/** | /** Our currently set padding mode */ | ||
protected LSLAESCryptoPad pad = LSLAESCryptoPad. | protected LSLAESCryptoPad pad = LSLAESCryptoPad.NONE; | ||
/** Our currently set pad-size */ | |||
protected int padSize = 512; | |||
/** The currently loaded key */ | /** The currently loaded key */ | ||
Line 262: | 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 270: | 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 283: | Line 353: | ||
public LSLAESCrypto( | public LSLAESCrypto( | ||
final LSLAESCryptoMode mode, | final LSLAESCryptoMode mode, | ||
final LSLAESCryptoPad pad, | |||
final int padSize, | |||
final String hexKey, | |||
final String hexIV) | |||
throws NoSuchAlgorithmException, | throws NoSuchAlgorithmException, | ||
NoSuchPaddingException { | |||
this.init(mode, pad, hexKey, hexIV); | this.init(mode, pad, padSize, hexKey, hexIV); | ||
} | } | ||
Line 309: | Line 380: | ||
public String decrypt(final String base64ciphertext) | public String decrypt(final String base64ciphertext) | ||
throws InvalidKeyException, | 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); | this.cipher.init(Cipher.DECRYPT_MODE, this.keySpec, this.ivSpec); | ||
return new String( | return new String(this.cipher.doFinal(Base64Coder | ||
.decode(base64ciphertext))); | |||
} | } | ||
Line 334: | Line 409: | ||
public String encrypt(final String text) | public String encrypt(final String text) | ||
throws IllegalBlockSizeException, | 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); | this.cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, this.ivSpec); | ||
return new | |||
Base64Coder. | 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 350: | 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 362: | 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 { | |||
if ((mode == null) || (pad == null) || (hexKey == null) | if ((mode == null) || (pad == null) || (hexKey == 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 415: | Line 608: | ||
this.mode = mode; | this.mode = mode; | ||
this.modeChanged = true; | |||
} | } | ||
Line 424: | 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 440: | Line 651: | ||
protected void createCipher() | protected void createCipher() | ||
throws NoSuchAlgorithmException, | throws NoSuchAlgorithmException, | ||
NoSuchPaddingException { | |||
this.cipher = Cipher.getInstance("AES/" + this.mode + "/" | this.cipher = Cipher.getInstance("AES/" + this.mode + "/NoPadding"); | ||
} | } | ||
Line 454: | Line 665: | ||
/** Defines padding schemes compatible with LSL */ | /** Defines padding schemes compatible with LSL */ | ||
public enum LSLAESCryptoPad { | 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; | |||
} | } | ||
}</ | }</source> | ||
= Examples = | = Examples = | ||
== Encryption == | == Encryption == | ||
<java>import lslAESCrypto.LSLAESCrypto; | <source lang="java">import lslAESCrypto.LSLAESCrypto; | ||
import lslAESCrypto.LSLAESCrypto.LSLAESCryptoMode; | import lslAESCrypto.LSLAESCrypto.LSLAESCryptoMode; | ||
import lslAESCrypto.LSLAESCrypto.LSLAESCryptoPad; | import lslAESCrypto.LSLAESCrypto.LSLAESCryptoPad; | ||
Line 485: | Line 721: | ||
System.out.println(aes.encrypt(myMsg)); | System.out.println(aes.encrypt(myMsg)); | ||
} | } | ||
}</ | }</source> | ||
== Decryption == | == Decryption == | ||
<java>import lslAESCrypto.LSLAESCrypto; | <source lang="java">import lslAESCrypto.LSLAESCrypto; | ||
import lslAESCrypto.LSLAESCrypto.LSLAESCryptoMode; | import lslAESCrypto.LSLAESCrypto.LSLAESCryptoMode; | ||
import lslAESCrypto.LSLAESCrypto.LSLAESCryptoPad; | import lslAESCrypto.LSLAESCrypto.LSLAESCryptoPad; | ||
Line 510: | Line 746: | ||
System.out.println(aes.decrypt(myMsg)); | System.out.println(aes.decrypt(myMsg)); | ||
} | } | ||
}</ | }</source> |
Latest revision as of 10:15, 25 January 2015
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>
*/
public class Base64Coder {
/** Mapping table from 6-bits to Base64 characters. */
private static char[] BITS_TO_BASE64_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 =
{ -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 <b>bits</b> to process from <code>in</code>.
* @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 <b>bits</b> to process from <code>bytes</code>.
* @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 */}
}
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 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;
/**
* <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;
/** 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;
}
}
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));
}
}