| // Copyright 2017 Google Inc. |
| // |
| // Licensed 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 |
| // |
| // http://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 com.google.crypto.tink.subtle; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.crypto.tink.config.TinkFips; |
| import com.google.crypto.tink.testing.TestUtil; |
| import java.security.GeneralSecurityException; |
| import java.security.Security; |
| import java.util.Arrays; |
| import org.conscrypt.Conscrypt; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Unit tests for AesCtrJceCipher. |
| */ |
| @RunWith(JUnit4.class) |
| public class AesCtrJceCipherTest { |
| |
| // NIST SP 800-38A pp 55. |
| private static final String NIST_KEY = "2b7e151628aed2a6abf7158809cf4f3c"; |
| private static final String NIST_PLAINTEXT = |
| "6bc1bee22e409f96e93d7e117393172a" |
| + "ae2d8a571e03ac9c9eb76fac45af8e51" |
| + "30c81c46a35ce411e5fbc1191a0a52ef" |
| + "f69f2445df4f9b17ad2b417be66c3710"; |
| private static final String NIST_CIPHERTEXT = |
| "874d6191b620e3261bef6864990db6ce" |
| + "9806f66b7970fdff8617187bb9fffdff" |
| + "5ae4df3edbd5d35e5b4f09020db03eab" |
| + "1e031dda2fbe03d1792170a0f3009cee"; |
| private static final String NIST_IV = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; |
| |
| private static final String PLAINTEXT = |
| "I'm counter mode and I'm not vulnerable to padding oracle attack like CBC mode"; |
| |
| private byte[] msg; |
| |
| @Before |
| public void setUp() { |
| try { |
| msg = PLAINTEXT.getBytes("UTF-8"); |
| } catch (Exception ignored) { |
| // Ignored |
| } |
| } |
| |
| @Before |
| public void useConscrypt() throws Exception { |
| // If Tink is build in FIPS-only mode, then we register Conscrypt for the tests. |
| if (TinkFips.useOnlyFips()) { |
| try { |
| Conscrypt.checkAvailability(); |
| Security.addProvider(Conscrypt.newProvider()); |
| } catch (Throwable cause) { |
| throw new IllegalStateException( |
| "Cannot test AesCtr in FIPS-mode without Conscrypt Provider", cause); |
| } |
| } |
| } |
| |
| @Test |
| public void testNistVector() throws Exception { |
| Assume.assumeTrue(!TinkFips.useOnlyFips() || TinkFips.fipsModuleAvailable()); |
| |
| byte[] rawCiphertext = TestUtil.hexDecode(NIST_CIPHERTEXT); |
| byte[] iv = TestUtil.hexDecode(NIST_IV); |
| byte[] ciphertext = new byte[iv.length + rawCiphertext.length]; |
| System.arraycopy(iv, 0, ciphertext, 0, iv.length); |
| System.arraycopy(rawCiphertext, 0, ciphertext, iv.length, rawCiphertext.length); |
| AesCtrJceCipher cipher = new AesCtrJceCipher(TestUtil.hexDecode(NIST_KEY), iv.length); |
| assertArrayEquals(TestUtil.hexDecode(NIST_PLAINTEXT), cipher.decrypt(ciphertext)); |
| } |
| |
| @Test |
| public void testMultipleEncrypts() throws Exception { |
| Assume.assumeTrue(!TinkFips.useOnlyFips() || TinkFips.fipsModuleAvailable()); |
| |
| // Checks whether multiple encryptions result in different ciphertexts. |
| byte[] key = Random.randBytes(16); |
| int ivSize = 16; |
| AesCtrJceCipher cipher = new AesCtrJceCipher(key, ivSize); |
| byte[] c1 = cipher.encrypt(msg); |
| byte[] c2 = cipher.encrypt(msg); |
| assertEquals(c1.length, c2.length); |
| assertFalse(Arrays.equals(c1, c2)); |
| } |
| |
| @Test |
| public void testCtrProperty() throws Exception { |
| Assume.assumeTrue(!TinkFips.useOnlyFips() || TinkFips.fipsModuleAvailable()); |
| |
| // Counter mode is malleable, i.e., if we flip the ciphertext, the plaintext is flipped. |
| byte[] key = Random.randBytes(16); |
| int ivSize = 16; |
| AesCtrJceCipher cipher = new AesCtrJceCipher(key, ivSize); |
| byte[] c1 = cipher.encrypt(msg); |
| for (int i = 0; i < msg.length; i++) { |
| for (int j = 0; j < 8; j++) { |
| byte[] p1 = Arrays.copyOf(msg, msg.length); |
| byte[] c2 = Arrays.copyOf(c1, c1.length); |
| p1[i] = (byte) (p1[i] ^ (1 << j)); |
| c2[i + ivSize] = (byte) (c2[i + ivSize] ^ (1 << j)); |
| byte[] p2 = cipher.decrypt(c2); |
| assertArrayEquals(p1, p2); |
| assertFalse(Arrays.equals(p2, msg)); |
| } |
| } |
| } |
| |
| @Test |
| public void testEncryptDecrypt() throws Exception { |
| Assume.assumeTrue(!TinkFips.useOnlyFips() || TinkFips.fipsModuleAvailable()); |
| |
| byte[] key = Random.randBytes(16); |
| int ivSize = 16; |
| AesCtrJceCipher c = new AesCtrJceCipher(key, ivSize); |
| byte[] ciphertext = c.encrypt(msg); |
| assertArrayEquals(msg, c.decrypt(ciphertext)); |
| } |
| |
| @Test |
| public void testFailIfFipsModuleNotAvailable() throws Exception { |
| Assume.assumeTrue(TinkFips.useOnlyFips() && !TinkFips.fipsModuleAvailable()); |
| |
| byte[] key = Random.randBytes(16); |
| int ivSize = 16; |
| assertThrows(GeneralSecurityException.class, () -> new AesCtrJceCipher(key, ivSize)); |
| } |
| } |