--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java Thu May 31 07:05:10 2018 -0700
@@ -0,0 +1,1389 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.crypto.provider;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.*;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+import java.util.Objects;
+import javax.crypto.spec.ChaCha20ParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.crypto.*;
+import sun.security.util.DerValue;
+
+/**
+ * Implementation of the ChaCha20 cipher, as described in RFC 7539.
+ *
+ * @since 11
+ */
+abstract class ChaCha20Cipher extends CipherSpi {
+ // Mode constants
+ private static final int MODE_NONE = 0;
+ private static final int MODE_AEAD = 1;
+
+ // Constants used in setting up the initial state
+ private static final int STATE_CONST_0 = 0x61707865;
+ private static final int STATE_CONST_1 = 0x3320646e;
+ private static final int STATE_CONST_2 = 0x79622d32;
+ private static final int STATE_CONST_3 = 0x6b206574;
+
+ // The keystream block size in bytes and as integers
+ private static final int KEYSTREAM_SIZE = 64;
+ private static final int KS_SIZE_INTS = KEYSTREAM_SIZE / Integer.BYTES;
+ private static final int CIPHERBUF_BASE = 1024;
+
+ // The initialization state of the cipher
+ private boolean initialized;
+
+ // The mode of operation for this object
+ protected int mode;
+
+ // The direction (encrypt vs. decrypt) for the data flow
+ private int direction;
+
+ // Has all AAD data been provided (i.e. have we called our first update)
+ private boolean aadDone = false;
+
+ // The key's encoding in bytes for this object
+ private byte[] keyBytes;
+
+ // The nonce used for this object
+ private byte[] nonce;
+
+ // The counter
+ private static final long MAX_UINT32 = 0x00000000FFFFFFFFL;
+ private long finalCounterValue;
+ private long counter;
+
+ // Two arrays, both implemented as 16-element integer arrays:
+ // The base state, created at initialization time, and a working
+ // state which is a clone of the start state, and is then modified
+ // with the counter and the ChaCha20 block function.
+ private final int[] startState = new int[KS_SIZE_INTS];
+ private final byte[] keyStream = new byte[KEYSTREAM_SIZE];
+
+ // The offset into the current keystream
+ private int keyStrOffset;
+
+ // AEAD-related fields and constants
+ private static final int TAG_LENGTH = 16;
+ private long aadLen;
+ private long dataLen;
+
+ // Have a buffer of zero padding that can be read all or in part
+ // by the authenticator.
+ private static final byte[] padBuf = new byte[TAG_LENGTH];
+
+ // Create a buffer for holding the AAD and Ciphertext lengths
+ private final byte[] lenBuf = new byte[TAG_LENGTH];
+
+ // The authenticator (Poly1305) when running in AEAD mode
+ protected String authAlgName;
+ private Poly1305 authenticator;
+
+ // The underlying engine for doing the ChaCha20/Poly1305 work
+ private ChaChaEngine engine;
+
+ // Use this VarHandle for converting the state elements into little-endian
+ // integer values for the ChaCha20 block function.
+ private static final VarHandle asIntLittleEndian =
+ MethodHandles.byteArrayViewVarHandle(int[].class,
+ ByteOrder.LITTLE_ENDIAN);
+
+ // Use this VarHandle for converting the AAD and data lengths into
+ // little-endian long values for AEAD tag computations.
+ private static final VarHandle asLongLittleEndian =
+ MethodHandles.byteArrayViewVarHandle(long[].class,
+ ByteOrder.LITTLE_ENDIAN);
+
+ // Use this for pulling in 8 bytes at a time as longs for XOR operations
+ private static final VarHandle asLongView =
+ MethodHandles.byteArrayViewVarHandle(long[].class,
+ ByteOrder.nativeOrder());
+
+ /**
+ * Default constructor.
+ */
+ protected ChaCha20Cipher() {
+ }
+
+ /**
+ * Set the mode of operation. Since this is a stream cipher, there
+ * is no mode of operation in the block-cipher sense of things. The
+ * protected {@code mode} field will only accept a value of {@code None}
+ * (case-insensitive).
+ *
+ * @param mode The mode value
+ *
+ * @throws NoSuchAlgorithmException if a mode of operation besides
+ * {@code None} is provided.
+ */
+ @Override
+ protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
+ if (mode.equalsIgnoreCase("None") == false) {
+ throw new NoSuchAlgorithmException("Mode must be None");
+ }
+ }
+
+ /**
+ * Set the padding scheme. Padding schemes do not make sense with stream
+ * ciphers, but allow {@code NoPadding}. See JCE spec.
+ *
+ * @param padding The padding type. The only allowed value is
+ * {@code NoPadding} case insensitive).
+ *
+ * @throws NoSuchPaddingException if a padding scheme besides
+ * {@code NoPadding} is provided.
+ */
+ @Override
+ protected void engineSetPadding(String padding)
+ throws NoSuchPaddingException {
+ if (padding.equalsIgnoreCase("NoPadding") == false) {
+ throw new NoSuchPaddingException("Padding must be NoPadding");
+ }
+ }
+
+ /**
+ * Returns the block size. For a stream cipher like ChaCha20, this
+ * value will always be zero.
+ *
+ * @return This method always returns 0. See the JCE Specification.
+ */
+ @Override
+ protected int engineGetBlockSize() {
+ return 0;
+ }
+
+ /**
+ * Get the output size based on an input length. In simple stream-cipher
+ * mode, the output size will equal the input size. For ChaCha20-Poly1305
+ * for encryption the output size will be the sum of the input length
+ * and tag length. For decryption, the output size will be the input
+ * length less the tag length or zero, whichever is larger.
+ *
+ * @param inputLen the length in bytes of the input
+ *
+ * @return the output length in bytes.
+ */
+ @Override
+ protected int engineGetOutputSize(int inputLen) {
+ int outLen = 0;
+
+ if (mode == MODE_NONE) {
+ outLen = inputLen;
+ } else if (mode == MODE_AEAD) {
+ outLen = (direction == Cipher.ENCRYPT_MODE) ?
+ Math.addExact(inputLen, TAG_LENGTH) :
+ Integer.max(inputLen - TAG_LENGTH, 0);
+ }
+
+ return outLen;
+ }
+
+ /**
+ * Get the nonce value used.
+ *
+ * @return the nonce bytes. For ChaCha20 this will be a 12-byte value.
+ */
+ @Override
+ protected byte[] engineGetIV() {
+ return nonce.clone();
+ }
+
+ /**
+ * Get the algorithm parameters for this cipher. For the ChaCha20
+ * cipher, this will always return {@code null} as there currently is
+ * no {@code AlgorithmParameters} implementation for ChaCha20. For
+ * ChaCha20-Poly1305, a {@code ChaCha20Poly1305Parameters} object will be
+ * created and initialized with the configured nonce value and returned
+ * to the caller.
+ *
+ * @return a {@code null} value if the ChaCha20 cipher is used (mode is
+ * MODE_NONE), or a {@code ChaCha20Poly1305Parameters} object containing
+ * the nonce if the mode is MODE_AEAD.
+ */
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ AlgorithmParameters params = null;
+ if (mode == MODE_AEAD) {
+ try {
+ // Force the 12-byte nonce into a DER-encoded OCTET_STRING
+ byte[] derNonce = new byte[nonce.length + 2];
+ derNonce[0] = 0x04; // OCTET_STRING tag
+ derNonce[1] = (byte)nonce.length; // 12-byte length;
+ System.arraycopy(nonce, 0, derNonce, 2, nonce.length);
+ params = AlgorithmParameters.getInstance("ChaCha20-Poly1305");
+ params.init(derNonce);
+ } catch (NoSuchAlgorithmException | IOException exc) {
+ throw new RuntimeException(exc);
+ }
+ }
+
+ return params;
+ }
+
+ /**
+ * Initialize the engine using a key and secure random implementation. If
+ * a SecureRandom object is provided it will be used to create a random
+ * nonce value. If the {@code random} parameter is null an internal
+ * secure random source will be used to create the random nonce.
+ * The counter value will be set to 1.
+ *
+ * @param opmode the type of operation to do. This value may not be
+ * {@code Cipher.DECRYPT_MODE} or {@code Cipher.UNWRAP_MODE} mode
+ * because it must generate random parameters like the nonce.
+ * @param key a 256-bit key suitable for ChaCha20
+ * @param random a {@code SecureRandom} implementation used to create the
+ * random nonce. If {@code null} is used for the random object,
+ * then an internal secure random source will be used to create the
+ * nonce.
+ *
+ * @throws UnsupportedOperationException if the mode of operation
+ * is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}
+ * (currently unsupported).
+ * @throws InvalidKeyException if the key is of the wrong type or is
+ * not 256-bits in length. This will also be thrown if the opmode
+ * parameter is {@code Cipher.DECRYPT_MODE}.
+ * {@code Cipher.UNWRAP_MODE} would normally be disallowed in this
+ * context but it is preempted by the UOE case above.
+ */
+ @Override
+ protected void engineInit(int opmode, Key key, SecureRandom random)
+ throws InvalidKeyException {
+ if (opmode != Cipher.DECRYPT_MODE) {
+ byte[] newNonce = createRandomNonce(random);
+ counter = 1;
+ init(opmode, key, newNonce);
+ } else {
+ throw new InvalidKeyException("Default parameter generation " +
+ "disallowed in DECRYPT and UNWRAP modes");
+ }
+ }
+
+ /**
+ * Initialize the engine using a key and secure random implementation.
+ *
+ * @param opmode the type of operation to do. This value must be either
+ * {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}
+ * @param key a 256-bit key suitable for ChaCha20
+ * @param params a {@code ChaCha20ParameterSpec} that will provide
+ * the nonce and initial block counter value.
+ * @param random a {@code SecureRandom} implementation, this parameter
+ * is not used in this form of the initializer.
+ *
+ * @throws UnsupportedOperationException if the mode of operation
+ * is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}
+ * (currently unsupported).
+ * @throws InvalidKeyException if the key is of the wrong type or is
+ * not 256-bits in length. This will also be thrown if the opmode
+ * parameter is not {@code Cipher.ENCRYPT_MODE} or
+ * {@code Cipher.DECRYPT_MODE} (excepting the UOE case above).
+ * @throws InvalidAlgorithmParameterException if {@code params} is
+ * not a {@code ChaCha20ParameterSpec}
+ * @throws NullPointerException if {@code params} is {@code null}
+ */
+ @Override
+ protected void engineInit(int opmode, Key key,
+ AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidKeyException, InvalidAlgorithmParameterException {
+
+ // If AlgorithmParameterSpec is null, then treat this like an init
+ // of the form (int, Key, SecureRandom)
+ if (params == null) {
+ engineInit(opmode, key, random);
+ return;
+ }
+
+ // We will ignore the secure random implementation and use the nonce
+ // from the AlgorithmParameterSpec instead.
+ byte[] newNonce = null;
+ switch (mode) {
+ case MODE_NONE:
+ if (!(params instanceof ChaCha20ParameterSpec)) {
+ throw new InvalidAlgorithmParameterException(
+ "ChaCha20 algorithm requires ChaCha20ParameterSpec");
+ }
+ ChaCha20ParameterSpec chaParams = (ChaCha20ParameterSpec)params;
+ newNonce = chaParams.getNonce();
+ counter = ((long)chaParams.getCounter()) & 0x00000000FFFFFFFFL;
+ break;
+ case MODE_AEAD:
+ if (!(params instanceof IvParameterSpec)) {
+ throw new InvalidAlgorithmParameterException(
+ "ChaCha20-Poly1305 requires IvParameterSpec");
+ }
+ IvParameterSpec ivParams = (IvParameterSpec)params;
+ newNonce = ivParams.getIV();
+ if (newNonce.length != 12) {
+ throw new InvalidAlgorithmParameterException(
+ "ChaCha20-Poly1305 nonce must be 12 bytes in length");
+ }
+ break;
+ default:
+ // Should never happen
+ throw new RuntimeException("ChaCha20 in unsupported mode");
+ }
+ init(opmode, key, newNonce);
+ }
+
+ /**
+ * Initialize the engine using the {@code AlgorithmParameter} initialization
+ * format. This cipher does supports initialization with
+ * {@code AlgorithmParameter} objects for ChaCha20-Poly1305 but not for
+ * ChaCha20 as a simple stream cipher. In the latter case, it will throw
+ * an {@code InvalidAlgorithmParameterException} if the value is non-null.
+ * If a null value is supplied for the {@code params} field
+ * the cipher will be initialized with the counter value set to 1 and
+ * a random nonce. If {@code null} is used for the random object,
+ * then an internal secure random source will be used to create the
+ * nonce.
+ *
+ * @param opmode the type of operation to do. This value must be either
+ * {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}
+ * @param key a 256-bit key suitable for ChaCha20
+ * @param params a {@code null} value if the algorithm is ChaCha20, or
+ * the appropriate {@code AlgorithmParameters} object containing the
+ * nonce information if the algorithm is ChaCha20-Poly1305.
+ * @param random a {@code SecureRandom} implementation, may be {@code null}.
+ *
+ * @throws UnsupportedOperationException if the mode of operation
+ * is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}
+ * (currently unsupported).
+ * @throws InvalidKeyException if the key is of the wrong type or is
+ * not 256-bits in length. This will also be thrown if the opmode
+ * parameter is not {@code Cipher.ENCRYPT_MODE} or
+ * {@code Cipher.DECRYPT_MODE} (excepting the UOE case above).
+ * @throws InvalidAlgorithmParameterException if {@code params} is
+ * non-null and the algorithm is ChaCha20. This exception will be
+ * also thrown if the algorithm is ChaCha20-Poly1305 and an incorrect
+ * {@code AlgorithmParameters} object is supplied.
+ */
+ @Override
+ protected void engineInit(int opmode, Key key,
+ AlgorithmParameters params, SecureRandom random)
+ throws InvalidKeyException, InvalidAlgorithmParameterException {
+
+ // If AlgorithmParameters is null, then treat this like an init
+ // of the form (int, Key, SecureRandom)
+ if (params == null) {
+ engineInit(opmode, key, random);
+ return;
+ }
+
+ byte[] newNonce = null;
+ switch (mode) {
+ case MODE_NONE:
+ throw new InvalidAlgorithmParameterException(
+ "AlgorithmParameters not supported");
+ case MODE_AEAD:
+ String paramAlg = params.getAlgorithm();
+ if (!paramAlg.equalsIgnoreCase("ChaCha20-Poly1305")) {
+ throw new InvalidAlgorithmParameterException(
+ "Invalid parameter type: " + paramAlg);
+ }
+ try {
+ DerValue dv = new DerValue(params.getEncoded());
+ newNonce = dv.getOctetString();
+ if (newNonce.length != 12) {
+ throw new InvalidAlgorithmParameterException(
+ "ChaCha20-Poly1305 nonce must be " +
+ "12 bytes in length");
+ }
+ } catch (IOException ioe) {
+ throw new InvalidAlgorithmParameterException(ioe);
+ }
+ break;
+ default:
+ throw new RuntimeException("Invalid mode: " + mode);
+ }
+
+ // If after all the above processing we still don't have a nonce value
+ // then supply a random one provided a random source has been given.
+ if (newNonce == null) {
+ newNonce = createRandomNonce(random);
+ }
+
+ // Continue with initialization
+ init(opmode, key, newNonce);
+ }
+
+ /**
+ * Update additional authenticated data (AAD).
+ *
+ * @param src the byte array containing the authentication data.
+ * @param offset the starting offset in the buffer to update.
+ * @param len the amount of authentication data to update.
+ *
+ * @throws IllegalStateException if the cipher has not been initialized,
+ * {@code engineUpdate} has been called, or the cipher is running
+ * in a non-AEAD mode of operation. It will also throw this
+ * exception if the submitted AAD would overflow a 64-bit length
+ * counter.
+ */
+ @Override
+ protected void engineUpdateAAD(byte[] src, int offset, int len) {
+ if (!initialized) {
+ // We know that the cipher has not been initialized if the key
+ // is still null.
+ throw new IllegalStateException(
+ "Attempted to update AAD on uninitialized Cipher");
+ } else if (aadDone) {
+ // No AAD updates allowed after the PT/CT update method is called
+ throw new IllegalStateException("Attempted to update AAD on " +
+ "Cipher after plaintext/ciphertext update");
+ } else if (mode != MODE_AEAD) {
+ throw new IllegalStateException(
+ "Cipher is running in non-AEAD mode");
+ } else {
+ try {
+ aadLen = Math.addExact(aadLen, len);
+ authUpdate(src, offset, len);
+ } catch (ArithmeticException ae) {
+ throw new IllegalStateException("AAD overflow", ae);
+ }
+ }
+ }
+
+ /**
+ * Update additional authenticated data (AAD).
+ *
+ * @param src the ByteBuffer containing the authentication data.
+ *
+ * @throws IllegalStateException if the cipher has not been initialized,
+ * {@code engineUpdate} has been called, or the cipher is running
+ * in a non-AEAD mode of operation. It will also throw this
+ * exception if the submitted AAD would overflow a 64-bit length
+ * counter.
+ */
+ @Override
+ protected void engineUpdateAAD(ByteBuffer src) {
+ if (!initialized) {
+ // We know that the cipher has not been initialized if the key
+ // is still null.
+ throw new IllegalStateException(
+ "Attempted to update AAD on uninitialized Cipher");
+ } else if (aadDone) {
+ // No AAD updates allowed after the PT/CT update method is called
+ throw new IllegalStateException("Attempted to update AAD on " +
+ "Cipher after plaintext/ciphertext update");
+ } else if (mode != MODE_AEAD) {
+ throw new IllegalStateException(
+ "Cipher is running in non-AEAD mode");
+ } else {
+ try {
+ aadLen = Math.addExact(aadLen, (src.limit() - src.position()));
+ authenticator.engineUpdate(src);
+ } catch (ArithmeticException ae) {
+ throw new IllegalStateException("AAD overflow", ae);
+ }
+ }
+ }
+
+ /**
+ * Create a random 12-byte nonce.
+ *
+ * @param random a {@code SecureRandom} object. If {@code null} is
+ * provided a new {@code SecureRandom} object will be instantiated.
+ *
+ * @return a 12-byte array containing the random nonce.
+ */
+ private byte[] createRandomNonce(SecureRandom random) {
+ byte[] newNonce = new byte[12];
+ SecureRandom rand = (random != null) ? random : new SecureRandom();
+ rand.nextBytes(newNonce);
+ return newNonce;
+ }
+
+ /**
+ * Perform additional initialization actions based on the key and operation
+ * type.
+ *
+ * @param opmode the type of operation to do. This value must be either
+ * {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}
+ * @param key a 256-bit key suitable for ChaCha20
+ * @param newNonce the new nonce value for this initialization.
+ *
+ * @throws UnsupportedOperationException if the {@code opmode} parameter
+ * is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}
+ * (currently unsupported).
+ * @throws InvalidKeyException if the {@code opmode} parameter is not
+ * {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}, or
+ * if the key format is not {@code RAW}.
+ */
+ private void init(int opmode, Key key, byte[] newNonce)
+ throws InvalidKeyException {
+ if ((opmode == Cipher.WRAP_MODE) || (opmode == Cipher.UNWRAP_MODE)) {
+ throw new UnsupportedOperationException(
+ "WRAP_MODE and UNWRAP_MODE are not currently supported");
+ } else if ((opmode != Cipher.ENCRYPT_MODE) &&
+ (opmode != Cipher.DECRYPT_MODE)) {
+ throw new InvalidKeyException("Unknown opmode: " + opmode);
+ }
+
+ // Make sure that the provided key and nonce are unique before
+ // assigning them to the object.
+ byte[] newKeyBytes = getEncodedKey(key);
+ checkKeyAndNonce(newKeyBytes, newNonce);
+ this.keyBytes = newKeyBytes;
+ nonce = newNonce;
+
+ // Now that we have the key and nonce, we can build the initial state
+ setInitialState();
+
+ if (mode == MODE_NONE) {
+ engine = new EngineStreamOnly();
+ } else if (mode == MODE_AEAD) {
+ if (opmode == Cipher.ENCRYPT_MODE) {
+ engine = new EngineAEADEnc();
+ } else if (opmode == Cipher.DECRYPT_MODE) {
+ engine = new EngineAEADDec();
+ } else {
+ throw new InvalidKeyException("Not encrypt or decrypt mode");
+ }
+ }
+
+ // We can also get one block's worth of keystream created
+ finalCounterValue = counter + MAX_UINT32;
+ generateKeystream();
+ direction = opmode;
+ aadDone = false;
+ this.keyStrOffset = 0;
+ initialized = true;
+ }
+
+ /**
+ * Check the key and nonce bytes to make sure that they do not repeat
+ * across reinitialization.
+ *
+ * @param newKeyBytes the byte encoding for the newly provided key
+ * @param newNonce the new nonce to be used with this initialization
+ *
+ * @throws InvalidKeyException if both the key and nonce match the
+ * previous initialization.
+ *
+ */
+ private void checkKeyAndNonce(byte[] newKeyBytes, byte[] newNonce)
+ throws InvalidKeyException {
+ // A new initialization must have either a different key or nonce
+ // so the starting state for each block is not the same as the
+ // previous initialization.
+ if (MessageDigest.isEqual(newKeyBytes, keyBytes) &&
+ MessageDigest.isEqual(newNonce, nonce)) {
+ throw new InvalidKeyException(
+ "Matching key and nonce from previous initialization");
+ }
+ }
+
+ /**
+ * Return the encoded key as a byte array
+ *
+ * @param key the {@code Key} object used for this {@code Cipher}
+ *
+ * @return the key bytes
+ *
+ * @throws InvalidKeyException if the key is of the wrong type or length,
+ * or if the key encoding format is not {@code RAW}.
+ */
+ private static byte[] getEncodedKey(Key key) throws InvalidKeyException {
+ if ("RAW".equals(key.getFormat()) == false) {
+ throw new InvalidKeyException("Key encoding format must be RAW");
+ }
+ byte[] encodedKey = key.getEncoded();
+ if (encodedKey == null || encodedKey.length != 32) {
+ throw new InvalidKeyException("Key length must be 256 bits");
+ }
+ return encodedKey;
+ }
+
+ /**
+ * Update the currently running operation with additional data
+ *
+ * @param in the plaintext or ciphertext input bytes (depending on the
+ * operation type).
+ * @param inOfs the offset into the input array
+ * @param inLen the length of the data to use for the update operation.
+ *
+ * @return the resulting plaintext or ciphertext bytes (depending on
+ * the operation type)
+ */
+ @Override
+ protected byte[] engineUpdate(byte[] in, int inOfs, int inLen) {
+ byte[] out = new byte[inLen];
+ try {
+ engine.doUpdate(in, inOfs, inLen, out, 0);
+ } catch (ShortBufferException | KeyException exc) {
+ throw new RuntimeException(exc);
+ }
+
+ return out;
+ }
+
+ /**
+ * Update the currently running operation with additional data
+ *
+ * @param in the plaintext or ciphertext input bytes (depending on the
+ * operation type).
+ * @param inOfs the offset into the input array
+ * @param inLen the length of the data to use for the update operation.
+ * @param out the byte array that will hold the resulting data. The array
+ * must be large enough to hold the resulting data.
+ * @param outOfs the offset for the {@code out} buffer to begin writing
+ * the resulting data.
+ *
+ * @return the length in bytes of the data written into the {@code out}
+ * buffer.
+ *
+ * @throws ShortBufferException if the buffer {@code out} does not have
+ * enough space to hold the resulting data.
+ */
+ @Override
+ protected int engineUpdate(byte[] in, int inOfs, int inLen,
+ byte[] out, int outOfs) throws ShortBufferException {
+ int bytesUpdated = 0;
+ try {
+ bytesUpdated = engine.doUpdate(in, inOfs, inLen, out, outOfs);
+ } catch (KeyException ke) {
+ throw new RuntimeException(ke);
+ }
+ return bytesUpdated;
+ }
+
+ /**
+ * Complete the currently running operation using any final
+ * data provided by the caller.
+ *
+ * @param in the plaintext or ciphertext input bytes (depending on the
+ * operation type).
+ * @param inOfs the offset into the input array
+ * @param inLen the length of the data to use for the update operation.
+ *
+ * @return the resulting plaintext or ciphertext bytes (depending on
+ * the operation type)
+ *
+ * @throws AEADBadTagException if, during decryption, the provided tag
+ * does not match the calculated tag.
+ */
+ @Override
+ protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen)
+ throws AEADBadTagException {
+ byte[] output = new byte[engineGetOutputSize(inLen)];
+ try {
+ engine.doFinal(in, inOfs, inLen, output, 0);
+ } catch (ShortBufferException | KeyException exc) {
+ throw new RuntimeException(exc);
+ } finally {
+ // Regardless of what happens, the cipher cannot be used for
+ // further processing until it has been freshly initialized.
+ initialized = false;
+ }
+ return output;
+ }
+
+ /**
+ * Complete the currently running operation using any final
+ * data provided by the caller.
+ *
+ * @param in the plaintext or ciphertext input bytes (depending on the
+ * operation type).
+ * @param inOfs the offset into the input array
+ * @param inLen the length of the data to use for the update operation.
+ * @param out the byte array that will hold the resulting data. The array
+ * must be large enough to hold the resulting data.
+ * @param outOfs the offset for the {@code out} buffer to begin writing
+ * the resulting data.
+ *
+ * @return the length in bytes of the data written into the {@code out}
+ * buffer.
+ *
+ * @throws ShortBufferException if the buffer {@code out} does not have
+ * enough space to hold the resulting data.
+ * @throws AEADBadTagException if, during decryption, the provided tag
+ * does not match the calculated tag.
+ */
+ @Override
+ protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,
+ int outOfs) throws ShortBufferException, AEADBadTagException {
+
+ int bytesUpdated = 0;
+ try {
+ bytesUpdated = engine.doFinal(in, inOfs, inLen, out, outOfs);
+ } catch (KeyException ke) {
+ throw new RuntimeException(ke);
+ } finally {
+ // Regardless of what happens, the cipher cannot be used for
+ // further processing until it has been freshly initialized.
+ initialized = false;
+ }
+ return bytesUpdated;
+ }
+
+ /**
+ * Wrap a {@code Key} using this Cipher's current encryption parameters.
+ *
+ * @param key the key to wrap. The data that will be encrypted will
+ * be the provided {@code Key} in its encoded form.
+ *
+ * @return a byte array consisting of the wrapped key.
+ *
+ * @throws UnsupportedOperationException this will (currently) always
+ * be thrown, as this method is not currently supported.
+ */
+ @Override
+ protected byte[] engineWrap(Key key) throws IllegalBlockSizeException,
+ InvalidKeyException {
+ throw new UnsupportedOperationException(
+ "Wrap operations are not supported");
+ }
+
+ /**
+ * Unwrap a {@code Key} using this Cipher's current encryption parameters.
+ *
+ * @param wrappedKey the key to unwrap.
+ * @param algorithm the algorithm associated with the wrapped key
+ * @param type the type of the wrapped key. This is one of
+ * {@code SECRET_KEY}, {@code PRIVATE_KEY}, or {@code PUBLIC_KEY}.
+ *
+ * @return the unwrapped key as a {@code Key} object.
+ *
+ * @throws UnsupportedOperationException this will (currently) always
+ * be thrown, as this method is not currently supported.
+ */
+ @Override
+ protected Key engineUnwrap(byte[] wrappedKey, String algorithm,
+ int type) throws InvalidKeyException, NoSuchAlgorithmException {
+ throw new UnsupportedOperationException(
+ "Unwrap operations are not supported");
+ }
+
+ /**
+ * Get the length of a provided key in bits.
+ *
+ * @param key the key to be evaluated
+ *
+ * @return the length of the key in bits
+ *
+ * @throws InvalidKeyException if the key is invalid or does not
+ * have an encoded form.
+ */
+ @Override
+ protected int engineGetKeySize(Key key) throws InvalidKeyException {
+ byte[] encodedKey = getEncodedKey(key);
+ return encodedKey.length << 3;
+ }
+
+ /**
+ * Set the initial state. This will populate the state array and put the
+ * key and nonce into their proper locations. The counter field is not
+ * set here.
+ *
+ * @throws IllegalArgumentException if the key or nonce are not in
+ * their proper lengths (32 bytes for the key, 12 bytes for the
+ * nonce).
+ * @throws InvalidKeyException if the key does not support an encoded form.
+ */
+ private void setInitialState() throws InvalidKeyException {
+ // Apply constants to first 4 words
+ startState[0] = STATE_CONST_0;
+ startState[1] = STATE_CONST_1;
+ startState[2] = STATE_CONST_2;
+ startState[3] = STATE_CONST_3;
+
+ // Apply the key bytes as 8 32-bit little endian ints (4 through 11)
+ for (int i = 0; i < 32; i += 4) {
+ startState[(i / 4) + 4] = (keyBytes[i] & 0x000000FF) |
+ ((keyBytes[i + 1] << 8) & 0x0000FF00) |
+ ((keyBytes[i + 2] << 16) & 0x00FF0000) |
+ ((keyBytes[i + 3] << 24) & 0xFF000000);
+ }
+
+ startState[12] = 0;
+
+ // The final integers for the state are from the nonce
+ // interpreted as 3 little endian integers
+ for (int i = 0; i < 12; i += 4) {
+ startState[(i / 4) + 13] = (nonce[i] & 0x000000FF) |
+ ((nonce[i + 1] << 8) & 0x0000FF00) |
+ ((nonce[i + 2] << 16) & 0x00FF0000) |
+ ((nonce[i + 3] << 24) & 0xFF000000);
+ }
+ }
+
+ /**
+ * Using the current state and counter create the next set of keystream
+ * bytes. This method will generate the next 512 bits of keystream and
+ * return it in the {@code keyStream} parameter. Following the
+ * block function the counter will be incremented.
+ */
+ private void generateKeystream() {
+ chaCha20Block(startState, counter, keyStream);
+ counter++;
+ }
+
+ /**
+ * Perform a full 20-round ChaCha20 transform on the initial state.
+ *
+ * @param initState the starting state, not including the counter
+ * value.
+ * @param counter the counter value to apply
+ * @param result the array that will hold the result of the ChaCha20
+ * block function.
+ *
+ * @note it is the caller's responsibility to ensure that the workState
+ * is sized the same as the initState, no checking is performed internally.
+ */
+ private static void chaCha20Block(int[] initState, long counter,
+ byte[] result) {
+ // Create an initial state and clone a working copy
+ int ws00 = STATE_CONST_0;
+ int ws01 = STATE_CONST_1;
+ int ws02 = STATE_CONST_2;
+ int ws03 = STATE_CONST_3;
+ int ws04 = initState[4];
+ int ws05 = initState[5];
+ int ws06 = initState[6];
+ int ws07 = initState[7];
+ int ws08 = initState[8];
+ int ws09 = initState[9];
+ int ws10 = initState[10];
+ int ws11 = initState[11];
+ int ws12 = (int)counter;
+ int ws13 = initState[13];
+ int ws14 = initState[14];
+ int ws15 = initState[15];
+
+ // Peform 10 iterations of the 8 quarter round set
+ for (int round = 0; round < 10; round++) {
+ ws00 += ws04;
+ ws12 = Integer.rotateLeft(ws12 ^ ws00, 16);
+
+ ws08 += ws12;
+ ws04 = Integer.rotateLeft(ws04 ^ ws08, 12);
+
+ ws00 += ws04;
+ ws12 = Integer.rotateLeft(ws12 ^ ws00, 8);
+
+ ws08 += ws12;
+ ws04 = Integer.rotateLeft(ws04 ^ ws08, 7);
+
+ ws01 += ws05;
+ ws13 = Integer.rotateLeft(ws13 ^ ws01, 16);
+
+ ws09 += ws13;
+ ws05 = Integer.rotateLeft(ws05 ^ ws09, 12);
+
+ ws01 += ws05;
+ ws13 = Integer.rotateLeft(ws13 ^ ws01, 8);
+
+ ws09 += ws13;
+ ws05 = Integer.rotateLeft(ws05 ^ ws09, 7);
+
+ ws02 += ws06;
+ ws14 = Integer.rotateLeft(ws14 ^ ws02, 16);
+
+ ws10 += ws14;
+ ws06 = Integer.rotateLeft(ws06 ^ ws10, 12);
+
+ ws02 += ws06;
+ ws14 = Integer.rotateLeft(ws14 ^ ws02, 8);
+
+ ws10 += ws14;
+ ws06 = Integer.rotateLeft(ws06 ^ ws10, 7);
+
+ ws03 += ws07;
+ ws15 = Integer.rotateLeft(ws15 ^ ws03, 16);
+
+ ws11 += ws15;
+ ws07 = Integer.rotateLeft(ws07 ^ ws11, 12);
+
+ ws03 += ws07;
+ ws15 = Integer.rotateLeft(ws15 ^ ws03, 8);
+
+ ws11 += ws15;
+ ws07 = Integer.rotateLeft(ws07 ^ ws11, 7);
+
+ ws00 += ws05;
+ ws15 = Integer.rotateLeft(ws15 ^ ws00, 16);
+
+ ws10 += ws15;
+ ws05 = Integer.rotateLeft(ws05 ^ ws10, 12);
+
+ ws00 += ws05;
+ ws15 = Integer.rotateLeft(ws15 ^ ws00, 8);
+
+ ws10 += ws15;
+ ws05 = Integer.rotateLeft(ws05 ^ ws10, 7);
+
+ ws01 += ws06;
+ ws12 = Integer.rotateLeft(ws12 ^ ws01, 16);
+
+ ws11 += ws12;
+ ws06 = Integer.rotateLeft(ws06 ^ ws11, 12);
+
+ ws01 += ws06;
+ ws12 = Integer.rotateLeft(ws12 ^ ws01, 8);
+
+ ws11 += ws12;
+ ws06 = Integer.rotateLeft(ws06 ^ ws11, 7);
+
+ ws02 += ws07;
+ ws13 = Integer.rotateLeft(ws13 ^ ws02, 16);
+
+ ws08 += ws13;
+ ws07 = Integer.rotateLeft(ws07 ^ ws08, 12);
+
+ ws02 += ws07;
+ ws13 = Integer.rotateLeft(ws13 ^ ws02, 8);
+
+ ws08 += ws13;
+ ws07 = Integer.rotateLeft(ws07 ^ ws08, 7);
+
+ ws03 += ws04;
+ ws14 = Integer.rotateLeft(ws14 ^ ws03, 16);
+
+ ws09 += ws14;
+ ws04 = Integer.rotateLeft(ws04 ^ ws09, 12);
+
+ ws03 += ws04;
+ ws14 = Integer.rotateLeft(ws14 ^ ws03, 8);
+
+ ws09 += ws14;
+ ws04 = Integer.rotateLeft(ws04 ^ ws09, 7);
+ }
+
+ // Add the end working state back into the original state
+ asIntLittleEndian.set(result, 0, ws00 + STATE_CONST_0);
+ asIntLittleEndian.set(result, 4, ws01 + STATE_CONST_1);
+ asIntLittleEndian.set(result, 8, ws02 + STATE_CONST_2);
+ asIntLittleEndian.set(result, 12, ws03 + STATE_CONST_3);
+ asIntLittleEndian.set(result, 16, ws04 + initState[4]);
+ asIntLittleEndian.set(result, 20, ws05 + initState[5]);
+ asIntLittleEndian.set(result, 24, ws06 + initState[6]);
+ asIntLittleEndian.set(result, 28, ws07 + initState[7]);
+ asIntLittleEndian.set(result, 32, ws08 + initState[8]);
+ asIntLittleEndian.set(result, 36, ws09 + initState[9]);
+ asIntLittleEndian.set(result, 40, ws10 + initState[10]);
+ asIntLittleEndian.set(result, 44, ws11 + initState[11]);
+ // Add the counter back into workState[12]
+ asIntLittleEndian.set(result, 48, ws12 + (int)counter);
+ asIntLittleEndian.set(result, 52, ws13 + initState[13]);
+ asIntLittleEndian.set(result, 56, ws14 + initState[14]);
+ asIntLittleEndian.set(result, 60, ws15 + initState[15]);
+ }
+
+ /**
+ * Perform the ChaCha20 transform.
+ *
+ * @param in the array of bytes for the input
+ * @param inOff the offset into the input array to start the transform
+ * @param inLen the length of the data to perform the transform on.
+ * @param out the output array. It must be large enough to hold the
+ * resulting data
+ * @param outOff the offset into the output array to place the resulting
+ * data.
+ */
+ private void chaCha20Transform(byte[] in, int inOff, int inLen,
+ byte[] out, int outOff) throws KeyException {
+ int remainingData = inLen;
+
+ while (remainingData > 0) {
+ int ksRemain = keyStream.length - keyStrOffset;
+ if (ksRemain <= 0) {
+ if (counter <= finalCounterValue) {
+ generateKeystream();
+ keyStrOffset = 0;
+ ksRemain = keyStream.length;
+ } else {
+ throw new KeyException("Counter exhausted. " +
+ "Reinitialize with new key and/or nonce");
+ }
+ }
+
+ // XOR each byte in the keystream against the input
+ int xformLen = Math.min(remainingData, ksRemain);
+ xor(keyStream, keyStrOffset, in, inOff, out, outOff, xformLen);
+ outOff += xformLen;
+ inOff += xformLen;
+ keyStrOffset += xformLen;
+ remainingData -= xformLen;
+ }
+ }
+
+ private static void xor(byte[] in1, int off1, byte[] in2, int off2,
+ byte[] out, int outOff, int len) {
+ while (len >= 8) {
+ long v1 = (long) asLongView.get(in1, off1);
+ long v2 = (long) asLongView.get(in2, off2);
+ asLongView.set(out, outOff, v1 ^ v2);
+ off1 += 8;
+ off2 += 8;
+ outOff += 8;
+ len -= 8;
+ }
+ while (len > 0) {
+ out[outOff] = (byte) (in1[off1] ^ in2[off2]);
+ off1++;
+ off2++;
+ outOff++;
+ len--;
+ }
+ }
+
+ /**
+ * Perform initialization steps for the authenticator
+ *
+ * @throws InvalidKeyException if the key is unusable for some reason
+ * (invalid length, etc.)
+ */
+ private void initAuthenticator() throws InvalidKeyException {
+ authenticator = new Poly1305();
+
+ // Derive the Poly1305 key from the starting state
+ byte[] serializedKey = new byte[KEYSTREAM_SIZE];
+ chaCha20Block(startState, 0, serializedKey);
+
+ authenticator.engineInit(new SecretKeySpec(serializedKey, 0, 32,
+ authAlgName), null);
+ aadLen = 0;
+ dataLen = 0;
+ }
+
+ /**
+ * Update the authenticator state with data. This routine can be used
+ * to add data to the authenticator, whether AAD or application data.
+ *
+ * @param data the data to stir into the authenticator.
+ * @param offset the offset into the data.
+ * @param length the length of data to add to the authenticator.
+ *
+ * @return the number of bytes processed by this method.
+ */
+ private int authUpdate(byte[] data, int offset, int length) {
+ Objects.checkFromIndexSize(offset, length, data.length);
+ authenticator.engineUpdate(data, offset, length);
+ return length;
+ }
+
+ /**
+ * Finalize the data and return the tag.
+ *
+ * @param data an array containing any remaining data to process.
+ * @param dataOff the offset into the data.
+ * @param length the length of the data to process.
+ * @param out the array to write the resulting tag into
+ * @param outOff the offset to begin writing the data.
+ *
+ * @throws ShortBufferException if there is insufficient room to
+ * write the tag.
+ */
+ private void authFinalizeData(byte[] data, int dataOff, int length,
+ byte[] out, int outOff) throws ShortBufferException {
+ // Update with the final chunk of ciphertext, then pad to a
+ // multiple of 16.
+ if (data != null) {
+ dataLen += authUpdate(data, dataOff, length);
+ }
+ authPad16(dataLen);
+
+ // Also write the AAD and ciphertext data lengths as little-endian
+ // 64-bit values.
+ authWriteLengths(aadLen, dataLen, lenBuf);
+ authenticator.engineUpdate(lenBuf, 0, lenBuf.length);
+ byte[] tag = authenticator.engineDoFinal();
+ Objects.checkFromIndexSize(outOff, tag.length, out.length);
+ System.arraycopy(tag, 0, out, outOff, tag.length);
+ aadLen = 0;
+ dataLen = 0;
+ }
+
+ /**
+ * Based on a given length of data, make the authenticator process
+ * zero bytes that will pad the length out to a multiple of 16.
+ *
+ * @param dataLen the starting length to be padded.
+ */
+ private void authPad16(long dataLen) {
+ // Pad out the AAD or data to a multiple of 16 bytes
+ authenticator.engineUpdate(padBuf, 0,
+ (TAG_LENGTH - ((int)dataLen & 15)) & 15);
+ }
+
+ /**
+ * Write the two 64-bit little-endian length fields into an array
+ * for processing by the poly1305 authenticator.
+ *
+ * @param aLen the length of the AAD.
+ * @param dLen the length of the application data.
+ * @param buf the buffer to write the two lengths into.
+ *
+ * @note it is the caller's responsibility to provide an array large
+ * enough to hold the two longs.
+ */
+ private void authWriteLengths(long aLen, long dLen, byte[] buf) {
+ asLongLittleEndian.set(buf, 0, aLen);
+ asLongLittleEndian.set(buf, Long.BYTES, dLen);
+ }
+
+ /**
+ * Interface for the underlying processing engines for ChaCha20
+ */
+ interface ChaChaEngine {
+ /**
+ * Perform a multi-part update for ChaCha20.
+ *
+ * @param in the input data.
+ * @param inOff the offset into the input.
+ * @param inLen the length of the data to process.
+ * @param out the output buffer.
+ * @param outOff the offset at which to write the output data.
+ *
+ * @return the number of output bytes written.
+ *
+ * @throws ShortBufferException if the output buffer does not
+ * provide enough space.
+ * @throws KeyException if the counter value has been exhausted.
+ */
+ int doUpdate(byte[] in, int inOff, int inLen, byte[] out, int outOff)
+ throws ShortBufferException, KeyException;
+
+ /**
+ * Finalize a multi-part or single-part ChaCha20 operation.
+ *
+ * @param in the input data.
+ * @param inOff the offset into the input.
+ * @param inLen the length of the data to process.
+ * @param out the output buffer.
+ * @param outOff the offset at which to write the output data.
+ *
+ * @return the number of output bytes written.
+ *
+ * @throws ShortBufferException if the output buffer does not
+ * provide enough space.
+ * @throws AEADBadTagException if in decryption mode the provided
+ * tag and calculated tag do not match.
+ * @throws KeyException if the counter value has been exhausted.
+ */
+ int doFinal(byte[] in, int inOff, int inLen, byte[] out, int outOff)
+ throws ShortBufferException, AEADBadTagException, KeyException;
+ }
+
+ private final class EngineStreamOnly implements ChaChaEngine {
+
+ private EngineStreamOnly () { }
+
+ @Override
+ public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,
+ int outOff) throws ShortBufferException, KeyException {
+ if (initialized) {
+ try {
+ if (out != null) {
+ Objects.checkFromIndexSize(outOff, inLen, out.length);
+ } else {
+ throw new ShortBufferException(
+ "Output buffer too small");
+ }
+ } catch (IndexOutOfBoundsException iobe) {
+ throw new ShortBufferException("Output buffer too small");
+ }
+ if (in != null) {
+ Objects.checkFromIndexSize(inOff, inLen, in.length);
+ chaCha20Transform(in, inOff, inLen, out, outOff);
+ }
+ return inLen;
+ } else {
+ throw new IllegalStateException(
+ "Must use either a different key or iv.");
+ }
+ }
+
+ @Override
+ public int doFinal(byte[] in, int inOff, int inLen, byte[] out,
+ int outOff) throws ShortBufferException, KeyException {
+ return doUpdate(in, inOff, inLen, out, outOff);
+ }
+ }
+
+ private final class EngineAEADEnc implements ChaChaEngine {
+
+ private EngineAEADEnc() throws InvalidKeyException {
+ initAuthenticator();
+ counter = 1;
+ }
+
+ @Override
+ public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,
+ int outOff) throws ShortBufferException, KeyException {
+ if (initialized) {
+ // If this is the first update since AAD updates, signal that
+ // we're done processing AAD info and pad the AAD to a multiple
+ // of 16 bytes.
+ if (!aadDone) {
+ authPad16(aadLen);
+ aadDone = true;
+ }
+ try {
+ if (out != null) {
+ Objects.checkFromIndexSize(outOff, inLen, out.length);
+ } else {
+ throw new ShortBufferException(
+ "Output buffer too small");
+ }
+ } catch (IndexOutOfBoundsException iobe) {
+ throw new ShortBufferException("Output buffer too small");
+ }
+ if (in != null) {
+ Objects.checkFromIndexSize(inOff, inLen, in.length);
+ chaCha20Transform(in, inOff, inLen, out, outOff);
+ dataLen += authUpdate(out, outOff, inLen);
+ }
+
+ return inLen;
+ } else {
+ throw new IllegalStateException(
+ "Must use either a different key or iv.");
+ }
+ }
+
+ @Override
+ public int doFinal(byte[] in, int inOff, int inLen, byte[] out,
+ int outOff) throws ShortBufferException, KeyException {
+ // Make sure we have enough room for the remaining data (if any)
+ // and the tag.
+ if ((inLen + TAG_LENGTH) > (out.length - outOff)) {
+ throw new ShortBufferException("Output buffer too small");
+ }
+
+ doUpdate(in, inOff, inLen, out, outOff);
+ authFinalizeData(null, 0, 0, out, outOff + inLen);
+ aadDone = false;
+ return inLen + TAG_LENGTH;
+ }
+ }
+
+ private final class EngineAEADDec implements ChaChaEngine {
+
+ private final ByteArrayOutputStream cipherBuf;
+ private final byte[] tag;
+
+ private EngineAEADDec() throws InvalidKeyException {
+ initAuthenticator();
+ counter = 1;
+ cipherBuf = new ByteArrayOutputStream(CIPHERBUF_BASE);
+ tag = new byte[TAG_LENGTH];
+ }
+
+ @Override
+ public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,
+ int outOff) {
+ if (initialized) {
+ // If this is the first update since AAD updates, signal that
+ // we're done processing AAD info and pad the AAD to a multiple
+ // of 16 bytes.
+ if (!aadDone) {
+ authPad16(aadLen);
+ aadDone = true;
+ }
+
+ if (in != null) {
+ Objects.checkFromIndexSize(inOff, inLen, in.length);
+ cipherBuf.write(in, inOff, inLen);
+ }
+ } else {
+ throw new IllegalStateException(
+ "Must use either a different key or iv.");
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int doFinal(byte[] in, int inOff, int inLen, byte[] out,
+ int outOff) throws ShortBufferException, AEADBadTagException,
+ KeyException {
+
+ byte[] ctPlusTag;
+ int ctPlusTagLen;
+ if (cipherBuf.size() == 0 && inOff == 0) {
+ // No previous data has been seen before doFinal, so we do
+ // not need to hold any ciphertext in a buffer. We can
+ // process it directly from the "in" parameter.
+ doUpdate(null, inOff, inLen, out, outOff);
+ ctPlusTag = in;
+ ctPlusTagLen = inLen;
+ } else {
+ doUpdate(in, inOff, inLen, out, outOff);
+ ctPlusTag = cipherBuf.toByteArray();
+ ctPlusTagLen = ctPlusTag.length;
+ }
+ cipherBuf.reset();
+
+ // There must at least be a tag length's worth of ciphertext
+ // data in the buffered input.
+ if (ctPlusTagLen < TAG_LENGTH) {
+ throw new AEADBadTagException("Input too short - need tag");
+ }
+ int ctLen = ctPlusTagLen - TAG_LENGTH;
+
+ // Make sure we will have enough room for the output buffer
+ try {
+ Objects.checkFromIndexSize(outOff, ctLen, out.length);
+ } catch (IndexOutOfBoundsException ioobe) {
+ throw new ShortBufferException("Output buffer too small");
+ }
+
+ // Calculate and compare the tag. Only do the decryption
+ // if and only if the tag matches.
+ authFinalizeData(ctPlusTag, 0, ctLen, tag, 0);
+ if (Arrays.compare(ctPlusTag, ctLen, ctPlusTagLen,
+ tag, 0, tag.length) != 0) {
+ throw new AEADBadTagException("Tag mismatch");
+ }
+ chaCha20Transform(ctPlusTag, 0, ctLen, out, outOff);
+ aadDone = false;
+
+ return ctLen;
+ }
+ }
+
+ public static final class ChaCha20Only extends ChaCha20Cipher {
+ public ChaCha20Only() {
+ mode = MODE_NONE;
+ }
+ }
+
+ public static final class ChaCha20Poly1305 extends ChaCha20Cipher {
+ public ChaCha20Poly1305() {
+ mode = MODE_AEAD;
+ authAlgName = "Poly1305";
+ }
+ }
+}