Path: blob/master/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java
41161 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 {537if ((opmode == Cipher.WRAP_MODE) || (opmode == Cipher.UNWRAP_MODE)) {538throw new UnsupportedOperationException(539"WRAP_MODE and UNWRAP_MODE are not currently supported");540} else if ((opmode != Cipher.ENCRYPT_MODE) &&541(opmode != Cipher.DECRYPT_MODE)) {542throw new InvalidKeyException("Unknown opmode: " + opmode);543}544545// Make sure that the provided key and nonce are unique before546// assigning them to the object.547byte[] newKeyBytes = getEncodedKey(key);548checkKeyAndNonce(newKeyBytes, newNonce);549if (this.keyBytes != null) {550Arrays.fill(this.keyBytes, (byte)0);551}552this.keyBytes = newKeyBytes;553nonce = newNonce;554555// Now that we have the key and nonce, we can build the initial state556setInitialState();557558if (mode == MODE_NONE) {559engine = new EngineStreamOnly();560} else if (mode == MODE_AEAD) {561if (opmode == Cipher.ENCRYPT_MODE) {562engine = new EngineAEADEnc();563} else if (opmode == Cipher.DECRYPT_MODE) {564engine = new EngineAEADDec();565} else {566throw new InvalidKeyException("Not encrypt or decrypt mode");567}568}569570// We can also get one block's worth of keystream created571finalCounterValue = counter + MAX_UINT32;572generateKeystream();573direction = opmode;574aadDone = false;575this.keyStrOffset = 0;576initialized = true;577}578579/**580* Check the key and nonce bytes to make sure that they do not repeat581* across reinitialization.582*583* @param newKeyBytes the byte encoding for the newly provided key584* @param newNonce the new nonce to be used with this initialization585*586* @throws InvalidKeyException if both the key and nonce match the587* previous initialization.588*589*/590private void checkKeyAndNonce(byte[] newKeyBytes, byte[] newNonce)591throws InvalidKeyException {592// A new initialization must have either a different key or nonce593// so the starting state for each block is not the same as the594// previous initialization.595if (MessageDigest.isEqual(newKeyBytes, keyBytes) &&596MessageDigest.isEqual(newNonce, nonce)) {597throw new InvalidKeyException(598"Matching key and nonce from previous initialization");599}600}601602/**603* Return the encoded key as a byte array604*605* @param key the {@code Key} object used for this {@code Cipher}606*607* @return the key bytes608*609* @throws InvalidKeyException if the key is of the wrong type or length,610* or if the key encoding format is not {@code RAW}.611*/612private static byte[] getEncodedKey(Key key) throws InvalidKeyException {613if ("RAW".equals(key.getFormat()) == false) {614throw new InvalidKeyException("Key encoding format must be RAW");615}616byte[] encodedKey = key.getEncoded();617if (encodedKey == null || encodedKey.length != 32) {618if (encodedKey != null) {619Arrays.fill(encodedKey, (byte)0);620}621throw new InvalidKeyException("Key length must be 256 bits");622}623return encodedKey;624}625626/**627* Update the currently running operation with additional data628*629* @param in the plaintext or ciphertext input bytes (depending on the630* operation type).631* @param inOfs the offset into the input array632* @param inLen the length of the data to use for the update operation.633*634* @return the resulting plaintext or ciphertext bytes (depending on635* the operation type)636*/637@Override638protected byte[] engineUpdate(byte[] in, int inOfs, int inLen) {639byte[] out = new byte[engine.getOutputSize(inLen, false)];640try {641engine.doUpdate(in, inOfs, inLen, out, 0);642} catch (ShortBufferException | KeyException exc) {643throw new RuntimeException(exc);644}645646return out;647}648649/**650* Update the currently running operation with additional data651*652* @param in the plaintext or ciphertext input bytes (depending on the653* operation type).654* @param inOfs the offset into the input array655* @param inLen the length of the data to use for the update operation.656* @param out the byte array that will hold the resulting data. The array657* must be large enough to hold the resulting data.658* @param outOfs the offset for the {@code out} buffer to begin writing659* the resulting data.660*661* @return the length in bytes of the data written into the {@code out}662* buffer.663*664* @throws ShortBufferException if the buffer {@code out} does not have665* enough space to hold the resulting data.666*/667@Override668protected int engineUpdate(byte[] in, int inOfs, int inLen,669byte[] out, int outOfs) throws ShortBufferException {670int bytesUpdated = 0;671try {672bytesUpdated = engine.doUpdate(in, inOfs, inLen, out, outOfs);673} catch (KeyException ke) {674throw new RuntimeException(ke);675}676return bytesUpdated;677}678679/**680* Complete the currently running operation using any final681* data provided by the caller.682*683* @param in the plaintext or ciphertext input bytes (depending on the684* operation type).685* @param inOfs the offset into the input array686* @param inLen the length of the data to use for the update operation.687*688* @return the resulting plaintext or ciphertext bytes (depending on689* the operation type)690*691* @throws AEADBadTagException if, during decryption, the provided tag692* does not match the calculated tag.693*/694@Override695protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen)696throws AEADBadTagException {697byte[] output = new byte[engine.getOutputSize(inLen, true)];698try {699engine.doFinal(in, inOfs, inLen, output, 0);700} catch (ShortBufferException | KeyException exc) {701throw new RuntimeException(exc);702} finally {703// Regardless of what happens, the cipher cannot be used for704// further processing until it has been freshly initialized.705initialized = false;706}707return output;708}709710/**711* Complete the currently running operation using any final712* data provided by the caller.713*714* @param in the plaintext or ciphertext input bytes (depending on the715* operation type).716* @param inOfs the offset into the input array717* @param inLen the length of the data to use for the update operation.718* @param out the byte array that will hold the resulting data. The array719* must be large enough to hold the resulting data.720* @param outOfs the offset for the {@code out} buffer to begin writing721* the resulting data.722*723* @return the length in bytes of the data written into the {@code out}724* buffer.725*726* @throws ShortBufferException if the buffer {@code out} does not have727* enough space to hold the resulting data.728* @throws AEADBadTagException if, during decryption, the provided tag729* does not match the calculated tag.730*/731@Override732protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,733int outOfs) throws ShortBufferException, AEADBadTagException {734735int bytesUpdated = 0;736try {737bytesUpdated = engine.doFinal(in, inOfs, inLen, out, outOfs);738} catch (KeyException ke) {739throw new RuntimeException(ke);740} finally {741// Regardless of what happens, the cipher cannot be used for742// further processing until it has been freshly initialized.743initialized = false;744}745return bytesUpdated;746}747748/**749* Wrap a {@code Key} using this Cipher's current encryption parameters.750*751* @param key the key to wrap. The data that will be encrypted will752* be the provided {@code Key} in its encoded form.753*754* @return a byte array consisting of the wrapped key.755*756* @throws UnsupportedOperationException this will (currently) always757* be thrown, as this method is not currently supported.758*/759@Override760protected byte[] engineWrap(Key key) throws IllegalBlockSizeException,761InvalidKeyException {762throw new UnsupportedOperationException(763"Wrap operations are not supported");764}765766/**767* Unwrap a {@code Key} using this Cipher's current encryption parameters.768*769* @param wrappedKey the key to unwrap.770* @param algorithm the algorithm associated with the wrapped key771* @param type the type of the wrapped key. This is one of772* {@code SECRET_KEY}, {@code PRIVATE_KEY}, or {@code PUBLIC_KEY}.773*774* @return the unwrapped key as a {@code Key} object.775*776* @throws UnsupportedOperationException this will (currently) always777* be thrown, as this method is not currently supported.778*/779@Override780protected Key engineUnwrap(byte[] wrappedKey, String algorithm,781int type) throws InvalidKeyException, NoSuchAlgorithmException {782throw new UnsupportedOperationException(783"Unwrap operations are not supported");784}785786/**787* Get the length of a provided key in bits.788*789* @param key the key to be evaluated790*791* @return the length of the key in bits792*793* @throws InvalidKeyException if the key is invalid or does not794* have an encoded form.795*/796@Override797protected int engineGetKeySize(Key key) throws InvalidKeyException {798byte[] encodedKey = getEncodedKey(key);799Arrays.fill(encodedKey, (byte)0);800return encodedKey.length << 3;801}802803/**804* Set the initial state. This will populate the state array and put the805* key and nonce into their proper locations. The counter field is not806* set here.807*808* @throws IllegalArgumentException if the key or nonce are not in809* their proper lengths (32 bytes for the key, 12 bytes for the810* nonce).811* @throws InvalidKeyException if the key does not support an encoded form.812*/813private void setInitialState() throws InvalidKeyException {814// Apply constants to first 4 words815startState[0] = STATE_CONST_0;816startState[1] = STATE_CONST_1;817startState[2] = STATE_CONST_2;818startState[3] = STATE_CONST_3;819820// Apply the key bytes as 8 32-bit little endian ints (4 through 11)821for (int i = 0; i < 32; i += 4) {822startState[(i / 4) + 4] = (keyBytes[i] & 0x000000FF) |823((keyBytes[i + 1] << 8) & 0x0000FF00) |824((keyBytes[i + 2] << 16) & 0x00FF0000) |825((keyBytes[i + 3] << 24) & 0xFF000000);826}827828startState[12] = 0;829830// The final integers for the state are from the nonce831// interpreted as 3 little endian integers832for (int i = 0; i < 12; i += 4) {833startState[(i / 4) + 13] = (nonce[i] & 0x000000FF) |834((nonce[i + 1] << 8) & 0x0000FF00) |835((nonce[i + 2] << 16) & 0x00FF0000) |836((nonce[i + 3] << 24) & 0xFF000000);837}838}839840/**841* Using the current state and counter create the next set of keystream842* bytes. This method will generate the next 512 bits of keystream and843* return it in the {@code keyStream} parameter. Following the844* block function the counter will be incremented.845*/846private void generateKeystream() {847chaCha20Block(startState, counter, keyStream);848counter++;849}850851/**852* Perform a full 20-round ChaCha20 transform on the initial state.853*854* @param initState the starting state, not including the counter855* value.856* @param counter the counter value to apply857* @param result the array that will hold the result of the ChaCha20858* block function.859*860* @note it is the caller's responsibility to ensure that the workState861* is sized the same as the initState, no checking is performed internally.862*/863private static void chaCha20Block(int[] initState, long counter,864byte[] result) {865// Create an initial state and clone a working copy866int ws00 = STATE_CONST_0;867int ws01 = STATE_CONST_1;868int ws02 = STATE_CONST_2;869int ws03 = STATE_CONST_3;870int ws04 = initState[4];871int ws05 = initState[5];872int ws06 = initState[6];873int ws07 = initState[7];874int ws08 = initState[8];875int ws09 = initState[9];876int ws10 = initState[10];877int ws11 = initState[11];878int ws12 = (int)counter;879int ws13 = initState[13];880int ws14 = initState[14];881int ws15 = initState[15];882883// Peform 10 iterations of the 8 quarter round set884for (int round = 0; round < 10; round++) {885ws00 += ws04;886ws12 = Integer.rotateLeft(ws12 ^ ws00, 16);887888ws08 += ws12;889ws04 = Integer.rotateLeft(ws04 ^ ws08, 12);890891ws00 += ws04;892ws12 = Integer.rotateLeft(ws12 ^ ws00, 8);893894ws08 += ws12;895ws04 = Integer.rotateLeft(ws04 ^ ws08, 7);896897ws01 += ws05;898ws13 = Integer.rotateLeft(ws13 ^ ws01, 16);899900ws09 += ws13;901ws05 = Integer.rotateLeft(ws05 ^ ws09, 12);902903ws01 += ws05;904ws13 = Integer.rotateLeft(ws13 ^ ws01, 8);905906ws09 += ws13;907ws05 = Integer.rotateLeft(ws05 ^ ws09, 7);908909ws02 += ws06;910ws14 = Integer.rotateLeft(ws14 ^ ws02, 16);911912ws10 += ws14;913ws06 = Integer.rotateLeft(ws06 ^ ws10, 12);914915ws02 += ws06;916ws14 = Integer.rotateLeft(ws14 ^ ws02, 8);917918ws10 += ws14;919ws06 = Integer.rotateLeft(ws06 ^ ws10, 7);920921ws03 += ws07;922ws15 = Integer.rotateLeft(ws15 ^ ws03, 16);923924ws11 += ws15;925ws07 = Integer.rotateLeft(ws07 ^ ws11, 12);926927ws03 += ws07;928ws15 = Integer.rotateLeft(ws15 ^ ws03, 8);929930ws11 += ws15;931ws07 = Integer.rotateLeft(ws07 ^ ws11, 7);932933ws00 += ws05;934ws15 = Integer.rotateLeft(ws15 ^ ws00, 16);935936ws10 += ws15;937ws05 = Integer.rotateLeft(ws05 ^ ws10, 12);938939ws00 += ws05;940ws15 = Integer.rotateLeft(ws15 ^ ws00, 8);941942ws10 += ws15;943ws05 = Integer.rotateLeft(ws05 ^ ws10, 7);944945ws01 += ws06;946ws12 = Integer.rotateLeft(ws12 ^ ws01, 16);947948ws11 += ws12;949ws06 = Integer.rotateLeft(ws06 ^ ws11, 12);950951ws01 += ws06;952ws12 = Integer.rotateLeft(ws12 ^ ws01, 8);953954ws11 += ws12;955ws06 = Integer.rotateLeft(ws06 ^ ws11, 7);956957ws02 += ws07;958ws13 = Integer.rotateLeft(ws13 ^ ws02, 16);959960ws08 += ws13;961ws07 = Integer.rotateLeft(ws07 ^ ws08, 12);962963ws02 += ws07;964ws13 = Integer.rotateLeft(ws13 ^ ws02, 8);965966ws08 += ws13;967ws07 = Integer.rotateLeft(ws07 ^ ws08, 7);968969ws03 += ws04;970ws14 = Integer.rotateLeft(ws14 ^ ws03, 16);971972ws09 += ws14;973ws04 = Integer.rotateLeft(ws04 ^ ws09, 12);974975ws03 += ws04;976ws14 = Integer.rotateLeft(ws14 ^ ws03, 8);977978ws09 += ws14;979ws04 = Integer.rotateLeft(ws04 ^ ws09, 7);980}981982// Add the end working state back into the original state983asIntLittleEndian.set(result, 0, ws00 + STATE_CONST_0);984asIntLittleEndian.set(result, 4, ws01 + STATE_CONST_1);985asIntLittleEndian.set(result, 8, ws02 + STATE_CONST_2);986asIntLittleEndian.set(result, 12, ws03 + STATE_CONST_3);987asIntLittleEndian.set(result, 16, ws04 + initState[4]);988asIntLittleEndian.set(result, 20, ws05 + initState[5]);989asIntLittleEndian.set(result, 24, ws06 + initState[6]);990asIntLittleEndian.set(result, 28, ws07 + initState[7]);991asIntLittleEndian.set(result, 32, ws08 + initState[8]);992asIntLittleEndian.set(result, 36, ws09 + initState[9]);993asIntLittleEndian.set(result, 40, ws10 + initState[10]);994asIntLittleEndian.set(result, 44, ws11 + initState[11]);995// Add the counter back into workState[12]996asIntLittleEndian.set(result, 48, ws12 + (int)counter);997asIntLittleEndian.set(result, 52, ws13 + initState[13]);998asIntLittleEndian.set(result, 56, ws14 + initState[14]);999asIntLittleEndian.set(result, 60, ws15 + initState[15]);1000}10011002/**1003* Perform the ChaCha20 transform.1004*1005* @param in the array of bytes for the input1006* @param inOff the offset into the input array to start the transform1007* @param inLen the length of the data to perform the transform on.1008* @param out the output array. It must be large enough to hold the1009* resulting data1010* @param outOff the offset into the output array to place the resulting1011* data.1012*/1013private void chaCha20Transform(byte[] in, int inOff, int inLen,1014byte[] out, int outOff) throws KeyException {1015int remainingData = inLen;10161017while (remainingData > 0) {1018int ksRemain = keyStream.length - keyStrOffset;1019if (ksRemain <= 0) {1020if (counter <= finalCounterValue) {1021generateKeystream();1022keyStrOffset = 0;1023ksRemain = keyStream.length;1024} else {1025throw new KeyException("Counter exhausted. " +1026"Reinitialize with new key and/or nonce");1027}1028}10291030// XOR each byte in the keystream against the input1031int xformLen = Math.min(remainingData, ksRemain);1032xor(keyStream, keyStrOffset, in, inOff, out, outOff, xformLen);1033outOff += xformLen;1034inOff += xformLen;1035keyStrOffset += xformLen;1036remainingData -= xformLen;1037}1038}10391040private static void xor(byte[] in1, int off1, byte[] in2, int off2,1041byte[] out, int outOff, int len) {1042while (len >= 8) {1043long v1 = (long) asLongView.get(in1, off1);1044long v2 = (long) asLongView.get(in2, off2);1045asLongView.set(out, outOff, v1 ^ v2);1046off1 += 8;1047off2 += 8;1048outOff += 8;1049len -= 8;1050}1051while (len > 0) {1052out[outOff] = (byte) (in1[off1] ^ in2[off2]);1053off1++;1054off2++;1055outOff++;1056len--;1057}1058}10591060/**1061* Perform initialization steps for the authenticator1062*1063* @throws InvalidKeyException if the key is unusable for some reason1064* (invalid length, etc.)1065*/1066private void initAuthenticator() throws InvalidKeyException {1067authenticator = new Poly1305();10681069// Derive the Poly1305 key from the starting state1070byte[] serializedKey = new byte[KEYSTREAM_SIZE];1071chaCha20Block(startState, 0, serializedKey);10721073authenticator.engineInit(new SecretKeySpec(serializedKey, 0, 32,1074authAlgName), null);1075aadLen = 0;1076dataLen = 0;1077}10781079/**1080* Update the authenticator state with data. This routine can be used1081* to add data to the authenticator, whether AAD or application data.1082*1083* @param data the data to stir into the authenticator.1084* @param offset the offset into the data.1085* @param length the length of data to add to the authenticator.1086*1087* @return the number of bytes processed by this method.1088*/1089private int authUpdate(byte[] data, int offset, int length) {1090Objects.checkFromIndexSize(offset, length, data.length);1091authenticator.engineUpdate(data, offset, length);1092return length;1093}10941095/**1096* Finalize the data and return the tag.1097*1098* @param data an array containing any remaining data to process.1099* @param dataOff the offset into the data.1100* @param length the length of the data to process.1101* @param out the array to write the resulting tag into1102* @param outOff the offset to begin writing the data.1103*1104* @throws ShortBufferException if there is insufficient room to1105* write the tag.1106*/1107private void authFinalizeData(byte[] data, int dataOff, int length,1108byte[] out, int outOff) throws ShortBufferException {1109// Update with the final chunk of ciphertext, then pad to a1110// multiple of 16.1111if (data != null) {1112dataLen += authUpdate(data, dataOff, length);1113}1114authPad16(dataLen);11151116// Also write the AAD and ciphertext data lengths as little-endian1117// 64-bit values.1118authWriteLengths(aadLen, dataLen, lenBuf);1119authenticator.engineUpdate(lenBuf, 0, lenBuf.length);1120byte[] tag = authenticator.engineDoFinal();1121Objects.checkFromIndexSize(outOff, tag.length, out.length);1122System.arraycopy(tag, 0, out, outOff, tag.length);1123aadLen = 0;1124dataLen = 0;1125}11261127/**1128* Based on a given length of data, make the authenticator process1129* zero bytes that will pad the length out to a multiple of 16.1130*1131* @param dataLen the starting length to be padded.1132*/1133private void authPad16(long dataLen) {1134// Pad out the AAD or data to a multiple of 16 bytes1135authenticator.engineUpdate(padBuf, 0,1136(TAG_LENGTH - ((int)dataLen & 15)) & 15);1137}11381139/**1140* Write the two 64-bit little-endian length fields into an array1141* for processing by the poly1305 authenticator.1142*1143* @param aLen the length of the AAD.1144* @param dLen the length of the application data.1145* @param buf the buffer to write the two lengths into.1146*1147* @note it is the caller's responsibility to provide an array large1148* enough to hold the two longs.1149*/1150private void authWriteLengths(long aLen, long dLen, byte[] buf) {1151asLongLittleEndian.set(buf, 0, aLen);1152asLongLittleEndian.set(buf, Long.BYTES, dLen);1153}11541155/**1156* Interface for the underlying processing engines for ChaCha201157*/1158interface ChaChaEngine {1159/**1160* Size an output buffer based on the input and where applicable1161* the current state of the engine in a multipart operation.1162*1163* @param inLength the input length.1164* @param isFinal true if this is invoked from a doFinal call.1165*1166* @return the recommended size for the output buffer.1167*/1168int getOutputSize(int inLength, boolean isFinal);11691170/**1171* Perform a multi-part update for ChaCha20.1172*1173* @param in the input data.1174* @param inOff the offset into the input.1175* @param inLen the length of the data to process.1176* @param out the output buffer.1177* @param outOff the offset at which to write the output data.1178*1179* @return the number of output bytes written.1180*1181* @throws ShortBufferException if the output buffer does not1182* provide enough space.1183* @throws KeyException if the counter value has been exhausted.1184*/1185int doUpdate(byte[] in, int inOff, int inLen, byte[] out, int outOff)1186throws ShortBufferException, KeyException;11871188/**1189* Finalize a multi-part or single-part ChaCha20 operation.1190*1191* @param in the input data.1192* @param inOff the offset into the input.1193* @param inLen the length of the data to process.1194* @param out the output buffer.1195* @param outOff the offset at which to write the output data.1196*1197* @return the number of output bytes written.1198*1199* @throws ShortBufferException if the output buffer does not1200* provide enough space.1201* @throws AEADBadTagException if in decryption mode the provided1202* tag and calculated tag do not match.1203* @throws KeyException if the counter value has been exhausted.1204*/1205int doFinal(byte[] in, int inOff, int inLen, byte[] out, int outOff)1206throws ShortBufferException, AEADBadTagException, KeyException;1207}12081209private final class EngineStreamOnly implements ChaChaEngine {12101211private EngineStreamOnly () { }12121213@Override1214public int getOutputSize(int inLength, boolean isFinal) {1215// The isFinal parameter is not relevant in this kind of engine1216return inLength;1217}12181219@Override1220public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,1221int outOff) throws ShortBufferException, KeyException {1222if (initialized) {1223try {1224if (out != null) {1225Objects.checkFromIndexSize(outOff, inLen, out.length);1226} else {1227throw new ShortBufferException(1228"Output buffer too small");1229}1230} catch (IndexOutOfBoundsException iobe) {1231throw new ShortBufferException("Output buffer too small");1232}1233if (in != null) {1234Objects.checkFromIndexSize(inOff, inLen, in.length);1235chaCha20Transform(in, inOff, inLen, out, outOff);1236}1237return inLen;1238} else {1239throw new IllegalStateException(1240"Must use either a different key or iv.");1241}1242}12431244@Override1245public int doFinal(byte[] in, int inOff, int inLen, byte[] out,1246int outOff) throws ShortBufferException, KeyException {1247return doUpdate(in, inOff, inLen, out, outOff);1248}1249}12501251private final class EngineAEADEnc implements ChaChaEngine {12521253@Override1254public int getOutputSize(int inLength, boolean isFinal) {1255return (isFinal ? Math.addExact(inLength, TAG_LENGTH) : inLength);1256}12571258private EngineAEADEnc() throws InvalidKeyException {1259initAuthenticator();1260counter = 1;1261}12621263@Override1264public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,1265int outOff) throws ShortBufferException, KeyException {1266if (initialized) {1267// If this is the first update since AAD updates, signal that1268// we're done processing AAD info and pad the AAD to a multiple1269// of 16 bytes.1270if (!aadDone) {1271authPad16(aadLen);1272aadDone = true;1273}1274try {1275if (out != null) {1276Objects.checkFromIndexSize(outOff, inLen, out.length);1277} else {1278throw new ShortBufferException(1279"Output buffer too small");1280}1281} catch (IndexOutOfBoundsException iobe) {1282throw new ShortBufferException("Output buffer too small");1283}1284if (in != null) {1285Objects.checkFromIndexSize(inOff, inLen, in.length);1286chaCha20Transform(in, inOff, inLen, out, outOff);1287dataLen += authUpdate(out, outOff, inLen);1288}12891290return inLen;1291} else {1292throw new IllegalStateException(1293"Must use either a different key or iv.");1294}1295}12961297@Override1298public int doFinal(byte[] in, int inOff, int inLen, byte[] out,1299int outOff) throws ShortBufferException, KeyException {1300// Make sure we have enough room for the remaining data (if any)1301// and the tag.1302if ((inLen + TAG_LENGTH) > (out.length - outOff)) {1303throw new ShortBufferException("Output buffer too small");1304}13051306doUpdate(in, inOff, inLen, out, outOff);1307authFinalizeData(null, 0, 0, out, outOff + inLen);1308aadDone = false;1309return inLen + TAG_LENGTH;1310}1311}13121313private final class EngineAEADDec implements ChaChaEngine {13141315private final ByteArrayOutputStream cipherBuf;1316private final byte[] tag;13171318@Override1319public int getOutputSize(int inLen, boolean isFinal) {1320// If we are performing a decrypt-update we should always return1321// zero length since we cannot return any data until the tag has1322// been consumed and verified. CipherSpi.engineGetOutputSize will1323// always set isFinal to true to get the required output buffer1324// size.1325return (isFinal ?1326Integer.max(Math.addExact((inLen - TAG_LENGTH),1327cipherBuf.size()), 0) : 0);1328}13291330private EngineAEADDec() throws InvalidKeyException {1331initAuthenticator();1332counter = 1;1333cipherBuf = new ByteArrayOutputStream(CIPHERBUF_BASE);1334tag = new byte[TAG_LENGTH];1335}13361337@Override1338public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,1339int outOff) {1340if (initialized) {1341// If this is the first update since AAD updates, signal that1342// we're done processing AAD info and pad the AAD to a multiple1343// of 16 bytes.1344if (!aadDone) {1345authPad16(aadLen);1346aadDone = true;1347}13481349if (in != null) {1350Objects.checkFromIndexSize(inOff, inLen, in.length);1351cipherBuf.write(in, inOff, inLen);1352}1353} else {1354throw new IllegalStateException(1355"Must use either a different key or iv.");1356}13571358return 0;1359}13601361@Override1362public int doFinal(byte[] in, int inOff, int inLen, byte[] out,1363int outOff) throws ShortBufferException, AEADBadTagException,1364KeyException {13651366byte[] ctPlusTag;1367int ctPlusTagLen;1368if (cipherBuf.size() == 0 && inOff == 0) {1369// No previous data has been seen before doFinal, so we do1370// not need to hold any ciphertext in a buffer. We can1371// process it directly from the "in" parameter.1372doUpdate(null, inOff, inLen, out, outOff);1373ctPlusTag = in;1374ctPlusTagLen = inLen;1375} else {1376doUpdate(in, inOff, inLen, out, outOff);1377ctPlusTag = cipherBuf.toByteArray();1378ctPlusTagLen = ctPlusTag.length;1379}1380cipherBuf.reset();13811382// There must at least be a tag length's worth of ciphertext1383// data in the buffered input.1384if (ctPlusTagLen < TAG_LENGTH) {1385throw new AEADBadTagException("Input too short - need tag");1386}1387int ctLen = ctPlusTagLen - TAG_LENGTH;13881389// Make sure we will have enough room for the output buffer1390try {1391Objects.checkFromIndexSize(outOff, ctLen, out.length);1392} catch (IndexOutOfBoundsException ioobe) {1393throw new ShortBufferException("Output buffer too small");1394}13951396// Calculate and compare the tag. Only do the decryption1397// if and only if the tag matches.1398authFinalizeData(ctPlusTag, 0, ctLen, tag, 0);1399long tagCompare = ((long)asLongView.get(ctPlusTag, ctLen) ^1400(long)asLongView.get(tag, 0)) |1401((long)asLongView.get(ctPlusTag, ctLen + Long.BYTES) ^1402(long)asLongView.get(tag, Long.BYTES));1403if (tagCompare != 0) {1404throw new AEADBadTagException("Tag mismatch");1405}1406chaCha20Transform(ctPlusTag, 0, ctLen, out, outOff);1407aadDone = false;14081409return ctLen;1410}1411}14121413public static final class ChaCha20Only extends ChaCha20Cipher {1414public ChaCha20Only() {1415mode = MODE_NONE;1416}1417}14181419public static final class ChaCha20Poly1305 extends ChaCha20Cipher {1420public ChaCha20Poly1305() {1421mode = MODE_AEAD;1422authAlgName = "Poly1305";1423}1424}1425}142614271428