diff options
author | Sergio Giro <sgiro@google.com> | 2016-02-02 15:27:40 +0000 |
---|---|---|
committer | Sergio Giro <sgiro@google.com> | 2016-02-02 15:30:05 +0000 |
commit | 11975162f2da08e65157d37cd272721485f2b34b (patch) | |
tree | e2d9c4866d82c0e93854d5e9a6200cfce47e6232 /bcprov/src/main/java/org/bouncycastle/crypto | |
parent | bdb7b3d37025690a0434040b4e0d0623d9fa74af (diff) | |
download | bouncycastle-11975162f2da08e65157d37cd272721485f2b34b.tar.gz |
bouncycastle: Android tree with upstream code for version 1.54.
Adding missing files
Change-Id: Ife77e8b1df7ec05555b29fb48a984f4c0da2e562
Diffstat (limited to 'bcprov/src/main/java/org/bouncycastle/crypto')
36 files changed, 5067 insertions, 0 deletions
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/CharToByteConverter.java b/bcprov/src/main/java/org/bouncycastle/crypto/CharToByteConverter.java new file mode 100644 index 00000000..8b640993 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/CharToByteConverter.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto; + +/** + * Interface for a converter that produces a byte encoding for a char array. + */ +public interface CharToByteConverter +{ + /** + * Return the type of the conversion. + * + * @return a type name for the conversion. + */ + String getType(); + + /** + * Return a byte encoded representation of the passed in password. + * + * @param password the characters to encode. + * @return a byte encoding of password. + */ + byte[] convert(char[] password); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/Xof.java b/bcprov/src/main/java/org/bouncycastle/crypto/Xof.java new file mode 100644 index 00000000..9183a7a2 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/Xof.java @@ -0,0 +1,19 @@ +package org.bouncycastle.crypto; + +/** + * With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes. + * This interface provides the extra method required to support variable output on an extended digest implementation. + */ +public interface Xof + extends ExtendedDigest +{ + /** + * Output the results of the final calculation for this digest to outLen number of bytes. + * + * @param out output array to write the output bytes to. + * @param outOff offset to start writing the bytes at. + * @param outLen the number of output bytes requested. + * @return the number of bytes written + */ + int doFinal(byte[] out, int outOff, int outLen); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ConcatenationKDFGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ConcatenationKDFGenerator.java new file mode 100644 index 00000000..c3353f9d --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ConcatenationKDFGenerator.java @@ -0,0 +1,124 @@ +package org.bouncycastle.crypto.agreement.kdf; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.params.KDFParameters; + +/** + * Generator for Concatenation Key Derivation Function defined in NIST SP 800-56A, Sect 5.8.1 + */ +public class ConcatenationKDFGenerator + implements DerivationFunction +{ + private Digest digest; + private byte[] shared; + private byte[] otherInfo; + private int hLen; + + /** + * @param digest the digest to be used as the source of generated bytes + */ + public ConcatenationKDFGenerator( + Digest digest) + { + this.digest = digest; + this.hLen = digest.getDigestSize(); + } + + public void init( + DerivationParameters param) + { + if (param instanceof KDFParameters) + { + KDFParameters p = (KDFParameters)param; + + shared = p.getSharedSecret(); + otherInfo = p.getIV(); + } + else + { + throw new IllegalArgumentException("KDF parameters required for KDF2Generator"); + } + } + + /** + * return the underlying digest. + */ + public Digest getDigest() + { + return digest; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * fill len bytes of the output buffer with bytes generated from + * the derivation function. + * + * @throws DataLengthException if the out buffer is too small. + */ + public int generateBytes( + byte[] out, + int outOff, + int len) + throws DataLengthException, IllegalArgumentException + { + if ((out.length - len) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 1; + int outputLen = 0; + + digest.reset(); + + if (len > hLen) + { + do + { + ItoOSP(counter, C); + + digest.update(C, 0, C.length); + digest.update(shared, 0, shared.length); + digest.update(otherInfo, 0, otherInfo.length); + + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, out, outOff + outputLen, hLen); + outputLen += hLen; + } + while ((counter++) < (len / hLen)); + } + + if (outputLen < len) + { + ItoOSP(counter, C); + + digest.update(C, 0, C.length); + digest.update(shared, 0, shared.length); + digest.update(otherInfo, 0, otherInfo.length); + + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, out, outOff + outputLen, len - outputLen); + } + + return len; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java new file mode 100644 index 00000000..0fd3ebb9 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java @@ -0,0 +1,580 @@ +package org.bouncycastle.crypto.digests; + + +/* The BLAKE2 cryptographic hash function was designed by Jean- + Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn, and Christian + Winnerlein. + + Reference Implementation and Description can be found at: https://blake2.net/ + Internet Draft: https://tools.ietf.org/html/draft-saarinen-blake2-02 + + This implementation does not support the Tree Hashing Mode. + + For unkeyed hashing, developers adapting BLAKE2 to ASN.1 - based + message formats SHOULD use the OID tree at x = 1.3.6.1.4.1.1722.12.2. + + Algorithm | Target | Collision | Hash | Hash ASN.1 | + Identifier | Arch | Security | nn | OID Suffix | + ---------------+--------+-----------+------+------------+ + id-blake2b160 | 64-bit | 2**80 | 20 | x.1.20 | + id-blake2b256 | 64-bit | 2**128 | 32 | x.1.32 | + id-blake2b384 | 64-bit | 2**192 | 48 | x.1.48 | + id-blake2b512 | 64-bit | 2**256 | 64 | x.1.64 | + ---------------+--------+-----------+------+------------+ + */ + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; + + +/** + * Implementation of the cryptographic hash function Blakbe2b. + * + * Blake2b offers a built-in keying mechanism to be used directly + * for authentication ("Prefix-MAC") rather than a HMAC construction. + * + * Blake2b offers a built-in support for a salt for randomized hashing + * and a personal string for defining a unique hash function for each application. + * + * BLAKE2b is optimized for 64-bit platforms and produces digests of any size + * between 1 and 64 bytes. + */ +public class Blake2bDigest + implements ExtendedDigest +{ + // Blake2b Initialization Vector: + private final static long blake2b_IV[] = + // Produced from the square root of primes 2, 3, 5, 7, 11, 13, 17, 19. + // The same as SHA-512 IV. + { + 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, + 0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, + 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L + }; + + // Message word permutations: + private final static byte[][] blake2b_sigma = + { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } + }; + + private static int rOUNDS = 12; // to use for Catenas H' + private final static int BLOCK_LENGTH_BYTES = 128;// bytes + + // General parameters: + private int digestLength = 64; // 1- 64 bytes + private int keyLength = 0; // 0 - 64 bytes for keyed hashing for MAC + private byte[] salt = null;// new byte[16]; + private byte[] personalization = null;// new byte[16]; + + // the key + private byte[] key = null; + + // Tree hashing parameters: + // Because this class does not implement the Tree Hashing Mode, + // these parameters can be treated as constants (see init() function) + /* + * private int fanout = 1; // 0-255 private int depth = 1; // 1 - 255 + * private int leafLength= 0; private long nodeOffset = 0L; private int + * nodeDepth = 0; private int innerHashLength = 0; + */ + + // whenever this buffer overflows, it will be processed + // in the compress() function. + // For performance issues, long messages will not use this buffer. + private byte[] buffer = null;// new byte[BLOCK_LENGTH_BYTES]; + // Position of last inserted byte: + private int bufferPos = 0;// a value from 0 up to 128 + + private long[] internalState = new long[16]; // In the Blake2b paper it is + // called: v + private long[] chainValue = null; // state vector, in the Blake2b paper it + // is called: h + + private long t0 = 0L; // holds last significant bits, counter (counts bytes) + private long t1 = 0L; // counter: Length up to 2^128 are supported + private long f0 = 0L; // finalization flag, for last block: ~0L + + // For Tree Hashing Mode, not used here: + // private long f1 = 0L; // finalization flag, for last node: ~0L + + public Blake2bDigest() + { + this(512); + } + + public Blake2bDigest(Blake2bDigest digest) + { + this.bufferPos = digest.bufferPos; + this.buffer = Arrays.clone(digest.buffer); + this.keyLength = digest.keyLength; + this.key = Arrays.clone(digest.key); + this.digestLength = digest.digestLength; + this.chainValue = Arrays.clone(digest.chainValue); + this.personalization = Arrays.clone(personalization); + } + + public Blake2bDigest(int digestLength) + { + if (digestLength != 160 && digestLength != 256 && digestLength != 384 && digestLength != 512) + { + throw new IllegalArgumentException("Blake2b digest restricted to one of [160, 256, 384, 512]"); + } + + buffer = new byte[BLOCK_LENGTH_BYTES]; + keyLength = 0; + this.digestLength = digestLength / 8; + init(); + } + + /** + * Blake2b for authentication ("Prefix-MAC mode"). + * After calling the doFinal() method, the key will + * remain to be used for further computations of + * this instance. + * The key can be overwritten using the clearKey() method. + * + * @param key A key up to 64 bytes or null + */ + public Blake2bDigest(byte[] key) + { + buffer = new byte[BLOCK_LENGTH_BYTES]; + if (key != null) + { + this.key = new byte[key.length]; + System.arraycopy(key, 0, this.key, 0, key.length); + + if (key.length > 64) + { + throw new IllegalArgumentException( + "Keys > 64 are not supported"); + } + keyLength = key.length; + System.arraycopy(key, 0, buffer, 0, key.length); + bufferPos = BLOCK_LENGTH_BYTES; // zero padding + } + digestLength = 64; + init(); + } + + /** + * Blake2b with key, required digest length, salt and personalization. + * After calling the doFinal() method, the key, the salt and the personal string + * will remain and might be used for further computations with this instance. + * The key can be overwritten using the clearKey() method, the salt (pepper) + * can be overwritten using the clearSalt() method. + * + * @param key A key up to 64 bytes or null + * @param digestLength from 1 up to 64 bytes + * @param salt up to 16 bytes or null + * @param personalization up to 16 bytes or null + */ + public Blake2bDigest(byte[] key, int digestLength, byte[] salt, + byte[] personalization) + { + + buffer = new byte[BLOCK_LENGTH_BYTES]; + if (digestLength < 1 || digestLength > 64) + { + throw new IllegalArgumentException( + "Invalid digest length (required: 1 - 64)"); + } + this.digestLength = digestLength; + if (salt != null) + { + if (salt.length != 16) + { + throw new IllegalArgumentException( + "salt length must be exactly 16 bytes"); + } + this.salt = new byte[16]; + System.arraycopy(salt, 0, this.salt, 0, salt.length); + } + if (personalization != null) + { + if (personalization.length != 16) + { + throw new IllegalArgumentException( + "personalization length must be exactly 16 bytes"); + } + this.personalization = new byte[16]; + System.arraycopy(personalization, 0, this.personalization, 0, + personalization.length); + } + if (key != null) + { + this.key = new byte[key.length]; + System.arraycopy(key, 0, this.key, 0, key.length); + + if (key.length > 64) + { + throw new IllegalArgumentException( + "Keys > 64 are not supported"); + } + keyLength = key.length; + System.arraycopy(key, 0, buffer, 0, key.length); + bufferPos = BLOCK_LENGTH_BYTES; // zero padding + } + init(); + } + + // initialize chainValue + private void init() + { + + if (chainValue == null) + { + chainValue = new long[8]; + + chainValue[0] = blake2b_IV[0] + ^ (digestLength | (keyLength << 8) | 0x1010000); + // 0x1010000 = ((fanout << 16) | (depth << 24) | (leafLength << + // 32)); + // with fanout = 1; depth = 0; leafLength = 0; + chainValue[1] = blake2b_IV[1];// ^ nodeOffset; with nodeOffset = 0; + chainValue[2] = blake2b_IV[2];// ^ ( nodeDepth | (innerHashLength << + // 8) ); + // with nodeDepth = 0; innerHashLength = 0; + + chainValue[3] = blake2b_IV[3]; + + chainValue[4] = blake2b_IV[4]; + chainValue[5] = blake2b_IV[5]; + if (salt != null) + { + chainValue[4] ^= (bytes2long(salt, 0)); + chainValue[5] ^= (bytes2long(salt, 8)); + } + + chainValue[6] = blake2b_IV[6]; + chainValue[7] = blake2b_IV[7]; + if (personalization != null) + { + chainValue[6] ^= (bytes2long(personalization, 0)); + chainValue[7] ^= (bytes2long(personalization, 8)); + } + } + } + + private void initializeInternalState() + { + // initialize v: + System.arraycopy(chainValue, 0, internalState, 0, chainValue.length); + System.arraycopy(blake2b_IV, 0, internalState, chainValue.length, 4); + internalState[12] = t0 ^ blake2b_IV[4]; + internalState[13] = t1 ^ blake2b_IV[5]; + internalState[14] = f0 ^ blake2b_IV[6]; + internalState[15] = blake2b_IV[7];// ^ f1 with f1 = 0 + } + + /** + * update the message digest with a single byte. + * + * @param b the input byte to be entered. + */ + public void update(byte b) + { + int remainingLength = 0; // left bytes of buffer + + // process the buffer if full else add to buffer: + remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength == 0) + { // full buffer + t0 += BLOCK_LENGTH_BYTES; + if (t0 == 0) + { // if message > 2^64 + t1++; + } + compress(buffer, 0); + Arrays.fill(buffer, (byte)0);// clear buffer + buffer[0] = b; + bufferPos = 1; + } else + { + buffer[bufferPos] = b; + bufferPos++; + return; + } + } + + /** + * update the message digest with a block of bytes. + * + * @param message the byte array containing the data. + * @param offset the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void update(byte[] message, int offset, int len) + { + + if (message == null || len == 0) + return; + + int remainingLength = 0; // left bytes of buffer + + if (bufferPos != 0) + { // commenced, incomplete buffer + + // complete the buffer: + remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength < len) + { // full buffer + at least 1 byte + System.arraycopy(message, offset, buffer, bufferPos, + remainingLength); + t0 += BLOCK_LENGTH_BYTES; + if (t0 == 0) + { // if message > 2^64 + t1++; + } + compress(buffer, 0); + bufferPos = 0; + Arrays.fill(buffer, (byte) 0);// clear buffer + } else + { + System.arraycopy(message, offset, buffer, bufferPos, len); + bufferPos += len; + return; + } + } + + // process blocks except last block (also if last block is full) + int messagePos; + int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES; + for (messagePos = offset + remainingLength; messagePos < blockWiseLastPos; messagePos += BLOCK_LENGTH_BYTES) + { // block wise 128 bytes + // without buffer: + t0 += BLOCK_LENGTH_BYTES; + if (t0 == 0) + { + t1++; + } + compress(message, messagePos); + } + + // fill the buffer with left bytes, this might be a full block + System.arraycopy(message, messagePos, buffer, 0, offset + len + - messagePos); + bufferPos += offset + len - messagePos; + } + + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * Key, salt and personal string remain. + * + * @param out the array the digest is to be copied into. + * @param outOffset the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOffset) + { + + f0 = 0xFFFFFFFFFFFFFFFFL; + t0 += bufferPos; + // bufferPos may be < 128, so (t0 == 0) does not work + // for 2^64 < message length > 2^64 - 127 + if ((t0 < 0) && (bufferPos > -t0)) + { + t1++; + } + compress(buffer, 0); + Arrays.fill(buffer, (byte) 0);// Holds eventually the key if input is null + Arrays.fill(internalState, 0L); + + for (int i = 0; i < chainValue.length && (i * 8 < digestLength); i++) + { + byte[] bytes = long2bytes(chainValue[i]); + + if (i * 8 < digestLength - 8) + { + System.arraycopy(bytes, 0, out, outOffset + i * 8, 8); + } + else + { + System.arraycopy(bytes, 0, out, outOffset + i * 8, digestLength - (i * 8)); + } + } + + Arrays.fill(chainValue, 0L); + + reset(); + + return digestLength; + } + + /** + * Reset the digest back to it's initial state. + * The key, the salt and the personal string will + * remain for further computations. + */ + public void reset() + { + bufferPos = 0; + f0 = 0L; + t0 = 0L; + t1 = 0L; + chainValue = null; + if (key != null) + { + System.arraycopy(key, 0, buffer, 0, key.length); + bufferPos = BLOCK_LENGTH_BYTES; // zero padding + } + init(); + } + + private void compress(byte[] message, int messagePos) + { + + initializeInternalState(); + + long[] m = new long[16]; + for (int j = 0; j < 16; j++) + { + m[j] = bytes2long(message, messagePos + j * 8); + } + + for (int round = 0; round < rOUNDS; round++) + { + + // G apply to columns of internalState:m[blake2b_sigma[round][2 * + // blockPos]] /+1 + G(m[blake2b_sigma[round][0]], m[blake2b_sigma[round][1]], 0, 4, 8, + 12); + G(m[blake2b_sigma[round][2]], m[blake2b_sigma[round][3]], 1, 5, 9, + 13); + G(m[blake2b_sigma[round][4]], m[blake2b_sigma[round][5]], 2, 6, 10, + 14); + G(m[blake2b_sigma[round][6]], m[blake2b_sigma[round][7]], 3, 7, 11, + 15); + // G apply to diagonals of internalState: + G(m[blake2b_sigma[round][8]], m[blake2b_sigma[round][9]], 0, 5, 10, + 15); + G(m[blake2b_sigma[round][10]], m[blake2b_sigma[round][11]], 1, 6, + 11, 12); + G(m[blake2b_sigma[round][12]], m[blake2b_sigma[round][13]], 2, 7, + 8, 13); + G(m[blake2b_sigma[round][14]], m[blake2b_sigma[round][15]], 3, 4, + 9, 14); + } + + // update chain values: + for (int offset = 0; offset < chainValue.length; offset++) + { + chainValue[offset] = chainValue[offset] ^ internalState[offset] + ^ internalState[offset + 8]; + } + } + + private void G(long m1, long m2, int posA, int posB, int posC, int posD) + { + + internalState[posA] = internalState[posA] + internalState[posB] + m1; + internalState[posD] = rotr64(internalState[posD] ^ internalState[posA], + 32); + internalState[posC] = internalState[posC] + internalState[posD]; + internalState[posB] = rotr64(internalState[posB] ^ internalState[posC], + 24); // replaces 25 of BLAKE + internalState[posA] = internalState[posA] + internalState[posB] + m2; + internalState[posD] = rotr64(internalState[posD] ^ internalState[posA], + 16); + internalState[posC] = internalState[posC] + internalState[posD]; + internalState[posB] = rotr64(internalState[posB] ^ internalState[posC], + 63); // replaces 11 of BLAKE + } + + private long rotr64(long x, int rot) + { + return x >>> rot | (x << (64 - rot)); + } + + // convert one long value in byte array + // little-endian byte order! + private final byte[] long2bytes(long longValue) + { + return new byte[] + { (byte) longValue, (byte) (longValue >> 8), + (byte) (longValue >> 16), (byte) (longValue >> 24), + (byte) (longValue >> 32), (byte) (longValue >> 40), + (byte) (longValue >> 48), (byte) (longValue >> 56) + }; + } + + // little-endian byte order! + private final long bytes2long(byte[] byteArray, int offset) + { + return (((long) byteArray[offset] & 0xFF) + | (((long) byteArray[offset + 1] & 0xFF) << 8) + | (((long) byteArray[offset + 2] & 0xFF) << 16) + | (((long) byteArray[offset + 3] & 0xFF) << 24) + | (((long) byteArray[offset + 4] & 0xFF) << 32) + | (((long) byteArray[offset + 5] & 0xFF) << 40) + | (((long) byteArray[offset + 6] & 0xFF) << 48) + | (((long) byteArray[offset + 7] & 0xFF) << 56)); + } + + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName() + { + return "Blake2b"; + } + + /** + * return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + public int getDigestSize() + { + return digestLength; + } + + /** + * Return the size in bytes of the internal buffer the digest applies it's compression + * function to. + * + * @return byte length of the digests internal buffer. + */ + public int getByteLength() + { + return BLOCK_LENGTH_BYTES; + } + + /** + * Overwrite the key + * if it is no longer used (zeroization) + */ + public void clearKey() + { + if (key != null) + { + Arrays.fill(key, (byte) 0); + Arrays.fill(buffer, (byte) 0); + } + } + + /** + * Overwrite the salt (pepper) if it + * is secret and no longer used (zeroization) + */ + public void clearSalt() + { + if (salt != null) + { + Arrays.fill(salt, (byte) 0); + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java new file mode 100644 index 00000000..40194461 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java @@ -0,0 +1,549 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; + +/** + * implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + * <p> + * Following the naming conventions used in the C source code to enable easy review of the implementation. + */ +public class KeccakDigest + implements ExtendedDigest +{ + private static long[] KeccakRoundConstants = keccakInitializeRoundConstants(); + + private static int[] KeccakRhoOffsets = keccakInitializeRhoOffsets(); + + private static long[] keccakInitializeRoundConstants() + { + long[] keccakRoundConstants = new long[24]; + byte[] LFSRstate = new byte[1]; + + LFSRstate[0] = 0x01; + int i, j, bitPosition; + + for (i = 0; i < 24; i++) + { + keccakRoundConstants[i] = 0; + for (j = 0; j < 7; j++) + { + bitPosition = (1 << j) - 1; + if (LFSR86540(LFSRstate)) + { + keccakRoundConstants[i] ^= 1L << bitPosition; + } + } + } + + return keccakRoundConstants; + } + + private static boolean LFSR86540(byte[] LFSR) + { + boolean result = (((LFSR[0]) & 0x01) != 0); + if (((LFSR[0]) & 0x80) != 0) + { + LFSR[0] = (byte)(((LFSR[0]) << 1) ^ 0x71); + } + else + { + LFSR[0] <<= 1; + } + + return result; + } + + private static int[] keccakInitializeRhoOffsets() + { + int[] keccakRhoOffsets = new int[25]; + int x, y, t, newX, newY; + + keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0; + x = 1; + y = 0; + for (t = 0; t < 24; t++) + { + keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64; + newX = (0 * x + 1 * y) % 5; + newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + return keccakRhoOffsets; + } + + protected byte[] state = new byte[(1600 / 8)]; + protected byte[] dataQueue = new byte[(1536 / 8)]; + protected int rate; + protected int bitsInQueue; + protected int fixedOutputLength; + protected boolean squeezing; + protected int bitsAvailableForSqueezing; + protected byte[] chunk; + protected byte[] oneByte; + + private void clearDataQueueSection(int off, int len) + { + for (int i = off; i != off + len; i++) + { + dataQueue[i] = 0; + } + } + + public KeccakDigest() + { + this(288); + } + + public KeccakDigest(int bitLength) + { + init(bitLength); + } + + public KeccakDigest(KeccakDigest source) { + System.arraycopy(source.state, 0, this.state, 0, source.state.length); + System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length); + this.rate = source.rate; + this.bitsInQueue = source.bitsInQueue; + this.fixedOutputLength = source.fixedOutputLength; + this.squeezing = source.squeezing; + this.bitsAvailableForSqueezing = source.bitsAvailableForSqueezing; + this.chunk = Arrays.clone(source.chunk); + this.oneByte = Arrays.clone(source.oneByte); + } + + public String getAlgorithmName() + { + return "Keccak-" + fixedOutputLength; + } + + public int getDigestSize() + { + return fixedOutputLength / 8; + } + + public void update(byte in) + { + oneByte[0] = in; + + absorb(oneByte, 0, 8L); + } + + public void update(byte[] in, int inOff, int len) + { + absorb(in, inOff, len * 8L); + } + + public int doFinal(byte[] out, int outOff) + { + squeeze(out, outOff, fixedOutputLength); + + reset(); + + return getDigestSize(); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) + { + if (partialBits > 0) + { + oneByte[0] = partialByte; + absorb(oneByte, 0, partialBits); + } + + squeeze(out, outOff, fixedOutputLength); + + reset(); + + return getDigestSize(); + } + + public void reset() + { + init(fixedOutputLength); + } + + /** + * Return the size of block that the compression function is applied to in bytes. + * + * @return internal byte length of a block. + */ + public int getByteLength() + { + return rate / 8; + } + + private void init(int bitLength) + { + switch (bitLength) + { + case 288: + initSponge(1024, 576); + break; + case 128: + initSponge(1344, 256); + break; + case 224: + initSponge(1152, 448); + break; + case 256: + initSponge(1088, 512); + break; + case 384: + initSponge(832, 768); + break; + case 512: + initSponge(576, 1024); + break; + default: + throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512."); + } + } + + private void initSponge(int rate, int capacity) + { + if (rate + capacity != 1600) + { + throw new IllegalStateException("rate + capacity != 1600"); + } + if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0)) + { + throw new IllegalStateException("invalid rate value"); + } + + this.rate = rate; + // this is never read, need to check to see why we want to save it + // this.capacity = capacity; + Arrays.fill(this.state, (byte)0); + Arrays.fill(this.dataQueue, (byte)0); + this.bitsInQueue = 0; + this.squeezing = false; + this.bitsAvailableForSqueezing = 0; + this.fixedOutputLength = capacity / 2; + this.chunk = new byte[rate / 8]; + this.oneByte = new byte[1]; + } + + private void absorbQueue() + { + KeccakAbsorb(state, dataQueue, rate / 8); + + bitsInQueue = 0; + } + + protected void absorb(byte[] data, int off, long databitlen) + { + long i, j, wholeBlocks; + + if ((bitsInQueue % 8) != 0) + { + throw new IllegalStateException("attempt to absorb with odd length queue."); + } + if (squeezing) + { + throw new IllegalStateException("attempt to absorb while squeezing."); + } + + i = 0; + while (i < databitlen) + { + if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate))) + { + wholeBlocks = (databitlen - i) / rate; + + for (j = 0; j < wholeBlocks; j++) + { + System.arraycopy(data, (int)(off + (i / 8) + (j * chunk.length)), chunk, 0, chunk.length); + +// displayIntermediateValues.displayBytes(1, "Block to be absorbed", curData, rate / 8); + + KeccakAbsorb(state, chunk, chunk.length); + } + + i += wholeBlocks * rate; + } + else + { + int partialBlock = (int)(databitlen - i); + if (partialBlock + bitsInQueue > rate) + { + partialBlock = rate - bitsInQueue; + } + int partialByte = partialBlock % 8; + partialBlock -= partialByte; + System.arraycopy(data, off + (int)(i / 8), dataQueue, bitsInQueue / 8, partialBlock / 8); + + bitsInQueue += partialBlock; + i += partialBlock; + if (bitsInQueue == rate) + { + absorbQueue(); + } + if (partialByte > 0) + { + int mask = (1 << partialByte) - 1; + dataQueue[bitsInQueue / 8] = (byte)(data[off + ((int)(i / 8))] & mask); + bitsInQueue += partialByte; + i += partialByte; + } + } + } + } + + private void padAndSwitchToSqueezingPhase() + { + if (bitsInQueue + 1 == rate) + { + dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8); + absorbQueue(); + clearDataQueueSection(0, rate / 8); + } + else + { + clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8); + dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8); + } + dataQueue[(rate - 1) / 8] |= 1 << ((rate - 1) % 8); + absorbQueue(); + + +// displayIntermediateValues.displayText(1, "--- Switching to squeezing phase ---"); + + + if (rate == 1024) + { + KeccakExtract1024bits(state, dataQueue); + bitsAvailableForSqueezing = 1024; + } + else + + { + KeccakExtract(state, dataQueue, rate / 64); + bitsAvailableForSqueezing = rate; + } + +// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8); + + squeezing = true; + } + + protected void squeeze(byte[] output, int offset, long outputLength) + { + long i; + int partialBlock; + + if (!squeezing) + { + padAndSwitchToSqueezingPhase(); + } + if ((outputLength % 8) != 0) + { + throw new IllegalStateException("outputLength not a multiple of 8"); + } + + i = 0; + while (i < outputLength) + { + if (bitsAvailableForSqueezing == 0) + { + keccakPermutation(state); + + if (rate == 1024) + { + KeccakExtract1024bits(state, dataQueue); + bitsAvailableForSqueezing = 1024; + } + else + + { + KeccakExtract(state, dataQueue, rate / 64); + bitsAvailableForSqueezing = rate; + } + +// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8); + + } + partialBlock = bitsAvailableForSqueezing; + if ((long)partialBlock > outputLength - i) + { + partialBlock = (int)(outputLength - i); + } + + System.arraycopy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int)(i / 8), partialBlock / 8); + bitsAvailableForSqueezing -= partialBlock; + i += partialBlock; + } + } + + private void fromBytesToWords(long[] stateAsWords, byte[] state) + { + for (int i = 0; i < (1600 / 64); i++) + { + stateAsWords[i] = 0; + int index = i * (64 / 8); + for (int j = 0; j < (64 / 8); j++) + { + stateAsWords[i] |= ((long)state[index + j] & 0xff) << ((8 * j)); + } + } + } + + private void fromWordsToBytes(byte[] state, long[] stateAsWords) + { + for (int i = 0; i < (1600 / 64); i++) + { + int index = i * (64 / 8); + for (int j = 0; j < (64 / 8); j++) + { + state[index + j] = (byte)((stateAsWords[i] >>> ((8 * j))) & 0xFF); + } + } + } + + private void keccakPermutation(byte[] state) + { + long[] longState = new long[state.length / 8]; + + fromBytesToWords(longState, state); + +// displayIntermediateValues.displayStateAsBytes(1, "Input of permutation", longState); + + keccakPermutationOnWords(longState); + +// displayIntermediateValues.displayStateAsBytes(1, "State after permutation", longState); + + fromWordsToBytes(state, longState); + } + + private void keccakPermutationAfterXor(byte[] state, byte[] data, int dataLengthInBytes) + { + int i; + + for (i = 0; i < dataLengthInBytes; i++) + { + state[i] ^= data[i]; + } + + keccakPermutation(state); + } + + private void keccakPermutationOnWords(long[] state) + { + int i; + +// displayIntermediateValues.displayStateAs64bitWords(3, "Same, with lanes as 64-bit words", state); + + for (i = 0; i < 24; i++) + { +// displayIntermediateValues.displayRoundNumber(3, i); + + theta(state); +// displayIntermediateValues.displayStateAs64bitWords(3, "After theta", state); + + rho(state); +// displayIntermediateValues.displayStateAs64bitWords(3, "After rho", state); + + pi(state); +// displayIntermediateValues.displayStateAs64bitWords(3, "After pi", state); + + chi(state); +// displayIntermediateValues.displayStateAs64bitWords(3, "After chi", state); + + iota(state, i); +// displayIntermediateValues.displayStateAs64bitWords(3, "After iota", state); + } + } + + long[] C = new long[5]; + + private void theta(long[] A) + { + for (int x = 0; x < 5; x++) + { + C[x] = 0; + for (int y = 0; y < 5; y++) + { + C[x] ^= A[x + 5 * y]; + } + } + for (int x = 0; x < 5; x++) + { + long dX = ((((C[(x + 1) % 5]) << 1) ^ ((C[(x + 1) % 5]) >>> (64 - 1)))) ^ C[(x + 4) % 5]; + for (int y = 0; y < 5; y++) + { + A[x + 5 * y] ^= dX; + } + } + } + + private void rho(long[] A) + { + for (int x = 0; x < 5; x++) + { + for (int y = 0; y < 5; y++) + { + int index = x + 5 * y; + A[index] = ((KeccakRhoOffsets[index] != 0) ? (((A[index]) << KeccakRhoOffsets[index]) ^ ((A[index]) >>> (64 - KeccakRhoOffsets[index]))) : A[index]); + } + } + } + + long[] tempA = new long[25]; + + private void pi(long[] A) + { + System.arraycopy(A, 0, tempA, 0, tempA.length); + + for (int x = 0; x < 5; x++) + { + for (int y = 0; y < 5; y++) + { + A[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y]; + } + } + } + + long[] chiC = new long[5]; + + private void chi(long[] A) + { + for (int y = 0; y < 5; y++) + { + for (int x = 0; x < 5; x++) + { + chiC[x] = A[x + 5 * y] ^ ((~A[(((x + 1) % 5) + 5 * y)]) & A[(((x + 2) % 5) + 5 * y)]); + } + for (int x = 0; x < 5; x++) + { + A[x + 5 * y] = chiC[x]; + } + } + } + + private void iota(long[] A, int indexRound) + { + A[(((0) % 5) + 5 * ((0) % 5))] ^= KeccakRoundConstants[indexRound]; + } + + private void KeccakAbsorb(byte[] byteState, byte[] data, int dataInBytes) + { + keccakPermutationAfterXor(byteState, data, dataInBytes); + } + + + private void KeccakExtract1024bits(byte[] byteState, byte[] data) + { + System.arraycopy(byteState, 0, data, 0, 128); + } + + + private void KeccakExtract(byte[] byteState, byte[] data, int laneCount) + { + System.arraycopy(byteState, 0, data, 0, laneCount * 8); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java new file mode 100644 index 00000000..20dc641c --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java @@ -0,0 +1,103 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.Xof; + + +/** + * implementation of SHAKE based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + * <p> + * Following the naming conventions used in the C source code to enable easy review of the implementation. + */ +public class SHAKEDigest + extends KeccakDigest + implements Xof +{ + private static int checkBitLength(int bitLength) + { + switch (bitLength) + { + case 128: + case 256: + return bitLength; + default: + throw new IllegalArgumentException("'bitLength' " + bitLength + " not supported for SHAKE"); + } + } + + public SHAKEDigest() + { + this(128); + } + + public SHAKEDigest(int bitLength) + { + super(checkBitLength(bitLength)); + } + + public SHAKEDigest(SHAKEDigest source) { + super(source); + } + + public String getAlgorithmName() + { + return "SHAKE" + fixedOutputLength; + } + + public int doFinal(byte[] out, int outOff) + { + return doFinal(out, outOff, getDigestSize()); + } + + public int doFinal(byte[] out, int outOff, int outLen) + { + absorb(new byte[]{ 0x0F }, 0, 4); + + squeeze(out, outOff, ((long)outLen) * 8); + + reset(); + + return outLen; + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) + { + return doFinal(out, outOff, getDigestSize(), partialByte, partialBits); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, int outLen, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + { + throw new IllegalArgumentException("'partialBits' must be in the range [0,7]"); + } + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits); + int finalBits = partialBits + 4; + + if (finalBits >= 8) + { + oneByte[0] = (byte)finalInput; + absorb(oneByte, 0, 8); + finalBits -= 8; + finalInput >>>= 8; + } + + if (finalBits > 0) + { + oneByte[0] = (byte)finalInput; + absorb(oneByte, 0, finalBits); + } + + squeeze(out, outOff, ((long)outLen) * 8); + + reset(); + + return outLen; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/OldIESEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/OldIESEngine.java new file mode 100644 index 00000000..48ecbe3c --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/OldIESEngine.java @@ -0,0 +1,61 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.util.Pack; + +/** + * Support class for constructing integrated encryption ciphers + * for doing basic message exchanges on top of key agreement ciphers. + * Follows the description given in IEEE Std 1363a. + */ +public class OldIESEngine + extends IESEngine +{ + /** + * set up for use with stream mode, where the key derivation function + * is used to provide a stream of bytes to xor with the message. + * + * @param agree the key agreement used as the basis for the encryption + * @param kdf the key derivation function used for byte generation + * @param mac the message authentication code generator for the message + */ + public OldIESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac) + { + super(agree, kdf, mac); + } + + + /** + * set up for use in conjunction with a block cipher to handle the + * message. + * + * @param agree the key agreement used as the basis for the encryption + * @param kdf the key derivation function used for byte generation + * @param mac the message authentication code generator for the message + * @param cipher the cipher to used for encrypting the message + */ + public OldIESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac, + BufferedBlockCipher cipher) + { + super(agree, kdf, mac, cipher); + } + + protected byte[] getLengthTag(byte[] p2) + { + byte[] L2 = new byte[4]; + if (p2 != null) + { + Pack.intToBigEndian(p2.length * 8, L2, 0); + } + return L2; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/SM4Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SM4Engine.java new file mode 100644 index 00000000..bf70eadb --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SM4Engine.java @@ -0,0 +1,267 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Pack; + +/** + * SM4 Block Cipher - SM4 is a 128 bit block cipher with a 128 bit key. + * <p> + * The implementation here is based on the document <a href="http://eprint.iacr.org/2008/329.pdf">http://eprint.iacr.org/2008/329.pdf</a> + * by Whitfield Diffie and George Ledin, which is a translation of Prof. LU Shu-wang's original standard. + * </p> + */ +public class SM4Engine + implements BlockCipher +{ + private static final int BLOCK_SIZE = 16; + + private final static byte[] Sbox = + { + (byte)0xd6, (byte)0x90, (byte)0xe9, (byte)0xfe, (byte)0xcc, (byte)0xe1, (byte)0x3d, (byte)0xb7, (byte)0x16, (byte)0xb6, (byte)0x14, (byte)0xc2, (byte)0x28, (byte)0xfb, (byte)0x2c, (byte)0x05, + (byte)0x2b, (byte)0x67, (byte)0x9a, (byte)0x76, (byte)0x2a, (byte)0xbe, (byte)0x04, (byte)0xc3, (byte)0xaa, (byte)0x44, (byte)0x13, (byte)0x26, (byte)0x49, (byte)0x86, (byte)0x06, (byte)0x99, + (byte)0x9c, (byte)0x42, (byte)0x50, (byte)0xf4, (byte)0x91, (byte)0xef, (byte)0x98, (byte)0x7a, (byte)0x33, (byte)0x54, (byte)0x0b, (byte)0x43, (byte)0xed, (byte)0xcf, (byte)0xac, (byte)0x62, + (byte)0xe4, (byte)0xb3, (byte)0x1c, (byte)0xa9, (byte)0xc9, (byte)0x08, (byte)0xe8, (byte)0x95, (byte)0x80, (byte)0xdf, (byte)0x94, (byte)0xfa, (byte)0x75, (byte)0x8f, (byte)0x3f, (byte)0xa6, + (byte)0x47, (byte)0x07, (byte)0xa7, (byte)0xfc, (byte)0xf3, (byte)0x73, (byte)0x17, (byte)0xba, (byte)0x83, (byte)0x59, (byte)0x3c, (byte)0x19, (byte)0xe6, (byte)0x85, (byte)0x4f, (byte)0xa8, + (byte)0x68, (byte)0x6b, (byte)0x81, (byte)0xb2, (byte)0x71, (byte)0x64, (byte)0xda, (byte)0x8b, (byte)0xf8, (byte)0xeb, (byte)0x0f, (byte)0x4b, (byte)0x70, (byte)0x56, (byte)0x9d, (byte)0x35, + (byte)0x1e, (byte)0x24, (byte)0x0e, (byte)0x5e, (byte)0x63, (byte)0x58, (byte)0xd1, (byte)0xa2, (byte)0x25, (byte)0x22, (byte)0x7c, (byte)0x3b, (byte)0x01, (byte)0x21, (byte)0x78, (byte)0x87, + (byte)0xd4, (byte)0x00, (byte)0x46, (byte)0x57, (byte)0x9f, (byte)0xd3, (byte)0x27, (byte)0x52, (byte)0x4c, (byte)0x36, (byte)0x02, (byte)0xe7, (byte)0xa0, (byte)0xc4, (byte)0xc8, (byte)0x9e, + (byte)0xea, (byte)0xbf, (byte)0x8a, (byte)0xd2, (byte)0x40, (byte)0xc7, (byte)0x38, (byte)0xb5, (byte)0xa3, (byte)0xf7, (byte)0xf2, (byte)0xce, (byte)0xf9, (byte)0x61, (byte)0x15, (byte)0xa1, + (byte)0xe0, (byte)0xae, (byte)0x5d, (byte)0xa4, (byte)0x9b, (byte)0x34, (byte)0x1a, (byte)0x55, (byte)0xad, (byte)0x93, (byte)0x32, (byte)0x30, (byte)0xf5, (byte)0x8c, (byte)0xb1, (byte)0xe3, + (byte)0x1d, (byte)0xf6, (byte)0xe2, (byte)0x2e, (byte)0x82, (byte)0x66, (byte)0xca, (byte)0x60, (byte)0xc0, (byte)0x29, (byte)0x23, (byte)0xab, (byte)0x0d, (byte)0x53, (byte)0x4e, (byte)0x6f, + (byte)0xd5, (byte)0xdb, (byte)0x37, (byte)0x45, (byte)0xde, (byte)0xfd, (byte)0x8e, (byte)0x2f, (byte)0x03, (byte)0xff, (byte)0x6a, (byte)0x72, (byte)0x6d, (byte)0x6c, (byte)0x5b, (byte)0x51, + (byte)0x8d, (byte)0x1b, (byte)0xaf, (byte)0x92, (byte)0xbb, (byte)0xdd, (byte)0xbc, (byte)0x7f, (byte)0x11, (byte)0xd9, (byte)0x5c, (byte)0x41, (byte)0x1f, (byte)0x10, (byte)0x5a, (byte)0xd8, + (byte)0x0a, (byte)0xc1, (byte)0x31, (byte)0x88, (byte)0xa5, (byte)0xcd, (byte)0x7b, (byte)0xbd, (byte)0x2d, (byte)0x74, (byte)0xd0, (byte)0x12, (byte)0xb8, (byte)0xe5, (byte)0xb4, (byte)0xb0, + (byte)0x89, (byte)0x69, (byte)0x97, (byte)0x4a, (byte)0x0c, (byte)0x96, (byte)0x77, (byte)0x7e, (byte)0x65, (byte)0xb9, (byte)0xf1, (byte)0x09, (byte)0xc5, (byte)0x6e, (byte)0xc6, (byte)0x84, + (byte)0x18, (byte)0xf0, (byte)0x7d, (byte)0xec, (byte)0x3a, (byte)0xdc, (byte)0x4d, (byte)0x20, (byte)0x79, (byte)0xee, (byte)0x5f, (byte)0x3e, (byte)0xd7, (byte)0xcb, (byte)0x39, (byte)0x48 + }; + + private final static int[] CK = + { + 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, + 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, + 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, + 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, + 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, + 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, + 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, + 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 + }; + + private final static int[] FK = + { + 0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc + }; + + private final int[] X = new int[4]; + + private int[] rk; + + private int rotateLeft( + int x, + int bits) + { + return (x << bits) | (x >>> -bits); + } + + // non-linear substitution tau. + private int tau( + int A) + { + int b0 = Sbox[(A >> 24) & 0xff] & 0xff; + int b1 = Sbox[(A >> 16) & 0xff] & 0xff; + int b2 = Sbox[(A >> 8) & 0xff] & 0xff; + int b3 = Sbox[A & 0xff] & 0xff; + + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + + private int L_ap( + int B) + { + return (B ^ (rotateLeft(B, 13)) ^ (rotateLeft(B, 23))); + } + + private int T_ap( + int Z) + { + return L_ap(tau(Z)); + } + + // Key expansion + private int[] expandKey(boolean forEncryption, byte[] key) + { + int[] rk = new int[32]; + int[] MK = new int[4]; + + MK[0] = Pack.bigEndianToInt(key, 0); + MK[1] = Pack.bigEndianToInt(key, 4); + MK[2] = Pack.bigEndianToInt(key, 8); + MK[3] = Pack.bigEndianToInt(key, 12); + + int i; + int[] K = new int[4]; + K[0] = MK[0] ^ FK[0]; + K[1] = MK[1] ^ FK[1]; + K[2] = MK[2] ^ FK[2]; + K[3] = MK[3] ^ FK[3]; + + if (forEncryption) + { + rk[0] = K[0] ^ T_ap(K[1] ^ K[2] ^ K[3] ^ CK[0]); + rk[1] = K[1] ^ T_ap(K[2] ^ K[3] ^ rk[0] ^ CK[1]); + rk[2] = K[2] ^ T_ap(K[3] ^ rk[0] ^ rk[1] ^ CK[2]); + rk[3] = K[3] ^ T_ap(rk[0] ^ rk[1] ^ rk[2] ^ CK[3]); + for (i = 4; i < 32; i++) + { + rk[i] = rk[i - 4] ^ T_ap(rk[i - 3] ^ rk[i - 2] ^ rk[i - 1] ^ CK[i]); + } + } + else + { + rk[31] = K[0] ^ T_ap(K[1] ^ K[2] ^ K[3] ^ CK[0]); + rk[30] = K[1] ^ T_ap(K[2] ^ K[3] ^ rk[31] ^ CK[1]); + rk[29] = K[2] ^ T_ap(K[3] ^ rk[31] ^ rk[30] ^ CK[2]); + rk[28] = K[3] ^ T_ap(rk[31] ^ rk[30] ^ rk[29] ^ CK[3]); + for (i = 27; i >= 0; i--) + { + rk[i] = rk[i + 4] ^ T_ap(rk[i + 3] ^ rk[i + 2] ^ rk[i + 1] ^ CK[31 - i]); + } + } + + return rk; + } + + + // Linear substitution L + private int L(int B) + { + int C; + C = (B ^ (rotateLeft(B, 2)) ^ (rotateLeft(B, 10)) ^ (rotateLeft(B, + 18)) ^ (rotateLeft(B, 24))); + return C; + } + + // Mixer-substitution T + private int T(int Z) + { + return L(tau(Z)); + } + + // reverse substitution + private void R(int[] A, int off) + { + int off0 = off; + int off1 = off + 1; + int off2 = off + 2; + int off3 = off + 3; + + A[off0] = A[off0] ^ A[off3]; + A[off3] = A[off0] ^ A[off3]; + A[off0] = A[off0] ^ A[off3]; + A[off1] = A[off1] ^ A[off2]; + A[off2] = A[off1] ^ A[off2]; + A[off1] = A[off1] ^ A[off2]; + } + + // The round functions + private int F0(int[] X, int rk) + { + return (X[0] ^ T(X[1] ^ X[2] ^ X[3] ^ rk)); + } + + private int F1(int[] X, int rk) + { + return (X[1] ^ T(X[2] ^ X[3] ^ X[0] ^ rk)); + } + + private int F2(int[] X, int rk) + { + return (X[2] ^ T(X[3] ^ X[0] ^ X[1] ^ rk)); + } + + private int F3(int[] X, int rk) + { + return (X[3] ^ T(X[0] ^ X[1] ^ X[2] ^ rk)); + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof KeyParameter) + { + byte[] key = ((KeyParameter)params).getKey(); + + if (key.length != 16) + { + throw new IllegalArgumentException("SM4 requires a 128 bit key"); + } + + rk = expandKey(forEncryption, key); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to SM4 init - " + params.getClass().getName()); + } + } + + public String getAlgorithmName() + { + return "SM4"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + if (rk == null) + { + throw new IllegalStateException("SM4 not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + X[0] = Pack.bigEndianToInt(in, inOff); + X[1] = Pack.bigEndianToInt(in, inOff + 4); + X[2] = Pack.bigEndianToInt(in, inOff + 8); + X[3] = Pack.bigEndianToInt(in, inOff + 12); + + int i; + + for (i = 0; i < 32; i += 4) + { + X[0] = F0(X, rk[i]); + X[1] = F1(X, rk[i + 1]); + X[2] = F2(X, rk[i + 2]); + X[3] = F3(X, rk[i + 3]); + } + R(X, 0); + + Pack.intToBigEndian(X[0], out, outOff); + Pack.intToBigEndian(X[1], out, outOff + 4); + Pack.intToBigEndian(X[2], out, outOff + 8); + Pack.intToBigEndian(X[3], out, outOff + 12); + + return 16; + } + + public void reset() + { + + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngineBase.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngineBase.java new file mode 100644 index 00000000..c47b2c26 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngineBase.java @@ -0,0 +1,486 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.params.KeyParameter; + +public abstract class SerpentEngineBase + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 16; + + static final int ROUNDS = 32; + static final int PHI = 0x9E3779B9; // (sqrt(5) - 1) * 2**31 + + protected boolean encrypting; + protected int[] wKey; + + protected int X0, X1, X2, X3; // registers + + SerpentEngineBase() + { + + } + + /** + * initialise a Serpent cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + this.encrypting = encrypting; + this.wKey = makeWorkingKey(((KeyParameter)params).getKey()); + return; + } + + throw new IllegalArgumentException("invalid parameter passed to " + getAlgorithmName() + " init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Serpent"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @return the number of bytes processed and produced. + * @throws DataLengthException if there isn't enough data in in, or + * space in out. + * @throws IllegalStateException if the cipher isn't initialised. + */ + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (wKey == null) + { + throw new IllegalStateException(getAlgorithmName() + " not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (encrypting) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + protected static int rotateLeft( + int x, + int bits) + { + return (x << bits) | (x >>> -bits); + } + + protected static int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } + + /** + * The sboxes below are based on the work of Brian Gladman and + * Sam Simpson, whose original notice appears below. + * <p> + * For further details see: + * http://fp.gladman.plus.com/cryptography_technology/serpent/ + */ + + /* Partially optimised Serpent S Box boolean functions derived */ + /* using a recursive descent analyser but without a full search */ + /* of all subtrees. This set of S boxes is the result of work */ + /* by Sam Simpson and Brian Gladman using the spare time on a */ + /* cluster of high capacity servers to search for S boxes with */ + /* this customised search engine. There are now an average of */ + /* 15.375 terms per S box. */ + /* */ + /* Copyright: Dr B. R Gladman (gladman@seven77.demon.co.uk) */ + /* and Sam Simpson (s.simpson@mia.co.uk) */ + /* 17th December 1998 */ + /* */ + /* We hereby give permission for information in this file to be */ + /* used freely subject only to acknowledgement of its origin. */ + + /** + * S0 - { 3, 8,15, 1,10, 6, 5,11,14,13, 4, 2, 7, 0, 9,12 } - 15 terms. + */ + protected final void sb0(int a, int b, int c, int d) + { + int t1 = a ^ d; + int t3 = c ^ t1; + int t4 = b ^ t3; + X3 = (a & d) ^ t4; + int t7 = a ^ (b & t1); + X2 = t4 ^ (c | t7); + int t12 = X3 & (t3 ^ t7); + X1 = (~t3) ^ t12; + X0 = t12 ^ (~t7); + } + + /** + * InvSO - {13, 3,11, 0,10, 6, 5,12, 1,14, 4, 7,15, 9, 8, 2 } - 15 terms. + */ + protected final void ib0(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ b; + int t4 = d ^ (t1 | t2); + int t5 = c ^ t4; + X2 = t2 ^ t5; + int t8 = t1 ^ (d & t2); + X1 = t4 ^ (X2 & t8); + X3 = (a & t4) ^ (t5 | X1); + X0 = X3 ^ (t5 ^ t8); + } + + /** + * S1 - {15,12, 2, 7, 9, 0, 5,10, 1,11,14, 8, 6,13, 3, 4 } - 14 terms. + */ + protected final void sb1(int a, int b, int c, int d) + { + int t2 = b ^ (~a); + int t5 = c ^ (a | t2); + X2 = d ^ t5; + int t7 = b ^ (d | t2); + int t8 = t2 ^ X2; + X3 = t8 ^ (t5 & t7); + int t11 = t5 ^ t7; + X1 = X3 ^ t11; + X0 = t5 ^ (t8 & t11); + } + + /** + * InvS1 - { 5, 8, 2,14,15, 6,12, 3,11, 4, 7, 9, 1,13,10, 0 } - 14 steps. + */ + protected final void ib1(int a, int b, int c, int d) + { + int t1 = b ^ d; + int t3 = a ^ (b & t1); + int t4 = t1 ^ t3; + X3 = c ^ t4; + int t7 = b ^ (t1 & t3); + int t8 = X3 | t7; + X1 = t3 ^ t8; + int t10 = ~X1; + int t11 = X3 ^ t7; + X0 = t10 ^ t11; + X2 = t4 ^ (t10 | t11); + } + + /** + * S2 - { 8, 6, 7, 9, 3,12,10,15,13, 1,14, 4, 0,11, 5, 2 } - 16 terms. + */ + protected final void sb2(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = b ^ d; + int t3 = c & t1; + X0 = t2 ^ t3; + int t5 = c ^ t1; + int t6 = c ^ X0; + int t7 = b & t6; + X3 = t5 ^ t7; + X2 = a ^ ((d | t7) & (X0 | t5)); + X1 = (t2 ^ X3) ^ (X2 ^ (d | t1)); + } + + /** + * InvS2 - {12, 9,15, 4,11,14, 1, 2, 0, 3, 6,13, 5, 8,10, 7 } - 16 steps. + */ + protected final void ib2(int a, int b, int c, int d) + { + int t1 = b ^ d; + int t2 = ~t1; + int t3 = a ^ c; + int t4 = c ^ t1; + int t5 = b & t4; + X0 = t3 ^ t5; + int t7 = a | t2; + int t8 = d ^ t7; + int t9 = t3 | t8; + X3 = t1 ^ t9; + int t11 = ~t4; + int t12 = X0 | X3; + X1 = t11 ^ t12; + X2 = (d & t11) ^ (t3 ^ t12); + } + + /** + * S3 - { 0,15,11, 8,12, 9, 6, 3,13, 1, 2, 4,10, 7, 5,14 } - 16 terms. + */ + protected final void sb3(int a, int b, int c, int d) + { + int t1 = a ^ b; + int t2 = a & c; + int t3 = a | d; + int t4 = c ^ d; + int t5 = t1 & t3; + int t6 = t2 | t5; + X2 = t4 ^ t6; + int t8 = b ^ t3; + int t9 = t6 ^ t8; + int t10 = t4 & t9; + X0 = t1 ^ t10; + int t12 = X2 & X0; + X1 = t9 ^ t12; + X3 = (b | d) ^ (t4 ^ t12); + } + + /** + * InvS3 - { 0, 9,10, 7,11,14, 6,13, 3, 5,12, 2, 4, 8,15, 1 } - 15 terms + */ + protected final void ib3(int a, int b, int c, int d) + { + int t1 = a | b; + int t2 = b ^ c; + int t3 = b & t2; + int t4 = a ^ t3; + int t5 = c ^ t4; + int t6 = d | t4; + X0 = t2 ^ t6; + int t8 = t2 | t6; + int t9 = d ^ t8; + X2 = t5 ^ t9; + int t11 = t1 ^ t9; + int t12 = X0 & t11; + X3 = t4 ^ t12; + X1 = X3 ^ (X0 ^ t11); + } + + /** + * S4 - { 1,15, 8, 3,12, 0,11, 6, 2, 5, 4,10, 9,14, 7,13 } - 15 terms. + */ + protected final void sb4(int a, int b, int c, int d) + { + int t1 = a ^ d; + int t2 = d & t1; + int t3 = c ^ t2; + int t4 = b | t3; + X3 = t1 ^ t4; + int t6 = ~b; + int t7 = t1 | t6; + X0 = t3 ^ t7; + int t9 = a & X0; + int t10 = t1 ^ t6; + int t11 = t4 & t10; + X2 = t9 ^ t11; + X1 = (a ^ t3) ^ (t10 & X2); + } + + /** + * InvS4 - { 5, 0, 8, 3,10, 9, 7,14, 2,12,11, 6, 4,15,13, 1 } - 15 terms. + */ + protected final void ib4(int a, int b, int c, int d) + { + int t1 = c | d; + int t2 = a & t1; + int t3 = b ^ t2; + int t4 = a & t3; + int t5 = c ^ t4; + X1 = d ^ t5; + int t7 = ~a; + int t8 = t5 & X1; + X3 = t3 ^ t8; + int t10 = X1 | t7; + int t11 = d ^ t10; + X0 = X3 ^ t11; + X2 = (t3 & t11) ^ (X1 ^ t7); + } + + /** + * S5 - {15, 5, 2,11, 4,10, 9,12, 0, 3,14, 8,13, 6, 7, 1 } - 16 terms. + */ + protected final void sb5(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ b; + int t3 = a ^ d; + int t4 = c ^ t1; + int t5 = t2 | t3; + X0 = t4 ^ t5; + int t7 = d & X0; + int t8 = t2 ^ X0; + X1 = t7 ^ t8; + int t10 = t1 | X0; + int t11 = t2 | t7; + int t12 = t3 ^ t10; + X2 = t11 ^ t12; + X3 = (b ^ t7) ^ (X1 & t12); + } + + /** + * InvS5 - { 8,15, 2, 9, 4, 1,13,14,11, 6, 5, 3, 7,12,10, 0 } - 16 terms. + */ + protected final void ib5(int a, int b, int c, int d) + { + int t1 = ~c; + int t2 = b & t1; + int t3 = d ^ t2; + int t4 = a & t3; + int t5 = b ^ t1; + X3 = t4 ^ t5; + int t7 = b | X3; + int t8 = a & t7; + X1 = t3 ^ t8; + int t10 = a | d; + int t11 = t1 ^ t7; + X0 = t10 ^ t11; + X2 = (b & t10) ^ (t4 | (a ^ c)); + } + + /** + * S6 - { 7, 2,12, 5, 8, 4, 6,11,14, 9, 1,15,13, 3,10, 0 } - 15 terms. + */ + protected final void sb6(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ d; + int t3 = b ^ t2; + int t4 = t1 | t2; + int t5 = c ^ t4; + X1 = b ^ t5; + int t7 = t2 | X1; + int t8 = d ^ t7; + int t9 = t5 & t8; + X2 = t3 ^ t9; + int t11 = t5 ^ t8; + X0 = X2 ^ t11; + X3 = (~t5) ^ (t3 & t11); + } + + /** + * InvS6 - {15,10, 1,13, 5, 3, 6, 0, 4, 9,14, 7, 2,12, 8,11 } - 15 terms. + */ + protected final void ib6(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ b; + int t3 = c ^ t2; + int t4 = c | t1; + int t5 = d ^ t4; + X1 = t3 ^ t5; + int t7 = t3 & t5; + int t8 = t2 ^ t7; + int t9 = b | t8; + X3 = t5 ^ t9; + int t11 = b | X3; + X0 = t8 ^ t11; + X2 = (d & t1) ^ (t3 ^ t11); + } + + /** + * S7 - { 1,13,15, 0,14, 8, 2,11, 7, 4,12,10, 9, 3, 5, 6 } - 16 terms. + */ + protected final void sb7(int a, int b, int c, int d) + { + int t1 = b ^ c; + int t2 = c & t1; + int t3 = d ^ t2; + int t4 = a ^ t3; + int t5 = d | t1; + int t6 = t4 & t5; + X1 = b ^ t6; + int t8 = t3 | X1; + int t9 = a & t4; + X3 = t1 ^ t9; + int t11 = t4 ^ t8; + int t12 = X3 & t11; + X2 = t3 ^ t12; + X0 = (~t11) ^ (X3 & X2); + } + + /** + * InvS7 - { 3, 0, 6,13, 9,14,15, 8, 5,12,11, 7,10, 1, 4, 2 } - 17 terms. + */ + protected final void ib7(int a, int b, int c, int d) + { + int t3 = c | (a & b); + int t4 = d & (a | b); + X3 = t3 ^ t4; + int t6 = ~d; + int t7 = b ^ t4; + int t9 = t7 | (X3 ^ t6); + X1 = a ^ t9; + X0 = (c ^ t7) ^ (d | X1); + X2 = (t3 ^ X1) ^ (X0 ^ (a & X3)); + } + + /** + * Apply the linear transformation to the register set. + */ + protected final void LT() + { + int x0 = rotateLeft(X0, 13); + int x2 = rotateLeft(X2, 3); + int x1 = X1 ^ x0 ^ x2 ; + int x3 = X3 ^ x2 ^ x0 << 3; + + X1 = rotateLeft(x1, 1); + X3 = rotateLeft(x3, 7); + X0 = rotateLeft(x0 ^ X1 ^ X3, 5); + X2 = rotateLeft(x2 ^ X3 ^ (X1 << 7), 22); + } + + /** + * Apply the inverse of the linear transformation to the register set. + */ + protected final void inverseLT() + { + int x2 = rotateRight(X2, 22) ^ X3 ^ (X1 << 7); + int x0 = rotateRight(X0, 5) ^ X1 ^ X3; + int x3 = rotateRight(X3, 7); + int x1 = rotateRight(X1, 1); + X3 = x3 ^ x2 ^ x0 << 3; + X1 = x1 ^ x0 ^ x2; + X2 = rotateRight(x2, 3); + X0 = rotateRight(x0, 13); + } + + protected abstract int[] makeWorkingKey(byte[] key); + + protected abstract void encryptBlock(byte[] input, int inOff, byte[] output, int outOff); + + protected abstract void decryptBlock(byte[] input, int inOff, byte[] output, int outOff); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java new file mode 100644 index 00000000..a34e0162 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java @@ -0,0 +1,303 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.util.Pack; + +/** + * Tnepres is a 128-bit 32-round block cipher with variable key lengths, + * including 128, 192 and 256 bit keys conjectured to be at least as + * secure as three-key triple-DES. + * <p> + * Tnepres is based on Serpent which was designed by Ross Anderson, Eli Biham and Lars Knudsen as a + * candidate algorithm for the NIST AES Quest. Unfortunately there was an endianness issue + * with test vectors in the AES submission and the resulting confusion lead to the Tnepres cipher + * as well, which is a byte swapped version of Serpent. + * <p> + * For full details see <a href="http://www.cl.cam.ac.uk/~rja14/serpent.html">The Serpent home page</a> + */ +public final class TnepresEngine + extends SerpentEngineBase +{ + public String getAlgorithmName() + { + return "Tnepres"; + } + + /** + * Expand a user-supplied key material into a session key. + * + * @param key The user-key bytes (multiples of 4) to use. + * @exception IllegalArgumentException + */ + protected int[] makeWorkingKey( + byte[] key) + throws IllegalArgumentException + { + // + // pad key to 256 bits + // + int[] kPad = new int[16]; + int off = 0; + int length = 0; + + for (off = key.length - 4; off > 0; off -= 4) + { + kPad[length++] = Pack.bigEndianToInt(key, off); + } + + if (off == 0) + { + kPad[length++] = Pack.bigEndianToInt(key, 0); + if (length < 8) + { + kPad[length] = 1; + } + } + else + { + throw new IllegalArgumentException("key must be a multiple of 4 bytes"); + } + + // + // expand the padded key up to 33 x 128 bits of key material + // + int amount = (ROUNDS + 1) * 4; + int[] w = new int[amount]; + + // + // compute w0 to w7 from w-8 to w-1 + // + for (int i = 8; i < 16; i++) + { + kPad[i] = rotateLeft(kPad[i - 8] ^ kPad[i - 5] ^ kPad[i - 3] ^ kPad[i - 1] ^ PHI ^ (i - 8), 11); + } + + System.arraycopy(kPad, 8, w, 0, 8); + + // + // compute w8 to w136 + // + for (int i = 8; i < amount; i++) + { + w[i] = rotateLeft(w[i - 8] ^ w[i - 5] ^ w[i - 3] ^ w[i - 1] ^ PHI ^ i, 11); + } + + // + // create the working keys by processing w with the Sbox and IP + // + sb3(w[0], w[1], w[2], w[3]); + w[0] = X0; w[1] = X1; w[2] = X2; w[3] = X3; + sb2(w[4], w[5], w[6], w[7]); + w[4] = X0; w[5] = X1; w[6] = X2; w[7] = X3; + sb1(w[8], w[9], w[10], w[11]); + w[8] = X0; w[9] = X1; w[10] = X2; w[11] = X3; + sb0(w[12], w[13], w[14], w[15]); + w[12] = X0; w[13] = X1; w[14] = X2; w[15] = X3; + sb7(w[16], w[17], w[18], w[19]); + w[16] = X0; w[17] = X1; w[18] = X2; w[19] = X3; + sb6(w[20], w[21], w[22], w[23]); + w[20] = X0; w[21] = X1; w[22] = X2; w[23] = X3; + sb5(w[24], w[25], w[26], w[27]); + w[24] = X0; w[25] = X1; w[26] = X2; w[27] = X3; + sb4(w[28], w[29], w[30], w[31]); + w[28] = X0; w[29] = X1; w[30] = X2; w[31] = X3; + sb3(w[32], w[33], w[34], w[35]); + w[32] = X0; w[33] = X1; w[34] = X2; w[35] = X3; + sb2(w[36], w[37], w[38], w[39]); + w[36] = X0; w[37] = X1; w[38] = X2; w[39] = X3; + sb1(w[40], w[41], w[42], w[43]); + w[40] = X0; w[41] = X1; w[42] = X2; w[43] = X3; + sb0(w[44], w[45], w[46], w[47]); + w[44] = X0; w[45] = X1; w[46] = X2; w[47] = X3; + sb7(w[48], w[49], w[50], w[51]); + w[48] = X0; w[49] = X1; w[50] = X2; w[51] = X3; + sb6(w[52], w[53], w[54], w[55]); + w[52] = X0; w[53] = X1; w[54] = X2; w[55] = X3; + sb5(w[56], w[57], w[58], w[59]); + w[56] = X0; w[57] = X1; w[58] = X2; w[59] = X3; + sb4(w[60], w[61], w[62], w[63]); + w[60] = X0; w[61] = X1; w[62] = X2; w[63] = X3; + sb3(w[64], w[65], w[66], w[67]); + w[64] = X0; w[65] = X1; w[66] = X2; w[67] = X3; + sb2(w[68], w[69], w[70], w[71]); + w[68] = X0; w[69] = X1; w[70] = X2; w[71] = X3; + sb1(w[72], w[73], w[74], w[75]); + w[72] = X0; w[73] = X1; w[74] = X2; w[75] = X3; + sb0(w[76], w[77], w[78], w[79]); + w[76] = X0; w[77] = X1; w[78] = X2; w[79] = X3; + sb7(w[80], w[81], w[82], w[83]); + w[80] = X0; w[81] = X1; w[82] = X2; w[83] = X3; + sb6(w[84], w[85], w[86], w[87]); + w[84] = X0; w[85] = X1; w[86] = X2; w[87] = X3; + sb5(w[88], w[89], w[90], w[91]); + w[88] = X0; w[89] = X1; w[90] = X2; w[91] = X3; + sb4(w[92], w[93], w[94], w[95]); + w[92] = X0; w[93] = X1; w[94] = X2; w[95] = X3; + sb3(w[96], w[97], w[98], w[99]); + w[96] = X0; w[97] = X1; w[98] = X2; w[99] = X3; + sb2(w[100], w[101], w[102], w[103]); + w[100] = X0; w[101] = X1; w[102] = X2; w[103] = X3; + sb1(w[104], w[105], w[106], w[107]); + w[104] = X0; w[105] = X1; w[106] = X2; w[107] = X3; + sb0(w[108], w[109], w[110], w[111]); + w[108] = X0; w[109] = X1; w[110] = X2; w[111] = X3; + sb7(w[112], w[113], w[114], w[115]); + w[112] = X0; w[113] = X1; w[114] = X2; w[115] = X3; + sb6(w[116], w[117], w[118], w[119]); + w[116] = X0; w[117] = X1; w[118] = X2; w[119] = X3; + sb5(w[120], w[121], w[122], w[123]); + w[120] = X0; w[121] = X1; w[122] = X2; w[123] = X3; + sb4(w[124], w[125], w[126], w[127]); + w[124] = X0; w[125] = X1; w[126] = X2; w[127] = X3; + sb3(w[128], w[129], w[130], w[131]); + w[128] = X0; w[129] = X1; w[130] = X2; w[131] = X3; + + return w; + } + + /** + * Encrypt one block of plaintext. + * + * @param input the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param output the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + protected void encryptBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + X3 = Pack.bigEndianToInt(input, inOff); + X2 = Pack.bigEndianToInt(input, inOff + 4); + X1 = Pack.bigEndianToInt(input, inOff + 8); + X0 = Pack.bigEndianToInt(input, inOff + 12); + + sb0(wKey[0] ^ X0, wKey[1] ^ X1, wKey[2] ^ X2, wKey[3] ^ X3); LT(); + sb1(wKey[4] ^ X0, wKey[5] ^ X1, wKey[6] ^ X2, wKey[7] ^ X3); LT(); + sb2(wKey[8] ^ X0, wKey[9] ^ X1, wKey[10] ^ X2, wKey[11] ^ X3); LT(); + sb3(wKey[12] ^ X0, wKey[13] ^ X1, wKey[14] ^ X2, wKey[15] ^ X3); LT(); + sb4(wKey[16] ^ X0, wKey[17] ^ X1, wKey[18] ^ X2, wKey[19] ^ X3); LT(); + sb5(wKey[20] ^ X0, wKey[21] ^ X1, wKey[22] ^ X2, wKey[23] ^ X3); LT(); + sb6(wKey[24] ^ X0, wKey[25] ^ X1, wKey[26] ^ X2, wKey[27] ^ X3); LT(); + sb7(wKey[28] ^ X0, wKey[29] ^ X1, wKey[30] ^ X2, wKey[31] ^ X3); LT(); + sb0(wKey[32] ^ X0, wKey[33] ^ X1, wKey[34] ^ X2, wKey[35] ^ X3); LT(); + sb1(wKey[36] ^ X0, wKey[37] ^ X1, wKey[38] ^ X2, wKey[39] ^ X3); LT(); + sb2(wKey[40] ^ X0, wKey[41] ^ X1, wKey[42] ^ X2, wKey[43] ^ X3); LT(); + sb3(wKey[44] ^ X0, wKey[45] ^ X1, wKey[46] ^ X2, wKey[47] ^ X3); LT(); + sb4(wKey[48] ^ X0, wKey[49] ^ X1, wKey[50] ^ X2, wKey[51] ^ X3); LT(); + sb5(wKey[52] ^ X0, wKey[53] ^ X1, wKey[54] ^ X2, wKey[55] ^ X3); LT(); + sb6(wKey[56] ^ X0, wKey[57] ^ X1, wKey[58] ^ X2, wKey[59] ^ X3); LT(); + sb7(wKey[60] ^ X0, wKey[61] ^ X1, wKey[62] ^ X2, wKey[63] ^ X3); LT(); + sb0(wKey[64] ^ X0, wKey[65] ^ X1, wKey[66] ^ X2, wKey[67] ^ X3); LT(); + sb1(wKey[68] ^ X0, wKey[69] ^ X1, wKey[70] ^ X2, wKey[71] ^ X3); LT(); + sb2(wKey[72] ^ X0, wKey[73] ^ X1, wKey[74] ^ X2, wKey[75] ^ X3); LT(); + sb3(wKey[76] ^ X0, wKey[77] ^ X1, wKey[78] ^ X2, wKey[79] ^ X3); LT(); + sb4(wKey[80] ^ X0, wKey[81] ^ X1, wKey[82] ^ X2, wKey[83] ^ X3); LT(); + sb5(wKey[84] ^ X0, wKey[85] ^ X1, wKey[86] ^ X2, wKey[87] ^ X3); LT(); + sb6(wKey[88] ^ X0, wKey[89] ^ X1, wKey[90] ^ X2, wKey[91] ^ X3); LT(); + sb7(wKey[92] ^ X0, wKey[93] ^ X1, wKey[94] ^ X2, wKey[95] ^ X3); LT(); + sb0(wKey[96] ^ X0, wKey[97] ^ X1, wKey[98] ^ X2, wKey[99] ^ X3); LT(); + sb1(wKey[100] ^ X0, wKey[101] ^ X1, wKey[102] ^ X2, wKey[103] ^ X3); LT(); + sb2(wKey[104] ^ X0, wKey[105] ^ X1, wKey[106] ^ X2, wKey[107] ^ X3); LT(); + sb3(wKey[108] ^ X0, wKey[109] ^ X1, wKey[110] ^ X2, wKey[111] ^ X3); LT(); + sb4(wKey[112] ^ X0, wKey[113] ^ X1, wKey[114] ^ X2, wKey[115] ^ X3); LT(); + sb5(wKey[116] ^ X0, wKey[117] ^ X1, wKey[118] ^ X2, wKey[119] ^ X3); LT(); + sb6(wKey[120] ^ X0, wKey[121] ^ X1, wKey[122] ^ X2, wKey[123] ^ X3); LT(); + sb7(wKey[124] ^ X0, wKey[125] ^ X1, wKey[126] ^ X2, wKey[127] ^ X3); + + Pack.intToBigEndian(wKey[131] ^ X3, output, outOff); + Pack.intToBigEndian(wKey[130] ^ X2, output, outOff + 4); + Pack.intToBigEndian(wKey[129] ^ X1, output, outOff + 8); + Pack.intToBigEndian(wKey[128] ^ X0, output, outOff + 12); + } + + /** + * Decrypt one block of ciphertext. + * + * @param input the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param output the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + protected void decryptBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + X3 = wKey[131] ^ Pack.bigEndianToInt(input, inOff); + X2 = wKey[130] ^ Pack.bigEndianToInt(input, inOff + 4); + X1 = wKey[129] ^ Pack.bigEndianToInt(input, inOff + 8); + X0 = wKey[128] ^ Pack.bigEndianToInt(input, inOff + 12); + + ib7(X0, X1, X2, X3); + X0 ^= wKey[124]; X1 ^= wKey[125]; X2 ^= wKey[126]; X3 ^= wKey[127]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[120]; X1 ^= wKey[121]; X2 ^= wKey[122]; X3 ^= wKey[123]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[116]; X1 ^= wKey[117]; X2 ^= wKey[118]; X3 ^= wKey[119]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[112]; X1 ^= wKey[113]; X2 ^= wKey[114]; X3 ^= wKey[115]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[108]; X1 ^= wKey[109]; X2 ^= wKey[110]; X3 ^= wKey[111]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[104]; X1 ^= wKey[105]; X2 ^= wKey[106]; X3 ^= wKey[107]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[100]; X1 ^= wKey[101]; X2 ^= wKey[102]; X3 ^= wKey[103]; + inverseLT(); ib0(X0, X1, X2, X3); + X0 ^= wKey[96]; X1 ^= wKey[97]; X2 ^= wKey[98]; X3 ^= wKey[99]; + inverseLT(); ib7(X0, X1, X2, X3); + X0 ^= wKey[92]; X1 ^= wKey[93]; X2 ^= wKey[94]; X3 ^= wKey[95]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[88]; X1 ^= wKey[89]; X2 ^= wKey[90]; X3 ^= wKey[91]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[84]; X1 ^= wKey[85]; X2 ^= wKey[86]; X3 ^= wKey[87]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[80]; X1 ^= wKey[81]; X2 ^= wKey[82]; X3 ^= wKey[83]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[76]; X1 ^= wKey[77]; X2 ^= wKey[78]; X3 ^= wKey[79]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[72]; X1 ^= wKey[73]; X2 ^= wKey[74]; X3 ^= wKey[75]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[68]; X1 ^= wKey[69]; X2 ^= wKey[70]; X3 ^= wKey[71]; + inverseLT(); ib0(X0, X1, X2, X3); + X0 ^= wKey[64]; X1 ^= wKey[65]; X2 ^= wKey[66]; X3 ^= wKey[67]; + inverseLT(); ib7(X0, X1, X2, X3); + X0 ^= wKey[60]; X1 ^= wKey[61]; X2 ^= wKey[62]; X3 ^= wKey[63]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[56]; X1 ^= wKey[57]; X2 ^= wKey[58]; X3 ^= wKey[59]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[52]; X1 ^= wKey[53]; X2 ^= wKey[54]; X3 ^= wKey[55]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[48]; X1 ^= wKey[49]; X2 ^= wKey[50]; X3 ^= wKey[51]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[44]; X1 ^= wKey[45]; X2 ^= wKey[46]; X3 ^= wKey[47]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[40]; X1 ^= wKey[41]; X2 ^= wKey[42]; X3 ^= wKey[43]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[36]; X1 ^= wKey[37]; X2 ^= wKey[38]; X3 ^= wKey[39]; + inverseLT(); ib0(X0, X1, X2, X3); + X0 ^= wKey[32]; X1 ^= wKey[33]; X2 ^= wKey[34]; X3 ^= wKey[35]; + inverseLT(); ib7(X0, X1, X2, X3); + X0 ^= wKey[28]; X1 ^= wKey[29]; X2 ^= wKey[30]; X3 ^= wKey[31]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[24]; X1 ^= wKey[25]; X2 ^= wKey[26]; X3 ^= wKey[27]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[20]; X1 ^= wKey[21]; X2 ^= wKey[22]; X3 ^= wKey[23]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[16]; X1 ^= wKey[17]; X2 ^= wKey[18]; X3 ^= wKey[19]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[12]; X1 ^= wKey[13]; X2 ^= wKey[14]; X3 ^= wKey[15]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[8]; X1 ^= wKey[9]; X2 ^= wKey[10]; X3 ^= wKey[11]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[4]; X1 ^= wKey[5]; X2 ^= wKey[6]; X3 ^= wKey[7]; + inverseLT(); ib0(X0, X1, X2, X3); + + Pack.intToBigEndian(X3 ^ wKey[3], output, outOff); + Pack.intToBigEndian(X2 ^ wKey[2], output, outOff + 4); + Pack.intToBigEndian(X1 ^ wKey[1], output, outOff + 8); + Pack.intToBigEndian(X0 ^ wKey[0], output, outOff + 12); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/EntropyUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/EntropyUtil.java new file mode 100644 index 00000000..b34e9938 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/EntropyUtil.java @@ -0,0 +1,46 @@ +package org.bouncycastle.crypto.prng; + +/** + * Utility methods for making use of EntropySources. + */ +public class EntropyUtil +{ + /** + * Generate numBytes worth of entropy from the passed in entropy source. + * + * @param entropySource the entropy source to request the data from. + * @param numBytes the number of bytes of entropy requested. + * @return a byte array populated with the random data. + */ + public static byte[] generateSeed(EntropySource entropySource, int numBytes) + { + byte[] bytes = new byte[numBytes]; + + if (numBytes * 8 <= entropySource.entropySize()) + { + byte[] ent = entropySource.getEntropy(); + + System.arraycopy(ent, 0, bytes, 0, bytes.length); + } + else + { + int entSize = entropySource.entropySize() / 8; + + for (int i = 0; i < bytes.length; i += entSize) + { + byte[] ent = entropySource.getEntropy(); + + if (ent.length <= bytes.length - i) + { + System.arraycopy(ent, 0, bytes, i, ent.length); + } + else + { + System.arraycopy(ent, 0, bytes, i, bytes.length - i); + } + } + } + + return bytes; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ISOTrailers.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ISOTrailers.java new file mode 100644 index 00000000..2793d60a --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ISOTrailers.java @@ -0,0 +1,56 @@ +package org.bouncycastle.crypto.signers; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.util.Integers; + +public class ISOTrailers +{ + private static final Map<String, Integer> trailerMap; + + static final public int TRAILER_IMPLICIT = 0xBC; + + static final public int TRAILER_RIPEMD160 = 0x31CC; + static final public int TRAILER_RIPEMD128 = 0x32CC; + static final public int TRAILER_SHA1 = 0x33CC; + static final public int TRAILER_SHA256 = 0x34CC; + static final public int TRAILER_SHA512 = 0x35CC; + static final public int TRAILER_SHA384 = 0x36CC; + static final public int TRAILER_WHIRLPOOL = 0x37CC; + static final public int TRAILER_SHA224 = 0x38CC; + static final public int TRAILER_SHA512_224 = 0x39CC; + static final public int TRAILER_SHA512_256 = 0x40CC; + + static + { + Map<String, Integer> trailers = new HashMap<String, Integer>(); + + trailers.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128)); + trailers.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160)); + + trailers.put("SHA-1", Integers.valueOf(TRAILER_SHA1)); + trailers.put("SHA-224", Integers.valueOf(TRAILER_SHA224)); + trailers.put("SHA-256", Integers.valueOf(TRAILER_SHA256)); + trailers.put("SHA-384", Integers.valueOf(TRAILER_SHA384)); + trailers.put("SHA-512", Integers.valueOf(TRAILER_SHA512)); + trailers.put("SHA-512/224", Integers.valueOf(TRAILER_SHA512_224)); + trailers.put("SHA-512/256", Integers.valueOf(TRAILER_SHA512_256)); + + trailers.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL)); + + trailerMap = Collections.unmodifiableMap(trailers); + } + + public static Integer getTrailer(Digest digest) + { + return (Integer)trailerMap.get(digest.getAlgorithmName()); // JDK 1.4 compatibility + } + + public static boolean noTrailerAvailable(Digest digest) + { + return !trailerMap.containsKey(digest.getAlgorithmName()); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Blake2bDigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Blake2bDigestTest.java new file mode 100644 index 00000000..e962af94 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Blake2bDigestTest.java @@ -0,0 +1,159 @@ +package org.bouncycastle.crypto.test; + +import java.io.UnsupportedEncodingException; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.Blake2bDigest; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +public class Blake2bDigestTest + extends SimpleTest +{ + + private static final String[][] keyedTestVectors = + { // input/message, key, hash + + // Vectors from BLAKE2 web site: https://blake2.net/blake2b-test.txt + { + "", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568" }, + + { + "00", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd" }, + + { + "0001", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965" }, + + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + "f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd" }, + + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + "c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f" }, + + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461" } }; + + private final static String[][] unkeyedTestVectors = + { // from: http://fossies.org/linux/john/src/rawBLAKE2_512_fmt_plug.c + // hash, input/message + // digests without leading $BLAKE2$ + { + "4245af08b46fbb290222ab8a68613621d92ce78577152d712467742417ebc1153668f1c9e1ec1e152a32a9c242dc686d175e087906377f0c483c5be2cb68953e", + "blake2" }, + { + "021ced8799296ceca557832ab941a50b4a11f83478cf141f51f933f653ab9fbcc05a037cddbed06e309bf334942c4e58cdf1a46e237911ccd7fcf9787cbc7fd0", + "hello world" }, + { + "1f7d9b7c9a90f7bfc66e52b69f3b6c3befbd6aee11aac860e99347a495526f30c9e51f6b0db01c24825092a09dd1a15740f0ade8def87e60c15da487571bcef7", + "verystrongandlongpassword" }, + { + "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918", + "The quick brown fox jumps over the lazy dog" }, + { + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", + "" }, + { + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", + "abc" }, + }; + + public String getName() + { + return "Blake2b"; + } + + private void offsetTest( + Digest digest, + byte[] input, + byte[] expected) + { + byte[] resBuf = new byte[expected.length + 11]; + + digest.update(input, 0, input.length); + + digest.doFinal(resBuf, 11); + + if (!areEqual(Arrays.copyOfRange(resBuf, 11, resBuf.length), expected)) + { + fail("Offset failed got " + new String(Hex.encode(resBuf))); + } + } + + public void performTest() throws Exception + { + // test keyed test vectors: + + Blake2bDigest blake2bkeyed = new Blake2bDigest(Hex.decode(keyedTestVectors[0][1])); + for (int tv = 0; tv < keyedTestVectors.length; tv++) + { + + byte[] input = Hex.decode(keyedTestVectors[tv][0]); + blake2bkeyed.reset(); + + blake2bkeyed.update(input, 0, input.length); + byte[] keyedHash = new byte[64]; + blake2bkeyed.doFinal(keyedHash, 0); + + if (!Arrays.areEqual(Hex.decode(keyedTestVectors[tv][2]), keyedHash)) + { + fail("Blake2b mismatch on test vector ", + keyedTestVectors[tv][2], + new String(Hex.encode(keyedHash))); + } + + offsetTest(blake2bkeyed, input, keyedHash); + } + + Blake2bDigest blake2bunkeyed = new Blake2bDigest(); + // test unkeyed test vectors: + for (int i = 0; i < unkeyedTestVectors.length; i++) + { + + try + { + // blake2bunkeyed.update( + // unkeyedTestVectors[i][1].getBytes("UTF-8")); + // test update(byte b) + byte[] unkeyedInput = unkeyedTestVectors[i][1] + .getBytes("UTF-8"); + for (int j = 0; j < unkeyedInput.length; j++) + { + blake2bunkeyed.update(unkeyedInput[j]); + } + } catch (UnsupportedEncodingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + byte[] unkeyedHash = new byte[64]; + blake2bunkeyed.doFinal(unkeyedHash, 0); + blake2bunkeyed.reset(); + + if (!Arrays.areEqual(Hex.decode(unkeyedTestVectors[i][0]), + unkeyedHash)) + { + fail("Blake2b mismatch on test vector ", + unkeyedTestVectors[i][0], + new String(Hex.encode(unkeyedHash))); + } + } + } + + public static void main(String[] args) throws Exception + { + runTest(new Blake2bDigestTest()); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java new file mode 100644 index 00000000..7d9bdf12 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java @@ -0,0 +1,363 @@ +package org.bouncycastle.crypto.test; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.digests.KeccakDigest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +/** + * Keccak Digest Test + */ +public class KeccakDigestTest + extends SimpleTest +{ + final static String[] messages = { + "", + "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67", + "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e" + }; + + final static String[] digests288 = { // the default settings + "6753e3380c09e385d0339eb6b050a68f66cfd60a73476e6fd6adeb72f5edd7c6f04a5d01", // message[0] + "0bbe6afae0d7e89054085c1cc47b1689772c89a41796891e197d1ca1b76f288154933ded", // message[1] + "82558a209b960ddeb531e6dcb281885b2400ca160472462486e79f071e88a3330a8a303d", // message[2] + "94049e1ad7ef5d5b0df2b880489e7ab09ec937c3bfc1b04470e503e1ac7b1133c18f86da", // 64k a-test + "a9cb5a75b5b81b7528301e72553ed6770214fa963956e790528afe420de33c074e6f4220", // random alphabet test + "eadaf5ba2ad6a2f6f338fce0e1efdad2a61bb38f6be6068b01093977acf99e97a5d5827c" // extremely long data test + }; + + final static String[] digests224 = { + "f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd", + "310aee6b30c47350576ac2873fa89fd190cdc488442f3ef654cf23fe", + "c59d4eaeac728671c635ff645014e2afa935bebffdb5fbd207ffdeab", + "f621e11c142fbf35fa8c22841c3a812ba1e0151be4f38d80b9f1ff53", + "68b5fc8c87193155bba68a2485377e809ee4f81a85ef023b9e64add0", + "c42e4aee858e1a8ad2976896b9d23dd187f64436ee15969afdbc68c5" + }; + + final static String[] digests256 = { + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15", + "578951e24efd62a3d63a86f7cd19aaa53c898fe287d2552133220370240b572d", + "0047a916daa1f92130d870b542e22d3108444f5a7e4429f05762fb647e6ed9ed", + "db368762253ede6d4f1db87e0b799b96e554eae005747a2ea687456ca8bcbd03", + "5f313c39963dcf792b5470d4ade9f3a356a3e4021748690a958372e2b06f82a4" + }; + + final static String[] digests384 = { + "2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff", + "283990fa9d5fb731d786c5bbee94ea4db4910f18c62c03d173fc0a5e494422e8a0b3da7574dae7fa0baf005e504063b3", + "9ad8e17325408eddb6edee6147f13856ad819bb7532668b605a24a2d958f88bd5c169e56dc4b2f89ffd325f6006d820b", + "c704cfe7a1a53208ca9526cd24251e0acdc252ecd978eee05acd16425cfb404ea81f5a9e2e5e97784d63ee6a0618a398", + "d4fe8586fd8f858dd2e4dee0bafc19b4c12b4e2a856054abc4b14927354931675cdcaf942267f204ea706c19f7beefc4", + "9b7168b4494a80a86408e6b9dc4e5a1837c85dd8ff452ed410f2832959c08c8c0d040a892eb9a755776372d4a8732315" + }; + + final static String[] digests512 = { + "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", + "d135bb84d0439dbac432247ee573a23ea7d3c9deb2a968eb31d47c4fb45f1ef4422d6c531b5b9bd6f449ebcc449ea94d0a8f05f62130fda612da53c79659f609", + "ab7192d2b11f51c7dd744e7b3441febf397ca07bf812cceae122ca4ded6387889064f8db9230f173f6d1ab6e24b6e50f065b039f799f5592360a6558eb52d760", + "34341ead153aa1d1fdcf6cf624c2b4f6894b6fd16dc38bd4ec971ac0385ad54fafcb2e0ed86a1e509456f4246fdcb02c3172824cd649d9ad54c51f7fb49ea67c", + "dc44d4f4d36b07ab5fc04016cbe53548e5a7778671c58a43cb379fd00c06719b8073141fc22191ffc3db5f8b8983ae8341fa37f18c1c969664393aa5ceade64e", + "3e122edaf37398231cfaca4c7c216c9d66d5b899ec1d7ac617c40c7261906a45fc01617a021e5da3bd8d4182695b5cb785a28237cbb167590e34718e56d8aab8" + }; + + // test vectors from http://www.di-mgt.com.au/hmac_sha3_testvectors.html + final static byte[][] macKeys = + { + Hex.decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + Hex.decode("4a656665"), + Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + Hex.decode("0102030405060708090a0b0c0d0e0f10111213141516171819"), + Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaa"), + Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaa"), + Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + }; + + final static String[] macData = + { + "4869205468657265", + "7768617420646f2079612077616e7420666f72206e6f7468696e673f", + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + + "dddddddddddddddddddddddddddddddddddd", + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a" + + "65204b6579202d2048617368204b6579204669727374", + "5468697320697320612074657374207573696e672061206c6172676572207468" + + "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" + + "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" + + "647320746f20626520686173686564206265666f7265206265696e6720757365" + + "642062792074686520484d414320616c676f726974686d2e", + "5468697320697320612074657374207573696e672061206c6172676572207468" + + "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" + + "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" + + "647320746f20626520686173686564206265666f7265206265696e6720757365\n" + + "642062792074686520484d414320616c676f726974686d2e" + }; + + final static String[] mac224 = + { + "b73d595a2ba9af815e9f2b4e53e78581ebd34a80b3bbaac4e702c4cc", + "e824fec96c074f22f99235bb942da1982664ab692ca8501053cbd414", + "770df38c99d6e2bacd68056dcfe07d4c89ae20b2686a6185e1faa449", + "305a8f2dfb94bad28861a03cbc4d590febe775c58cb4961c28428a0b", + "e7a52dfa45f95a217c100066b239aa8ad519be9b35d667268b1b57ff", + "ba13009405a929f398b348885caa5419191bb948ada32194afc84104", + "92649468be236c3c72c189909c063b13f994be05749dc91310db639e" + }; + + final static String[] mac256 = + { + "9663d10c73ee294054dc9faf95647cb99731d12210ff7075fb3d3395abfb9821", + "aa9aed448c7abc8b5e326ffa6a01cdedf7b4b831881468c044ba8dd4566369a1", + "95f43e50f8df80a21977d51a8db3ba572dcd71db24687e6f86f47c1139b26260", + "6331ba9b4af5804a68725b3663eb74814494b63c6093e35fb320a85d507936fd", + "b4d0cdee7ec2ba81a88b86918958312300a15622377929a054a9ce3ae1fac2b6", + "1fdc8cb4e27d07c10d897dec39c217792a6e64fa9c63a77ce42ad106ef284e02", + "fdaa10a0299aecff9bb411cf2d7748a4022e4a26be3fb5b11b33d8c2b7ef5484" + }; + + final static String[] mac384 = + { + "892dfdf5d51e4679bf320cd16d4c9dc6f749744608e003add7fba894acff87361efa4e5799be06b6461f43b60ae97048", + "5af5c9a77a23a6a93d80649e562ab77f4f3552e3c5caffd93bdf8b3cfc6920e3023fc26775d9df1f3c94613146ad2c9d", + "4243c29f2201992ff96441e3b91ff81d8c601d706fbc83252684a4bc51101ca9b2c06ddd03677303c502ac5331752a3c", + "b730724d3d4090cda1be799f63acbbe389fef7792fc18676fa5453aab398664650ed029c3498bbe8056f06c658e1e693", + "d62482ef601d7847439b55236e9679388ffcd53c62cd126f39be6ea63de762e26cd5974cb9a8de401b786b5555040f6f", + "4860ea191ac34994cf88957afe5a836ef36e4cc1a66d75bf77defb7576122d75f60660e4cf731c6effac06402787e2b9", + "fe9357e3cfa538eb0373a2ce8f1e26ad6590afdaf266f1300522e8896d27e73f654d0631c8fa598d4bb82af6b744f4f5" + }; + + final static String[] mac512 = + { + "8852c63be8cfc21541a4ee5e5a9a852fc2f7a9adec2ff3a13718ab4ed81aaea0b87b7eb397323548e261a64e7fc75198f6663a11b22cd957f7c8ec858a1c7755", + "c2962e5bbe1238007852f79d814dbbecd4682e6f097d37a363587c03bfa2eb0859d8d9c701e04cececfd3dd7bfd438f20b8b648e01bf8c11d26824b96cebbdcb", + "eb0ed9580e0ec11fc66cbb646b1be904eaff6da4556d9334f65ee4b2c85739157bae9027c51505e49d1bb81cfa55e6822db55262d5a252c088a29a5e95b84a66", + "b46193bb59f4f696bf702597616da91e2a4558a593f4b015e69141ba81e1e50ea580834c2b87f87baa25a3a03bfc9bb389847f2dc820beae69d30c4bb75369cb", + "d05888a6ebf8460423ea7bc85ea4ffda847b32df32291d2ce115fd187707325c7ce4f71880d91008084ce24a38795d20e6a28328a0f0712dc38253370da3ebb5", + "2c6b9748d35c4c8db0b4407dd2ed2381f133bdbd1dfaa69e30051eb6badfcca64299b88ae05fdbd3dd3dd7fe627e42e39e48b0fe8c7f1e85f2dbd52c2d753572", + "6adc502f14e27812402fc81a807b28bf8a53c87bea7a1df6256bf66f5de1a4cb741407ad15ab8abc136846057f881969fbb159c321c904bfb557b77afb7778c8" + }; + + final static KeyParameter truncKey = new KeyParameter(Hex.decode("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c")); + final static byte[] truncData = Hex.decode("546573742057697468205472756e636174696f6e"); + + final static byte[] trunc224 = Hex.decode("f52bbcfd654264e7133085c5e69b72c3"); + final static byte[] trunc256 = Hex.decode("745e7e687f8335280d54202ef13cecc6"); + final static byte[] trunc384 = Hex.decode("fa9aea2bc1e181e47cbb8c3df243814d"); + final static byte[] trunc512 = Hex.decode("04c929fead434bba190dacfa554ce3f5"); + + final static byte[] xtremeData = Hex.decode("61626364656667686263646566676869636465666768696a6465666768696a6b65666768696a6b6c666768696a6b6c6d6768696a6b6c6d6e68696a6b6c6d6e6f"); + + KeccakDigestTest() + { + } + + public String getName() + { + return "Keccak"; + } + + private void testDigest(Digest digest, String[] expected) + { + byte[] hash = new byte[digest.getDigestSize()]; + + for (int i = 0; i != messages.length; i++) + { + if (messages.length != 0) + { + byte[] data = Hex.decode(messages[i]); + + digest.update(data, 0, data.length); + } + + digest.doFinal(hash, 0); + + if (!Arrays.areEqual(Hex.decode(expected[i]), hash)) + { + fail("Keccak mismatch on " + digest.getAlgorithmName() + " index " + i); + } + } + + byte[] k64 = new byte[1024 * 64]; + + for (int i = 0; i != k64.length; i++) + { + k64[i] = (byte)'a'; + } + + digest.update(k64, 0, k64.length); + + digest.doFinal(hash, 0); + + if (!Arrays.areEqual(Hex.decode(expected[messages.length]), hash)) + { + fail("Keccak mismatch on " + digest.getAlgorithmName() + " 64k a"); + } + + for (int i = 0; i != k64.length; i++) + { + digest.update((byte)'a'); + } + + digest.doFinal(hash, 0); + + if (!Arrays.areEqual(Hex.decode(expected[messages.length]), hash)) + { + fail("Keccak mismatch on " + digest.getAlgorithmName() + " 64k a single"); + } + + + for (int i = 0; i != k64.length; i++) + { + k64[i] = (byte)('a' + (i % 26)); + } + + digest.update(k64, 0, k64.length); + + digest.doFinal(hash, 0); + + if (!Arrays.areEqual(Hex.decode(expected[messages.length + 1]), hash)) + { + fail("Keccak mismatch on " + digest.getAlgorithmName() + " 64k alpha"); + } + + for (int i = 0; i != 64; i++) + { + digest.update(k64[i * 1024]); + digest.update(k64, i * 1024 + 1, 1023); + } + + digest.doFinal(hash, 0); + + if (!Arrays.areEqual(Hex.decode(expected[messages.length + 1]), hash)) + { + fail("Keccak mismatch on " + digest.getAlgorithmName() + " 64k chunked alpha"); + } + + testDigestDoFinal(digest); + + // + // extremely long data test + // +// System.out.println("Starting very long"); +// for (int i = 0; i != 16384; i++) +// { +// for (int j = 0; j != 1024; j++) +// { +// digest.update(xtremeData, 0, xtremeData.length); +// } +// } +// +// digest.doFinal(hash, 0); +// +// if (!Arrays.areEqual(Hex.decode(expected[messages.length + 2]), hash)) +// { +// fail("Keccak mismatch on " + digest.getAlgorithmName() + " extreme data test"); +// } +// System.out.println("Done"); + } + + private void testDigestDoFinal(Digest digest) + { + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + for (int i = 0; i <= digest.getDigestSize(); ++i) + { + byte[] cmp = new byte[2 * digest.getDigestSize()]; + System.arraycopy(hash, 0, cmp, i, hash.length); + + byte[] buf = new byte[2 * digest.getDigestSize()]; + digest.doFinal(buf, i); + + if (!Arrays.areEqual(cmp, buf)) + { + fail("Keccak offset doFinal on " + digest.getAlgorithmName()); + } + } + } + + private void testMac(Digest digest, byte[][] keys, String[] data, String[] expected, byte[] truncExpected) + { + Mac mac = new HMac(digest); + + for (int i = 0; i != keys.length; i++) + { + mac.init(new KeyParameter(keys[i])); + + byte[] mData = Hex.decode(data[i]); + + mac.update(mData, 0, mData.length); + + byte[] macV = new byte[mac.getMacSize()]; + + mac.doFinal(macV, 0); + + if (!Arrays.areEqual(Hex.decode(expected[i]), macV)) + { + fail("Keccak HMAC mismatch on " + digest.getAlgorithmName()); + } + } + + mac = new HMac(digest); + + mac.init(truncKey); + + mac.update(truncData, 0, truncData.length); + + byte[] macV = new byte[mac.getMacSize()]; + + mac.doFinal(macV, 0); + + for (int i = 0; i != truncExpected.length; i++) + { + if (macV[i] != truncExpected[i]) + { + fail("mismatch on truncated HMAC for " + digest.getAlgorithmName()); + } + } + } + + public void performTest() throws Exception + { + testDigest(new KeccakDigest(), digests288); + testDigest(new KeccakDigest(224), digests224); + testDigest(new KeccakDigest(256), digests256); + testDigest(new KeccakDigest(384), digests384); + testDigest(new KeccakDigest(512), digests512); + + testMac(new KeccakDigest(224), macKeys, macData, mac224, trunc224); + testMac(new KeccakDigest(256), macKeys, macData, mac256, trunc256); + testMac(new KeccakDigest(384), macKeys, macData, mac384, trunc384); + testMac(new KeccakDigest(512), macKeys, macData, mac512, trunc512); + } + + protected Digest cloneDigest(Digest digest) + { + return new KeccakDigest((KeccakDigest)digest); + } + + public static void main( + String[] args) + { + runTest(new KeccakDigestTest()); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/RNGUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/RNGUtils.java new file mode 100644 index 00000000..166c49da --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/RNGUtils.java @@ -0,0 +1,11 @@ +package org.bouncycastle.crypto.test; + +import java.util.Random; + +class RNGUtils +{ + public static int nextInt(Random rng, int n) + { + return rng.nextInt(n); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SHAKEDigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SHAKEDigestTest.java new file mode 100644 index 00000000..a3ec2eba --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SHAKEDigestTest.java @@ -0,0 +1,305 @@ +package org.bouncycastle.crypto.test; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.bouncycastle.crypto.digests.SHAKEDigest; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +/** + * SHAKE Digest Test + */ +public class SHAKEDigestTest + extends SimpleTest +{ + static class MySHAKEDigest extends SHAKEDigest + { + MySHAKEDigest(int bitLength) + { + super(bitLength); + } + + int myDoFinal(byte[] out, int outOff, int outLen, byte partialByte, int partialBits) + { + return doFinal(out, outOff, outLen, partialByte, partialBits); + } + } + + SHAKEDigestTest() + { + } + + public String getName() + { + return "SHAKE"; + } + + public void performTest() throws Exception + { + testVectors(); + } + + public void testVectors() throws Exception + { + BufferedReader r = new BufferedReader(new InputStreamReader( + getClass().getResourceAsStream("SHAKETestVectors.txt"))); + + String line; + while (null != (line = readLine(r))) + { + if (line.length() != 0) + { + TestVector v = readTestVector(r, line); + runTestVector(v); + } + } + + r.close(); + } + + private MySHAKEDigest createDigest(String algorithm) throws Exception + { + if (algorithm.startsWith("SHAKE-")) + { + int bits = parseDecimal(algorithm.substring("SHAKE-".length())); + return new MySHAKEDigest(bits); + } + throw new IllegalArgumentException("Unknown algorithm: " + algorithm); + } + + private byte[] decodeBinary(String block) + { + int bits = block.length(); + int fullBytes = bits / 8; + int totalBytes = (bits + 7) / 8; + byte[] result = new byte[totalBytes]; + + for (int i = 0; i < fullBytes; ++i) + { + String byteStr = reverse(block.substring(i * 8, (i + 1) * 8)); + result[i] = (byte)parseBinary(byteStr); + } + + if (totalBytes > fullBytes) + { + String byteStr = reverse(block.substring(fullBytes * 8)); + result[fullBytes] = (byte)parseBinary(byteStr); + } + + return result; + } + + private int parseBinary(String s) + { + return Integer.parseInt(s, 2); + } + + private int parseDecimal(String s) + { + return Integer.parseInt(s); + } + + private String readBlock(BufferedReader r) throws IOException + { + StringBuffer b = new StringBuffer(); + String line; + while ((line = readBlockLine(r)) != null) + { + b.append(line); + } + return b.toString(); + } + + private String readBlockLine(BufferedReader r) throws IOException + { + String line = readLine(r); + if (line == null || line.length() == 0) + { + return null; + } + + char[] chars = line.toCharArray(); + + int pos = 0; + for (int i = 0; i != chars.length; i++) + { + if (chars[i] != ' ') + { + chars[pos++] = chars[i]; + } + } + + return new String(chars, 0, pos); + } + + private TestVector readTestVector(BufferedReader r, String header) throws IOException + { + String[] parts = splitAround(header, TestVector.SAMPLE_OF); + + String algorithm = parts[0]; + int bits = parseDecimal(stripFromChar(parts[1], '-')); + + skipUntil(r, TestVector.MSG_HEADER); + String messageBlock = readBlock(r); + if (messageBlock.length() != bits) + { + throw new IllegalStateException("Test vector length mismatch"); + } + byte[] message = decodeBinary(messageBlock); + + skipUntil(r, TestVector.OUTPUT_HEADER); + byte[] output = Hex.decode(readBlock(r)); + + return new TestVector(algorithm, bits, message, output); + } + + private String readLine(BufferedReader r) throws IOException + { + String line = r.readLine(); + return line == null ? null : stripFromChar(line, '#').trim(); + } + + private String requireLine(BufferedReader r) throws IOException + { + String line = readLine(r); + if (line == null) + { + throw new EOFException(); + } + return line; + } + + private String reverse(String s) + { + return new StringBuffer(s).reverse().toString(); + } + + private void runTestVector(TestVector v) throws Exception + { + int bits = v.getBits(); + int partialBits = bits % 8; + + byte[] expected = v.getOutput(); + +// System.out.println(v.getAlgorithm() + " " + bits + "-bit"); +// System.out.println(Hex.toHexString(v.getMessage()).toUpperCase()); +// System.out.println(Hex.toHexString(expected).toUpperCase()); + + int outLen = expected.length; + + MySHAKEDigest d = createDigest(v.getAlgorithm()); + byte[] output = new byte[outLen]; + + byte[] m = v.getMessage(); + if (partialBits == 0) + { + d.update(m, 0, m.length); + d.doFinal(output, 0, outLen); + } + else + { + d.update(m, 0, m.length - 1); + d.myDoFinal(output, 0, outLen, m[m.length - 1], partialBits); + } + + if (!Arrays.areEqual(expected, output)) + { + fail(v.getAlgorithm() + " " + v.getBits() + "-bit test vector hash mismatch"); +// System.err.println(v.getAlgorithm() + " " + v.getBits() + "-bit test vector hash mismatch"); +// System.err.println(Hex.toHexString(output).toUpperCase()); + } + } + + private void skipUntil(BufferedReader r, String header) throws IOException + { + String line; + do + { + line = requireLine(r); + } + while (line.length() == 0); + if (!line.equals(header)) + { + throw new IOException("Expected: " + header); + } + } + + private String[] splitAround(String s, String separator) + { + List strings = new ArrayList(); + + String remaining = s; + int index; + + while ((index = remaining.indexOf(separator)) > 0) + { + strings.add(remaining.substring(0, index)); + remaining = remaining.substring(index + separator.length()); + } + strings.add(remaining); + + return (String[])strings.toArray(new String[strings.size()]); + } + + private String stripFromChar(String s, char c) + { + int i = s.indexOf(c); + if (i >= 0) + { + s = s.substring(0, i); + } + return s; + } + + public static void main( + String[] args) + { + runTest(new SHAKEDigestTest()); + } + + private static class TestVector + { + private static String SAMPLE_OF = " sample of "; + private static String MSG_HEADER = "Msg as bit string"; + private static String OUTPUT_HEADER = "Output val is"; + + private String algorithm; + private int bits; + private byte[] message; + private byte[] output; + + private TestVector(String algorithm, int bits, byte[] message, byte[] output) + { + this.algorithm = algorithm; + this.bits = bits; + this.message = message; + this.output = output; + } + + public String getAlgorithm() + { + return algorithm; + } + + public int getBits() + { + return bits; + } + + public byte[] getMessage() + { + return message; + } + + public byte[] getOutput() + { + return output; + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java new file mode 100644 index 00000000..7c12e5b1 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java @@ -0,0 +1,81 @@ +package org.bouncycastle.crypto.test; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.engines.SM4Engine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +/** + * SM4 tester, vectors from <a href="http://eprint.iacr.org/2008/329.pdf">http://eprint.iacr.org/2008/329.pdf</a> + */ +public class SM4Test + extends CipherTest +{ + static SimpleTest[] tests = { + new BlockCipherVectorTest(0, new SM4Engine(), + new KeyParameter(Hex.decode("0123456789abcdeffedcba9876543210")), + "0123456789abcdeffedcba9876543210", + "681edf34d206965e86b3e94f536e4246") + }; + + SM4Test() + { + super(tests, new SM4Engine(), new KeyParameter(new byte[16])); + } + + public void performTest() + throws Exception + { + super.performTest(); + + test1000000(); + } + + private void test1000000() + { + byte[] plain = Hex.decode("0123456789abcdeffedcba9876543210"); + byte[] key = Hex.decode("0123456789abcdeffedcba9876543210"); + byte[] cipher = Hex.decode("595298c7c6fd271f0402f804c33d3f66"); + byte[] buf = new byte[16]; + + BlockCipher engine = new SM4Engine(); + + engine.init(true, new KeyParameter(key)); + + System.arraycopy(plain, 0, buf, 0, buf.length); + + for (int i = 0; i != 1000000; i++) + { + engine.processBlock(buf, 0, buf, 0); + } + + if (!areEqual(cipher, buf)) + { + fail("1000000 encryption test failed"); + } + + engine.init(false, new KeyParameter(key)); + + for (int i = 0; i != 1000000; i++) + { + engine.processBlock(buf, 0, buf, 0); + } + + if (!areEqual(plain, buf)) + { + fail("1000000 decryption test failed"); + } + } + + public String getName() + { + return "SM4"; + } + + public static void main( + String[] args) + { + runTest(new SM4Test()); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/TnepresTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/TnepresTest.java new file mode 100644 index 00000000..6b873587 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/TnepresTest.java @@ -0,0 +1,144 @@ +package org.bouncycastle.crypto.test; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.engines.TnepresEngine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +/** + * Test vectors based on Floppy 4 of the Serpent AES submission. + */ +public class TnepresTest + extends CipherTest +{ + static SimpleTest[] tests = + { + new BlockCipherVectorTest(0, new TnepresEngine(), + new KeyParameter(Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")), + "00000000000000000000000000000000", "8910494504181950f98dd998a82b6749"), + new BlockCipherVectorTest(1, new TnepresEngine(), + new KeyParameter(Hex.decode("00000000000000000000000000000000")), + "80000000000000000000000000000000", "10b5ffb720b8cb9002a1142b0ba2e94a"), + new BlockCipherVectorTest(2, new TnepresEngine(), + new KeyParameter(Hex.decode("00000000000000000000000000000000")), + "00000000008000000000000000000000", "4f057a42d8d5bd9746e434680ddcd5e5"), + new BlockCipherVectorTest(3, new TnepresEngine(), + new KeyParameter(Hex.decode("00000000000000000000000000000000")), + "00000000000000000000400000000000", "99407bf8582ef12550886ef5b6f169b9"), + new BlockCipherVectorTest(4, new TnepresEngine(), + new KeyParameter(Hex.decode("000000000000000000000000000000000000000000000000")), + "40000000000000000000000000000000", "d522a3b8d6d89d4d2a124fdd88f36896"), + new BlockCipherVectorTest(5, new TnepresEngine(), + new KeyParameter(Hex.decode("000000000000000000000000000000000000000000000000")), + "00000000000200000000000000000000", "189b8ec3470085b3da97e82ca8964e32"), + new BlockCipherVectorTest(6, new TnepresEngine(), + new KeyParameter(Hex.decode("000000000000000000000000000000000000000000000000")), + "00000000000000000000008000000000", "f77d868cf760b9143a89809510ccb099"), + new BlockCipherVectorTest(7, new TnepresEngine(), + new KeyParameter(Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")), + "08000000000000000000000000000000", "d43b7b981b829342fce0e3ec6f5f4c82"), + new BlockCipherVectorTest(8, new TnepresEngine(), + new KeyParameter(Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")), + "00000000000000000100000000000000", "0bf30e1a0c33ccf6d5293177886912a7"), + new BlockCipherVectorTest(9, new TnepresEngine(), + new KeyParameter(Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")), + "00000000000000000000000000000001", "6a7f3b805d2ddcba49b89770ade5e507"), + new BlockCipherVectorTest(10, new TnepresEngine(), + new KeyParameter(Hex.decode("80000000000000000000000000000000")), + "00000000000000000000000000000000", "49afbfad9d5a34052cd8ffa5986bd2dd"), + new BlockCipherVectorTest(11, new TnepresEngine(), + new KeyParameter(Hex.decode("000000000000000000000000004000000000000000000000")), + "00000000000000000000000000000000", "ba8829b1de058c4b48615d851fc74f17"), + new BlockCipherVectorTest(12, new TnepresEngine(), + new KeyParameter(Hex.decode("0000000000000000000000000000000000000000000000000000000100000000")), + "00000000000000000000000000000000", "89f64377bf1e8a46c8247044e8056a98"), +/* + new BlockCipherMonteCarloTest(13, 10000, new TnepresEngine(), + new KeyParameter(Hex.decode("47f5f881daab9b67b43bd1342e339c19")), + "7a4f7db38c52a8b711b778a38d203b6b", "003380e19f10065740394f48e2fe80b7"), +*/ + new BlockCipherMonteCarloTest(13, 100, new TnepresEngine(), + new KeyParameter(Hex.decode("47f5f881daab9b67b43bd1342e339c19")), + "7a4f7db38c52a8b711b778a38d203b6b", "4db75303d815c2f7cc6ca935d1c5a046"), +/* + new BlockCipherMonteCarloTest(14, 10000, new TnepresEngine(), + new KeyParameter(Hex.decode("31fba879ebc5e80df35e6fa33eaf92d6")), + "70a05e12f74589009692a337f53ff614", "afb5425426906db26b70bdf842ac5400"), +*/ + new BlockCipherMonteCarloTest(14, 100, new TnepresEngine(), + new KeyParameter(Hex.decode("31fba879ebc5e80df35e6fa33eaf92d6")), + "70a05e12f74589009692a337f53ff614", "fc53a50f4d3bc9836001893d2f41742d"), +/* + new BlockCipherMonteCarloTest(15, 10000, new TnepresEngine(), + new KeyParameter(Hex.decode("bde6dd392307984695aee80e574f9977caae9aa78eda53e8")), + "9cc523d034a93740a0aa4e2054bb34d8", "1949d506ada7de1f1344986e8ea049b2"), +*/ + new BlockCipherMonteCarloTest(15, 100, new TnepresEngine(), + new KeyParameter(Hex.decode("bde6dd392307984695aee80e574f9977caae9aa78eda53e8")), + "9cc523d034a93740a0aa4e2054bb34d8", "77117e6a9e80f40b2a36b7d755573c2d"), +/* + new BlockCipherMonteCarloTest(16, 10000, new TnepresEngine(), + new KeyParameter(Hex.decode("60f6f8ad4290699dc50921a1bbcca92da914e7d9cf01a9317c79c0af8f2487a1")), + "ee1a61106fae2d381d686cbf854bab65", "e57f45559027cb1f2ed9603d814e1c34"), +*/ + new BlockCipherMonteCarloTest(16, 100, new TnepresEngine(), + new KeyParameter(Hex.decode("60f6f8ad4290699dc50921a1bbcca92da914e7d9cf01a9317c79c0af8f2487a1")), + "ee1a61106fae2d381d686cbf854bab65", "dcd7f13ea0dcdfd0139d1a42e2ffb84b") + }; + + TnepresTest() + { + super(tests, new TnepresEngine(), new KeyParameter(new byte[32])); + } + + public void performTest() + throws Exception + { + super.performTest(); + + doCbcMonte(new byte[16], new byte[16], new byte[16], Hex.decode("9ea101ecebaa41c712bcb0d9bab3e2e4")); + doCbcMonte(Hex.decode("9ea101ecebaa41c712bcb0d9bab3e2e4"), Hex.decode("9ea101ecebaa41c712bcb0d9bab3e2e4"), Hex.decode("b4813d8a66244188b9e92c75913fa2f4"), Hex.decode("f86b2c265b9c75869f31e2c684c13e9f")); + } + + private void doCbcMonte(byte[] key, byte[] iv, byte[] pt, byte[] expected) + { + BlockCipher c = new TnepresEngine(); + + byte[] ct = new byte[16]; + + System.arraycopy(iv, 0, ct, 0, 16); + + for (int i = 0; i < 10000; i++) + { + for (int k = 0; k != iv.length; k++) + { + iv[k] ^= pt[k]; + } + System.arraycopy(ct, 0, pt, 0, 16); + + c.init(true, new KeyParameter(key)); + + c.processBlock(iv, 0, ct, 0); + + System.arraycopy(ct, 0, iv, 0, 16); + } + + if (!Arrays.areEqual(expected, ct)) + { + fail("CBC monte test failed"); + } + } + + public String getName() + { + return "Tnepres"; + } + + public static void main( + String[] args) + { + runTest(new TnepresTest()); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/CAVPListener.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/CAVPListener.java new file mode 100644 index 00000000..81f86e57 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/CAVPListener.java @@ -0,0 +1,18 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.util.Properties; + +public interface CAVPListener +{ + public void setup(); + + public void receiveStart(String name); + + public void receiveCAVPVectors(String name, Properties config, Properties vectors); + + public void receiveCommentLine(String commentLine); + + public void receiveEnd(); + + public void tearDown(); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/CAVPReader.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/CAVPReader.java new file mode 100644 index 00000000..9cd33afc --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/CAVPReader.java @@ -0,0 +1,152 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.digests.SHA224Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.digests.SHA384Digest; +import org.bouncycastle.crypto.digests.SHA512Digest; +import org.bouncycastle.crypto.engines.AESFastEngine; +import org.bouncycastle.crypto.engines.DESedeEngine; +import org.bouncycastle.crypto.macs.CMac; +import org.bouncycastle.crypto.macs.HMac; + +public class CAVPReader +{ + + private static final Pattern COMMENT_PATTERN = Pattern.compile("^\\s*\\#\\s*(.*)$"); + private static final Pattern CONFIG_PATTERN = Pattern.compile("^\\s*+\\[\\s*+(.*?)\\s*+=\\s*+(.*?)\\s*+\\]\\s*+$"); + private static final Pattern VECTOR_PATTERN = Pattern.compile("^\\s*+(.*?)\\s*+=\\s*+(.*?)\\s*+$"); + private static final Pattern EMPTY_PATTERN = Pattern.compile("^\\s*+$"); + static final Pattern PATTERN_FOR_R = Pattern.compile("(\\d+)_BITS"); + private final CAVPListener listener; + private String name; + private BufferedReader lineReader; + + + public CAVPReader(CAVPListener listener) + { + this.listener = listener; + } + + public void setInput(String name, Reader reader) + { + this.name = name; + this.lineReader = new BufferedReader(reader); + } + + public void readAll() + throws IOException + { + + listener.setup(); + + Properties config = new Properties(); + + boolean startNewVector = true; + + Properties vectors = new Properties(); + + while (true) + { + final String line = lineReader.readLine(); + if (line == null) + { + listener.receiveEnd(); + break; + } + + final Matcher commentMatcher = COMMENT_PATTERN.matcher(line); + if (commentMatcher.matches()) + { + listener.receiveCommentLine(commentMatcher.group(1)); + continue; + } + + final Matcher configMatcher = CONFIG_PATTERN.matcher(line); + if (configMatcher.matches()) + { + config.put(configMatcher.group(1), configMatcher.group(2)); + continue; + } + + final Matcher vectorMatcher = VECTOR_PATTERN.matcher(line); + if (vectorMatcher.matches()) + { + vectors.put(vectorMatcher.group(1), vectorMatcher.group(2)); + startNewVector = false; + continue; + } + + final Matcher emptyMatcher = EMPTY_PATTERN.matcher(line); + if (emptyMatcher.matches()) + { + if (startNewVector) + { + continue; + } + + listener.receiveCAVPVectors(name, config, vectors); + vectors = new Properties(); + startNewVector = true; + } + } + + listener.tearDown(); + } + + static Mac createPRF(Properties config) + { + final Mac prf; + if (config.getProperty("PRF").matches("CMAC_AES\\d\\d\\d")) + { + BlockCipher blockCipher = new AESFastEngine(); + prf = new CMac(blockCipher); + } + else if (config.getProperty("PRF").matches("CMAC_TDES\\d")) + { + BlockCipher blockCipher = new DESedeEngine(); + prf = new CMac(blockCipher); + } + else if (config.getProperty("PRF").matches("HMAC_SHA1")) + { + Digest digest = new SHA1Digest(); + prf = new HMac(digest); + } + else if (config.getProperty("PRF").matches("HMAC_SHA224")) + { + Digest digest = new SHA224Digest(); + prf = new HMac(digest); + } + else if (config.getProperty("PRF").matches("HMAC_SHA256")) + { + Digest digest = new SHA256Digest(); + prf = new HMac(digest); + } + else if (config.getProperty("PRF").matches("HMAC_SHA384")) + { + Digest digest = new SHA384Digest(); + prf = new HMac(digest); + } + else if (config.getProperty("PRF").matches("HMAC_SHA512")) + { + Digest digest = new SHA512Digest(); + prf = new HMac(digest); + } + else + { + throw new IllegalStateException("Unknown Mac for PRF"); + } + return prf; + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFCounterTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFCounterTests.java new file mode 100644 index 00000000..81f10824 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFCounterTests.java @@ -0,0 +1,119 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Properties; +import java.util.regex.Matcher; + +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.KDFCounterBytesGenerator; +import org.bouncycastle.crypto.params.KDFCounterParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTestResult; +import org.bouncycastle.util.test.TestFailedException; + +public final class KDFCounterTests + implements CAVPListener +{ + private PrintWriter out; + + public void receiveCAVPVectors(String name, Properties config, + Properties vectors) + { + + // create Mac based PRF from PRF property, create the KDF + final Mac prf = CAVPReader.createPRF(config); + final KDFCounterBytesGenerator gen = new KDFCounterBytesGenerator(prf); + + + Matcher matcherForR = CAVPReader.PATTERN_FOR_R.matcher(config.getProperty("RLEN")); + if (!matcherForR.matches()) + { + throw new IllegalStateException("RLEN value should always match"); + } + final int r = Integer.parseInt(matcherForR.group(1)); + + final int count = Integer.parseInt(vectors.getProperty("COUNT")); + final int l = Integer.parseInt(vectors.getProperty("L")); + final byte[] ki = Hex.decode(vectors.getProperty("KI")); + + //Three variants of this KDF are possible, with the counter before the fixed data, after the fixed data, or in the middle of the fixed data. + if (config.getProperty("CTRLOCATION").matches("BEFORE_FIXED")) + { + final byte[] fixedInputData = Hex.decode(vectors.getProperty("FixedInputData")); + final KDFCounterParameters params = new KDFCounterParameters(ki, null, fixedInputData, r); + gen.init(params); + } + else if (config.getProperty("CTRLOCATION").matches("AFTER_FIXED")) + { + final byte[] fixedInputData = Hex.decode(vectors.getProperty("FixedInputData")); + final KDFCounterParameters params = new KDFCounterParameters(ki, fixedInputData, null, r); + gen.init(params); + } + else if (config.getProperty("CTRLOCATION").matches("MIDDLE_FIXED")) + { + final byte[] DataBeforeCtrData = Hex.decode(vectors.getProperty("DataBeforeCtrData")); + final byte[] DataAfterCtrData = Hex.decode(vectors.getProperty("DataAfterCtrData")); + final KDFCounterParameters params = new KDFCounterParameters(ki, DataBeforeCtrData, DataAfterCtrData, r); + gen.init(params); + } + else + { + return; // Unknown CTRLOCATION + } + + + final byte[] koGenerated = new byte[l / 8]; + gen.generateBytes(koGenerated, 0, koGenerated.length); + + final byte[] koVectors = Hex.decode(vectors.getProperty("KO")); + + compareKO(name, config, count, koGenerated, koVectors); + } + + private static void compareKO( + String name, Properties config, int test, byte[] calculatedOKM, byte[] testOKM) + { + + if (!Arrays.areEqual(calculatedOKM, testOKM)) + { + throw new TestFailedException(new SimpleTestResult( + false, name + " using " + config + " test " + test + " failed")); + + } + } + + public void receiveCommentLine(String commentLine) + { + // out.println("# " + commentLine); + } + + public void receiveStart(String name) + { + // do nothing + } + + public void receiveEnd() + { + out.println(" *** *** *** "); + } + + public void setup() + { + try + { + out = new PrintWriter(new FileWriter("KDFCTR.gen")); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + + public void tearDown() + { + out.close(); + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFDoublePipelineCounterTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFDoublePipelineCounterTests.java new file mode 100644 index 00000000..5b3df023 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFDoublePipelineCounterTests.java @@ -0,0 +1,107 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Properties; +import java.util.regex.Matcher; + +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.KDFDoublePipelineIterationBytesGenerator; +import org.bouncycastle.crypto.params.KDFDoublePipelineIterationParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTestResult; +import org.bouncycastle.util.test.TestFailedException; + +public final class KDFDoublePipelineCounterTests + implements CAVPListener +{ + private PrintWriter out; + + public void receiveCAVPVectors(String name, Properties config, + Properties vectors) + { + // out.println(" === " + name + " === "); + // out.println(" --- config --- "); + // out.println(config); + // out.println(" --- vectors --- "); + // out.println(vectors); + + // always skip AFTER_FIXED + if (!config.getProperty("CTRLOCATION").matches("AFTER_ITER")) + { + return; + } + + // create Mac based PRF from PRF property, create the KDF + final Mac prf = CAVPReader.createPRF(config); + final KDFDoublePipelineIterationBytesGenerator gen = new KDFDoublePipelineIterationBytesGenerator(prf); + + + Matcher matcherForR = CAVPReader.PATTERN_FOR_R.matcher(config.getProperty("RLEN")); + if (!matcherForR.matches()) + { + throw new IllegalStateException("RLEN value should always match"); + } + final int r = Integer.parseInt(matcherForR.group(1)); + + final int count = Integer.parseInt(vectors.getProperty("COUNT")); + final int l = Integer.parseInt(vectors.getProperty("L")); + final byte[] ki = Hex.decode(vectors.getProperty("KI")); + final byte[] fixedInputData = Hex.decode(vectors.getProperty("FixedInputData")); + final KDFDoublePipelineIterationParameters params = KDFDoublePipelineIterationParameters.createWithCounter(ki, fixedInputData, r); + gen.init(params); + + final byte[] koGenerated = new byte[l / 8]; + gen.generateBytes(koGenerated, 0, koGenerated.length); + + final byte[] koVectors = Hex.decode(vectors.getProperty("KO")); + + compareKO(name, config, count, koGenerated, koVectors); + } + + private static void compareKO( + String name, Properties config, int test, byte[] calculatedOKM, byte[] testOKM) + { + + if (!Arrays.areEqual(calculatedOKM, testOKM)) + { + throw new TestFailedException(new SimpleTestResult( + false, name + " using " + config + " test " + test + " failed")); + + } + } + + public void receiveCommentLine(String commentLine) + { + // out.println("# " + commentLine); + } + + public void receiveStart(String name) + { + // do nothing + } + + public void receiveEnd() + { + out.println(" *** *** *** "); + } + + public void setup() + { + try + { + out = new PrintWriter(new FileWriter("KDFDblPipelineCounter.gen")); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + + public void tearDown() + { + out.close(); + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFDoublePipelineIterationNoCounterTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFDoublePipelineIterationNoCounterTests.java new file mode 100644 index 00000000..3923f9a1 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFDoublePipelineIterationNoCounterTests.java @@ -0,0 +1,88 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Properties; + +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.KDFDoublePipelineIterationBytesGenerator; +import org.bouncycastle.crypto.params.KDFDoublePipelineIterationParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTestResult; +import org.bouncycastle.util.test.TestFailedException; + +public final class KDFDoublePipelineIterationNoCounterTests + implements CAVPListener +{ + private PrintWriter out; + + public void receiveCAVPVectors(String name, Properties config, + Properties vectors) + { + + + // create Mac based PRF from PRF property, create the KDF + final Mac prf = CAVPReader.createPRF(config); + final KDFDoublePipelineIterationBytesGenerator gen = new KDFDoublePipelineIterationBytesGenerator(prf); + + final int count = Integer.parseInt(vectors.getProperty("COUNT")); + final int l = Integer.parseInt(vectors.getProperty("L")); + final byte[] ki = Hex.decode(vectors.getProperty("KI")); + final byte[] fixedInputData = Hex.decode(vectors.getProperty("FixedInputData")); + final KDFDoublePipelineIterationParameters params = KDFDoublePipelineIterationParameters.createWithoutCounter(ki, fixedInputData); + gen.init(params); + + final byte[] koGenerated = new byte[l / 8]; + gen.generateBytes(koGenerated, 0, koGenerated.length); + + final byte[] koVectors = Hex.decode(vectors.getProperty("KO")); + + compareKO(name, config, count, koGenerated, koVectors); + } + + private static void compareKO( + String name, Properties config, int test, byte[] calculatedOKM, byte[] testOKM) + { + + if (!Arrays.areEqual(calculatedOKM, testOKM)) + { + throw new TestFailedException(new SimpleTestResult( + false, name + " using " + config + " test " + test + " failed")); + + } + } + + public void receiveCommentLine(String commentLine) + { + // out.println("# " + commentLine); + } + + public void receiveStart(String name) + { + // do nothing + } + + public void receiveEnd() + { + out.println(" *** *** *** "); + } + + public void setup() + { + try + { + out = new PrintWriter(new FileWriter("KDFDblPipelineNoCounter.gen")); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + + public void tearDown() + { + out.close(); + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFFeedbackCounterTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFFeedbackCounterTests.java new file mode 100644 index 00000000..6f8a0fde --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFFeedbackCounterTests.java @@ -0,0 +1,108 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Properties; +import java.util.regex.Matcher; + +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.KDFFeedbackBytesGenerator; +import org.bouncycastle.crypto.params.KDFFeedbackParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTestResult; +import org.bouncycastle.util.test.TestFailedException; + +public final class KDFFeedbackCounterTests + implements CAVPListener +{ + private PrintWriter out; + + public void receiveCAVPVectors(String name, Properties config, + Properties vectors) + { + // out.println(" === " + name + " === "); + // out.println(" --- config --- "); + // out.println(config); + // out.println(" --- vectors --- "); + // out.println(vectors); + + // always skip AFTER_FIXED + if (!config.getProperty("CTRLOCATION").matches("AFTER_ITER")) + { + return; + } + + // create Mac based PRF from PRF property, create the KDF + final Mac prf = CAVPReader.createPRF(config); + final KDFFeedbackBytesGenerator gen = new KDFFeedbackBytesGenerator(prf); + + + Matcher matcherForR = CAVPReader.PATTERN_FOR_R.matcher(config.getProperty("RLEN")); + if (!matcherForR.matches()) + { + throw new IllegalStateException("RLEN value should always match"); + } + final int r = Integer.parseInt(matcherForR.group(1)); + + final int count = Integer.parseInt(vectors.getProperty("COUNT")); + final int l = Integer.parseInt(vectors.getProperty("L")); + final byte[] ki = Hex.decode(vectors.getProperty("KI")); + final byte[] iv = Hex.decode(vectors.getProperty("IV")); + final byte[] fixedInputData = Hex.decode(vectors.getProperty("FixedInputData")); + final KDFFeedbackParameters params = KDFFeedbackParameters.createWithCounter(ki, iv, fixedInputData, r); + gen.init(params); + + final byte[] koGenerated = new byte[l / 8]; + gen.generateBytes(koGenerated, 0, koGenerated.length); + + final byte[] koVectors = Hex.decode(vectors.getProperty("KO")); + + compareKO(name, config, count, koGenerated, koVectors); + } + + private static void compareKO( + String name, Properties config, int test, byte[] calculatedOKM, byte[] testOKM) + { + + if (!Arrays.areEqual(calculatedOKM, testOKM)) + { + throw new TestFailedException(new SimpleTestResult( + false, name + " using " + config + " test " + test + " failed")); + + } + } + + public void receiveCommentLine(String commentLine) + { + // out.println("# " + commentLine); + } + + public void receiveStart(String name) + { + // do nothing + } + + public void receiveEnd() + { + out.println(" *** *** *** "); + } + + public void setup() + { + try + { + out = new PrintWriter(new FileWriter("KDFFeedbackCounter.gen")); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + + public void tearDown() + { + out.close(); + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFFeedbackNoCounterTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFFeedbackNoCounterTests.java new file mode 100644 index 00000000..cd7d8b80 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/cavp/KDFFeedbackNoCounterTests.java @@ -0,0 +1,89 @@ +package org.bouncycastle.crypto.test.cavp; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Properties; + +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.KDFFeedbackBytesGenerator; +import org.bouncycastle.crypto.params.KDFFeedbackParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTestResult; +import org.bouncycastle.util.test.TestFailedException; + +public final class KDFFeedbackNoCounterTests + implements CAVPListener +{ + private PrintWriter out; + + public void receiveCAVPVectors(String name, Properties config, + Properties vectors) + { + + + // create Mac based PRF from PRF property, create the KDF + final Mac prf = CAVPReader.createPRF(config); + final KDFFeedbackBytesGenerator gen = new KDFFeedbackBytesGenerator(prf); + + final int count = Integer.parseInt(vectors.getProperty("COUNT")); + final int l = Integer.parseInt(vectors.getProperty("L")); + final byte[] ki = Hex.decode(vectors.getProperty("KI")); + final byte[] iv = Hex.decode(vectors.getProperty("IV")); + final byte[] fixedInputData = Hex.decode(vectors.getProperty("FixedInputData")); + final KDFFeedbackParameters params = KDFFeedbackParameters.createWithoutCounter(ki, iv, fixedInputData); + gen.init(params); + + final byte[] koGenerated = new byte[l / 8]; + gen.generateBytes(koGenerated, 0, koGenerated.length); + + final byte[] koVectors = Hex.decode(vectors.getProperty("KO")); + + compareKO(name, config, count, koGenerated, koVectors); + } + + private static void compareKO( + String name, Properties config, int test, byte[] calculatedOKM, byte[] testOKM) + { + + if (!Arrays.areEqual(calculatedOKM, testOKM)) + { + throw new TestFailedException(new SimpleTestResult( + false, name + " using " + config + " test " + test + " failed")); + + } + } + + public void receiveCommentLine(String commentLine) + { +// out.println("# " + commentLine); + } + + public void receiveStart(String name) + { + // do nothing + } + + public void receiveEnd() + { + out.println(" *** *** *** "); + } + + public void setup() + { + try + { + out = new PrintWriter(new FileWriter("KDFFeedbackNoCounter.gen")); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + + public void tearDown() + { + out.close(); + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueueInputStream.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueueInputStream.java new file mode 100644 index 00000000..34bb035a --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueueInputStream.java @@ -0,0 +1,63 @@ +package org.bouncycastle.crypto.tls; + +import java.io.InputStream; + +public class ByteQueueInputStream + extends InputStream +{ + private ByteQueue buffer; + + public ByteQueueInputStream() + { + buffer = new ByteQueue(); + } + + public void addBytes(byte[] bytes) + { + buffer.addData(bytes, 0, bytes.length); + } + + public int peek(byte[] buf) + { + int bytesToRead = Math.min(buffer.available(), buf.length); + buffer.read(buf, 0, bytesToRead, 0); + return bytesToRead; + } + + public int read() + { + if (buffer.available() == 0) + { + return -1; + } + return buffer.removeData(1, 0)[0] & 0xFF; + } + + public int read(byte[] b) + { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) + { + int bytesToRead = Math.min(buffer.available(), len); + buffer.removeData(b, off, bytesToRead, 0); + return bytesToRead; + } + + public long skip(long n) + { + int bytesToRemove = Math.min((int)n, buffer.available()); + buffer.removeData(bytesToRemove); + return bytesToRemove; + } + + public int available() + { + return buffer.available(); + } + + public void close() + { + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueueOutputStream.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueueOutputStream.java new file mode 100644 index 00000000..aa3b36a4 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueueOutputStream.java @@ -0,0 +1,32 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.OutputStream; + +import org.bouncycastle.crypto.tls.ByteQueue; + +public class ByteQueueOutputStream + extends OutputStream +{ + private ByteQueue buffer; + + public ByteQueueOutputStream() + { + buffer = new ByteQueue(); + } + + public ByteQueue getBuffer() + { + return buffer; + } + + public void write(int b) throws IOException + { + buffer.addData(new byte[]{ (byte)b }, 0, 1); + } + + public void write(byte[] b, int off, int len) throws IOException + { + buffer.addData(b, off, len); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/ByteQueueInputStreamTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/ByteQueueInputStreamTest.java new file mode 100644 index 00000000..de4d5598 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/ByteQueueInputStreamTest.java @@ -0,0 +1,127 @@ +package org.bouncycastle.crypto.tls.test; + +import org.bouncycastle.crypto.tls.ByteQueueInputStream; +import org.bouncycastle.util.Arrays; + +import junit.framework.TestCase; + +public class ByteQueueInputStreamTest + extends TestCase +{ + public void testAvailable() + { + ByteQueueInputStream in = new ByteQueueInputStream(); + + // buffer is empty + assertEquals(0, in.available()); + + // after adding once + in.addBytes(new byte[10]); + assertEquals(10, in.available()); + + // after adding more than once + in.addBytes(new byte[5]); + assertEquals(15, in.available()); + + // after reading a single byte + in.read(); + assertEquals(14, in.available()); + + // after reading into a byte array + in.read(new byte[4]); + assertEquals(10, in.available()); + + in.close();// so Eclipse doesn't whine about a resource leak + } + + public void testSkip() + { + ByteQueueInputStream in = new ByteQueueInputStream(); + + // skip when buffer is empty + assertEquals(0, in.skip(10)); + + // skip equal to available + in.addBytes(new byte[2]); + assertEquals(2, in.skip(2)); + assertEquals(0, in.available()); + + // skip less than available + in.addBytes(new byte[10]); + assertEquals(5, in.skip(5)); + assertEquals(5, in.available()); + + // skip more than available + assertEquals(5, in.skip(20)); + assertEquals(0, in.available()); + + in.close();// so Eclipse doesn't whine about a resource leak + } + + public void testRead() + { + ByteQueueInputStream in = new ByteQueueInputStream(); + in.addBytes(new byte[]{ 0x01, 0x02 }); + in.addBytes(new byte[]{ 0x03 }); + + assertEquals(0x01, in.read()); + assertEquals(0x02, in.read()); + assertEquals(0x03, in.read()); + assertEquals(-1, in.read()); + + in.close();// so Eclipse doesn't whine about a resource leak + } + + public void testReadArray() + { + ByteQueueInputStream in = new ByteQueueInputStream(); + in.addBytes(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }); + + byte[] buffer = new byte[5]; + + // read less than available into specified position + assertEquals(1, in.read(buffer, 2, 1)); + assertArrayEquals(new byte[]{ 0x00, 0x00, 0x01, 0x00, 0x00 }, buffer); + + // read equal to available + assertEquals(5, in.read(buffer)); + assertArrayEquals(new byte[]{ 0x02, 0x03, 0x04, 0x05, 0x06 }, buffer); + + // read more than available + in.addBytes(new byte[]{ 0x01, 0x02, 0x03 }); + assertEquals(3, in.read(buffer)); + assertArrayEquals(new byte[]{ 0x01, 0x02, 0x03, 0x05, 0x06 }, buffer); + + in.close();// so Eclipse doesn't whine about a resource leak + } + + public void testPeek() + { + ByteQueueInputStream in = new ByteQueueInputStream(); + + byte[] buffer = new byte[5]; + + // peek more than available + assertEquals(0, in.peek(buffer)); + assertArrayEquals(new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00 }, buffer); + + // peek less than available + in.addBytes(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }); + assertEquals(5, in.peek(buffer)); + assertArrayEquals(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05 }, buffer); + assertEquals(6, in.available()); + + // peek equal to available + in.read(); + assertEquals(5, in.peek(buffer)); + assertArrayEquals(new byte[]{ 0x02, 0x03, 0x04, 0x05, 0x06 }, buffer); + assertEquals(5, in.available()); + + in.close();// so Eclipse doesn't whine about a resource leak + } + + private static void assertArrayEquals(byte[] a, byte[] b) + { + assertTrue(Arrays.areEqual(a, b)); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/DTLSTestClientProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/DTLSTestClientProtocol.java new file mode 100644 index 00000000..84109cdb --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/DTLSTestClientProtocol.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto.tls.test; + +import java.io.IOException; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.tls.DTLSClientProtocol; +import org.bouncycastle.crypto.tls.DigitallySigned; + +class DTLSTestClientProtocol extends DTLSClientProtocol +{ + protected final TlsTestConfig config; + + public DTLSTestClientProtocol(SecureRandom secureRandom, TlsTestConfig config) + { + super(secureRandom); + + this.config = config; + } + + protected byte[] generateCertificateVerify(ClientHandshakeState state, DigitallySigned certificateVerify) + throws IOException + { + if (certificateVerify.getAlgorithm() != null && config.clientAuthSigAlgClaimed != null) + { + certificateVerify = new DigitallySigned(config.clientAuthSigAlgClaimed, certificateVerify.getSignature()); + } + + return super.generateCertificateVerify(state, certificateVerify); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/DTLSTestServerProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/DTLSTestServerProtocol.java new file mode 100644 index 00000000..9244eeb8 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/DTLSTestServerProtocol.java @@ -0,0 +1,17 @@ +package org.bouncycastle.crypto.tls.test; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.tls.DTLSServerProtocol; + +class DTLSTestServerProtocol extends DTLSServerProtocol +{ + protected final TlsTestConfig config; + + public DTLSTestServerProtocol(SecureRandom secureRandom, TlsTestConfig config) + { + super(secureRandom); + + this.config = config; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsProtocolNonBlockingTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsProtocolNonBlockingTest.java new file mode 100644 index 00000000..564bb742 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsProtocolNonBlockingTest.java @@ -0,0 +1,126 @@ +package org.bouncycastle.crypto.tls.test; + +import java.io.IOException; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.tls.TlsClientProtocol; +import org.bouncycastle.crypto.tls.TlsProtocol; +import org.bouncycastle.crypto.tls.TlsServerProtocol; +import org.bouncycastle.util.Arrays; + +import junit.framework.TestCase; + +public class TlsProtocolNonBlockingTest + extends TestCase +{ + public void testClientServerFragmented() throws IOException + { + // tests if it's really non-blocking when partial records arrive + testClientServer(true); + } + + public void testClientServerNonFragmented() throws IOException + { + testClientServer(false); + } + + private static void testClientServer(boolean fragment) throws IOException + { + SecureRandom secureRandom = new SecureRandom(); + + TlsClientProtocol clientProtocol = new TlsClientProtocol(secureRandom); + TlsServerProtocol serverProtocol = new TlsServerProtocol(secureRandom); + + clientProtocol.connect(new MockTlsClient(null)); + serverProtocol.accept(new MockTlsServer()); + + // pump handshake + boolean hadDataFromServer = true; + boolean hadDataFromClient = true; + while (hadDataFromServer || hadDataFromClient) + { + hadDataFromServer = pumpData(serverProtocol, clientProtocol, fragment); + hadDataFromClient = pumpData(clientProtocol, serverProtocol, fragment); + } + + // send data in both directions + byte[] data = new byte[1024]; + secureRandom.nextBytes(data); + writeAndRead(clientProtocol, serverProtocol, data, fragment); + writeAndRead(serverProtocol, clientProtocol, data, fragment); + + // close the connection + clientProtocol.close(); + pumpData(clientProtocol, serverProtocol, fragment); + checkClosed(serverProtocol); + checkClosed(clientProtocol); + } + + private static void writeAndRead(TlsProtocol writer, TlsProtocol reader, byte[] data, boolean fragment) + throws IOException + { + int dataSize = data.length; + writer.offerOutput(data, 0, dataSize); + pumpData(writer, reader, fragment); + + assertEquals(dataSize, reader.getAvailableInputBytes()); + byte[] readData = new byte[dataSize]; + reader.readInput(readData, 0, dataSize); + assertArrayEquals(data, readData); + } + + private static boolean pumpData(TlsProtocol from, TlsProtocol to, boolean fragment) throws IOException + { + int byteCount = from.getAvailableOutputBytes(); + if (byteCount == 0) + { + return false; + } + + if (fragment) + { + while (from.getAvailableOutputBytes() > 0) + { + byte[] buffer = new byte[1]; + from.readOutput(buffer, 0, 1); + to.offerInput(buffer); + } + } + else + { + byte[] buffer = new byte[byteCount]; + from.readOutput(buffer, 0, buffer.length); + to.offerInput(buffer); + } + + return true; + } + + private static void checkClosed(TlsProtocol protocol) + { + assertTrue(protocol.isClosed()); + + try + { + protocol.offerInput(new byte[10]); + fail("Input was accepted after close"); + } + catch (IOException e) + { + } + + try + { + protocol.offerOutput(new byte[10], 0, 10); + fail("Output was accepted after close"); + } + catch (IOException e) + { + } + } + + private static void assertArrayEquals(byte[] a, byte[] b) + { + assertTrue(Arrays.areEqual(a, b)); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsTestClientProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsTestClientProtocol.java new file mode 100644 index 00000000..2f6f751e --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsTestClientProtocol.java @@ -0,0 +1,31 @@ +package org.bouncycastle.crypto.tls.test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.tls.DigitallySigned; +import org.bouncycastle.crypto.tls.TlsClientProtocol; + +class TlsTestClientProtocol extends TlsClientProtocol +{ + protected final TlsTestConfig config; + + public TlsTestClientProtocol(InputStream input, OutputStream output, SecureRandom secureRandom, TlsTestConfig config) + { + super(input, output, secureRandom); + + this.config = config; + } + + protected void sendCertificateVerifyMessage(DigitallySigned certificateVerify) throws IOException + { + if (certificateVerify.getAlgorithm() != null && config.clientAuthSigAlgClaimed != null) + { + certificateVerify = new DigitallySigned(config.clientAuthSigAlgClaimed, certificateVerify.getSignature()); + } + + super.sendCertificateVerifyMessage(certificateVerify); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsTestServerProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsTestServerProtocol.java new file mode 100644 index 00000000..0bfc985b --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/test/TlsTestServerProtocol.java @@ -0,0 +1,19 @@ +package org.bouncycastle.crypto.tls.test; + +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.tls.TlsServerProtocol; + +class TlsTestServerProtocol extends TlsServerProtocol +{ + protected final TlsTestConfig config; + + public TlsTestServerProtocol(InputStream input, OutputStream output, SecureRandom secureRandom, TlsTestConfig config) + { + super(input, output, secureRandom); + + this.config = config; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/DERMacData.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/DERMacData.java new file mode 100644 index 00000000..d895a1fd --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/DERMacData.java @@ -0,0 +1,114 @@ +package org.bouncycastle.crypto.util; + +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Strings; + +/** + * Builder and holder class for preparing SP 800-56A compliant MacData. Elements in the data are encoded + * as DER objects with empty octet strings used to represent nulls in compulsory fields. + */ +public final class DERMacData +{ + public enum Type + { + UNILATERALU("KC_1_U"), + UNILATERALV("KC_1_V"), + BILATERALU("KC_2_U"), + BILATERALV("KC_2_V"); + + private final String enc; + + Type(String enc) + { + this.enc = enc; + } + + public byte[] getHeader() + { + return Strings.toByteArray(enc); + } + } + + /** + * Builder to create OtherInfo + */ + public static final class Builder + { + private final Type type; + + private ASN1OctetString idU; + private ASN1OctetString idV; + private ASN1OctetString ephemDataU; + private ASN1OctetString ephemDataV; + private byte[] text; + + /** + * Create a basic builder with just the compulsory fields. + * + * @param type the MAC header + * @param idU sender party ID. + * @param idV receiver party ID. + * @param ephemDataU ephemeral data from sender. + * @param ephemDataV ephemeral data from receiver. + */ + public Builder(Type type, byte[] idU, byte[] idV, byte[] ephemDataU, byte[] ephemDataV) + { + this.type = type; + this.idU = DerUtil.getOctetString(idU); + this.idV = DerUtil.getOctetString(idV); + this.ephemDataU = DerUtil.getOctetString(ephemDataU); + this.ephemDataV = DerUtil.getOctetString(ephemDataV); + } + + /** + * Add optional text. + * + * @param text optional agreed text to add to the MAC. + * @return the current builder instance. + */ + public Builder withText(byte[] text) + { + this.text = DerUtil.toByteArray(new DERTaggedObject(false, 0, DerUtil.getOctetString(text))); + + return this; + } + + public DERMacData build() + { + switch (type) + { + case UNILATERALU: + case BILATERALU: + return new DERMacData(concatenate(type.getHeader(), + DerUtil.toByteArray(idU), DerUtil.toByteArray(idV), + DerUtil.toByteArray(ephemDataU), DerUtil.toByteArray(ephemDataV), text)); + case UNILATERALV: + case BILATERALV: + return new DERMacData(concatenate(type.getHeader(), + DerUtil.toByteArray(idV), DerUtil.toByteArray(idU), + DerUtil.toByteArray(ephemDataV), DerUtil.toByteArray(ephemDataU), text)); + } + + throw new IllegalStateException("Unknown type encountered in build"); // should never happen + } + + private byte[] concatenate(byte[] header, byte[] id1, byte[] id2, byte[] ed1, byte[] ed2, byte[] text) + { + return Arrays.concatenate(Arrays.concatenate(header, id1, id2), Arrays.concatenate(ed1, ed2, text)); + } + } + + private final byte[] macData; + + private DERMacData(byte[] macData) + { + this.macData = macData; + } + + public byte[] getMacData() + { + return Arrays.clone(macData); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/DEROtherInfo.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/DEROtherInfo.java new file mode 100644 index 00000000..4e821f7e --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/DEROtherInfo.java @@ -0,0 +1,109 @@ +package org.bouncycastle.crypto.util; + +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.ASN1TaggedObject; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * Builder and holder class for preparing SP 800-56A compliant OtherInfo. The data is ultimately encoded as a DER SEQUENCE. + * Empty octet strings are used to represent nulls in compulsory fields. + */ +public class DEROtherInfo +{ + /** + * Builder to create OtherInfo + */ + public static final class Builder + { + private final AlgorithmIdentifier algorithmID; + private final ASN1OctetString partyUVInfo; + private final ASN1OctetString partyVInfo; + + private ASN1TaggedObject suppPubInfo; + private ASN1TaggedObject suppPrivInfo; + + /** + * Create a basic builder with just the compulsory fields. + * + * @param algorithmID the algorithm associated with this invocation of the KDF. + * @param partyUInfo sender party info. + * @param partyVInfo receiver party info. + */ + public Builder(AlgorithmIdentifier algorithmID, byte[] partyUInfo, byte[] partyVInfo) + { + this.algorithmID = algorithmID; + this.partyUVInfo = DerUtil.getOctetString(partyUInfo); + this.partyVInfo = DerUtil.getOctetString(partyVInfo); + } + + /** + * Add optional supplementary public info (DER tagged, implicit, 0). + * + * @param suppPubInfo supplementary public info. + * @return the current builder instance. + */ + public Builder withSuppPubInfo(byte[] suppPubInfo) + { + this.suppPubInfo = new DERTaggedObject(false, 0, DerUtil.getOctetString(suppPubInfo)); + + return this; + } + + /** + * Add optional supplementary private info (DER tagged, implicit, 1). + * + * @param suppPrivInfo supplementary private info. + * @return the current builder instance. + */ + public Builder withSuppPrivInfo(byte[] suppPrivInfo) + { + this.suppPrivInfo = new DERTaggedObject(false, 1, DerUtil.getOctetString(suppPrivInfo)); + + return this; + } + + /** + * Build the KTSOtherInfo. + * + * @return an KTSOtherInfo containing the data. + */ + public DEROtherInfo build() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(algorithmID); + v.add(partyUVInfo); + v.add(partyVInfo); + + if (suppPubInfo != null) + { + v.add(suppPubInfo); + } + + if (suppPrivInfo != null) + { + v.add(suppPrivInfo); + } + + return new DEROtherInfo(new DERSequence(v)); + } + } + + private final DERSequence sequence; + + private DEROtherInfo(DERSequence sequence) + { + this.sequence = sequence; + } + + public byte[] getEncoded() + throws IOException + { + return sequence.getEncoded(); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/DerUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/DerUtil.java new file mode 100644 index 00000000..324c5ae4 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/DerUtil.java @@ -0,0 +1,39 @@ +package org.bouncycastle.crypto.util; + +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.util.Arrays; + +class DerUtil +{ + static ASN1OctetString getOctetString(byte[] data) + { + if (data == null) + { + return new DEROctetString(new byte[0]); + } + + return new DEROctetString(Arrays.clone(data)); + } + + static byte[] toByteArray(ASN1Primitive primitive) + { + try + { + return primitive.getEncoded(); + } + catch (final IOException e) + { + throw new IllegalStateException("Cannot get encoding: " + e.getMessage()) + { + public Throwable getCause() + { + return e; + } + }; + } + } +} |