• 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.crypto.tink.internal.Util.isPrefix;
20 
21 import com.google.crypto.tink.AccessesPartialKey;
22 import com.google.crypto.tink.Aead;
23 import com.google.crypto.tink.InsecureSecretKeyAccess;
24 import com.google.crypto.tink.aead.XChaCha20Poly1305Key;
25 import com.google.crypto.tink.aead.internal.InsecureNonceXChaCha20Poly1305;
26 import com.google.crypto.tink.aead.internal.Poly1305;
27 import java.nio.ByteBuffer;
28 import java.security.GeneralSecurityException;
29 import java.util.Arrays;
30 
31 /**
32  * XChaCha20Poly1305 AEAD construction, as described in
33  * https://tools.ietf.org/html/draft-arciszewski-xchacha-01.
34  */
35 public final class XChaCha20Poly1305 implements Aead {
36   private final InsecureNonceXChaCha20Poly1305 cipher;
37   private final byte[] outputPrefix;
38 
XChaCha20Poly1305(final byte[] key, final byte[] outputPrefix)39   private XChaCha20Poly1305(final byte[] key, final byte[] outputPrefix)
40       throws GeneralSecurityException {
41     cipher = new InsecureNonceXChaCha20Poly1305(key);
42     this.outputPrefix = outputPrefix;
43   }
44 
XChaCha20Poly1305(final byte[] key)45   public XChaCha20Poly1305(final byte[] key) throws GeneralSecurityException {
46     this(key, new byte[0]);
47   }
48 
49   @AccessesPartialKey
create(XChaCha20Poly1305Key key)50   public static Aead create(XChaCha20Poly1305Key key) throws GeneralSecurityException {
51     return new XChaCha20Poly1305(
52         key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()),
53         key.getOutputPrefix().toByteArray());
54   }
55 
rawEncrypt(final byte[] plaintext, final byte[] associatedData)56   private byte[] rawEncrypt(final byte[] plaintext, final byte[] associatedData)
57       throws GeneralSecurityException {
58     ByteBuffer output =
59         ByteBuffer.allocate(
60             XChaCha20.NONCE_LENGTH_IN_BYTES + plaintext.length + Poly1305.MAC_TAG_SIZE_IN_BYTES);
61     byte[] nonce = Random.randBytes(XChaCha20.NONCE_LENGTH_IN_BYTES);
62     output.put(nonce); // Prepend nonce to ciphertext output.
63     cipher.encrypt(output, nonce, plaintext, associatedData);
64     return output.array();
65   }
66 
67   @Override
encrypt(final byte[] plaintext, final byte[] associatedData)68   public byte[] encrypt(final byte[] plaintext, final byte[] associatedData)
69       throws GeneralSecurityException {
70     byte[] ciphertext = rawEncrypt(plaintext, associatedData);
71     if (outputPrefix.length == 0) {
72       return ciphertext;
73     }
74     return Bytes.concat(outputPrefix, ciphertext);
75   }
76 
rawDecrypt(final byte[] ciphertext, final byte[] associatedData)77   private byte[] rawDecrypt(final byte[] ciphertext, final byte[] associatedData)
78       throws GeneralSecurityException {
79     if (ciphertext.length < XChaCha20.NONCE_LENGTH_IN_BYTES + Poly1305.MAC_TAG_SIZE_IN_BYTES) {
80       throw new GeneralSecurityException("ciphertext too short");
81     }
82     byte[] nonce = Arrays.copyOf(ciphertext, XChaCha20.NONCE_LENGTH_IN_BYTES);
83     ByteBuffer rawCiphertext =
84         ByteBuffer.wrap(
85             ciphertext,
86             XChaCha20.NONCE_LENGTH_IN_BYTES,
87             ciphertext.length - XChaCha20.NONCE_LENGTH_IN_BYTES);
88     return cipher.decrypt(rawCiphertext, nonce, associatedData);
89   }
90 
91   @Override
decrypt(final byte[] ciphertext, final byte[] associatedData)92   public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData)
93       throws GeneralSecurityException {
94     if (outputPrefix.length == 0) {
95       return rawDecrypt(ciphertext, associatedData);
96     }
97     if (!isPrefix(outputPrefix, ciphertext)) {
98       throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch).");
99     }
100     byte[] copiedCiphertext =
101         Arrays.copyOfRange(ciphertext, outputPrefix.length, ciphertext.length);
102     return rawDecrypt(copiedCiphertext, associatedData);
103   }
104 }
105