Implement AES/GCM on top of AES/CTR
Older JDK's do not have built-in AES/GCM but they do have AES/CTR.
This commit is contained in:
parent
7901c7df23
commit
8b83fc5c27
204
NoiseJava/src/com/southernstorm/noise/crypto/GHASH.java
Normal file
204
NoiseJava/src/com/southernstorm/noise/crypto/GHASH.java
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.southernstorm.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Implementation of the GHASH primitive for GCM.
|
||||
*/
|
||||
public final class GHASH implements Destroyable {
|
||||
|
||||
private long[] H;
|
||||
private byte[] Y;
|
||||
int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new GHASH object.
|
||||
*/
|
||||
public GHASH()
|
||||
{
|
||||
H = new long [2];
|
||||
Y = new byte [16];
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this GHASH object with a new key.
|
||||
*
|
||||
* @param key The key, which must contain at least 16 bytes.
|
||||
* @param offset The offset of the first key byte.
|
||||
*/
|
||||
public void reset(byte[] key, int offset)
|
||||
{
|
||||
H[0] = readBigEndian(key, offset);
|
||||
H[1] = readBigEndian(key, offset + 8);
|
||||
Arrays.fill(Y, (byte)0);
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the GHASH object but retains the previous key.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
Arrays.fill(Y, (byte)0);
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this GHASH object with more data.
|
||||
*
|
||||
* @param data Buffer containing the data.
|
||||
* @param offset Offset of the first data byte in the buffer.
|
||||
* @param length The number of bytes from the buffer to hash.
|
||||
*/
|
||||
public void update(byte[] data, int offset, int length)
|
||||
{
|
||||
while (length > 0) {
|
||||
int size = 16 - posn;
|
||||
if (size > length)
|
||||
size = length;
|
||||
for (int index = 0; index < size; ++index)
|
||||
Y[posn + index] ^= data[offset + index];
|
||||
posn += size;
|
||||
length -= size;
|
||||
offset += size;
|
||||
if (posn == 16) {
|
||||
GF128_mul(Y, H);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the GHASH process and returns the tag.
|
||||
*
|
||||
* @param tag Buffer to receive the tag.
|
||||
* @param offset Offset of the first byte of the tag.
|
||||
* @param length The length of the tag, which must be less
|
||||
* than or equal to 16.
|
||||
*/
|
||||
public void finish(byte[] tag, int offset, int length)
|
||||
{
|
||||
pad();
|
||||
System.arraycopy(Y, 0, tag, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the input to a 16-byte boundary.
|
||||
*/
|
||||
public void pad()
|
||||
{
|
||||
if (posn != 0) {
|
||||
// Padding involves XOR'ing the rest of state->Y with zeroes,
|
||||
// which does nothing. Immediately process the next chunk.
|
||||
GF128_mul(Y, H);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the input to a 16-byte boundary and then adds a block
|
||||
* containing the AD and data lengths.
|
||||
*
|
||||
* @param adLen Length of the associated data in bytes.
|
||||
* @param dataLen Length of the data in bytes.
|
||||
*/
|
||||
public void pad(long adLen, long dataLen)
|
||||
{
|
||||
byte[] temp = new byte [16];
|
||||
try {
|
||||
pad();
|
||||
writeBigEndian(temp, 0, adLen * 8);
|
||||
writeBigEndian(temp, 8, dataLen * 8);
|
||||
update(temp, 0, 16);
|
||||
} finally {
|
||||
Arrays.fill(temp, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(H, 0L);
|
||||
Arrays.fill(Y, (byte)0);
|
||||
}
|
||||
|
||||
private static long readBigEndian(byte[] buf, int offset)
|
||||
{
|
||||
return ((buf[offset] & 0xFFL) << 56) |
|
||||
((buf[offset + 1] & 0xFFL) << 48) |
|
||||
((buf[offset + 2] & 0xFFL) << 40) |
|
||||
((buf[offset + 3] & 0xFFL) << 32) |
|
||||
((buf[offset + 4] & 0xFFL) << 24) |
|
||||
((buf[offset + 5] & 0xFFL) << 16) |
|
||||
((buf[offset + 6] & 0xFFL) << 8) |
|
||||
(buf[offset + 7] & 0xFFL);
|
||||
}
|
||||
|
||||
private static void writeBigEndian(byte[] buf, int offset, long value)
|
||||
{
|
||||
buf[offset] = (byte)(value >> 56);
|
||||
buf[offset + 1] = (byte)(value >> 48);
|
||||
buf[offset + 2] = (byte)(value >> 40);
|
||||
buf[offset + 3] = (byte)(value >> 32);
|
||||
buf[offset + 4] = (byte)(value >> 24);
|
||||
buf[offset + 5] = (byte)(value >> 16);
|
||||
buf[offset + 6] = (byte)(value >> 8);
|
||||
buf[offset + 7] = (byte)value;
|
||||
}
|
||||
|
||||
private static void GF128_mul(byte[] Y, long[] H)
|
||||
{
|
||||
long Z0 = 0; // Z = 0
|
||||
long Z1 = 0;
|
||||
long V0 = H[0]; // V = H
|
||||
long V1 = H[1];
|
||||
|
||||
// Multiply Z by V for the set bits in Y, starting at the top.
|
||||
// This is a very simple bit by bit version that may not be very
|
||||
// fast but it should be resistant to cache timing attacks.
|
||||
for (int posn = 0; posn < 16; ++posn) {
|
||||
int value = Y[posn] & 0xFF;
|
||||
for (int bit = 7; bit >= 0; --bit) {
|
||||
// Extract the high bit of "value" and turn it into a mask.
|
||||
long mask = -((long)((value >> bit) & 0x01));
|
||||
|
||||
// XOR V with Z if the bit is 1.
|
||||
Z0 ^= (V0 & mask);
|
||||
Z1 ^= (V1 & mask);
|
||||
|
||||
// Rotate V right by 1 bit.
|
||||
mask = ((~(V1 & 0x01)) + 1) & 0xE100000000000000L;
|
||||
V1 = (V1 >>> 1) | (V0 << 63);
|
||||
V0 = (V0 >>> 1) ^ mask;
|
||||
}
|
||||
}
|
||||
|
||||
// We have finished the block so copy Z into Y and byte-swap.
|
||||
writeBigEndian(Y, 0, Z0);
|
||||
writeBigEndian(Y, 8, Z1);
|
||||
}
|
||||
}
|
||||
@ -176,20 +176,29 @@ class AESGCMCipherState implements CipherState {
|
||||
int ciphertextOffset, byte[] plaintext, int plaintextOffset,
|
||||
int length) throws ShortBufferException, AEADBadTagException {
|
||||
int space;
|
||||
if (ciphertextOffset > ciphertext.length)
|
||||
space = 0;
|
||||
else
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintextOffset > plaintext.length)
|
||||
space = 0;
|
||||
else
|
||||
space = plaintext.length - plaintextOffset;
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (n < 0)
|
||||
throw new IllegalStateException("Nonce has wrapped around");
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (length < 16)
|
||||
throw new AEADBadTagException();
|
||||
int dataLen = length - 16;
|
||||
if (dataLen > space)
|
||||
throw new ShortBufferException();
|
||||
if (n < 0)
|
||||
throw new IllegalStateException("Nonce has wrapped around");
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, createGCMParams());
|
||||
} catch (InvalidKeyException e) {
|
||||
|
||||
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.protocol;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.southernstorm.noise.crypto.GHASH;
|
||||
|
||||
/**
|
||||
* Emulates the "AESGCM" cipher for Noise using the "AES/CTR/NoPadding"
|
||||
* transformation from JCA/JCE.
|
||||
*
|
||||
* This class is used on platforms that don't have "AES/GCM/NoPadding",
|
||||
* but which do have the older "AES/CTR/NoPadding".
|
||||
*/
|
||||
class AESGCMOnCtrCipherState implements CipherState {
|
||||
|
||||
private Cipher cipher;
|
||||
private SecretKeySpec keySpec;
|
||||
private long n;
|
||||
private byte[] iv;
|
||||
private byte[] hashKey;
|
||||
private GHASH ghash;
|
||||
|
||||
/**
|
||||
* Constructs a new cipher state for the "AESGCM" algorithm.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException The system does not have a
|
||||
* provider for this algorithm.
|
||||
*/
|
||||
public AESGCMOnCtrCipherState() throws NoSuchAlgorithmException
|
||||
{
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
// AES/CTR is available, but not the unpadded version? Huh?
|
||||
throw new NoSuchAlgorithmException("AES/CTR/NoPadding not available", e);
|
||||
}
|
||||
keySpec = null;
|
||||
n = 0;
|
||||
iv = new byte [16];
|
||||
hashKey = new byte [16];
|
||||
ghash = new GHASH();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// There doesn't seem to be a standard API to clean out a Cipher.
|
||||
// So we instead set the key and IV to all-zeroes to hopefully
|
||||
// destroy the sensitive data in the cipher instance.
|
||||
ghash.destroy();
|
||||
Noise.destroy(hashKey);
|
||||
Noise.destroy(iv);
|
||||
keySpec = new SecretKeySpec(new byte [32], "AES");
|
||||
IvParameterSpec params = new IvParameterSpec(iv);
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, params);
|
||||
} catch (InvalidKeyException e) {
|
||||
// Shouldn't happen.
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherName() {
|
||||
return "AESGCM";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMACLength() {
|
||||
return keySpec != null ? 16 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeKey(byte[] key, int offset) {
|
||||
// Set the encryption key.
|
||||
keySpec = new SecretKeySpec(key, offset, 32, "AES");
|
||||
|
||||
// Generate the hashing key by encrypting a block of zeroes.
|
||||
Arrays.fill(iv, (byte)0);
|
||||
Arrays.fill(hashKey, (byte)0);
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
try {
|
||||
int result = cipher.update(hashKey, 0, 16, hashKey, 0);
|
||||
cipher.doFinal(hashKey, result);
|
||||
} catch (ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
ghash.reset(hashKey, 0);
|
||||
|
||||
// Reset the nonce.
|
||||
n = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey() {
|
||||
return keySpec != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up to encrypt or decrypt the next packet.
|
||||
*
|
||||
* @param ad The associated data for the packet.
|
||||
*/
|
||||
private void setup(byte[] ad) throws InvalidKeyException, InvalidAlgorithmParameterException
|
||||
{
|
||||
// Check for nonce wrap-around.
|
||||
if (n < 0)
|
||||
throw new IllegalStateException("Nonce has wrapped around");
|
||||
|
||||
// Format the counter/IV block for AES/CTR/NoPadding.
|
||||
iv[0] = 0;
|
||||
iv[1] = 0;
|
||||
iv[2] = 0;
|
||||
iv[3] = 0;
|
||||
iv[4] = (byte)(n >> 56);
|
||||
iv[5] = (byte)(n >> 48);
|
||||
iv[6] = (byte)(n >> 40);
|
||||
iv[7] = (byte)(n >> 32);
|
||||
iv[8] = (byte)(n >> 24);
|
||||
iv[9] = (byte)(n >> 16);
|
||||
iv[10] = (byte)(n >> 8);
|
||||
iv[11] = (byte)n;
|
||||
iv[12] = 0;
|
||||
iv[13] = 0;
|
||||
iv[14] = 0;
|
||||
iv[15] = 1;
|
||||
++n;
|
||||
|
||||
// Initialize the CTR mode cipher with the key and IV.
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
|
||||
|
||||
// Encrypt a block of zeroes to generate the hash key to XOR
|
||||
// the GHASH tag with at the end of the encrypt/decrypt operation.
|
||||
Arrays.fill(hashKey, (byte)0);
|
||||
try {
|
||||
cipher.update(hashKey, 0, 16, hashKey, 0);
|
||||
} catch (ShortBufferException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
// Initialize the GHASH with the associated data value.
|
||||
ghash.reset();
|
||||
if (ad != null) {
|
||||
ghash.update(ad, 0, ad.length);
|
||||
ghash.pad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length)
|
||||
throws ShortBufferException {
|
||||
int space;
|
||||
if (ciphertextOffset > ciphertext.length)
|
||||
space = 0;
|
||||
else
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (space < 16 || length > (space - 16))
|
||||
throw new ShortBufferException();
|
||||
try {
|
||||
setup(ad);
|
||||
int result = cipher.update(plaintext, plaintextOffset, length, ciphertext, ciphertextOffset);
|
||||
cipher.doFinal(ciphertext, ciphertextOffset + result);
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
ghash.update(ciphertext, ciphertextOffset, length);
|
||||
ghash.pad(ad != null ? ad.length : 0, length);
|
||||
ghash.finish(ciphertext, ciphertextOffset + length, 16);
|
||||
for (int index = 0; index < 16; ++index)
|
||||
ciphertext[ciphertextOffset + length + index] ^= hashKey[index];
|
||||
return length + 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decryptWithAd(byte[] ad, byte[] ciphertext,
|
||||
int ciphertextOffset, byte[] plaintext, int plaintextOffset,
|
||||
int length) throws ShortBufferException, AEADBadTagException {
|
||||
int space;
|
||||
if (ciphertextOffset > ciphertext.length)
|
||||
space = 0;
|
||||
else
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintextOffset > plaintext.length)
|
||||
space = 0;
|
||||
else
|
||||
space = plaintext.length - plaintextOffset;
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (length < 16)
|
||||
throw new AEADBadTagException();
|
||||
int dataLen = length - 16;
|
||||
if (dataLen > space)
|
||||
throw new ShortBufferException();
|
||||
try {
|
||||
setup(ad);
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
ghash.update(ciphertext, ciphertextOffset, dataLen);
|
||||
ghash.pad(ad != null ? ad.length : 0, dataLen);
|
||||
ghash.finish(iv, 0, 16);
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (hashKey[index] ^ iv[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if (temp != 0)
|
||||
throw new AEADBadTagException();
|
||||
try {
|
||||
int result = cipher.update(ciphertext, ciphertextOffset, dataLen, plaintext, plaintextOffset);
|
||||
cipher.doFinal(plaintext, plaintextOffset + result);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return dataLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CipherState fork(byte[] key, int offset) {
|
||||
CipherState cipher;
|
||||
try {
|
||||
cipher = new AESGCMOnCtrCipherState();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Shouldn't happen.
|
||||
return null;
|
||||
}
|
||||
cipher.initializeKey(key, offset);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonce(long nonce) {
|
||||
if (nonce < n)
|
||||
throw new IllegalArgumentException("Nonce values cannot go backwards");
|
||||
n = nonce;
|
||||
}
|
||||
}
|
||||
@ -83,10 +83,22 @@ public final class Noise {
|
||||
*/
|
||||
public static CipherState createCipher(String name) throws NoSuchAlgorithmException
|
||||
{
|
||||
if (name.equals("AESGCM"))
|
||||
return new AESGCMCipherState();
|
||||
else if (name.equals("ChaChaPoly"))
|
||||
if (name.equals("AESGCM")) {
|
||||
try {
|
||||
return new AESGCMCipherState();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// The JCA/JCE does not have "AES/GCM/NoPadding" so try
|
||||
// emulating it on top of "AES/CTR/NoPadding" instead.
|
||||
try {
|
||||
return new AESGCMOnCtrCipherState();
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
// Re-throw the original "GCM not found" exception".
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else if (name.equals("ChaChaPoly")) {
|
||||
return new ChaChaPolyCipherState();
|
||||
}
|
||||
throw new NoSuchAlgorithmException("Unknown Noise cipher algorithm name: " + name);
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
package com.southernstorm.noise.tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
@ -49,18 +48,19 @@ public class CipherStateTests {
|
||||
byte[] keyBytes = TestUtils.stringToData(key);
|
||||
byte[] adBytes = TestUtils.stringToData(ad);
|
||||
byte[] plaintextBytes = TestUtils.stringToData(plaintext);
|
||||
byte[] ciphertextBytes = TestUtils.stringToData(ciphertext + mac.substring(2));
|
||||
byte[] ciphertextBytes;
|
||||
byte[] buffer;
|
||||
if (ciphertext.length() > 0)
|
||||
ciphertextBytes = TestUtils.stringToData(ciphertext + mac.substring(2));
|
||||
else
|
||||
ciphertextBytes = TestUtils.stringToData(mac);
|
||||
|
||||
// Create the cipher object and check its properties.
|
||||
CipherState cipher = null;
|
||||
try {
|
||||
cipher = Noise.createCipher(name);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// FIXME: AES/GCM/NoPadding not supported in JavaSE-1.7.
|
||||
// Remove this once we have a suitable replacement.
|
||||
assumeNoException(e);
|
||||
//fail(name + " cipher is not supported");
|
||||
fail(name + " cipher is not supported");
|
||||
}
|
||||
assertEquals(name, cipher.getCipherName());
|
||||
assertEquals(keyLen, cipher.getKeyLength());
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.southernstorm.noise.crypto.GHASH;
|
||||
|
||||
public class GHASHTests {
|
||||
|
||||
private void testGHASH(String key, String data, String hash)
|
||||
{
|
||||
byte[] keyBytes = TestUtils.stringToData(key);
|
||||
byte[] dataBytes = TestUtils.stringToData(data);
|
||||
byte[] hashBytes = TestUtils.stringToData(hash);
|
||||
byte[] tag = new byte [16];
|
||||
|
||||
GHASH ghash = new GHASH();
|
||||
ghash.reset(keyBytes, 0);
|
||||
ghash.update(dataBytes, 0, dataBytes.length);
|
||||
Arrays.fill(tag, (byte)0xAA);
|
||||
ghash.finish(tag, 0, 16);
|
||||
assertArrayEquals(hashBytes, tag);
|
||||
|
||||
ghash.reset();
|
||||
ghash.update(dataBytes, 0, dataBytes.length / 3);
|
||||
ghash.update(dataBytes, dataBytes.length / 3, dataBytes.length - (dataBytes.length / 3));
|
||||
Arrays.fill(tag, (byte)0xAA);
|
||||
ghash.finish(tag, 0, 16);
|
||||
assertArrayEquals(hashBytes, tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ghash() {
|
||||
// Test vectors from Appendix B of:
|
||||
// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
|
||||
|
||||
testGHASH("0x66e94bd4ef8a2c3b884cfa59ca342b2e",
|
||||
"0x00000000000000000000000000000000",
|
||||
"0x00000000000000000000000000000000");
|
||||
|
||||
testGHASH("0x66e94bd4ef8a2c3b884cfa59ca342b2e",
|
||||
"0x0388dace60b6a392f328c2b971b2fe7800000000000000000000000000000080",
|
||||
"0xf38cbb1ad69223dcc3457ae5b6b0f885");
|
||||
|
||||
testGHASH("0xb83b533708bf535d0aa6e52980d53b78",
|
||||
"0x42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f598500000000000000000000000000000200",
|
||||
"0x7f1b32b81b820d02614f8895ac1d4eac");
|
||||
|
||||
testGHASH("0xb83b533708bf535d0aa6e52980d53b78",
|
||||
"0xfeedfacedeadbeeffeedfacedeadbeefabaddad200000000000000000000000042831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e0910000000000000000000000a000000000000001e0",
|
||||
"0x698e57f70e6ecc7fd9463b7260a9ae5f");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user