Path: blob/master/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java
67773 views
/*1* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.crypto.provider;2627import java.io.ByteArrayOutputStream;28import java.io.IOException;29import java.lang.invoke.MethodHandles;30import java.lang.invoke.VarHandle;31import java.nio.ByteBuffer;32import java.nio.ByteOrder;33import java.security.*;34import java.security.spec.AlgorithmParameterSpec;35import java.util.Arrays;36import java.util.Objects;37import javax.crypto.*;38import javax.crypto.spec.ChaCha20ParameterSpec;39import javax.crypto.spec.IvParameterSpec;40import javax.crypto.spec.SecretKeySpec;41import sun.security.util.DerValue;4243/**44* Implementation of the ChaCha20 cipher, as described in RFC 7539.45*46* @since 1147*/48abstract class ChaCha20Cipher extends CipherSpi {49// Mode constants50private static final int MODE_NONE = 0;51private static final int MODE_AEAD = 1;5253// Constants used in setting up the initial state54private static final int STATE_CONST_0 = 0x61707865;55private static final int STATE_CONST_1 = 0x3320646e;56private static final int STATE_CONST_2 = 0x79622d32;57private static final int STATE_CONST_3 = 0x6b206574;5859// The keystream block size in bytes and as integers60private static final int KEYSTREAM_SIZE = 64;61private static final int KS_SIZE_INTS = KEYSTREAM_SIZE / Integer.BYTES;62private static final int CIPHERBUF_BASE = 1024;6364// The initialization state of the cipher65private boolean initialized;6667// The mode of operation for this object68protected int mode;6970// The direction (encrypt vs. decrypt) for the data flow71private int direction;7273// Has all AAD data been provided (i.e. have we called our first update)74private boolean aadDone = false;7576// The key's encoding in bytes for this object77private byte[] keyBytes;7879// The nonce used for this object80private byte[] nonce;8182// The counter83private static final long MAX_UINT32 = 0x00000000FFFFFFFFL;84private long finalCounterValue;85private long counter;8687// Two arrays, both implemented as 16-element integer arrays:88// The base state, created at initialization time, and a working89// state which is a clone of the start state, and is then modified90// with the counter and the ChaCha20 block function.91private final int[] startState = new int[KS_SIZE_INTS];92private final byte[] keyStream = new byte[KEYSTREAM_SIZE];9394// The offset into the current keystream95private int keyStrOffset;9697// AEAD-related fields and constants98private static final int TAG_LENGTH = 16;99private long aadLen;100private long dataLen;101102// Have a buffer of zero padding that can be read all or in part103// by the authenticator.104private static final byte[] padBuf = new byte[TAG_LENGTH];105106// Create a buffer for holding the AAD and Ciphertext lengths107private final byte[] lenBuf = new byte[TAG_LENGTH];108109// The authenticator (Poly1305) when running in AEAD mode110protected String authAlgName;111private Poly1305 authenticator;112113// The underlying engine for doing the ChaCha20/Poly1305 work114private ChaChaEngine engine;115116// Use this VarHandle for converting the state elements into little-endian117// integer values for the ChaCha20 block function.118private static final VarHandle asIntLittleEndian =119MethodHandles.byteArrayViewVarHandle(int[].class,120ByteOrder.LITTLE_ENDIAN);121122// Use this VarHandle for converting the AAD and data lengths into123// little-endian long values for AEAD tag computations.124private static final VarHandle asLongLittleEndian =125MethodHandles.byteArrayViewVarHandle(long[].class,126ByteOrder.LITTLE_ENDIAN);127128// Use this for pulling in 8 bytes at a time as longs for XOR operations129private static final VarHandle asLongView =130MethodHandles.byteArrayViewVarHandle(long[].class,131ByteOrder.nativeOrder());132133/**134* Default constructor.135*/136protected ChaCha20Cipher() { }137138/**139* Set the mode of operation. Since this is a stream cipher, there140* is no mode of operation in the block-cipher sense of things. The141* protected {@code mode} field will only accept a value of {@code None}142* (case-insensitive).143*144* @param mode The mode value145*146* @throws NoSuchAlgorithmException if a mode of operation besides147* {@code None} is provided.148*/149@Override150protected void engineSetMode(String mode) throws NoSuchAlgorithmException {151if (mode.equalsIgnoreCase("None") == false) {152throw new NoSuchAlgorithmException("Mode must be None");153}154}155156/**157* Set the padding scheme. Padding schemes do not make sense with stream158* ciphers, but allow {@code NoPadding}. See JCE spec.159*160* @param padding The padding type. The only allowed value is161* {@code NoPadding} case insensitive).162*163* @throws NoSuchPaddingException if a padding scheme besides164* {@code NoPadding} is provided.165*/166@Override167protected void engineSetPadding(String padding)168throws NoSuchPaddingException {169if (padding.equalsIgnoreCase("NoPadding") == false) {170throw new NoSuchPaddingException("Padding must be NoPadding");171}172}173174/**175* Returns the block size. For a stream cipher like ChaCha20, this176* value will always be zero.177*178* @return This method always returns 0. See the JCE Specification.179*/180@Override181protected int engineGetBlockSize() {182return 0;183}184185/**186* Get the output size required to hold the result of the next update or187* doFinal operation. In simple stream-cipher188* mode, the output size will equal the input size. For ChaCha20-Poly1305189* for encryption the output size will be the sum of the input length190* and tag length. For decryption, the output size will be the input191* length plus any previously unprocessed data minus the tag192* length, minimum zero.193*194* @param inputLen the length in bytes of the input195*196* @return the output length in bytes.197*/198@Override199protected int engineGetOutputSize(int inputLen) {200return engine.getOutputSize(inputLen, true);201}202203/**204* Get the nonce value used.205*206* @return the nonce bytes. For ChaCha20 this will be a 12-byte value.207*/208@Override209protected byte[] engineGetIV() {210return (nonce != null) ? nonce.clone() : null;211}212213/**214* Get the algorithm parameters for this cipher. For the ChaCha20215* cipher, this will always return {@code null} as there currently is216* no {@code AlgorithmParameters} implementation for ChaCha20. For217* ChaCha20-Poly1305, a {@code ChaCha20Poly1305Parameters} object will be218* created and initialized with the configured nonce value and returned219* to the caller.220*221* @return a {@code null} value if the ChaCha20 cipher is used (mode is222* MODE_NONE), or a {@code ChaCha20Poly1305Parameters} object containing223* the nonce if the mode is MODE_AEAD.224*/225@Override226protected AlgorithmParameters engineGetParameters() {227AlgorithmParameters params = null;228if (mode == MODE_AEAD) {229// In a pre-initialized state or any state without a nonce value230// this call should cause a random nonce to be generated, but231// not attached to the object.232byte[] nonceData = (initialized || nonce != null) ? nonce :233createRandomNonce(null);234try {235// Place the 12-byte nonce into a DER-encoded OCTET_STRING236params = AlgorithmParameters.getInstance("ChaCha20-Poly1305");237params.init((new DerValue(238DerValue.tag_OctetString, nonceData).toByteArray()));239} catch (NoSuchAlgorithmException | IOException exc) {240throw new RuntimeException(exc);241}242}243244return params;245}246247/**248* Initialize the engine using a key and secure random implementation. If249* a SecureRandom object is provided it will be used to create a random250* nonce value. If the {@code random} parameter is null an internal251* secure random source will be used to create the random nonce.252* The counter value will be set to 1.253*254* @param opmode the type of operation to do. This value may not be255* {@code Cipher.DECRYPT_MODE} or {@code Cipher.UNWRAP_MODE} mode256* because it must generate random parameters like the nonce.257* @param key a 256-bit key suitable for ChaCha20258* @param random a {@code SecureRandom} implementation used to create the259* random nonce. If {@code null} is used for the random object,260* then an internal secure random source will be used to create the261* nonce.262*263* @throws UnsupportedOperationException if the mode of operation264* is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}265* (currently unsupported).266* @throws InvalidKeyException if the key is of the wrong type or is267* not 256-bits in length. This will also be thrown if the opmode268* parameter is {@code Cipher.DECRYPT_MODE}.269* {@code Cipher.UNWRAP_MODE} would normally be disallowed in this270* context but it is preempted by the UOE case above.271*/272@Override273protected void engineInit(int opmode, Key key, SecureRandom random)274throws InvalidKeyException {275if (opmode != Cipher.DECRYPT_MODE) {276byte[] newNonce = createRandomNonce(random);277counter = 1;278init(opmode, key, newNonce);279} else {280throw new InvalidKeyException("Default parameter generation " +281"disallowed in DECRYPT and UNWRAP modes");282}283}284285/**286* Initialize the engine using a key and secure random implementation.287*288* @param opmode the type of operation to do. This value must be either289* {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}290* @param key a 256-bit key suitable for ChaCha20291* @param params a {@code ChaCha20ParameterSpec} that will provide292* the nonce and initial block counter value.293* @param random a {@code SecureRandom} implementation, this parameter294* is not used in this form of the initializer.295*296* @throws UnsupportedOperationException if the mode of operation297* is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}298* (currently unsupported).299* @throws InvalidKeyException if the key is of the wrong type or is300* not 256-bits in length. This will also be thrown if the opmode301* parameter is not {@code Cipher.ENCRYPT_MODE} or302* {@code Cipher.DECRYPT_MODE} (excepting the UOE case above).303* @throws InvalidAlgorithmParameterException if {@code params} is304* not a {@code ChaCha20ParameterSpec}305* @throws NullPointerException if {@code params} is {@code null}306*/307@Override308protected void engineInit(int opmode, Key key,309AlgorithmParameterSpec params, SecureRandom random)310throws InvalidKeyException, InvalidAlgorithmParameterException {311312// If AlgorithmParameterSpec is null, then treat this like an init313// of the form (int, Key, SecureRandom)314if (params == null) {315engineInit(opmode, key, random);316return;317}318319// We will ignore the secure random implementation and use the nonce320// from the AlgorithmParameterSpec instead.321byte[] newNonce = null;322switch (mode) {323case MODE_NONE:324if (!(params instanceof ChaCha20ParameterSpec)) {325throw new InvalidAlgorithmParameterException(326"ChaCha20 algorithm requires ChaCha20ParameterSpec");327}328ChaCha20ParameterSpec chaParams = (ChaCha20ParameterSpec)params;329newNonce = chaParams.getNonce();330counter = ((long)chaParams.getCounter()) & 0x00000000FFFFFFFFL;331break;332case MODE_AEAD:333if (!(params instanceof IvParameterSpec)) {334throw new InvalidAlgorithmParameterException(335"ChaCha20-Poly1305 requires IvParameterSpec");336}337IvParameterSpec ivParams = (IvParameterSpec)params;338newNonce = ivParams.getIV();339if (newNonce.length != 12) {340throw new InvalidAlgorithmParameterException(341"ChaCha20-Poly1305 nonce must be 12 bytes in length");342}343break;344default:345// Should never happen346throw new RuntimeException("ChaCha20 in unsupported mode");347}348init(opmode, key, newNonce);349}350351/**352* Initialize the engine using the {@code AlgorithmParameter} initialization353* format. This cipher does supports initialization with354* {@code AlgorithmParameter} objects for ChaCha20-Poly1305 but not for355* ChaCha20 as a simple stream cipher. In the latter case, it will throw356* an {@code InvalidAlgorithmParameterException} if the value is non-null.357* If a null value is supplied for the {@code params} field358* the cipher will be initialized with the counter value set to 1 and359* a random nonce. If {@code null} is used for the random object,360* then an internal secure random source will be used to create the361* nonce.362*363* @param opmode the type of operation to do. This value must be either364* {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}365* @param key a 256-bit key suitable for ChaCha20366* @param params a {@code null} value if the algorithm is ChaCha20, or367* the appropriate {@code AlgorithmParameters} object containing the368* nonce information if the algorithm is ChaCha20-Poly1305.369* @param random a {@code SecureRandom} implementation, may be {@code null}.370*371* @throws UnsupportedOperationException if the mode of operation372* is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}373* (currently unsupported).374* @throws InvalidKeyException if the key is of the wrong type or is375* not 256-bits in length. This will also be thrown if the opmode376* parameter is not {@code Cipher.ENCRYPT_MODE} or377* {@code Cipher.DECRYPT_MODE} (excepting the UOE case above).378* @throws InvalidAlgorithmParameterException if {@code params} is379* non-null and the algorithm is ChaCha20. This exception will be380* also thrown if the algorithm is ChaCha20-Poly1305 and an incorrect381* {@code AlgorithmParameters} object is supplied.382*/383@Override384protected void engineInit(int opmode, Key key,385AlgorithmParameters params, SecureRandom random)386throws InvalidKeyException, InvalidAlgorithmParameterException {387388// If AlgorithmParameters is null, then treat this like an init389// of the form (int, Key, SecureRandom)390if (params == null) {391engineInit(opmode, key, random);392return;393}394395byte[] newNonce = null;396switch (mode) {397case MODE_NONE:398throw new InvalidAlgorithmParameterException(399"AlgorithmParameters not supported");400case MODE_AEAD:401String paramAlg = params.getAlgorithm();402if (!paramAlg.equalsIgnoreCase("ChaCha20-Poly1305")) {403throw new InvalidAlgorithmParameterException(404"Invalid parameter type: " + paramAlg);405}406try {407DerValue dv = new DerValue(params.getEncoded());408newNonce = dv.getOctetString();409if (newNonce.length != 12) {410throw new InvalidAlgorithmParameterException(411"ChaCha20-Poly1305 nonce must be " +412"12 bytes in length");413}414} catch (IOException ioe) {415throw new InvalidAlgorithmParameterException(ioe);416}417break;418default:419throw new RuntimeException("Invalid mode: " + mode);420}421422// If after all the above processing we still don't have a nonce value423// then supply a random one provided a random source has been given.424if (newNonce == null) {425newNonce = createRandomNonce(random);426}427428// Continue with initialization429init(opmode, key, newNonce);430}431432/**433* Update additional authenticated data (AAD).434*435* @param src the byte array containing the authentication data.436* @param offset the starting offset in the buffer to update.437* @param len the amount of authentication data to update.438*439* @throws IllegalStateException if the cipher has not been initialized,440* {@code engineUpdate} has been called, or the cipher is running441* in a non-AEAD mode of operation. It will also throw this442* exception if the submitted AAD would overflow a 64-bit length443* counter.444*/445@Override446protected void engineUpdateAAD(byte[] src, int offset, int len) {447if (!initialized) {448// We know that the cipher has not been initialized if the key449// is still null.450throw new IllegalStateException(451"Attempted to update AAD on uninitialized Cipher");452} else if (aadDone) {453// No AAD updates allowed after the PT/CT update method is called454throw new IllegalStateException("Attempted to update AAD on " +455"Cipher after plaintext/ciphertext update");456} else if (mode != MODE_AEAD) {457throw new IllegalStateException(458"Cipher is running in non-AEAD mode");459} else {460try {461aadLen = Math.addExact(aadLen, len);462authUpdate(src, offset, len);463} catch (ArithmeticException ae) {464throw new IllegalStateException("AAD overflow", ae);465}466}467}468469/**470* Update additional authenticated data (AAD).471*472* @param src the ByteBuffer containing the authentication data.473*474* @throws IllegalStateException if the cipher has not been initialized,475* {@code engineUpdate} has been called, or the cipher is running476* in a non-AEAD mode of operation. It will also throw this477* exception if the submitted AAD would overflow a 64-bit length478* counter.479*/480@Override481protected void engineUpdateAAD(ByteBuffer src) {482if (!initialized) {483// We know that the cipher has not been initialized if the key484// is still null.485throw new IllegalStateException(486"Attempted to update AAD on uninitialized Cipher");487} else if (aadDone) {488// No AAD updates allowed after the PT/CT update method is called489throw new IllegalStateException("Attempted to update AAD on " +490"Cipher after plaintext/ciphertext update");491} else if (mode != MODE_AEAD) {492throw new IllegalStateException(493"Cipher is running in non-AEAD mode");494} else {495try {496aadLen = Math.addExact(aadLen, (src.limit() - src.position()));497authenticator.engineUpdate(src);498} catch (ArithmeticException ae) {499throw new IllegalStateException("AAD overflow", ae);500}501}502}503504/**505* Create a random 12-byte nonce.506*507* @param random a {@code SecureRandom} object. If {@code null} is508* provided a new {@code SecureRandom} object will be instantiated.509*510* @return a 12-byte array containing the random nonce.511*/512private static byte[] createRandomNonce(SecureRandom random) {513byte[] newNonce = new byte[12];514SecureRandom rand = (random != null) ? random : new SecureRandom();515rand.nextBytes(newNonce);516return newNonce;517}518519/**520* Perform additional initialization actions based on the key and operation521* type.522*523* @param opmode the type of operation to do. This value must be either524* {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}525* @param key a 256-bit key suitable for ChaCha20526* @param newNonce the new nonce value for this initialization.527*528* @throws UnsupportedOperationException if the {@code opmode} parameter529* is {@code Cipher.WRAP_MODE} or {@code Cipher.UNWRAP_MODE}530* (currently unsupported).531* @throws InvalidKeyException if the {@code opmode} parameter is not532* {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}, or533* if the key format is not {@code RAW}.534*/535private void init(int opmode, Key key, byte[] newNonce)536throws InvalidKeyException {537// Cipher.init() already checks opmode to be:538// ENCRYPT_MODE/DECRYPT_MODE/WRAP_MODE/UNWRAP_MODE539if ((opmode == Cipher.WRAP_MODE) || (opmode == Cipher.UNWRAP_MODE)) {540throw new UnsupportedOperationException(541"WRAP_MODE and UNWRAP_MODE are not currently supported");542}543544// Make sure that the provided key and nonce are unique before545// assigning them to the object.546byte[] newKeyBytes = getEncodedKey(key);547checkKeyAndNonce(newKeyBytes, newNonce);548if (this.keyBytes != null) {549Arrays.fill(this.keyBytes, (byte)0);550}551this.keyBytes = newKeyBytes;552nonce = newNonce;553554// Now that we have the key and nonce, we can build the initial state555setInitialState();556557if (mode == MODE_NONE) {558engine = new EngineStreamOnly();559} else if (mode == MODE_AEAD) {560if (opmode == Cipher.ENCRYPT_MODE) {561engine = new EngineAEADEnc();562} else if (opmode == Cipher.DECRYPT_MODE) {563engine = new EngineAEADDec();564} else {565throw new InvalidKeyException("Not encrypt or decrypt mode");566}567}568569// We can also get one block's worth of keystream created570finalCounterValue = counter + MAX_UINT32;571generateKeystream();572direction = opmode;573aadDone = false;574this.keyStrOffset = 0;575initialized = true;576}577578/**579* Check the key and nonce bytes to make sure that they do not repeat580* across reinitialization.581*582* @param newKeyBytes the byte encoding for the newly provided key583* @param newNonce the new nonce to be used with this initialization584*585* @throws InvalidKeyException if both the key and nonce match the586* previous initialization.587*588*/589private void checkKeyAndNonce(byte[] newKeyBytes, byte[] newNonce)590throws InvalidKeyException {591// A new initialization must have either a different key or nonce592// so the starting state for each block is not the same as the593// previous initialization.594if (MessageDigest.isEqual(newKeyBytes, keyBytes) &&595MessageDigest.isEqual(newNonce, nonce)) {596throw new InvalidKeyException(597"Matching key and nonce from previous initialization");598}599}600601/**602* Return the encoded key as a byte array603*604* @param key the {@code Key} object used for this {@code Cipher}605*606* @return the key bytes607*608* @throws InvalidKeyException if the key is of the wrong type or length,609* or if the key encoding format is not {@code RAW}.610*/611private static byte[] getEncodedKey(Key key) throws InvalidKeyException {612if ("RAW".equals(key.getFormat()) == false) {613throw new InvalidKeyException("Key encoding format must be RAW");614}615byte[] encodedKey = key.getEncoded();616if (encodedKey == null || encodedKey.length != 32) {617if (encodedKey != null) {618Arrays.fill(encodedKey, (byte)0);619}620throw new InvalidKeyException("Key length must be 256 bits");621}622return encodedKey;623}624625/**626* Update the currently running operation with additional data627*628* @param in the plaintext or ciphertext input bytes (depending on the629* operation type).630* @param inOfs the offset into the input array631* @param inLen the length of the data to use for the update operation.632*633* @return the resulting plaintext or ciphertext bytes (depending on634* the operation type)635*/636@Override637protected byte[] engineUpdate(byte[] in, int inOfs, int inLen) {638byte[] out = new byte[engine.getOutputSize(inLen, false)];639try {640engine.doUpdate(in, inOfs, inLen, out, 0);641} catch (ShortBufferException | KeyException exc) {642throw new RuntimeException(exc);643}644645return out;646}647648/**649* Update the currently running operation with additional data650*651* @param in the plaintext or ciphertext input bytes (depending on the652* operation type).653* @param inOfs the offset into the input array654* @param inLen the length of the data to use for the update operation.655* @param out the byte array that will hold the resulting data. The array656* must be large enough to hold the resulting data.657* @param outOfs the offset for the {@code out} buffer to begin writing658* the resulting data.659*660* @return the length in bytes of the data written into the {@code out}661* buffer.662*663* @throws ShortBufferException if the buffer {@code out} does not have664* enough space to hold the resulting data.665*/666@Override667protected int engineUpdate(byte[] in, int inOfs, int inLen,668byte[] out, int outOfs) throws ShortBufferException {669int bytesUpdated = 0;670try {671bytesUpdated = engine.doUpdate(in, inOfs, inLen, out, outOfs);672} catch (KeyException ke) {673throw new RuntimeException(ke);674}675return bytesUpdated;676}677678/**679* Complete the currently running operation using any final680* data provided by the caller.681*682* @param in the plaintext or ciphertext input bytes (depending on the683* operation type).684* @param inOfs the offset into the input array685* @param inLen the length of the data to use for the update operation.686*687* @return the resulting plaintext or ciphertext bytes (depending on688* the operation type)689*690* @throws AEADBadTagException if, during decryption, the provided tag691* does not match the calculated tag.692*/693@Override694protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen)695throws AEADBadTagException {696byte[] output = new byte[engine.getOutputSize(inLen, true)];697try {698engine.doFinal(in, inOfs, inLen, output, 0);699} catch (ShortBufferException | KeyException exc) {700throw new RuntimeException(exc);701} finally {702// Regardless of what happens, the cipher cannot be used for703// further processing until it has been freshly initialized.704initialized = false;705}706return output;707}708709/**710* Complete the currently running operation using any final711* data provided by the caller.712*713* @param in the plaintext or ciphertext input bytes (depending on the714* operation type).715* @param inOfs the offset into the input array716* @param inLen the length of the data to use for the update operation.717* @param out the byte array that will hold the resulting data. The array718* must be large enough to hold the resulting data.719* @param outOfs the offset for the {@code out} buffer to begin writing720* the resulting data.721*722* @return the length in bytes of the data written into the {@code out}723* buffer.724*725* @throws ShortBufferException if the buffer {@code out} does not have726* enough space to hold the resulting data.727* @throws AEADBadTagException if, during decryption, the provided tag728* does not match the calculated tag.729*/730@Override731protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,732int outOfs) throws ShortBufferException, AEADBadTagException {733734int bytesUpdated = 0;735try {736bytesUpdated = engine.doFinal(in, inOfs, inLen, out, outOfs);737} catch (KeyException ke) {738throw new RuntimeException(ke);739} finally {740// Regardless of what happens, the cipher cannot be used for741// further processing until it has been freshly initialized.742initialized = false;743}744return bytesUpdated;745}746747/**748* Wrap a {@code Key} using this Cipher's current encryption parameters.749*750* @param key the key to wrap. The data that will be encrypted will751* be the provided {@code Key} in its encoded form.752*753* @return a byte array consisting of the wrapped key.754*755* @throws UnsupportedOperationException this will (currently) always756* be thrown, as this method is not currently supported.757*/758@Override759protected byte[] engineWrap(Key key) throws IllegalBlockSizeException,760InvalidKeyException {761throw new UnsupportedOperationException(762"Wrap operations are not supported");763}764765/**766* Unwrap a {@code Key} using this Cipher's current encryption parameters.767*768* @param wrappedKey the key to unwrap.769* @param algorithm the algorithm associated with the wrapped key770* @param type the type of the wrapped key. This is one of771* {@code SECRET_KEY}, {@code PRIVATE_KEY}, or {@code PUBLIC_KEY}.772*773* @return the unwrapped key as a {@code Key} object.774*775* @throws UnsupportedOperationException this will (currently) always776* be thrown, as this method is not currently supported.777*/778@Override779protected Key engineUnwrap(byte[] wrappedKey, String algorithm,780int type) throws InvalidKeyException, NoSuchAlgorithmException {781throw new UnsupportedOperationException(782"Unwrap operations are not supported");783}784785/**786* Get the length of a provided key in bits.787*788* @param key the key to be evaluated789*790* @return the length of the key in bits791*792* @throws InvalidKeyException if the key is invalid or does not793* have an encoded form.794*/795@Override796protected int engineGetKeySize(Key key) throws InvalidKeyException {797byte[] encodedKey = getEncodedKey(key);798Arrays.fill(encodedKey, (byte)0);799return encodedKey.length << 3;800}801802/**803* Set the initial state. This will populate the state array and put the804* key and nonce into their proper locations. The counter field is not805* set here.806*807* @throws IllegalArgumentException if the key or nonce are not in808* their proper lengths (32 bytes for the key, 12 bytes for the809* nonce).810* @throws InvalidKeyException if the key does not support an encoded form.811*/812private void setInitialState() throws InvalidKeyException {813// Apply constants to first 4 words814startState[0] = STATE_CONST_0;815startState[1] = STATE_CONST_1;816startState[2] = STATE_CONST_2;817startState[3] = STATE_CONST_3;818819// Apply the key bytes as 8 32-bit little endian ints (4 through 11)820for (int i = 0; i < 32; i += 4) {821startState[(i / 4) + 4] = (keyBytes[i] & 0x000000FF) |822((keyBytes[i + 1] << 8) & 0x0000FF00) |823((keyBytes[i + 2] << 16) & 0x00FF0000) |824((keyBytes[i + 3] << 24) & 0xFF000000);825}826827startState[12] = 0;828829// The final integers for the state are from the nonce830// interpreted as 3 little endian integers831for (int i = 0; i < 12; i += 4) {832startState[(i / 4) + 13] = (nonce[i] & 0x000000FF) |833((nonce[i + 1] << 8) & 0x0000FF00) |834((nonce[i + 2] << 16) & 0x00FF0000) |835((nonce[i + 3] << 24) & 0xFF000000);836}837}838839/**840* Using the current state and counter create the next set of keystream841* bytes. This method will generate the next 512 bits of keystream and842* return it in the {@code keyStream} parameter. Following the843* block function the counter will be incremented.844*/845private void generateKeystream() {846chaCha20Block(startState, counter, keyStream);847counter++;848}849850/**851* Perform a full 20-round ChaCha20 transform on the initial state.852*853* @param initState the starting state, not including the counter854* value.855* @param counter the counter value to apply856* @param result the array that will hold the result of the ChaCha20857* block function.858*859* @note it is the caller's responsibility to ensure that the workState860* is sized the same as the initState, no checking is performed internally.861*/862private static void chaCha20Block(int[] initState, long counter,863byte[] result) {864// Create an initial state and clone a working copy865int ws00 = STATE_CONST_0;866int ws01 = STATE_CONST_1;867int ws02 = STATE_CONST_2;868int ws03 = STATE_CONST_3;869int ws04 = initState[4];870int ws05 = initState[5];871int ws06 = initState[6];872int ws07 = initState[7];873int ws08 = initState[8];874int ws09 = initState[9];875int ws10 = initState[10];876int ws11 = initState[11];877int ws12 = (int)counter;878int ws13 = initState[13];879int ws14 = initState[14];880int ws15 = initState[15];881882// Peform 10 iterations of the 8 quarter round set883for (int round = 0; round < 10; round++) {884ws00 += ws04;885ws12 = Integer.rotateLeft(ws12 ^ ws00, 16);886887ws08 += ws12;888ws04 = Integer.rotateLeft(ws04 ^ ws08, 12);889890ws00 += ws04;891ws12 = Integer.rotateLeft(ws12 ^ ws00, 8);892893ws08 += ws12;894ws04 = Integer.rotateLeft(ws04 ^ ws08, 7);895896ws01 += ws05;897ws13 = Integer.rotateLeft(ws13 ^ ws01, 16);898899ws09 += ws13;900ws05 = Integer.rotateLeft(ws05 ^ ws09, 12);901902ws01 += ws05;903ws13 = Integer.rotateLeft(ws13 ^ ws01, 8);904905ws09 += ws13;906ws05 = Integer.rotateLeft(ws05 ^ ws09, 7);907908ws02 += ws06;909ws14 = Integer.rotateLeft(ws14 ^ ws02, 16);910911ws10 += ws14;912ws06 = Integer.rotateLeft(ws06 ^ ws10, 12);913914ws02 += ws06;915ws14 = Integer.rotateLeft(ws14 ^ ws02, 8);916917ws10 += ws14;918ws06 = Integer.rotateLeft(ws06 ^ ws10, 7);919920ws03 += ws07;921ws15 = Integer.rotateLeft(ws15 ^ ws03, 16);922923ws11 += ws15;924ws07 = Integer.rotateLeft(ws07 ^ ws11, 12);925926ws03 += ws07;927ws15 = Integer.rotateLeft(ws15 ^ ws03, 8);928929ws11 += ws15;930ws07 = Integer.rotateLeft(ws07 ^ ws11, 7);931932ws00 += ws05;933ws15 = Integer.rotateLeft(ws15 ^ ws00, 16);934935ws10 += ws15;936ws05 = Integer.rotateLeft(ws05 ^ ws10, 12);937938ws00 += ws05;939ws15 = Integer.rotateLeft(ws15 ^ ws00, 8);940941ws10 += ws15;942ws05 = Integer.rotateLeft(ws05 ^ ws10, 7);943944ws01 += ws06;945ws12 = Integer.rotateLeft(ws12 ^ ws01, 16);946947ws11 += ws12;948ws06 = Integer.rotateLeft(ws06 ^ ws11, 12);949950ws01 += ws06;951ws12 = Integer.rotateLeft(ws12 ^ ws01, 8);952953ws11 += ws12;954ws06 = Integer.rotateLeft(ws06 ^ ws11, 7);955956ws02 += ws07;957ws13 = Integer.rotateLeft(ws13 ^ ws02, 16);958959ws08 += ws13;960ws07 = Integer.rotateLeft(ws07 ^ ws08, 12);961962ws02 += ws07;963ws13 = Integer.rotateLeft(ws13 ^ ws02, 8);964965ws08 += ws13;966ws07 = Integer.rotateLeft(ws07 ^ ws08, 7);967968ws03 += ws04;969ws14 = Integer.rotateLeft(ws14 ^ ws03, 16);970971ws09 += ws14;972ws04 = Integer.rotateLeft(ws04 ^ ws09, 12);973974ws03 += ws04;975ws14 = Integer.rotateLeft(ws14 ^ ws03, 8);976977ws09 += ws14;978ws04 = Integer.rotateLeft(ws04 ^ ws09, 7);979}980981// Add the end working state back into the original state982asIntLittleEndian.set(result, 0, ws00 + STATE_CONST_0);983asIntLittleEndian.set(result, 4, ws01 + STATE_CONST_1);984asIntLittleEndian.set(result, 8, ws02 + STATE_CONST_2);985asIntLittleEndian.set(result, 12, ws03 + STATE_CONST_3);986asIntLittleEndian.set(result, 16, ws04 + initState[4]);987asIntLittleEndian.set(result, 20, ws05 + initState[5]);988asIntLittleEndian.set(result, 24, ws06 + initState[6]);989asIntLittleEndian.set(result, 28, ws07 + initState[7]);990asIntLittleEndian.set(result, 32, ws08 + initState[8]);991asIntLittleEndian.set(result, 36, ws09 + initState[9]);992asIntLittleEndian.set(result, 40, ws10 + initState[10]);993asIntLittleEndian.set(result, 44, ws11 + initState[11]);994// Add the counter back into workState[12]995asIntLittleEndian.set(result, 48, ws12 + (int)counter);996asIntLittleEndian.set(result, 52, ws13 + initState[13]);997asIntLittleEndian.set(result, 56, ws14 + initState[14]);998asIntLittleEndian.set(result, 60, ws15 + initState[15]);999}10001001/**1002* Perform the ChaCha20 transform.1003*1004* @param in the array of bytes for the input1005* @param inOff the offset into the input array to start the transform1006* @param inLen the length of the data to perform the transform on.1007* @param out the output array. It must be large enough to hold the1008* resulting data1009* @param outOff the offset into the output array to place the resulting1010* data.1011*/1012private void chaCha20Transform(byte[] in, int inOff, int inLen,1013byte[] out, int outOff) throws KeyException {1014int remainingData = inLen;10151016while (remainingData > 0) {1017int ksRemain = keyStream.length - keyStrOffset;1018if (ksRemain <= 0) {1019if (counter <= finalCounterValue) {1020generateKeystream();1021keyStrOffset = 0;1022ksRemain = keyStream.length;1023} else {1024throw new KeyException("Counter exhausted. " +1025"Reinitialize with new key and/or nonce");1026}1027}10281029// XOR each byte in the keystream against the input1030int xformLen = Math.min(remainingData, ksRemain);1031xor(keyStream, keyStrOffset, in, inOff, out, outOff, xformLen);1032outOff += xformLen;1033inOff += xformLen;1034keyStrOffset += xformLen;1035remainingData -= xformLen;1036}1037}10381039private static void xor(byte[] in1, int off1, byte[] in2, int off2,1040byte[] out, int outOff, int len) {1041while (len >= 8) {1042long v1 = (long) asLongView.get(in1, off1);1043long v2 = (long) asLongView.get(in2, off2);1044asLongView.set(out, outOff, v1 ^ v2);1045off1 += 8;1046off2 += 8;1047outOff += 8;1048len -= 8;1049}1050while (len > 0) {1051out[outOff] = (byte) (in1[off1] ^ in2[off2]);1052off1++;1053off2++;1054outOff++;1055len--;1056}1057}10581059/**1060* Perform initialization steps for the authenticator1061*1062* @throws InvalidKeyException if the key is unusable for some reason1063* (invalid length, etc.)1064*/1065private void initAuthenticator() throws InvalidKeyException {1066authenticator = new Poly1305();10671068// Derive the Poly1305 key from the starting state1069byte[] serializedKey = new byte[KEYSTREAM_SIZE];1070chaCha20Block(startState, 0, serializedKey);10711072authenticator.engineInit(new SecretKeySpec(serializedKey, 0, 32,1073authAlgName), null);1074aadLen = 0;1075dataLen = 0;1076}10771078/**1079* Update the authenticator state with data. This routine can be used1080* to add data to the authenticator, whether AAD or application data.1081*1082* @param data the data to stir into the authenticator.1083* @param offset the offset into the data.1084* @param length the length of data to add to the authenticator.1085*1086* @return the number of bytes processed by this method.1087*/1088private int authUpdate(byte[] data, int offset, int length) {1089Objects.checkFromIndexSize(offset, length, data.length);1090authenticator.engineUpdate(data, offset, length);1091return length;1092}10931094/**1095* Finalize the data and return the tag.1096*1097* @param data an array containing any remaining data to process.1098* @param dataOff the offset into the data.1099* @param length the length of the data to process.1100* @param out the array to write the resulting tag into1101* @param outOff the offset to begin writing the data.1102*1103* @throws ShortBufferException if there is insufficient room to1104* write the tag.1105*/1106private void authFinalizeData(byte[] data, int dataOff, int length,1107byte[] out, int outOff) throws ShortBufferException {1108// Update with the final chunk of ciphertext, then pad to a1109// multiple of 16.1110if (data != null) {1111dataLen += authUpdate(data, dataOff, length);1112}1113authPad16(dataLen);11141115// Also write the AAD and ciphertext data lengths as little-endian1116// 64-bit values.1117authWriteLengths(aadLen, dataLen, lenBuf);1118authenticator.engineUpdate(lenBuf, 0, lenBuf.length);1119byte[] tag = authenticator.engineDoFinal();1120Objects.checkFromIndexSize(outOff, tag.length, out.length);1121System.arraycopy(tag, 0, out, outOff, tag.length);1122aadLen = 0;1123dataLen = 0;1124}11251126/**1127* Based on a given length of data, make the authenticator process1128* zero bytes that will pad the length out to a multiple of 16.1129*1130* @param dataLen the starting length to be padded.1131*/1132private void authPad16(long dataLen) {1133// Pad out the AAD or data to a multiple of 16 bytes1134authenticator.engineUpdate(padBuf, 0,1135(TAG_LENGTH - ((int)dataLen & 15)) & 15);1136}11371138/**1139* Write the two 64-bit little-endian length fields into an array1140* for processing by the poly1305 authenticator.1141*1142* @param aLen the length of the AAD.1143* @param dLen the length of the application data.1144* @param buf the buffer to write the two lengths into.1145*1146* @note it is the caller's responsibility to provide an array large1147* enough to hold the two longs.1148*/1149private void authWriteLengths(long aLen, long dLen, byte[] buf) {1150asLongLittleEndian.set(buf, 0, aLen);1151asLongLittleEndian.set(buf, Long.BYTES, dLen);1152}11531154/**1155* Interface for the underlying processing engines for ChaCha201156*/1157interface ChaChaEngine {1158/**1159* Size an output buffer based on the input and where applicable1160* the current state of the engine in a multipart operation.1161*1162* @param inLength the input length.1163* @param isFinal true if this is invoked from a doFinal call.1164*1165* @return the recommended size for the output buffer.1166*/1167int getOutputSize(int inLength, boolean isFinal);11681169/**1170* Perform a multi-part update for ChaCha20.1171*1172* @param in the input data.1173* @param inOff the offset into the input.1174* @param inLen the length of the data to process.1175* @param out the output buffer.1176* @param outOff the offset at which to write the output data.1177*1178* @return the number of output bytes written.1179*1180* @throws ShortBufferException if the output buffer does not1181* provide enough space.1182* @throws KeyException if the counter value has been exhausted.1183*/1184int doUpdate(byte[] in, int inOff, int inLen, byte[] out, int outOff)1185throws ShortBufferException, KeyException;11861187/**1188* Finalize a multi-part or single-part ChaCha20 operation.1189*1190* @param in the input data.1191* @param inOff the offset into the input.1192* @param inLen the length of the data to process.1193* @param out the output buffer.1194* @param outOff the offset at which to write the output data.1195*1196* @return the number of output bytes written.1197*1198* @throws ShortBufferException if the output buffer does not1199* provide enough space.1200* @throws AEADBadTagException if in decryption mode the provided1201* tag and calculated tag do not match.1202* @throws KeyException if the counter value has been exhausted.1203*/1204int doFinal(byte[] in, int inOff, int inLen, byte[] out, int outOff)1205throws ShortBufferException, AEADBadTagException, KeyException;1206}12071208private final class EngineStreamOnly implements ChaChaEngine {12091210private EngineStreamOnly () { }12111212@Override1213public int getOutputSize(int inLength, boolean isFinal) {1214// The isFinal parameter is not relevant in this kind of engine1215return inLength;1216}12171218@Override1219public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,1220int outOff) throws ShortBufferException, KeyException {1221if (initialized) {1222try {1223if (out != null) {1224Objects.checkFromIndexSize(outOff, inLen, out.length);1225} else {1226throw new ShortBufferException(1227"Output buffer too small");1228}1229} catch (IndexOutOfBoundsException iobe) {1230throw new ShortBufferException("Output buffer too small");1231}1232if (in != null) {1233Objects.checkFromIndexSize(inOff, inLen, in.length);1234chaCha20Transform(in, inOff, inLen, out, outOff);1235}1236return inLen;1237} else {1238throw new IllegalStateException(1239"Must use either a different key or iv.");1240}1241}12421243@Override1244public int doFinal(byte[] in, int inOff, int inLen, byte[] out,1245int outOff) throws ShortBufferException, KeyException {1246return doUpdate(in, inOff, inLen, out, outOff);1247}1248}12491250private final class EngineAEADEnc implements ChaChaEngine {12511252@Override1253public int getOutputSize(int inLength, boolean isFinal) {1254return (isFinal ? Math.addExact(inLength, TAG_LENGTH) : inLength);1255}12561257private EngineAEADEnc() throws InvalidKeyException {1258initAuthenticator();1259counter = 1;1260}12611262@Override1263public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,1264int outOff) throws ShortBufferException, KeyException {1265if (initialized) {1266// If this is the first update since AAD updates, signal that1267// we're done processing AAD info and pad the AAD to a multiple1268// of 16 bytes.1269if (!aadDone) {1270authPad16(aadLen);1271aadDone = true;1272}1273try {1274if (out != null) {1275Objects.checkFromIndexSize(outOff, inLen, out.length);1276} else {1277throw new ShortBufferException(1278"Output buffer too small");1279}1280} catch (IndexOutOfBoundsException iobe) {1281throw new ShortBufferException("Output buffer too small");1282}1283if (in != null) {1284Objects.checkFromIndexSize(inOff, inLen, in.length);1285chaCha20Transform(in, inOff, inLen, out, outOff);1286dataLen += authUpdate(out, outOff, inLen);1287}12881289return inLen;1290} else {1291throw new IllegalStateException(1292"Must use either a different key or iv.");1293}1294}12951296@Override1297public int doFinal(byte[] in, int inOff, int inLen, byte[] out,1298int outOff) throws ShortBufferException, KeyException {1299// Make sure we have enough room for the remaining data (if any)1300// and the tag.1301if ((inLen + TAG_LENGTH) > (out.length - outOff)) {1302throw new ShortBufferException("Output buffer too small");1303}13041305doUpdate(in, inOff, inLen, out, outOff);1306authFinalizeData(null, 0, 0, out, outOff + inLen);1307aadDone = false;1308return inLen + TAG_LENGTH;1309}1310}13111312private final class EngineAEADDec implements ChaChaEngine {13131314private final ByteArrayOutputStream cipherBuf;1315private final byte[] tag;13161317@Override1318public int getOutputSize(int inLen, boolean isFinal) {1319// If we are performing a decrypt-update we should always return1320// zero length since we cannot return any data until the tag has1321// been consumed and verified. CipherSpi.engineGetOutputSize will1322// always set isFinal to true to get the required output buffer1323// size.1324return (isFinal ?1325Integer.max(Math.addExact((inLen - TAG_LENGTH),1326cipherBuf.size()), 0) : 0);1327}13281329private EngineAEADDec() throws InvalidKeyException {1330initAuthenticator();1331counter = 1;1332cipherBuf = new ByteArrayOutputStream(CIPHERBUF_BASE);1333tag = new byte[TAG_LENGTH];1334}13351336@Override1337public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,1338int outOff) {1339if (initialized) {1340// If this is the first update since AAD updates, signal that1341// we're done processing AAD info and pad the AAD to a multiple1342// of 16 bytes.1343if (!aadDone) {1344authPad16(aadLen);1345aadDone = true;1346}13471348if (in != null) {1349Objects.checkFromIndexSize(inOff, inLen, in.length);1350cipherBuf.write(in, inOff, inLen);1351}1352} else {1353throw new IllegalStateException(1354"Must use either a different key or iv.");1355}13561357return 0;1358}13591360@Override1361public int doFinal(byte[] in, int inOff, int inLen, byte[] out,1362int outOff) throws ShortBufferException, AEADBadTagException,1363KeyException {13641365byte[] ctPlusTag;1366int ctPlusTagLen;1367if (cipherBuf.size() == 0 && inOff == 0) {1368// No previous data has been seen before doFinal, so we do1369// not need to hold any ciphertext in a buffer. We can1370// process it directly from the "in" parameter.1371doUpdate(null, inOff, inLen, out, outOff);1372ctPlusTag = in;1373ctPlusTagLen = inLen;1374} else {1375doUpdate(in, inOff, inLen, out, outOff);1376ctPlusTag = cipherBuf.toByteArray();1377ctPlusTagLen = ctPlusTag.length;1378}1379cipherBuf.reset();13801381// There must at least be a tag length's worth of ciphertext1382// data in the buffered input.1383if (ctPlusTagLen < TAG_LENGTH) {1384throw new AEADBadTagException("Input too short - need tag");1385}1386int ctLen = ctPlusTagLen - TAG_LENGTH;13871388// Make sure we will have enough room for the output buffer1389try {1390Objects.checkFromIndexSize(outOff, ctLen, out.length);1391} catch (IndexOutOfBoundsException ioobe) {1392throw new ShortBufferException("Output buffer too small");1393}13941395// Calculate and compare the tag. Only do the decryption1396// if and only if the tag matches.1397authFinalizeData(ctPlusTag, 0, ctLen, tag, 0);1398long tagCompare = ((long)asLongView.get(ctPlusTag, ctLen) ^1399(long)asLongView.get(tag, 0)) |1400((long)asLongView.get(ctPlusTag, ctLen + Long.BYTES) ^1401(long)asLongView.get(tag, Long.BYTES));1402if (tagCompare != 0) {1403throw new AEADBadTagException("Tag mismatch");1404}1405chaCha20Transform(ctPlusTag, 0, ctLen, out, outOff);1406aadDone = false;14071408return ctLen;1409}1410}14111412public static final class ChaCha20Only extends ChaCha20Cipher {1413public ChaCha20Only() {1414mode = MODE_NONE;1415}1416}14171418public static final class ChaCha20Poly1305 extends ChaCha20Cipher {1419public ChaCha20Poly1305() {1420mode = MODE_AEAD;1421authAlgName = "Poly1305";1422}1423}1424}142514261427