| /* |
| * 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 |
| * |
| * https://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.apache.commons.crypto.cipher; |
| |
| import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| |
| import java.nio.ByteBuffer; |
| import java.security.Key; |
| import java.util.Arrays; |
| import java.util.Properties; |
| import java.util.Random; |
| |
| import javax.crypto.AEADBadTagException; |
| import javax.crypto.Cipher; |
| import javax.crypto.spec.GCMParameterSpec; |
| |
| import org.apache.commons.crypto.utils.AES; |
| import org.apache.commons.crypto.utils.Utils; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| import jakarta.xml.bind.DatatypeConverter; |
| |
| class GcmCipherTest { |
| |
| private static final String GCM_NO_PADDING = "AES/GCM/NoPadding"; |
| Properties props; |
| String cipherClass; |
| String transformation = GCM_NO_PADDING; |
| |
| private String[] kHex; |
| private String[] pHex; |
| private String[] ivHex; |
| private String[] aadHex; |
| private String[] cHex; |
| private String[] tHex; |
| |
| private void initTestData() { |
| |
| final int casesNumber = 4; |
| |
| kHex = new String[casesNumber]; |
| pHex = new String[casesNumber]; |
| ivHex = new String[casesNumber]; |
| aadHex = new String[casesNumber]; |
| cHex = new String[casesNumber]; |
| tHex = new String[casesNumber]; |
| |
| // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf |
| // https://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf |
| // NIST Case2 ----------------------------- |
| // key length: 16 bytes |
| // plain text length: 16 bytes |
| // iv length: 12 bytes |
| // aad length: 0 bytes |
| kHex[0] = "00000000000000000000000000000000"; |
| pHex[0] = "00000000000000000000000000000000"; |
| ivHex[0] = "000000000000000000000000"; |
| aadHex[0] = ""; |
| cHex[0] = "0388dace60b6a392f328c2b971b2fe78"; |
| tHex[0] = "ab6e47d42cec13bdf53a67b21257bddf"; |
| |
| // NIST Case4 --------------------------------- |
| // key length: 16 bytes |
| // plain text length: 60 bytes |
| // iv length: 12 bytes |
| // aad length: 20 bytes |
| kHex[1] = "feffe9928665731c6d6a8f9467308308"; |
| pHex[1] = "d9313225f88406e5a55909c5aff5269a" |
| + "86a7a9531534f7da2e4c303d8a318a72" |
| + "1c3c0c95956809532fcf0e2449a6b525" |
| + "b16aedf5aa0de657ba637b39"; |
| ivHex[1] = "cafebabefacedbaddecaf888"; |
| aadHex[1] = "feedfacedeadbeeffeedfacedeadbeef" |
| + "abaddad2"; |
| cHex[1] = "42831ec2217774244b7221b784d0d49c" |
| + "e3aa212f2c02a4e035c17e2329aca12e" |
| + "21d514b25466931c7d8f6a5aac84aa05" |
| + "1ba30b396a0aac973d58e091"; |
| tHex[1] = "5bc94fbc3221a5db94fae95ae7121a47"; |
| |
| // NIST Case5 --------------------------------- |
| // key length: 16 bytes |
| // plain text length: 60 bytes |
| // iv length: 8 bytes |
| // aad length: 20 bytes |
| kHex[2] = "feffe9928665731c6d6a8f9467308308"; |
| pHex[2] = "d9313225f88406e5a55909c5aff5269a" |
| + "86a7a9531534f7da2e4c303d8a318a72" |
| + "1c3c0c95956809532fcf0e2449a6b525" |
| + "b16aedf5aa0de657ba637b39"; |
| ivHex[2] ="cafebabefacedbad"; // 64bits < 96bits |
| aadHex[2]="feedfacedeadbeeffeedfacedeadbeef" |
| + "abaddad2"; |
| cHex[2] = "61353b4c2806934a777ff51fa22a4755" |
| + "699b2a714fcdc6f83766e5f97b6c7423" |
| + "73806900e49f24b22b097544d4896b42" |
| + "4989b5e1ebac0f07c23f4598"; |
| tHex[2] = "3612d2e79e3b0785561be14aaca2fccb"; |
| |
| // NIST Case6 --------------------------------- |
| // key length: 16 bytes |
| // plain text length: 60 bytes |
| // iv length: 60 bytes |
| // aad length: 20 bytes |
| kHex[3] = "feffe9928665731c6d6a8f9467308308"; |
| pHex[3] = "d9313225f88406e5a55909c5aff5269a" |
| + "86a7a9531534f7da2e4c303d8a318a72" |
| + "1c3c0c95956809532fcf0e2449a6b525" |
| + "b16aedf5aa0de657ba637b39"; |
| ivHex[3] = "9313225df88406e555909c5aff5269aa" |
| + "6a7a9538534f7da1e4c303d2a318a728" |
| + "c3c0c95156809539fcf0e2429a6b5254" |
| + "16aedbf5a0de6a57a637b39b"; // > 96bits |
| aadHex[3] = "feedfacedeadbeeffeedfacedeadbeef" |
| + "abaddad2"; |
| cHex[3] = "8ce24998625615b603a033aca13fb894" |
| + "be9112a5c3a211a8ba262a3cca7e2ca7" |
| + "01e4a9a4fba43c90ccdcb281d48c7c6f" |
| + "d62875d2aca417034c34aee5"; |
| tHex[3] = "619cc5aefffe0bfa462af43c1699d050"; |
| } |
| |
| @BeforeEach |
| public void setup() { |
| //init |
| cipherClass = OpenSslCipher.class.getName(); |
| |
| props = new Properties(); |
| props.setProperty(CryptoCipherFactory.CLASSES_KEY, |
| cipherClass); |
| initTestData(); |
| } |
| |
| private void testGcmArbitraryLengthUpdate(final String kHex, final String pHex, final String ivHex, final String aadHex, |
| final String cHex, final String tHex) throws Exception { |
| |
| final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); |
| final byte[] input = DatatypeConverter.parseHexBinary(pHex); |
| final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); |
| final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); |
| final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex); |
| |
| final byte[] encOutput = new byte[expectedOutput.length]; |
| final byte[] decOutput = new byte[input.length]; |
| |
| final Random r = new Random(); |
| |
| int partLen; |
| int len; |
| try (final CryptoCipher enc = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| enc.init(Cipher.ENCRYPT_MODE, key, iv); |
| if (aad.length > 0) { |
| final int len1 = r.nextInt(aad.length); |
| final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1); |
| final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length); |
| enc.updateAAD(aad1); |
| enc.updateAAD(aad2); |
| } |
| |
| partLen = r.nextInt(input.length); |
| len = enc.update(input, 0, partLen, encOutput, 0); |
| assertEquals(partLen, len); |
| len = enc.doFinal(input, partLen, input.length - partLen, encOutput, partLen); |
| assertEquals(input.length + (iv.getTLen() >> 3) - partLen, len); |
| |
| assertArrayEquals(expectedOutput, encOutput); |
| } |
| |
| // Decryption |
| try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) { |
| dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes)); |
| if (aad.length > 0) { |
| final int len1 = r.nextInt(aad.length); |
| final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1); |
| final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length); |
| dec.updateAAD(aad1); |
| dec.updateAAD(aad2); |
| } |
| final byte[] decInput = encOutput; |
| partLen = r.nextInt(input.length); |
| len = dec.update(decInput, 0, partLen, decOutput, 0); |
| assertEquals(len, 0); |
| len = dec.doFinal(decInput, partLen, decInput.length - partLen, decOutput, 0); |
| assertEquals(input.length, len); |
| |
| assertArrayEquals(input, decOutput); |
| } |
| } |
| |
| private void testGcmByteBuffer(final String kHex, final String pHex, final String ivHex, final String aadHex, |
| final String cHex, final String tHex) throws Exception { |
| |
| final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); |
| final byte[] plainText = DatatypeConverter.parseHexBinary(pHex); |
| final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); |
| final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); |
| final byte[] cipherText = DatatypeConverter.parseHexBinary(cHex+tHex); |
| |
| final byte[] encOutput = new byte[cipherText.length]; |
| final byte[] decOutput = new byte[plainText.length]; |
| |
| final ByteBuffer bfAAD = ByteBuffer.allocateDirect(aad.length); |
| bfAAD.put(aad); |
| |
| final ByteBuffer bfPlainText; |
| final ByteBuffer bfCipherText; |
| bfPlainText = ByteBuffer.allocateDirect(plainText.length); |
| bfCipherText = ByteBuffer.allocateDirect(encOutput.length); |
| |
| // Encryption ------------------- |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| |
| bfAAD.flip(); |
| c.updateAAD(bfAAD); |
| |
| bfPlainText.put(plainText); |
| bfPlainText.flip(); |
| bfCipherText.position(0); |
| |
| c.doFinal(bfPlainText, bfCipherText); |
| |
| bfCipherText.flip(); |
| bfCipherText.get(encOutput); |
| assertArrayEquals(cipherText, encOutput); |
| } |
| |
| // Decryption ------------------- |
| try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) { |
| dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes)); |
| bfAAD.flip(); |
| dec.updateAAD(bfAAD); |
| bfCipherText.clear(); |
| bfPlainText.clear(); |
| bfCipherText.put(cipherText); |
| bfCipherText.flip(); |
| dec.doFinal(bfCipherText, bfPlainText); |
| bfPlainText.flip(); |
| bfPlainText.get(decOutput); |
| assertArrayEquals(plainText, decOutput); |
| } |
| } |
| |
| private void testGcmDecryption(final String kHex, final String pHex, final String ivHex, final String aadHex, |
| final String cHex, final String tHex) throws Exception { |
| |
| final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); |
| final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex); |
| final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); |
| |
| final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); |
| final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex); |
| |
| final byte[] input = cipherBytes; |
| final byte[] output = new byte[plainBytes.length]; |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.DECRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| c.doFinal(input, 0, input.length, output, 0); |
| |
| assertArrayEquals(plainBytes, output); |
| } |
| } |
| |
| private void testGcmEncryption(final String kHex, final String pHex, final String ivHex, final String aadHex, |
| final String cHex, final String tHex) throws Exception { |
| |
| final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); |
| final byte[] input = DatatypeConverter.parseHexBinary(pHex); |
| final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); |
| final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); |
| final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex); |
| |
| final byte[] output = new byte[expectedOutput.length]; |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| |
| c.doFinal(input, 0, input.length, output, 0); |
| |
| assertArrayEquals(expectedOutput, output); |
| } |
| } |
| |
| /** |
| * NIST AES Test Vectors |
| * https://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf |
| * @throws Exception Test failure |
| */ |
| @Test |
| void testGcmNistCase2() throws Exception { |
| // key length: 16 bytes |
| // plain text length: 16 bytes |
| // iv length: 12 bytes |
| // aad length: 0 bytes |
| |
| final String kHex = "00000000000000000000000000000000"; |
| final String pHex = "00000000000000000000000000000000"; |
| final String ivHex = "000000000000000000000000"; |
| final String aadHex = ""; |
| |
| final String cHex = "0388dace60b6a392f328c2b971b2fe78"; |
| final String tHex = "ab6e47d42cec13bdf53a67b21257bddf"; |
| |
| testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| } |
| |
| @Test |
| void testGcmNistCase4() throws Exception { |
| // key length: 16 bytes |
| // plain text length: 60 bytes |
| // iv length: 12 bytes |
| // aad length: 20 bytes |
| |
| final String kHex = "feffe9928665731c6d6a8f9467308308"; |
| final String pHex = "d9313225f88406e5a55909c5aff5269a" |
| + "86a7a9531534f7da2e4c303d8a318a72" |
| + "1c3c0c95956809532fcf0e2449a6b525" |
| + "b16aedf5aa0de657ba637b39"; |
| final String ivHex = "cafebabefacedbaddecaf888"; |
| final String aadHex = "feedfacedeadbeeffeedfacedeadbeef" |
| + "abaddad2"; |
| |
| final String cHex = "42831ec2217774244b7221b784d0d49c" |
| + "e3aa212f2c02a4e035c17e2329aca12e" |
| + "21d514b25466931c7d8f6a5aac84aa05" |
| + "1ba30b396a0aac973d58e091"; |
| final String tHex = "5bc94fbc3221a5db94fae95ae7121a47"; |
| |
| testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| } |
| |
| @Test |
| void testGcmNistCase5() throws Exception { |
| // key length: 16 bytes |
| // plain text length: 60 bytes |
| // iv length: 8 bytes |
| // aad length: 20 bytes |
| |
| final String kHex = "feffe9928665731c6d6a8f9467308308"; |
| |
| final String pHex = "d9313225f88406e5a55909c5aff5269a" |
| + "86a7a9531534f7da2e4c303d8a318a72" |
| + "1c3c0c95956809532fcf0e2449a6b525" |
| + "b16aedf5aa0de657ba637b39"; |
| |
| final String ivHex ="cafebabefacedbad"; // 64bits < 96bits |
| |
| final String aadHex="feedfacedeadbeeffeedfacedeadbeef" |
| + "abaddad2"; |
| |
| final String cHex = "61353b4c2806934a777ff51fa22a4755" |
| + "699b2a714fcdc6f83766e5f97b6c7423" |
| + "73806900e49f24b22b097544d4896b42" |
| + "4989b5e1ebac0f07c23f4598"; |
| |
| final String tHex = "3612d2e79e3b0785561be14aaca2fccb"; |
| |
| testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| } |
| |
| @Test |
| void testGcmNistCase6() throws Exception { |
| // key length: 16 bytes |
| // plain text length: 60 bytes |
| // iv length: 60 bytes |
| // aad length: 20 bytes |
| |
| final String kHex = "feffe9928665731c6d6a8f9467308308"; |
| |
| final String pHex = "d9313225f88406e5a55909c5aff5269a" |
| + "86a7a9531534f7da2e4c303d8a318a72" |
| + "1c3c0c95956809532fcf0e2449a6b525" |
| + "b16aedf5aa0de657ba637b39"; |
| |
| final String ivHex ="9313225df88406e555909c5aff5269aa" |
| + "6a7a9538534f7da1e4c303d2a318a728" |
| + "c3c0c95156809539fcf0e2429a6b5254" |
| + "16aedbf5a0de6a57a637b39b"; // > 96bits |
| |
| final String aadHex="feedfacedeadbeeffeedfacedeadbeef" |
| + "abaddad2"; |
| |
| final String cHex = "8ce24998625615b603a033aca13fb894" |
| + "be9112a5c3a211a8ba262a3cca7e2ca7" |
| + "01e4a9a4fba43c90ccdcb281d48c7c6f" |
| + "d62875d2aca417034c34aee5"; |
| |
| final String tHex = "619cc5aefffe0bfa462af43c1699d050"; |
| |
| testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex); |
| } |
| |
| @Test |
| void testGcmNistCases() throws Exception { |
| for(int i = 0; i < kHex.length; i++) { |
| testGcmEncryption(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]); |
| testGcmDecryption(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]); |
| testGcmByteBuffer(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]); |
| testGcmReturnDataAfterTagVerified(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]); |
| testGcmArbitraryLengthUpdate(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]); |
| } |
| } |
| |
| private void testGcmReturnDataAfterTagVerified(final String kHex, final String pHex, final String ivHex, final String aadHex, |
| final String cHex, final String tHex) throws Exception { |
| |
| final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex); |
| final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex); |
| final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex); |
| |
| final byte[] aad = DatatypeConverter.parseHexBinary(aadHex); |
| final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex); |
| |
| final byte[] input = cipherBytes; |
| final byte[] output = new byte[plainBytes.length]; |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.DECRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| |
| // only return recovered data after tag is successfully verified |
| int len = c.update(input, 0, input.length, output, 0); |
| assertEquals(len, 0); |
| len += c.doFinal(input, input.length, 0, output, 0); |
| assertEquals(plainBytes.length, len); |
| |
| assertArrayEquals(plainBytes, output); |
| } |
| } |
| |
| @Test |
| void testGcmTamperedData() throws Exception { |
| |
| final Random r = new Random(); |
| final int textLength = r.nextInt(1024*1024); |
| final int ivLength = r.nextInt(59) + 1; |
| final int keyLength = 16; |
| final int tagLength = 128; // bits |
| final int aadLength = r.nextInt(128); |
| |
| final byte[] keyBytes = new byte[keyLength]; |
| final byte[] plainBytes = new byte[textLength]; |
| final byte[] ivBytes = new byte[ivLength]; |
| final byte[] aadBytes = new byte[aadLength]; |
| |
| r.nextBytes(keyBytes); |
| r.nextBytes(plainBytes); |
| r.nextBytes(ivBytes); |
| r.nextBytes(aadBytes); |
| |
| final byte[] encOutput = new byte[plainBytes.length + (tagLength >> 3)]; |
| final byte[] decOutput = new byte[plainBytes.length]; |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| |
| final GCMParameterSpec iv = new GCMParameterSpec(tagLength, ivBytes); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| c.updateAAD(aadBytes); |
| c.doFinal(plainBytes, 0, plainBytes.length, encOutput, 0); |
| } |
| |
| // Tamper the encrypted data. |
| encOutput[0] = (byte)(encOutput[0] + 1); |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| |
| final GCMParameterSpec iv = new GCMParameterSpec(tagLength, ivBytes); |
| c.init(Cipher.DECRYPT_MODE, key, iv); |
| c.updateAAD(aadBytes); |
| final Exception ex = assertThrows(AEADBadTagException.class, () -> c.doFinal(encOutput, 0, encOutput.length, decOutput, 0)); |
| assertEquals(ex.getMessage(), "Tag mismatch!"); |
| } |
| |
| } |
| |
| @Test |
| void testGMac() throws Exception { |
| // for GMAC, aad is the input data, |
| // tag is the digest message |
| |
| final Random r = new Random(); |
| final byte[] keyBytes = new byte[32]; |
| final byte[] input = {}; // no input for GMAC |
| final byte[] ivBytes = new byte[16]; |
| |
| final byte[] tag_orig = new byte[16]; // JDK's tag |
| final byte[] tag = new byte[16]; |
| |
| // aad is the data to be hashed |
| final byte[] aad = new byte[r.nextInt() % 1000 + 1000 ]; |
| |
| r.nextBytes(keyBytes); |
| r.nextBytes(input); |
| r.nextBytes(ivBytes); |
| r.nextBytes(aad); |
| |
| { |
| final Cipher c = Cipher.getInstance(transformation); |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| c.doFinal(input, 0, input.length, tag_orig, 0); |
| } |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| c.doFinal(input, 0, input.length, tag, 0); |
| } |
| |
| // tag should be the same as JDK's cipher |
| assertArrayEquals(tag_orig, tag); |
| |
| // like JDK's decrypt mode. The plaintext+tag is the input for decrypt mode |
| // let's verify the add & tag now |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.DECRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| c.doFinal(tag, 0, tag.length, input, 0); |
| } |
| } |
| |
| @Test |
| //(expected = AEADBadTagException.class) |
| void testGMacTamperedData() throws Exception { |
| final Random r = new Random(); |
| final byte[] keyBytes = new byte[32]; |
| final byte[] input = {}; |
| final byte[] ivBytes = new byte[16]; |
| |
| final byte[] tag = new byte[16]; |
| |
| final byte[] aad = new byte[r.nextInt() % 1000 + 1000 ]; |
| |
| r.nextBytes(keyBytes); |
| r.nextBytes(input); |
| r.nextBytes(ivBytes); |
| r.nextBytes(aad); |
| |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| c.updateAAD(aad); |
| c.doFinal(input, 0, input.length, tag, 0); |
| } |
| |
| // like JDK's decrypt mode. The plaintext+tag is the input for decrypt mode |
| try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) { |
| final Key key = AES.newSecretKeySpec(keyBytes); |
| final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes); |
| c.init(Cipher.DECRYPT_MODE, key, iv); |
| |
| // if the origin data is tampered |
| aad[0] = (byte) (aad[0] + 1); |
| c.updateAAD(aad); |
| |
| final Exception ex = assertThrows(AEADBadTagException.class, () -> c.doFinal(tag, 0, tag.length, input, 0)); |
| assertEquals(ex.getMessage(), "Tag mismatch!"); |
| } |
| |
| } |
| } |