提交 da6c04b0 编写于 作者: A asmotrak

8048596: Tests for AEAD ciphers

Reviewed-by: valeriep
上级 b9d354e3
此差异已折叠。
/*
* Copyright (c) 2007, 2015, 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.
*/
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
/*
* @test
* @bug 8048596
* @summary Check if GCMParameterSpec works as expected
*/
public class GCMParameterSpecTest {
private static final int[] IV_LENGTHS = { 96, 8, 1024 };
private static final int[] KEY_LENGTHS = { 128, 192, 256 };
private static final int[] DATA_LENGTHS = { 0, 128, 1024 };
private static final int[] AAD_LENGTHS = { 0, 128, 1024 };
private static final int[] TAG_LENGTHS = { 128, 120, 112, 104, 96 };
private static final int[] OFFSETS = { 0, 2, 5, 99 };
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final String TEMPLATE = "Test:\n tag = %d\n"
+ " IV length = %d\n data length = %d\n"
+ " AAD length = %d\n offset = %d\n keylength = %d\n";
private final byte[] IV;
private final byte[] IVO;
private final byte[] data;
private final byte[] AAD;
private final SecretKey key;
private final int tagLength;
private final int IVlength;
private final int offset;
/**
* Initialize IV, IV with offset, plain text, AAD and SecretKey
*
* @param keyLength length of a secret key
* @param tagLength tag length
* @param IVlength IV length
* @param offset offset in a buffer for IV
* @param textLength plain text length
* @param AADLength AAD length
*/
public GCMParameterSpecTest(int keyLength, int tagLength, int IVlength,
int offset, int textLength, int AADLength)
throws NoSuchAlgorithmException, NoSuchProviderException {
this.tagLength = tagLength; // save tag length
this.IVlength = IVlength; // save IV length
this.offset = offset; // save IV offset
// prepare IV
IV = Helper.generateBytes(IVlength);
// prepare IV with offset
IVO = new byte[this.IVlength + this.offset];
System.arraycopy(IV, 0, IVO, offset, this.IVlength);
// prepare data
data = Helper.generateBytes(textLength);
// prepare AAD
AAD = Helper.generateBytes(AADLength);
// init a secret key
KeyGenerator kg = KeyGenerator.getInstance("AES", "SunJCE");
kg.init(keyLength);
key = kg.generateKey();
}
/*
* Run the test for each key length, tag length, IV length, plain text
* length, AAD length and offset.
*/
public static void main(String[] args) throws Exception {
boolean success = true;
for (int k : KEY_LENGTHS) {
if (k > Cipher.getMaxAllowedKeyLength(TRANSFORMATION)) {
// skip this if this key length is larger than what's
// allowed in the jce jurisdiction policy files
continue;
}
for (int t : TAG_LENGTHS) {
for (int n : IV_LENGTHS) {
for (int p : DATA_LENGTHS) {
for (int a : AAD_LENGTHS) {
for (int o : OFFSETS) {
System.out.printf(TEMPLATE, t, n, p, a, o, k);
success &= new GCMParameterSpecTest(
k, t, n, o, p, a).doTest();
}
}
}
}
}
}
if (!success) {
throw new RuntimeException("At least one test case failed");
}
}
/*
* Run the test:
* - check if result of encryption of plain text is the same
* when parameters constructed with different GCMParameterSpec
* constructors are used
* - check if GCMParameterSpec.getTLen() is equal to actual tag length
* - check if ciphertext has the same length as plaintext
*/
private boolean doTest() throws Exception {
GCMParameterSpec spec1 = new GCMParameterSpec(tagLength, IV);
GCMParameterSpec spec2 = new GCMParameterSpec(tagLength, IVO, offset,
IVlength);
byte[] cipherText1 = getCipherTextBySpec(spec1);
if (cipherText1 == null) {
return false;
}
byte[] cipherText2 = getCipherTextBySpec(spec2);
if (cipherText2 == null) {
return false;
}
if (!Arrays.equals(cipherText1, cipherText2)) {
System.out.println("Cipher texts are different");
return false;
}
if (spec1.getTLen() != spec2.getTLen()) {
System.out.println("Tag lengths are not equal");
return false;
}
byte[] recoveredText1 = recoverCipherText(cipherText1, spec2);
if (recoveredText1 == null) {
return false;
}
byte[] recoveredText2 = recoverCipherText(cipherText2, spec1);
if (recoveredText2 == null) {
return false;
}
if (!Arrays.equals(recoveredText1, recoveredText2)) {
System.out.println("Recovered texts are different");
return false;
}
if (!Arrays.equals(recoveredText1, data)) {
System.out.println("Recovered and original texts are not equal");
return false;
}
return true;
}
/*
* Encrypt a plain text, and check if GCMParameterSpec.getIV()
* is equal to Cipher.getIV()
*/
private byte[] getCipherTextBySpec(GCMParameterSpec spec) throws Exception {
// init a cipher
Cipher cipher = createCipher(Cipher.ENCRYPT_MODE, spec);
cipher.updateAAD(AAD);
byte[] cipherText = cipher.doFinal(data);
// check IVs
if (!Arrays.equals(cipher.getIV(), spec.getIV())) {
System.out.println("IV in parameters is incorrect");
return null;
}
if (spec.getTLen() != (cipherText.length - data.length) * 8) {
System.out.println("Tag length is incorrect");
return null;
}
return cipherText;
}
private byte[] recoverCipherText(byte[] cipherText, GCMParameterSpec spec)
throws Exception {
// init a cipher
Cipher cipher = createCipher(Cipher.DECRYPT_MODE, spec);
// check IVs
if (!Arrays.equals(cipher.getIV(), spec.getIV())) {
System.out.println("IV in parameters is incorrect");
return null;
}
cipher.updateAAD(AAD);
return cipher.doFinal(cipherText);
}
private Cipher createCipher(int mode, GCMParameterSpec spec)
throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION, "SunJCE");
cipher.init(mode, key, spec);
return cipher;
}
}
/*
* Copyright (c) 2015, 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.
*/
public class Helper {
public static byte[] generateBytes(int length) {
byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) {
bytes[i] = (byte) (i % 256);
}
return bytes;
}
}
/*
* Copyright (c) 2007, 2015, 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.
*/
import java.security.AlgorithmParameters;
import java.security.Key;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
/*
* @test
* @bug 8048596
* @summary Check if a key wrapper works properly with GCM mode
*/
public class KeyWrapper {
static final String AES = "AES";
static final String TRANSFORMATION = "AES/GCM/NoPadding";
static final String PROVIDER = "SunJCE";
static final int KEY_LENGTH = 128;
public static void main(String argv[]) throws Exception {
doTest(PROVIDER, TRANSFORMATION);
}
private static void doTest(String provider, String algo) throws Exception {
SecretKey key;
SecretKey keyToWrap;
// init a secret Key
KeyGenerator kg = KeyGenerator.getInstance(AES, PROVIDER);
kg.init(KEY_LENGTH);
key = kg.generateKey();
keyToWrap = kg.generateKey();
// initialization
Cipher cipher = Cipher.getInstance(algo, provider);
cipher.init(Cipher.WRAP_MODE, key);
AlgorithmParameters params = cipher.getParameters();
// wrap the key
byte[] keyWrapper = cipher.wrap(keyToWrap);
try {
// check if we can't wrap it again with the same key/IV
keyWrapper = cipher.wrap(keyToWrap);
throw new RuntimeException(
"FAILED: expected IllegalStateException hasn't "
+ "been thrown ");
} catch (IllegalStateException ise) {
System.out.println(ise.getMessage());
System.out.println("Expected exception");
}
// unwrap the key
cipher.init(Cipher.UNWRAP_MODE, key, params);
cipher.unwrap(keyWrapper, algo, Cipher.SECRET_KEY);
// check if we can unwrap second time
Key unwrapKey = cipher.unwrap(keyWrapper, algo, Cipher.SECRET_KEY);
if (!Arrays.equals(keyToWrap.getEncoded(), unwrapKey.getEncoded())) {
throw new RuntimeException(
"FAILED: original and unwrapped keys are not equal");
}
}
}
/*
* Copyright (c) 2007, 2015, 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.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
/*
* @test
* @bug 8048596
* @summary Test CICO AEAD read/write/skip operations
*/
public class ReadWriteSkip {
static enum BufferType {
BYTE_ARRAY_BUFFERING, INT_BYTE_BUFFERING
}
static final int KEY_LENGTHS[] = {128, 192, 256};
static final int TXT_LENGTHS[] = {800, 0};
static final int AAD_LENGTHS[] = {0, 100};
static final int BLOCK = 50;
static final int SAVE = 45;
static final int DISCARD = BLOCK - SAVE;
static final String PROVIDER = "SunJCE";
static final String AES = "AES";
static final String GCM = "GCM";
static final String PADDING = "NoPadding";
static final String TRANSFORM = AES + "/" + GCM + "/" + PADDING;
final SecretKey key;
final byte[] plaintext;
final byte[] AAD;
final int textLength;;
final int keyLength;
Cipher encryptCipher;
Cipher decryptCipher;
CipherInputStream ciInput;
public static void main(String[] args) throws Exception {
boolean success = true;
for (int keyLength : KEY_LENGTHS) {
if (keyLength > Cipher.getMaxAllowedKeyLength(TRANSFORM)) {
// skip this if this key length is larger than what's
// configured in the jce jurisdiction policy files
continue;
}
for (int textLength : TXT_LENGTHS) {
for (int AADLength : AAD_LENGTHS) {
System.out.println("Key length = " + keyLength
+ ", text length = " + textLength
+ ", AAD length = " + AADLength);
try {
run(keyLength, textLength, AADLength);
System.out.println("Test case passed");
} catch (Exception e) {
System.out.println("Test case failed: " + e);
success = false;
}
}
}
}
if (!success) {
throw new RuntimeException("At least one test case failed");
}
System.out.println("Test passed");
}
ReadWriteSkip(int keyLength, int textLength, int AADLength)
throws Exception {
this.keyLength = keyLength;
this.textLength = textLength;
// init AAD
this.AAD = Helper.generateBytes(AADLength);
// init a secret Key
KeyGenerator kg = KeyGenerator.getInstance(AES, PROVIDER);
kg.init(this.keyLength);
this.key = kg.generateKey();
this.plaintext = Helper.generateBytes(textLength);
}
final void doTest(BufferType type) throws Exception {
// init ciphers
encryptCipher = createCipher(Cipher.ENCRYPT_MODE);
decryptCipher = createCipher(Cipher.DECRYPT_MODE);
// init cipher input stream
ciInput = new CipherInputStream(new ByteArrayInputStream(plaintext),
encryptCipher);
runTest(type);
}
void runTest(BufferType type) throws Exception {}
private Cipher createCipher(int mode) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(TRANSFORM, PROVIDER);
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(Cipher.ENCRYPT_MODE, key);
} else {
if (encryptCipher != null) {
cipher.init(Cipher.DECRYPT_MODE, key,
encryptCipher.getParameters());
} else {
throw new RuntimeException("Can't create a cipher");
}
}
cipher.updateAAD(AAD);
return cipher;
}
/*
* Run test cases
*/
static void run(int keyLength, int textLength, int AADLength)
throws Exception {
new ReadWriteTest(keyLength, textLength, AADLength)
.doTest(BufferType.BYTE_ARRAY_BUFFERING);
new ReadWriteTest(keyLength, textLength, AADLength)
.doTest(BufferType.INT_BYTE_BUFFERING);
new SkipTest(keyLength, textLength, AADLength)
.doTest(BufferType.BYTE_ARRAY_BUFFERING);
new SkipTest(keyLength, textLength, AADLength)
.doTest(BufferType.INT_BYTE_BUFFERING);
}
static void check(byte[] first, byte[] second) {
if (!Arrays.equals(first, second)) {
throw new RuntimeException("Arrays are not equal");
}
}
/*
* CICO AEAD read/write functional test.
*
* Check if encrypt/decrypt operations work correctly.
*
* Test scenario:
* - initializes plain text
* - for given AEAD algorithm instantiates encrypt and decrypt Ciphers
* - instantiates CipherInputStream with the encrypt Cipher
* - instantiates CipherOutputStream with the decrypt Cipher
* - performs reading from the CipherInputStream (encryption data)
* and writing to the CipherOutputStream (decryption). As a result,
* output of the CipherOutputStream should be equal
* with original plain text
* - check if the original plain text is equal to output
* of the CipherOutputStream
* - if it is equal the test passes, otherwise it fails
*/
static class ReadWriteTest extends ReadWriteSkip {
public ReadWriteTest(int keyLength, int textLength, int AADLength)
throws Exception {
super(keyLength, textLength, AADLength);
}
@Override
public void runTest(BufferType bufType) throws IOException,
GeneralSecurityException {
ByteArrayOutputStream baOutput = new ByteArrayOutputStream();
try (CipherOutputStream ciOutput = new CipherOutputStream(baOutput,
decryptCipher)) {
if (bufType == BufferType.BYTE_ARRAY_BUFFERING) {
doByteTest(ciOutput);
} else {
doIntTest(ciOutput);
}
}
check(plaintext, baOutput.toByteArray());
}
/*
* Implements byte array buffering type test case
*/
public void doByteTest(CipherOutputStream out) throws IOException {
byte[] buffer = Helper.generateBytes(textLength + 1);
int len = ciInput.read(buffer);
while (len != -1) {
out.write(buffer, 0, len);
len = ciInput.read(buffer);
}
}
/*
* Implements integer buffering type test case
*/
public void doIntTest(CipherOutputStream out) throws IOException {
int buffer = ciInput.read();
while (buffer != -1) {
out.write(buffer);
buffer = ciInput.read();
}
}
}
/*
* CICO AEAD SKIP functional test.
*
* Checks if the encrypt/decrypt operations work correctly
* when skip() method is used.
*
* Test scenario:
* - initializes a plain text
* - initializes ciphers
* - initializes cipher streams
* - split plain text to TEXT_SIZE/BLOCK blocks
* - read from CipherInputStream2 one block at time
* - the last DISCARD = BLOCK - SAVE bytes are skipping for each block
* - therefore, plain text data go through CipherInputStream1 (encrypting)
* and CipherInputStream2 (decrypting)
* - as a result, output should equal to the original text
* except DISCART byte for each block
* - check result buffers
*/
static class SkipTest extends ReadWriteSkip {
private final int numberOfBlocks;
private final byte[] outputText;
public SkipTest(int keyLength, int textLength, int AADLength)
throws Exception {
super(keyLength, textLength, AADLength);
numberOfBlocks = this.textLength / BLOCK;
outputText = new byte[numberOfBlocks * SAVE];
}
private void doByteTest(int blockNum, CipherInputStream cis)
throws IOException {
int index = blockNum * SAVE;
int len = cis.read(outputText, index, SAVE);
index += len;
int read = 0;
while (len != SAVE && read != -1) {
read = cis.read(outputText, index, SAVE - len);
len += read;
index += read;
}
}
private void doIntTest(int blockNum, CipherInputStream cis)
throws IOException {
int i = blockNum * SAVE;
for (int j = 0; j < SAVE && i < outputText.length; j++, i++) {
int b = cis.read();
if (b != -1) {
outputText[i] = (byte) b;
}
}
}
@Override
public void runTest(BufferType type) throws IOException,
NoSuchAlgorithmException {
try (CipherInputStream cis = new CipherInputStream(ciInput,
decryptCipher)) {
for (int i = 0; i < numberOfBlocks; i++) {
if (type == BufferType.BYTE_ARRAY_BUFFERING) {
doByteTest(i, cis);
} else {
doIntTest(i, cis);
}
if (cis.available() >= DISCARD) {
cis.skip(DISCARD);
} else {
for (int k = 0; k < DISCARD; k++) {
cis.read();
}
}
}
}
byte[] expectedText = new byte[numberOfBlocks * SAVE];
for (int m = 0; m < numberOfBlocks; m++) {
for (int n = 0; n < SAVE; n++) {
expectedText[m * SAVE + n] = plaintext[m * BLOCK + n];
}
}
check(expectedText, outputText);
}
}
}
/*
* Copyright (c) 2007, 2015, 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.
*/
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.Provider;
import java.security.Security;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
/*
* @test
* @bug 8048596
* @summary Check if AEAD operations work correctly when buffers used
* for storing plain text and cipher text are overlapped or the same
*/
public class SameBuffer {
private static final String PROVIDER = "SunJCE";
private static final String AES = "AES";
private static final String GCM = "GCM";
private static final String PADDING = "NoPadding";
private static final int OFFSET = 2;
private static final int OFFSETS = 4;
private static final int KEY_LENGTHS[] = { 128, 192, 256 };
private static final int TEXT_LENGTHS[] = { 0, 1024 };
private static final int AAD_LENGTHS[] = { 0, 1024 };
private final Provider provider;
private final SecretKey key;
private final String transformation;
private final int textLength;
private final int AADLength;
/**
* Constructor of the test
*
* @param provider security provider
* @param keyStrength key length
* @param textLength length of data
* @param AADLength AAD length
*/
public SameBuffer(Provider provider, String algorithm, String mode,
String padding, int keyStrength, int textLength, int AADLength)
throws Exception {
// init a secret key
KeyGenerator kg = KeyGenerator.getInstance(algorithm, provider);
kg.init(keyStrength);
key = kg.generateKey();
this.transformation = algorithm + "/" + mode + "/" + padding;
this.provider = provider;
this.textLength = textLength;
this.AADLength = AADLength;
}
public static void main(String[] args) throws Exception {
Provider p = Security.getProvider(PROVIDER);
for (int keyLength : KEY_LENGTHS) {
for (int textLength : TEXT_LENGTHS) {
for (int AADLength : AAD_LENGTHS) {
for (int i = 0; i < OFFSETS; i++) {
// try different offsets
int offset = i * OFFSET;
runTest(p, AES, GCM, PADDING, keyLength, textLength,
AADLength, offset);
}
}
}
}
}
/*
* Run single test case with given parameters
*/
static void runTest(Provider p, String algo, String mode,
String padding, int keyLength, int textLength, int AADLength,
int offset) throws Exception {
System.out.println("Testing " + keyLength + " key length; "
+ textLength + " text lenght; " + AADLength + " AAD length; "
+ offset + " offset");
if (keyLength > Cipher.getMaxAllowedKeyLength(algo)) {
// skip this if this key length is larger than what's
// configured in the jce jurisdiction policy files
return;
}
SameBuffer test = new SameBuffer(p, algo, mode,
padding, keyLength, textLength, AADLength);
/*
* There are four test cases:
* 1. AAD and text are placed in separated byte arrays
* 2. AAD and text are placed in the same byte array
* 3. AAD and text are placed in separated byte buffers
* 4. AAD and text are placed in the same byte buffer
*/
Cipher ci = test.createCipher(Cipher.ENCRYPT_MODE, null);
AlgorithmParameters params = ci.getParameters();
test.doTestWithSeparateArrays(offset, params);
test.doTestWithSameArrays(offset, params);
test.doTestWithSeparatedBuffer(offset, params);
test.doTestWithSameBuffer(offset, params);
}
/*
* Run the test in case when AAD and text are placed in separated byte
* arrays.
*/
private void doTestWithSeparateArrays(int offset,
AlgorithmParameters params) throws Exception {
// prepare buffers to test
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
int outputLength = c.getOutputSize(textLength);
int outputBufSize = outputLength + offset * 2;
byte[] inputText = Helper.generateBytes(outputBufSize);
byte[] AAD = Helper.generateBytes(AADLength);
// do the test
runGCMWithSeparateArray(Cipher.ENCRYPT_MODE, AAD, inputText, offset * 2,
textLength, offset, params);
int tagLength = c.getParameters()
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
runGCMWithSeparateArray(Cipher.DECRYPT_MODE, AAD, inputText, offset,
textLength + tagLength, offset, params);
}
/**
* Run the test in case when AAD and text are placed in the same byte
* array.
*/
private void doTestWithSameArrays(int offset, AlgorithmParameters params)
throws Exception {
// prepare buffers to test
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
int outputLength = c.getOutputSize(textLength);
int outputBufSize = AADLength + outputLength + offset * 2;
byte[] AAD_and_text = Helper.generateBytes(outputBufSize);
// do the test
runGCMWithSameArray(Cipher.ENCRYPT_MODE, AAD_and_text, AADLength + offset,
textLength, params);
int tagLength = c.getParameters()
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
runGCMWithSameArray(Cipher.DECRYPT_MODE, AAD_and_text, AADLength + offset,
textLength + tagLength, params);
}
/*
* Run the test in case when AAD and text are placed in separated ByteBuffer
*/
private void doTestWithSeparatedBuffer(int offset,
AlgorithmParameters params) throws Exception {
// prepare AAD byte buffers to test
byte[] AAD = Helper.generateBytes(AADLength);
ByteBuffer AAD_Buf = ByteBuffer.allocate(AADLength);
AAD_Buf.put(AAD, 0, AAD.length);
AAD_Buf.flip();
// prepare text byte buffer to encrypt/decrypt
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
int outputLength = c.getOutputSize(textLength);
int outputBufSize = outputLength + offset;
byte[] inputText = Helper.generateBytes(outputBufSize);
ByteBuffer plainTextBB = ByteBuffer.allocateDirect(inputText.length);
plainTextBB.put(inputText);
plainTextBB.position(offset);
plainTextBB.limit(offset + textLength);
// do test
runGCMWithSeparateBuffers(Cipher.ENCRYPT_MODE, AAD_Buf, plainTextBB, offset,
textLength, params);
int tagLength = c.getParameters()
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
plainTextBB.position(offset);
plainTextBB.limit(offset + textLength + tagLength);
runGCMWithSeparateBuffers(Cipher.DECRYPT_MODE, AAD_Buf, plainTextBB, offset,
textLength + tagLength, params);
}
/*
* Run the test in case when AAD and text are placed in the same ByteBuffer
*/
private void doTestWithSameBuffer(int offset, AlgorithmParameters params)
throws Exception {
// calculate output length
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
int outputLength = c.getOutputSize(textLength);
// prepare byte buffer contained AAD and plain text
int bufSize = AADLength + offset + outputLength;
byte[] AAD_and_Text = Helper.generateBytes(bufSize);
ByteBuffer AAD_and_Text_Buf = ByteBuffer.allocate(bufSize);
AAD_and_Text_Buf.put(AAD_and_Text, 0, AAD_and_Text.length);
// do test
runGCMWithSameBuffer(Cipher.ENCRYPT_MODE, AAD_and_Text_Buf, offset,
textLength, params);
int tagLength = c.getParameters()
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
AAD_and_Text_Buf.limit(AADLength + offset + textLength + tagLength);
runGCMWithSameBuffer(Cipher.DECRYPT_MODE, AAD_and_Text_Buf, offset,
textLength + tagLength, params);
}
/*
* Execute GCM encryption/decryption of a text placed in a byte array.
* AAD is placed in the separated byte array.
* Data are processed twice:
* - in a separately allocated buffer
* - in the text buffer
* Check if two results are equal
*/
private void runGCMWithSeparateArray(int mode, byte[] AAD, byte[] text,
int txtOffset, int lenght, int offset, AlgorithmParameters params)
throws Exception {
// first, generate the cipher text at an allocated buffer
Cipher cipher = createCipher(mode, params);
cipher.updateAAD(AAD);
byte[] outputText = cipher.doFinal(text, txtOffset, lenght);
// new cipher for encrypt operation
Cipher anotherCipher = createCipher(mode, params);
anotherCipher.updateAAD(AAD);
// next, generate cipher text again at the same buffer of plain text
int myoff = offset;
int off = anotherCipher.update(text, txtOffset, lenght, text, myoff);
anotherCipher.doFinal(text, myoff + off);
// check if two resutls are equal
if (!isEqual(text, myoff, outputText, 0, outputText.length)) {
throw new RuntimeException("Two results not equal, mode:" + mode);
}
}
/*
* Execute GCM encrption/decryption of a text. The AAD and text to process
* are placed in the same byte array. Data are processed twice:
* - in a separetly allocated buffer
* - in a buffer that shares content of the AAD_and_Text_BA
* Check if two results are equal
*/
private void runGCMWithSameArray(int mode, byte[] array, int txtOffset,
int length, AlgorithmParameters params) throws Exception {
// first, generate cipher text at an allocated buffer
Cipher cipher = createCipher(mode, params);
cipher.updateAAD(array, 0, AADLength);
byte[] outputText = cipher.doFinal(array, txtOffset, length);
// new cipher for encrypt operation
Cipher anotherCipher = createCipher(mode, params);
anotherCipher.updateAAD(array, 0, AADLength);
// next, generate cipher text again at the same buffer of plain text
int off = anotherCipher.update(array, txtOffset, length,
array, txtOffset);
anotherCipher.doFinal(array, txtOffset + off);
// check if two results are equal or not
if (!isEqual(array, txtOffset, outputText, 0,
outputText.length)) {
throw new RuntimeException(
"Two results are not equal, mode:" + mode);
}
}
/*
* Execute GCM encryption/decryption of textBB. AAD and text to process are
* placed in different byte buffers. Data are processed twice:
* - in a separately allocated buffer
* - in a buffer that shares content of the textBB
* Check if results are equal
*/
private void runGCMWithSeparateBuffers(int mode, ByteBuffer buffer,
ByteBuffer textBB, int txtOffset, int dataLength,
AlgorithmParameters params) throws Exception {
// take offset into account
textBB.position(txtOffset);
textBB.mark();
// first, generate the cipher text at an allocated buffer
Cipher cipher = createCipher(mode, params);
cipher.updateAAD(buffer);
buffer.flip();
ByteBuffer outBB = ByteBuffer.allocateDirect(
cipher.getOutputSize(dataLength));
cipher.doFinal(textBB, outBB);// get cipher text in outBB
outBB.flip();
// restore positions
textBB.reset();
// next, generate cipher text again in a buffer that shares content
Cipher anotherCipher = createCipher(mode, params);
anotherCipher.updateAAD(buffer);
buffer.flip();
ByteBuffer buf2 = textBB.duplicate(); // buf2 shares textBuf context
buf2.limit(txtOffset + anotherCipher.getOutputSize(dataLength));
int dataProcessed2 = anotherCipher.doFinal(textBB, buf2);
buf2.position(txtOffset);
buf2.limit(txtOffset + dataProcessed2);
if (!buf2.equals(outBB)) {
throw new RuntimeException(
"Two results are not equal, mode:" + mode);
}
}
/*
* Execute GCM encryption/decryption of text. AAD and a text to process are
* placed in the same buffer. Data is processed twice:
* - in a separately allocated buffer
* - in a buffer that shares content of the AAD_and_Text_BB
*/
private void runGCMWithSameBuffer(int mode, ByteBuffer buffer,
int txtOffset, int length, AlgorithmParameters params)
throws Exception {
// allocate a separate buffer
Cipher cipher = createCipher(mode, params);
ByteBuffer outBB = ByteBuffer.allocateDirect(
cipher.getOutputSize(length));
// first, generate the cipher text at an allocated buffer
buffer.flip();
buffer.limit(AADLength);
cipher.updateAAD(buffer);
buffer.limit(AADLength + txtOffset + length);
buffer.position(AADLength + txtOffset);
cipher.doFinal(buffer, outBB);
outBB.flip(); // cipher text in outBB
// next, generate cipherText again in the same buffer
Cipher anotherCipher = createCipher(mode, params);
buffer.flip();
buffer.limit(AADLength);
anotherCipher.updateAAD(buffer);
buffer.limit(AADLength + txtOffset + length);
buffer.position(AADLength + txtOffset);
// share textBuf context
ByteBuffer buf2 = buffer.duplicate();
buf2.limit(AADLength + txtOffset + anotherCipher.getOutputSize(length));
int dataProcessed2 = anotherCipher.doFinal(buffer, buf2);
buf2.position(AADLength + txtOffset);
buf2.limit(AADLength + txtOffset + dataProcessed2);
if (!buf2.equals(outBB)) {
throw new RuntimeException(
"Two results are not equal, mode:" + mode);
}
}
private boolean isEqual(byte[] A, int offsetA, byte[] B, int offsetB,
int bytesToCompare) {
System.out.println("offsetA: " + offsetA + " offsetB: " + offsetA
+ " bytesToCompare: " + bytesToCompare);
for (int i = 0; i < bytesToCompare; i++) {
int setA = i + offsetA;
int setB = i + offsetB;
if (setA > A.length - 1 || setB > B.length - 1
|| A[setA] != B[setB]) {
return false;
}
}
return true;
}
/*
* Creates a Cipher object for testing: for encryption it creates new Cipher
* based on previously saved parameters (it is prohibited to use the same
* Cipher twice for encription during GCM mode), or returns initiated
* existing Cipher.
*/
private Cipher createCipher(int mode, AlgorithmParameters params)
throws Exception {
Cipher cipher = Cipher.getInstance(transformation, provider);
if (Cipher.ENCRYPT_MODE == mode) {
// initiate it with the saved parameters
if (params != null) {
cipher.init(Cipher.ENCRYPT_MODE, key, params);
} else {
// intiate the cipher and save parameters
cipher.init(Cipher.ENCRYPT_MODE, key);
}
} else if (cipher != null) {
cipher.init(Cipher.DECRYPT_MODE, key, params);
} else {
throw new RuntimeException("Can't create cipher");
}
return cipher;
}
}
/*
* Copyright (c) 2007, 2015, 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.
*/
import java.security.AlgorithmParameters;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SealedObject;
/*
* @test
* @bug 8048596
* @summary Check if the seal/unseal feature works properly in AEAD/GCM mode.
*/
public class SealedObjectTest {
private static final String AES = "AES";
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final String PROVIDER = "SunJCE";
private static final int KEY_LENGTH = 128;
public static void main(String[] args) throws Exception {
doTest();
}
/*
* Run the test:
* - init a cipher with AES/GCM/NoPadding transformation
* - seal an object
* - check if we can't seal it again with the same key/IV
* - unseal the object using different methods of SealedObject class
* - check if the original and sealed objects are equal
*/
static void doTest() throws Exception {
// init a secret Key
KeyGenerator kg = KeyGenerator.getInstance(AES, PROVIDER);
kg.init(KEY_LENGTH);
SecretKey key = kg.generateKey();
// initialization
Cipher cipher = Cipher.getInstance(TRANSFORMATION, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key);
AlgorithmParameters params = cipher.getParameters();
// seal an object
SealedObject so = new SealedObject(key, cipher);
try {
// check if we can't seal it again with the same key/IV
so = new SealedObject(key, cipher);
throw new RuntimeException(
"FAILED: expected IllegalStateException hasn't "
+ "been thrown");
} catch (IllegalStateException ise) {
System.out.println("Expected exception when seal it again with"
+ " the same key/IV: " + ise);
}
// unseal the object using getObject(Cipher) and compare
cipher.init(Cipher.DECRYPT_MODE, key, params);
SecretKey unsealedKey = (SecretKey) so.getObject(cipher);
assertKeysSame(unsealedKey, key, "SealedObject.getObject(Cipher)");
// unseal the object using getObject(Key) and compare
unsealedKey = (SecretKey) so.getObject(key);
assertKeysSame(unsealedKey, key, "SealedObject.getObject(Key)");
// unseal the object using getObject(Key, String) and compare
unsealedKey = (SecretKey) so.getObject(key, PROVIDER);
assertKeysSame(unsealedKey, key,
"SealedObject.getObject(Key, String)");
}
/**
* Compare two SecretKey objects.
*
* @param key1 first key
* @param key2 second key
* @param meth method that was used for unsealing the SecretKey object
* @return true if key1 and key2 are the same, false otherwise.
*/
static void assertKeysSame(SecretKey key1, SecretKey key2, String meth) {
if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
throw new RuntimeException(
"FAILED: original and unsealed objects aren't the same for "
+ meth);
}
}
}
/*
* Copyright (c) 2007, 2015, 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.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
/*
* @test
* @bug 8048596
* @summary Check if wrong or empty AAD is rejected
*/
public class WrongAAD {
private static final String PROVIDER = "SunJCE";
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final int TEXT_SIZE = 800;
private static final int KEY_SIZE = 128;
private static final int AAD_SIZE = 128;
private final SecretKey key;
private final byte[] plainText;
private final Cipher encryptCipher;
public WrongAAD() throws Exception {
// init a secret key
KeyGenerator kg = KeyGenerator.getInstance("AES", PROVIDER);
kg.init(KEY_SIZE);
key = kg.generateKey();
// generate a plain text
plainText = Helper.generateBytes(TEXT_SIZE);
// init AADs
byte[] AAD = Helper.generateBytes(AAD_SIZE);
// init a cipher
encryptCipher = createCipher(Cipher.ENCRYPT_MODE, null);
encryptCipher.updateAAD(AAD);
}
public static void main(String[] args) throws Exception {
WrongAAD test = new WrongAAD();
test.decryptWithEmptyAAD();
test.decryptWithWrongAAD();
}
/*
* Attempt to decrypt a cipher text using Cipher object
* initialized without AAD used for encryption.
*/
private void decryptWithEmptyAAD() throws Exception {
System.out.println("decryptWithEmptyAAD() started");
// initialize it with empty AAD to get exception during decryption
Cipher decryptCipher = createCipher(Cipher.DECRYPT_MODE,
encryptCipher.getParameters());
try (ByteArrayOutputStream baOutput = new ByteArrayOutputStream();
CipherOutputStream ciOutput = new CipherOutputStream(baOutput,
decryptCipher)) {
if (decrypt(ciOutput, baOutput)) {
throw new RuntimeException(
"Decryption has been perfomed successfully in"
+ " spite of the decrypt Cipher has NOT been"
+ " initialized with AAD");
}
}
System.out.println("decryptWithEmptyAAD() passed");
}
/*
* Attempt to decrypt the cipher text using Cipher object
* initialized with some fake AAD.
*/
private void decryptWithWrongAAD() throws Exception {
System.out.println("decrypt with wrong AAD");
// initialize it with wrong AAD to get an exception during decryption
Cipher decryptCipher = createCipher(Cipher.DECRYPT_MODE,
encryptCipher.getParameters());
byte[] someAAD = Helper.generateBytes(AAD_SIZE + 1);
decryptCipher.updateAAD(someAAD);
// init output stream
try (ByteArrayOutputStream baOutput = new ByteArrayOutputStream();
CipherOutputStream ciOutput = new CipherOutputStream(baOutput,
decryptCipher);) {
if (decrypt(ciOutput, baOutput)) {
throw new RuntimeException(
"A decryption has been perfomed successfully in"
+ " spite of the decrypt Cipher has been"
+ " initialized with fake AAD");
}
}
System.out.println("Passed");
}
private boolean decrypt(CipherOutputStream ciOutput,
ByteArrayOutputStream baOutput) throws IOException {
try (ByteArrayInputStream baInput = new ByteArrayInputStream(plainText);
CipherInputStream ciInput = new CipherInputStream(baInput,
encryptCipher)) {
byte[] buffer = new byte[TEXT_SIZE];
int len = ciInput.read(buffer);
while (len != -1) {
ciOutput.write(buffer, 0, len);
len = ciInput.read(buffer);
}
ciOutput.flush();
byte[] recoveredText = baOutput.toByteArray();
System.out.println("recoveredText: " + new String(recoveredText));
/*
* See bug 8012900, AEADBadTagException is swalloed by CI/CO streams
* If recovered text is empty, than decryption failed
*/
if (recoveredText.length == 0) {
return false;
}
return Arrays.equals(plainText, recoveredText);
} catch (IllegalStateException e) {
System.out.println("Expected IllegalStateException: "
+ e.getMessage());
e.printStackTrace(System.out);
return false;
}
}
private Cipher createCipher(int mode, AlgorithmParameters params)
throws NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(TRANSFORMATION, PROVIDER);
if (params != null) {
cipher.init(mode, key, params);
} else {
cipher.init(mode, key);
}
return cipher;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册