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