1 // Copyright 2017 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.ChaCha20Poly1305Key; 25 import com.google.crypto.tink.aead.internal.InsecureNonceChaCha20Poly1305; 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 * ChaCha20Poly1305 AEAD construction, as described in <a 33 * href="https://tools.ietf.org/html/rfc8439#section-2.8">RFC 8439, section 2.8</a>. 34 * 35 * @since 1.1.0 36 */ 37 public final class ChaCha20Poly1305 implements Aead { 38 private final InsecureNonceChaCha20Poly1305 cipher; 39 private final byte[] outputPrefix; 40 ChaCha20Poly1305(final byte[] key, final byte[] outputPrefix)41 private ChaCha20Poly1305(final byte[] key, final byte[] outputPrefix) 42 throws GeneralSecurityException { 43 cipher = new InsecureNonceChaCha20Poly1305(key); 44 this.outputPrefix = outputPrefix; 45 } 46 ChaCha20Poly1305(final byte[] key)47 public ChaCha20Poly1305(final byte[] key) throws GeneralSecurityException { 48 this(key, new byte[0]); 49 } 50 51 @AccessesPartialKey create(ChaCha20Poly1305Key key)52 public static Aead create(ChaCha20Poly1305Key key) throws GeneralSecurityException { 53 return new ChaCha20Poly1305( 54 key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), 55 key.getOutputPrefix().toByteArray()); 56 } 57 rawEncrypt(final byte[] plaintext, final byte[] associatedData)58 private byte[] rawEncrypt(final byte[] plaintext, final byte[] associatedData) 59 throws GeneralSecurityException { 60 ByteBuffer output = 61 ByteBuffer.allocate( 62 ChaCha20.NONCE_LENGTH_IN_BYTES + plaintext.length + Poly1305.MAC_TAG_SIZE_IN_BYTES); 63 byte[] nonce = Random.randBytes(ChaCha20.NONCE_LENGTH_IN_BYTES); 64 output.put(nonce); // Prepend nonce to ciphertext output. 65 cipher.encrypt(output, nonce, plaintext, associatedData); 66 return output.array(); 67 } 68 69 @Override encrypt(final byte[] plaintext, final byte[] associatedData)70 public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) 71 throws GeneralSecurityException { 72 byte[] ciphertext = rawEncrypt(plaintext, associatedData); 73 if (outputPrefix.length == 0) { 74 return ciphertext; 75 } 76 return Bytes.concat(outputPrefix, ciphertext); 77 } 78 rawDecrypt(final byte[] ciphertext, final byte[] associatedData)79 private byte[] rawDecrypt(final byte[] ciphertext, final byte[] associatedData) 80 throws GeneralSecurityException { 81 if (ciphertext.length < ChaCha20.NONCE_LENGTH_IN_BYTES + Poly1305.MAC_TAG_SIZE_IN_BYTES) { 82 throw new GeneralSecurityException("ciphertext too short"); 83 } 84 byte[] nonce = Arrays.copyOf(ciphertext, ChaCha20.NONCE_LENGTH_IN_BYTES); 85 ByteBuffer rawCiphertext = 86 ByteBuffer.wrap( 87 ciphertext, 88 ChaCha20.NONCE_LENGTH_IN_BYTES, 89 ciphertext.length - ChaCha20.NONCE_LENGTH_IN_BYTES); 90 return cipher.decrypt(rawCiphertext, nonce, associatedData); 91 } 92 93 @Override decrypt(final byte[] ciphertext, final byte[] associatedData)94 public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) 95 throws GeneralSecurityException { 96 if (outputPrefix.length == 0) { 97 return rawDecrypt(ciphertext, associatedData); 98 } 99 if (!isPrefix(outputPrefix, ciphertext)) { 100 throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch)."); 101 } 102 byte[] copiedCiphertext = 103 Arrays.copyOfRange(ciphertext, outputPrefix.length, ciphertext.length); 104 return rawDecrypt(copiedCiphertext, associatedData); 105 } 106 } 107