• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 package com.google.crypto.tink.subtle;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertArrayEquals;
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertThrows;
24 
25 import com.google.crypto.tink.Aead;
26 import com.google.crypto.tink.InsecureSecretKeyAccess;
27 import com.google.crypto.tink.aead.XChaCha20Poly1305Key;
28 import com.google.crypto.tink.aead.XChaCha20Poly1305Parameters;
29 import com.google.crypto.tink.config.TinkFips;
30 import com.google.crypto.tink.testing.TestUtil;
31 import com.google.crypto.tink.util.SecretBytes;
32 import java.security.GeneralSecurityException;
33 import java.util.Arrays;
34 import java.util.HashSet;
35 import javax.crypto.AEADBadTagException;
36 import org.junit.Assume;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.junit.runners.JUnit4;
40 
41 /** Unit tests for XChaCha20Poly1305. */
42 @RunWith(JUnit4.class)
43 public class XChaCha20Poly1305Test {
44   private static final int KEY_SIZE = 32;
45 
46   private static class XChaCha20Poly1305TestVector {
47     public byte[] key;
48     public byte[] nonce;
49     public byte[] plaintext;
50     public byte[] aad;
51     public byte[] ciphertext;
52     public byte[] tag;
53 
XChaCha20Poly1305TestVector( String key, String nonce, String plaintext, String aad, String ciphertext, String tag)54     public XChaCha20Poly1305TestVector(
55         String key, String nonce, String plaintext, String aad, String ciphertext, String tag) {
56       this.key = Hex.decode(key);
57       this.nonce = Hex.decode(nonce);
58       this.plaintext = Hex.decode(plaintext);
59       this.aad = Hex.decode(aad);
60       this.ciphertext = Hex.decode(ciphertext);
61       this.tag = Hex.decode(tag);
62     }
63   }
64 
65   private static final XChaCha20Poly1305TestVector[] xChaCha20Poly1305TestVectors = {
66     // From libsodium's test/default/aead_xchacha20poly1305.c
67     // see test/default/aead_xchacha20poly1305.exp for ciphertext values.
68     new XChaCha20Poly1305TestVector(
69         "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
70         "07000000404142434445464748494a4b0000000000000000",
71         "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a20496620"
72             + "4920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f722074686520667574"
73             + "7572652c2073756e73637265656e20776f756c642062652069742e",
74         "50515253c0c1c2c3c4c5c6c7",
75         "453c0693a7407f04ff4c56aedb17a3c0a1afff01174930fc22287c33dbcf0ac8b89ad929530a1bb3ab5e69f24c"
76             + "7f6070c8f840c9abb4f69fbfc8a7ff5126faeebbb55805ee9c1cf2ce5a57263287aec5780f04ec324c35"
77             + "14122cfc3231fc1a8b718a62863730a2702bb76366116bed09e0fd",
78         "5c6d84b6b0c1abaf249d5dd0f7f5a7ea"),
79     new XChaCha20Poly1305TestVector(
80         "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
81         "07000000404142434445464748494a4b0000000000000000",
82         "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a20496620"
83             + "4920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f722074686520667574"
84             + "7572652c2073756e73637265656e20776f756c642062652069742e",
85         "" /* empty aad */,
86         "453c0693a7407f04ff4c56aedb17a3c0a1afff01174930fc22287c33dbcf0ac8b89ad929530a1bb3ab5e69f24c"
87             + "7f6070c8f840c9abb4f69fbfc8a7ff5126faeebbb55805ee9c1cf2ce5a57263287aec5780f04ec324c35"
88             + "14122cfc3231fc1a8b718a62863730a2702bb76366116bed09e0fd",
89         "d4c860b7074be894fac9697399be5cc1"),
90     // From  https://tools.ietf.org/html/draft-arciszewski-xchacha-01.
91     new XChaCha20Poly1305TestVector(
92         "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
93         "404142434445464748494a4b4c4d4e4f5051525354555657",
94         "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a20496620"
95             + "4920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f722074686520667574"
96             + "7572652c2073756e73637265656e20776f756c642062652069742e",
97         "50515253c0c1c2c3c4c5c6c7",
98         "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb731c7f1b0b4aa6440bf3a82f4e"
99             + "da7e39ae64c6708c54c216cb96b72e1213b4522f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc36948"
100             + "8f76b2383565d3fff921f9664c97637da9768812f615c68b13b52e",
101         "c0875924c1c7987947deafd8780acf49")
102   };
103 
104   @Test
testXChaCha20Poly1305TestVectors()105   public void testXChaCha20Poly1305TestVectors() throws Exception {
106     for (XChaCha20Poly1305TestVector test : xChaCha20Poly1305TestVectors) {
107       Aead cipher = new XChaCha20Poly1305(test.key);
108       byte[] message =
109           cipher.decrypt(Bytes.concat(test.nonce, test.ciphertext, test.tag), test.aad);
110       assertThat(message).isEqualTo(test.plaintext);
111     }
112   }
113 
createInstance(byte[] key)114   public Aead createInstance(byte[] key) throws GeneralSecurityException {
115     return new XChaCha20Poly1305(key);
116   }
117 
118   @Test
testSnufflePoly1305ThrowsIllegalArgExpWhenKeyLenIsGreaterThan32()119   public void testSnufflePoly1305ThrowsIllegalArgExpWhenKeyLenIsGreaterThan32()
120       throws GeneralSecurityException {
121     GeneralSecurityException e =
122         assertThrows(GeneralSecurityException.class, () -> createInstance(new byte[KEY_SIZE + 1]));
123     assertThat(e).hasMessageThat().containsMatch("The key length in bytes must be 32.");
124   }
125 
126   @Test
testSnufflePoly1305ThrowsIllegalArgExpWhenKeyLenIsLessThan32()127   public void testSnufflePoly1305ThrowsIllegalArgExpWhenKeyLenIsLessThan32()
128       throws GeneralSecurityException {
129     GeneralSecurityException e =
130         assertThrows(GeneralSecurityException.class, () -> createInstance(new byte[KEY_SIZE - 1]));
131     assertThat(e).hasMessageThat().containsMatch("The key length in bytes must be 32.");
132   }
133 
134   @Test
testDecryptThrowsGeneralSecurityExpWhenCiphertextIsTooShort()135   public void testDecryptThrowsGeneralSecurityExpWhenCiphertextIsTooShort()
136       throws GeneralSecurityException {
137     Aead cipher = createInstance(new byte[KEY_SIZE]);
138     GeneralSecurityException e =
139         assertThrows(
140             GeneralSecurityException.class, () -> cipher.decrypt(new byte[27], new byte[1]));
141     assertThat(e).hasMessageThat().containsMatch("ciphertext too short");
142   }
143 
144   @Test
testEncryptDecrypt()145   public void testEncryptDecrypt() throws Exception {
146     Aead aead = createInstance(Random.randBytes(KEY_SIZE));
147     for (int i = 0; i < 100; i++) {
148       byte[] message = Random.randBytes(i);
149       byte[] aad = Random.randBytes(i);
150       byte[] ciphertext = aead.encrypt(message, aad);
151       byte[] decrypted = aead.decrypt(ciphertext, aad);
152       assertArrayEquals(message, decrypted);
153     }
154   }
155 
156   @Test
testLongMessages()157   public void testLongMessages() throws Exception {
158     if (TestUtil.isAndroid() || TestUtil.isTsan()) {
159       System.out.println("testLongMessages doesn't work on Android and under tsan, skipping");
160       return;
161     }
162     int dataSize = 16;
163     while (dataSize <= (1 << 24)) {
164       byte[] plaintext = Random.randBytes(dataSize);
165       byte[] aad = Random.randBytes(dataSize / 3);
166       byte[] key = Random.randBytes(KEY_SIZE);
167       Aead aead = createInstance(key);
168       byte[] ciphertext = aead.encrypt(plaintext, aad);
169       byte[] decrypted = aead.decrypt(ciphertext, aad);
170       assertArrayEquals(plaintext, decrypted);
171       dataSize += 5 * dataSize / 11;
172     }
173   }
174 
175   @Test
testModifyCiphertext()176   public void testModifyCiphertext() throws Exception {
177     byte[] key = Random.randBytes(KEY_SIZE);
178     Aead aead = createInstance(key);
179     byte[] aad = Random.randBytes(16);
180     byte[] message = Random.randBytes(32);
181     byte[] ciphertext = aead.encrypt(message, aad);
182 
183     // Flipping bits
184     for (int b = 0; b < ciphertext.length; b++) {
185       for (int bit = 0; bit < 8; bit++) {
186         byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length);
187         modified[b] ^= (byte) (1 << bit);
188         assertThrows(
189             AEADBadTagException.class,
190             () -> {
191               byte[] unused = aead.decrypt(modified, aad);
192             });
193       }
194     }
195 
196     // Truncate the message.
197     for (int length = 0; length < ciphertext.length; length++) {
198       byte[] modified = Arrays.copyOf(ciphertext, length);
199       assertThrows(
200           GeneralSecurityException.class,
201           () -> {
202             byte[] unused = aead.decrypt(modified, aad);
203           });
204     }
205 
206     // Modify AAD
207     for (int b = 0; b < aad.length; b++) {
208       for (int bit = 0; bit < 8; bit++) {
209         byte[] modified = Arrays.copyOf(aad, aad.length);
210         modified[b] ^= (byte) (1 << bit);
211         assertThrows(
212             AEADBadTagException.class,
213             () -> {
214               byte[] unused = aead.decrypt(ciphertext, modified);
215             });
216       }
217     }
218   }
219 
220   /**
221    * This is a very simple test for the randomness of the nonce. The test simply checks that the
222    * multiple ciphertexts of the same message are distinct.
223    */
224   @Test
testRandomNonce()225   public void testRandomNonce() throws Exception {
226     if (TestUtil.isTsan()) {
227       System.out.println("testRandomNonce takes too long under tsan, skipping");
228       return;
229     }
230     byte[] key = Random.randBytes(KEY_SIZE);
231     Aead aead = createInstance(key);
232     byte[] message = new byte[0];
233     byte[] aad = new byte[0];
234     HashSet<String> ciphertexts = new HashSet<>();
235     final int samples = 1 << 17;
236     for (int i = 0; i < samples; i++) {
237       byte[] ct = aead.encrypt(message, aad);
238       String ctHex = Hex.encode(ct);
239       assertFalse(ciphertexts.contains(ctHex));
240       ciphertexts.add(ctHex);
241     }
242     assertEquals(samples, ciphertexts.size());
243   }
244 
245   /**
246    * Test case taken from Wycheproof (testcase 5)
247    * https://github.com/google/wycheproof/blob/b063b4aedae951c69df014cd25fa6d69ae9e8cb9/testvectors/xchacha20_poly1305_test.json#L69
248    */
249   @Test
testWithXChaCha20Poly1305Key_noPrefix_works()250   public void testWithXChaCha20Poly1305Key_noPrefix_works() throws Exception {
251     Assume.assumeFalse(TinkFips.useOnlyFips());
252     byte[] plaintext = Hex.decode("e1");
253     byte[] associatedData = Hex.decode("6384f4714ff18c18");
254     byte[] keyBytes =
255         Hex.decode("697c197c9e0023c8eee42ddf08c12c46718a436561b0c66d998c81879f7cb74c");
256     XChaCha20Poly1305Key key =
257         XChaCha20Poly1305Key.create(SecretBytes.copyFrom(keyBytes, InsecureSecretKeyAccess.get()));
258     Aead aead = XChaCha20Poly1305.create(key);
259     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
260     assertThat(ciphertext).hasLength(41); // msg (1) + iv(24) + tag(16)
261 
262     assertThat(aead.decrypt(ciphertext, associatedData)).isEqualTo(plaintext);
263 
264     byte[] fixedCiphertext =
265         Hex.decode(
266             "cd78f4533c94648feacd5aef0291b00b454ee3dcdb76dcc8b0e5e35f5332f91bdd2d28e59d68a0b141");
267     assertThat(aead.decrypt(fixedCiphertext, associatedData)).isEqualTo(plaintext);
268   }
269 
270   @Test
testWithXChaCha20Poly1305Key_tinkPrefix_works()271   public void testWithXChaCha20Poly1305Key_tinkPrefix_works() throws Exception {
272     Assume.assumeFalse(TinkFips.useOnlyFips());
273     byte[] plaintext = Hex.decode("e1");
274     byte[] associatedData = Hex.decode("6384f4714ff18c18");
275     byte[] keyBytes =
276         Hex.decode("697c197c9e0023c8eee42ddf08c12c46718a436561b0c66d998c81879f7cb74c");
277     XChaCha20Poly1305Key key =
278         XChaCha20Poly1305Key.create(
279             XChaCha20Poly1305Parameters.Variant.TINK,
280             SecretBytes.copyFrom(keyBytes, InsecureSecretKeyAccess.get()),
281             0x99887766);
282     Aead aead = XChaCha20Poly1305.create(key);
283     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
284     assertThat(ciphertext).hasLength(46); // prefix(5) + msg(1) + iv(24) + tag(16)
285 
286     assertThat(aead.decrypt(ciphertext, associatedData)).isEqualTo(plaintext);
287 
288     byte[] fixedCiphertext =
289         Hex.decode(
290             "0199887766cd78f4533c94648feacd5aef0291b00b454ee3dcdb76dcc8b0e5e35f5332f91bdd2d28e59d68a0b141");
291     assertThat(aead.decrypt(fixedCiphertext, associatedData)).isEqualTo(plaintext);
292   }
293 
294   @Test
testWithXChaCha20Poly1305Key_crunchyPrefix_works()295   public void testWithXChaCha20Poly1305Key_crunchyPrefix_works() throws Exception {
296     Assume.assumeFalse(TinkFips.useOnlyFips());
297     byte[] plaintext = Hex.decode("e1");
298     byte[] associatedData = Hex.decode("6384f4714ff18c18");
299     byte[] keyBytes =
300         Hex.decode("697c197c9e0023c8eee42ddf08c12c46718a436561b0c66d998c81879f7cb74c");
301     XChaCha20Poly1305Key key =
302         XChaCha20Poly1305Key.create(
303             XChaCha20Poly1305Parameters.Variant.CRUNCHY,
304             SecretBytes.copyFrom(keyBytes, InsecureSecretKeyAccess.get()),
305             0x99887766);
306     Aead aead = XChaCha20Poly1305.create(key);
307     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
308     assertThat(ciphertext).hasLength(46); // prefix(5) + msg(1) + iv(24) + tag(16)
309 
310     assertThat(aead.decrypt(ciphertext, associatedData)).isEqualTo(plaintext);
311 
312     byte[] fixedCiphertext =
313         Hex.decode(
314             "0099887766cd78f4533c94648feacd5aef0291b00b454ee3dcdb76dcc8b0e5e35f5332f91bdd2d28e59d68a0b141");
315     assertThat(aead.decrypt(fixedCiphertext, associatedData)).isEqualTo(plaintext);
316   }
317 }
318