8153029: ChaCha20 Cipher Implementation
Add the ChaCha20 and ChaCha20-Poly1305 Cipher implementations Reviewed-by: mullan
This commit is contained in:
parent
89251ae9a3
commit
fcb805f9a6
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.
|
||||
*
|
||||
* 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) {
|
||||
core = new KeyGeneratorCore(algoName, len);
|
||||
}
|
||||
@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) {
|
||||
core.implInit(keySize, random);
|
||||
}
|
||||
@Override
|
||||
protected SecretKey engineGenerateKey() {
|
||||
return core.implGenerateKey();
|
||||
}
|
||||
@ -153,13 +157,16 @@ final class KeyGeneratorCore {
|
||||
public RC2KeyGenerator() {
|
||||
core = new KeyGeneratorCore("RC2", 128);
|
||||
}
|
||||
@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 < 40) || (keySize > 1024)) {
|
||||
throw new InvalidParameterException("Key length for RC2"
|
||||
@ -167,6 +174,7 @@ final class KeyGeneratorCore {
|
||||
}
|
||||
core.implInit(keySize, random);
|
||||
}
|
||||
@Override
|
||||
protected SecretKey engineGenerateKey() {
|
||||
return core.implGenerateKey();
|
||||
}
|
||||
@ -178,13 +186,16 @@ final class KeyGeneratorCore {
|
||||
public ARCFOURKeyGenerator() {
|
||||
core = new KeyGeneratorCore("ARCFOUR", 128);
|
||||
}
|
||||
@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 < 40) || (keySize > 1024)) {
|
||||
throw new InvalidParameterException("Key length for ARCFOUR"
|
||||
@ -192,9 +203,38 @@ final class KeyGeneratorCore {
|
||||
}
|
||||
core.implInit(keySize, random);
|
||||
}
|
||||
@Override
|
||||
protected SecretKey engineGenerateKey() {
|
||||
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)
|
||||
*
|
||||
* - ChaCha20 (Stream cipher only and in AEAD mode with Poly1305)
|
||||
*
|
||||
* - Cipher modes ECB, CBC, CFB, OFB, PCBC, CTR, and CTS for all block ciphers
|
||||
* and mode GCM for AES cipher
|
||||
*
|
||||
@ -77,7 +79,7 @@ public final class SunJCE extends Provider {
|
||||
|
||||
private static final String info = "SunJCE Provider " +
|
||||
"(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_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 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
|
||||
*/
|
||||
@ -361,6 +372,10 @@ public final class SunJCE extends Provider {
|
||||
"ARCFOURKeyGenerator");
|
||||
put("Alg.Alias.KeyGenerator.RC4", "ARCFOUR");
|
||||
|
||||
put("KeyGenerator.ChaCha20",
|
||||
"com.sun.crypto.provider.KeyGeneratorCore$" +
|
||||
"ChaCha20KeyGenerator");
|
||||
|
||||
put("KeyGenerator.HmacMD5",
|
||||
"com.sun.crypto.provider.HmacMD5KeyGenerator");
|
||||
|
||||
@ -541,6 +556,9 @@ public final class SunJCE extends Provider {
|
||||
put("AlgorithmParameters.OAEP",
|
||||
"com.sun.crypto.provider.OAEPParameters");
|
||||
|
||||
put("AlgorithmParameters.ChaCha20-Poly1305",
|
||||
"com.sun.crypto.provider.ChaCha20Poly1305Parameters");
|
||||
|
||||
/*
|
||||
* Key factories
|
||||
*/
|
||||
|
@ -111,7 +111,7 @@ import sun.security.jca.*;
|
||||
* encryption with a given key. When IVs are repeated for GCM
|
||||
* encryption, such usages are subject to forgery attacks. Thus, after
|
||||
* 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>
|
||||
* GCMParameterSpec s = ...;
|
||||
* cipher.init(..., s);
|
||||
@ -131,6 +131,13 @@ import sun.security.jca.*;
|
||||
* ...
|
||||
*
|
||||
* </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
|
||||
* the following standard {@code Cipher} transformations with the keysizes
|
||||
* 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…
Reference in New Issue
Block a user