Skip to content

Commit 7462310

Browse files
committedJul 6, 2020
Using (and specifying) 128-bit authentication tag for future AES(-GCM) encryption. Supports legacy format with unspecified tag length for decryption.
1 parent fc6eeb6 commit 7462310

File tree

2 files changed

+45
-6
lines changed

2 files changed

+45
-6
lines changed
 

‎src/main/java/com/softwareverde/cryptography/aes/AesKey.java

+16-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class AesKey {
1919

2020
private static final String KEY_ALGORITHM = "AES";
2121
private static final String ENCRYPTION_CIPHER = "AES/GCM/NoPadding"; // Using GCM instead of CBC as it provides authentication
22+
private static final int LEGACY_AUTHENTICATION_TAG_LENGTH = 12; // default is tag length is not specified
23+
private static final int AUTHENTICATION_TAG_LENGTH = 16; // max allowed value
2224
private static final int INITIALIZATION_VECTOR_LENGTH = 12; // IV size of 12-bytes is specifically recommended for AES-GCM (more efficient than other lengths)
2325

2426
private SecretKey _key;
@@ -77,13 +79,14 @@ public byte[] encrypt(byte[] plainText) {
7779
try {
7880
final Cipher aesCipher = Cipher.getInstance(ENCRYPTION_CIPHER);
7981
final byte[] initializationVectorBytes = _createInitializationVector();
80-
final AlgorithmParameterSpec initializationVector = new GCMParameterSpec(initializationVectorBytes.length * Byte.SIZE, initializationVectorBytes);
82+
final AlgorithmParameterSpec initializationVector = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH * Byte.SIZE, initializationVectorBytes);
8183
aesCipher.init(Cipher.ENCRYPT_MODE, _key, initializationVector);
8284
final byte[] cipherText = aesCipher.doFinal(plainText);
8385

8486
// prefix cipher text with initialization vector
8587
final ByteArrayBuilder byteArrayBuilder = new ByteArrayBuilder();
86-
byteArrayBuilder.appendByte((byte) initializationVectorBytes.length);
88+
byteArrayBuilder.appendByte((byte) (0x80 | initializationVectorBytes.length));
89+
byteArrayBuilder.appendByte((byte) AUTHENTICATION_TAG_LENGTH);
8790
byteArrayBuilder.appendBytes(initializationVectorBytes);
8891
byteArrayBuilder.appendBytes(cipherText);
8992

@@ -100,10 +103,17 @@ public byte[] encrypt(byte[] plainText) {
100103
public byte[] decrypt(byte[] cipherText) {
101104
try {
102105
// remove initialization vector from cipher text
103-
final byte initializationVectorLength = cipherText[0];
104-
final int cipherTextOffset = ByteUtil.byteToInteger(initializationVectorLength) + 1;
105-
final byte[] initializationVectorBytes = Arrays.copyOfRange(cipherText, 1, cipherTextOffset);
106-
final AlgorithmParameterSpec initializationVector = new GCMParameterSpec(initializationVectorLength * Byte.SIZE, initializationVectorBytes);
106+
byte initializationVectorLength = cipherText[0];
107+
int authenticationTagLength = LEGACY_AUTHENTICATION_TAG_LENGTH;
108+
int ivStartIndex = 1;
109+
if ((initializationVectorLength & 0x80) != 0) {
110+
initializationVectorLength = (byte) (initializationVectorLength ^ 0x80);
111+
authenticationTagLength = cipherText[1];
112+
ivStartIndex = 2;
113+
}
114+
int cipherTextOffset = ByteUtil.byteToInteger(initializationVectorLength) + ivStartIndex;
115+
final byte[] initializationVectorBytes = Arrays.copyOfRange(cipherText, ivStartIndex, cipherTextOffset);
116+
final AlgorithmParameterSpec initializationVector = new GCMParameterSpec(authenticationTagLength * Byte.SIZE, initializationVectorBytes);
107117
final byte[] encryptedData = Arrays.copyOfRange(cipherText, cipherTextOffset, cipherText.length);
108118

109119
final Cipher aesCipher = Cipher.getInstance(ENCRYPTION_CIPHER);

‎src/test/java/com/softwareverde/cryptography/aes/AesKeyTests.java

+29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.softwareverde.cryptography.aes;
22

33
import com.softwareverde.constable.bytearray.MutableByteArray;
4+
import com.softwareverde.util.HexUtil;
45
import com.softwareverde.util.StringUtil;
56
import com.softwareverde.util.Util;
67
import org.junit.Assert;
@@ -40,4 +41,32 @@ public void should_encrypt_and_decrypt_binary_data() throws IOException {
4041
Assert.assertFalse(Util.areEqual(encryptedData, decryptedData));
4142
Assert.assertTrue(Util.areEqual(data, decryptedData));
4243
}
44+
45+
@Test
46+
public void should_perform_legacy_decryption_with_implied_authentication_tag_length() {
47+
// Setup
48+
final AesKey aesKey = new AesKey(HexUtil.hexStringToByteArray("943CBFA720FA5B76BC8C31CDB618E166221AE8644D3B00F567C77EA4DBD7D9AA"));
49+
final MutableByteArray data = MutableByteArray.wrap(StringUtil.stringToBytes("Test"));
50+
final byte[] cipherText = HexUtil.hexStringToByteArray("0C9550E3017B60CEFB003B4E54A026DFC7533DC92F229CB78D63B42021");
51+
52+
// Action
53+
final byte[] decryptedData = aesKey.decrypt(cipherText);
54+
55+
// Assert
56+
Assert.assertTrue(Util.areEqual(data, decryptedData));
57+
}
58+
59+
@Test
60+
public void should_perform_decryption_with_specified_authentication_tag_length() {
61+
// Setup
62+
final AesKey aesKey = new AesKey(HexUtil.hexStringToByteArray("8AB983E6F8E71519D2B683E564047A7D8CD27E2DC8D6C3DED0C41CA1B1BB8287"));
63+
final MutableByteArray data = MutableByteArray.wrap(StringUtil.stringToBytes("Test"));
64+
final byte[] cipherText = HexUtil.hexStringToByteArray("8C10E19046F0A887F4893C04E28398CDD7F00522E8BB9D96B2B0B1D7ED080E7E8D7A");
65+
66+
// Action
67+
final byte[] decryptedData = aesKey.decrypt(cipherText);
68+
69+
// Assert
70+
Assert.assertTrue(Util.areEqual(data, decryptedData));
71+
}
4372
}

0 commit comments

Comments
 (0)
Please sign in to comment.