mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
8153029: ChaCha20 Cipher Implementation
Add the ChaCha20 and ChaCha20-Poly1305 Cipher implementations Reviewed-by: mullan
This commit is contained in:
parent
89251ae9a3
commit
fcb805f9a6
10 changed files with 3556 additions and 3 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sun.crypto.provider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.AlgorithmParametersSpi;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.security.spec.InvalidParameterSpecException;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import sun.security.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements the parameter set used with the ChaCha20-Poly1305
|
||||||
|
* algorithm. The parameter definition comes from
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc8103"><i>RFC 8103</i></a>
|
||||||
|
* and is defined according to the following ASN.1:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* id-alg-AEADChaCha20Poly1305 OBJECT IDENTIFIER ::=
|
||||||
|
{ iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
|
||||||
|
pkcs9(9) smime(16) alg(3) 18 }
|
||||||
|
|
||||||
|
* AEADChaCha20Poly1305Nonce ::= OCTET STRING (SIZE(12))
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The AlgorithmParameters may be instantiated either by its name
|
||||||
|
* ("ChaCha20-Poly1305") or via its OID (1.2.840.113549.1.9.16.3.18)
|
||||||
|
*/
|
||||||
|
public final class ChaCha20Poly1305Parameters extends AlgorithmParametersSpi {
|
||||||
|
|
||||||
|
private static final String DEFAULT_FMT = "ASN.1";
|
||||||
|
private byte[] nonce;
|
||||||
|
|
||||||
|
public ChaCha20Poly1305Parameters() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the ChaCha20Poly1305Parameters using an IvParameterSpec.
|
||||||
|
*
|
||||||
|
* @param paramSpec the {@code IvParameterSpec} used to configure
|
||||||
|
* this object.
|
||||||
|
*
|
||||||
|
* @throws InvalidParameterSpecException if an object of a type other
|
||||||
|
* than {@code IvParameterSpec} is used.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void engineInit(AlgorithmParameterSpec paramSpec)
|
||||||
|
throws InvalidParameterSpecException {
|
||||||
|
|
||||||
|
if (!(paramSpec instanceof IvParameterSpec)) {
|
||||||
|
throw new InvalidParameterSpecException
|
||||||
|
("Inappropriate parameter specification");
|
||||||
|
}
|
||||||
|
IvParameterSpec ivps = (IvParameterSpec)paramSpec;
|
||||||
|
|
||||||
|
// Obtain the nonce
|
||||||
|
nonce = ivps.getIV();
|
||||||
|
if (nonce.length != 12) {
|
||||||
|
throw new InvalidParameterSpecException("ChaCha20-Poly1305 nonce" +
|
||||||
|
" must be 12 bytes in length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the ChaCha20Poly1305Parameters from a DER encoded
|
||||||
|
* parameter block.
|
||||||
|
|
||||||
|
* @param encoded the DER encoding of the nonce as an OCTET STRING.
|
||||||
|
*
|
||||||
|
* @throws IOException if the encoded nonce is not 12 bytes long or a DER
|
||||||
|
* decoding error occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void engineInit(byte[] encoded) throws IOException {
|
||||||
|
DerValue val = new DerValue(encoded);
|
||||||
|
|
||||||
|
// Get the nonce value
|
||||||
|
nonce = val.getOctetString();
|
||||||
|
if (nonce.length != 12) {
|
||||||
|
throw new IOException(
|
||||||
|
"ChaCha20-Poly1305 nonce must be 12 bytes in length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the ChaCha20Poly1305Parameters from a DER encoded
|
||||||
|
* parameter block.
|
||||||
|
*
|
||||||
|
* @param encoded the DER encoding of the nonce and initial block counter.
|
||||||
|
* @param decodingMethod the decoding method. The only currently accepted
|
||||||
|
* value is "ASN.1"
|
||||||
|
*
|
||||||
|
* @throws IOException if the encoded nonce is not 12 bytes long, a DER
|
||||||
|
* decoding error occurs, or an unsupported decoding method is
|
||||||
|
* provided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void engineInit(byte[] encoded, String decodingMethod)
|
||||||
|
throws IOException {
|
||||||
|
if (decodingMethod == null ||
|
||||||
|
decodingMethod.equalsIgnoreCase(DEFAULT_FMT)) {
|
||||||
|
engineInit(encoded);
|
||||||
|
} else {
|
||||||
|
throw new IOException("Unsupported parameter format: " +
|
||||||
|
decodingMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an IvParameterSpec with the same parameters as those
|
||||||
|
* held in this object.
|
||||||
|
*
|
||||||
|
* @param paramSpec the class name of the spec. In this case it should
|
||||||
|
* be {@code IvParameterSpec.class}.
|
||||||
|
*
|
||||||
|
* @return a {@code IvParameterSpec} object containing the nonce
|
||||||
|
* value held in this object.
|
||||||
|
*
|
||||||
|
* @throws InvalidParameterSpecException if a class other than
|
||||||
|
* {@code IvParameterSpec.class} was specified in the paramSpec
|
||||||
|
* parameter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected <T extends AlgorithmParameterSpec>
|
||||||
|
T engineGetParameterSpec(Class<T> paramSpec)
|
||||||
|
throws InvalidParameterSpecException {
|
||||||
|
|
||||||
|
if (IvParameterSpec.class.isAssignableFrom(paramSpec)) {
|
||||||
|
return paramSpec.cast(new IvParameterSpec(nonce));
|
||||||
|
} else {
|
||||||
|
throw new InvalidParameterSpecException
|
||||||
|
("Inappropriate parameter specification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the encoded parameters in ASN.1 form.
|
||||||
|
*
|
||||||
|
* @return a byte array containing the DER-encoding for the
|
||||||
|
* ChaCha20-Poly1305 parameters. This will be the nonce
|
||||||
|
* encoded as a DER OCTET STRING.
|
||||||
|
*
|
||||||
|
* @throws IOException if any DER encoding error occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected byte[] engineGetEncoded() throws IOException {
|
||||||
|
DerOutputStream out = new DerOutputStream();
|
||||||
|
out.write(DerValue.tag_OctetString, nonce);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the encoded parameters in ASN.1 form.
|
||||||
|
*
|
||||||
|
* @param encodingMethod the encoding method to be used. This parameter
|
||||||
|
* must be "ASN.1" as it is the only currently supported encoding
|
||||||
|
* format. If the parameter is {@code null} then the default
|
||||||
|
* encoding format will be used.
|
||||||
|
*
|
||||||
|
* @return a byte array containing the DER-encoding for the
|
||||||
|
* ChaCha20-Poly1305 parameters.
|
||||||
|
*
|
||||||
|
* @throws IOException if any DER encoding error occurs or an unsupported
|
||||||
|
* encoding method is provided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected byte[] engineGetEncoded(String encodingMethod)
|
||||||
|
throws IOException {
|
||||||
|
if (encodingMethod == null ||
|
||||||
|
encodingMethod.equalsIgnoreCase(DEFAULT_FMT)) {
|
||||||
|
return engineGetEncoded();
|
||||||
|
} else {
|
||||||
|
throw new IOException("Unsupported encoding format: " +
|
||||||
|
encodingMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string describing the parameters.
|
||||||
|
*
|
||||||
|
* @return a string representation of the ChaCha20 parameters.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String engineToString() {
|
||||||
|
String LINE_SEP = System.lineSeparator();
|
||||||
|
HexDumpEncoder encoder = new HexDumpEncoder();
|
||||||
|
StringBuilder sb = new StringBuilder(LINE_SEP + "nonce:" +
|
||||||
|
LINE_SEP + "[" + encoder.encodeBuffer(nonce) + "]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -111,16 +111,20 @@ final class KeyGeneratorCore {
|
||||||
protected HmacSHA2KG(String algoName, int len) {
|
protected HmacSHA2KG(String algoName, int len) {
|
||||||
core = new KeyGeneratorCore(algoName, len);
|
core = new KeyGeneratorCore(algoName, len);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(SecureRandom random) {
|
protected void engineInit(SecureRandom random) {
|
||||||
core.implInit(random);
|
core.implInit(random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(AlgorithmParameterSpec params,
|
protected void engineInit(AlgorithmParameterSpec params,
|
||||||
SecureRandom random) throws InvalidAlgorithmParameterException {
|
SecureRandom random) throws InvalidAlgorithmParameterException {
|
||||||
core.implInit(params, random);
|
core.implInit(params, random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(int keySize, SecureRandom random) {
|
protected void engineInit(int keySize, SecureRandom random) {
|
||||||
core.implInit(keySize, random);
|
core.implInit(keySize, random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected SecretKey engineGenerateKey() {
|
protected SecretKey engineGenerateKey() {
|
||||||
return core.implGenerateKey();
|
return core.implGenerateKey();
|
||||||
}
|
}
|
||||||
|
@ -153,13 +157,16 @@ final class KeyGeneratorCore {
|
||||||
public RC2KeyGenerator() {
|
public RC2KeyGenerator() {
|
||||||
core = new KeyGeneratorCore("RC2", 128);
|
core = new KeyGeneratorCore("RC2", 128);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(SecureRandom random) {
|
protected void engineInit(SecureRandom random) {
|
||||||
core.implInit(random);
|
core.implInit(random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(AlgorithmParameterSpec params,
|
protected void engineInit(AlgorithmParameterSpec params,
|
||||||
SecureRandom random) throws InvalidAlgorithmParameterException {
|
SecureRandom random) throws InvalidAlgorithmParameterException {
|
||||||
core.implInit(params, random);
|
core.implInit(params, random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(int keySize, SecureRandom random) {
|
protected void engineInit(int keySize, SecureRandom random) {
|
||||||
if ((keySize < 40) || (keySize > 1024)) {
|
if ((keySize < 40) || (keySize > 1024)) {
|
||||||
throw new InvalidParameterException("Key length for RC2"
|
throw new InvalidParameterException("Key length for RC2"
|
||||||
|
@ -167,6 +174,7 @@ final class KeyGeneratorCore {
|
||||||
}
|
}
|
||||||
core.implInit(keySize, random);
|
core.implInit(keySize, random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected SecretKey engineGenerateKey() {
|
protected SecretKey engineGenerateKey() {
|
||||||
return core.implGenerateKey();
|
return core.implGenerateKey();
|
||||||
}
|
}
|
||||||
|
@ -178,13 +186,16 @@ final class KeyGeneratorCore {
|
||||||
public ARCFOURKeyGenerator() {
|
public ARCFOURKeyGenerator() {
|
||||||
core = new KeyGeneratorCore("ARCFOUR", 128);
|
core = new KeyGeneratorCore("ARCFOUR", 128);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(SecureRandom random) {
|
protected void engineInit(SecureRandom random) {
|
||||||
core.implInit(random);
|
core.implInit(random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(AlgorithmParameterSpec params,
|
protected void engineInit(AlgorithmParameterSpec params,
|
||||||
SecureRandom random) throws InvalidAlgorithmParameterException {
|
SecureRandom random) throws InvalidAlgorithmParameterException {
|
||||||
core.implInit(params, random);
|
core.implInit(params, random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected void engineInit(int keySize, SecureRandom random) {
|
protected void engineInit(int keySize, SecureRandom random) {
|
||||||
if ((keySize < 40) || (keySize > 1024)) {
|
if ((keySize < 40) || (keySize > 1024)) {
|
||||||
throw new InvalidParameterException("Key length for ARCFOUR"
|
throw new InvalidParameterException("Key length for ARCFOUR"
|
||||||
|
@ -192,9 +203,38 @@ final class KeyGeneratorCore {
|
||||||
}
|
}
|
||||||
core.implInit(keySize, random);
|
core.implInit(keySize, random);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
protected SecretKey engineGenerateKey() {
|
protected SecretKey engineGenerateKey() {
|
||||||
return core.implGenerateKey();
|
return core.implGenerateKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nested static class for the ChaCha20 key generator
|
||||||
|
public static final class ChaCha20KeyGenerator extends KeyGeneratorSpi {
|
||||||
|
private final KeyGeneratorCore core;
|
||||||
|
public ChaCha20KeyGenerator() {
|
||||||
|
core = new KeyGeneratorCore("ChaCha20", 256);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void engineInit(SecureRandom random) {
|
||||||
|
core.implInit(random);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void engineInit(AlgorithmParameterSpec params,
|
||||||
|
SecureRandom random) throws InvalidAlgorithmParameterException {
|
||||||
|
core.implInit(params, random);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int keySize, SecureRandom random) {
|
||||||
|
if (keySize != 256) {
|
||||||
|
throw new InvalidParameterException(
|
||||||
|
"Key length for ChaCha20 must be 256 bits");
|
||||||
|
}
|
||||||
|
core.implInit(keySize, random);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected SecretKey engineGenerateKey() {
|
||||||
|
return core.implGenerateKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sun.crypto.provider;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import sun.security.util.math.*;
|
||||||
|
import sun.security.util.math.intpoly.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the Poly1305 function defined in RFC 7539.
|
||||||
|
*
|
||||||
|
* This function is used in the implementation of ChaCha20/Poly1305
|
||||||
|
* AEAD mode.
|
||||||
|
*/
|
||||||
|
final class Poly1305 {
|
||||||
|
|
||||||
|
private static final int KEY_LENGTH = 32;
|
||||||
|
private static final int RS_LENGTH = KEY_LENGTH / 2;
|
||||||
|
private static final int BLOCK_LENGTH = 16;
|
||||||
|
private static final int TAG_LENGTH = 16;
|
||||||
|
|
||||||
|
private static final IntegerFieldModuloP ipl1305 =
|
||||||
|
new IntegerPolynomial1305();
|
||||||
|
|
||||||
|
private byte[] keyBytes;
|
||||||
|
private final byte[] block = new byte[BLOCK_LENGTH];
|
||||||
|
private int blockOffset;
|
||||||
|
|
||||||
|
private IntegerModuloP r;
|
||||||
|
private IntegerModuloP s;
|
||||||
|
private MutableIntegerModuloP a;
|
||||||
|
private final MutableIntegerModuloP n = ipl1305.get1().mutable();
|
||||||
|
|
||||||
|
Poly1305() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Poly1305 object
|
||||||
|
*
|
||||||
|
* @param newKey the {@code Key} which will be used for the authentication.
|
||||||
|
* @param params this parameter is unused.
|
||||||
|
*
|
||||||
|
* @throws InvalidKeyException if {@code newKey} is {@code null} or is
|
||||||
|
* not 32 bytes in length.
|
||||||
|
*/
|
||||||
|
void engineInit(Key newKey, AlgorithmParameterSpec params)
|
||||||
|
throws InvalidKeyException {
|
||||||
|
Objects.requireNonNull(newKey, "Null key provided during init");
|
||||||
|
keyBytes = newKey.getEncoded();
|
||||||
|
if (keyBytes == null) {
|
||||||
|
throw new InvalidKeyException("Key does not support encoding");
|
||||||
|
} else if (keyBytes.length != KEY_LENGTH) {
|
||||||
|
throw new InvalidKeyException("Incorrect length for key: " +
|
||||||
|
keyBytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
engineReset();
|
||||||
|
setRSVals();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of the MAC (authentication tag).
|
||||||
|
*
|
||||||
|
* @return the length of the auth tag, which is always 16 bytes.
|
||||||
|
*/
|
||||||
|
int engineGetMacLength() {
|
||||||
|
return TAG_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the Poly1305 object, discarding any current operation but
|
||||||
|
* maintaining the same key.
|
||||||
|
*/
|
||||||
|
void engineReset() {
|
||||||
|
// Clear the block and reset the offset
|
||||||
|
Arrays.fill(block, (byte)0);
|
||||||
|
blockOffset = 0;
|
||||||
|
// Discard any previous accumulator and start at zero
|
||||||
|
a = ipl1305.get0().mutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the MAC with bytes from a {@code ByteBuffer}
|
||||||
|
*
|
||||||
|
* @param buf the {@code ByteBuffer} containing the data to be consumed.
|
||||||
|
* Upon return the buffer's position will be equal to its limit.
|
||||||
|
*/
|
||||||
|
void engineUpdate(ByteBuffer buf) {
|
||||||
|
int remaining = buf.remaining();
|
||||||
|
while (remaining > 0) {
|
||||||
|
int bytesToWrite = Integer.min(remaining,
|
||||||
|
BLOCK_LENGTH - blockOffset);
|
||||||
|
|
||||||
|
if (bytesToWrite >= BLOCK_LENGTH) {
|
||||||
|
// If bytes to write == BLOCK_LENGTH, then we have no
|
||||||
|
// left-over data from previous updates and we can create
|
||||||
|
// the IntegerModuloP directly from the input buffer.
|
||||||
|
processBlock(buf, bytesToWrite);
|
||||||
|
} else {
|
||||||
|
// We have some left-over data from previous updates, so
|
||||||
|
// copy that into the holding block until we get a full block.
|
||||||
|
buf.get(block, blockOffset, bytesToWrite);
|
||||||
|
blockOffset += bytesToWrite;
|
||||||
|
|
||||||
|
if (blockOffset >= BLOCK_LENGTH) {
|
||||||
|
processBlock(block, 0, BLOCK_LENGTH);
|
||||||
|
blockOffset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining -= bytesToWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the MAC with bytes from an array.
|
||||||
|
*
|
||||||
|
* @param input the input bytes.
|
||||||
|
* @param offset the starting index from which to update the MAC.
|
||||||
|
* @param len the number of bytes to process.
|
||||||
|
*/
|
||||||
|
void engineUpdate(byte[] input, int offset, int len) {
|
||||||
|
Objects.checkFromIndexSize(offset, len, input.length);
|
||||||
|
if (blockOffset > 0) {
|
||||||
|
// We have some left-over data from previous updates
|
||||||
|
int blockSpaceLeft = BLOCK_LENGTH - blockOffset;
|
||||||
|
if (len < blockSpaceLeft) {
|
||||||
|
System.arraycopy(input, offset, block, blockOffset, len);
|
||||||
|
blockOffset += len;
|
||||||
|
return; // block wasn't filled
|
||||||
|
} else {
|
||||||
|
System.arraycopy(input, offset, block, blockOffset,
|
||||||
|
blockSpaceLeft);
|
||||||
|
offset += blockSpaceLeft;
|
||||||
|
len -= blockSpaceLeft;
|
||||||
|
processBlock(block, 0, BLOCK_LENGTH);
|
||||||
|
blockOffset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (len >= BLOCK_LENGTH) {
|
||||||
|
processBlock(input, offset, BLOCK_LENGTH);
|
||||||
|
offset += BLOCK_LENGTH;
|
||||||
|
len -= BLOCK_LENGTH;
|
||||||
|
}
|
||||||
|
if (len > 0) { // and len < BLOCK_LENGTH
|
||||||
|
System.arraycopy(input, offset, block, 0, len);
|
||||||
|
blockOffset = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the MAC with a single byte of input
|
||||||
|
*
|
||||||
|
* @param input the byte to update the MAC with.
|
||||||
|
*/
|
||||||
|
void engineUpdate(byte input) {
|
||||||
|
assert (blockOffset < BLOCK_LENGTH);
|
||||||
|
// we can't hold fully filled unprocessed block
|
||||||
|
block[blockOffset++] = input;
|
||||||
|
|
||||||
|
if (blockOffset == BLOCK_LENGTH) {
|
||||||
|
processBlock(block, 0, BLOCK_LENGTH);
|
||||||
|
blockOffset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish the authentication operation and reset the MAC for a new
|
||||||
|
* authentication operation.
|
||||||
|
*
|
||||||
|
* @return the authentication tag as a byte array.
|
||||||
|
*/
|
||||||
|
byte[] engineDoFinal() {
|
||||||
|
byte[] tag = new byte[BLOCK_LENGTH];
|
||||||
|
|
||||||
|
// Finish up: process any remaining data < BLOCK_SIZE, then
|
||||||
|
// create the tag from the resulting little-endian integer.
|
||||||
|
if (blockOffset > 0) {
|
||||||
|
processBlock(block, 0, blockOffset);
|
||||||
|
blockOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add in the s-half of the key to the accumulator
|
||||||
|
a.addModPowerTwo(s, tag);
|
||||||
|
|
||||||
|
// Reset for the next auth
|
||||||
|
engineReset();
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a single block of data. This should only be called
|
||||||
|
* when the block array is complete. That may not necessarily
|
||||||
|
* be a full 16 bytes if the last block has less than 16 bytes.
|
||||||
|
*/
|
||||||
|
private void processBlock(ByteBuffer buf, int len) {
|
||||||
|
n.setValue(buf, len, (byte)0x01);
|
||||||
|
a.setSum(n); // a += (n | 0x01)
|
||||||
|
a.setProduct(r); // a = (a * r) % p
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBlock(byte[] block, int offset, int length) {
|
||||||
|
Objects.checkFromIndexSize(offset, length, block.length);
|
||||||
|
n.setValue(block, offset, length, (byte)0x01);
|
||||||
|
a.setSum(n); // a += (n | 0x01)
|
||||||
|
a.setProduct(r); // a = (a * r) % p
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partition the authentication key into the R and S components, clamp
|
||||||
|
* the R value, and instantiate IntegerModuloP objects to R and S's
|
||||||
|
* numeric values.
|
||||||
|
*/
|
||||||
|
private void setRSVals() {
|
||||||
|
// Clamp the bytes in the "r" half of the key.
|
||||||
|
keyBytes[3] &= 15;
|
||||||
|
keyBytes[7] &= 15;
|
||||||
|
keyBytes[11] &= 15;
|
||||||
|
keyBytes[15] &= 15;
|
||||||
|
keyBytes[4] &= 252;
|
||||||
|
keyBytes[8] &= 252;
|
||||||
|
keyBytes[12] &= 252;
|
||||||
|
|
||||||
|
// Create IntegerModuloP elements from the r and s values
|
||||||
|
r = ipl1305.getElement(keyBytes, 0, RS_LENGTH, (byte)0);
|
||||||
|
s = ipl1305.getElement(keyBytes, RS_LENGTH, RS_LENGTH, (byte)0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ import static sun.security.util.SecurityConstants.PROVIDER_VER;
|
||||||
*
|
*
|
||||||
* - ARCFOUR (RC4 compatible)
|
* - ARCFOUR (RC4 compatible)
|
||||||
*
|
*
|
||||||
|
* - ChaCha20 (Stream cipher only and in AEAD mode with Poly1305)
|
||||||
|
*
|
||||||
* - Cipher modes ECB, CBC, CFB, OFB, PCBC, CTR, and CTS for all block ciphers
|
* - Cipher modes ECB, CBC, CFB, OFB, PCBC, CTR, and CTS for all block ciphers
|
||||||
* and mode GCM for AES cipher
|
* and mode GCM for AES cipher
|
||||||
*
|
*
|
||||||
|
@ -77,7 +79,7 @@ public final class SunJCE extends Provider {
|
||||||
|
|
||||||
private static final String info = "SunJCE Provider " +
|
private static final String info = "SunJCE Provider " +
|
||||||
"(implements RSA, DES, Triple DES, AES, Blowfish, ARCFOUR, RC2, PBE, "
|
"(implements RSA, DES, Triple DES, AES, Blowfish, ARCFOUR, RC2, PBE, "
|
||||||
+ "Diffie-Hellman, HMAC)";
|
+ "Diffie-Hellman, HMAC, ChaCha20)";
|
||||||
|
|
||||||
private static final String OID_PKCS12_RC4_128 = "1.2.840.113549.1.12.1.1";
|
private static final String OID_PKCS12_RC4_128 = "1.2.840.113549.1.12.1.1";
|
||||||
private static final String OID_PKCS12_RC4_40 = "1.2.840.113549.1.12.1.2";
|
private static final String OID_PKCS12_RC4_40 = "1.2.840.113549.1.12.1.2";
|
||||||
|
@ -336,6 +338,15 @@ public final class SunJCE extends Provider {
|
||||||
put("Cipher.ARCFOUR SupportedPaddings", "NOPADDING");
|
put("Cipher.ARCFOUR SupportedPaddings", "NOPADDING");
|
||||||
put("Cipher.ARCFOUR SupportedKeyFormats", "RAW");
|
put("Cipher.ARCFOUR SupportedKeyFormats", "RAW");
|
||||||
|
|
||||||
|
put("Cipher.ChaCha20",
|
||||||
|
"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Only");
|
||||||
|
put("Cipher.ChaCha20 SupportedKeyFormats", "RAW");
|
||||||
|
put("Cipher.ChaCha20-Poly1305",
|
||||||
|
"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305");
|
||||||
|
put("Cipher.ChaCha20-Poly1305 SupportedKeyFormats", "RAW");
|
||||||
|
put("Alg.Alias.Cipher.1.2.840.113549.1.9.16.3.18", "ChaCha20-Poly1305");
|
||||||
|
put("Alg.Alias.Cipher.OID.1.2.840.113549.1.9.16.3.18", "ChaCha20-Poly1305");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Key(pair) Generator engines
|
* Key(pair) Generator engines
|
||||||
*/
|
*/
|
||||||
|
@ -361,6 +372,10 @@ public final class SunJCE extends Provider {
|
||||||
"ARCFOURKeyGenerator");
|
"ARCFOURKeyGenerator");
|
||||||
put("Alg.Alias.KeyGenerator.RC4", "ARCFOUR");
|
put("Alg.Alias.KeyGenerator.RC4", "ARCFOUR");
|
||||||
|
|
||||||
|
put("KeyGenerator.ChaCha20",
|
||||||
|
"com.sun.crypto.provider.KeyGeneratorCore$" +
|
||||||
|
"ChaCha20KeyGenerator");
|
||||||
|
|
||||||
put("KeyGenerator.HmacMD5",
|
put("KeyGenerator.HmacMD5",
|
||||||
"com.sun.crypto.provider.HmacMD5KeyGenerator");
|
"com.sun.crypto.provider.HmacMD5KeyGenerator");
|
||||||
|
|
||||||
|
@ -541,6 +556,9 @@ public final class SunJCE extends Provider {
|
||||||
put("AlgorithmParameters.OAEP",
|
put("AlgorithmParameters.OAEP",
|
||||||
"com.sun.crypto.provider.OAEPParameters");
|
"com.sun.crypto.provider.OAEPParameters");
|
||||||
|
|
||||||
|
put("AlgorithmParameters.ChaCha20-Poly1305",
|
||||||
|
"com.sun.crypto.provider.ChaCha20Poly1305Parameters");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Key factories
|
* Key factories
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -111,7 +111,7 @@ import sun.security.jca.*;
|
||||||
* encryption with a given key. When IVs are repeated for GCM
|
* encryption with a given key. When IVs are repeated for GCM
|
||||||
* encryption, such usages are subject to forgery attacks. Thus, after
|
* encryption, such usages are subject to forgery attacks. Thus, after
|
||||||
* each encryption operation using GCM mode, callers should re-initialize
|
* each encryption operation using GCM mode, callers should re-initialize
|
||||||
* the cipher objects with GCM parameters which has a different IV value.
|
* the cipher objects with GCM parameters which have a different IV value.
|
||||||
* <pre>
|
* <pre>
|
||||||
* GCMParameterSpec s = ...;
|
* GCMParameterSpec s = ...;
|
||||||
* cipher.init(..., s);
|
* cipher.init(..., s);
|
||||||
|
@ -131,6 +131,13 @@ import sun.security.jca.*;
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
|
* The ChaCha20 and ChaCha20-Poly1305 algorithms have a similar requirement
|
||||||
|
* for unique nonces with a given key. After each encryption or decryption
|
||||||
|
* operation, callers should re-initialize their ChaCha20 or ChaCha20-Poly1305
|
||||||
|
* ciphers with parameters that specify a different nonce value. Please
|
||||||
|
* see <a href="https://tools.ietf.org/html/rfc7539">RFC 7539</a> for more
|
||||||
|
* information on the ChaCha20 and ChaCha20-Poly1305 algorithms.
|
||||||
|
* <p>
|
||||||
* Every implementation of the Java platform is required to support
|
* Every implementation of the Java platform is required to support
|
||||||
* the following standard {@code Cipher} transformations with the keysizes
|
* the following standard {@code Cipher} transformations with the keysizes
|
||||||
* in parentheses:
|
* in parentheses:
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package javax.crypto.spec;
|
||||||
|
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class specifies the parameters used with the
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc7539"><i>ChaCha20</i></a>
|
||||||
|
* algorithm.
|
||||||
|
*
|
||||||
|
* <p> The parameters consist of a 12-byte nonce and an initial
|
||||||
|
* counter value expressed as a 32-bit integer.
|
||||||
|
*
|
||||||
|
* <p> This class can be used to initialize a {@code Cipher} object that
|
||||||
|
* implements the <i>ChaCha20</i> algorithm.
|
||||||
|
*
|
||||||
|
* @since 11
|
||||||
|
*/
|
||||||
|
public final class ChaCha20ParameterSpec implements AlgorithmParameterSpec {
|
||||||
|
|
||||||
|
// The nonce length is defined by the spec as 96 bits (12 bytes) in length.
|
||||||
|
private static final int NONCE_LENGTH = 12;
|
||||||
|
|
||||||
|
private final byte[] nonce;
|
||||||
|
private final int counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parameter set for ChaCha20 from the given nonce
|
||||||
|
* and counter.
|
||||||
|
*
|
||||||
|
* @param nonce a 12-byte nonce value
|
||||||
|
* @param counter the initial counter value
|
||||||
|
*
|
||||||
|
* @throws NullPointerException if {@code nonce} is {@code null}
|
||||||
|
* @throws IllegalArgumentException if {@code nonce} is not 12 bytes
|
||||||
|
* in length
|
||||||
|
*/
|
||||||
|
public ChaCha20ParameterSpec(byte[] nonce, int counter) {
|
||||||
|
this.counter = counter;
|
||||||
|
|
||||||
|
Objects.requireNonNull(nonce, "Nonce must be non-null");
|
||||||
|
this.nonce = nonce.clone();
|
||||||
|
if (this.nonce.length != NONCE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Nonce must be 12-bytes in length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the nonce value.
|
||||||
|
*
|
||||||
|
* @return the nonce value. This method returns a new array each time
|
||||||
|
* this method is called.
|
||||||
|
*/
|
||||||
|
public byte[] getNonce() {
|
||||||
|
return nonce.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured counter value.
|
||||||
|
*
|
||||||
|
* @return the counter value
|
||||||
|
*/
|
||||||
|
public int getCounter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,498 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8153029
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.lib.Convert
|
||||||
|
* @run main ChaCha20KAT
|
||||||
|
* @summary ChaCha20 Cipher Implementation (KAT)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.ChaCha20ParameterSpec;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.crypto.AEADBadTagException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import jdk.test.lib.Convert;
|
||||||
|
|
||||||
|
public class ChaCha20KAT {
|
||||||
|
public static class TestData {
|
||||||
|
public TestData(String name, String keyStr, String nonceStr, int ctr,
|
||||||
|
int dir, String inputStr, String aadStr, String outStr) {
|
||||||
|
testName = Objects.requireNonNull(name);
|
||||||
|
key = Convert.hexStringToByteArray(Objects.requireNonNull(keyStr));
|
||||||
|
nonce = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(nonceStr));
|
||||||
|
if ((counter = ctr) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"counter must be 0 or greater");
|
||||||
|
}
|
||||||
|
direction = dir;
|
||||||
|
if ((direction != Cipher.ENCRYPT_MODE) &&
|
||||||
|
(direction != Cipher.DECRYPT_MODE)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Direction must be ENCRYPT_MODE or DECRYPT_MODE");
|
||||||
|
}
|
||||||
|
input = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(inputStr));
|
||||||
|
aad = (aadStr != null) ?
|
||||||
|
Convert.hexStringToByteArray(aadStr) : null;
|
||||||
|
expOutput = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(outStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String testName;
|
||||||
|
public final byte[] key;
|
||||||
|
public final byte[] nonce;
|
||||||
|
public final int counter;
|
||||||
|
public final int direction;
|
||||||
|
public final byte[] input;
|
||||||
|
public final byte[] aad;
|
||||||
|
public final byte[] expOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final List<TestData> testList = new LinkedList<TestData>() {{
|
||||||
|
add(new TestData("RFC 7539 Sample Test Vector",
|
||||||
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"000000000000004a00000000",
|
||||||
|
1, Cipher.ENCRYPT_MODE,
|
||||||
|
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
|
||||||
|
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
|
||||||
|
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
|
||||||
|
"637265656e20776f756c642062652069742e",
|
||||||
|
null,
|
||||||
|
"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" +
|
||||||
|
"f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" +
|
||||||
|
"07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" +
|
||||||
|
"5af90bbf74a35be6b40b8eedf2785e42874d"));
|
||||||
|
add(new TestData("RFC 7539 Test Vector 1 (all zeroes)",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"000000000000000000000000",
|
||||||
|
0, Cipher.ENCRYPT_MODE,
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
null,
|
||||||
|
"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" +
|
||||||
|
"da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"));
|
||||||
|
add(new TestData("RFC 7539 Test Vector 2",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"000000000000000000000002",
|
||||||
|
1, Cipher.ENCRYPT_MODE,
|
||||||
|
"416e79207375626d697373696f6e20746f20746865204945544620696e74656e" +
|
||||||
|
"6465642062792074686520436f6e7472696275746f7220666f72207075626c69" +
|
||||||
|
"636174696f6e20617320616c6c206f722070617274206f6620616e2049455446" +
|
||||||
|
"20496e7465726e65742d4472616674206f722052464320616e6420616e792073" +
|
||||||
|
"746174656d656e74206d6164652077697468696e2074686520636f6e74657874" +
|
||||||
|
"206f6620616e204945544620616374697669747920697320636f6e7369646572" +
|
||||||
|
"656420616e20224945544620436f6e747269627574696f6e222e205375636820" +
|
||||||
|
"73746174656d656e747320696e636c756465206f72616c2073746174656d656e" +
|
||||||
|
"747320696e20494554462073657373696f6e732c2061732077656c6c20617320" +
|
||||||
|
"7772697474656e20616e6420656c656374726f6e696320636f6d6d756e696361" +
|
||||||
|
"74696f6e73206d61646520617420616e792074696d65206f7220706c6163652c" +
|
||||||
|
"207768696368206172652061646472657373656420746f",
|
||||||
|
null,
|
||||||
|
"a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec" +
|
||||||
|
"2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d" +
|
||||||
|
"4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e527950" +
|
||||||
|
"42bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85a" +
|
||||||
|
"d00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259d" +
|
||||||
|
"c4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b" +
|
||||||
|
"0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6c" +
|
||||||
|
"cc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0b" +
|
||||||
|
"c39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f" +
|
||||||
|
"5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e6" +
|
||||||
|
"98ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab" +
|
||||||
|
"7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221"));
|
||||||
|
add(new TestData("RFC 7539 Test Vector 3",
|
||||||
|
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
|
||||||
|
"000000000000000000000002",
|
||||||
|
42, Cipher.ENCRYPT_MODE,
|
||||||
|
"2754776173206272696c6c69672c20616e642074686520736c6974687920746f" +
|
||||||
|
"7665730a446964206779726520616e642067696d626c6520696e207468652077" +
|
||||||
|
"6162653a0a416c6c206d696d737920776572652074686520626f726f676f7665" +
|
||||||
|
"732c0a416e6420746865206d6f6d65207261746873206f757467726162652e",
|
||||||
|
null,
|
||||||
|
"62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf" +
|
||||||
|
"166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553eb" +
|
||||||
|
"f39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f77" +
|
||||||
|
"04c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
public static final List<TestData> aeadTestList =
|
||||||
|
new LinkedList<TestData>() {{
|
||||||
|
add(new TestData("RFC 7539 Sample AEAD Test Vector",
|
||||||
|
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||||
|
"070000004041424344454647",
|
||||||
|
1, Cipher.ENCRYPT_MODE,
|
||||||
|
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
|
||||||
|
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
|
||||||
|
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
|
||||||
|
"637265656e20776f756c642062652069742e",
|
||||||
|
"50515253c0c1c2c3c4c5c6c7",
|
||||||
|
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" +
|
||||||
|
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" +
|
||||||
|
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" +
|
||||||
|
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" +
|
||||||
|
"0691"));
|
||||||
|
add(new TestData("RFC 7539 A.5 Sample Decryption",
|
||||||
|
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
|
||||||
|
"000000000102030405060708",
|
||||||
|
1, Cipher.DECRYPT_MODE,
|
||||||
|
"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" +
|
||||||
|
"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" +
|
||||||
|
"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" +
|
||||||
|
"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" +
|
||||||
|
"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" +
|
||||||
|
"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" +
|
||||||
|
"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" +
|
||||||
|
"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" +
|
||||||
|
"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38",
|
||||||
|
"f33388860000000000004e91",
|
||||||
|
"496e7465726e65742d4472616674732061726520647261667420646f63756d65" +
|
||||||
|
"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" +
|
||||||
|
"6f6e74687320616e64206d617920626520757064617465642c207265706c6163" +
|
||||||
|
"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" +
|
||||||
|
"6e747320617420616e792074696d652e20497420697320696e617070726f7072" +
|
||||||
|
"6961746520746f2075736520496e7465726e65742d4472616674732061732072" +
|
||||||
|
"65666572656e6365206d6174657269616c206f7220746f206369746520746865" +
|
||||||
|
"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" +
|
||||||
|
"726573732e2fe2809d"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String args[]) throws Exception {
|
||||||
|
int testsPassed = 0;
|
||||||
|
int testNumber = 0;
|
||||||
|
|
||||||
|
System.out.println("----- Single-part (byte[]) Tests -----");
|
||||||
|
for (TestData test : testList) {
|
||||||
|
System.out.println("*** Test " + ++testNumber + ": " +
|
||||||
|
test.testName);
|
||||||
|
if (runSinglePartTest(test)) {
|
||||||
|
testsPassed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
System.out.println("----- Single-part (ByteBuffer) Tests -----");
|
||||||
|
for (TestData test : testList) {
|
||||||
|
System.out.println("*** Test " + ++testNumber + ": " +
|
||||||
|
test.testName);
|
||||||
|
if (runByteBuffer(test)) {
|
||||||
|
testsPassed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
System.out.println("----- Multi-part Tests -----");
|
||||||
|
for (TestData test : testList) {
|
||||||
|
System.out.println("*** Test " + ++testNumber + ": " +
|
||||||
|
test.testName);
|
||||||
|
if (runMultiPartTest(test)) {
|
||||||
|
testsPassed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
System.out.println("----- AEAD Tests -----");
|
||||||
|
for (TestData test : aeadTestList) {
|
||||||
|
System.out.println("*** Test " + ++testNumber + ": " +
|
||||||
|
test.testName);
|
||||||
|
if (runAEADTest(test)) {
|
||||||
|
testsPassed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
System.out.println("Total tests: " + testNumber +
|
||||||
|
", Passed: " + testsPassed + ", Failed: " +
|
||||||
|
(testNumber - testsPassed));
|
||||||
|
if (testsPassed != testNumber) {
|
||||||
|
throw new RuntimeException("One or more tests failed. " +
|
||||||
|
"Check output for details");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean runSinglePartTest(TestData testData)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
boolean encRes = false;
|
||||||
|
boolean decRes = false;
|
||||||
|
byte[] encryptedResult;
|
||||||
|
byte[] decryptedResult;
|
||||||
|
|
||||||
|
// Get a Cipher instance and set up the parameters
|
||||||
|
Cipher mambo = Cipher.getInstance("ChaCha20");
|
||||||
|
SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");
|
||||||
|
ChaCha20ParameterSpec mamboSpec = new ChaCha20ParameterSpec(
|
||||||
|
testData.nonce, testData.counter);
|
||||||
|
|
||||||
|
// Encrypt our input
|
||||||
|
mambo.init(Cipher.ENCRYPT_MODE, mamboKey, mamboSpec);
|
||||||
|
encryptedResult = mambo.doFinal(testData.input);
|
||||||
|
|
||||||
|
if (!Arrays.equals(encryptedResult, testData.expOutput)) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(testData.expOutput, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(encryptedResult, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
encRes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the result of the encryption operation
|
||||||
|
mambo = Cipher.getInstance("ChaCha20");
|
||||||
|
mambo.init(Cipher.DECRYPT_MODE, mamboKey, mamboSpec);
|
||||||
|
decryptedResult = mambo.doFinal(encryptedResult);
|
||||||
|
|
||||||
|
if (!Arrays.equals(decryptedResult, testData.input)) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(testData.input, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(decryptedResult, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
decRes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (encRes && decRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean runMultiPartTest(TestData testData)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
boolean encRes = false;
|
||||||
|
boolean decRes = false;
|
||||||
|
|
||||||
|
// Get a cipher instance and initialize it
|
||||||
|
Cipher mambo = Cipher.getInstance("ChaCha20");
|
||||||
|
SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");
|
||||||
|
ChaCha20ParameterSpec mamboSpec = new ChaCha20ParameterSpec(
|
||||||
|
testData.nonce, testData.counter);
|
||||||
|
|
||||||
|
byte[] encryptedResult = new byte[testData.input.length];
|
||||||
|
mambo.init(Cipher.ENCRYPT_MODE, mamboKey, mamboSpec);
|
||||||
|
System.out.print("Encrypt - ");
|
||||||
|
doMulti(mambo, testData.input, encryptedResult);
|
||||||
|
|
||||||
|
if (!Arrays.equals(encryptedResult, testData.expOutput)) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(testData.expOutput, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(encryptedResult, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
encRes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the result of the encryption operation
|
||||||
|
mambo = Cipher.getInstance("ChaCha20");
|
||||||
|
byte[] decryptedResult = new byte[encryptedResult.length];
|
||||||
|
mambo.init(Cipher.DECRYPT_MODE, mamboKey, mamboSpec);
|
||||||
|
System.out.print("Decrypt - ");
|
||||||
|
doMulti(mambo, encryptedResult, decryptedResult);
|
||||||
|
|
||||||
|
if (!Arrays.equals(decryptedResult, testData.input)) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(testData.input, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(decryptedResult, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
decRes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (encRes && decRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doMulti(Cipher c, byte[] input, byte[] output)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
int offset = 0;
|
||||||
|
boolean done = false;
|
||||||
|
Random randIn = new Random(System.currentTimeMillis());
|
||||||
|
|
||||||
|
// Send small updates between 1 - 8 bytes in length until we get
|
||||||
|
// 8 or less bytes from the end of the input, then finalize.
|
||||||
|
System.out.println("Input length: " + input.length);
|
||||||
|
System.out.print("Multipart (bytes in/out): ");
|
||||||
|
while (!done) {
|
||||||
|
int mPartLen = randIn.nextInt(8) + 1;
|
||||||
|
int bytesLeft = input.length - offset;
|
||||||
|
int processed;
|
||||||
|
if (mPartLen < bytesLeft) {
|
||||||
|
System.out.print(mPartLen + "/");
|
||||||
|
processed = c.update(input, offset, mPartLen,
|
||||||
|
output, offset);
|
||||||
|
offset += processed;
|
||||||
|
System.out.print(processed + " ");
|
||||||
|
} else {
|
||||||
|
processed = c.doFinal(input, offset, bytesLeft,
|
||||||
|
output, offset);
|
||||||
|
System.out.print(bytesLeft + "/" + processed + " ");
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean runByteBuffer(TestData testData)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
boolean encRes = false;
|
||||||
|
boolean decRes = false;
|
||||||
|
|
||||||
|
// Get a cipher instance and initialize it
|
||||||
|
Cipher mambo = Cipher.getInstance("ChaCha20");
|
||||||
|
SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");
|
||||||
|
ChaCha20ParameterSpec mamboSpec = new ChaCha20ParameterSpec(
|
||||||
|
testData.nonce, testData.counter);
|
||||||
|
mambo.init(Cipher.ENCRYPT_MODE, mamboKey, mamboSpec);
|
||||||
|
|
||||||
|
ByteBuffer bbIn = ByteBuffer.wrap(testData.input);
|
||||||
|
ByteBuffer bbEncOut = ByteBuffer.allocate(
|
||||||
|
mambo.getOutputSize(testData.input.length));
|
||||||
|
ByteBuffer bbExpOut = ByteBuffer.wrap(testData.expOutput);
|
||||||
|
|
||||||
|
mambo.doFinal(bbIn, bbEncOut);
|
||||||
|
bbIn.rewind();
|
||||||
|
bbEncOut.rewind();
|
||||||
|
|
||||||
|
if (bbEncOut.compareTo(bbExpOut) != 0) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(bbExpOut, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(bbEncOut, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
encRes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the result of the encryption operation
|
||||||
|
mambo = Cipher.getInstance("ChaCha20");
|
||||||
|
mambo.init(Cipher.DECRYPT_MODE, mamboKey, mamboSpec);
|
||||||
|
System.out.print("Decrypt - ");
|
||||||
|
ByteBuffer bbDecOut = ByteBuffer.allocate(
|
||||||
|
mambo.getOutputSize(bbEncOut.remaining()));
|
||||||
|
|
||||||
|
mambo.doFinal(bbEncOut, bbDecOut);
|
||||||
|
bbEncOut.rewind();
|
||||||
|
bbDecOut.rewind();
|
||||||
|
|
||||||
|
if (bbDecOut.compareTo(bbIn) != 0) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(bbIn, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(bbDecOut, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
decRes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (encRes && decRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean runAEADTest(TestData testData)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
Cipher mambo = Cipher.getInstance("ChaCha20-Poly1305");
|
||||||
|
SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");
|
||||||
|
IvParameterSpec mamboSpec = new IvParameterSpec(testData.nonce);
|
||||||
|
|
||||||
|
mambo.init(testData.direction, mamboKey, mamboSpec);
|
||||||
|
|
||||||
|
byte[] out = new byte[mambo.getOutputSize(testData.input.length)];
|
||||||
|
int outOff = 0;
|
||||||
|
try {
|
||||||
|
mambo.updateAAD(testData.aad);
|
||||||
|
outOff += mambo.update(testData.input, 0, testData.input.length,
|
||||||
|
out, outOff);
|
||||||
|
outOff += mambo.doFinal(out, outOff);
|
||||||
|
} catch (AEADBadTagException abte) {
|
||||||
|
// If we get a bad tag or derive a tag mismatch, log it
|
||||||
|
// and register it as a failure
|
||||||
|
System.out.println("FAIL: " + abte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Arrays.equals(out, testData.expOutput)) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(testData.expOutput, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(out, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump the hex bytes of a buffer into string form.
|
||||||
|
*
|
||||||
|
* @param data The array of bytes to dump to stdout.
|
||||||
|
* @param itemsPerLine The number of bytes to display per line
|
||||||
|
* if the {@code lineDelim} character is blank then all bytes
|
||||||
|
* will be printed on a single line.
|
||||||
|
* @param lineDelim The delimiter between lines
|
||||||
|
* @param itemDelim The delimiter between bytes
|
||||||
|
*
|
||||||
|
* @return The hexdump of the byte array
|
||||||
|
*/
|
||||||
|
private static String dumpHexBytes(byte[] data, int itemsPerLine,
|
||||||
|
String lineDelim, String itemDelim) {
|
||||||
|
return dumpHexBytes(ByteBuffer.wrap(data), itemsPerLine, lineDelim,
|
||||||
|
itemDelim);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String dumpHexBytes(ByteBuffer data, int itemsPerLine,
|
||||||
|
String lineDelim, String itemDelim) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (data != null) {
|
||||||
|
data.mark();
|
||||||
|
int i = 0;
|
||||||
|
while (data.remaining() > 0) {
|
||||||
|
if (i % itemsPerLine == 0 && i != 0) {
|
||||||
|
sb.append(lineDelim);
|
||||||
|
}
|
||||||
|
sb.append(String.format("%02X", data.get())).append(itemDelim);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
data.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,625 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8153029
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.lib.Convert
|
||||||
|
* @run main ChaCha20NoReuse
|
||||||
|
* @summary ChaCha20 Cipher Implementation (key/nonce reuse protection)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import javax.crypto.spec.ChaCha20ParameterSpec;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.crypto.AEADBadTagException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import jdk.test.lib.Convert;
|
||||||
|
|
||||||
|
public class ChaCha20NoReuse {
|
||||||
|
|
||||||
|
private static final String ALG_CC20 = "ChaCha20";
|
||||||
|
private static final String ALG_CC20_P1305 = "ChaCha20-Poly1305";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic TestMethod interface definition.
|
||||||
|
*/
|
||||||
|
public interface TestMethod {
|
||||||
|
/**
|
||||||
|
* Runs the actual test case
|
||||||
|
*
|
||||||
|
* @param algorithm the algorithm to use (e.g. ChaCha20, etc.)
|
||||||
|
*
|
||||||
|
* @return true if the test passes, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean run(String algorithm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this TestMethod can be run for this algorithm. Some tests
|
||||||
|
* are specific to ChaCha20 or ChaCha20-Poly1305, so this method
|
||||||
|
* can be used to determine if a given Cipher type is appropriate.
|
||||||
|
*
|
||||||
|
* @param algorithm the algorithm to use.
|
||||||
|
*
|
||||||
|
* @return true if this test can be run on this algorithm,
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
boolean isValid(String algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestData {
|
||||||
|
public TestData(String name, String keyStr, String nonceStr, int ctr,
|
||||||
|
int dir, String inputStr, String aadStr, String outStr) {
|
||||||
|
testName = Objects.requireNonNull(name);
|
||||||
|
key = Convert.hexStringToByteArray(Objects.requireNonNull(keyStr));
|
||||||
|
nonce = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(nonceStr));
|
||||||
|
if ((counter = ctr) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"counter must be 0 or greater");
|
||||||
|
}
|
||||||
|
direction = dir;
|
||||||
|
if ((direction != Cipher.ENCRYPT_MODE) &&
|
||||||
|
(direction != Cipher.DECRYPT_MODE)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Direction must be ENCRYPT_MODE or DECRYPT_MODE");
|
||||||
|
}
|
||||||
|
input = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(inputStr));
|
||||||
|
aad = (aadStr != null) ?
|
||||||
|
Convert.hexStringToByteArray(aadStr) : null;
|
||||||
|
expOutput = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(outStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String testName;
|
||||||
|
public final byte[] key;
|
||||||
|
public final byte[] nonce;
|
||||||
|
public final int counter;
|
||||||
|
public final int direction;
|
||||||
|
public final byte[] input;
|
||||||
|
public final byte[] aad;
|
||||||
|
public final byte[] expOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final List<TestData> testList = new LinkedList<TestData>() {{
|
||||||
|
add(new TestData("RFC 7539 Sample Test Vector [ENCRYPT]",
|
||||||
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"000000000000004a00000000",
|
||||||
|
1, Cipher.ENCRYPT_MODE,
|
||||||
|
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
|
||||||
|
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
|
||||||
|
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
|
||||||
|
"637265656e20776f756c642062652069742e",
|
||||||
|
null,
|
||||||
|
"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" +
|
||||||
|
"f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" +
|
||||||
|
"07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" +
|
||||||
|
"5af90bbf74a35be6b40b8eedf2785e42874d"));
|
||||||
|
add(new TestData("RFC 7539 Sample Test Vector [DECRYPT]",
|
||||||
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"000000000000004a00000000",
|
||||||
|
1, Cipher.DECRYPT_MODE,
|
||||||
|
"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" +
|
||||||
|
"f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" +
|
||||||
|
"07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" +
|
||||||
|
"5af90bbf74a35be6b40b8eedf2785e42874d",
|
||||||
|
null,
|
||||||
|
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
|
||||||
|
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
|
||||||
|
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
|
||||||
|
"637265656e20776f756c642062652069742e"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
public static final List<TestData> aeadTestList =
|
||||||
|
new LinkedList<TestData>() {{
|
||||||
|
add(new TestData("RFC 7539 Sample AEAD Test Vector",
|
||||||
|
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||||
|
"070000004041424344454647",
|
||||||
|
1, Cipher.ENCRYPT_MODE,
|
||||||
|
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
|
||||||
|
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
|
||||||
|
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
|
||||||
|
"637265656e20776f756c642062652069742e",
|
||||||
|
"50515253c0c1c2c3c4c5c6c7",
|
||||||
|
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" +
|
||||||
|
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" +
|
||||||
|
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" +
|
||||||
|
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" +
|
||||||
|
"0691"));
|
||||||
|
add(new TestData("RFC 7539 A.5 Sample Decryption",
|
||||||
|
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
|
||||||
|
"000000000102030405060708",
|
||||||
|
1, Cipher.DECRYPT_MODE,
|
||||||
|
"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" +
|
||||||
|
"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" +
|
||||||
|
"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" +
|
||||||
|
"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" +
|
||||||
|
"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" +
|
||||||
|
"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" +
|
||||||
|
"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" +
|
||||||
|
"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" +
|
||||||
|
"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38",
|
||||||
|
"f33388860000000000004e91",
|
||||||
|
"496e7465726e65742d4472616674732061726520647261667420646f63756d65" +
|
||||||
|
"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" +
|
||||||
|
"6f6e74687320616e64206d617920626520757064617465642c207265706c6163" +
|
||||||
|
"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" +
|
||||||
|
"6e747320617420616e792074696d652e20497420697320696e617070726f7072" +
|
||||||
|
"6961746520746f2075736520496e7465726e65742d4472616674732061732072" +
|
||||||
|
"65666572656e6365206d6174657269616c206f7220746f206369746520746865" +
|
||||||
|
"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" +
|
||||||
|
"726573732e2fe2809d"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure we do not use this Cipher object without initializing it
|
||||||
|
* at all
|
||||||
|
*/
|
||||||
|
public static final TestMethod noInitTest = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return true; // Valid for all algs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println("----- No Init Test [" + algorithm +
|
||||||
|
"] -----");
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
TestData testData;
|
||||||
|
switch (algorithm) {
|
||||||
|
case ALG_CC20:
|
||||||
|
testData = testList.get(0);
|
||||||
|
break;
|
||||||
|
case ALG_CC20_P1305:
|
||||||
|
testData = aeadTestList.get(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported cipher type: " + algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempting to use the cipher without initializing it
|
||||||
|
// should throw an IllegalStateException
|
||||||
|
try {
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected IllegalStateException not thrown");
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure we don't allow a double init using the same parameters
|
||||||
|
*/
|
||||||
|
public static final TestMethod doubleInitTest = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return true; // Valid for all algs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println("----- Double Init Test [" + algorithm +
|
||||||
|
"] -----");
|
||||||
|
try {
|
||||||
|
AlgorithmParameterSpec spec;
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
TestData testData;
|
||||||
|
switch (algorithm) {
|
||||||
|
case ALG_CC20:
|
||||||
|
testData = testList.get(0);
|
||||||
|
spec = new ChaCha20ParameterSpec(testData.nonce,
|
||||||
|
testData.counter);
|
||||||
|
break;
|
||||||
|
case ALG_CC20_P1305:
|
||||||
|
testData = aeadTestList.get(0);
|
||||||
|
spec = new IvParameterSpec(testData.nonce);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported cipher type: " + algorithm);
|
||||||
|
}
|
||||||
|
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
|
||||||
|
|
||||||
|
// Initialize the first time, this should work.
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
|
||||||
|
// Immediately initializing a second time with the same
|
||||||
|
// parameters should fail
|
||||||
|
try {
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected InvalidKeyException not thrown");
|
||||||
|
} catch (InvalidKeyException ike) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to run two full encryption operations without an init in
|
||||||
|
* between.
|
||||||
|
*/
|
||||||
|
public static final TestMethod encTwiceNoInit = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return true; // Valid for all algs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println("----- Encrypt second time without init [" +
|
||||||
|
algorithm + "] -----");
|
||||||
|
try {
|
||||||
|
AlgorithmParameterSpec spec;
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
TestData testData;
|
||||||
|
switch (algorithm) {
|
||||||
|
case ALG_CC20:
|
||||||
|
testData = testList.get(0);
|
||||||
|
spec = new ChaCha20ParameterSpec(testData.nonce,
|
||||||
|
testData.counter);
|
||||||
|
break;
|
||||||
|
case ALG_CC20_P1305:
|
||||||
|
testData = aeadTestList.get(0);
|
||||||
|
spec = new IvParameterSpec(testData.nonce);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported cipher type: " + algorithm);
|
||||||
|
}
|
||||||
|
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
|
||||||
|
|
||||||
|
// Initialize and encrypt
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
System.out.println("First encryption complete");
|
||||||
|
|
||||||
|
// Now attempt to encrypt again without changing the key/IV
|
||||||
|
// This should fail.
|
||||||
|
try {
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected IllegalStateException not thrown");
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to run two full decryption operations without an init in
|
||||||
|
* between.
|
||||||
|
*/
|
||||||
|
public static final TestMethod decTwiceNoInit = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return true; // Valid for all algs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println("----- Decrypt second time without init [" +
|
||||||
|
algorithm + "] -----");
|
||||||
|
try {
|
||||||
|
AlgorithmParameterSpec spec;
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
TestData testData;
|
||||||
|
switch (algorithm) {
|
||||||
|
case ALG_CC20:
|
||||||
|
testData = testList.get(1);
|
||||||
|
spec = new ChaCha20ParameterSpec(testData.nonce,
|
||||||
|
testData.counter);
|
||||||
|
break;
|
||||||
|
case ALG_CC20_P1305:
|
||||||
|
testData = aeadTestList.get(1);
|
||||||
|
spec = new IvParameterSpec(testData.nonce);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported cipher type: " + algorithm);
|
||||||
|
}
|
||||||
|
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
|
||||||
|
|
||||||
|
// Initialize and encrypt
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
System.out.println("First decryption complete");
|
||||||
|
|
||||||
|
// Now attempt to encrypt again without changing the key/IV
|
||||||
|
// This should fail.
|
||||||
|
try {
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected IllegalStateException not thrown");
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an AEAD decryption with corrupted data so the tag does not
|
||||||
|
* match. Then attempt to reuse the cipher without initialization.
|
||||||
|
*/
|
||||||
|
public static final TestMethod decFailNoInit = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return algorithm.equals(ALG_CC20_P1305);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println(
|
||||||
|
"----- Fail decryption, try again with no init [" +
|
||||||
|
algorithm + "] -----");
|
||||||
|
try {
|
||||||
|
TestData testData = aeadTestList.get(1);
|
||||||
|
AlgorithmParameterSpec spec =
|
||||||
|
new IvParameterSpec(testData.nonce);
|
||||||
|
byte[] corruptInput = testData.input.clone();
|
||||||
|
corruptInput[0]++; // Corrupt the ciphertext
|
||||||
|
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize and encrypt
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
cipher.doFinal(corruptInput);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected AEADBadTagException not thrown");
|
||||||
|
} catch (AEADBadTagException abte) {
|
||||||
|
System.out.println("Expected decryption failure occurred");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that despite the exception, the Cipher object is
|
||||||
|
// not in a state that would leave it initialized and able
|
||||||
|
// to process future decryption operations without init.
|
||||||
|
try {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected IllegalStateException not thrown");
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt once successfully, then attempt to init with the same
|
||||||
|
* key and nonce.
|
||||||
|
*/
|
||||||
|
public static final TestMethod encTwiceInitSameParams = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return true; // Valid for all algs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println("----- Encrypt, then init with same params [" +
|
||||||
|
algorithm + "] -----");
|
||||||
|
try {
|
||||||
|
AlgorithmParameterSpec spec;
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
TestData testData;
|
||||||
|
switch (algorithm) {
|
||||||
|
case ALG_CC20:
|
||||||
|
testData = testList.get(0);
|
||||||
|
spec = new ChaCha20ParameterSpec(testData.nonce,
|
||||||
|
testData.counter);
|
||||||
|
break;
|
||||||
|
case ALG_CC20_P1305:
|
||||||
|
testData = aeadTestList.get(0);
|
||||||
|
spec = new IvParameterSpec(testData.nonce);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported cipher type: " + algorithm);
|
||||||
|
}
|
||||||
|
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
|
||||||
|
|
||||||
|
// Initialize then encrypt
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
System.out.println("First encryption complete");
|
||||||
|
|
||||||
|
// Initializing after the completed encryption with
|
||||||
|
// the same key and nonce should fail.
|
||||||
|
try {
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected InvalidKeyException not thrown");
|
||||||
|
} catch (InvalidKeyException ike) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt once successfully, then attempt to init with the same
|
||||||
|
* key and nonce.
|
||||||
|
*/
|
||||||
|
public static final TestMethod decTwiceInitSameParams = new TestMethod() {
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String algorithm) {
|
||||||
|
return true; // Valid for all algs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean run(String algorithm) {
|
||||||
|
System.out.println("----- Decrypt, then init with same params [" +
|
||||||
|
algorithm + "] -----");
|
||||||
|
try {
|
||||||
|
AlgorithmParameterSpec spec;
|
||||||
|
Cipher cipher = Cipher.getInstance(algorithm);
|
||||||
|
TestData testData;
|
||||||
|
switch (algorithm) {
|
||||||
|
case ALG_CC20:
|
||||||
|
testData = testList.get(1);
|
||||||
|
spec = new ChaCha20ParameterSpec(testData.nonce,
|
||||||
|
testData.counter);
|
||||||
|
break;
|
||||||
|
case ALG_CC20_P1305:
|
||||||
|
testData = aeadTestList.get(1);
|
||||||
|
spec = new IvParameterSpec(testData.nonce);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported cipher type: " + algorithm);
|
||||||
|
}
|
||||||
|
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
|
||||||
|
|
||||||
|
// Initialize then decrypt
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
if (algorithm.equals(ALG_CC20_P1305)) {
|
||||||
|
cipher.updateAAD(testData.aad);
|
||||||
|
}
|
||||||
|
cipher.doFinal(testData.input);
|
||||||
|
System.out.println("First decryption complete");
|
||||||
|
|
||||||
|
// Initializing after the completed decryption with
|
||||||
|
// the same key and nonce should fail.
|
||||||
|
try {
|
||||||
|
cipher.init(testData.direction, key, spec);
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected InvalidKeyException not thrown");
|
||||||
|
} catch (InvalidKeyException ike) {
|
||||||
|
// Do nothing, this is what we expected to happen
|
||||||
|
}
|
||||||
|
} catch (Exception exc) {
|
||||||
|
System.out.println("Unexpected exception: " + exc);
|
||||||
|
exc.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final List<String> algList =
|
||||||
|
Arrays.asList(ALG_CC20, ALG_CC20_P1305);
|
||||||
|
|
||||||
|
public static final List<TestMethod> testMethodList =
|
||||||
|
Arrays.asList(noInitTest, doubleInitTest, encTwiceNoInit,
|
||||||
|
decTwiceNoInit, decFailNoInit, encTwiceInitSameParams,
|
||||||
|
decTwiceInitSameParams);
|
||||||
|
|
||||||
|
public static void main(String args[]) throws Exception {
|
||||||
|
int testsPassed = 0;
|
||||||
|
int testNumber = 0;
|
||||||
|
|
||||||
|
for (TestMethod tm : testMethodList) {
|
||||||
|
for (String alg : algList) {
|
||||||
|
if (tm.isValid(alg)) {
|
||||||
|
testNumber++;
|
||||||
|
boolean result = tm.run(alg);
|
||||||
|
System.out.println("Result: " + (result ? "PASS" : "FAIL"));
|
||||||
|
if (result) {
|
||||||
|
testsPassed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Total Tests: " + testNumber +
|
||||||
|
", Tests passed: " + testsPassed);
|
||||||
|
if (testsPassed < testNumber) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Not all tests passed. See output for failure info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,414 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8153029
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.lib.Convert
|
||||||
|
* @run main ChaCha20Poly1305ParamTest
|
||||||
|
* @summary ChaCha20 Cipher Implementation (parameters)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.ChaCha20ParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.crypto.AEADBadTagException;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import jdk.test.lib.Convert;
|
||||||
|
|
||||||
|
public class ChaCha20Poly1305ParamTest {
|
||||||
|
public static class TestData {
|
||||||
|
public TestData(String name, String keyStr, String nonceStr, int ctr,
|
||||||
|
int dir, String inputStr, String aadStr, String outStr) {
|
||||||
|
testName = Objects.requireNonNull(name);
|
||||||
|
key = Convert.hexStringToByteArray(Objects.requireNonNull(keyStr));
|
||||||
|
nonce = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(nonceStr));
|
||||||
|
if ((counter = ctr) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"counter must be 0 or greater");
|
||||||
|
}
|
||||||
|
direction = dir;
|
||||||
|
if ((direction != Cipher.ENCRYPT_MODE) &&
|
||||||
|
(direction != Cipher.DECRYPT_MODE)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Direction must be ENCRYPT_MODE or DECRYPT_MODE");
|
||||||
|
}
|
||||||
|
input = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(inputStr));
|
||||||
|
aad = (aadStr != null) ?
|
||||||
|
Convert.hexStringToByteArray(aadStr) : null;
|
||||||
|
expOutput = Convert.hexStringToByteArray(
|
||||||
|
Objects.requireNonNull(outStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String testName;
|
||||||
|
public final byte[] key;
|
||||||
|
public final byte[] nonce;
|
||||||
|
public final int counter;
|
||||||
|
public final int direction;
|
||||||
|
public final byte[] input;
|
||||||
|
public final byte[] aad;
|
||||||
|
public final byte[] expOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final List<TestData> aeadTestList =
|
||||||
|
new LinkedList<TestData>() {{
|
||||||
|
add(new TestData("RFC 7539 Sample AEAD Test Vector",
|
||||||
|
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||||
|
"070000004041424344454647",
|
||||||
|
1, Cipher.ENCRYPT_MODE,
|
||||||
|
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
|
||||||
|
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
|
||||||
|
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
|
||||||
|
"637265656e20776f756c642062652069742e",
|
||||||
|
"50515253c0c1c2c3c4c5c6c7",
|
||||||
|
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" +
|
||||||
|
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" +
|
||||||
|
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" +
|
||||||
|
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" +
|
||||||
|
"0691"));
|
||||||
|
add(new TestData("RFC 7539 A.5 Sample Decryption",
|
||||||
|
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
|
||||||
|
"000000000102030405060708",
|
||||||
|
1, Cipher.DECRYPT_MODE,
|
||||||
|
"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" +
|
||||||
|
"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" +
|
||||||
|
"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" +
|
||||||
|
"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" +
|
||||||
|
"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" +
|
||||||
|
"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" +
|
||||||
|
"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" +
|
||||||
|
"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" +
|
||||||
|
"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38",
|
||||||
|
"f33388860000000000004e91",
|
||||||
|
"496e7465726e65742d4472616674732061726520647261667420646f63756d65" +
|
||||||
|
"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" +
|
||||||
|
"6f6e74687320616e64206d617920626520757064617465642c207265706c6163" +
|
||||||
|
"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" +
|
||||||
|
"6e747320617420616e792074696d652e20497420697320696e617070726f7072" +
|
||||||
|
"6961746520746f2075736520496e7465726e65742d4472616674732061732072" +
|
||||||
|
"65666572656e6365206d6174657269616c206f7220746f206369746520746865" +
|
||||||
|
"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" +
|
||||||
|
"726573732e2fe2809d"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
// 12-byte nonce DER-encoded as an OCTET_STRING
|
||||||
|
public static final byte[] NONCE_OCTET_STR_12 = {
|
||||||
|
4, 12, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||||
|
};
|
||||||
|
|
||||||
|
// Invalid 16-byte nonce DER-encoded as an OCTET_STRING
|
||||||
|
public static final byte[] NONCE_OCTET_STR_16 = {
|
||||||
|
4, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||||
|
};
|
||||||
|
|
||||||
|
// Throwaway key for default init tests
|
||||||
|
public static final SecretKey DEF_KEY = new SecretKeySpec(new byte[]
|
||||||
|
{
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
|
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
|
||||||
|
}, "ChaCha20");
|
||||||
|
|
||||||
|
public static void main(String args[]) throws Exception {
|
||||||
|
int testsPassed = 0;
|
||||||
|
int testNumber = 0;
|
||||||
|
|
||||||
|
// Try some default initializations
|
||||||
|
testDefaultAlgParams("ChaCha20", Cipher.ENCRYPT_MODE, true);
|
||||||
|
testDefaultAlgParams("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true);
|
||||||
|
testDefaultAlgParamSpec("ChaCha20", Cipher.ENCRYPT_MODE, true);
|
||||||
|
testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true);
|
||||||
|
testDefaultAlgParams("ChaCha20", Cipher.DECRYPT_MODE, false);
|
||||||
|
testDefaultAlgParams("ChaCha20-Poly1305", Cipher.DECRYPT_MODE, false);
|
||||||
|
testDefaultAlgParamSpec("ChaCha20", Cipher.DECRYPT_MODE, false);
|
||||||
|
testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.DECRYPT_MODE,
|
||||||
|
false);
|
||||||
|
|
||||||
|
// Try (and hopefully fail) to create a ChaCha20 AlgorithmParameterSpec
|
||||||
|
System.out.println(
|
||||||
|
"*** Test: Try to make ChaCha20 AlgorithmParameterSpec");
|
||||||
|
try {
|
||||||
|
ChaCha20ParameterSpec badChaCha20Spec =
|
||||||
|
new ChaCha20ParameterSpec(NONCE_OCTET_STR_16, 1);
|
||||||
|
throw new RuntimeException("ChaCha20 AlgorithmParameterSpec " +
|
||||||
|
"with 16 byte nonce should fail");
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
System.out.println("Caught expected exception: " + iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try (and hopefully fail) to create a ChaCha20 AlgorithmParameters
|
||||||
|
System.out.println(
|
||||||
|
"*** Test: Try to make ChaCha20 AlgorithmParameters");
|
||||||
|
try {
|
||||||
|
AlgorithmParameters apsNoChaCha20 =
|
||||||
|
AlgorithmParameters.getInstance("ChaCha20");
|
||||||
|
throw new RuntimeException(
|
||||||
|
"ChaCha20 AlgorithmParameters should fail");
|
||||||
|
} catch (NoSuchAlgorithmException nsae) {
|
||||||
|
System.out.println("Caught expected exception: " + nsae);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the AlgorithmParameters object from a valid encoding
|
||||||
|
System.out.println("*** Test: Create and init ChaCha20-Poly1305 APS");
|
||||||
|
AlgorithmParameters apsGood =
|
||||||
|
AlgorithmParameters.getInstance("ChaCha20-Poly1305");
|
||||||
|
apsGood.init(NONCE_OCTET_STR_12);
|
||||||
|
System.out.println("Test Passed");
|
||||||
|
|
||||||
|
// Pull an AlgorithmParameters object out of the initialized cipher
|
||||||
|
// and compare its value against the original.
|
||||||
|
System.out.println("*** Test: Init ChaCha20-Poly1305 Cipher using " +
|
||||||
|
"AP, retrieve AP and compare");
|
||||||
|
Cipher cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");
|
||||||
|
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY, apsGood);
|
||||||
|
AlgorithmParameters pulledParams = cc20p1305.getParameters();
|
||||||
|
byte[] apsGoodData = apsGood.getEncoded();
|
||||||
|
byte[] pulledParamsData = pulledParams.getEncoded();
|
||||||
|
if (!Arrays.equals(apsGoodData, pulledParamsData)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Retrieved parameters do not match those used to init cipher");
|
||||||
|
}
|
||||||
|
System.out.println("Test Passed");
|
||||||
|
|
||||||
|
// Try the same test with ChaCha20. It should always be null.
|
||||||
|
System.out.println("*** Test: Init ChaCha20 Cipher using " +
|
||||||
|
"AP, retrieve AP and compare");
|
||||||
|
Cipher cc20 = Cipher.getInstance("ChaCha20");
|
||||||
|
cc20.init(Cipher.ENCRYPT_MODE, DEF_KEY);
|
||||||
|
pulledParams = cc20.getParameters();
|
||||||
|
if (pulledParams != null) {
|
||||||
|
throw new RuntimeException("Unexpected non-null " +
|
||||||
|
"AlgorithmParameters from ChaCha20 cipiher");
|
||||||
|
}
|
||||||
|
System.out.println("Test Passed");
|
||||||
|
|
||||||
|
// Create and try to init using invalid encoding
|
||||||
|
AlgorithmParameters apsBad =
|
||||||
|
AlgorithmParameters.getInstance("ChaCha20-Poly1305");
|
||||||
|
System.out.println("*** Test: Use invalid encoding scheme");
|
||||||
|
try {
|
||||||
|
apsBad.init(NONCE_OCTET_STR_12, "OraclePrivate");
|
||||||
|
throw new RuntimeException("Allowed unsupported encoding scheme: " +
|
||||||
|
apsBad.getAlgorithm());
|
||||||
|
} catch (IOException iae) {
|
||||||
|
System.out.println("Caught expected exception: " + iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to init using supported scheme but invalid length
|
||||||
|
System.out.println("*** Test: Use supported scheme, nonce too large");
|
||||||
|
try {
|
||||||
|
apsBad.init(NONCE_OCTET_STR_16, "ASN.1");
|
||||||
|
throw new RuntimeException("Allowed invalid encoded length");
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
System.out.println("Caught expected exception: " + ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("----- AEAD Tests -----");
|
||||||
|
for (TestData test : aeadTestList) {
|
||||||
|
System.out.println("*** Test " + ++testNumber + ": " +
|
||||||
|
test.testName);
|
||||||
|
if (runAEADTest(test)) {
|
||||||
|
testsPassed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
System.out.println("Total tests: " + testNumber +
|
||||||
|
", Passed: " + testsPassed + ", Failed: " +
|
||||||
|
(testNumber - testsPassed));
|
||||||
|
if (testsPassed != testNumber) {
|
||||||
|
throw new RuntimeException("One or more tests failed. " +
|
||||||
|
"Check output for details");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt default inits with null AlgorithmParameters
|
||||||
|
*
|
||||||
|
* @param alg the algorithm (ChaCha20, ChaCha20-Poly1305)
|
||||||
|
* @param mode the Cipher mode (ENCRYPT_MODE, etc.)
|
||||||
|
*/
|
||||||
|
private static void testDefaultAlgParams(String alg, int mode,
|
||||||
|
boolean shouldPass) {
|
||||||
|
byte[] ivOne = null, ivTwo = null;
|
||||||
|
System.out.println("Test default AlgorithmParameters: Cipher = " +
|
||||||
|
alg + ", mode = " + mode);
|
||||||
|
try {
|
||||||
|
AlgorithmParameters params = null;
|
||||||
|
Cipher cipher = Cipher.getInstance(alg);
|
||||||
|
cipher.init(mode, DEF_KEY, params, null);
|
||||||
|
ivOne = cipher.getIV();
|
||||||
|
cipher.init(mode, DEF_KEY, params, null);
|
||||||
|
ivTwo = cipher.getIV();
|
||||||
|
if (!shouldPass) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Did not receive expected exception");
|
||||||
|
}
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
if (shouldPass) {
|
||||||
|
throw new RuntimeException(gse);
|
||||||
|
}
|
||||||
|
System.out.println("Caught expected exception: " + gse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Arrays.equals(ivOne, ivTwo)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"FAIL! Two inits generated same nonces");
|
||||||
|
} else {
|
||||||
|
System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " "));
|
||||||
|
System.out.println("IV 1:\n" + dumpHexBytes(ivTwo, 16, "\n", " "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt default inits with null AlgorithmParameters
|
||||||
|
*
|
||||||
|
* @param alg the algorithm (ChaCha20, ChaCha20-Poly1305)
|
||||||
|
* @param mode the Cipher mode (ENCRYPT_MODE, etc.)
|
||||||
|
*/
|
||||||
|
private static void testDefaultAlgParamSpec(String alg, int mode,
|
||||||
|
boolean shouldPass) {
|
||||||
|
byte[] ivOne = null, ivTwo = null;
|
||||||
|
System.out.println("Test default AlgorithmParameterSpec: Cipher = " +
|
||||||
|
alg + ", mode = " + mode);
|
||||||
|
try {
|
||||||
|
AlgorithmParameterSpec params = null;
|
||||||
|
Cipher cipher = Cipher.getInstance(alg);
|
||||||
|
cipher.init(mode, DEF_KEY, params, null);
|
||||||
|
ivOne = cipher.getIV();
|
||||||
|
cipher.init(mode, DEF_KEY, params, null);
|
||||||
|
ivTwo = cipher.getIV();
|
||||||
|
if (!shouldPass) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Did not receive expected exception");
|
||||||
|
}
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
if (shouldPass) {
|
||||||
|
throw new RuntimeException(gse);
|
||||||
|
}
|
||||||
|
System.out.println("Caught expected exception: " + gse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Arrays.equals(ivOne, ivTwo)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"FAIL! Two inits generated same nonces");
|
||||||
|
} else {
|
||||||
|
System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " "));
|
||||||
|
System.out.println("IV 2:\n" + dumpHexBytes(ivTwo, 16, "\n", " "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean runAEADTest(TestData testData)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
Cipher mambo = Cipher.getInstance("ChaCha20-Poly1305");
|
||||||
|
SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");
|
||||||
|
AlgorithmParameters mamboParams =
|
||||||
|
AlgorithmParameters.getInstance("ChaCha20-Poly1305");
|
||||||
|
|
||||||
|
// Put the nonce into ASN.1 ChaCha20-Poly1305 parameter format
|
||||||
|
byte[] derNonce = new byte[testData.nonce.length + 2];
|
||||||
|
derNonce[0] = 0x04;
|
||||||
|
derNonce[1] = (byte)testData.nonce.length;
|
||||||
|
System.arraycopy(testData.nonce, 0, derNonce, 2,
|
||||||
|
testData.nonce.length);
|
||||||
|
mamboParams.init(derNonce);
|
||||||
|
|
||||||
|
mambo.init(testData.direction, mamboKey, mamboParams);
|
||||||
|
|
||||||
|
byte[] out = new byte[mambo.getOutputSize(testData.input.length)];
|
||||||
|
int outOff = 0;
|
||||||
|
try {
|
||||||
|
mambo.updateAAD(testData.aad);
|
||||||
|
outOff += mambo.update(testData.input, 0, testData.input.length,
|
||||||
|
out, outOff);
|
||||||
|
outOff += mambo.doFinal(out, outOff);
|
||||||
|
} catch (AEADBadTagException abte) {
|
||||||
|
// If we get a bad tag or derive a tag mismatch, log it
|
||||||
|
// and register it as a failure
|
||||||
|
System.out.println("FAIL: " + abte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Arrays.equals(out, testData.expOutput)) {
|
||||||
|
System.out.println("ERROR - Output Mismatch!");
|
||||||
|
System.out.println("Expected:\n" +
|
||||||
|
dumpHexBytes(testData.expOutput, 16, "\n", " "));
|
||||||
|
System.out.println("Actual:\n" +
|
||||||
|
dumpHexBytes(out, 16, "\n", " "));
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump the hex bytes of a buffer into string form.
|
||||||
|
*
|
||||||
|
* @param data The array of bytes to dump to stdout.
|
||||||
|
* @param itemsPerLine The number of bytes to display per line
|
||||||
|
* if the {@code lineDelim} character is blank then all bytes
|
||||||
|
* will be printed on a single line.
|
||||||
|
* @param lineDelim The delimiter between lines
|
||||||
|
* @param itemDelim The delimiter between bytes
|
||||||
|
*
|
||||||
|
* @return The hexdump of the byte array
|
||||||
|
*/
|
||||||
|
private static String dumpHexBytes(byte[] data, int itemsPerLine,
|
||||||
|
String lineDelim, String itemDelim) {
|
||||||
|
return dumpHexBytes(ByteBuffer.wrap(data), itemsPerLine, lineDelim,
|
||||||
|
itemDelim);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String dumpHexBytes(ByteBuffer data, int itemsPerLine,
|
||||||
|
String lineDelim, String itemDelim) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (data != null) {
|
||||||
|
data.mark();
|
||||||
|
int i = 0;
|
||||||
|
while (data.remaining() > 0) {
|
||||||
|
if (i % itemsPerLine == 0 && i != 0) {
|
||||||
|
sb.append(lineDelim);
|
||||||
|
}
|
||||||
|
sb.append(String.format("%02X", data.get())).append(itemDelim);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
data.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue