/* * Copyright (c) 2018, 2024, 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.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.*; import javax.crypto.spec.ChaCha20ParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; 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 KS_MAX_LEN = 1024; private static final int KS_BLK_SIZE = 64; private static final int KS_SIZE_INTS = KS_BLK_SIZE / Integer.BYTES; // 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 initCounterValue; private long counter; // The base state is created at initialization time as a 16-int array // and then is copied into either local variables for computations (Java) or // into SIMD registers (intrinsics). private final int[] startState = new int[KS_SIZE_INTS]; // The output keystream array is sized to hold keystream output from the // implChaCha20Block method. This can range from a single block at a time // (Java software) up to 16 blocks on x86_64 with AVX512 support. private final byte[] keyStream = new byte[KS_MAX_LEN]; // The keystream buffer limit and offset private int keyStrLimit; 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")) { 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")) { 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 required to hold the result of the next update or * doFinal operation. 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 plus any previously unprocessed data minus the tag * length, minimum zero. * * @param inputLen the length in bytes of the input * * @return the output length in bytes. */ @Override protected int engineGetOutputSize(int inputLen) { return engine.getOutputSize(inputLen, true); } /** * Get the nonce value used. * * @return the nonce bytes. For ChaCha20 this will be a 12-byte value. */ @Override protected byte[] engineGetIV() { return (nonce != null) ? nonce.clone() : null; } /** * 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) { // In a pre-initialized state or any state without a nonce value // this call should cause a random nonce to be generated, but // not attached to the object. byte[] nonceData = (initialized || nonce != null) ? nonce : createRandomNonce(null); try { // Place the 12-byte nonce into a DER-encoded OCTET_STRING params = AlgorithmParameters.getInstance("ChaCha20-Poly1305"); params.init((new DerValue( DerValue.tag_OctetString, nonceData).toByteArray())); } catch (NoSuchAlgorithmException | IOException exc) { throw new ProviderException(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; switch (mode) { case MODE_NONE: if (!(params instanceof ChaCha20ParameterSpec)) { throw new InvalidAlgorithmParameterException( "ChaCha20 algorithm requires ChaCha20ParameterSpec"); } ChaCha20ParameterSpec chaParams = (ChaCha20ParameterSpec)params; newNonce = chaParams.getNonce(); initCounterValue = ((long)chaParams.getCounter()) & 0x00000000FFFFFFFFL; counter = initCounterValue; 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 ProviderException("ChaCha20 in unsupported mode"); } init(opmode, key, newNonce); } /** * Initialize the engine using the {@code AlgorithmParameter} initialization * format. This cipher 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; 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 ProviderException("Invalid mode: " + mode); } // 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 static 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 { // Cipher.init() already checks opmode to be: // ENCRYPT_MODE/DECRYPT_MODE/WRAP_MODE/UNWRAP_MODE if ((opmode == Cipher.WRAP_MODE) || (opmode == Cipher.UNWRAP_MODE)) { throw new UnsupportedOperationException( "WRAP_MODE and UNWRAP_MODE are not currently supported"); } // Make sure that the provided key and nonce are unique before // assigning them to the object. Key and nonce uniqueness // protection is for encryption operations only. byte[] newKeyBytes = getEncodedKey(key); if (opmode == Cipher.ENCRYPT_MODE) { checkKeyAndNonce(newKeyBytes, newNonce); } if (this.keyBytes != null) { Arrays.fill(this.keyBytes, (byte)0); } 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 generate the first block (or blocks if intrinsics // are capable of doing multiple blocks at a time) of keystream. finalCounterValue = counter + MAX_UINT32; this.keyStrLimit = chaCha20Block(startState, counter, keyStream); this.keyStrOffset = 0; this.counter += (keyStrLimit / KS_BLK_SIZE); direction = opmode; aadDone = false; 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())) { throw new InvalidKeyException("Key encoding format must be RAW"); } byte[] encodedKey = key.getEncoded(); if (encodedKey == null || encodedKey.length != 32) { if (encodedKey != null) { Arrays.fill(encodedKey, (byte)0); } 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[engine.getOutputSize(inLen, false)]; try { engine.doUpdate(in, inOfs, inLen, out, 0); } catch (ShortBufferException | KeyException exc) { throw new ProviderException(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; try { bytesUpdated = engine.doUpdate(in, inOfs, inLen, out, outOfs); } catch (KeyException ke) { throw new ProviderException(ke); } return bytesUpdated; } /** * Update the currently running operation with additional data * * @param input the plaintext or ciphertext ByteBuffer * @param output ByteBuffer that will hold the resulting data. This * must be large enough to hold the resulting data. * * @return the length in bytes of the data written into the {@code output} * buffer. * * @throws ShortBufferException if the buffer {@code output} does not have * enough space to hold the resulting data. */ @Override protected int engineUpdate(ByteBuffer input, ByteBuffer output) throws ShortBufferException { try { return bufferCrypt(input, output, true); } catch (AEADBadTagException e) { // exception is never thrown by update ops throw new AssertionError(e); } } /** * 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[engine.getOutputSize(inLen, true)]; try { engine.doFinal(in, inOfs, inLen, output, 0); } catch (ShortBufferException | KeyException exc) { throw new ProviderException(exc); } finally { // Reset the cipher's state to post-init values. resetStartState(); } 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; try { bytesUpdated = engine.doFinal(in, inOfs, inLen, out, outOfs); } catch (KeyException ke) { throw new ProviderException(ke); } finally { // Reset the cipher's state to post-init values. resetStartState(); } return bytesUpdated; } /** * Complete the currently running operation using any final * data provided by the caller. * * @param input the plaintext or ciphertext input bytebuffer. * @param output ByteBuffer that will hold the resulting data. This * must be large enough to hold the resulting data. * * @return the resulting plaintext or ciphertext bytes. * * @throws ShortBufferException if the buffer {@code output} 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(ByteBuffer input, ByteBuffer output) throws ShortBufferException, AEADBadTagException { return bufferCrypt(input, output, false); } /* * Optimized version of bufferCrypt from CipherSpi.java. Direct * ByteBuffers send to the engine code. */ private int bufferCrypt(ByteBuffer input, ByteBuffer output, boolean isUpdate) throws ShortBufferException, AEADBadTagException { if ((input == null) || (output == null)) { throw new NullPointerException ("Input and output buffers must not be null"); } int inPos = input.position(); int inLimit = input.limit(); int inLen = inLimit - inPos; if (isUpdate && (inLen == 0)) { return 0; } int outLenNeeded = engine.getOutputSize(inLen, !isUpdate); if (output.remaining() < outLenNeeded) { throw new ShortBufferException("Need at least " + outLenNeeded + " bytes of space in output buffer"); } int total = 0; // Check if input bytebuffer is heap-backed if (input.hasArray()) { byte[] inArray = input.array(); int inOfs = input.arrayOffset() + inPos; byte[] outArray; // Check if output bytebuffer is heap-backed if (output.hasArray()) { outArray = output.array(); int outPos = output.position(); int outOfs = output.arrayOffset() + outPos; // check array address and offsets and use temp output buffer // if output offset is larger than input offset and // falls within the range of input data boolean useTempOut = false; if (inArray == outArray && ((inOfs < outOfs) && (outOfs < inOfs + inLen))) { useTempOut = true; outArray = new byte[outLenNeeded]; outOfs = 0; } try { if (isUpdate) { total = engine.doUpdate(inArray, inOfs, inLen, outArray, outOfs); } else { total = engine.doFinal(inArray, inOfs, inLen, outArray, outOfs); } } catch (KeyException e) { throw new ProviderException(e); } if (useTempOut) { output.put(outArray, outOfs, total); } else { // adjust output position manually output.position(outPos + total); } } else { // if output is direct if (isUpdate) { outArray = engineUpdate(inArray, inOfs, inLen); } else { outArray = engineDoFinal(inArray, inOfs, inLen); } if (outArray != null && outArray.length != 0) { output.put(outArray); total = outArray.length; } } // adjust input position manually input.position(inLimit); } else { // Bytebuffers are both direct try { if (isUpdate) { return engine.doUpdate(input, output); } return engine.doFinal(input, output); } catch (KeyException e) { throw new ProviderException(e); } } return total; } /** * 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); Arrays.fill(encodedKey, (byte)0); 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. * */ private void setInitialState() { // 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); } } @ForceInline private static int chaCha20Block(int[] initState, long counter, byte[] result) { if (initState.length != KS_SIZE_INTS || result.length != KS_MAX_LEN) { throw new IllegalArgumentException( "Illegal state or keystream buffer length"); } // Set the counter value before sending into the underlying // private block method initState[12] = (int)counter; return implChaCha20Block(initState, result); } /** * Perform a full 20-round ChaCha20 transform on the initial state. * * @param initState the starting state using the current counter value. * @param result the array that will hold the result of the ChaCha20 * block function. * * @return the number of keystream bytes generated. In a pure Java method * this will always be 64 bytes, but intrinsics that make use of * AVX2 or AVX512 registers may generate multiple blocks of keystream * in a single call and therefore may be a larger multiple of 64. */ @IntrinsicCandidate private static int implChaCha20Block(int[] initState, 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 = initState[12]; int ws13 = initState[13]; int ws14 = initState[14]; int ws15 = initState[15]; // Perform 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]); asIntLittleEndian.set(result, 48, ws12 + initState[12]); asIntLittleEndian.set(result, 52, ws13 + initState[13]); asIntLittleEndian.set(result, 56, ws14 + initState[14]); asIntLittleEndian.set(result, 60, ws15 + initState[15]); return KS_BLK_SIZE; } /** * 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 = keyStrLimit - keyStrOffset; if (ksRemain <= 0) { if (counter <= finalCounterValue) { // Intrinsics can do multiple blocks at once. This means // it may overrun the counter. In order to prevent key // stream reuse, we adjust the key stream limit to only the // key stream length that is calculated from unique // counter values. keyStrLimit = chaCha20Block(startState, counter, keyStream); counter += (keyStrLimit / KS_BLK_SIZE); if (counter > finalCounterValue) { keyStrLimit -= (int)(counter - finalCounterValue) * 64; } keyStrOffset = 0; ksRemain = keyStrLimit; } 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 with the counter // value forced to zero. byte[] serializedKey = new byte[KS_MAX_LEN]; chaCha20Block(startState, 0L, 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 ProviderException if there is insufficient room to * write the tag. */ private void authFinalizeData(byte[] data, int dataOff, int length, byte[] out, int outOff) { // 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. * * @implNote 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); } /** * reset the Cipher's state to the values it had after * the initial init() call. * * Note: The cipher's internal "initialized" field is set differently * for ENCRYPT_MODE and DECRYPT_MODE in order to allow DECRYPT_MODE * ciphers to reuse the key/nonce/counter values. This kind of reuse * is disallowed in ENCRYPT_MODE. */ private void resetStartState() { keyStrLimit = 0; keyStrOffset = 0; counter = initCounterValue; aadDone = false; initialized = (direction == Cipher.DECRYPT_MODE); } /** * Interface for the underlying processing engines for ChaCha20 */ interface ChaChaEngine { /** * Size an output buffer based on the input and where applicable * the current state of the engine in a multipart operation. * * @param inLength the input length. * @param isFinal true if this is invoked from a doFinal call. * * @return the recommended size for the output buffer. */ int getOutputSize(int inLength, boolean isFinal); /** * 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; int doUpdate(ByteBuffer input, ByteBuffer output) throws ShortBufferException, KeyException; int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, KeyException, AEADBadTagException; } private final class EngineStreamOnly implements ChaChaEngine { private EngineStreamOnly () { } @Override public int getOutputSize(int inLength, boolean isFinal) { // The isFinal parameter is not relevant in this kind of engine return inLength; } @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); } @Override public int doUpdate(ByteBuffer input, ByteBuffer output) throws ShortBufferException, KeyException { byte[] data = new byte[input.remaining()]; input.get(data); doUpdate(data, 0, data.length, data, 0); output.put(data); return data.length; } @Override public int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, KeyException { return doUpdate(input, output); } } private final class EngineAEADEnc implements ChaChaEngine { @Override public int getOutputSize(int inLength, boolean isFinal) { return (isFinal ? Math.addExact(inLength, TAG_LENGTH) : inLength); } private EngineAEADEnc() throws InvalidKeyException { initAuthenticator(); initCounterValue = 1; counter = initCounterValue; } @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; } @Override public int doUpdate(ByteBuffer input, ByteBuffer output) throws ShortBufferException, KeyException { byte[] data = new byte[input.remaining()]; input.get(data); doUpdate(data, 0, data.length, data, 0); output.put(data); return data.length; } @Override public int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, KeyException { int len = input.remaining(); byte[] data = new byte[len + TAG_LENGTH]; input.get(data, 0, len); doFinal(data, 0, len, data, 0); output.put(data); return data.length; } } private final class EngineAEADDec implements ChaChaEngine { private AEADBufferedStream cipherBuf = null; private final byte[] tag; @Override public int getOutputSize(int inLen, boolean isFinal) { // If we are performing a decrypt-update we should always return // zero length since we cannot return any data until the tag has // been consumed and verified. CipherSpi.engineGetOutputSize will // always set isFinal to true to get the required output buffer // size. return (isFinal ? Integer.max(Math.addExact((inLen - TAG_LENGTH), getBufferedLength()), 0) : 0); } private void initBuffer(int len) { if (cipherBuf == null) { cipherBuf = new AEADBufferedStream(len); } } private int getBufferedLength() { if (cipherBuf != null) { return cipherBuf.size(); } return 0; } private EngineAEADDec() throws InvalidKeyException { initAuthenticator(); initCounterValue = 1; counter = initCounterValue; 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); initBuffer(inLen); cipherBuf.write(in, inOff, inLen); } } else { throw new IllegalStateException( "Must use either a different key or iv."); } return 0; } @Override public int doUpdate(ByteBuffer input, ByteBuffer output) { initBuffer(input.remaining()); cipherBuf.write(input); 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 (getBufferedLength() == 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.getBuffer(); inOff = 0; ctPlusTagLen = cipherBuf.size(); 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, inOff, ctLen, tag, 0); long tagCompare = ((long)asLongView.get(ctPlusTag, ctLen + inOff) ^ (long)asLongView.get(tag, 0)) | ((long)asLongView.get(ctPlusTag, ctLen + inOff + Long.BYTES) ^ (long)asLongView.get(tag, Long.BYTES)); if (tagCompare != 0) { throw new AEADBadTagException("Tag mismatch"); } chaCha20Transform(ctPlusTag, inOff, ctLen, out, outOff); aadDone = false; return ctLen; } @Override public int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, AEADBadTagException, KeyException { int len; int inLen = input.remaining(); byte[] ct = null, buf = null; //buf = (getBufferedLength() == 0 ? null : cipherBuf.toByteArray()); int bufLen = 0; // The length of cipher text and tag int ctLen = getBufferedLength() + inLen; if (ctLen < TAG_LENGTH) { throw new AEADBadTagException("Input too short - need tag"); } if (inLen < TAG_LENGTH) { if (inLen > 0) { doUpdate(input, output); } if (cipherBuf != null) { ct = cipherBuf.getBuffer(); } len = ctLen; } else { if (cipherBuf != null) { buf = cipherBuf.getBuffer(); bufLen = cipherBuf.size(); } ct = new byte[inLen]; input.get(ct, 0, inLen); len = inLen; } doUpdate(null, 0, 0, null, 0); // If there is an internal buffer, calculate its tag contribution. if (buf != null) { dataLen = authUpdate(buf, 0, bufLen); } // Complete tag calculation len -= TAG_LENGTH; authFinalizeData(ct, 0, len, tag, 0); // Check tag if ((((long) asLongView.get(ct, len) ^ (long) asLongView.get(tag, 0)) | ((long) asLongView.get(ct, len + Long.BYTES) ^ (long) asLongView.get(tag, Long.BYTES))) != 0) { throw new AEADBadTagException("Tag mismatch"); } // decrypt internal buffer in-place, then put it into the bytebuffer if (buf != null) { chaCha20Transform(buf, 0, bufLen, buf, 0); output.put(buf, 0, bufLen); } // decrypt input buffer in-place, append it to the bytebuffer chaCha20Transform(ct, 0, len, ct, 0); output.put(ct, 0, len); aadDone = false; return ctLen - TAG_LENGTH; } } 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"; } } }