diff options
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto')
434 files changed, 65816 insertions, 0 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/AsymmetricBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/AsymmetricBlockCipher.java new file mode 100644 index 00000000..565effcc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/AsymmetricBlockCipher.java @@ -0,0 +1,45 @@ +package org.bouncycastle.crypto; + + +/** + * base interface that a public/private key block cipher needs + * to conform to. + */ +public interface AsymmetricBlockCipher +{ + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + */ + public void init(boolean forEncryption, CipherParameters param); + + /** + * returns the largest size an input block can be. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize(); + + /** + * returns the maximum size of the block produced by this cipher. + * + * @return maximum size of the output block produced by the cipher. + */ + public int getOutputBlockSize(); + + /** + * process the block of len bytes stored in in from offset inOff. + * + * @param in the input data + * @param inOff offset into the in array where the data starts + * @param len the length of the block to be processed. + * @return the resulting byte array of the encryption/decryption process. + * @exception InvalidCipherTextException data decrypts improperly. + * @exception DataLengthException the input data is too large for the cipher. + */ + public byte[] processBlock(byte[] in, int inOff, int len) + throws InvalidCipherTextException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java b/core/src/main/java/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java new file mode 100644 index 00000000..ddee7019 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java @@ -0,0 +1,61 @@ +package org.bouncycastle.crypto; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +/** + * a holding class for public/private parameter pairs. + */ +public class AsymmetricCipherKeyPair +{ + private AsymmetricKeyParameter publicParam; + private AsymmetricKeyParameter privateParam; + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + */ + public AsymmetricCipherKeyPair( + AsymmetricKeyParameter publicParam, + AsymmetricKeyParameter privateParam) + { + this.publicParam = publicParam; + this.privateParam = privateParam; + } + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + * @deprecated use AsymmetricKeyParameter + */ + public AsymmetricCipherKeyPair( + CipherParameters publicParam, + CipherParameters privateParam) + { + this.publicParam = (AsymmetricKeyParameter)publicParam; + this.privateParam = (AsymmetricKeyParameter)privateParam; + } + + /** + * return the public key parameters. + * + * @return the public key parameters. + */ + public AsymmetricKeyParameter getPublic() + { + return publicParam; + } + + /** + * return the private key parameters. + * + * @return the private key parameters. + */ + public AsymmetricKeyParameter getPrivate() + { + return privateParam; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java new file mode 100644 index 00000000..919db199 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto; + +/** + * interface that a public/private key pair generator should conform to. + */ +public interface AsymmetricCipherKeyPairGenerator +{ + /** + * intialise the key pair generator. + * + * @param param the parameters the key pair is to be initialised with. + */ + public void init(KeyGenerationParameters param); + + /** + * return an AsymmetricCipherKeyPair containing the generated keys. + * + * @return an AsymmetricCipherKeyPair containing the generated keys. + */ + public AsymmetricCipherKeyPair generateKeyPair(); +} + diff --git a/core/src/main/java/org/bouncycastle/crypto/BasicAgreement.java b/core/src/main/java/org/bouncycastle/crypto/BasicAgreement.java new file mode 100644 index 00000000..8e5ff0da --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/BasicAgreement.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto; + +import java.math.BigInteger; + +/** + * The basic interface that basic Diffie-Hellman implementations + * conforms to. + */ +public interface BasicAgreement +{ + /** + * initialise the agreement engine. + */ + void init(CipherParameters param); + + /** + * return the field size for the agreement algorithm in bytes. + */ + int getFieldSize(); + + /** + * given a public key from a given party calculate the next + * message in the agreement sequence. + */ + BigInteger calculateAgreement(CipherParameters pubKey); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/BlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/BlockCipher.java new file mode 100644 index 00000000..3cfa25a0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/BlockCipher.java @@ -0,0 +1,56 @@ +package org.bouncycastle.crypto; + + +/** + * Block cipher engines are expected to conform to this interface. + */ +public interface BlockCipher +{ + /** + * Initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getAlgorithmName(); + + /** + * Return the block size for this cipher (in bytes). + * + * @return the block size for this cipher in bytes. + */ + public int getBlockSize(); + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException; + + /** + * Reset the cipher. After resetting the cipher is in the same state + * as it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java new file mode 100644 index 00000000..1bf7ce3e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java @@ -0,0 +1,171 @@ +package org.bouncycastle.crypto; + +/** + * a buffer wrapper for an asymmetric block cipher, allowing input + * to be accumulated in a piecemeal fashion until final processing. + */ +public class BufferedAsymmetricBlockCipher +{ + protected byte[] buf; + protected int bufOff; + + private final AsymmetricBlockCipher cipher; + + /** + * base constructor. + * + * @param cipher the cipher this buffering object wraps. + */ + public BufferedAsymmetricBlockCipher( + AsymmetricBlockCipher cipher) + { + this.cipher = cipher; + } + + /** + * return the underlying cipher for the buffer. + * + * @return the underlying cipher for the buffer. + */ + public AsymmetricBlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * return the amount of data sitting in the buffer. + * + * @return the amount of data sitting in the buffer. + */ + public int getBufferPosition() + { + return bufOff; + } + + /** + * initialise the buffer and the underlying cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + reset(); + + cipher.init(forEncryption, params); + + // + // we allow for an extra byte where people are using their own padding + // mechanisms on a raw cipher. + // + buf = new byte[cipher.getInputBlockSize() + (forEncryption ? 1 : 0)]; + bufOff = 0; + } + + /** + * returns the largest size an input block can be. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return cipher.getInputBlockSize(); + } + + /** + * returns the maximum size of the block produced by this cipher. + * + * @return maximum size of the output block produced by the cipher. + */ + public int getOutputBlockSize() + { + return cipher.getOutputBlockSize(); + } + + /** + * add another byte for processing. + * + * @param in the input byte. + */ + public void processByte( + byte in) + { + if (bufOff >= buf.length) + { + throw new DataLengthException("attempt to process message too long for cipher"); + } + + buf[bufOff++] = in; + } + + /** + * add len bytes to the buffer for processing. + * + * @param in the input data + * @param inOff offset into the in array where the data starts + * @param len the length of the block to be processed. + */ + public void processBytes( + byte[] in, + int inOff, + int len) + { + if (len == 0) + { + return; + } + + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + if (bufOff + len > buf.length) + { + throw new DataLengthException("attempt to process message too long for cipher"); + } + + System.arraycopy(in, inOff, buf, bufOff, len); + bufOff += len; + } + + /** + * process the contents of the buffer using the underlying + * cipher. + * + * @return the result of the encryption/decryption process on the + * buffer. + * @exception InvalidCipherTextException if we are given a garbage block. + */ + public byte[] doFinal() + throws InvalidCipherTextException + { + byte[] out = cipher.processBlock(buf, 0, bufOff); + + reset(); + + return out; + } + + /** + * Reset the buffer and the underlying cipher. + */ + public void reset() + { + /* + * clean the buffer. + */ + if (buf != null) + { + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + } + + bufOff = 0; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java new file mode 100644 index 00000000..bdb694d4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java @@ -0,0 +1,313 @@ +package org.bouncycastle.crypto; + + +/** + * A wrapper class that allows block ciphers to be used to process data in + * a piecemeal fashion. The BufferedBlockCipher outputs a block only when the + * buffer is full and more data is being added, or on a doFinal. + * <p> + * Note: in the case where the underlying cipher is either a CFB cipher or an + * OFB one the last block may not be a multiple of the block size. + */ +public class BufferedBlockCipher +{ + protected byte[] buf; + protected int bufOff; + + protected boolean forEncryption; + protected BlockCipher cipher; + + protected boolean partialBlockOkay; + protected boolean pgpCFB; + + /** + * constructor for subclasses + */ + protected BufferedBlockCipher() + { + } + + /** + * Create a buffered block cipher without padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public BufferedBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + + // + // check if we can handle partial blocks on doFinal. + // + String name = cipher.getAlgorithmName(); + int idx = name.indexOf('/') + 1; + + pgpCFB = (idx > 0 && name.startsWith("PGP", idx)); + + if (pgpCFB) + { + partialBlockOkay = true; + } + else + { + partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx) || name.startsWith("OFB", idx) || name.startsWith("OpenPGP", idx) || name.startsWith("SIC", idx) || name.startsWith("GCTR", idx))); + } + } + + /** + * return the cipher this object wraps. + * + * @return the cipher this object wraps. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + reset(); + + cipher.init(forEncryption, params); + } + + /** + * return the blocksize for the underlying cipher. + * + * @return the blocksize for the underlying cipher. + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver; + + if (pgpCFB) + { + leftOver = total % buf.length - (cipher.getBlockSize() + 2); + } + else + { + leftOver = total % buf.length; + } + + return total - leftOver; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of 'length' bytes. + * + * @param length the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with 'length' bytes of input. + */ + public int getOutputSize( + int length) + { + // Note: Can assume partialBlockOkay is true for purposes of this calculation + return length + bufOff; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + buf[bufOff++] = in; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > buf.length) + { + resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + if (bufOff == buf.length) + { + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + bufOff = 0; + } + + return resultLen; + } + + /** + * Process the last block in the buffer. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output, or the input is not block size aligned and should be. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + * @exception DataLengthException if the input is not block size + * aligned. + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + try + { + int resultLen = 0; + + if (outOff + bufOff > out.length) + { + throw new OutputLengthException("output buffer too short for doFinal()"); + } + + if (bufOff != 0) + { + if (!partialBlockOkay) + { + throw new DataLengthException("data not block size aligned"); + } + + cipher.processBlock(buf, 0, buf, 0); + resultLen = bufOff; + bufOff = 0; + System.arraycopy(buf, 0, out, outOff, resultLen); + } + + return resultLen; + } + finally + { + reset(); + } + } + + /** + * Reset the buffer and cipher. After resetting the object is in the same + * state as it was after the last init (if there was one). + */ + public void reset() + { + // + // clean the buffer. + // + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + // + // reset the underlying cipher. + // + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/CipherKeyGenerator.java b/core/src/main/java/org/bouncycastle/crypto/CipherKeyGenerator.java new file mode 100644 index 00000000..451f8e8f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/CipherKeyGenerator.java @@ -0,0 +1,38 @@ +package org.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for symmetric, or secret, cipher key generators. + */ +public class CipherKeyGenerator +{ + protected SecureRandom random; + protected int strength; + + /** + * initialise the key generator. + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + this.random = param.getRandom(); + this.strength = (param.getStrength() + 7) / 8; + } + + /** + * generate a secret key. + * + * @return a byte array containing the key value. + */ + public byte[] generateKey() + { + byte[] key = new byte[strength]; + + random.nextBytes(key); + + return key; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/CipherParameters.java b/core/src/main/java/org/bouncycastle/crypto/CipherParameters.java new file mode 100644 index 00000000..5be87304 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/CipherParameters.java @@ -0,0 +1,8 @@ +package org.bouncycastle.crypto; + +/** + * all parameter classes implement this. + */ +public interface CipherParameters +{ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/Commitment.java b/core/src/main/java/org/bouncycastle/crypto/Commitment.java new file mode 100644 index 00000000..f1dc05a3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/Commitment.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto; + +/** + * General holding class for a commitment. + */ +public class Commitment +{ + private final byte[] secret; + private final byte[] commitment; + + /** + * Base constructor. + * + * @param secret an encoding of the secret required to reveal the commitment. + * @param commitment an encoding of the sealed commitment. + */ + public Commitment(byte[] secret, byte[] commitment) + { + this.secret = secret; + this.commitment = commitment; + } + + /** + * The secret required to reveal the commitment. + * + * @return an encoding of the secret associated with the commitment. + */ + public byte[] getSecret() + { + return secret; + } + + /** + * The sealed commitment. + * + * @return an encoding of the sealed commitment. + */ + public byte[] getCommitment() + { + return commitment; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/Committer.java b/core/src/main/java/org/bouncycastle/crypto/Committer.java new file mode 100644 index 00000000..5c93e5d1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/Committer.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto; + +/** + * General interface fdr classes that produce and validate commitments. + */ +public interface Committer +{ + /** + * Generate a commitment for the passed in message. + * + * @param message the message to be committed to, + * @return a Commitment + */ + Commitment commit(byte[] message); + + /** + * Return true if the passed in commitment represents a commitment to the passed in maessage. + * + * @param commitment a commitment previously generated. + * @param message the message that was expected to have been committed to. + * @return true if commitment matches message, false otherwise. + */ + boolean isRevealed(Commitment commitment, byte[] message); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/CryptoException.java b/core/src/main/java/org/bouncycastle/crypto/CryptoException.java new file mode 100644 index 00000000..352c5569 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/CryptoException.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto; + +/** + * the foundation class for the hard exceptions thrown by the crypto packages. + */ +public class CryptoException + extends Exception +{ + private Throwable cause; + + /** + * base constructor. + */ + public CryptoException() + { + } + + /** + * create a CryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public CryptoException( + String message) + { + super(message); + } + + /** + * Create a CryptoException with the given message and underlying cause. + * + * @param message message describing exception. + * @param cause the throwable that was the underlying cause. + */ + public CryptoException( + String message, + Throwable cause) + { + super(message); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/DSA.java b/core/src/main/java/org/bouncycastle/crypto/DSA.java new file mode 100644 index 00000000..1f584762 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/DSA.java @@ -0,0 +1,36 @@ +package org.bouncycastle.crypto; + +import java.math.BigInteger; + +/** + * interface for classes implementing algorithms modeled similar to the Digital Signature Alorithm. + */ +public interface DSA +{ + /** + * initialise the signer for signature generation or signature + * verification. + * + * @param forSigning true if we are generating a signature, false + * otherwise. + * @param param key parameters for signature generation. + */ + public void init(boolean forSigning, CipherParameters param); + + /** + * sign the passed in message (usually the output of a hash function). + * + * @param message the message to be signed. + * @return two big integers representing the r and s values respectively. + */ + public BigInteger[] generateSignature(byte[] message); + + /** + * verify the message message against the signature values r and s. + * + * @param message the message that was supposed to have been signed. + * @param r the r signature value. + * @param s the s signature value. + */ + public boolean verifySignature(byte[] message, BigInteger r, BigInteger s); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/DataLengthException.java b/core/src/main/java/org/bouncycastle/crypto/DataLengthException.java new file mode 100644 index 00000000..fbf047cf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/DataLengthException.java @@ -0,0 +1,29 @@ +package org.bouncycastle.crypto; + +/** + * this exception is thrown if a buffer that is meant to have output + * copied into it turns out to be too short, or if we've been given + * insufficient input. In general this exception will get thrown rather + * than an ArrayOutOfBounds exception. + */ +public class DataLengthException + extends RuntimeCryptoException +{ + /** + * base constructor. + */ + public DataLengthException() + { + } + + /** + * create a DataLengthException with the given message. + * + * @param message the message to be carried with the exception. + */ + public DataLengthException( + String message) + { + super(message); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/DerivationFunction.java b/core/src/main/java/org/bouncycastle/crypto/DerivationFunction.java new file mode 100644 index 00000000..ef6e29eb --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/DerivationFunction.java @@ -0,0 +1,17 @@ +package org.bouncycastle.crypto; + +/** + * base interface for general purpose byte derivation functions. + */ +public interface DerivationFunction +{ + public void init(DerivationParameters param); + + /** + * return the message digest used as the basis for the function + */ + public Digest getDigest(); + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/DerivationParameters.java b/core/src/main/java/org/bouncycastle/crypto/DerivationParameters.java new file mode 100644 index 00000000..e11eb867 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/DerivationParameters.java @@ -0,0 +1,8 @@ +package org.bouncycastle.crypto; + +/** + * Parameters for key/byte stream derivation classes + */ +public interface DerivationParameters +{ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/Digest.java b/core/src/main/java/org/bouncycastle/crypto/Digest.java new file mode 100644 index 00000000..f44fad0d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/Digest.java @@ -0,0 +1,51 @@ +package org.bouncycastle.crypto; + +/** + * interface that a message digest conforms to. + */ +public interface Digest +{ + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName(); + + /** + * 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(); + + /** + * update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + public void update(byte in); + + /** + * update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void update(byte[] in, int inOff, int len); + + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff); + + /** + * reset the digest back to it's initial state. + */ + public void reset(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/EphemeralKeyPair.java b/core/src/main/java/org/bouncycastle/crypto/EphemeralKeyPair.java new file mode 100644 index 00000000..f16812f9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/EphemeralKeyPair.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto; + +public class EphemeralKeyPair +{ + private AsymmetricCipherKeyPair keyPair; + private KeyEncoder publicKeyEncoder; + + public EphemeralKeyPair(AsymmetricCipherKeyPair keyPair, KeyEncoder publicKeyEncoder) + { + this.keyPair = keyPair; + this.publicKeyEncoder = publicKeyEncoder; + } + + public AsymmetricCipherKeyPair getKeyPair() + { + return keyPair; + } + + public byte[] getEncodedPublicKey() + { + return publicKeyEncoder.getEncoded(keyPair.getPublic()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ExtendedDigest.java b/core/src/main/java/org/bouncycastle/crypto/ExtendedDigest.java new file mode 100644 index 00000000..c5e9e8b0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ExtendedDigest.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto; + +public interface ExtendedDigest + extends Digest +{ + /** + * 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(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/InvalidCipherTextException.java b/core/src/main/java/org/bouncycastle/crypto/InvalidCipherTextException.java new file mode 100644 index 00000000..21c150d9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/InvalidCipherTextException.java @@ -0,0 +1,40 @@ +package org.bouncycastle.crypto; + +/** + * this exception is thrown whenever we find something we don't expect in a + * message. + */ +public class InvalidCipherTextException + extends CryptoException +{ + /** + * base constructor. + */ + public InvalidCipherTextException() + { + } + + /** + * create a InvalidCipherTextException with the given message. + * + * @param message the message to be carried with the exception. + */ + public InvalidCipherTextException( + String message) + { + super(message); + } + + /** + * create a InvalidCipherTextException with the given message. + * + * @param message the message to be carried with the exception. + * @param cause the root cause of the exception. + */ + public InvalidCipherTextException( + String message, + Throwable cause) + { + super(message, cause); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/KeyEncapsulation.java b/core/src/main/java/org/bouncycastle/crypto/KeyEncapsulation.java new file mode 100755 index 00000000..16744573 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/KeyEncapsulation.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto; + +/** + * The basic interface for key encapsulation mechanisms. + */ +public interface KeyEncapsulation +{ + /** + * Initialise the key encapsulation mechanism. + */ + public void init(CipherParameters param); + + /** + * Encapsulate a randomly generated session key. + */ + public CipherParameters encrypt(byte[] out, int outOff, int keyLen); + + /** + * Decapsulate an encapsulated session key. + */ + public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/KeyEncoder.java b/core/src/main/java/org/bouncycastle/crypto/KeyEncoder.java new file mode 100644 index 00000000..92ded9cd --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/KeyEncoder.java @@ -0,0 +1,8 @@ +package org.bouncycastle.crypto; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public interface KeyEncoder +{ + byte[] getEncoded(AsymmetricKeyParameter keyParameter); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java new file mode 100644 index 00000000..9a63522f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for parameters to key generators. + */ +public class KeyGenerationParameters +{ + private SecureRandom random; + private int strength; + + /** + * initialise the generator with a source of randomness + * and a strength (in bits). + * + * @param random the random byte source. + * @param strength the size, in bits, of the keys we want to produce. + */ + public KeyGenerationParameters( + SecureRandom random, + int strength) + { + this.random = random; + this.strength = strength; + } + + /** + * return the random source associated with this + * generator. + * + * @return the generators random source. + */ + public SecureRandom getRandom() + { + return random; + } + + /** + * return the bit strength for keys produced by this generator, + * + * @return the strength of the keys this generator produces (in bits). + */ + public int getStrength() + { + return strength; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/KeyParser.java b/core/src/main/java/org/bouncycastle/crypto/KeyParser.java new file mode 100644 index 00000000..60ce29de --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/KeyParser.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto; + +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public interface KeyParser +{ + AsymmetricKeyParameter readKey(InputStream stream) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/Mac.java b/core/src/main/java/org/bouncycastle/crypto/Mac.java new file mode 100644 index 00000000..c00cd58c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/Mac.java @@ -0,0 +1,71 @@ +package org.bouncycastle.crypto; + + +/** + * The base interface for implementations of message authentication codes (MACs). + */ +public interface Mac +{ + /** + * Initialise the MAC. + * + * @param params the key and other data required by the MAC. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the MAC implements. + * + * @return the name of the algorithm the MAC implements. + */ + public String getAlgorithmName(); + + /** + * Return the block size for this MAC (in bytes). + * + * @return the block size for this MAC in bytes. + */ + public int getMacSize(); + + /** + * add a single byte to the mac for processing. + * + * @param in the byte to be processed. + * @exception IllegalStateException if the MAC is not initialised. + */ + public void update(byte in) + throws IllegalStateException; + + /** + * @param in the array containing the input. + * @param inOff the index in the array the data begins at. + * @param len the length of the input starting at inOff. + * @exception IllegalStateException if the MAC is not initialised. + * @exception DataLengthException if there isn't enough data in in. + */ + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException; + + /** + * Compute the final stage of the MAC writing the output to the out + * parameter. + * <p> + * doFinal leaves the MAC in the same state it was after the last init. + * + * @param out the array the MAC is to be output to. + * @param outOff the offset into the out buffer the output is to start at. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the MAC is not initialised. + */ + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException; + + /** + * Reset the MAC. At the end of resetting the MAC should be in the + * in the same state it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/MaxBytesExceededException.java b/core/src/main/java/org/bouncycastle/crypto/MaxBytesExceededException.java new file mode 100644 index 00000000..bfa15442 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/MaxBytesExceededException.java @@ -0,0 +1,27 @@ +package org.bouncycastle.crypto; + +/** + * this exception is thrown whenever a cipher requires a change of key, iv + * or similar after x amount of bytes enciphered + */ +public class MaxBytesExceededException + extends RuntimeCryptoException +{ + /** + * base constructor. + */ + public MaxBytesExceededException() + { + } + + /** + * create an with the given message. + * + * @param message the message to be carried with the exception. + */ + public MaxBytesExceededException( + String message) + { + super(message); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/OutputLengthException.java b/core/src/main/java/org/bouncycastle/crypto/OutputLengthException.java new file mode 100644 index 00000000..62811a2b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/OutputLengthException.java @@ -0,0 +1,10 @@ +package org.bouncycastle.crypto; + +public class OutputLengthException + extends DataLengthException +{ + public OutputLengthException(String msg) + { + super(msg); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/PBEParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/PBEParametersGenerator.java new file mode 100644 index 00000000..18cc648a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/PBEParametersGenerator.java @@ -0,0 +1,171 @@ +package org.bouncycastle.crypto; + +import org.bouncycastle.util.Strings; + +/** + * super class for all Password Based Encryption (PBE) parameter generator classes. + */ +public abstract class PBEParametersGenerator +{ + protected byte[] password; + protected byte[] salt; + protected int iterationCount; + + /** + * base constructor. + */ + protected PBEParametersGenerator() + { + } + + /** + * initialise the PBE generator. + * + * @param password the password converted into bytes (see below). + * @param salt the salt to be mixed with the password. + * @param iterationCount the number of iterations the "mixing" function + * is to be applied for. + */ + public void init( + byte[] password, + byte[] salt, + int iterationCount) + { + this.password = password; + this.salt = salt; + this.iterationCount = iterationCount; + } + + /** + * return the password byte array. + * + * @return the password byte array. + */ + public byte[] getPassword() + { + return password; + } + + /** + * return the salt byte array. + * + * @return the salt byte array. + */ + public byte[] getSalt() + { + return salt; + } + + /** + * return the iteration count. + * + * @return the iteration count. + */ + public int getIterationCount() + { + return iterationCount; + } + + /** + * generate derived parameters for a key of length keySize. + * + * @param keySize the length, in bits, of the key required. + * @return a parameters object representing a key. + */ + public abstract CipherParameters generateDerivedParameters(int keySize); + + /** + * generate derived parameters for a key of length keySize, and + * an initialisation vector (IV) of length ivSize. + * + * @param keySize the length, in bits, of the key required. + * @param ivSize the length, in bits, of the iv required. + * @return a parameters object representing a key and an IV. + */ + public abstract CipherParameters generateDerivedParameters(int keySize, int ivSize); + + /** + * generate derived parameters for a key of length keySize, specifically + * for use with a MAC. + * + * @param keySize the length, in bits, of the key required. + * @return a parameters object representing a key. + */ + public abstract CipherParameters generateDerivedMacParameters(int keySize); + + /** + * converts a password to a byte array according to the scheme in + * PKCS5 (ascii, no padding) + * + * @param password a character array representing the password. + * @return a byte array representing the password. + */ + public static byte[] PKCS5PasswordToBytes( + char[] password) + { + if (password != null) + { + byte[] bytes = new byte[password.length]; + + for (int i = 0; i != bytes.length; i++) + { + bytes[i] = (byte)password[i]; + } + + return bytes; + } + else + { + return new byte[0]; + } + } + + /** + * converts a password to a byte array according to the scheme in + * PKCS5 (UTF-8, no padding) + * + * @param password a character array representing the password. + * @return a byte array representing the password. + */ + public static byte[] PKCS5PasswordToUTF8Bytes( + char[] password) + { + if (password != null) + { + return Strings.toUTF8ByteArray(password); + } + else + { + return new byte[0]; + } + } + + /** + * converts a password to a byte array according to the scheme in + * PKCS12 (unicode, big endian, 2 zero pad bytes at the end). + * + * @param password a character array representing the password. + * @return a byte array representing the password. + */ + public static byte[] PKCS12PasswordToBytes( + char[] password) + { + if (password != null && password.length > 0) + { + // +1 for extra 2 pad bytes. + byte[] bytes = new byte[(password.length + 1) * 2]; + + for (int i = 0; i != password.length; i ++) + { + bytes[i * 2] = (byte)(password[i] >>> 8); + bytes[i * 2 + 1] = (byte)password[i]; + } + + return bytes; + } + else + { + return new byte[0]; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/RuntimeCryptoException.java b/core/src/main/java/org/bouncycastle/crypto/RuntimeCryptoException.java new file mode 100644 index 00000000..c1572020 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/RuntimeCryptoException.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto; + +/** + * the foundation class for the exceptions thrown by the crypto packages. + */ +public class RuntimeCryptoException + extends RuntimeException +{ + /** + * base constructor. + */ + public RuntimeCryptoException() + { + } + + /** + * create a RuntimeCryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public RuntimeCryptoException( + String message) + { + super(message); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/Signer.java b/core/src/main/java/org/bouncycastle/crypto/Signer.java new file mode 100644 index 00000000..357b0da4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/Signer.java @@ -0,0 +1,43 @@ +package org.bouncycastle.crypto; + +/** + * Generic signer interface for hash based and message recovery signers. + */ +public interface Signer +{ + /** + * Initialise the signer for signing or verification. + * + * @param forSigning true if for signing, false otherwise + * @param param necessary parameters. + */ + public void init(boolean forSigning, CipherParameters param); + + /** + * update the internal digest with the byte b + */ + public void update(byte b); + + /** + * update the internal digest with the byte array in + */ + public void update(byte[] in, int off, int len); + + /** + * generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException; + + /** + * return true if the internal state represents the signature described + * in the passed in array. + */ + public boolean verifySignature(byte[] signature); + + /** + * reset the internal state + */ + public void reset(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/SignerWithRecovery.java b/core/src/main/java/org/bouncycastle/crypto/SignerWithRecovery.java new file mode 100644 index 00000000..452b367f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/SignerWithRecovery.java @@ -0,0 +1,34 @@ +package org.bouncycastle.crypto; + +/** + * Signer with message recovery. + */ +public interface SignerWithRecovery + extends Signer +{ + /** + * Returns true if the signer has recovered the full message as + * part of signature verification. + * + * @return true if full message recovered. + */ + public boolean hasFullMessage(); + + /** + * Returns a reference to what message was recovered (if any). + * + * @return full/partial message, null if nothing. + */ + public byte[] getRecoveredMessage(); + + /** + * Perform an update with the recovered message before adding any other data. This must + * be the first update method called, and calling it will result in the signer assuming + * that further calls to update will include message content past what is recoverable. + * + * @param signature the signature that we are in the process of verifying. + * @throws IllegalStateException + */ + public void updateWithRecoveredMessage(byte[] signature) + throws InvalidCipherTextException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/StreamBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/StreamBlockCipher.java new file mode 100644 index 00000000..8fdd2324 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/StreamBlockCipher.java @@ -0,0 +1,108 @@ +package org.bouncycastle.crypto; + +/** + * a wrapper for block ciphers with a single byte block size, so that they + * can be treated like stream ciphers. + */ +public class StreamBlockCipher + implements StreamCipher +{ + private BlockCipher cipher; + + private byte[] oneByte = new byte[1]; + + /** + * basic constructor. + * + * @param cipher the block cipher to be wrapped. + * @exception IllegalArgumentException if the cipher has a block size other than + * one. + */ + public StreamBlockCipher( + BlockCipher cipher) + { + if (cipher.getBlockSize() != 1) + { + throw new IllegalArgumentException("block cipher block size != 1."); + } + + this.cipher = cipher; + } + + /** + * initialise the underlying cipher. + * + * @param forEncryption true if we are setting up for encryption, false otherwise. + * @param params the necessary parameters for the underlying cipher to be initialised. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + cipher.init(forEncryption, params); + } + + /** + * return the name of the algorithm we are wrapping. + * + * @return the name of the algorithm we are wrapping. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + /** + * encrypt/decrypt a single byte returning the result. + * + * @param in the byte to be processed. + * @return the result of processing the input byte. + */ + public byte returnByte( + byte in) + { + oneByte[0] = in; + + cipher.processBlock(oneByte, 0, oneByte, 0); + + return oneByte[0]; + } + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data stars at. + * @exception DataLengthException if the output buffer is too small. + */ + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException + { + if (outOff + len > out.length) + { + throw new DataLengthException("output buffer too small in processBytes()"); + } + + for (int i = 0; i != len; i++) + { + cipher.processBlock(in, inOff + i, out, outOff + i); + } + } + + /** + * reset the underlying cipher. This leaves it in the same state + * it was at after the last init (if there was one). + */ + public void reset() + { + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/StreamCipher.java b/core/src/main/java/org/bouncycastle/crypto/StreamCipher.java new file mode 100644 index 00000000..2a55d4f6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/StreamCipher.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto; + +/** + * the interface stream ciphers conform to. + */ +public interface StreamCipher +{ + /** + * Initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getAlgorithmName(); + + /** + * encrypt/decrypt a single byte returning the result. + * + * @param in the byte to be processed. + * @return the result of processing the input byte. + */ + public byte returnByte(byte in); + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data starts at. + * @exception DataLengthException if the output buffer is too small. + */ + public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException; + + /** + * reset the cipher. This leaves it in the same state + * it was at after the last init (if there was one). + */ + public void reset(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/Wrapper.java b/core/src/main/java/org/bouncycastle/crypto/Wrapper.java new file mode 100644 index 00000000..3956a6fc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/Wrapper.java @@ -0,0 +1,18 @@ +package org.bouncycastle.crypto; + +public interface Wrapper +{ + public void init(boolean forWrapping, CipherParameters param); + + /** + * Return the name of the algorithm the wrapper implements. + * + * @return the name of the algorithm the wrapper implements. + */ + public String getAlgorithmName(); + + public byte[] wrap(byte[] in, int inOff, int inLen); + + public byte[] unwrap(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/DHAgreement.java b/core/src/main/java/org/bouncycastle/crypto/agreement/DHAgreement.java new file mode 100644 index 00000000..021a7153 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/DHAgreement.java @@ -0,0 +1,94 @@ +package org.bouncycastle.crypto.agreement; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.generators.DHKeyPairGenerator; +import org.bouncycastle.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * a Diffie-Hellman key exchange engine. + * <p> + * note: This uses MTI/A0 key agreement in order to make the key agreement + * secure against passive attacks. If you're doing Diffie-Hellman and both + * parties have long term public keys you should look at using this. For + * further information have a look at RFC 2631. + * <p> + * It's possible to extend this to more than two parties as well, for the moment + * that is left as an exercise for the reader. + */ +public class DHAgreement +{ + private DHPrivateKeyParameters key; + private DHParameters dhParams; + private BigInteger privateValue; + private SecureRandom random; + + public void init( + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)param; + } + + + if (!(kParam instanceof DHPrivateKeyParameters)) + { + throw new IllegalArgumentException("DHEngine expects DHPrivateKeyParameters"); + } + + this.key = (DHPrivateKeyParameters)kParam; + this.dhParams = key.getParameters(); + } + + /** + * calculate our initial message. + */ + public BigInteger calculateMessage() + { + DHKeyPairGenerator dhGen = new DHKeyPairGenerator(); + dhGen.init(new DHKeyGenerationParameters(random, dhParams)); + AsymmetricCipherKeyPair dhPair = dhGen.generateKeyPair(); + + this.privateValue = ((DHPrivateKeyParameters)dhPair.getPrivate()).getX(); + + return ((DHPublicKeyParameters)dhPair.getPublic()).getY(); + } + + /** + * given a message from a given party and the corresponding public key, + * calculate the next message in the agreement sequence. In this case + * this will represent the shared secret. + */ + public BigInteger calculateAgreement( + DHPublicKeyParameters pub, + BigInteger message) + { + if (!pub.getParameters().equals(dhParams)) + { + throw new IllegalArgumentException("Diffie-Hellman public key has wrong parameters."); + } + + BigInteger p = dhParams.getP(); + + return message.modPow(key.getX(), p).multiply(pub.getY().modPow(privateValue, p)).mod(p); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/DHBasicAgreement.java b/core/src/main/java/org/bouncycastle/crypto/agreement/DHBasicAgreement.java new file mode 100644 index 00000000..d2e2a09f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/DHBasicAgreement.java @@ -0,0 +1,71 @@ +package org.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * a Diffie-Hellman key agreement class. + * <p> + * note: This is only the basic algorithm, it doesn't take advantage of + * long term public keys if they are available. See the DHAgreement class + * for a "better" implementation. + */ +public class DHBasicAgreement + implements BasicAgreement +{ + private DHPrivateKeyParameters key; + private DHParameters dhParams; + + public void init( + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + kParam = (AsymmetricKeyParameter)param; + } + + if (!(kParam instanceof DHPrivateKeyParameters)) + { + throw new IllegalArgumentException("DHEngine expects DHPrivateKeyParameters"); + } + + this.key = (DHPrivateKeyParameters)kParam; + this.dhParams = key.getParameters(); + } + + public int getFieldSize() + { + return (key.getParameters().getP().bitLength() + 7) / 8; + } + + /** + * given a short term public key from a given party calculate the next + * message in the agreement sequence. + */ + public BigInteger calculateAgreement( + CipherParameters pubKey) + { + DHPublicKeyParameters pub = (DHPublicKeyParameters)pubKey; + + if (!pub.getParameters().equals(dhParams)) + { + throw new IllegalArgumentException("Diffie-Hellman public key has wrong parameters."); + } + + return pub.getY().modPow(key.getX(), dhParams.getP()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java b/core/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java new file mode 100644 index 00000000..638bcb13 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java @@ -0,0 +1,206 @@ +package org.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.util.encoders.Hex; + +/** + * Standard Diffie-Hellman groups from various IETF specifications. + */ +public class DHStandardGroups +{ + + private static DHParameters fromPG(String hexP, String hexG) + { + BigInteger p = new BigInteger(1, Hex.decode(hexP)); + BigInteger g = new BigInteger(1, Hex.decode(hexG)); + return new DHParameters(p, g); + } + + private static DHParameters fromPGQ(String hexP, String hexG, String hexQ) + { + BigInteger p = new BigInteger(1, Hex.decode(hexP)); + BigInteger g = new BigInteger(1, Hex.decode(hexG)); + BigInteger q = new BigInteger(1, Hex.decode(hexQ)); + return new DHParameters(p, g, q); + } + + /* + * RFC 2409 + */ + private static final String rfc2409_768_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF"; + private static final String rfc2409_768_g = "02"; + public static final DHParameters rfc2409_768 = fromPG(rfc2409_768_p, rfc2409_768_g); + + private static final String rfc2409_1024_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + + "FFFFFFFFFFFFFFFF"; + private static final String rfc2409_1024_g = "02"; + public static final DHParameters rfc2409_1024 = fromPG(rfc2409_1024_p, rfc2409_1024_g); + + /* + * RFC 3526 + */ + private static final String rfc3526_1536_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"; + private static final String rfc3526_1536_g = "02"; + public static final DHParameters rfc3526_1536 = fromPG(rfc3526_1536_p, rfc3526_1536_g); + + private static final String rfc3526_2048_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF"; + private static final String rfc3526_2048_g = "02"; + public static final DHParameters rfc3526_2048 = fromPG(rfc3526_2048_p, rfc3526_2048_g); + + private static final String rfc3526_3072_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; + private static final String rfc3526_3072_g = "02"; + public static final DHParameters rfc3526_3072 = fromPG(rfc3526_3072_p, rfc3526_3072_g); + + private static final String rfc3526_4096_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF"; + private static final String rfc3526_4096_g = "02"; + public static final DHParameters rfc3526_4096 = fromPG(rfc3526_4096_p, rfc3526_4096_g); + + private static final String rfc3526_6144_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + "6DCC4024FFFFFFFFFFFFFFFF"; + private static final String rfc3526_6144_g = "02"; + public static final DHParameters rfc3526_6144 = fromPG(rfc3526_6144_p, rfc3526_6144_g); + + private static final String rfc3526_8192_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF"; + private static final String rfc3526_8192_g = "02"; + public static final DHParameters rfc3526_8192 = fromPG(rfc3526_8192_p, rfc3526_8192_g); + + /* + * RFC 4306 + */ + public static final DHParameters rfc4306_768 = rfc2409_768; + public static final DHParameters rfc4306_1024 = rfc2409_1024; + + /* + * RFC 5114 + */ + private static final String rfc5114_1024_160_p = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6" + + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0" + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70" + + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0" + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708" + + "DF1FB2BC2E4A4371"; + private static final String rfc5114_1024_160_g = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F" + + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213" + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1" + + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A" + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24" + + "855E6EEB22B3B2E5"; + private static final String rfc5114_1024_160_q = "F518AA8781A8DF278ABA4E7D64B7CB9D49462353"; + public static final DHParameters rfc5114_1024_160 = fromPGQ(rfc5114_1024_160_p, rfc5114_1024_160_g, + rfc5114_1024_160_q); + + private static final String rfc5114_2048_224_p = "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1" + + "B54B1597B61D0A75E6FA141DF95A56DBAF9A3C407BA1DF15" + "EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC212" + + "9037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207" + "C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708" + + "B3BF8A317091883681286130BC8985DB1602E714415D9330" + "278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486D" + + "CDF93ACC44328387315D75E198C641A480CD86A1B9E587E8" + "BE60E69CC928B2B9C52172E413042E9B23F10B0E16E79763" + + "C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71" + "CF9DE5384E71B81C0AC4DFFE0C10E64F"; + private static final String rfc5114_2048_224_g = "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF" + + "74866A08CFE4FFE3A6824A4E10B9A6F0DD921F01A70C4AFA" + "AB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7" + + "C17669101999024AF4D027275AC1348BB8A762D0521BC98A" + "E247150422EA1ED409939D54DA7460CDB5F6C6B250717CBE" + + "F180EB34118E98D119529A45D6F834566E3025E316A330EF" + "BB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB" + + "10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381" + "B539CCE3409D13CD566AFBB48D6C019181E1BCFE94B30269" + + "EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC0179" + "81BC087F2A7065B384B890D3191F2BFA"; + private static final String rfc5114_2048_224_q = "801C0D34C58D93FE997177101F80535A4738CEBCBF389A99B36371EB"; + public static final DHParameters rfc5114_2048_224 = fromPGQ(rfc5114_2048_224_p, rfc5114_2048_224_g, + rfc5114_2048_224_q); + + private static final String rfc5114_2048_256_p = "87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F2" + + "5D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA30" + "16C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD" + + "5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B" + "6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C" + + "4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0E" + "F13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D9" + + "67E144E5140564251CCACB83E6B486F6B3CA3F7971506026" + "C0B857F689962856DED4010ABD0BE621C3A3960A54E710C3" + + "75F26375D7014103A4B54330C198AF126116D2276E11715F" + "693877FAD7EF09CADB094AE91E1A1597"; + private static final String rfc5114_2048_256_g = "3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF2054" + + "07F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555" + "BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18" + + "A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B" + "777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC83" + + "1D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55" + "A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14" + + "C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915" + "B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6" + + "184B523D1DB246C32F63078490F00EF8D647D148D4795451" + "5E2327CFEF98C582664B4C0F6CC41659"; + private static final String rfc5114_2048_256_q = "8CF83642A709A097B447997640129DA299B1A47D1EB3750B" + + "A308B0FE64F5FBD3"; + public static final DHParameters rfc5114_2048_256 = fromPGQ(rfc5114_2048_256_p, rfc5114_2048_256_g, + rfc5114_2048_256_q); + + /* + * RFC 5996 + */ + public static final DHParameters rfc5996_768 = rfc4306_768; + public static final DHParameters rfc5996_1024 = rfc4306_1024; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java b/core/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java new file mode 100644 index 00000000..59944e07 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java @@ -0,0 +1,51 @@ +package org.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECPoint; + +/** + * P1363 7.2.1 ECSVDP-DH + * + * ECSVDP-DH is Elliptic Curve Secret Value Derivation Primitive, + * Diffie-Hellman version. It is based on the work of [DH76], [Mil86], + * and [Kob87]. This primitive derives a shared secret value from one + * party's private key and another party's public key, where both have + * the same set of EC domain parameters. If two parties correctly + * execute this primitive, they will produce the same output. This + * primitive can be invoked by a scheme to derive a shared secret key; + * specifically, it may be used with the schemes ECKAS-DH1 and + * DL/ECKAS-DH2. It assumes that the input keys are valid (see also + * Section 7.2.2). + */ +public class ECDHBasicAgreement + implements BasicAgreement +{ + private ECPrivateKeyParameters key; + + public void init( + CipherParameters key) + { + this.key = (ECPrivateKeyParameters)key; + } + + public int getFieldSize() + { + return (key.getParameters().getCurve().getFieldSize() + 7) / 8; + } + + public BigInteger calculateAgreement( + CipherParameters pubKey) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; + ECPoint P = pub.getQ().multiply(key.getD()); + + // if (p.isInfinity()) throw new RuntimeException("d*Q == infinity"); + + return P.getX().toBigInteger(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java b/core/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java new file mode 100644 index 00000000..12b84052 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java @@ -0,0 +1,58 @@ +package org.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECPoint; + +/** + * P1363 7.2.2 ECSVDP-DHC + * + * ECSVDP-DHC is Elliptic Curve Secret Value Derivation Primitive, + * Diffie-Hellman version with cofactor multiplication. It is based on + * the work of [DH76], [Mil86], [Kob87], [LMQ98] and [Kal98a]. This + * primitive derives a shared secret value from one party's private key + * and another party's public key, where both have the same set of EC + * domain parameters. If two parties correctly execute this primitive, + * they will produce the same output. This primitive can be invoked by a + * scheme to derive a shared secret key; specifically, it may be used + * with the schemes ECKAS-DH1 and DL/ECKAS-DH2. It does not assume the + * validity of the input public key (see also Section 7.2.1). + * <p> + * Note: As stated P1363 compatibility mode with ECDH can be preset, and + * in this case the implementation doesn't have a ECDH compatibility mode + * (if you want that just use ECDHBasicAgreement and note they both implement + * BasicAgreement!). + */ +public class ECDHCBasicAgreement + implements BasicAgreement +{ + ECPrivateKeyParameters key; + + public void init( + CipherParameters key) + { + this.key = (ECPrivateKeyParameters)key; + } + + public int getFieldSize() + { + return (key.getParameters().getCurve().getFieldSize() + 7) / 8; + } + + public BigInteger calculateAgreement( + CipherParameters pubKey) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; + ECDomainParameters params = pub.getParameters(); + ECPoint P = pub.getQ().multiply(params.getH().multiply(key.getD())); + + // if (p.isInfinity()) throw new RuntimeException("Invalid public key"); + + return P.getX().toBigInteger(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java b/core/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java new file mode 100644 index 00000000..da88b4ac --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java @@ -0,0 +1,91 @@ +package org.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.MQVPrivateParameters; +import org.bouncycastle.crypto.params.MQVPublicParameters; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +public class ECMQVBasicAgreement + implements BasicAgreement +{ + MQVPrivateParameters privParams; + + public void init( + CipherParameters key) + { + this.privParams = (MQVPrivateParameters)key; + } + + public int getFieldSize() + { + return (privParams.getStaticPrivateKey().getParameters().getCurve().getFieldSize() + 7) / 8; + } + + public BigInteger calculateAgreement(CipherParameters pubKey) + { + MQVPublicParameters pubParams = (MQVPublicParameters)pubKey; + + ECPrivateKeyParameters staticPrivateKey = privParams.getStaticPrivateKey(); + + ECPoint agreement = calculateMqvAgreement(staticPrivateKey.getParameters(), staticPrivateKey, + privParams.getEphemeralPrivateKey(), privParams.getEphemeralPublicKey(), + pubParams.getStaticPublicKey(), pubParams.getEphemeralPublicKey()); + + return agreement.getX().toBigInteger(); + } + + // The ECMQV Primitive as described in SEC-1, 3.4 + private ECPoint calculateMqvAgreement( + ECDomainParameters parameters, + ECPrivateKeyParameters d1U, + ECPrivateKeyParameters d2U, + ECPublicKeyParameters Q2U, + ECPublicKeyParameters Q1V, + ECPublicKeyParameters Q2V) + { + BigInteger n = parameters.getN(); + int e = (n.bitLength() + 1) / 2; + BigInteger powE = ECConstants.ONE.shiftLeft(e); + + // The Q2U public key is optional + ECPoint q; + if (Q2U == null) + { + q = parameters.getG().multiply(d2U.getD()); + } + else + { + q = Q2U.getQ(); + } + + BigInteger x = q.getX().toBigInteger(); + BigInteger xBar = x.mod(powE); + BigInteger Q2UBar = xBar.setBit(e); + BigInteger s = d1U.getD().multiply(Q2UBar).mod(n).add(d2U.getD()).mod(n); + + BigInteger xPrime = Q2V.getQ().getX().toBigInteger(); + BigInteger xPrimeBar = xPrime.mod(powE); + BigInteger Q2VBar = xPrimeBar.setBit(e); + + BigInteger hs = parameters.getH().multiply(s).mod(n); + +// ECPoint p = Q1V.getQ().multiply(Q2VBar).add(Q2V.getQ()).multiply(hs); + ECPoint p = ECAlgorithms.sumOfTwoMultiplies( + Q1V.getQ(), Q2VBar.multiply(hs).mod(n), Q2V.getQ(), hs); + + if (p.isInfinity()) + { + throw new IllegalStateException("Infinity is not a valid agreement value for MQV"); + } + + return p; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java new file mode 100644 index 00000000..94efd92d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java @@ -0,0 +1,573 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.util.Arrays; + +/** + * A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. + * <p/> + * <p/> + * The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper + * <a href="http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf"> + * "Password Authenticated Key Exchange by Juggling, 2008."</a> + * <p/> + * <p/> + * The J-PAKE protocol is symmetric. + * There is no notion of a <i>client</i> or <i>server</i>, but rather just two <i>participants</i>. + * An instance of {@link JPAKEParticipant} represents one participant, and + * is the primary interface for executing the exchange. + * <p/> + * <p/> + * To execute an exchange, construct a {@link JPAKEParticipant} on each end, + * and call the following 7 methods + * (once and only once, in the given order, for each participant, sending messages between them as described): + * <ol> + * <li>{@link #createRound1PayloadToSend()} - and send the payload to the other participant</li> + * <li>{@link #validateRound1PayloadReceived(JPAKERound1Payload)} - use the payload received from the other participant</li> + * <li>{@link #createRound2PayloadToSend()} - and send the payload to the other participant</li> + * <li>{@link #validateRound2PayloadReceived(JPAKERound2Payload)} - use the payload received from the other participant</li> + * <li>{@link #calculateKeyingMaterial()}</li> + * <li>{@link #createRound3PayloadToSend(BigInteger)} - and send the payload to the other participant</li> + * <li>{@link #validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} - use the payload received from the other participant</li> + * </ol> + * <p/> + * <p/> + * Each side should derive a session key from the keying material returned by {@link #calculateKeyingMaterial()}. + * The caller is responsible for deriving the session key using a secure key derivation function (KDF). + * <p/> + * <p/> + * Round 3 is an optional key confirmation process. + * If you do not execute round 3, then there is no assurance that both participants are using the same key. + * (i.e. if the participants used different passwords, then their session keys will differ.) + * <p/> + * <p/> + * If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. + * <p/> + * <p/> + * The symmetric design can easily support the asymmetric cases when one party initiates the communication. + * e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. + * Also, in some cases, the key confirmation payload can be sent together with the round2 payload. + * These are the trivial techniques to optimize the communication. + * <p/> + * <p/> + * The key confirmation process is implemented as specified in + * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>, + * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + * <p/> + * <p/> + * This class is stateful and NOT threadsafe. + * Each instance should only be used for ONE complete J-PAKE exchange + * (i.e. a new {@link JPAKEParticipant} should be constructed for each new J-PAKE exchange). + * <p/> + * <p/> + * See {@link JPAKEExample} for example usage. + */ +public class JPAKEParticipant +{ + /* + * Possible internal states. Used for state checking. + */ + + public static final int STATE_INITIALIZED = 0; + public static final int STATE_ROUND_1_CREATED = 10; + public static final int STATE_ROUND_1_VALIDATED = 20; + public static final int STATE_ROUND_2_CREATED = 30; + public static final int STATE_ROUND_2_VALIDATED = 40; + public static final int STATE_KEY_CALCULATED = 50; + public static final int STATE_ROUND_3_CREATED = 60; + public static final int STATE_ROUND_3_VALIDATED = 70; + + /** + * Unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + */ + private final String participantId; + + /** + * Shared secret. This only contains the secret between construction + * and the call to {@link #calculateKeyingMaterial()}. + * <p/> + * i.e. When {@link #calculateKeyingMaterial()} is called, this buffer overwritten with 0's, + * and the field is set to null. + */ + private char[] password; + + /** + * Digest to use during calculations. + */ + private final Digest digest; + + /** + * Source of secure random data. + */ + private final SecureRandom random; + + private final BigInteger p; + private final BigInteger q; + private final BigInteger g; + + /** + * The participantId of the other participant in this exchange. + */ + private String partnerParticipantId; + + /** + * Alice's x1 or Bob's x3. + */ + private BigInteger x1; + /** + * Alice's x2 or Bob's x4. + */ + private BigInteger x2; + /** + * Alice's g^x1 or Bob's g^x3. + */ + private BigInteger gx1; + /** + * Alice's g^x2 or Bob's g^x4. + */ + private BigInteger gx2; + /** + * Alice's g^x3 or Bob's g^x1. + */ + private BigInteger gx3; + /** + * Alice's g^x4 or Bob's g^x2. + */ + private BigInteger gx4; + /** + * Alice's B or Bob's A. + */ + private BigInteger b; + + /** + * The current state. + * See the <tt>STATE_*</tt> constants for possible values. + */ + private int state; + + /** + * Convenience constructor for a new {@link JPAKEParticipant} that uses + * the {@link JPAKEPrimeOrderGroups#NIST_3072} prime order group, + * a SHA-256 digest, and a default {@link SecureRandom} implementation. + * <p/> + * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. + * + * @param participantId unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + * @param password shared secret. + * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). + * Caller should clear the input password as soon as possible. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public JPAKEParticipant( + String participantId, + char[] password) + { + this( + participantId, + password, + JPAKEPrimeOrderGroups.NIST_3072); + } + + + /** + * Convenience constructor for a new {@link JPAKEParticipant} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + * <p/> + * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. + * + * @param participantId unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + * @param password shared secret. + * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). + * Caller should clear the input password as soon as possible. + * @param group prime order group. + * See {@link JPAKEPrimeOrderGroups} for standard groups + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public JPAKEParticipant( + String participantId, + char[] password, + JPAKEPrimeOrderGroup group) + { + this( + participantId, + password, + group, + new SHA256Digest(), + new SecureRandom()); + } + + + /** + * Construct a new {@link JPAKEParticipant}. + * <p/> + * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. + * + * @param participantId unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + * @param password shared secret. + * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). + * Caller should clear the input password as soon as possible. + * @param group prime order group. + * See {@link JPAKEPrimeOrderGroups} for standard groups + * @param digest digest to use during zero knowledge proofs and key confirmation (SHA-256 or stronger preferred) + * @param random source of secure random data for x1 and x2, and for the zero knowledge proofs + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public JPAKEParticipant( + String participantId, + char[] password, + JPAKEPrimeOrderGroup group, + Digest digest, + SecureRandom random) + { + JPAKEUtil.validateNotNull(participantId, "participantId"); + JPAKEUtil.validateNotNull(password, "password"); + JPAKEUtil.validateNotNull(group, "p"); + JPAKEUtil.validateNotNull(digest, "digest"); + JPAKEUtil.validateNotNull(random, "random"); + if (password.length == 0) + { + throw new IllegalArgumentException("Password must not be empty."); + } + + this.participantId = participantId; + + /* + * Create a defensive copy so as to fully encapsulate the password. + * + * This array will contain the password for the lifetime of this + * participant BEFORE {@link #calculateKeyingMaterial()} is called. + * + * i.e. When {@link #calculateKeyingMaterial()} is called, the array will be cleared + * in order to remove the password from memory. + * + * The caller is responsible for clearing the original password array + * given as input to this constructor. + */ + this.password = Arrays.copyOf(password, password.length); + + this.p = group.getP(); + this.q = group.getQ(); + this.g = group.getG(); + + this.digest = digest; + this.random = random; + + this.state = STATE_INITIALIZED; + } + + /** + * Gets the current state of this participant. + * See the <tt>STATE_*</tt> constants for possible values. + */ + public int getState() + { + return this.state; + } + + /** + * Creates and returns the payload to send to the other participant during round 1. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_1_CREATED}. + */ + public JPAKERound1Payload createRound1PayloadToSend() + { + if (this.state >= STATE_ROUND_1_CREATED) + { + throw new IllegalStateException("Round1 payload already created for " + participantId); + } + + this.x1 = JPAKEUtil.generateX1(q, random); + this.x2 = JPAKEUtil.generateX2(q, random); + + this.gx1 = JPAKEUtil.calculateGx(p, g, x1); + this.gx2 = JPAKEUtil.calculateGx(p, g, x2); + BigInteger[] knowledgeProofForX1 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); + BigInteger[] knowledgeProofForX2 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); + + this.state = STATE_ROUND_1_CREATED; + + return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + } + + /** + * Validates the payload received from the other participant during round 1. + * <p/> + * <p/> + * Must be called prior to {@link #createRound2PayloadToSend()}. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_1_VALIDATED}. + * + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called multiple times. + */ + public void validateRound1PayloadReceived(JPAKERound1Payload round1PayloadReceived) + throws CryptoException + { + if (this.state >= STATE_ROUND_1_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for round1 payload for" + participantId); + } + this.partnerParticipantId = round1PayloadReceived.getParticipantId(); + this.gx3 = round1PayloadReceived.getGx1(); + this.gx4 = round1PayloadReceived.getGx2(); + + BigInteger[] knowledgeProofForX3 = round1PayloadReceived.getKnowledgeProofForX1(); + BigInteger[] knowledgeProofForX4 = round1PayloadReceived.getKnowledgeProofForX2(); + + JPAKEUtil.validateParticipantIdsDiffer(participantId, round1PayloadReceived.getParticipantId()); + JPAKEUtil.validateGx4(gx4); + JPAKEUtil.validateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.getParticipantId(), digest); + JPAKEUtil.validateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.getParticipantId(), digest); + + this.state = STATE_ROUND_1_VALIDATED; + } + + /** + * Creates and returns the payload to send to the other participant during round 2. + * <p/> + * <p/> + * {@link #validateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_2_CREATED}. + * + * @throws IllegalStateException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times + */ + public JPAKERound2Payload createRound2PayloadToSend() + { + if (this.state >= STATE_ROUND_2_CREATED) + { + throw new IllegalStateException("Round2 payload already created for " + this.participantId); + } + if (this.state < STATE_ROUND_1_VALIDATED) + { + throw new IllegalStateException("Round1 payload must be validated prior to creating Round2 payload for " + this.participantId); + } + BigInteger gA = JPAKEUtil.calculateGA(p, gx1, gx3, gx4); + BigInteger s = JPAKEUtil.calculateS(password); + BigInteger x2s = JPAKEUtil.calculateX2s(q, x2, s); + BigInteger A = JPAKEUtil.calculateA(p, q, gA, x2s); + BigInteger[] knowledgeProofForX2s = JPAKEUtil.calculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); + + this.state = STATE_ROUND_2_CREATED; + + return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s); + } + + /** + * Validates the payload received from the other participant during round 2. + * <p/> + * <p/> + * Note that this DOES NOT detect a non-common password. + * The only indication of a non-common password is through derivation + * of different keys (which can be detected explicitly by executing round 3 and round 4) + * <p/> + * <p/> + * Must be called prior to {@link #calculateKeyingMaterial()}. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_2_VALIDATED}. + * + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times + */ + public void validateRound2PayloadReceived(JPAKERound2Payload round2PayloadReceived) + throws CryptoException + { + if (this.state >= STATE_ROUND_2_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for round2 payload for" + participantId); + } + if (this.state < STATE_ROUND_1_VALIDATED) + { + throw new IllegalStateException("Round1 payload must be validated prior to validating Round2 payload for " + this.participantId); + } + BigInteger gB = JPAKEUtil.calculateGA(p, gx3, gx1, gx2); + this.b = round2PayloadReceived.getA(); + BigInteger[] knowledgeProofForX4s = round2PayloadReceived.getKnowledgeProofForX2s(); + + JPAKEUtil.validateParticipantIdsDiffer(participantId, round2PayloadReceived.getParticipantId()); + JPAKEUtil.validateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.getParticipantId()); + JPAKEUtil.validateGa(gB); + JPAKEUtil.validateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.getParticipantId(), digest); + + this.state = STATE_ROUND_2_VALIDATED; + } + + /** + * Calculates and returns the key material. + * A session key must be derived from this key material using a secure key derivation function (KDF). + * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}). + * <p/> + * <p/> + * The keying material will be identical for each participant if and only if + * each participant's password is the same. i.e. If the participants do not + * share the same password, then each participant will derive a different key. + * Therefore, if you immediately start using a key derived from + * the keying material, then you must handle detection of incorrect keys. + * If you want to handle this detection explicitly, you can optionally perform + * rounds 3 and 4. See {@link JPAKEParticipant} for details on how to execute + * rounds 3 and 4. + * <p/> + * <p/> + * The keying material will be in the range <tt>[0, p-1]</tt>. + * <p/> + * <p/> + * {@link #validateRound2PayloadReceived(JPAKERound2Payload)} must be called prior to this method. + * <p/> + * <p/> + * As a side effect, the internal {@link #password} array is cleared, since it is no longer needed. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_KEY_CALCULATED}. + * + * @throws IllegalStateException if called prior to {@link #validateRound2PayloadReceived(JPAKERound2Payload)}, + * or if called multiple times. + */ + public BigInteger calculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Key already calculated for " + participantId); + } + if (this.state < STATE_ROUND_2_VALIDATED) + { + throw new IllegalStateException("Round2 payload must be validated prior to creating key for " + participantId); + } + BigInteger s = JPAKEUtil.calculateS(password); + + /* + * Clear the password array from memory, since we don't need it anymore. + * + * Also set the field to null as a flag to indicate that the key has already been calculated. + */ + Arrays.fill(password, (char)0); + this.password = null; + + BigInteger keyingMaterial = JPAKEUtil.calculateKeyingMaterial(p, q, gx4, x2, s, b); + + /* + * Clear the ephemeral private key fields as well. + * Note that we're relying on the garbage collector to do its job to clean these up. + * The old objects will hang around in memory until the garbage collector destroys them. + * + * If the ephemeral private keys x1 and x2 are leaked, + * the attacker might be able to brute-force the password. + */ + this.x1 = null; + this.x2 = null; + this.b = null; + + /* + * Do not clear gx* yet, since those are needed by round 3. + */ + + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + + /** + * Creates and returns the payload to send to the other participant during round 3. + * <p/> + * <p/> + * See {@link JPAKEParticipant} for more details on round 3. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_CREATED}. + * + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public JPAKERound3Payload createRound3PayloadToSend(BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_CREATED) + { + throw new IllegalStateException("Round3 payload already created for " + this.participantId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to creating Round3 payload for " + this.participantId); + } + + BigInteger macTag = JPAKEUtil.calculateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest); + + this.state = STATE_ROUND_3_CREATED; + + return new JPAKERound3Payload(participantId, macTag); + } + + /** + * Validates the payload received from the other participant during round 3. + * <p/> + * <p/> + * See {@link JPAKEParticipant} for more details on round 3. + * <p/> + * <p/> + * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_VALIDATED}. + * + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public void validateRound3PayloadReceived(JPAKERound3Payload round3PayloadReceived, BigInteger keyingMaterial) + throws CryptoException + { + if (this.state >= STATE_ROUND_3_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for round3 payload for" + participantId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated validated prior to validating Round3 payload for " + this.participantId); + } + JPAKEUtil.validateParticipantIdsDiffer(participantId, round3PayloadReceived.getParticipantId()); + JPAKEUtil.validateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.getParticipantId()); + + JPAKEUtil.validateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest, + round3PayloadReceived.getMacTag()); + + + /* + * Clear the rest of the fields. + */ + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_ROUND_3_VALIDATED; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java new file mode 100644 index 00000000..d5df727d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java @@ -0,0 +1,122 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; + +/** + * A pre-computed prime order group for use during a J-PAKE exchange. + * <p/> + * <p/> + * Typically a Schnorr group is used. In general, J-PAKE can use any prime order group + * that is suitable for public key cryptography, including elliptic curve cryptography. + * <p/> + * <p/> + * See {@link JPAKEPrimeOrderGroups} for convenient standard groups. + * <p/> + * <p/> + * NIST <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a> + * many groups that can be used for the desired level of security. + */ +public class JPAKEPrimeOrderGroup +{ + private final BigInteger p; + private final BigInteger q; + private final BigInteger g; + + /** + * Constructs a new {@link JPAKEPrimeOrderGroup}. + * <p/> + * <p/> + * In general, you should use one of the pre-approved groups from + * {@link JPAKEPrimeOrderGroups}, rather than manually constructing one. + * <p/> + * <p/> + * The following basic checks are performed: + * <ul> + * <li>p-1 must be evenly divisible by q</li> + * <li>g must be in [2, p-1]</li> + * <li>g^q mod p must equal 1</li> + * <li>p must be prime (within reasonably certainty)</li> + * <li>q must be prime (within reasonably certainty)</li> + * </ul> + * <p/> + * <p/> + * The prime checks are performed using {@link BigInteger#isProbablePrime(int)}, + * and are therefore subject to the same probability guarantees. + * <p/> + * <p/> + * These checks prevent trivial mistakes. + * However, due to the small uncertainties if p and q are not prime, + * advanced attacks are not prevented. + * Use it at your own risk. + * + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if any of the above validations fail + */ + public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) + { + /* + * Don't skip the checks on user-specified groups. + */ + this(p, q, g, false); + } + + /** + * Internal package-private constructor used by the pre-approved + * groups in {@link JPAKEPrimeOrderGroups}. + * These pre-approved groups can avoid the expensive checks. + */ + JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, boolean skipChecks) + { + JPAKEUtil.validateNotNull(p, "p"); + JPAKEUtil.validateNotNull(q, "q"); + JPAKEUtil.validateNotNull(g, "g"); + + if (!skipChecks) + { + if (!p.subtract(JPAKEUtil.ONE).mod(q).equals(JPAKEUtil.ZERO)) + { + throw new IllegalArgumentException("p-1 must be evenly divisible by q"); + } + if (g.compareTo(BigInteger.valueOf(2)) == -1 || g.compareTo(p.subtract(JPAKEUtil.ONE)) == 1) + { + throw new IllegalArgumentException("g must be in [2, p-1]"); + } + if (!g.modPow(q, p).equals(JPAKEUtil.ONE)) + { + throw new IllegalArgumentException("g^q mod p must equal 1"); + } + /* + * Note that these checks do not guarantee that p and q are prime. + * We just have reasonable certainty that they are prime. + */ + if (!p.isProbablePrime(20)) + { + throw new IllegalArgumentException("p must be prime"); + } + if (!q.isProbablePrime(20)) + { + throw new IllegalArgumentException("q must be prime"); + } + } + + this.p = p; + this.q = q; + this.g = g; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getG() + { + return g; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java new file mode 100644 index 00000000..812d776e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java @@ -0,0 +1,113 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; + +/** + * Standard pre-computed prime order groups for use by J-PAKE. + * (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) + * <p/> + * <p/> + * This class contains some convenient constants for use as input for + * constructing {@link JPAKEParticipant}s. + * <p/> + * <p/> + * The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), + * and from the prime order groups + * <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">published by NIST</a>. + */ +public class JPAKEPrimeOrderGroups +{ + /** + * From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) + * 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. + */ + public static final JPAKEPrimeOrderGroup SUN_JCE_1024 = new JPAKEPrimeOrderGroup( + // p + new BigInteger( + "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + + "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" + + "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" + + "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16), + // q + new BigInteger( + "9760508f15230bccb292b982a2eb840bf0581cf5", 16), + // g + new BigInteger( + "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" + + "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" + + "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" + + "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16), + true + ); + + /** + * From NIST. + * 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. + */ + public static final JPAKEPrimeOrderGroup NIST_2048 = new JPAKEPrimeOrderGroup( + // p + new BigInteger( + "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" + + "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" + + "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" + + "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" + + "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" + + "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" + + "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" + + "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16), + // q + new BigInteger( + "90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16), + // g + new BigInteger( + "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" + + "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" + + "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" + + "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" + + "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" + + "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" + + "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" + + "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16), + true + ); + + /** + * From NIST. + * 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. + */ + public static final JPAKEPrimeOrderGroup NIST_3072 = new JPAKEPrimeOrderGroup( + // p + new BigInteger( + "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + + "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" + + "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" + + "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" + + "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" + + "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" + + "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" + + "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" + + "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" + + "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" + + "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" + + "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16), + // q + new BigInteger( + "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16), + // g + new BigInteger( + "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" + + "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" + + "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" + + "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" + + "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" + + "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" + + "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" + + "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" + + "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" + + "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" + + "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" + + "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16), + true + ); + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound1Payload.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound1Payload.java new file mode 100644 index 00000000..b319f9c4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound1Payload.java @@ -0,0 +1,99 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; + +import org.bouncycastle.util.Arrays; + +/** + * The payload sent/received during the first round of a J-PAKE exchange. + * <p/> + * <p/> + * Each {@link JPAKEParticipant} creates and sends an instance + * of this payload to the other {@link JPAKEParticipant}. + * The payload to send should be created via + * {@link JPAKEParticipant#createRound1PayloadToSend()}. + * <p/> + * <p/> + * Each {@link JPAKEParticipant} must also validate the payload + * received from the other {@link JPAKEParticipant}. + * The received payload should be validated via + * {@link JPAKEParticipant#validateRound1PayloadReceived(JPAKERound1Payload)}. + * <p/> + */ +public class JPAKERound1Payload +{ + /** + * The id of the {@link JPAKEParticipant} who created/sent this payload. + */ + private final String participantId; + + /** + * The value of g^x1 + */ + private final BigInteger gx1; + + /** + * The value of g^x2 + */ + private final BigInteger gx2; + + /** + * The zero knowledge proof for x1. + * <p/> + * This is a two element array, containing {g^v, r} for x1. + */ + private final BigInteger[] knowledgeProofForX1; + + /** + * The zero knowledge proof for x2. + * <p/> + * This is a two element array, containing {g^v, r} for x2. + */ + private final BigInteger[] knowledgeProofForX2; + + public JPAKERound1Payload( + String participantId, + BigInteger gx1, + BigInteger gx2, + BigInteger[] knowledgeProofForX1, + BigInteger[] knowledgeProofForX2) + { + JPAKEUtil.validateNotNull(participantId, "participantId"); + JPAKEUtil.validateNotNull(gx1, "gx1"); + JPAKEUtil.validateNotNull(gx2, "gx2"); + JPAKEUtil.validateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + JPAKEUtil.validateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + + this.participantId = participantId; + this.gx1 = gx1; + this.gx2 = gx2; + this.knowledgeProofForX1 = Arrays.copyOf(knowledgeProofForX1, knowledgeProofForX1.length); + this.knowledgeProofForX2 = Arrays.copyOf(knowledgeProofForX2, knowledgeProofForX2.length); + } + + public String getParticipantId() + { + return participantId; + } + + public BigInteger getGx1() + { + return gx1; + } + + public BigInteger getGx2() + { + return gx2; + } + + public BigInteger[] getKnowledgeProofForX1() + { + return Arrays.copyOf(knowledgeProofForX1, knowledgeProofForX1.length); + } + + public BigInteger[] getKnowledgeProofForX2() + { + return Arrays.copyOf(knowledgeProofForX2, knowledgeProofForX2.length); + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound2Payload.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound2Payload.java new file mode 100644 index 00000000..8800cf5f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound2Payload.java @@ -0,0 +1,71 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; + +import org.bouncycastle.util.Arrays; + +/** + * The payload sent/received during the second round of a J-PAKE exchange. + * <p/> + * <p/> + * Each {@link JPAKEParticipant} creates and sends an instance + * of this payload to the other {@link JPAKEParticipant}. + * The payload to send should be created via + * {@link JPAKEParticipant#createRound2PayloadToSend()} + * <p/> + * <p/> + * Each {@link JPAKEParticipant} must also validate the payload + * received from the other {@link JPAKEParticipant}. + * The received payload should be validated via + * {@link JPAKEParticipant#validateRound2PayloadReceived(JPAKERound2Payload)} + * <p/> + */ +public class JPAKERound2Payload +{ + /** + * The id of the {@link JPAKEParticipant} who created/sent this payload. + */ + private final String participantId; + + /** + * The value of A, as computed during round 2. + */ + private final BigInteger a; + + /** + * The zero knowledge proof for x2 * s. + * <p/> + * This is a two element array, containing {g^v, r} for x2 * s. + */ + private final BigInteger[] knowledgeProofForX2s; + + public JPAKERound2Payload( + String participantId, + BigInteger a, + BigInteger[] knowledgeProofForX2s) + { + JPAKEUtil.validateNotNull(participantId, "participantId"); + JPAKEUtil.validateNotNull(a, "a"); + JPAKEUtil.validateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); + + this.participantId = participantId; + this.a = a; + this.knowledgeProofForX2s = Arrays.copyOf(knowledgeProofForX2s, knowledgeProofForX2s.length); + } + + public String getParticipantId() + { + return participantId; + } + + public BigInteger getA() + { + return a; + } + + public BigInteger[] getKnowledgeProofForX2s() + { + return Arrays.copyOf(knowledgeProofForX2s, knowledgeProofForX2s.length); + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound3Payload.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound3Payload.java new file mode 100644 index 00000000..c1255df6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKERound3Payload.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; + +/** + * The payload sent/received during the optional third round of a J-PAKE exchange, + * which is for explicit key confirmation. + * <p/> + * <p/> + * Each {@link JPAKEParticipant} creates and sends an instance + * of this payload to the other {@link JPAKEParticipant}. + * The payload to send should be created via + * {@link JPAKEParticipant#createRound3PayloadToSend(BigInteger)} + * <p/> + * <p/> + * Each {@link JPAKEParticipant} must also validate the payload + * received from the other {@link JPAKEParticipant}. + * The received payload should be validated via + * {@link JPAKEParticipant#validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} + * <p/> + */ +public class JPAKERound3Payload +{ + /** + * The id of the {@link JPAKEParticipant} who created/sent this payload. + */ + private final String participantId; + + /** + * The value of MacTag, as computed by round 3. + * + * @see JPAKEUtil#calculateMacTag(String, String, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest) + */ + private final BigInteger macTag; + + public JPAKERound3Payload(String participantId, BigInteger magTag) + { + this.participantId = participantId; + this.macTag = magTag; + } + + public String getParticipantId() + { + return participantId; + } + + public BigInteger getMacTag() + { + return macTag; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java new file mode 100644 index 00000000..416152e9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java @@ -0,0 +1,508 @@ +package org.bouncycastle.crypto.agreement.jpake; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.Strings; + +/** + * Primitives needed for a J-PAKE exchange. + * <p/> + * <p/> + * The recommended way to perform a J-PAKE exchange is by using + * two {@link JPAKEParticipant}s. Internally, those participants + * call these primitive operations in {@link JPAKEUtil}. + * <p/> + * <p/> + * The primitives, however, can be used without a {@link JPAKEParticipant} + * if needed. + */ +public class JPAKEUtil +{ + static final BigInteger ZERO = BigInteger.valueOf(0); + static final BigInteger ONE = BigInteger.valueOf(1); + + /** + * Return a value that can be used as x1 or x3 during round 1. + * <p/> + * <p/> + * The returned value is a random value in the range <tt>[0, q-1]</tt>. + */ + public static BigInteger generateX1( + BigInteger q, + SecureRandom random) + { + BigInteger min = ZERO; + BigInteger max = q.subtract(ONE); + return BigIntegers.createRandomInRange(min, max, random); + } + + /** + * Return a value that can be used as x2 or x4 during round 1. + * <p/> + * <p/> + * The returned value is a random value in the range <tt>[1, q-1]</tt>. + */ + public static BigInteger generateX2( + BigInteger q, + SecureRandom random) + { + BigInteger min = ONE; + BigInteger max = q.subtract(ONE); + return BigIntegers.createRandomInRange(min, max, random); + } + + /** + * Converts the given password to a {@link BigInteger} + * for use in arithmetic calculations. + */ + public static BigInteger calculateS(char[] password) + { + return new BigInteger(Strings.toUTF8ByteArray(password)); + } + + /** + * Calculate g^x mod p as done in round 1. + */ + public static BigInteger calculateGx( + BigInteger p, + BigInteger g, + BigInteger x) + { + return g.modPow(x, p); + } + + + /** + * Calculate ga as done in round 2. + */ + public static BigInteger calculateGA( + BigInteger p, + BigInteger gx1, + BigInteger gx3, + BigInteger gx4) + { + // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 + return gx1.multiply(gx3).multiply(gx4).mod(p); + } + + + /** + * Calculate x2 * s as done in round 2. + */ + public static BigInteger calculateX2s( + BigInteger q, + BigInteger x2, + BigInteger s) + { + return x2.multiply(s).mod(q); + } + + + /** + * Calculate A as done in round 2. + */ + public static BigInteger calculateA( + BigInteger p, + BigInteger q, + BigInteger gA, + BigInteger x2s) + { + // A = ga^(x*s) + return gA.modPow(x2s, p); + } + + /** + * Calculate a zero knowledge proof of x using Schnorr's signature. + * The returned array has two elements {g^v, r = v-x*h} for x. + */ + public static BigInteger[] calculateZeroKnowledgeProof( + BigInteger p, + BigInteger q, + BigInteger g, + BigInteger gx, + BigInteger x, + String participantId, + Digest digest, + SecureRandom random) + { + BigInteger[] zeroKnowledgeProof = new BigInteger[2]; + + /* Generate a random v, and compute g^v */ + BigInteger vMin = ZERO; + BigInteger vMax = q.subtract(ONE); + BigInteger v = BigIntegers.createRandomInRange(vMin, vMax, random); + + BigInteger gv = g.modPow(v, p); + BigInteger h = calculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h + + zeroKnowledgeProof[0] = gv; + zeroKnowledgeProof[1] = v.subtract(x.multiply(h)).mod(q); // r = v-x*h + + return zeroKnowledgeProof; + } + + private static BigInteger calculateHashForZeroKnowledgeProof( + BigInteger g, + BigInteger gr, + BigInteger gx, + String participantId, + Digest digest) + { + digest.reset(); + + updateDigestIncludingSize(digest, g); + + updateDigestIncludingSize(digest, gr); + + updateDigestIncludingSize(digest, gx); + + updateDigestIncludingSize(digest, participantId); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return new BigInteger(output); + } + + /** + * Validates that g^x4 is not 1. + * + * @throws CryptoException if g^x4 is 1 + */ + public static void validateGx4(BigInteger gx4) + throws CryptoException + { + if (gx4.equals(ONE)) + { + throw new CryptoException("g^x validation failed. g^x should not be 1."); + } + } + + /** + * Validates that ga is not 1. + * <p/> + * <p/> + * As described by Feng Hao... + * <p/> + * <blockquote> + * Alice could simply check ga != 1 to ensure it is a generator. + * In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks. + * Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q. + * </blockquote> + * + * @throws CryptoException if ga is 1 + */ + public static void validateGa(BigInteger ga) + throws CryptoException + { + if (ga.equals(ONE)) + { + throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again."); + } + } + + /** + * Validates the zero knowledge proof (generated by + * {@link #calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, String, Digest, SecureRandom)}) + * is correct. + * + * @throws CryptoException if the zero knowledge proof is not correct + */ + public static void validateZeroKnowledgeProof( + BigInteger p, + BigInteger q, + BigInteger g, + BigInteger gx, + BigInteger[] zeroKnowledgeProof, + String participantId, + Digest digest) + throws CryptoException + { + + /* sig={g^v,r} */ + BigInteger gv = zeroKnowledgeProof[0]; + BigInteger r = zeroKnowledgeProof[1]; + + BigInteger h = calculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); + if (!(gx.compareTo(ZERO) == 1 && // g^x > 0 + gx.compareTo(p) == -1 && // g^x < p + gx.modPow(q, p).compareTo(ONE) == 0 && // g^x^q mod q = 1 + /* + * Below, I took an straightforward way to compute g^r * g^x^h, + * which needs 2 exp. Using a simultaneous computation technique + * would only need 1 exp. + */ + g.modPow(r, p).multiply(gx.modPow(h, p)).mod(p).compareTo(gv) == 0)) // g^v=g^r * g^x^h + { + throw new CryptoException("Zero-knowledge proof validation failed"); + } + } + + /** + * Calculates the keying material, which can be done after round 2 has completed. + * A session key must be derived from this key material using a secure key derivation function (KDF). + * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}). + * <p/> + * <p/> + * <pre> + * KeyingMaterial = (B/g^{x2*x4*s})^x2 + * </pre> + */ + public static BigInteger calculateKeyingMaterial( + BigInteger p, + BigInteger q, + BigInteger gx4, + BigInteger x2, + BigInteger s, + BigInteger B) + { + return gx4.modPow(x2.multiply(s).negate().mod(q), p).multiply(B).modPow(x2, p); + } + + /** + * Validates that the given participant ids are not equal. + * (For the J-PAKE exchange, each participant must use a unique id.) + * + * @throws CryptoException if the participantId strings are equal. + */ + public static void validateParticipantIdsDiffer(String participantId1, String participantId2) + throws CryptoException + { + if (participantId1.equals(participantId2)) + { + throw new CryptoException( + "Both participants are using the same participantId (" + + participantId1 + + "). This is not allowed. " + + "Each participant must use a unique participantId."); + } + } + + /** + * Validates that the given participant ids are equal. + * This is used to ensure that the payloads received from + * each round all come from the same participant. + * + * @throws CryptoException if the participantId strings are equal. + */ + public static void validateParticipantIdsEqual(String expectedParticipantId, String actualParticipantId) + throws CryptoException + { + if (!expectedParticipantId.equals(actualParticipantId)) + { + throw new CryptoException( + "Received payload from incorrect partner (" + + actualParticipantId + + "). Expected to receive payload from " + + expectedParticipantId + + "."); + } + } + + /** + * Validates that the given object is not null. + * + * @param object object in question + * @param description name of the object (to be used in exception message) + * @throws NullPointerException if the object is null. + */ + public static void validateNotNull(Object object, String description) + { + if (object == null) + { + throw new NullPointerException(description + " must not be null"); + } + } + + /** + * Calculates the MacTag (to be used for key confirmation), as defined by + * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>, + * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + * <p/> + * <p/> + * <pre> + * MacTag = HMAC(MacKey, MacLen, MacData) + * + * MacKey = H(K || "JPAKE_KC") + * + * MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4 + * + * Note that both participants use "KC_1_U" because the sender of the round 3 message + * is always the initiator for key confirmation. + * + * HMAC = {@link HMac} used with the given {@link Digest} + * H = The given {@link Digest}</li> + * MacLen = length of MacTag + * </pre> + * <p/> + */ + public static BigInteger calculateMacTag( + String participantId, + String partnerParticipantId, + BigInteger gx1, + BigInteger gx2, + BigInteger gx3, + BigInteger gx4, + BigInteger keyingMaterial, + Digest digest) + { + byte[] macKey = calculateMacKey( + keyingMaterial, + digest); + + HMac mac = new HMac(digest); + byte[] macOutput = new byte[mac.getMacSize()]; + mac.init(new KeyParameter(macKey)); + + /* + * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4. + */ + updateMac(mac, "KC_1_U"); + updateMac(mac, participantId); + updateMac(mac, partnerParticipantId); + updateMac(mac, gx1); + updateMac(mac, gx2); + updateMac(mac, gx3); + updateMac(mac, gx4); + + mac.doFinal(macOutput, 0); + + Arrays.fill(macKey, (byte)0); + + return new BigInteger(macOutput); + + } + + /** + * Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). + * <p/> + * <p/> + * <pre> + * MacKey = H(K || "JPAKE_KC") + * </pre> + */ + private static byte[] calculateMacKey(BigInteger keyingMaterial, Digest digest) + { + digest.reset(); + + updateDigest(digest, keyingMaterial); + /* + * This constant is used to ensure that the macKey is NOT the same as the derived key. + */ + updateDigest(digest, "JPAKE_KC"); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return output; + } + + /** + * Validates the MacTag received from the partner participant. + * <p/> + * + * @param partnerMacTag the MacTag received from the partner. + * @throws CryptoException if the participantId strings are equal. + */ + public static void validateMacTag( + String participantId, + String partnerParticipantId, + BigInteger gx1, + BigInteger gx2, + BigInteger gx3, + BigInteger gx4, + BigInteger keyingMaterial, + Digest digest, + BigInteger partnerMacTag) + throws CryptoException + { + /* + * Calculate the expected MacTag using the parameters as the partner + * would have used when the partner called calculateMacTag. + * + * i.e. basically all the parameters are reversed. + * participantId <-> partnerParticipantId + * x1 <-> x3 + * x2 <-> x4 + */ + BigInteger expectedMacTag = calculateMacTag( + partnerParticipantId, + participantId, + gx3, + gx4, + gx1, + gx2, + keyingMaterial, + digest); + + if (!expectedMacTag.equals(partnerMacTag)) + { + throw new CryptoException( + "Partner MacTag validation failed. " + + "Therefore, the password, MAC, or digest algorithm of each participant does not match."); + } + } + + private static void updateDigest(Digest digest, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigestIncludingSize(Digest digest, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger); + digest.update(intToByteArray(byteArray.length), 0, 4); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigestIncludingSize(Digest digest, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + digest.update(intToByteArray(byteArray.length), 0, 4); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateMac(Mac mac, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger); + mac.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateMac(Mac mac, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + mac.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static byte[] intToByteArray(int value) + { + return new byte[]{ + (byte)(value >>> 24), + (byte)(value >>> 16), + (byte)(value >>> 8), + (byte)value + }; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java b/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java new file mode 100644 index 00000000..ae551dd5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java @@ -0,0 +1,54 @@ +package org.bouncycastle.crypto.agreement.kdf; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.crypto.DerivationParameters; + +public class DHKDFParameters + implements DerivationParameters +{ + private ASN1ObjectIdentifier algorithm; + private int keySize; + private byte[] z; + private byte[] extraInfo; + + public DHKDFParameters( + DERObjectIdentifier algorithm, + int keySize, + byte[] z) + { + this(algorithm, keySize, z, null); + } + + public DHKDFParameters( + DERObjectIdentifier algorithm, + int keySize, + byte[] z, + byte[] extraInfo) + { + this.algorithm = new ASN1ObjectIdentifier(algorithm.getId()); + this.keySize = keySize; + this.z = z; + this.extraInfo = extraInfo; + } + + public ASN1ObjectIdentifier getAlgorithm() + { + return algorithm; + } + + public int getKeySize() + { + return keySize; + } + + public byte[] getZ() + { + return z; + } + + public byte[] getExtraInfo() + { + return extraInfo; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java b/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java new file mode 100644 index 00000000..947bc5c4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java @@ -0,0 +1,131 @@ +package org.bouncycastle.crypto.agreement.kdf; + +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.util.Pack; + +/** + * RFC 2631 Diffie-hellman KEK derivation function. + */ +public class DHKEKGenerator + implements DerivationFunction +{ + private final Digest digest; + + private DERObjectIdentifier algorithm; + private int keySize; + private byte[] z; + private byte[] partyAInfo; + + public DHKEKGenerator( + Digest digest) + { + this.digest = digest; + } + + public void init(DerivationParameters param) + { + DHKDFParameters params = (DHKDFParameters)param; + + this.algorithm = params.getAlgorithm(); + this.keySize = params.getKeySize(); + this.z = params.getZ(); + this.partyAInfo = params.getExtraInfo(); + } + + public Digest getDigest() + { + return digest; + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + if ((out.length - len) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + long oBytes = len; + int outLen = digest.getDigestSize(); + + // + // this is at odds with the standard implementation, the + // maximum value should be hBits * (2^32 - 1) where hBits + // is the digest output size in bits. We can't have an + // array with a long index at the moment... + // + if (oBytes > ((2L << 32) - 1)) + { + throw new IllegalArgumentException("Output length too large"); + } + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = new byte[digest.getDigestSize()]; + + int counter = 1; + + for (int i = 0; i < cThreshold; i++) + { + digest.update(z, 0, z.length); + + // OtherInfo + ASN1EncodableVector v1 = new ASN1EncodableVector(); + // KeySpecificInfo + ASN1EncodableVector v2 = new ASN1EncodableVector(); + + v2.add(algorithm); + v2.add(new DEROctetString(Pack.intToBigEndian(counter))); + + v1.add(new DERSequence(v2)); + + if (partyAInfo != null) + { + v1.add(new DERTaggedObject(true, 0, new DEROctetString(partyAInfo))); + } + + v1.add(new DERTaggedObject(true, 2, new DEROctetString(Pack.intToBigEndian(keySize)))); + + try + { + byte[] other = new DERSequence(v1).getEncoded(ASN1Encoding.DER); + + digest.update(other, 0, other.length); + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to encode parameter info: " + e.getMessage()); + } + + digest.doFinal(dig, 0); + + if (len > outLen) + { + System.arraycopy(dig, 0, out, outOff, outLen); + outOff += outLen; + len -= outLen; + } + else + { + System.arraycopy(dig, 0, out, outOff, len); + } + + counter++; + } + + digest.reset(); + + return (int)oBytes; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java b/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java new file mode 100644 index 00000000..68039533 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.agreement.kdf; + +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.generators.KDF2BytesGenerator; +import org.bouncycastle.crypto.params.KDFParameters; +import org.bouncycastle.crypto.util.Pack; + +/** + * X9.63 based key derivation function for ECDH CMS. + */ +public class ECDHKEKGenerator + implements DerivationFunction +{ + private DerivationFunction kdf; + + private ASN1ObjectIdentifier algorithm; + private int keySize; + private byte[] z; + + public ECDHKEKGenerator( + Digest digest) + { + this.kdf = new KDF2BytesGenerator(digest); + } + + public void init(DerivationParameters param) + { + DHKDFParameters params = (DHKDFParameters)param; + + this.algorithm = params.getAlgorithm(); + this.keySize = params.getKeySize(); + this.z = params.getZ(); + } + + public Digest getDigest() + { + return kdf.getDigest(); + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + // TODO Create an ASN.1 class for this (RFC3278) + // ECC-CMS-SharedInfo + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new AlgorithmIdentifier(algorithm, DERNull.INSTANCE)); + v.add(new DERTaggedObject(true, 2, new DEROctetString(Pack.intToBigEndian(keySize)))); + + try + { + kdf.init(new KDFParameters(z, new DERSequence(v).getEncoded(ASN1Encoding.DER))); + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to initialise kdf: " + e.getMessage()); + } + + return kdf.generateBytes(out, outOff, len); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Client.java b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Client.java new file mode 100644 index 00000000..4df90236 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Client.java @@ -0,0 +1,93 @@ +package org.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; + +/** + * Implements the client side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + * This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + * "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + */ +public class SRP6Client +{ + protected BigInteger N; + protected BigInteger g; + + protected BigInteger a; + protected BigInteger A; + + protected BigInteger B; + + protected BigInteger x; + protected BigInteger u; + protected BigInteger S; + + protected Digest digest; + protected SecureRandom random; + + public SRP6Client() + { + } + + /** + * Initialises the client to begin new authentication attempt + * @param N The safe prime associated with the client's verifier + * @param g The group parameter associated with the client's verifier + * @param digest The digest algorithm associated with the client's verifier + * @param random For key generation + */ + public void init(BigInteger N, BigInteger g, Digest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.digest = digest; + this.random = random; + } + + /** + * Generates client's credentials given the client's salt, identity and password + * @param salt The salt used in the client's verifier. + * @param identity The user's identity (eg. username) + * @param password The user's password + * @return Client's public value to send to server + */ + public BigInteger generateClientCredentials(byte[] salt, byte[] identity, byte[] password) + { + this.x = SRP6Util.calculateX(digest, N, salt, identity, password); + this.a = selectPrivateValue(); + this.A = g.modPow(a, N); + + return A; + } + + /** + * Generates client's verification message given the server's credentials + * @param serverB The server's credentials + * @return Client's verification message for the server + * @throws CryptoException If server's credentials are invalid + */ + public BigInteger calculateSecret(BigInteger serverB) throws CryptoException + { + this.B = SRP6Util.validatePublicValue(N, serverB); + this.u = SRP6Util.calculateU(digest, N, A, B); + this.S = calculateS(); + + return S; + } + + protected BigInteger selectPrivateValue() + { + return SRP6Util.generatePrivateValue(digest, N, g, random); + } + + private BigInteger calculateS() + { + BigInteger k = SRP6Util.calculateK(digest, N, g); + BigInteger exp = u.multiply(x).add(a); + BigInteger tmp = g.modPow(x, N).multiply(k).mod(N); + return B.subtract(tmp).mod(N).modPow(exp, N); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Server.java b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Server.java new file mode 100644 index 00000000..fb208388 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Server.java @@ -0,0 +1,90 @@ +package org.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; + +/** + * Implements the server side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + * This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + * "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + */ +public class SRP6Server +{ + protected BigInteger N; + protected BigInteger g; + protected BigInteger v; + + protected SecureRandom random; + protected Digest digest; + + protected BigInteger A; + + protected BigInteger b; + protected BigInteger B; + + protected BigInteger u; + protected BigInteger S; + + public SRP6Server() + { + } + + /** + * Initialises the server to accept a new client authentication attempt + * @param N The safe prime associated with the client's verifier + * @param g The group parameter associated with the client's verifier + * @param v The client's verifier + * @param digest The digest algorithm associated with the client's verifier + * @param random For key generation + */ + public void init(BigInteger N, BigInteger g, BigInteger v, Digest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.v = v; + + this.random = random; + this.digest = digest; + } + + /** + * Generates the server's credentials that are to be sent to the client. + * @return The server's public value to the client + */ + public BigInteger generateServerCredentials() + { + BigInteger k = SRP6Util.calculateK(digest, N, g); + this.b = selectPrivateValue(); + this.B = k.multiply(v).mod(N).add(g.modPow(b, N)).mod(N); + + return B; + } + + /** + * Processes the client's credentials. If valid the shared secret is generated and returned. + * @param clientA The client's credentials + * @return A shared secret BigInteger + * @throws CryptoException If client's credentials are invalid + */ + public BigInteger calculateSecret(BigInteger clientA) throws CryptoException + { + this.A = SRP6Util.validatePublicValue(N, clientA); + this.u = SRP6Util.calculateU(digest, N, A, B); + this.S = calculateS(); + + return S; + } + + protected BigInteger selectPrivateValue() + { + return SRP6Util.generatePrivateValue(digest, N, g, random); + } + + private BigInteger calculateS() + { + return v.modPow(u, N).multiply(A).mod(N).modPow(b, N); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Util.java b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Util.java new file mode 100644 index 00000000..ad5ceac8 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6Util.java @@ -0,0 +1,91 @@ +package org.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.util.BigIntegers; + +public class SRP6Util +{ + private static BigInteger ZERO = BigInteger.valueOf(0); + private static BigInteger ONE = BigInteger.valueOf(1); + + public static BigInteger calculateK(Digest digest, BigInteger N, BigInteger g) + { + return hashPaddedPair(digest, N, N, g); + } + + public static BigInteger calculateU(Digest digest, BigInteger N, BigInteger A, BigInteger B) + { + return hashPaddedPair(digest, N, A, B); + } + + public static BigInteger calculateX(Digest digest, BigInteger N, byte[] salt, byte[] identity, byte[] password) + { + byte[] output = new byte[digest.getDigestSize()]; + + digest.update(identity, 0, identity.length); + digest.update((byte)':'); + digest.update(password, 0, password.length); + digest.doFinal(output, 0); + + digest.update(salt, 0, salt.length); + digest.update(output, 0, output.length); + digest.doFinal(output, 0); + + return new BigInteger(1, output); + } + + public static BigInteger generatePrivateValue(Digest digest, BigInteger N, BigInteger g, SecureRandom random) + { + int minBits = Math.min(256, N.bitLength() / 2); + BigInteger min = ONE.shiftLeft(minBits - 1); + BigInteger max = N.subtract(ONE); + + return BigIntegers.createRandomInRange(min, max, random); + } + + public static BigInteger validatePublicValue(BigInteger N, BigInteger val) + throws CryptoException + { + val = val.mod(N); + + // Check that val % N != 0 + if (val.equals(ZERO)) + { + throw new CryptoException("Invalid public value: 0"); + } + + return val; + } + + private static BigInteger hashPaddedPair(Digest digest, BigInteger N, BigInteger n1, BigInteger n2) + { + int padLength = (N.bitLength() + 7) / 8; + + byte[] n1_bytes = getPadded(n1, padLength); + byte[] n2_bytes = getPadded(n2, padLength); + + digest.update(n1_bytes, 0, n1_bytes.length); + digest.update(n2_bytes, 0, n2_bytes.length); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return new BigInteger(1, output); + } + + private static byte[] getPadded(BigInteger n, int length) + { + byte[] bs = BigIntegers.asUnsignedByteArray(n); + if (bs.length < length) + { + byte[] tmp = new byte[length]; + System.arraycopy(bs, 0, tmp, length - bs.length, bs.length); + bs = tmp; + } + return bs; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java new file mode 100644 index 00000000..631ecc6e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java @@ -0,0 +1,47 @@ +package org.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.Digest; + +/** + * Generates new SRP verifier for user + */ +public class SRP6VerifierGenerator +{ + protected BigInteger N; + protected BigInteger g; + protected Digest digest; + + public SRP6VerifierGenerator() + { + } + + /** + * Initialises generator to create new verifiers + * @param N The safe prime to use (see DHParametersGenerator) + * @param g The group parameter to use (see DHParametersGenerator) + * @param digest The digest to use. The same digest type will need to be used later for the actual authentication + * attempt. Also note that the final session key size is dependent on the chosen digest. + */ + public void init(BigInteger N, BigInteger g, Digest digest) + { + this.N = N; + this.g = g; + this.digest = digest; + } + + /** + * Creates a new SRP verifier + * @param salt The salt to use, generally should be large and random + * @param identity The user's identifying information (eg. username) + * @param password The user's password + * @return A new verifier for use in future SRP authentication + */ + public BigInteger generateVerifier(byte[] salt, byte[] identity, byte[] password) + { + BigInteger x = SRP6Util.calculateX(digest, N, salt, identity, password); + + return g.modPow(x, N); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java b/core/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java new file mode 100644 index 00000000..1494c3ce --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java @@ -0,0 +1,81 @@ +package org.bouncycastle.crypto.commitments; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Commitment; +import org.bouncycastle.crypto.Committer; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; + +/** + * A basic hash-committer as described in "Making Mix Nets Robust for Electronic Voting by Randomized Partial Checking", + * by Jakobsson, Juels, and Rivest (11th Usenix Security Symposium, 2002). + */ +public class HashCommitter + implements Committer +{ + private final Digest digest; + private final int byteLength; + private final SecureRandom random; + + /** + * Base Constructor. The maximum message length that can be committed to is half the length of the internal + * block size for the digest (ExtendedDigest.getBlockLength()). + * + * @param digest digest to use for creating commitments. + * @param random source of randomness for generating secrets. + */ + public HashCommitter(ExtendedDigest digest, SecureRandom random) + { + this.digest = digest; + this.byteLength = digest.getByteLength(); + this.random = random; + } + + /** + * Generate a commitment for the passed in message. + * + * @param message the message to be committed to, + * @return a Commitment + */ + public Commitment commit(byte[] message) + { + if (message.length > byteLength / 2) + { + throw new DataLengthException("Message to be committed to too large for digest."); + } + + byte[] w = new byte[byteLength - message.length]; + + random.nextBytes(w); + + return new Commitment(w, calculateCommitment(w, message)); + } + + /** + * Return true if the passed in commitment represents a commitment to the passed in maessage. + * + * @param commitment a commitment previously generated. + * @param message the message that was expected to have been committed to. + * @return true if commitment matches message, false otherwise. + */ + public boolean isRevealed(Commitment commitment, byte[] message) + { + byte[] calcCommitment = calculateCommitment(commitment.getSecret(), message); + + return Arrays.constantTimeAreEqual(commitment.getCommitment(), calcCommitment); + } + + private byte[] calculateCommitment(byte[] w, byte[] message) + { + byte[] commitment = new byte[digest.getDigestSize()]; + + digest.update(w, 0, w.length); + digest.update(message, 0, message.length); + digest.doFinal(commitment, 0); + + return commitment; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/GOST3411Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/GOST3411Digest.java new file mode 100644 index 00000000..38a52aab --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/GOST3411Digest.java @@ -0,0 +1,362 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.crypto.engines.GOST28147Engine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithSBox; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Memoable; + +/** + * implementation of GOST R 34.11-94 + */ +public class GOST3411Digest + implements ExtendedDigest, Memoable +{ + private static final int DIGEST_LENGTH = 32; + + private byte[] H = new byte[32], L = new byte[32], + M = new byte[32], Sum = new byte[32]; + private byte[][] C = new byte[4][32]; + + private byte[] xBuf = new byte[32]; + private int xBufOff; + private long byteCount; + + private BlockCipher cipher = new GOST28147Engine(); + private byte[] sBox; + + /** + * Standard constructor + */ + public GOST3411Digest() + { + sBox = GOST28147Engine.getSBox("D-A"); + cipher.init(true, new ParametersWithSBox(null, sBox)); + + reset(); + } + + /** + * Constructor to allow use of a particular sbox with GOST28147 + * @see GOST28147Engine#getSBox(String) + */ + public GOST3411Digest(byte[] sBoxParam) + { + sBox = Arrays.clone(sBoxParam); + cipher.init(true, new ParametersWithSBox(null, sBox)); + + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public GOST3411Digest(GOST3411Digest t) + { + reset(t); + } + + public String getAlgorithmName() + { + return "GOST3411"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public void update(byte in) + { + xBuf[xBufOff++] = in; + if (xBufOff == xBuf.length) + { + sumByteArray(xBuf); // calc sum M + processBlock(xBuf, 0); + xBufOff = 0; + } + byteCount++; + } + + public void update(byte[] in, int inOff, int len) + { + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + inOff++; + len--; + } + + while (len > xBuf.length) + { + System.arraycopy(in, inOff, xBuf, 0, xBuf.length); + + sumByteArray(xBuf); // calc sum M + processBlock(xBuf, 0); + inOff += xBuf.length; + len -= xBuf.length; + byteCount += xBuf.length; + } + + // load in the remainder. + while (len > 0) + { + update(in[inOff]); + inOff++; + len--; + } + } + + // (i + 1 + 4(k - 1)) = 8i + k i = 0-3, k = 1-8 + private byte[] K = new byte[32]; + + private byte[] P(byte[] in) + { + for(int k = 0; k < 8; k++) + { + K[4*k] = in[k]; + K[1 + 4*k] = in[ 8 + k]; + K[2 + 4*k] = in[16 + k]; + K[3 + 4*k] = in[24 + k]; + } + + return K; + } + + //A (x) = (x0 ^ x1) || x3 || x2 || x1 + byte[] a = new byte[8]; + private byte[] A(byte[] in) + { + for(int j=0; j<8; j++) + { + a[j]=(byte)(in[j] ^ in[j+8]); + } + + System.arraycopy(in, 8, in, 0, 24); + System.arraycopy(a, 0, in, 24, 8); + + return in; + } + + //Encrypt function, ECB mode + private void E(byte[] key, byte[] s, int sOff, byte[] in, int inOff) + { + cipher.init(true, new KeyParameter(key)); + + cipher.processBlock(in, inOff, s, sOff); + } + + // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2 + short[] wS = new short[16], w_S = new short[16]; + + private void fw(byte[] in) + { + cpyBytesToShort(in, wS); + w_S[15] = (short)(wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15]); + System.arraycopy(wS, 1, w_S, 0, 15); + cpyShortToBytes(w_S, in); + } + + // block processing + byte[] S = new byte[32]; + byte[] U = new byte[32], V = new byte[32], W = new byte[32]; + + protected void processBlock(byte[] in, int inOff) + { + System.arraycopy(in, inOff, M, 0, 32); + + //key step 1 + + // H = h3 || h2 || h1 || h0 + // S = s3 || s2 || s1 || s0 + System.arraycopy(H, 0, U, 0, 32); + System.arraycopy(M, 0, V, 0, 32); + for (int j=0; j<32; j++) + { + W[j] = (byte)(U[j]^V[j]); + } + // Encrypt gost28147-ECB + E(P(W), S, 0, H, 0); // s0 = EK0 [h0] + + //keys step 2,3,4 + for (int i=1; i<4; i++) + { + byte[] tmpA = A(U); + for (int j=0; j<32; j++) + { + U[j] = (byte)(tmpA[j] ^ C[i][j]); + } + V = A(A(V)); + for (int j=0; j<32; j++) + { + W[j] = (byte)(U[j]^V[j]); + } + // Encrypt gost28147-ECB + E(P(W), S, i * 8, H, i * 8); // si = EKi [hi] + } + + // x(M, H) = y61(H^y(M^y12(S))) + for(int n = 0; n < 12; n++) + { + fw(S); + } + for(int n = 0; n < 32; n++) + { + S[n] = (byte)(S[n] ^ M[n]); + } + + fw(S); + + for(int n = 0; n < 32; n++) + { + S[n] = (byte)(H[n] ^ S[n]); + } + for(int n = 0; n < 61; n++) + { + fw(S); + } + System.arraycopy(S, 0, H, 0, H.length); + } + + private void finish() + { + Pack.longToLittleEndian(byteCount * 8, L, 0); // get length into L (byteCount * 8 = bitCount) + + while (xBufOff != 0) + { + update((byte)0); + } + + processBlock(L, 0); + processBlock(Sum, 0); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + System.arraycopy(H, 0, out, outOff, H.length); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + private static final byte[] C2 = { + 0x00,(byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF, + (byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF,0x00, + 0x00,(byte)0xFF,(byte)0xFF,0x00,(byte)0xFF,0x00,0x00,(byte)0xFF, + (byte)0xFF,0x00,0x00,0x00,(byte)0xFF,(byte)0xFF,0x00,(byte)0xFF}; + + public void reset() + { + byteCount = 0; + xBufOff = 0; + + for(int i=0; i<H.length; i++) + { + H[i] = 0; // start vector H + } + for(int i=0; i<L.length; i++) + { + L[i] = 0; + } + for(int i=0; i<M.length; i++) + { + M[i] = 0; + } + for(int i=0; i<C[1].length; i++) + { + C[1][i] = 0; // real index C = +1 because index array with 0. + } + for(int i=0; i<C[3].length; i++) + { + C[3][i] = 0; + } + for(int i=0; i<Sum.length; i++) + { + Sum[i] = 0; + } + for(int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + + System.arraycopy(C2, 0, C[2], 0, C2.length); + } + + // 256 bitsblock modul -> (Sum + a mod (2^256)) + private void sumByteArray(byte[] in) + { + int carry = 0; + + for (int i = 0; i != Sum.length; i++) + { + int sum = (Sum[i] & 0xff) + (in[i] & 0xff) + carry; + + Sum[i] = (byte)sum; + + carry = sum >>> 8; + } + } + + private void cpyBytesToShort(byte[] S, short[] wS) + { + for(int i=0; i<S.length/2; i++) + { + wS[i] = (short)(((S[i*2+1]<<8)&0xFF00)|(S[i*2]&0xFF)); + } + } + + private void cpyShortToBytes(short[] wS, byte[] S) + { + for(int i=0; i<S.length/2; i++) + { + S[i*2 + 1] = (byte)(wS[i] >> 8); + S[i*2] = (byte)wS[i]; + } + } + + public int getByteLength() + { + return 32; + } + + public Memoable copy() + { + return new GOST3411Digest(this); + } + + public void reset(Memoable other) + { + GOST3411Digest t = (GOST3411Digest)other; + + this.sBox = t.sBox; + cipher.init(true, new ParametersWithSBox(null, sBox)); + + reset(); + + System.arraycopy(t.H, 0, this.H, 0, t.H.length); + System.arraycopy(t.L, 0, this.L, 0, t.L.length); + System.arraycopy(t.M, 0, this.M, 0, t.M.length); + System.arraycopy(t.Sum, 0, this.Sum, 0, t.Sum.length); + System.arraycopy(t.C[1], 0, this.C[1], 0, t.C[1].length); + System.arraycopy(t.C[2], 0, this.C[2], 0, t.C[2].length); + System.arraycopy(t.C[3], 0, this.C[3], 0, t.C[3].length); + System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); + + this.xBufOff = t.xBufOff; + this.byteCount = t.byteCount; + } +} + + diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/GeneralDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/GeneralDigest.java new file mode 100644 index 00000000..15f3ebbd --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/GeneralDigest.java @@ -0,0 +1,142 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Memoable; + +/** + * base implementation of MD4 family style digest as outlined in + * "Handbook of Applied Cryptography", pages 344 - 347. + */ +public abstract class GeneralDigest + implements ExtendedDigest, Memoable +{ + private static final int BYTE_LENGTH = 64; + private byte[] xBuf; + private int xBufOff; + + private long byteCount; + + /** + * Standard constructor + */ + protected GeneralDigest() + { + xBuf = new byte[4]; + xBufOff = 0; + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected GeneralDigest(GeneralDigest t) + { + xBuf = new byte[t.xBuf.length]; + + copyIn(t); + } + + protected void copyIn(GeneralDigest t) + { + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount = t.byteCount; + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + long bitLength = (byteCount << 3); + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(bitLength); + + processBlock(); + } + + public void reset() + { + byteCount = 0; + + xBufOff = 0; + for (int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + protected abstract void processWord(byte[] in, int inOff); + + protected abstract void processLength(long bitLength); + + protected abstract void processBlock(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/LongDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/LongDigest.java new file mode 100644 index 00000000..5c79e4ee --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/LongDigest.java @@ -0,0 +1,361 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + +/** + * Base class for SHA-384 and SHA-512. + */ +public abstract class LongDigest + implements ExtendedDigest, Memoable +{ + private static final int BYTE_LENGTH = 128; + + private byte[] xBuf; + private int xBufOff; + + private long byteCount1; + private long byteCount2; + + protected long H1, H2, H3, H4, H5, H6, H7, H8; + + private long[] W = new long[80]; + private int wOff; + + /** + * Constructor for variable length word + */ + protected LongDigest() + { + xBuf = new byte[8]; + xBufOff = 0; + + reset(); + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected LongDigest(LongDigest t) + { + xBuf = new byte[t.xBuf.length]; + + copyIn(t); + } + + protected void copyIn(LongDigest t) + { + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount1 = t.byteCount1; + byteCount2 = t.byteCount2; + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.W, 0, W, 0, t.W.length); + wOff = t.wOff; + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount1++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount1 += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + adjustByteCounts(); + + long lowBitLength = byteCount1 << 3; + long hiBitLength = byteCount2; + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(lowBitLength, hiBitLength); + + processBlock(); + } + + public void reset() + { + byteCount1 = 0; + byteCount2 = 0; + + xBufOff = 0; + for (int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + + wOff = 0; + for (int i = 0; i != W.length; i++) + { + W[i] = 0; + } + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + W[wOff] = Pack.bigEndianToLong(in, inOff); + + if (++wOff == 16) + { + processBlock(); + } + } + + /** + * adjust the byte counts so that byteCount2 represents the + * upper long (less 3 bits) word of the byte count. + */ + private void adjustByteCounts() + { + if (byteCount1 > 0x1fffffffffffffffL) + { + byteCount2 += (byteCount1 >>> 61); + byteCount1 &= 0x1fffffffffffffffL; + } + } + + protected void processLength( + long lowW, + long hiW) + { + if (wOff > 14) + { + processBlock(); + } + + W[14] = hiW; + W[15] = lowW; + } + + protected void processBlock() + { + adjustByteCounts(); + + // + // expand 16 word block into 80 word blocks. + // + for (int t = 16; t <= 79; t++) + { + W[t] = Sigma1(W[t - 2]) + W[t - 7] + Sigma0(W[t - 15]) + W[t - 16]; + } + + // + // set up working variables. + // + long a = H1; + long b = H2; + long c = H3; + long d = H4; + long e = H5; + long f = H6; + long g = H7; + long h = H8; + + int t = 0; + for(int i = 0; i < 10; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++]; + d += h; + h += Sum0(a) + Maj(a, b, c); + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++]; + c += g; + g += Sum0(h) + Maj(h, a, b); + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++]; + b += f; + f += Sum0(g) + Maj(g, h, a); + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++]; + a += e; + e += Sum0(f) + Maj(f, g, h); + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++]; + h += d; + d += Sum0(e) + Maj(e, f, g); + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++]; + g += c; + c += Sum0(d) + Maj(d, e, f); + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++]; + f += b; + b += Sum0(c) + Maj(c, d, e); + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++]; + e += a; + a += Sum0(b) + Maj(b, c, d); + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + wOff = 0; + for (int i = 0; i < 16; i++) + { + W[i] = 0; + } + } + + /* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */ + private long Ch( + long x, + long y, + long z) + { + return ((x & y) ^ ((~x) & z)); + } + + private long Maj( + long x, + long y, + long z) + { + return ((x & y) ^ (x & z) ^ (y & z)); + } + + private long Sum0( + long x) + { + return ((x << 36)|(x >>> 28)) ^ ((x << 30)|(x >>> 34)) ^ ((x << 25)|(x >>> 39)); + } + + private long Sum1( + long x) + { + return ((x << 50)|(x >>> 14)) ^ ((x << 46)|(x >>> 18)) ^ ((x << 23)|(x >>> 41)); + } + + private long Sigma0( + long x) + { + return ((x << 63)|(x >>> 1)) ^ ((x << 56)|(x >>> 8)) ^ (x >>> 7); + } + + private long Sigma1( + long x) + { + return ((x << 45)|(x >>> 19)) ^ ((x << 3)|(x >>> 61)) ^ (x >>> 6); + } + + /* SHA-384 and SHA-512 Constants + * (represent the first 64 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final long K[] = { +0x428a2f98d728ae22L, 0x7137449123ef65cdL, 0xb5c0fbcfec4d3b2fL, 0xe9b5dba58189dbbcL, +0x3956c25bf348b538L, 0x59f111f1b605d019L, 0x923f82a4af194f9bL, 0xab1c5ed5da6d8118L, +0xd807aa98a3030242L, 0x12835b0145706fbeL, 0x243185be4ee4b28cL, 0x550c7dc3d5ffb4e2L, +0x72be5d74f27b896fL, 0x80deb1fe3b1696b1L, 0x9bdc06a725c71235L, 0xc19bf174cf692694L, +0xe49b69c19ef14ad2L, 0xefbe4786384f25e3L, 0x0fc19dc68b8cd5b5L, 0x240ca1cc77ac9c65L, +0x2de92c6f592b0275L, 0x4a7484aa6ea6e483L, 0x5cb0a9dcbd41fbd4L, 0x76f988da831153b5L, +0x983e5152ee66dfabL, 0xa831c66d2db43210L, 0xb00327c898fb213fL, 0xbf597fc7beef0ee4L, +0xc6e00bf33da88fc2L, 0xd5a79147930aa725L, 0x06ca6351e003826fL, 0x142929670a0e6e70L, +0x27b70a8546d22ffcL, 0x2e1b21385c26c926L, 0x4d2c6dfc5ac42aedL, 0x53380d139d95b3dfL, +0x650a73548baf63deL, 0x766a0abb3c77b2a8L, 0x81c2c92e47edaee6L, 0x92722c851482353bL, +0xa2bfe8a14cf10364L, 0xa81a664bbc423001L, 0xc24b8b70d0f89791L, 0xc76c51a30654be30L, +0xd192e819d6ef5218L, 0xd69906245565a910L, 0xf40e35855771202aL, 0x106aa07032bbd1b8L, +0x19a4c116b8d2d0c8L, 0x1e376c085141ab53L, 0x2748774cdf8eeb99L, 0x34b0bcb5e19b48a8L, +0x391c0cb3c5c95a63L, 0x4ed8aa4ae3418acbL, 0x5b9cca4f7763e373L, 0x682e6ff3d6b2b8a3L, +0x748f82ee5defb2fcL, 0x78a5636f43172f60L, 0x84c87814a1f0ab72L, 0x8cc702081a6439ecL, +0x90befffa23631e28L, 0xa4506cebde82bde9L, 0xbef9a3f7b2c67915L, 0xc67178f2e372532bL, +0xca273eceea26619cL, 0xd186b8c721c0c207L, 0xeada7dd6cde0eb1eL, 0xf57d4f7fee6ed178L, +0x06f067aa72176fbaL, 0x0a637dc5a2c898a6L, 0x113f9804bef90daeL, 0x1b710b35131c471bL, +0x28db77f523047d84L, 0x32caab7b40c72493L, 0x3c9ebe0a15c9bebcL, 0x431d67c49c100d4cL, +0x4cc5d4becb3e42b6L, 0x597f299cfc657e2aL, 0x5fcb6fab3ad6faecL, 0x6c44198c4a475817L + }; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/MD2Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/MD2Digest.java new file mode 100644 index 00000000..f96b4a15 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/MD2Digest.java @@ -0,0 +1,258 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.*; +import org.bouncycastle.util.Memoable; + +/** + * implementation of MD2 + * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992 + */ +public class MD2Digest + implements ExtendedDigest, Memoable +{ + private static final int DIGEST_LENGTH = 16; + + /* X buffer */ + private byte[] X = new byte[48]; + private int xOff; + /* M buffer */ + private byte[] M = new byte[16]; + private int mOff; + /* check sum */ + private byte[] C = new byte[16]; + private int COff; + + public MD2Digest() + { + reset(); + } + + public MD2Digest(MD2Digest t) + { + copyIn(t); + } + + private void copyIn(MD2Digest t) + { + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + System.arraycopy(t.M, 0, M, 0, t.M.length); + mOff = t.mOff; + System.arraycopy(t.C, 0, C, 0, t.C.length); + COff = t.COff; + } + + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName() + { + return "MD2"; + } + /** + * 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 DIGEST_LENGTH; + } + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff) + { + // add padding + byte paddingByte = (byte)(M.length-mOff); + for (int i=mOff;i<M.length;i++) + { + M[i] = paddingByte; + } + //do final check sum + processCheckSum(M); + // do final block process + processBlock(M); + + processBlock(C); + + System.arraycopy(X,xOff,out,outOff,16); + + reset(); + + return DIGEST_LENGTH; + } + /** + * reset the digest back to it's initial state. + */ + public void reset() + { + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + mOff = 0; + for (int i = 0; i != M.length; i++) + { + M[i] = 0; + } + COff = 0; + for (int i = 0; i != C.length; i++) + { + C[i] = 0; + } + } + /** + * update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + public void update(byte in) + { + M[mOff++] = in; + + if (mOff == 16) + { + processCheckSum(M); + processBlock(M); + mOff = 0; + } + } + + /** + * update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void update(byte[] in, int inOff, int len) + { + // + // fill the current word + // + while ((mOff != 0) && (len > 0)) + { + update(in[inOff]); + inOff++; + len--; + } + + // + // process whole words. + // + while (len > 16) + { + System.arraycopy(in,inOff,M,0,16); + processCheckSum(M); + processBlock(M); + len -= 16; + inOff += 16; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + inOff++; + len--; + } + } + protected void processCheckSum(byte[] m) + { + int L = C[15]; + for (int i=0;i<16;i++) + { + C[i] ^= S[(m[i] ^ L) & 0xff]; + L = C[i]; + } + } + protected void processBlock(byte[] m) + { + for (int i=0;i<16;i++) + { + X[i+16] = m[i]; + X[i+32] = (byte)(m[i] ^ X[i]); + } + // encrypt block + int t = 0; + + for (int j=0;j<18;j++) + { + for (int k=0;k<48;k++) + { + t = X[k] ^= S[t]; + t = t & 0xff; + } + t = (t + j)%256; + } + } + // 256-byte random permutation constructed from the digits of PI + private static final byte[] S = { + (byte)41,(byte)46,(byte)67,(byte)201,(byte)162,(byte)216,(byte)124, + (byte)1,(byte)61,(byte)54,(byte)84,(byte)161,(byte)236,(byte)240, + (byte)6,(byte)19,(byte)98,(byte)167,(byte)5,(byte)243,(byte)192, + (byte)199,(byte)115,(byte)140,(byte)152,(byte)147,(byte)43,(byte)217, + (byte)188,(byte)76,(byte)130,(byte)202,(byte)30,(byte)155,(byte)87, + (byte)60,(byte)253,(byte)212,(byte)224,(byte)22,(byte)103,(byte)66, + (byte)111,(byte)24,(byte)138,(byte)23,(byte)229,(byte)18,(byte)190, + (byte)78,(byte)196,(byte)214,(byte)218,(byte)158,(byte)222,(byte)73, + (byte)160,(byte)251,(byte)245,(byte)142,(byte)187,(byte)47,(byte)238, + (byte)122,(byte)169,(byte)104,(byte)121,(byte)145,(byte)21,(byte)178, + (byte)7,(byte)63,(byte)148,(byte)194,(byte)16,(byte)137,(byte)11, + (byte)34,(byte)95,(byte)33,(byte)128,(byte)127,(byte)93,(byte)154, + (byte)90,(byte)144,(byte)50,(byte)39,(byte)53,(byte)62,(byte)204, + (byte)231,(byte)191,(byte)247,(byte)151,(byte)3,(byte)255,(byte)25, + (byte)48,(byte)179,(byte)72,(byte)165,(byte)181,(byte)209,(byte)215, + (byte)94,(byte)146,(byte)42,(byte)172,(byte)86,(byte)170,(byte)198, + (byte)79,(byte)184,(byte)56,(byte)210,(byte)150,(byte)164,(byte)125, + (byte)182,(byte)118,(byte)252,(byte)107,(byte)226,(byte)156,(byte)116, + (byte)4,(byte)241,(byte)69,(byte)157,(byte)112,(byte)89,(byte)100, + (byte)113,(byte)135,(byte)32,(byte)134,(byte)91,(byte)207,(byte)101, + (byte)230,(byte)45,(byte)168,(byte)2,(byte)27,(byte)96,(byte)37, + (byte)173,(byte)174,(byte)176,(byte)185,(byte)246,(byte)28,(byte)70, + (byte)97,(byte)105,(byte)52,(byte)64,(byte)126,(byte)15,(byte)85, + (byte)71,(byte)163,(byte)35,(byte)221,(byte)81,(byte)175,(byte)58, + (byte)195,(byte)92,(byte)249,(byte)206,(byte)186,(byte)197,(byte)234, + (byte)38,(byte)44,(byte)83,(byte)13,(byte)110,(byte)133,(byte)40, + (byte)132, 9,(byte)211,(byte)223,(byte)205,(byte)244,(byte)65, + (byte)129,(byte)77,(byte)82,(byte)106,(byte)220,(byte)55,(byte)200, + (byte)108,(byte)193,(byte)171,(byte)250,(byte)36,(byte)225,(byte)123, + (byte)8,(byte)12,(byte)189,(byte)177,(byte)74,(byte)120,(byte)136, + (byte)149,(byte)139,(byte)227,(byte)99,(byte)232,(byte)109,(byte)233, + (byte)203,(byte)213,(byte)254,(byte)59,(byte)0,(byte)29,(byte)57, + (byte)242,(byte)239,(byte)183,(byte)14,(byte)102,(byte)88,(byte)208, + (byte)228,(byte)166,(byte)119,(byte)114,(byte)248,(byte)235,(byte)117, + (byte)75,(byte)10,(byte)49,(byte)68,(byte)80,(byte)180,(byte)143, + (byte)237,(byte)31,(byte)26,(byte)219,(byte)153,(byte)141,(byte)51, + (byte)159,(byte)17,(byte)131,(byte)20 + }; + + public int getByteLength() + { + return 16; + } + + public Memoable copy() + { + return new MD2Digest(this); + } + + public void reset(Memoable other) + { + MD2Digest d = (MD2Digest)other; + + copyIn(d); + } +} + + diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/MD4Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/MD4Digest.java new file mode 100644 index 00000000..68532bd2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/MD4Digest.java @@ -0,0 +1,291 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.util.Memoable; + +/** + * implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for + * Computer Science and RSA Data Security, Inc. + * <p> + * <b>NOTE</b>: This algorithm is only included for backwards compatability + * with legacy applications, it's not secure, don't use it for anything new! + */ +public class MD4Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public MD4Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD4Digest(MD4Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(MD4Digest t) + { + super.copyIn(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "MD4"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private static final int S11 = 3; + private static final int S12 = 7; + private static final int S13 = 11; + private static final int S14 = 19; + + // + // round 2 left rotates + // + private static final int S21 = 3; + private static final int S22 = 5; + private static final int S23 = 9; + private static final int S24 = 13; + + // + // round 3 left rotates + // + private static final int S31 = 3; + private static final int S32 = 9; + private static final int S33 = 11; + private static final int S34 = 15; + + /* + * rotate int x left n bits. + */ + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * F, G, H and I are the basic MD4 functions. + */ + private int F( + int u, + int v, + int w) + { + return (u & v) | (~u & w); + } + + private int G( + int u, + int v, + int w) + { + return (u & v) | (u & w) | (v & w); + } + + private int H( + int u, + int v, + int w) + { + return u ^ v ^ w; + } + + protected void processBlock() + { + int a = H1; + int b = H2; + int c = H3; + int d = H4; + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft(a + F(b, c, d) + X[ 0], S11); + d = rotateLeft(d + F(a, b, c) + X[ 1], S12); + c = rotateLeft(c + F(d, a, b) + X[ 2], S13); + b = rotateLeft(b + F(c, d, a) + X[ 3], S14); + a = rotateLeft(a + F(b, c, d) + X[ 4], S11); + d = rotateLeft(d + F(a, b, c) + X[ 5], S12); + c = rotateLeft(c + F(d, a, b) + X[ 6], S13); + b = rotateLeft(b + F(c, d, a) + X[ 7], S14); + a = rotateLeft(a + F(b, c, d) + X[ 8], S11); + d = rotateLeft(d + F(a, b, c) + X[ 9], S12); + c = rotateLeft(c + F(d, a, b) + X[10], S13); + b = rotateLeft(b + F(c, d, a) + X[11], S14); + a = rotateLeft(a + F(b, c, d) + X[12], S11); + d = rotateLeft(d + F(a, b, c) + X[13], S12); + c = rotateLeft(c + F(d, a, b) + X[14], S13); + b = rotateLeft(b + F(c, d, a) + X[15], S14); + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft(a + G(b, c, d) + X[ 0] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 4] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[ 8] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24); + a = rotateLeft(a + G(b, c, d) + X[ 1] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 5] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[ 9] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24); + a = rotateLeft(a + G(b, c, d) + X[ 2] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 6] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24); + a = rotateLeft(a + G(b, c, d) + X[ 3] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 7] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24); + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft(a + H(b, c, d) + X[ 0] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[ 8] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 4] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34); + a = rotateLeft(a + H(b, c, d) + X[ 2] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 6] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34); + a = rotateLeft(a + H(b, c, d) + X[ 1] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[ 9] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 5] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34); + a = rotateLeft(a + H(b, c, d) + X[ 3] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 7] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34); + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new MD4Digest(this); + } + + public void reset(Memoable other) + { + MD4Digest d = (MD4Digest)other; + + copyIn(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/MD5Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/MD5Digest.java new file mode 100644 index 00000000..ff9cedf0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/MD5Digest.java @@ -0,0 +1,323 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.util.Memoable; + +/** + * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347. + */ +public class MD5Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public MD5Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD5Digest(MD5Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(MD5Digest t) + { + super.copyIn(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "MD5"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private static final int S11 = 7; + private static final int S12 = 12; + private static final int S13 = 17; + private static final int S14 = 22; + + // + // round 2 left rotates + // + private static final int S21 = 5; + private static final int S22 = 9; + private static final int S23 = 14; + private static final int S24 = 20; + + // + // round 3 left rotates + // + private static final int S31 = 4; + private static final int S32 = 11; + private static final int S33 = 16; + private static final int S34 = 23; + + // + // round 4 left rotates + // + private static final int S41 = 6; + private static final int S42 = 10; + private static final int S43 = 15; + private static final int S44 = 21; + + /* + * rotate int x left n bits. + */ + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * F, G, H and I are the basic MD5 functions. + */ + private int F( + int u, + int v, + int w) + { + return (u & v) | (~u & w); + } + + private int G( + int u, + int v, + int w) + { + return (u & w) | (v & ~w); + } + + private int H( + int u, + int v, + int w) + { + return u ^ v ^ w; + } + + private int K( + int u, + int v, + int w) + { + return v ^ (u | ~w); + } + + protected void processBlock() + { + int a = H1; + int b = H2; + int c = H3; + int d = H4; + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft(a + F(b, c, d) + X[ 0] + 0xd76aa478, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[ 1] + 0xe8c7b756, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[ 2] + 0x242070db, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[ 3] + 0xc1bdceee, S14) + c; + a = rotateLeft(a + F(b, c, d) + X[ 4] + 0xf57c0faf, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[ 5] + 0x4787c62a, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[ 6] + 0xa8304613, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[ 7] + 0xfd469501, S14) + c; + a = rotateLeft(a + F(b, c, d) + X[ 8] + 0x698098d8, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[ 9] + 0x8b44f7af, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[10] + 0xffff5bb1, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[11] + 0x895cd7be, S14) + c; + a = rotateLeft(a + F(b, c, d) + X[12] + 0x6b901122, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[13] + 0xfd987193, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[14] + 0xa679438e, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[15] + 0x49b40821, S14) + c; + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft(a + G(b, c, d) + X[ 1] + 0xf61e2562, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[ 6] + 0xc040b340, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[11] + 0x265e5a51, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[ 0] + 0xe9b6c7aa, S24) + c; + a = rotateLeft(a + G(b, c, d) + X[ 5] + 0xd62f105d, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[10] + 0x02441453, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[15] + 0xd8a1e681, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[ 4] + 0xe7d3fbc8, S24) + c; + a = rotateLeft(a + G(b, c, d) + X[ 9] + 0x21e1cde6, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[14] + 0xc33707d6, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[ 3] + 0xf4d50d87, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[ 8] + 0x455a14ed, S24) + c; + a = rotateLeft(a + G(b, c, d) + X[13] + 0xa9e3e905, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[ 2] + 0xfcefa3f8, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[ 7] + 0x676f02d9, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[12] + 0x8d2a4c8a, S24) + c; + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft(a + H(b, c, d) + X[ 5] + 0xfffa3942, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[ 8] + 0x8771f681, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[11] + 0x6d9d6122, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[14] + 0xfde5380c, S34) + c; + a = rotateLeft(a + H(b, c, d) + X[ 1] + 0xa4beea44, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[ 4] + 0x4bdecfa9, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[ 7] + 0xf6bb4b60, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[10] + 0xbebfbc70, S34) + c; + a = rotateLeft(a + H(b, c, d) + X[13] + 0x289b7ec6, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[ 0] + 0xeaa127fa, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[ 3] + 0xd4ef3085, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[ 6] + 0x04881d05, S34) + c; + a = rotateLeft(a + H(b, c, d) + X[ 9] + 0xd9d4d039, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[12] + 0xe6db99e5, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[15] + 0x1fa27cf8, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[ 2] + 0xc4ac5665, S34) + c; + + // + // Round 4 - K cycle, 16 times. + // + a = rotateLeft(a + K(b, c, d) + X[ 0] + 0xf4292244, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[ 7] + 0x432aff97, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[14] + 0xab9423a7, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[ 5] + 0xfc93a039, S44) + c; + a = rotateLeft(a + K(b, c, d) + X[12] + 0x655b59c3, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[ 3] + 0x8f0ccc92, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[10] + 0xffeff47d, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[ 1] + 0x85845dd1, S44) + c; + a = rotateLeft(a + K(b, c, d) + X[ 8] + 0x6fa87e4f, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[15] + 0xfe2ce6e0, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[ 6] + 0xa3014314, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[13] + 0x4e0811a1, S44) + c; + a = rotateLeft(a + K(b, c, d) + X[ 4] + 0xf7537e82, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[11] + 0xbd3af235, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[ 2] + 0x2ad7d2bb, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[ 9] + 0xeb86d391, S44) + c; + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new MD5Digest(this); + } + + public void reset(Memoable other) + { + MD5Digest d = (MD5Digest)other; + + copyIn(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/NonMemoableDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/NonMemoableDigest.java new file mode 100644 index 00000000..87a4d249 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/NonMemoableDigest.java @@ -0,0 +1,64 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; + +/** + * Wrapper removes exposure to the Memoable interface on an ExtendedDigest implementation. + */ +public class NonMemoableDigest + implements ExtendedDigest +{ + private ExtendedDigest baseDigest; + + /** + * Base constructor. + * + * @param baseDigest underlying digest to use. + * @exception IllegalArgumentException if baseDigest is null + */ + public NonMemoableDigest( + ExtendedDigest baseDigest) + { + if (baseDigest == null) + { + throw new IllegalArgumentException("baseDigest must not be null"); + } + + this.baseDigest = baseDigest; + } + + public String getAlgorithmName() + { + return baseDigest.getAlgorithmName(); + } + + public int getDigestSize() + { + return baseDigest.getDigestSize(); + } + + public void update(byte in) + { + baseDigest.update(in); + } + + public void update(byte[] in, int inOff, int len) + { + baseDigest.update(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + return baseDigest.doFinal(out, outOff); + } + + public void reset() + { + baseDigest.reset(); + } + + public int getByteLength() + { + return baseDigest.getByteLength(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/NullDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/NullDigest.java new file mode 100644 index 00000000..6cb0d4ac --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/NullDigest.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto.digests; + +import java.io.ByteArrayOutputStream; + +import org.bouncycastle.crypto.Digest; + + +public class NullDigest + implements Digest +{ + private ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + public String getAlgorithmName() + { + return "NULL"; + } + + public int getDigestSize() + { + return bOut.size(); + } + + public void update(byte in) + { + bOut.write(in); + } + + public void update(byte[] in, int inOff, int len) + { + bOut.write(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + byte[] res = bOut.toByteArray(); + + System.arraycopy(res, 0, out, outOff, res.length); + + reset(); + + return res.length; + } + + public void reset() + { + bOut.reset(); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD128Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD128Digest.java new file mode 100644 index 00000000..ec7fa859 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD128Digest.java @@ -0,0 +1,482 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.util.Memoable; + +/** + * implementation of RIPEMD128 + */ +public class RIPEMD128Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H0, H1, H2, H3; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD128Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD128Digest(RIPEMD128Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(RIPEMD128Digest t) + { + super.copyIn(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD128"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4 are the basic RIPEMD128 functions. + */ + + /* + * F + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * G + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * H + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * I + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + private int F1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int F2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x5a827999, s); + } + + private int F3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x6ed9eba1, s); + } + + private int F4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x8f1bbcdc, s); + } + + private int FF1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int FF2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x6d703ef3, s); + } + + private int FF3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x5c4dd124, s); + } + + private int FF4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x50a28be6, s); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + + a = aa = H0; + b = bb = H1; + c = cc = H2; + d = dd = H3; + + // + // Round 1 + // + a = F1(a, b, c, d, X[ 0], 11); + d = F1(d, a, b, c, X[ 1], 14); + c = F1(c, d, a, b, X[ 2], 15); + b = F1(b, c, d, a, X[ 3], 12); + a = F1(a, b, c, d, X[ 4], 5); + d = F1(d, a, b, c, X[ 5], 8); + c = F1(c, d, a, b, X[ 6], 7); + b = F1(b, c, d, a, X[ 7], 9); + a = F1(a, b, c, d, X[ 8], 11); + d = F1(d, a, b, c, X[ 9], 13); + c = F1(c, d, a, b, X[10], 14); + b = F1(b, c, d, a, X[11], 15); + a = F1(a, b, c, d, X[12], 6); + d = F1(d, a, b, c, X[13], 7); + c = F1(c, d, a, b, X[14], 9); + b = F1(b, c, d, a, X[15], 8); + + // + // Round 2 + // + a = F2(a, b, c, d, X[ 7], 7); + d = F2(d, a, b, c, X[ 4], 6); + c = F2(c, d, a, b, X[13], 8); + b = F2(b, c, d, a, X[ 1], 13); + a = F2(a, b, c, d, X[10], 11); + d = F2(d, a, b, c, X[ 6], 9); + c = F2(c, d, a, b, X[15], 7); + b = F2(b, c, d, a, X[ 3], 15); + a = F2(a, b, c, d, X[12], 7); + d = F2(d, a, b, c, X[ 0], 12); + c = F2(c, d, a, b, X[ 9], 15); + b = F2(b, c, d, a, X[ 5], 9); + a = F2(a, b, c, d, X[ 2], 11); + d = F2(d, a, b, c, X[14], 7); + c = F2(c, d, a, b, X[11], 13); + b = F2(b, c, d, a, X[ 8], 12); + + // + // Round 3 + // + a = F3(a, b, c, d, X[ 3], 11); + d = F3(d, a, b, c, X[10], 13); + c = F3(c, d, a, b, X[14], 6); + b = F3(b, c, d, a, X[ 4], 7); + a = F3(a, b, c, d, X[ 9], 14); + d = F3(d, a, b, c, X[15], 9); + c = F3(c, d, a, b, X[ 8], 13); + b = F3(b, c, d, a, X[ 1], 15); + a = F3(a, b, c, d, X[ 2], 14); + d = F3(d, a, b, c, X[ 7], 8); + c = F3(c, d, a, b, X[ 0], 13); + b = F3(b, c, d, a, X[ 6], 6); + a = F3(a, b, c, d, X[13], 5); + d = F3(d, a, b, c, X[11], 12); + c = F3(c, d, a, b, X[ 5], 7); + b = F3(b, c, d, a, X[12], 5); + + // + // Round 4 + // + a = F4(a, b, c, d, X[ 1], 11); + d = F4(d, a, b, c, X[ 9], 12); + c = F4(c, d, a, b, X[11], 14); + b = F4(b, c, d, a, X[10], 15); + a = F4(a, b, c, d, X[ 0], 14); + d = F4(d, a, b, c, X[ 8], 15); + c = F4(c, d, a, b, X[12], 9); + b = F4(b, c, d, a, X[ 4], 8); + a = F4(a, b, c, d, X[13], 9); + d = F4(d, a, b, c, X[ 3], 14); + c = F4(c, d, a, b, X[ 7], 5); + b = F4(b, c, d, a, X[15], 6); + a = F4(a, b, c, d, X[14], 8); + d = F4(d, a, b, c, X[ 5], 6); + c = F4(c, d, a, b, X[ 6], 5); + b = F4(b, c, d, a, X[ 2], 12); + + // + // Parallel round 1 + // + aa = FF4(aa, bb, cc, dd, X[ 5], 8); + dd = FF4(dd, aa, bb, cc, X[14], 9); + cc = FF4(cc, dd, aa, bb, X[ 7], 9); + bb = FF4(bb, cc, dd, aa, X[ 0], 11); + aa = FF4(aa, bb, cc, dd, X[ 9], 13); + dd = FF4(dd, aa, bb, cc, X[ 2], 15); + cc = FF4(cc, dd, aa, bb, X[11], 15); + bb = FF4(bb, cc, dd, aa, X[ 4], 5); + aa = FF4(aa, bb, cc, dd, X[13], 7); + dd = FF4(dd, aa, bb, cc, X[ 6], 7); + cc = FF4(cc, dd, aa, bb, X[15], 8); + bb = FF4(bb, cc, dd, aa, X[ 8], 11); + aa = FF4(aa, bb, cc, dd, X[ 1], 14); + dd = FF4(dd, aa, bb, cc, X[10], 14); + cc = FF4(cc, dd, aa, bb, X[ 3], 12); + bb = FF4(bb, cc, dd, aa, X[12], 6); + + // + // Parallel round 2 + // + aa = FF3(aa, bb, cc, dd, X[ 6], 9); + dd = FF3(dd, aa, bb, cc, X[11], 13); + cc = FF3(cc, dd, aa, bb, X[ 3], 15); + bb = FF3(bb, cc, dd, aa, X[ 7], 7); + aa = FF3(aa, bb, cc, dd, X[ 0], 12); + dd = FF3(dd, aa, bb, cc, X[13], 8); + cc = FF3(cc, dd, aa, bb, X[ 5], 9); + bb = FF3(bb, cc, dd, aa, X[10], 11); + aa = FF3(aa, bb, cc, dd, X[14], 7); + dd = FF3(dd, aa, bb, cc, X[15], 7); + cc = FF3(cc, dd, aa, bb, X[ 8], 12); + bb = FF3(bb, cc, dd, aa, X[12], 7); + aa = FF3(aa, bb, cc, dd, X[ 4], 6); + dd = FF3(dd, aa, bb, cc, X[ 9], 15); + cc = FF3(cc, dd, aa, bb, X[ 1], 13); + bb = FF3(bb, cc, dd, aa, X[ 2], 11); + + // + // Parallel round 3 + // + aa = FF2(aa, bb, cc, dd, X[15], 9); + dd = FF2(dd, aa, bb, cc, X[ 5], 7); + cc = FF2(cc, dd, aa, bb, X[ 1], 15); + bb = FF2(bb, cc, dd, aa, X[ 3], 11); + aa = FF2(aa, bb, cc, dd, X[ 7], 8); + dd = FF2(dd, aa, bb, cc, X[14], 6); + cc = FF2(cc, dd, aa, bb, X[ 6], 6); + bb = FF2(bb, cc, dd, aa, X[ 9], 14); + aa = FF2(aa, bb, cc, dd, X[11], 12); + dd = FF2(dd, aa, bb, cc, X[ 8], 13); + cc = FF2(cc, dd, aa, bb, X[12], 5); + bb = FF2(bb, cc, dd, aa, X[ 2], 14); + aa = FF2(aa, bb, cc, dd, X[10], 13); + dd = FF2(dd, aa, bb, cc, X[ 0], 13); + cc = FF2(cc, dd, aa, bb, X[ 4], 7); + bb = FF2(bb, cc, dd, aa, X[13], 5); + + // + // Parallel round 4 + // + aa = FF1(aa, bb, cc, dd, X[ 8], 15); + dd = FF1(dd, aa, bb, cc, X[ 6], 5); + cc = FF1(cc, dd, aa, bb, X[ 4], 8); + bb = FF1(bb, cc, dd, aa, X[ 1], 11); + aa = FF1(aa, bb, cc, dd, X[ 3], 14); + dd = FF1(dd, aa, bb, cc, X[11], 14); + cc = FF1(cc, dd, aa, bb, X[15], 6); + bb = FF1(bb, cc, dd, aa, X[ 0], 14); + aa = FF1(aa, bb, cc, dd, X[ 5], 6); + dd = FF1(dd, aa, bb, cc, X[12], 9); + cc = FF1(cc, dd, aa, bb, X[ 2], 12); + bb = FF1(bb, cc, dd, aa, X[13], 9); + aa = FF1(aa, bb, cc, dd, X[ 9], 12); + dd = FF1(dd, aa, bb, cc, X[ 7], 5); + cc = FF1(cc, dd, aa, bb, X[10], 15); + bb = FF1(bb, cc, dd, aa, X[14], 8); + + dd += c + H1; // final result for H0 + + // + // combine the results + // + H1 = H2 + d + aa; + H2 = H3 + a + bb; + H3 = H0 + b + cc; + H0 = dd; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new RIPEMD128Digest(this); + } + + public void reset(Memoable other) + { + RIPEMD128Digest d = (RIPEMD128Digest)other; + + copyIn(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java new file mode 100644 index 00000000..20c81e68 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java @@ -0,0 +1,443 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.util.Memoable; + +/** + * implementation of RIPEMD see, + * http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html + */ +public class RIPEMD160Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 20; + + private int H0, H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD160Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD160Digest(RIPEMD160Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(RIPEMD160Digest t) + { + super.copyIn(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD160"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + unpackWord(H4, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + H4 = 0xc3d2e1f0; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4,f5 are the basic RIPEMD160 functions. + */ + + /* + * rounds 0-15 + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * rounds 16-31 + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * rounds 32-47 + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * rounds 48-63 + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + /* + * rounds 64-79 + */ + private int f5( + int x, + int y, + int z) + { + return x ^ (y | ~z); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + int e, ee; + + a = aa = H0; + b = bb = H1; + c = cc = H2; + d = dd = H3; + e = ee = H4; + + // + // Rounds 1 - 16 + // + // left + a = RL(a + f1(b,c,d) + X[ 0], 11) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 1], 14) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 2], 15) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 3], 12) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 4], 5) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[ 5], 8) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 6], 7) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 7], 9) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 8], 11) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 9], 13) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[10], 14) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[11], 15) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[12], 6) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[13], 7) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[14], 9) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[15], 8) + e; c = RL(c, 10); + + // right + aa = RL(aa + f5(bb,cc,dd) + X[ 5] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[14] + 0x50a28be6, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 7] + 0x50a28be6, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[ 0] + 0x50a28be6, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 9] + 0x50a28be6, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[ 2] + 0x50a28be6, 15) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[11] + 0x50a28be6, 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 4] + 0x50a28be6, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[13] + 0x50a28be6, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 6] + 0x50a28be6, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[15] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[ 8] + 0x50a28be6, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 1] + 0x50a28be6, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[10] + 0x50a28be6, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 3] + 0x50a28be6, 12) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[12] + 0x50a28be6, 6) + ee; cc = RL(cc, 10); + + // + // Rounds 16-31 + // + // left + e = RL(e + f2(a,b,c) + X[ 7] + 0x5a827999, 7) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 4] + 0x5a827999, 6) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[13] + 0x5a827999, 8) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[ 1] + 0x5a827999, 13) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[10] + 0x5a827999, 11) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 6] + 0x5a827999, 9) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[15] + 0x5a827999, 7) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 3] + 0x5a827999, 15) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[12] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[ 0] + 0x5a827999, 12) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 9] + 0x5a827999, 15) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 5] + 0x5a827999, 9) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 2] + 0x5a827999, 11) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[14] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[11] + 0x5a827999, 13) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 8] + 0x5a827999, 12) + d; b = RL(b, 10); + + // right + ee = RL(ee + f4(aa,bb,cc) + X[ 6] + 0x5c4dd124, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[11] + 0x5c4dd124, 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 3] + 0x5c4dd124, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 7] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 0] + 0x5c4dd124, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[13] + 0x5c4dd124, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[ 5] + 0x5c4dd124, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[10] + 0x5c4dd124, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[14] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[15] + 0x5c4dd124, 7) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 8] + 0x5c4dd124, 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[12] + 0x5c4dd124, 7) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 4] + 0x5c4dd124, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 9] + 0x5c4dd124, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 1] + 0x5c4dd124, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 2] + 0x5c4dd124, 11) + dd; bb = RL(bb, 10); + + // + // Rounds 32-47 + // + // left + d = RL(d + f3(e,a,b) + X[ 3] + 0x6ed9eba1, 11) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[10] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[14] + 0x6ed9eba1, 6) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 4] + 0x6ed9eba1, 7) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 9] + 0x6ed9eba1, 14) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[15] + 0x6ed9eba1, 9) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 8] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[ 1] + 0x6ed9eba1, 15) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 2] + 0x6ed9eba1, 14) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 7] + 0x6ed9eba1, 8) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[ 0] + 0x6ed9eba1, 13) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 6] + 0x6ed9eba1, 6) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[13] + 0x6ed9eba1, 5) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[11] + 0x6ed9eba1, 12) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 5] + 0x6ed9eba1, 7) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[12] + 0x6ed9eba1, 5) + c; a = RL(a, 10); + + // right + dd = RL(dd + f3(ee,aa,bb) + X[15] + 0x6d703ef3, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 5] + 0x6d703ef3, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 1] + 0x6d703ef3, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 3] + 0x6d703ef3, 11) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 7] + 0x6d703ef3, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[14] + 0x6d703ef3, 6) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 6] + 0x6d703ef3, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 9] + 0x6d703ef3, 14) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[11] + 0x6d703ef3, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 8] + 0x6d703ef3, 13) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[12] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 2] + 0x6d703ef3, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[10] + 0x6d703ef3, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 0] + 0x6d703ef3, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 4] + 0x6d703ef3, 7) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[13] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + + // + // Rounds 48-63 + // + // left + c = RL(c + f4(d,e,a) + X[ 1] + 0x8f1bbcdc, 11) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[ 9] + 0x8f1bbcdc, 12) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[11] + 0x8f1bbcdc, 14) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[10] + 0x8f1bbcdc, 15) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 0] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 8] + 0x8f1bbcdc, 15) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[12] + 0x8f1bbcdc, 9) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[ 4] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[13] + 0x8f1bbcdc, 9) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 3] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 7] + 0x8f1bbcdc, 5) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[15] + 0x8f1bbcdc, 6) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[14] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[ 5] + 0x8f1bbcdc, 6) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 6] + 0x8f1bbcdc, 5) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 2] + 0x8f1bbcdc, 12) + b; e = RL(e, 10); + + // right + cc = RL(cc + f2(dd,ee,aa) + X[ 8] + 0x7a6d76e9, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[ 6] + 0x7a6d76e9, 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 4] + 0x7a6d76e9, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 1] + 0x7a6d76e9, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[ 3] + 0x7a6d76e9, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[11] + 0x7a6d76e9, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[15] + 0x7a6d76e9, 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 0] + 0x7a6d76e9, 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 5] + 0x7a6d76e9, 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[12] + 0x7a6d76e9, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[ 2] + 0x7a6d76e9, 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[13] + 0x7a6d76e9, 9) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 9] + 0x7a6d76e9, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 7] + 0x7a6d76e9, 5) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[10] + 0x7a6d76e9, 15) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[14] + 0x7a6d76e9, 8) + bb; ee = RL(ee, 10); + + // + // Rounds 64-79 + // + // left + b = RL(b + f5(c,d,e) + X[ 4] + 0xa953fd4e, 9) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 0] + 0xa953fd4e, 15) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[ 5] + 0xa953fd4e, 5) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 9] + 0xa953fd4e, 11) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 7] + 0xa953fd4e, 6) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[12] + 0xa953fd4e, 8) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 2] + 0xa953fd4e, 13) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[10] + 0xa953fd4e, 12) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[14] + 0xa953fd4e, 5) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 1] + 0xa953fd4e, 12) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[ 3] + 0xa953fd4e, 13) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 8] + 0xa953fd4e, 14) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[11] + 0xa953fd4e, 11) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 6] + 0xa953fd4e, 8) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[15] + 0xa953fd4e, 5) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[13] + 0xa953fd4e, 6) + a; d = RL(d, 10); + + // right + bb = RL(bb + f1(cc,dd,ee) + X[12], 8) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[15], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[10], 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 4], 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 1], 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[ 5], 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[ 8], 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 7], 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 6], 8) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 2], 13) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[13], 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[14], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 0], 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 3], 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 9], 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[11], 11) + aa; dd = RL(dd, 10); + + dd += c + H1; + H1 = H2 + d + ee; + H2 = H3 + e + aa; + H3 = H4 + a + bb; + H4 = H0 + b + cc; + H0 = dd; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new RIPEMD160Digest(this); + } + + public void reset(Memoable other) + { + RIPEMD160Digest d = (RIPEMD160Digest)other; + + copyIn(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD256Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD256Digest.java new file mode 100644 index 00000000..86746b45 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD256Digest.java @@ -0,0 +1,497 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.util.Memoable; + +/** + * implementation of RIPEMD256. + * <p> + * <b>note:</b> this algorithm offers the same level of security as RIPEMD128. + */ +public class RIPEMD256Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 32; + + private int H0, H1, H2, H3, H4, H5, H6, H7; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD256Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD256Digest(RIPEMD256Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(RIPEMD256Digest t) + { + super.copyIn(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD256"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + unpackWord(H4, out, outOff + 16); + unpackWord(H5, out, outOff + 20); + unpackWord(H6, out, outOff + 24); + unpackWord(H7, out, outOff + 28); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + H4 = 0x76543210; + H5 = 0xFEDCBA98; + H6 = 0x89ABCDEF; + H7 = 0x01234567; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4 are the basic RIPEMD128 functions. + */ + + /* + * F + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * G + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * H + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * I + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + private int F1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int F2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x5a827999, s); + } + + private int F3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x6ed9eba1, s); + } + + private int F4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x8f1bbcdc, s); + } + + private int FF1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int FF2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x6d703ef3, s); + } + + private int FF3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x5c4dd124, s); + } + + private int FF4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x50a28be6, s); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + int t; + + a = H0; + b = H1; + c = H2; + d = H3; + aa = H4; + bb = H5; + cc = H6; + dd = H7; + + // + // Round 1 + // + + a = F1(a, b, c, d, X[ 0], 11); + d = F1(d, a, b, c, X[ 1], 14); + c = F1(c, d, a, b, X[ 2], 15); + b = F1(b, c, d, a, X[ 3], 12); + a = F1(a, b, c, d, X[ 4], 5); + d = F1(d, a, b, c, X[ 5], 8); + c = F1(c, d, a, b, X[ 6], 7); + b = F1(b, c, d, a, X[ 7], 9); + a = F1(a, b, c, d, X[ 8], 11); + d = F1(d, a, b, c, X[ 9], 13); + c = F1(c, d, a, b, X[10], 14); + b = F1(b, c, d, a, X[11], 15); + a = F1(a, b, c, d, X[12], 6); + d = F1(d, a, b, c, X[13], 7); + c = F1(c, d, a, b, X[14], 9); + b = F1(b, c, d, a, X[15], 8); + + aa = FF4(aa, bb, cc, dd, X[ 5], 8); + dd = FF4(dd, aa, bb, cc, X[14], 9); + cc = FF4(cc, dd, aa, bb, X[ 7], 9); + bb = FF4(bb, cc, dd, aa, X[ 0], 11); + aa = FF4(aa, bb, cc, dd, X[ 9], 13); + dd = FF4(dd, aa, bb, cc, X[ 2], 15); + cc = FF4(cc, dd, aa, bb, X[11], 15); + bb = FF4(bb, cc, dd, aa, X[ 4], 5); + aa = FF4(aa, bb, cc, dd, X[13], 7); + dd = FF4(dd, aa, bb, cc, X[ 6], 7); + cc = FF4(cc, dd, aa, bb, X[15], 8); + bb = FF4(bb, cc, dd, aa, X[ 8], 11); + aa = FF4(aa, bb, cc, dd, X[ 1], 14); + dd = FF4(dd, aa, bb, cc, X[10], 14); + cc = FF4(cc, dd, aa, bb, X[ 3], 12); + bb = FF4(bb, cc, dd, aa, X[12], 6); + + t = a; a = aa; aa = t; + + // + // Round 2 + // + a = F2(a, b, c, d, X[ 7], 7); + d = F2(d, a, b, c, X[ 4], 6); + c = F2(c, d, a, b, X[13], 8); + b = F2(b, c, d, a, X[ 1], 13); + a = F2(a, b, c, d, X[10], 11); + d = F2(d, a, b, c, X[ 6], 9); + c = F2(c, d, a, b, X[15], 7); + b = F2(b, c, d, a, X[ 3], 15); + a = F2(a, b, c, d, X[12], 7); + d = F2(d, a, b, c, X[ 0], 12); + c = F2(c, d, a, b, X[ 9], 15); + b = F2(b, c, d, a, X[ 5], 9); + a = F2(a, b, c, d, X[ 2], 11); + d = F2(d, a, b, c, X[14], 7); + c = F2(c, d, a, b, X[11], 13); + b = F2(b, c, d, a, X[ 8], 12); + + aa = FF3(aa, bb, cc, dd, X[ 6], 9); + dd = FF3(dd, aa, bb, cc, X[ 11], 13); + cc = FF3(cc, dd, aa, bb, X[3], 15); + bb = FF3(bb, cc, dd, aa, X[ 7], 7); + aa = FF3(aa, bb, cc, dd, X[0], 12); + dd = FF3(dd, aa, bb, cc, X[13], 8); + cc = FF3(cc, dd, aa, bb, X[5], 9); + bb = FF3(bb, cc, dd, aa, X[10], 11); + aa = FF3(aa, bb, cc, dd, X[14], 7); + dd = FF3(dd, aa, bb, cc, X[15], 7); + cc = FF3(cc, dd, aa, bb, X[ 8], 12); + bb = FF3(bb, cc, dd, aa, X[12], 7); + aa = FF3(aa, bb, cc, dd, X[ 4], 6); + dd = FF3(dd, aa, bb, cc, X[ 9], 15); + cc = FF3(cc, dd, aa, bb, X[ 1], 13); + bb = FF3(bb, cc, dd, aa, X[ 2], 11); + + t = b; b = bb; bb = t; + + // + // Round 3 + // + a = F3(a, b, c, d, X[ 3], 11); + d = F3(d, a, b, c, X[10], 13); + c = F3(c, d, a, b, X[14], 6); + b = F3(b, c, d, a, X[ 4], 7); + a = F3(a, b, c, d, X[ 9], 14); + d = F3(d, a, b, c, X[15], 9); + c = F3(c, d, a, b, X[ 8], 13); + b = F3(b, c, d, a, X[ 1], 15); + a = F3(a, b, c, d, X[ 2], 14); + d = F3(d, a, b, c, X[ 7], 8); + c = F3(c, d, a, b, X[ 0], 13); + b = F3(b, c, d, a, X[ 6], 6); + a = F3(a, b, c, d, X[13], 5); + d = F3(d, a, b, c, X[11], 12); + c = F3(c, d, a, b, X[ 5], 7); + b = F3(b, c, d, a, X[12], 5); + + aa = FF2(aa, bb, cc, dd, X[ 15], 9); + dd = FF2(dd, aa, bb, cc, X[5], 7); + cc = FF2(cc, dd, aa, bb, X[1], 15); + bb = FF2(bb, cc, dd, aa, X[ 3], 11); + aa = FF2(aa, bb, cc, dd, X[ 7], 8); + dd = FF2(dd, aa, bb, cc, X[14], 6); + cc = FF2(cc, dd, aa, bb, X[ 6], 6); + bb = FF2(bb, cc, dd, aa, X[ 9], 14); + aa = FF2(aa, bb, cc, dd, X[11], 12); + dd = FF2(dd, aa, bb, cc, X[ 8], 13); + cc = FF2(cc, dd, aa, bb, X[12], 5); + bb = FF2(bb, cc, dd, aa, X[ 2], 14); + aa = FF2(aa, bb, cc, dd, X[10], 13); + dd = FF2(dd, aa, bb, cc, X[ 0], 13); + cc = FF2(cc, dd, aa, bb, X[ 4], 7); + bb = FF2(bb, cc, dd, aa, X[13], 5); + + t = c; c = cc; cc = t; + + // + // Round 4 + // + a = F4(a, b, c, d, X[ 1], 11); + d = F4(d, a, b, c, X[ 9], 12); + c = F4(c, d, a, b, X[11], 14); + b = F4(b, c, d, a, X[10], 15); + a = F4(a, b, c, d, X[ 0], 14); + d = F4(d, a, b, c, X[ 8], 15); + c = F4(c, d, a, b, X[12], 9); + b = F4(b, c, d, a, X[ 4], 8); + a = F4(a, b, c, d, X[13], 9); + d = F4(d, a, b, c, X[ 3], 14); + c = F4(c, d, a, b, X[ 7], 5); + b = F4(b, c, d, a, X[15], 6); + a = F4(a, b, c, d, X[14], 8); + d = F4(d, a, b, c, X[ 5], 6); + c = F4(c, d, a, b, X[ 6], 5); + b = F4(b, c, d, a, X[ 2], 12); + + aa = FF1(aa, bb, cc, dd, X[ 8], 15); + dd = FF1(dd, aa, bb, cc, X[ 6], 5); + cc = FF1(cc, dd, aa, bb, X[ 4], 8); + bb = FF1(bb, cc, dd, aa, X[ 1], 11); + aa = FF1(aa, bb, cc, dd, X[ 3], 14); + dd = FF1(dd, aa, bb, cc, X[11], 14); + cc = FF1(cc, dd, aa, bb, X[15], 6); + bb = FF1(bb, cc, dd, aa, X[ 0], 14); + aa = FF1(aa, bb, cc, dd, X[ 5], 6); + dd = FF1(dd, aa, bb, cc, X[12], 9); + cc = FF1(cc, dd, aa, bb, X[ 2], 12); + bb = FF1(bb, cc, dd, aa, X[13], 9); + aa = FF1(aa, bb, cc, dd, X[ 9], 12); + dd = FF1(dd, aa, bb, cc, X[ 7], 5); + cc = FF1(cc, dd, aa, bb, X[10], 15); + bb = FF1(bb, cc, dd, aa, X[14], 8); + + t = d; d = dd; dd = t; + + H0 += a; + H1 += b; + H2 += c; + H3 += d; + H4 += aa; + H5 += bb; + H6 += cc; + H7 += dd; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new RIPEMD256Digest(this); + } + + public void reset(Memoable other) + { + RIPEMD256Digest d = (RIPEMD256Digest)other; + + copyIn(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD320Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD320Digest.java new file mode 100644 index 00000000..32775e77 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/RIPEMD320Digest.java @@ -0,0 +1,481 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.util.Memoable; + +/** + * implementation of RIPEMD 320. + * <p> + * <b>Note:</b> this implementation offers the same level of security + * as RIPEMD 160. + */ +public class RIPEMD320Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 40; + + private int H0, H1, H2, H3, H4, H5, H6, H7, H8, H9; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD320Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD320Digest(RIPEMD320Digest t) + { + super(t); + + doCopy(t); + } + + private void doCopy(RIPEMD320Digest t) + { + super.copyIn(t); + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + H9 = t.H9; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD320"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + unpackWord(H4, out, outOff + 16); + unpackWord(H5, out, outOff + 20); + unpackWord(H6, out, outOff + 24); + unpackWord(H7, out, outOff + 28); + unpackWord(H8, out, outOff + 32); + unpackWord(H9, out, outOff + 36); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + H4 = 0xc3d2e1f0; + H5 = 0x76543210; + H6 = 0xFEDCBA98; + H7 = 0x89ABCDEF; + H8 = 0x01234567; + H9 = 0x3C2D1E0F; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4,f5 are the basic RIPEMD160 functions. + */ + + /* + * rounds 0-15 + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * rounds 16-31 + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * rounds 32-47 + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * rounds 48-63 + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + /* + * rounds 64-79 + */ + private int f5( + int x, + int y, + int z) + { + return x ^ (y | ~z); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + int e, ee; + int t; + + a = H0; + b = H1; + c = H2; + d = H3; + e = H4; + aa = H5; + bb = H6; + cc = H7; + dd = H8; + ee = H9; + + // + // Rounds 1 - 16 + // + // left + a = RL(a + f1(b,c,d) + X[ 0], 11) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 1], 14) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 2], 15) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 3], 12) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 4], 5) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[ 5], 8) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 6], 7) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 7], 9) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 8], 11) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 9], 13) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[10], 14) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[11], 15) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[12], 6) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[13], 7) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[14], 9) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[15], 8) + e; c = RL(c, 10); + + // right + aa = RL(aa + f5(bb,cc,dd) + X[ 5] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[14] + 0x50a28be6, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 7] + 0x50a28be6, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[ 0] + 0x50a28be6, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 9] + 0x50a28be6, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[ 2] + 0x50a28be6, 15) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[11] + 0x50a28be6, 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 4] + 0x50a28be6, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[13] + 0x50a28be6, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 6] + 0x50a28be6, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[15] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[ 8] + 0x50a28be6, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 1] + 0x50a28be6, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[10] + 0x50a28be6, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 3] + 0x50a28be6, 12) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[12] + 0x50a28be6, 6) + ee; cc = RL(cc, 10); + + t = a; a = aa; aa = t; + + // + // Rounds 16-31 + // + // left + e = RL(e + f2(a,b,c) + X[ 7] + 0x5a827999, 7) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 4] + 0x5a827999, 6) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[13] + 0x5a827999, 8) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[ 1] + 0x5a827999, 13) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[10] + 0x5a827999, 11) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 6] + 0x5a827999, 9) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[15] + 0x5a827999, 7) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 3] + 0x5a827999, 15) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[12] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[ 0] + 0x5a827999, 12) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 9] + 0x5a827999, 15) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 5] + 0x5a827999, 9) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 2] + 0x5a827999, 11) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[14] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[11] + 0x5a827999, 13) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 8] + 0x5a827999, 12) + d; b = RL(b, 10); + + // right + ee = RL(ee + f4(aa,bb,cc) + X[ 6] + 0x5c4dd124, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[11] + 0x5c4dd124, 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 3] + 0x5c4dd124, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 7] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 0] + 0x5c4dd124, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[13] + 0x5c4dd124, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[ 5] + 0x5c4dd124, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[10] + 0x5c4dd124, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[14] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[15] + 0x5c4dd124, 7) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 8] + 0x5c4dd124, 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[12] + 0x5c4dd124, 7) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 4] + 0x5c4dd124, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 9] + 0x5c4dd124, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 1] + 0x5c4dd124, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 2] + 0x5c4dd124, 11) + dd; bb = RL(bb, 10); + + t = b; b = bb; bb = t; + + // + // Rounds 32-47 + // + // left + d = RL(d + f3(e,a,b) + X[ 3] + 0x6ed9eba1, 11) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[10] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[14] + 0x6ed9eba1, 6) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 4] + 0x6ed9eba1, 7) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 9] + 0x6ed9eba1, 14) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[15] + 0x6ed9eba1, 9) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 8] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[ 1] + 0x6ed9eba1, 15) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 2] + 0x6ed9eba1, 14) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 7] + 0x6ed9eba1, 8) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[ 0] + 0x6ed9eba1, 13) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 6] + 0x6ed9eba1, 6) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[13] + 0x6ed9eba1, 5) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[11] + 0x6ed9eba1, 12) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 5] + 0x6ed9eba1, 7) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[12] + 0x6ed9eba1, 5) + c; a = RL(a, 10); + + // right + dd = RL(dd + f3(ee,aa,bb) + X[15] + 0x6d703ef3, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 5] + 0x6d703ef3, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 1] + 0x6d703ef3, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 3] + 0x6d703ef3, 11) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 7] + 0x6d703ef3, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[14] + 0x6d703ef3, 6) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 6] + 0x6d703ef3, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 9] + 0x6d703ef3, 14) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[11] + 0x6d703ef3, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 8] + 0x6d703ef3, 13) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[12] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 2] + 0x6d703ef3, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[10] + 0x6d703ef3, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 0] + 0x6d703ef3, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 4] + 0x6d703ef3, 7) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[13] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + + t = c; c = cc; cc = t; + + // + // Rounds 48-63 + // + // left + c = RL(c + f4(d,e,a) + X[ 1] + 0x8f1bbcdc, 11) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[ 9] + 0x8f1bbcdc, 12) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[11] + 0x8f1bbcdc, 14) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[10] + 0x8f1bbcdc, 15) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 0] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 8] + 0x8f1bbcdc, 15) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[12] + 0x8f1bbcdc, 9) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[ 4] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[13] + 0x8f1bbcdc, 9) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 3] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 7] + 0x8f1bbcdc, 5) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[15] + 0x8f1bbcdc, 6) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[14] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[ 5] + 0x8f1bbcdc, 6) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 6] + 0x8f1bbcdc, 5) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 2] + 0x8f1bbcdc, 12) + b; e = RL(e, 10); + + // right + cc = RL(cc + f2(dd,ee,aa) + X[ 8] + 0x7a6d76e9, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[ 6] + 0x7a6d76e9, 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 4] + 0x7a6d76e9, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 1] + 0x7a6d76e9, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[ 3] + 0x7a6d76e9, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[11] + 0x7a6d76e9, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[15] + 0x7a6d76e9, 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 0] + 0x7a6d76e9, 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 5] + 0x7a6d76e9, 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[12] + 0x7a6d76e9, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[ 2] + 0x7a6d76e9, 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[13] + 0x7a6d76e9, 9) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 9] + 0x7a6d76e9, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 7] + 0x7a6d76e9, 5) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[10] + 0x7a6d76e9, 15) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[14] + 0x7a6d76e9, 8) + bb; ee = RL(ee, 10); + + t = d; d = dd; dd = t; + + // + // Rounds 64-79 + // + // left + b = RL(b + f5(c,d,e) + X[ 4] + 0xa953fd4e, 9) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 0] + 0xa953fd4e, 15) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[ 5] + 0xa953fd4e, 5) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 9] + 0xa953fd4e, 11) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 7] + 0xa953fd4e, 6) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[12] + 0xa953fd4e, 8) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 2] + 0xa953fd4e, 13) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[10] + 0xa953fd4e, 12) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[14] + 0xa953fd4e, 5) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 1] + 0xa953fd4e, 12) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[ 3] + 0xa953fd4e, 13) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 8] + 0xa953fd4e, 14) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[11] + 0xa953fd4e, 11) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 6] + 0xa953fd4e, 8) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[15] + 0xa953fd4e, 5) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[13] + 0xa953fd4e, 6) + a; d = RL(d, 10); + + // right + bb = RL(bb + f1(cc,dd,ee) + X[12], 8) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[15], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[10], 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 4], 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 1], 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[ 5], 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[ 8], 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 7], 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 6], 8) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 2], 13) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[13], 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[14], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 0], 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 3], 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 9], 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[11], 11) + aa; dd = RL(dd, 10); + + // + // do (e, ee) swap as part of assignment. + // + + H0 += a; + H1 += b; + H2 += c; + H3 += d; + H4 += ee; + H5 += aa; + H6 += bb; + H7 += cc; + H8 += dd; + H9 += e; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new RIPEMD320Digest(this); + } + + public void reset(Memoable other) + { + RIPEMD320Digest d = (RIPEMD320Digest)other; + + doCopy(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/SHA1Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA1Digest.java new file mode 100644 index 00000000..21b1024e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA1Digest.java @@ -0,0 +1,309 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + +/** + * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349. + * + * It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5 + * is the "endianness" of the word processing! + */ +public class SHA1Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 20; + + private int H1, H2, H3, H4, H5; + + private int[] X = new int[80]; + private int xOff; + + /** + * Standard constructor + */ + public SHA1Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA1Digest(SHA1Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(SHA1Digest t) + { + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-1"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance +// X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[ inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + H5 = 0xc3d2e1f0; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // Additive constants + // + private static final int Y1 = 0x5a827999; + private static final int Y2 = 0x6ed9eba1; + private static final int Y3 = 0x8f1bbcdc; + private static final int Y4 = 0xca62c1d6; + + private int f( + int u, + int v, + int w) + { + return ((u & v) | ((~u) & w)); + } + + private int h( + int u, + int v, + int w) + { + return (u ^ v ^ w); + } + + private int g( + int u, + int v, + int w) + { + return ((u & v) | (u & w) | (v & w)); + } + + protected void processBlock() + { + // + // expand 16 word block into 80 word block. + // + for (int i = 16; i < 80; i++) + { + int t = X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]; + X[i] = t << 1 | t >>> 31; + } + + // + // set up working variables. + // + int A = H1; + int B = H2; + int C = H3; + int D = H4; + int E = H5; + + // + // round 1 + // + int idx = 0; + + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + f(B, C, D) + E + X[idx++] + Y1 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + f(B, C, D) + X[idx++] + Y1; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + f(A, B, C) + X[idx++] + Y1; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + f(E, A, B) + X[idx++] + Y1; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + f(D, E, A) + X[idx++] + Y1; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + f(C, D, E) + X[idx++] + Y1; + C = C << 30 | C >>> 2; + } + + // + // round 2 + // + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + h(B, C, D) + E + X[idx++] + Y2 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + h(B, C, D) + X[idx++] + Y2; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + h(A, B, C) + X[idx++] + Y2; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + h(E, A, B) + X[idx++] + Y2; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + h(D, E, A) + X[idx++] + Y2; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + h(C, D, E) + X[idx++] + Y2; + C = C << 30 | C >>> 2; + } + + // + // round 3 + // + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + g(B, C, D) + E + X[idx++] + Y3 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + g(B, C, D) + X[idx++] + Y3; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + g(A, B, C) + X[idx++] + Y3; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + g(E, A, B) + X[idx++] + Y3; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + g(D, E, A) + X[idx++] + Y3; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + g(C, D, E) + X[idx++] + Y3; + C = C << 30 | C >>> 2; + } + + // + // round 4 + // + for (int j = 0; j <= 3; j++) + { + // E = rotateLeft(A, 5) + h(B, C, D) + E + X[idx++] + Y4 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + h(B, C, D) + X[idx++] + Y4; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + h(A, B, C) + X[idx++] + Y4; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + h(E, A, B) + X[idx++] + Y4; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + h(D, E, A) + X[idx++] + Y4; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + h(C, D, E) + X[idx++] + Y4; + C = C << 30 | C >>> 2; + } + + + H1 += A; + H2 += B; + H3 += C; + H4 += D; + H5 += E; + + // + // reset start of the buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } + + public Memoable copy() + { + return new SHA1Digest(this); + } + + public void reset(Memoable other) + { + SHA1Digest d = (SHA1Digest)other; + + super.copyIn(d); + copyIn(d); + } +} + + + + diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/SHA224Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA224Digest.java new file mode 100644 index 00000000..d430321b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA224Digest.java @@ -0,0 +1,311 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + + +/** + * SHA-224 as described in RFC 3874 + * <pre> + * block word digest + * SHA-1 512 32 160 + * SHA-224 512 32 224 + * SHA-256 512 32 256 + * SHA-384 1024 64 384 + * SHA-512 1024 64 512 + * </pre> + */ +public class SHA224Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 28; + + private int H1, H2, H3, H4, H5, H6, H7, H8; + + private int[] X = new int[64]; + private int xOff; + + /** + * Standard constructor + */ + public SHA224Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA224Digest(SHA224Digest t) + { + super(t); + + doCopy(t); + } + + private void doCopy(SHA224Digest t) + { + super.copyIn(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-224"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance +// X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[ inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + Pack.intToBigEndian(H6, out, outOff + 20); + Pack.intToBigEndian(H7, out, outOff + 24); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-224 initial hash value + */ + + H1 = 0xc1059ed8; + H2 = 0x367cd507; + H3 = 0x3070dd17; + H4 = 0xf70e5939; + H5 = 0xffc00b31; + H6 = 0x68581511; + H7 = 0x64f98fa7; + H8 = 0xbefa4fa4; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + protected void processBlock() + { + // + // expand 16 word block into 64 word blocks. + // + for (int t = 16; t <= 63; t++) + { + X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16]; + } + + // + // set up working variables. + // + int a = H1; + int b = H2; + int c = H3; + int d = H4; + int e = H5; + int f = H6; + int g = H7; + int h = H8; + + + int t = 0; + for(int i = 0; i < 8; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + X[t]; + d += h; + h += Sum0(a) + Maj(a, b, c); + ++t; + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + X[t]; + c += g; + g += Sum0(h) + Maj(h, a, b); + ++t; + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + X[t]; + b += f; + f += Sum0(g) + Maj(g, h, a); + ++t; + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + X[t]; + a += e; + e += Sum0(f) + Maj(f, g, h); + ++t; + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + X[t]; + h += d; + d += Sum0(e) + Maj(e, f, g); + ++t; + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + X[t]; + g += c; + c += Sum0(d) + Maj(d, e, f); + ++t; + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + X[t]; + f += b; + b += Sum0(c) + Maj(c, d, e); + ++t; + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + X[t]; + e += a; + a += Sum0(b) + Maj(b, c, d); + ++t; + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } + + /* SHA-224 functions */ + private int Ch( + int x, + int y, + int z) + { + return ((x & y) ^ ((~x) & z)); + } + + private int Maj( + int x, + int y, + int z) + { + return ((x & y) ^ (x & z) ^ (y & z)); + } + + private int Sum0( + int x) + { + return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); + } + + private int Sum1( + int x) + { + return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); + } + + private int Theta0( + int x) + { + return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); + } + + private int Theta1( + int x) + { + return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); + } + + /* SHA-224 Constants + * (represent the first 32 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final int K[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + public Memoable copy() + { + return new SHA224Digest(this); + } + + public void reset(Memoable other) + { + SHA224Digest d = (SHA224Digest)other; + + doCopy(d); + } +} + diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/SHA256Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA256Digest.java new file mode 100644 index 00000000..a2ceda3d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA256Digest.java @@ -0,0 +1,314 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + + +/** + * FIPS 180-2 implementation of SHA-256. + * + * <pre> + * block word digest + * SHA-1 512 32 160 + * SHA-256 512 32 256 + * SHA-384 1024 64 384 + * SHA-512 1024 64 512 + * </pre> + */ +public class SHA256Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 32; + + private int H1, H2, H3, H4, H5, H6, H7, H8; + + private int[] X = new int[64]; + private int xOff; + + /** + * Standard constructor + */ + public SHA256Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA256Digest(SHA256Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(SHA256Digest t) + { + super.copyIn(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-256"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance +// X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + Pack.intToBigEndian(H6, out, outOff + 20); + Pack.intToBigEndian(H7, out, outOff + 24); + Pack.intToBigEndian(H8, out, outOff + 28); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-256 initial hash value + * The first 32 bits of the fractional parts of the square roots + * of the first eight prime numbers + */ + + H1 = 0x6a09e667; + H2 = 0xbb67ae85; + H3 = 0x3c6ef372; + H4 = 0xa54ff53a; + H5 = 0x510e527f; + H6 = 0x9b05688c; + H7 = 0x1f83d9ab; + H8 = 0x5be0cd19; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + protected void processBlock() + { + // + // expand 16 word block into 64 word blocks. + // + for (int t = 16; t <= 63; t++) + { + X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16]; + } + + // + // set up working variables. + // + int a = H1; + int b = H2; + int c = H3; + int d = H4; + int e = H5; + int f = H6; + int g = H7; + int h = H8; + + int t = 0; + for(int i = 0; i < 8; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + X[t]; + d += h; + h += Sum0(a) + Maj(a, b, c); + ++t; + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + X[t]; + c += g; + g += Sum0(h) + Maj(h, a, b); + ++t; + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + X[t]; + b += f; + f += Sum0(g) + Maj(g, h, a); + ++t; + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + X[t]; + a += e; + e += Sum0(f) + Maj(f, g, h); + ++t; + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + X[t]; + h += d; + d += Sum0(e) + Maj(e, f, g); + ++t; + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + X[t]; + g += c; + c += Sum0(d) + Maj(d, e, f); + ++t; + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + X[t]; + f += b; + b += Sum0(c) + Maj(c, d, e); + ++t; + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + X[t]; + e += a; + a += Sum0(b) + Maj(b, c, d); + ++t; + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } + + /* SHA-256 functions */ + private int Ch( + int x, + int y, + int z) + { + return (x & y) ^ ((~x) & z); + } + + private int Maj( + int x, + int y, + int z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + private int Sum0( + int x) + { + return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); + } + + private int Sum1( + int x) + { + return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); + } + + private int Theta0( + int x) + { + return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); + } + + private int Theta1( + int x) + { + return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); + } + + /* SHA-256 Constants + * (represent the first 32 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final int K[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + public Memoable copy() + { + return new SHA256Digest(this); + } + + public void reset(Memoable other) + { + SHA256Digest d = (SHA256Digest)other; + + copyIn(d); + } +} + diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/SHA384Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA384Digest.java new file mode 100644 index 00000000..75d195d4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA384Digest.java @@ -0,0 +1,99 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + + +/** + * FIPS 180-2 implementation of SHA-384. + * + * <pre> + * block word digest + * SHA-1 512 32 160 + * SHA-256 512 32 256 + * SHA-384 1024 64 384 + * SHA-512 1024 64 512 + * </pre> + */ +public class SHA384Digest + extends LongDigest +{ + private static final int DIGEST_LENGTH = 48; + + /** + * Standard constructor + */ + public SHA384Digest() + { + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA384Digest(SHA384Digest t) + { + super(t); + } + + public String getAlgorithmName() + { + return "SHA-384"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.longToBigEndian(H1, out, outOff); + Pack.longToBigEndian(H2, out, outOff + 8); + Pack.longToBigEndian(H3, out, outOff + 16); + Pack.longToBigEndian(H4, out, outOff + 24); + Pack.longToBigEndian(H5, out, outOff + 32); + Pack.longToBigEndian(H6, out, outOff + 40); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-384 initial hash value + * The first 64 bits of the fractional parts of the square roots + * of the 9th through 16th prime numbers + */ + H1 = 0xcbbb9d5dc1059ed8l; + H2 = 0x629a292a367cd507l; + H3 = 0x9159015a3070dd17l; + H4 = 0x152fecd8f70e5939l; + H5 = 0x67332667ffc00b31l; + H6 = 0x8eb44a8768581511l; + H7 = 0xdb0c2e0d64f98fa7l; + H8 = 0x47b5481dbefa4fa4l; + } + + public Memoable copy() + { + return new SHA384Digest(this); + } + + public void reset(Memoable other) + { + SHA384Digest d = (SHA384Digest)other; + + super.copyIn(d); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java new file mode 100644 index 00000000..15eb77ce --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java @@ -0,0 +1,547 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; + +/** + * implementation of SHA-3 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 SHA3Digest + 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; + } + + private byte[] state = new byte[(1600 / 8)]; + private byte[] dataQueue = new byte[(1536 / 8)]; + private int rate; + private int bitsInQueue; + private int fixedOutputLength; + private boolean squeezing; + private int bitsAvailableForSqueezing; + private byte[] chunk; + private byte[] oneByte; + + private void clearDataQueueSection(int off, int len) + { + for (int i = off; i != off + len; i++) + { + dataQueue[i] = 0; + } + } + + public SHA3Digest() + { + init(0); + } + + public SHA3Digest(int bitLength) + { + init(bitLength); + } + + public SHA3Digest(SHA3Digest 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 "SHA3-" + fixedOutputLength; + } + + public int getDigestSize() + { + return fixedOutputLength / 8; + } + + public void update(byte in) + { + oneByte[0] = in; + + doUpdate(oneByte, 0, 8L); + } + + public void update(byte[] in, int inOff, int len) + { + doUpdate(in, inOff, len * 8L); + } + + public int doFinal(byte[] out, int outOff) + { + 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 0: + case 288: + initSponge(1024, 576); + 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 224, 256, 384, or 512."); + } + } + + private void doUpdate(byte[] data, int off, long databitlen) + { + if ((databitlen % 8) == 0) + { + absorb(data, off, databitlen); + } + else + { + absorb(data, off, databitlen - (databitlen % 8)); + + byte[] lastByte = new byte[1]; + + lastByte[0] = (byte)(data[off + (int)(databitlen / 8)] >> (8 - (databitlen % 8))); + absorb(lastByte, off, databitlen % 8); + } + } + + 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; + this.fixedOutputLength = 0; + 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; + } + + private 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; + } + + private 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/core/src/main/java/org/bouncycastle/crypto/digests/SHA512Digest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA512Digest.java new file mode 100644 index 00000000..7db63ad2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA512Digest.java @@ -0,0 +1,102 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + + +/** + * FIPS 180-2 implementation of SHA-512. + * + * <pre> + * block word digest + * SHA-1 512 32 160 + * SHA-256 512 32 256 + * SHA-384 1024 64 384 + * SHA-512 1024 64 512 + * </pre> + */ +public class SHA512Digest + extends LongDigest +{ + private static final int DIGEST_LENGTH = 64; + + /** + * Standard constructor + */ + public SHA512Digest() + { + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA512Digest(SHA512Digest t) + { + super(t); + } + + public String getAlgorithmName() + { + return "SHA-512"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.longToBigEndian(H1, out, outOff); + Pack.longToBigEndian(H2, out, outOff + 8); + Pack.longToBigEndian(H3, out, outOff + 16); + Pack.longToBigEndian(H4, out, outOff + 24); + Pack.longToBigEndian(H5, out, outOff + 32); + Pack.longToBigEndian(H6, out, outOff + 40); + Pack.longToBigEndian(H7, out, outOff + 48); + Pack.longToBigEndian(H8, out, outOff + 56); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-512 initial hash value + * The first 64 bits of the fractional parts of the square roots + * of the first eight prime numbers + */ + H1 = 0x6a09e667f3bcc908L; + H2 = 0xbb67ae8584caa73bL; + H3 = 0x3c6ef372fe94f82bL; + H4 = 0xa54ff53a5f1d36f1L; + H5 = 0x510e527fade682d1L; + H6 = 0x9b05688c2b3e6c1fL; + H7 = 0x1f83d9abfb41bd6bL; + H8 = 0x5be0cd19137e2179L; + } + + public Memoable copy() + { + return new SHA512Digest(this); + } + + public void reset(Memoable other) + { + SHA512Digest d = (SHA512Digest)other; + + copyIn(d); + } +} + diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/SHA512tDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/SHA512tDigest.java new file mode 100644 index 00000000..46154618 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/SHA512tDigest.java @@ -0,0 +1,205 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.util.Memoable; +import org.bouncycastle.util.MemoableResetException; + +/** + * FIPS 180-4 implementation of SHA-512/t + */ +public class SHA512tDigest + extends LongDigest +{ + private final int digestLength; + + private long H1t, H2t, H3t, H4t, H5t, H6t, H7t, H8t; + + /** + * Standard constructor + */ + public SHA512tDigest(int bitLength) + { + if (bitLength >= 512) + { + throw new IllegalArgumentException("bitLength cannot be >= 512"); + } + + if (bitLength % 8 != 0) + { + throw new IllegalArgumentException("bitLength needs to be a multiple of 8"); + } + + if (bitLength == 384) + { + throw new IllegalArgumentException("bitLength cannot be 384 use SHA384 instead"); + } + + this.digestLength = bitLength / 8; + + tIvGenerate(digestLength * 8); + + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA512tDigest(SHA512tDigest t) + { + super(t); + + this.digestLength = t.digestLength; + + reset(t); + } + + public String getAlgorithmName() + { + return "SHA-512/" + Integer.toString(digestLength * 8); + } + + public int getDigestSize() + { + return digestLength; + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + longToBigEndian(H1, out, outOff, digestLength); + longToBigEndian(H2, out, outOff + 8, digestLength - 8); + longToBigEndian(H3, out, outOff + 16, digestLength - 16); + longToBigEndian(H4, out, outOff + 24, digestLength - 24); + longToBigEndian(H5, out, outOff + 32, digestLength - 32); + longToBigEndian(H6, out, outOff + 40, digestLength - 40); + longToBigEndian(H7, out, outOff + 48, digestLength - 48); + longToBigEndian(H8, out, outOff + 56, digestLength - 56); + + reset(); + + return digestLength; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* + * initial hash values use the iv generation algorithm for t. + */ + H1 = H1t; + H2 = H2t; + H3 = H3t; + H4 = H4t; + H5 = H5t; + H6 = H6t; + H7 = H7t; + H8 = H8t; + } + + private void tIvGenerate(int bitLength) + { + H1 = 0x6a09e667f3bcc908L ^ 0xa5a5a5a5a5a5a5a5L; + H2 = 0xbb67ae8584caa73bL ^ 0xa5a5a5a5a5a5a5a5L; + H3 = 0x3c6ef372fe94f82bL ^ 0xa5a5a5a5a5a5a5a5L; + H4 = 0xa54ff53a5f1d36f1L ^ 0xa5a5a5a5a5a5a5a5L; + H5 = 0x510e527fade682d1L ^ 0xa5a5a5a5a5a5a5a5L; + H6 = 0x9b05688c2b3e6c1fL ^ 0xa5a5a5a5a5a5a5a5L; + H7 = 0x1f83d9abfb41bd6bL ^ 0xa5a5a5a5a5a5a5a5L; + H8 = 0x5be0cd19137e2179L ^ 0xa5a5a5a5a5a5a5a5L; + + update((byte)0x53); + update((byte)0x48); + update((byte)0x41); + update((byte)0x2D); + update((byte)0x35); + update((byte)0x31); + update((byte)0x32); + update((byte)0x2F); + + if (bitLength > 100) + { + update((byte)(bitLength / 100 + 0x30)); + bitLength = bitLength % 100; + update((byte)(bitLength / 10 + 0x30)); + bitLength = bitLength % 10; + update((byte)(bitLength + 0x30)); + } + else if (bitLength > 10) + { + update((byte)(bitLength / 10 + 0x30)); + bitLength = bitLength % 10; + update((byte)(bitLength + 0x30)); + } + else + { + update((byte)(bitLength + 0x30)); + } + + finish(); + + H1t = H1; + H2t = H2; + H3t = H3; + H4t = H4; + H5t = H5; + H6t = H6; + H7t = H7; + H8t = H8; + } + + private static void longToBigEndian(long n, byte[] bs, int off, int max) + { + if (max > 0) + { + intToBigEndian((int)(n >>> 32), bs, off, max); + + if (max > 4) + { + intToBigEndian((int)(n & 0xffffffffL), bs, off + 4, max - 4); + } + } + } + + private static void intToBigEndian(int n, byte[] bs, int off, int max) + { + int num = Math.min(4, max); + while (--num >= 0) + { + int shift = 8 * (3 - num); + bs[off + num] = (byte)(n >>> shift); + } + } + + public Memoable copy() + { + return new SHA512tDigest(this); + } + + public void reset(Memoable other) + { + SHA512tDigest t = (SHA512tDigest)other; + + if (this.digestLength != t.digestLength) + { + throw new MemoableResetException("digestLength inappropriate in other"); + } + + super.copyIn(t); + + this.H1t = t.H1t; + this.H2t = t.H2t; + this.H3t = t.H3t; + this.H4t = t.H4t; + this.H5t = t.H5t; + this.H6t = t.H6t; + this.H7t = t.H7t; + this.H8t = t.H8t; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/ShortenedDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/ShortenedDigest.java new file mode 100644 index 00000000..89033e80 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/ShortenedDigest.java @@ -0,0 +1,80 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; + +/** + * Wrapper class that reduces the output length of a particular digest to + * only the first n bytes of the digest function. + */ +public class ShortenedDigest + implements ExtendedDigest +{ + private ExtendedDigest baseDigest; + private int length; + + /** + * Base constructor. + * + * @param baseDigest underlying digest to use. + * @param length length in bytes of the output of doFinal. + * @exception IllegalArgumentException if baseDigest is null, or length is greater than baseDigest.getDigestSize(). + */ + public ShortenedDigest( + ExtendedDigest baseDigest, + int length) + { + if (baseDigest == null) + { + throw new IllegalArgumentException("baseDigest must not be null"); + } + + if (length > baseDigest.getDigestSize()) + { + throw new IllegalArgumentException("baseDigest output not large enough to support length"); + } + + this.baseDigest = baseDigest; + this.length = length; + } + + public String getAlgorithmName() + { + return baseDigest.getAlgorithmName() + "(" + length * 8 + ")"; + } + + public int getDigestSize() + { + return length; + } + + public void update(byte in) + { + baseDigest.update(in); + } + + public void update(byte[] in, int inOff, int len) + { + baseDigest.update(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + byte[] tmp = new byte[baseDigest.getDigestSize()]; + + baseDigest.doFinal(tmp, 0); + + System.arraycopy(tmp, 0, out, outOff, length); + + return length; + } + + public void reset() + { + baseDigest.reset(); + } + + public int getByteLength() + { + return baseDigest.getByteLength(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java new file mode 100644 index 00000000..2899e305 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java @@ -0,0 +1,879 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Memoable; + +/** + * implementation of Tiger based on: + * <a href="http://www.cs.technion.ac.il/~biham/Reports/Tiger"> + * http://www.cs.technion.ac.il/~biham/Reports/Tiger</a> + */ +public class TigerDigest + implements ExtendedDigest, Memoable +{ + private static final int BYTE_LENGTH = 64; + + /* + * S-Boxes. + */ + private static final long[] t1 = { + 0x02AAB17CF7E90C5EL /* 0 */, 0xAC424B03E243A8ECL /* 1 */, + 0x72CD5BE30DD5FCD3L /* 2 */, 0x6D019B93F6F97F3AL /* 3 */, + 0xCD9978FFD21F9193L /* 4 */, 0x7573A1C9708029E2L /* 5 */, + 0xB164326B922A83C3L /* 6 */, 0x46883EEE04915870L /* 7 */, + 0xEAACE3057103ECE6L /* 8 */, 0xC54169B808A3535CL /* 9 */, + 0x4CE754918DDEC47CL /* 10 */, 0x0AA2F4DFDC0DF40CL /* 11 */, + 0x10B76F18A74DBEFAL /* 12 */, 0xC6CCB6235AD1AB6AL /* 13 */, + 0x13726121572FE2FFL /* 14 */, 0x1A488C6F199D921EL /* 15 */, + 0x4BC9F9F4DA0007CAL /* 16 */, 0x26F5E6F6E85241C7L /* 17 */, + 0x859079DBEA5947B6L /* 18 */, 0x4F1885C5C99E8C92L /* 19 */, + 0xD78E761EA96F864BL /* 20 */, 0x8E36428C52B5C17DL /* 21 */, + 0x69CF6827373063C1L /* 22 */, 0xB607C93D9BB4C56EL /* 23 */, + 0x7D820E760E76B5EAL /* 24 */, 0x645C9CC6F07FDC42L /* 25 */, + 0xBF38A078243342E0L /* 26 */, 0x5F6B343C9D2E7D04L /* 27 */, + 0xF2C28AEB600B0EC6L /* 28 */, 0x6C0ED85F7254BCACL /* 29 */, + 0x71592281A4DB4FE5L /* 30 */, 0x1967FA69CE0FED9FL /* 31 */, + 0xFD5293F8B96545DBL /* 32 */, 0xC879E9D7F2A7600BL /* 33 */, + 0x860248920193194EL /* 34 */, 0xA4F9533B2D9CC0B3L /* 35 */, + 0x9053836C15957613L /* 36 */, 0xDB6DCF8AFC357BF1L /* 37 */, + 0x18BEEA7A7A370F57L /* 38 */, 0x037117CA50B99066L /* 39 */, + 0x6AB30A9774424A35L /* 40 */, 0xF4E92F02E325249BL /* 41 */, + 0x7739DB07061CCAE1L /* 42 */, 0xD8F3B49CECA42A05L /* 43 */, + 0xBD56BE3F51382F73L /* 44 */, 0x45FAED5843B0BB28L /* 45 */, + 0x1C813D5C11BF1F83L /* 46 */, 0x8AF0E4B6D75FA169L /* 47 */, + 0x33EE18A487AD9999L /* 48 */, 0x3C26E8EAB1C94410L /* 49 */, + 0xB510102BC0A822F9L /* 50 */, 0x141EEF310CE6123BL /* 51 */, + 0xFC65B90059DDB154L /* 52 */, 0xE0158640C5E0E607L /* 53 */, + 0x884E079826C3A3CFL /* 54 */, 0x930D0D9523C535FDL /* 55 */, + 0x35638D754E9A2B00L /* 56 */, 0x4085FCCF40469DD5L /* 57 */, + 0xC4B17AD28BE23A4CL /* 58 */, 0xCAB2F0FC6A3E6A2EL /* 59 */, + 0x2860971A6B943FCDL /* 60 */, 0x3DDE6EE212E30446L /* 61 */, + 0x6222F32AE01765AEL /* 62 */, 0x5D550BB5478308FEL /* 63 */, + 0xA9EFA98DA0EDA22AL /* 64 */, 0xC351A71686C40DA7L /* 65 */, + 0x1105586D9C867C84L /* 66 */, 0xDCFFEE85FDA22853L /* 67 */, + 0xCCFBD0262C5EEF76L /* 68 */, 0xBAF294CB8990D201L /* 69 */, + 0xE69464F52AFAD975L /* 70 */, 0x94B013AFDF133E14L /* 71 */, + 0x06A7D1A32823C958L /* 72 */, 0x6F95FE5130F61119L /* 73 */, + 0xD92AB34E462C06C0L /* 74 */, 0xED7BDE33887C71D2L /* 75 */, + 0x79746D6E6518393EL /* 76 */, 0x5BA419385D713329L /* 77 */, + 0x7C1BA6B948A97564L /* 78 */, 0x31987C197BFDAC67L /* 79 */, + 0xDE6C23C44B053D02L /* 80 */, 0x581C49FED002D64DL /* 81 */, + 0xDD474D6338261571L /* 82 */, 0xAA4546C3E473D062L /* 83 */, + 0x928FCE349455F860L /* 84 */, 0x48161BBACAAB94D9L /* 85 */, + 0x63912430770E6F68L /* 86 */, 0x6EC8A5E602C6641CL /* 87 */, + 0x87282515337DDD2BL /* 88 */, 0x2CDA6B42034B701BL /* 89 */, + 0xB03D37C181CB096DL /* 90 */, 0xE108438266C71C6FL /* 91 */, + 0x2B3180C7EB51B255L /* 92 */, 0xDF92B82F96C08BBCL /* 93 */, + 0x5C68C8C0A632F3BAL /* 94 */, 0x5504CC861C3D0556L /* 95 */, + 0xABBFA4E55FB26B8FL /* 96 */, 0x41848B0AB3BACEB4L /* 97 */, + 0xB334A273AA445D32L /* 98 */, 0xBCA696F0A85AD881L /* 99 */, + 0x24F6EC65B528D56CL /* 100 */, 0x0CE1512E90F4524AL /* 101 */, + 0x4E9DD79D5506D35AL /* 102 */, 0x258905FAC6CE9779L /* 103 */, + 0x2019295B3E109B33L /* 104 */, 0xF8A9478B73A054CCL /* 105 */, + 0x2924F2F934417EB0L /* 106 */, 0x3993357D536D1BC4L /* 107 */, + 0x38A81AC21DB6FF8BL /* 108 */, 0x47C4FBF17D6016BFL /* 109 */, + 0x1E0FAADD7667E3F5L /* 110 */, 0x7ABCFF62938BEB96L /* 111 */, + 0xA78DAD948FC179C9L /* 112 */, 0x8F1F98B72911E50DL /* 113 */, + 0x61E48EAE27121A91L /* 114 */, 0x4D62F7AD31859808L /* 115 */, + 0xECEBA345EF5CEAEBL /* 116 */, 0xF5CEB25EBC9684CEL /* 117 */, + 0xF633E20CB7F76221L /* 118 */, 0xA32CDF06AB8293E4L /* 119 */, + 0x985A202CA5EE2CA4L /* 120 */, 0xCF0B8447CC8A8FB1L /* 121 */, + 0x9F765244979859A3L /* 122 */, 0xA8D516B1A1240017L /* 123 */, + 0x0BD7BA3EBB5DC726L /* 124 */, 0xE54BCA55B86ADB39L /* 125 */, + 0x1D7A3AFD6C478063L /* 126 */, 0x519EC608E7669EDDL /* 127 */, + 0x0E5715A2D149AA23L /* 128 */, 0x177D4571848FF194L /* 129 */, + 0xEEB55F3241014C22L /* 130 */, 0x0F5E5CA13A6E2EC2L /* 131 */, + 0x8029927B75F5C361L /* 132 */, 0xAD139FABC3D6E436L /* 133 */, + 0x0D5DF1A94CCF402FL /* 134 */, 0x3E8BD948BEA5DFC8L /* 135 */, + 0xA5A0D357BD3FF77EL /* 136 */, 0xA2D12E251F74F645L /* 137 */, + 0x66FD9E525E81A082L /* 138 */, 0x2E0C90CE7F687A49L /* 139 */, + 0xC2E8BCBEBA973BC5L /* 140 */, 0x000001BCE509745FL /* 141 */, + 0x423777BBE6DAB3D6L /* 142 */, 0xD1661C7EAEF06EB5L /* 143 */, + 0xA1781F354DAACFD8L /* 144 */, 0x2D11284A2B16AFFCL /* 145 */, + 0xF1FC4F67FA891D1FL /* 146 */, 0x73ECC25DCB920ADAL /* 147 */, + 0xAE610C22C2A12651L /* 148 */, 0x96E0A810D356B78AL /* 149 */, + 0x5A9A381F2FE7870FL /* 150 */, 0xD5AD62EDE94E5530L /* 151 */, + 0xD225E5E8368D1427L /* 152 */, 0x65977B70C7AF4631L /* 153 */, + 0x99F889B2DE39D74FL /* 154 */, 0x233F30BF54E1D143L /* 155 */, + 0x9A9675D3D9A63C97L /* 156 */, 0x5470554FF334F9A8L /* 157 */, + 0x166ACB744A4F5688L /* 158 */, 0x70C74CAAB2E4AEADL /* 159 */, + 0xF0D091646F294D12L /* 160 */, 0x57B82A89684031D1L /* 161 */, + 0xEFD95A5A61BE0B6BL /* 162 */, 0x2FBD12E969F2F29AL /* 163 */, + 0x9BD37013FEFF9FE8L /* 164 */, 0x3F9B0404D6085A06L /* 165 */, + 0x4940C1F3166CFE15L /* 166 */, 0x09542C4DCDF3DEFBL /* 167 */, + 0xB4C5218385CD5CE3L /* 168 */, 0xC935B7DC4462A641L /* 169 */, + 0x3417F8A68ED3B63FL /* 170 */, 0xB80959295B215B40L /* 171 */, + 0xF99CDAEF3B8C8572L /* 172 */, 0x018C0614F8FCB95DL /* 173 */, + 0x1B14ACCD1A3ACDF3L /* 174 */, 0x84D471F200BB732DL /* 175 */, + 0xC1A3110E95E8DA16L /* 176 */, 0x430A7220BF1A82B8L /* 177 */, + 0xB77E090D39DF210EL /* 178 */, 0x5EF4BD9F3CD05E9DL /* 179 */, + 0x9D4FF6DA7E57A444L /* 180 */, 0xDA1D60E183D4A5F8L /* 181 */, + 0xB287C38417998E47L /* 182 */, 0xFE3EDC121BB31886L /* 183 */, + 0xC7FE3CCC980CCBEFL /* 184 */, 0xE46FB590189BFD03L /* 185 */, + 0x3732FD469A4C57DCL /* 186 */, 0x7EF700A07CF1AD65L /* 187 */, + 0x59C64468A31D8859L /* 188 */, 0x762FB0B4D45B61F6L /* 189 */, + 0x155BAED099047718L /* 190 */, 0x68755E4C3D50BAA6L /* 191 */, + 0xE9214E7F22D8B4DFL /* 192 */, 0x2ADDBF532EAC95F4L /* 193 */, + 0x32AE3909B4BD0109L /* 194 */, 0x834DF537B08E3450L /* 195 */, + 0xFA209DA84220728DL /* 196 */, 0x9E691D9B9EFE23F7L /* 197 */, + 0x0446D288C4AE8D7FL /* 198 */, 0x7B4CC524E169785BL /* 199 */, + 0x21D87F0135CA1385L /* 200 */, 0xCEBB400F137B8AA5L /* 201 */, + 0x272E2B66580796BEL /* 202 */, 0x3612264125C2B0DEL /* 203 */, + 0x057702BDAD1EFBB2L /* 204 */, 0xD4BABB8EACF84BE9L /* 205 */, + 0x91583139641BC67BL /* 206 */, 0x8BDC2DE08036E024L /* 207 */, + 0x603C8156F49F68EDL /* 208 */, 0xF7D236F7DBEF5111L /* 209 */, + 0x9727C4598AD21E80L /* 210 */, 0xA08A0896670A5FD7L /* 211 */, + 0xCB4A8F4309EBA9CBL /* 212 */, 0x81AF564B0F7036A1L /* 213 */, + 0xC0B99AA778199ABDL /* 214 */, 0x959F1EC83FC8E952L /* 215 */, + 0x8C505077794A81B9L /* 216 */, 0x3ACAAF8F056338F0L /* 217 */, + 0x07B43F50627A6778L /* 218 */, 0x4A44AB49F5ECCC77L /* 219 */, + 0x3BC3D6E4B679EE98L /* 220 */, 0x9CC0D4D1CF14108CL /* 221 */, + 0x4406C00B206BC8A0L /* 222 */, 0x82A18854C8D72D89L /* 223 */, + 0x67E366B35C3C432CL /* 224 */, 0xB923DD61102B37F2L /* 225 */, + 0x56AB2779D884271DL /* 226 */, 0xBE83E1B0FF1525AFL /* 227 */, + 0xFB7C65D4217E49A9L /* 228 */, 0x6BDBE0E76D48E7D4L /* 229 */, + 0x08DF828745D9179EL /* 230 */, 0x22EA6A9ADD53BD34L /* 231 */, + 0xE36E141C5622200AL /* 232 */, 0x7F805D1B8CB750EEL /* 233 */, + 0xAFE5C7A59F58E837L /* 234 */, 0xE27F996A4FB1C23CL /* 235 */, + 0xD3867DFB0775F0D0L /* 236 */, 0xD0E673DE6E88891AL /* 237 */, + 0x123AEB9EAFB86C25L /* 238 */, 0x30F1D5D5C145B895L /* 239 */, + 0xBB434A2DEE7269E7L /* 240 */, 0x78CB67ECF931FA38L /* 241 */, + 0xF33B0372323BBF9CL /* 242 */, 0x52D66336FB279C74L /* 243 */, + 0x505F33AC0AFB4EAAL /* 244 */, 0xE8A5CD99A2CCE187L /* 245 */, + 0x534974801E2D30BBL /* 246 */, 0x8D2D5711D5876D90L /* 247 */, + 0x1F1A412891BC038EL /* 248 */, 0xD6E2E71D82E56648L /* 249 */, + 0x74036C3A497732B7L /* 250 */, 0x89B67ED96361F5ABL /* 251 */, + 0xFFED95D8F1EA02A2L /* 252 */, 0xE72B3BD61464D43DL /* 253 */, + 0xA6300F170BDC4820L /* 254 */, 0xEBC18760ED78A77AL /* 255 */, + }; + + private static final long[] t2 = { + 0xE6A6BE5A05A12138L /* 256 */, 0xB5A122A5B4F87C98L /* 257 */, + 0x563C6089140B6990L /* 258 */, 0x4C46CB2E391F5DD5L /* 259 */, + 0xD932ADDBC9B79434L /* 260 */, 0x08EA70E42015AFF5L /* 261 */, + 0xD765A6673E478CF1L /* 262 */, 0xC4FB757EAB278D99L /* 263 */, + 0xDF11C6862D6E0692L /* 264 */, 0xDDEB84F10D7F3B16L /* 265 */, + 0x6F2EF604A665EA04L /* 266 */, 0x4A8E0F0FF0E0DFB3L /* 267 */, + 0xA5EDEEF83DBCBA51L /* 268 */, 0xFC4F0A2A0EA4371EL /* 269 */, + 0xE83E1DA85CB38429L /* 270 */, 0xDC8FF882BA1B1CE2L /* 271 */, + 0xCD45505E8353E80DL /* 272 */, 0x18D19A00D4DB0717L /* 273 */, + 0x34A0CFEDA5F38101L /* 274 */, 0x0BE77E518887CAF2L /* 275 */, + 0x1E341438B3C45136L /* 276 */, 0xE05797F49089CCF9L /* 277 */, + 0xFFD23F9DF2591D14L /* 278 */, 0x543DDA228595C5CDL /* 279 */, + 0x661F81FD99052A33L /* 280 */, 0x8736E641DB0F7B76L /* 281 */, + 0x15227725418E5307L /* 282 */, 0xE25F7F46162EB2FAL /* 283 */, + 0x48A8B2126C13D9FEL /* 284 */, 0xAFDC541792E76EEAL /* 285 */, + 0x03D912BFC6D1898FL /* 286 */, 0x31B1AAFA1B83F51BL /* 287 */, + 0xF1AC2796E42AB7D9L /* 288 */, 0x40A3A7D7FCD2EBACL /* 289 */, + 0x1056136D0AFBBCC5L /* 290 */, 0x7889E1DD9A6D0C85L /* 291 */, + 0xD33525782A7974AAL /* 292 */, 0xA7E25D09078AC09BL /* 293 */, + 0xBD4138B3EAC6EDD0L /* 294 */, 0x920ABFBE71EB9E70L /* 295 */, + 0xA2A5D0F54FC2625CL /* 296 */, 0xC054E36B0B1290A3L /* 297 */, + 0xF6DD59FF62FE932BL /* 298 */, 0x3537354511A8AC7DL /* 299 */, + 0xCA845E9172FADCD4L /* 300 */, 0x84F82B60329D20DCL /* 301 */, + 0x79C62CE1CD672F18L /* 302 */, 0x8B09A2ADD124642CL /* 303 */, + 0xD0C1E96A19D9E726L /* 304 */, 0x5A786A9B4BA9500CL /* 305 */, + 0x0E020336634C43F3L /* 306 */, 0xC17B474AEB66D822L /* 307 */, + 0x6A731AE3EC9BAAC2L /* 308 */, 0x8226667AE0840258L /* 309 */, + 0x67D4567691CAECA5L /* 310 */, 0x1D94155C4875ADB5L /* 311 */, + 0x6D00FD985B813FDFL /* 312 */, 0x51286EFCB774CD06L /* 313 */, + 0x5E8834471FA744AFL /* 314 */, 0xF72CA0AEE761AE2EL /* 315 */, + 0xBE40E4CDAEE8E09AL /* 316 */, 0xE9970BBB5118F665L /* 317 */, + 0x726E4BEB33DF1964L /* 318 */, 0x703B000729199762L /* 319 */, + 0x4631D816F5EF30A7L /* 320 */, 0xB880B5B51504A6BEL /* 321 */, + 0x641793C37ED84B6CL /* 322 */, 0x7B21ED77F6E97D96L /* 323 */, + 0x776306312EF96B73L /* 324 */, 0xAE528948E86FF3F4L /* 325 */, + 0x53DBD7F286A3F8F8L /* 326 */, 0x16CADCE74CFC1063L /* 327 */, + 0x005C19BDFA52C6DDL /* 328 */, 0x68868F5D64D46AD3L /* 329 */, + 0x3A9D512CCF1E186AL /* 330 */, 0x367E62C2385660AEL /* 331 */, + 0xE359E7EA77DCB1D7L /* 332 */, 0x526C0773749ABE6EL /* 333 */, + 0x735AE5F9D09F734BL /* 334 */, 0x493FC7CC8A558BA8L /* 335 */, + 0xB0B9C1533041AB45L /* 336 */, 0x321958BA470A59BDL /* 337 */, + 0x852DB00B5F46C393L /* 338 */, 0x91209B2BD336B0E5L /* 339 */, + 0x6E604F7D659EF19FL /* 340 */, 0xB99A8AE2782CCB24L /* 341 */, + 0xCCF52AB6C814C4C7L /* 342 */, 0x4727D9AFBE11727BL /* 343 */, + 0x7E950D0C0121B34DL /* 344 */, 0x756F435670AD471FL /* 345 */, + 0xF5ADD442615A6849L /* 346 */, 0x4E87E09980B9957AL /* 347 */, + 0x2ACFA1DF50AEE355L /* 348 */, 0xD898263AFD2FD556L /* 349 */, + 0xC8F4924DD80C8FD6L /* 350 */, 0xCF99CA3D754A173AL /* 351 */, + 0xFE477BACAF91BF3CL /* 352 */, 0xED5371F6D690C12DL /* 353 */, + 0x831A5C285E687094L /* 354 */, 0xC5D3C90A3708A0A4L /* 355 */, + 0x0F7F903717D06580L /* 356 */, 0x19F9BB13B8FDF27FL /* 357 */, + 0xB1BD6F1B4D502843L /* 358 */, 0x1C761BA38FFF4012L /* 359 */, + 0x0D1530C4E2E21F3BL /* 360 */, 0x8943CE69A7372C8AL /* 361 */, + 0xE5184E11FEB5CE66L /* 362 */, 0x618BDB80BD736621L /* 363 */, + 0x7D29BAD68B574D0BL /* 364 */, 0x81BB613E25E6FE5BL /* 365 */, + 0x071C9C10BC07913FL /* 366 */, 0xC7BEEB7909AC2D97L /* 367 */, + 0xC3E58D353BC5D757L /* 368 */, 0xEB017892F38F61E8L /* 369 */, + 0xD4EFFB9C9B1CC21AL /* 370 */, 0x99727D26F494F7ABL /* 371 */, + 0xA3E063A2956B3E03L /* 372 */, 0x9D4A8B9A4AA09C30L /* 373 */, + 0x3F6AB7D500090FB4L /* 374 */, 0x9CC0F2A057268AC0L /* 375 */, + 0x3DEE9D2DEDBF42D1L /* 376 */, 0x330F49C87960A972L /* 377 */, + 0xC6B2720287421B41L /* 378 */, 0x0AC59EC07C00369CL /* 379 */, + 0xEF4EAC49CB353425L /* 380 */, 0xF450244EEF0129D8L /* 381 */, + 0x8ACC46E5CAF4DEB6L /* 382 */, 0x2FFEAB63989263F7L /* 383 */, + 0x8F7CB9FE5D7A4578L /* 384 */, 0x5BD8F7644E634635L /* 385 */, + 0x427A7315BF2DC900L /* 386 */, 0x17D0C4AA2125261CL /* 387 */, + 0x3992486C93518E50L /* 388 */, 0xB4CBFEE0A2D7D4C3L /* 389 */, + 0x7C75D6202C5DDD8DL /* 390 */, 0xDBC295D8E35B6C61L /* 391 */, + 0x60B369D302032B19L /* 392 */, 0xCE42685FDCE44132L /* 393 */, + 0x06F3DDB9DDF65610L /* 394 */, 0x8EA4D21DB5E148F0L /* 395 */, + 0x20B0FCE62FCD496FL /* 396 */, 0x2C1B912358B0EE31L /* 397 */, + 0xB28317B818F5A308L /* 398 */, 0xA89C1E189CA6D2CFL /* 399 */, + 0x0C6B18576AAADBC8L /* 400 */, 0xB65DEAA91299FAE3L /* 401 */, + 0xFB2B794B7F1027E7L /* 402 */, 0x04E4317F443B5BEBL /* 403 */, + 0x4B852D325939D0A6L /* 404 */, 0xD5AE6BEEFB207FFCL /* 405 */, + 0x309682B281C7D374L /* 406 */, 0xBAE309A194C3B475L /* 407 */, + 0x8CC3F97B13B49F05L /* 408 */, 0x98A9422FF8293967L /* 409 */, + 0x244B16B01076FF7CL /* 410 */, 0xF8BF571C663D67EEL /* 411 */, + 0x1F0D6758EEE30DA1L /* 412 */, 0xC9B611D97ADEB9B7L /* 413 */, + 0xB7AFD5887B6C57A2L /* 414 */, 0x6290AE846B984FE1L /* 415 */, + 0x94DF4CDEACC1A5FDL /* 416 */, 0x058A5BD1C5483AFFL /* 417 */, + 0x63166CC142BA3C37L /* 418 */, 0x8DB8526EB2F76F40L /* 419 */, + 0xE10880036F0D6D4EL /* 420 */, 0x9E0523C9971D311DL /* 421 */, + 0x45EC2824CC7CD691L /* 422 */, 0x575B8359E62382C9L /* 423 */, + 0xFA9E400DC4889995L /* 424 */, 0xD1823ECB45721568L /* 425 */, + 0xDAFD983B8206082FL /* 426 */, 0xAA7D29082386A8CBL /* 427 */, + 0x269FCD4403B87588L /* 428 */, 0x1B91F5F728BDD1E0L /* 429 */, + 0xE4669F39040201F6L /* 430 */, 0x7A1D7C218CF04ADEL /* 431 */, + 0x65623C29D79CE5CEL /* 432 */, 0x2368449096C00BB1L /* 433 */, + 0xAB9BF1879DA503BAL /* 434 */, 0xBC23ECB1A458058EL /* 435 */, + 0x9A58DF01BB401ECCL /* 436 */, 0xA070E868A85F143DL /* 437 */, + 0x4FF188307DF2239EL /* 438 */, 0x14D565B41A641183L /* 439 */, + 0xEE13337452701602L /* 440 */, 0x950E3DCF3F285E09L /* 441 */, + 0x59930254B9C80953L /* 442 */, 0x3BF299408930DA6DL /* 443 */, + 0xA955943F53691387L /* 444 */, 0xA15EDECAA9CB8784L /* 445 */, + 0x29142127352BE9A0L /* 446 */, 0x76F0371FFF4E7AFBL /* 447 */, + 0x0239F450274F2228L /* 448 */, 0xBB073AF01D5E868BL /* 449 */, + 0xBFC80571C10E96C1L /* 450 */, 0xD267088568222E23L /* 451 */, + 0x9671A3D48E80B5B0L /* 452 */, 0x55B5D38AE193BB81L /* 453 */, + 0x693AE2D0A18B04B8L /* 454 */, 0x5C48B4ECADD5335FL /* 455 */, + 0xFD743B194916A1CAL /* 456 */, 0x2577018134BE98C4L /* 457 */, + 0xE77987E83C54A4ADL /* 458 */, 0x28E11014DA33E1B9L /* 459 */, + 0x270CC59E226AA213L /* 460 */, 0x71495F756D1A5F60L /* 461 */, + 0x9BE853FB60AFEF77L /* 462 */, 0xADC786A7F7443DBFL /* 463 */, + 0x0904456173B29A82L /* 464 */, 0x58BC7A66C232BD5EL /* 465 */, + 0xF306558C673AC8B2L /* 466 */, 0x41F639C6B6C9772AL /* 467 */, + 0x216DEFE99FDA35DAL /* 468 */, 0x11640CC71C7BE615L /* 469 */, + 0x93C43694565C5527L /* 470 */, 0xEA038E6246777839L /* 471 */, + 0xF9ABF3CE5A3E2469L /* 472 */, 0x741E768D0FD312D2L /* 473 */, + 0x0144B883CED652C6L /* 474 */, 0xC20B5A5BA33F8552L /* 475 */, + 0x1AE69633C3435A9DL /* 476 */, 0x97A28CA4088CFDECL /* 477 */, + 0x8824A43C1E96F420L /* 478 */, 0x37612FA66EEEA746L /* 479 */, + 0x6B4CB165F9CF0E5AL /* 480 */, 0x43AA1C06A0ABFB4AL /* 481 */, + 0x7F4DC26FF162796BL /* 482 */, 0x6CBACC8E54ED9B0FL /* 483 */, + 0xA6B7FFEFD2BB253EL /* 484 */, 0x2E25BC95B0A29D4FL /* 485 */, + 0x86D6A58BDEF1388CL /* 486 */, 0xDED74AC576B6F054L /* 487 */, + 0x8030BDBC2B45805DL /* 488 */, 0x3C81AF70E94D9289L /* 489 */, + 0x3EFF6DDA9E3100DBL /* 490 */, 0xB38DC39FDFCC8847L /* 491 */, + 0x123885528D17B87EL /* 492 */, 0xF2DA0ED240B1B642L /* 493 */, + 0x44CEFADCD54BF9A9L /* 494 */, 0x1312200E433C7EE6L /* 495 */, + 0x9FFCC84F3A78C748L /* 496 */, 0xF0CD1F72248576BBL /* 497 */, + 0xEC6974053638CFE4L /* 498 */, 0x2BA7B67C0CEC4E4CL /* 499 */, + 0xAC2F4DF3E5CE32EDL /* 500 */, 0xCB33D14326EA4C11L /* 501 */, + 0xA4E9044CC77E58BCL /* 502 */, 0x5F513293D934FCEFL /* 503 */, + 0x5DC9645506E55444L /* 504 */, 0x50DE418F317DE40AL /* 505 */, + 0x388CB31A69DDE259L /* 506 */, 0x2DB4A83455820A86L /* 507 */, + 0x9010A91E84711AE9L /* 508 */, 0x4DF7F0B7B1498371L /* 509 */, + 0xD62A2EABC0977179L /* 510 */, 0x22FAC097AA8D5C0EL /* 511 */, + }; + + private static final long[] t3 = { + 0xF49FCC2FF1DAF39BL /* 512 */, 0x487FD5C66FF29281L /* 513 */, + 0xE8A30667FCDCA83FL /* 514 */, 0x2C9B4BE3D2FCCE63L /* 515 */, + 0xDA3FF74B93FBBBC2L /* 516 */, 0x2FA165D2FE70BA66L /* 517 */, + 0xA103E279970E93D4L /* 518 */, 0xBECDEC77B0E45E71L /* 519 */, + 0xCFB41E723985E497L /* 520 */, 0xB70AAA025EF75017L /* 521 */, + 0xD42309F03840B8E0L /* 522 */, 0x8EFC1AD035898579L /* 523 */, + 0x96C6920BE2B2ABC5L /* 524 */, 0x66AF4163375A9172L /* 525 */, + 0x2174ABDCCA7127FBL /* 526 */, 0xB33CCEA64A72FF41L /* 527 */, + 0xF04A4933083066A5L /* 528 */, 0x8D970ACDD7289AF5L /* 529 */, + 0x8F96E8E031C8C25EL /* 530 */, 0xF3FEC02276875D47L /* 531 */, + 0xEC7BF310056190DDL /* 532 */, 0xF5ADB0AEBB0F1491L /* 533 */, + 0x9B50F8850FD58892L /* 534 */, 0x4975488358B74DE8L /* 535 */, + 0xA3354FF691531C61L /* 536 */, 0x0702BBE481D2C6EEL /* 537 */, + 0x89FB24057DEDED98L /* 538 */, 0xAC3075138596E902L /* 539 */, + 0x1D2D3580172772EDL /* 540 */, 0xEB738FC28E6BC30DL /* 541 */, + 0x5854EF8F63044326L /* 542 */, 0x9E5C52325ADD3BBEL /* 543 */, + 0x90AA53CF325C4623L /* 544 */, 0xC1D24D51349DD067L /* 545 */, + 0x2051CFEEA69EA624L /* 546 */, 0x13220F0A862E7E4FL /* 547 */, + 0xCE39399404E04864L /* 548 */, 0xD9C42CA47086FCB7L /* 549 */, + 0x685AD2238A03E7CCL /* 550 */, 0x066484B2AB2FF1DBL /* 551 */, + 0xFE9D5D70EFBF79ECL /* 552 */, 0x5B13B9DD9C481854L /* 553 */, + 0x15F0D475ED1509ADL /* 554 */, 0x0BEBCD060EC79851L /* 555 */, + 0xD58C6791183AB7F8L /* 556 */, 0xD1187C5052F3EEE4L /* 557 */, + 0xC95D1192E54E82FFL /* 558 */, 0x86EEA14CB9AC6CA2L /* 559 */, + 0x3485BEB153677D5DL /* 560 */, 0xDD191D781F8C492AL /* 561 */, + 0xF60866BAA784EBF9L /* 562 */, 0x518F643BA2D08C74L /* 563 */, + 0x8852E956E1087C22L /* 564 */, 0xA768CB8DC410AE8DL /* 565 */, + 0x38047726BFEC8E1AL /* 566 */, 0xA67738B4CD3B45AAL /* 567 */, + 0xAD16691CEC0DDE19L /* 568 */, 0xC6D4319380462E07L /* 569 */, + 0xC5A5876D0BA61938L /* 570 */, 0x16B9FA1FA58FD840L /* 571 */, + 0x188AB1173CA74F18L /* 572 */, 0xABDA2F98C99C021FL /* 573 */, + 0x3E0580AB134AE816L /* 574 */, 0x5F3B05B773645ABBL /* 575 */, + 0x2501A2BE5575F2F6L /* 576 */, 0x1B2F74004E7E8BA9L /* 577 */, + 0x1CD7580371E8D953L /* 578 */, 0x7F6ED89562764E30L /* 579 */, + 0xB15926FF596F003DL /* 580 */, 0x9F65293DA8C5D6B9L /* 581 */, + 0x6ECEF04DD690F84CL /* 582 */, 0x4782275FFF33AF88L /* 583 */, + 0xE41433083F820801L /* 584 */, 0xFD0DFE409A1AF9B5L /* 585 */, + 0x4325A3342CDB396BL /* 586 */, 0x8AE77E62B301B252L /* 587 */, + 0xC36F9E9F6655615AL /* 588 */, 0x85455A2D92D32C09L /* 589 */, + 0xF2C7DEA949477485L /* 590 */, 0x63CFB4C133A39EBAL /* 591 */, + 0x83B040CC6EBC5462L /* 592 */, 0x3B9454C8FDB326B0L /* 593 */, + 0x56F56A9E87FFD78CL /* 594 */, 0x2DC2940D99F42BC6L /* 595 */, + 0x98F7DF096B096E2DL /* 596 */, 0x19A6E01E3AD852BFL /* 597 */, + 0x42A99CCBDBD4B40BL /* 598 */, 0xA59998AF45E9C559L /* 599 */, + 0x366295E807D93186L /* 600 */, 0x6B48181BFAA1F773L /* 601 */, + 0x1FEC57E2157A0A1DL /* 602 */, 0x4667446AF6201AD5L /* 603 */, + 0xE615EBCACFB0F075L /* 604 */, 0xB8F31F4F68290778L /* 605 */, + 0x22713ED6CE22D11EL /* 606 */, 0x3057C1A72EC3C93BL /* 607 */, + 0xCB46ACC37C3F1F2FL /* 608 */, 0xDBB893FD02AAF50EL /* 609 */, + 0x331FD92E600B9FCFL /* 610 */, 0xA498F96148EA3AD6L /* 611 */, + 0xA8D8426E8B6A83EAL /* 612 */, 0xA089B274B7735CDCL /* 613 */, + 0x87F6B3731E524A11L /* 614 */, 0x118808E5CBC96749L /* 615 */, + 0x9906E4C7B19BD394L /* 616 */, 0xAFED7F7E9B24A20CL /* 617 */, + 0x6509EADEEB3644A7L /* 618 */, 0x6C1EF1D3E8EF0EDEL /* 619 */, + 0xB9C97D43E9798FB4L /* 620 */, 0xA2F2D784740C28A3L /* 621 */, + 0x7B8496476197566FL /* 622 */, 0x7A5BE3E6B65F069DL /* 623 */, + 0xF96330ED78BE6F10L /* 624 */, 0xEEE60DE77A076A15L /* 625 */, + 0x2B4BEE4AA08B9BD0L /* 626 */, 0x6A56A63EC7B8894EL /* 627 */, + 0x02121359BA34FEF4L /* 628 */, 0x4CBF99F8283703FCL /* 629 */, + 0x398071350CAF30C8L /* 630 */, 0xD0A77A89F017687AL /* 631 */, + 0xF1C1A9EB9E423569L /* 632 */, 0x8C7976282DEE8199L /* 633 */, + 0x5D1737A5DD1F7ABDL /* 634 */, 0x4F53433C09A9FA80L /* 635 */, + 0xFA8B0C53DF7CA1D9L /* 636 */, 0x3FD9DCBC886CCB77L /* 637 */, + 0xC040917CA91B4720L /* 638 */, 0x7DD00142F9D1DCDFL /* 639 */, + 0x8476FC1D4F387B58L /* 640 */, 0x23F8E7C5F3316503L /* 641 */, + 0x032A2244E7E37339L /* 642 */, 0x5C87A5D750F5A74BL /* 643 */, + 0x082B4CC43698992EL /* 644 */, 0xDF917BECB858F63CL /* 645 */, + 0x3270B8FC5BF86DDAL /* 646 */, 0x10AE72BB29B5DD76L /* 647 */, + 0x576AC94E7700362BL /* 648 */, 0x1AD112DAC61EFB8FL /* 649 */, + 0x691BC30EC5FAA427L /* 650 */, 0xFF246311CC327143L /* 651 */, + 0x3142368E30E53206L /* 652 */, 0x71380E31E02CA396L /* 653 */, + 0x958D5C960AAD76F1L /* 654 */, 0xF8D6F430C16DA536L /* 655 */, + 0xC8FFD13F1BE7E1D2L /* 656 */, 0x7578AE66004DDBE1L /* 657 */, + 0x05833F01067BE646L /* 658 */, 0xBB34B5AD3BFE586DL /* 659 */, + 0x095F34C9A12B97F0L /* 660 */, 0x247AB64525D60CA8L /* 661 */, + 0xDCDBC6F3017477D1L /* 662 */, 0x4A2E14D4DECAD24DL /* 663 */, + 0xBDB5E6D9BE0A1EEBL /* 664 */, 0x2A7E70F7794301ABL /* 665 */, + 0xDEF42D8A270540FDL /* 666 */, 0x01078EC0A34C22C1L /* 667 */, + 0xE5DE511AF4C16387L /* 668 */, 0x7EBB3A52BD9A330AL /* 669 */, + 0x77697857AA7D6435L /* 670 */, 0x004E831603AE4C32L /* 671 */, + 0xE7A21020AD78E312L /* 672 */, 0x9D41A70C6AB420F2L /* 673 */, + 0x28E06C18EA1141E6L /* 674 */, 0xD2B28CBD984F6B28L /* 675 */, + 0x26B75F6C446E9D83L /* 676 */, 0xBA47568C4D418D7FL /* 677 */, + 0xD80BADBFE6183D8EL /* 678 */, 0x0E206D7F5F166044L /* 679 */, + 0xE258A43911CBCA3EL /* 680 */, 0x723A1746B21DC0BCL /* 681 */, + 0xC7CAA854F5D7CDD3L /* 682 */, 0x7CAC32883D261D9CL /* 683 */, + 0x7690C26423BA942CL /* 684 */, 0x17E55524478042B8L /* 685 */, + 0xE0BE477656A2389FL /* 686 */, 0x4D289B5E67AB2DA0L /* 687 */, + 0x44862B9C8FBBFD31L /* 688 */, 0xB47CC8049D141365L /* 689 */, + 0x822C1B362B91C793L /* 690 */, 0x4EB14655FB13DFD8L /* 691 */, + 0x1ECBBA0714E2A97BL /* 692 */, 0x6143459D5CDE5F14L /* 693 */, + 0x53A8FBF1D5F0AC89L /* 694 */, 0x97EA04D81C5E5B00L /* 695 */, + 0x622181A8D4FDB3F3L /* 696 */, 0xE9BCD341572A1208L /* 697 */, + 0x1411258643CCE58AL /* 698 */, 0x9144C5FEA4C6E0A4L /* 699 */, + 0x0D33D06565CF620FL /* 700 */, 0x54A48D489F219CA1L /* 701 */, + 0xC43E5EAC6D63C821L /* 702 */, 0xA9728B3A72770DAFL /* 703 */, + 0xD7934E7B20DF87EFL /* 704 */, 0xE35503B61A3E86E5L /* 705 */, + 0xCAE321FBC819D504L /* 706 */, 0x129A50B3AC60BFA6L /* 707 */, + 0xCD5E68EA7E9FB6C3L /* 708 */, 0xB01C90199483B1C7L /* 709 */, + 0x3DE93CD5C295376CL /* 710 */, 0xAED52EDF2AB9AD13L /* 711 */, + 0x2E60F512C0A07884L /* 712 */, 0xBC3D86A3E36210C9L /* 713 */, + 0x35269D9B163951CEL /* 714 */, 0x0C7D6E2AD0CDB5FAL /* 715 */, + 0x59E86297D87F5733L /* 716 */, 0x298EF221898DB0E7L /* 717 */, + 0x55000029D1A5AA7EL /* 718 */, 0x8BC08AE1B5061B45L /* 719 */, + 0xC2C31C2B6C92703AL /* 720 */, 0x94CC596BAF25EF42L /* 721 */, + 0x0A1D73DB22540456L /* 722 */, 0x04B6A0F9D9C4179AL /* 723 */, + 0xEFFDAFA2AE3D3C60L /* 724 */, 0xF7C8075BB49496C4L /* 725 */, + 0x9CC5C7141D1CD4E3L /* 726 */, 0x78BD1638218E5534L /* 727 */, + 0xB2F11568F850246AL /* 728 */, 0xEDFABCFA9502BC29L /* 729 */, + 0x796CE5F2DA23051BL /* 730 */, 0xAAE128B0DC93537CL /* 731 */, + 0x3A493DA0EE4B29AEL /* 732 */, 0xB5DF6B2C416895D7L /* 733 */, + 0xFCABBD25122D7F37L /* 734 */, 0x70810B58105DC4B1L /* 735 */, + 0xE10FDD37F7882A90L /* 736 */, 0x524DCAB5518A3F5CL /* 737 */, + 0x3C9E85878451255BL /* 738 */, 0x4029828119BD34E2L /* 739 */, + 0x74A05B6F5D3CECCBL /* 740 */, 0xB610021542E13ECAL /* 741 */, + 0x0FF979D12F59E2ACL /* 742 */, 0x6037DA27E4F9CC50L /* 743 */, + 0x5E92975A0DF1847DL /* 744 */, 0xD66DE190D3E623FEL /* 745 */, + 0x5032D6B87B568048L /* 746 */, 0x9A36B7CE8235216EL /* 747 */, + 0x80272A7A24F64B4AL /* 748 */, 0x93EFED8B8C6916F7L /* 749 */, + 0x37DDBFF44CCE1555L /* 750 */, 0x4B95DB5D4B99BD25L /* 751 */, + 0x92D3FDA169812FC0L /* 752 */, 0xFB1A4A9A90660BB6L /* 753 */, + 0x730C196946A4B9B2L /* 754 */, 0x81E289AA7F49DA68L /* 755 */, + 0x64669A0F83B1A05FL /* 756 */, 0x27B3FF7D9644F48BL /* 757 */, + 0xCC6B615C8DB675B3L /* 758 */, 0x674F20B9BCEBBE95L /* 759 */, + 0x6F31238275655982L /* 760 */, 0x5AE488713E45CF05L /* 761 */, + 0xBF619F9954C21157L /* 762 */, 0xEABAC46040A8EAE9L /* 763 */, + 0x454C6FE9F2C0C1CDL /* 764 */, 0x419CF6496412691CL /* 765 */, + 0xD3DC3BEF265B0F70L /* 766 */, 0x6D0E60F5C3578A9EL /* 767 */, + }; + + private static final long[] t4 = { + 0x5B0E608526323C55L /* 768 */, 0x1A46C1A9FA1B59F5L /* 769 */, + 0xA9E245A17C4C8FFAL /* 770 */, 0x65CA5159DB2955D7L /* 771 */, + 0x05DB0A76CE35AFC2L /* 772 */, 0x81EAC77EA9113D45L /* 773 */, + 0x528EF88AB6AC0A0DL /* 774 */, 0xA09EA253597BE3FFL /* 775 */, + 0x430DDFB3AC48CD56L /* 776 */, 0xC4B3A67AF45CE46FL /* 777 */, + 0x4ECECFD8FBE2D05EL /* 778 */, 0x3EF56F10B39935F0L /* 779 */, + 0x0B22D6829CD619C6L /* 780 */, 0x17FD460A74DF2069L /* 781 */, + 0x6CF8CC8E8510ED40L /* 782 */, 0xD6C824BF3A6ECAA7L /* 783 */, + 0x61243D581A817049L /* 784 */, 0x048BACB6BBC163A2L /* 785 */, + 0xD9A38AC27D44CC32L /* 786 */, 0x7FDDFF5BAAF410ABL /* 787 */, + 0xAD6D495AA804824BL /* 788 */, 0xE1A6A74F2D8C9F94L /* 789 */, + 0xD4F7851235DEE8E3L /* 790 */, 0xFD4B7F886540D893L /* 791 */, + 0x247C20042AA4BFDAL /* 792 */, 0x096EA1C517D1327CL /* 793 */, + 0xD56966B4361A6685L /* 794 */, 0x277DA5C31221057DL /* 795 */, + 0x94D59893A43ACFF7L /* 796 */, 0x64F0C51CCDC02281L /* 797 */, + 0x3D33BCC4FF6189DBL /* 798 */, 0xE005CB184CE66AF1L /* 799 */, + 0xFF5CCD1D1DB99BEAL /* 800 */, 0xB0B854A7FE42980FL /* 801 */, + 0x7BD46A6A718D4B9FL /* 802 */, 0xD10FA8CC22A5FD8CL /* 803 */, + 0xD31484952BE4BD31L /* 804 */, 0xC7FA975FCB243847L /* 805 */, + 0x4886ED1E5846C407L /* 806 */, 0x28CDDB791EB70B04L /* 807 */, + 0xC2B00BE2F573417FL /* 808 */, 0x5C9590452180F877L /* 809 */, + 0x7A6BDDFFF370EB00L /* 810 */, 0xCE509E38D6D9D6A4L /* 811 */, + 0xEBEB0F00647FA702L /* 812 */, 0x1DCC06CF76606F06L /* 813 */, + 0xE4D9F28BA286FF0AL /* 814 */, 0xD85A305DC918C262L /* 815 */, + 0x475B1D8732225F54L /* 816 */, 0x2D4FB51668CCB5FEL /* 817 */, + 0xA679B9D9D72BBA20L /* 818 */, 0x53841C0D912D43A5L /* 819 */, + 0x3B7EAA48BF12A4E8L /* 820 */, 0x781E0E47F22F1DDFL /* 821 */, + 0xEFF20CE60AB50973L /* 822 */, 0x20D261D19DFFB742L /* 823 */, + 0x16A12B03062A2E39L /* 824 */, 0x1960EB2239650495L /* 825 */, + 0x251C16FED50EB8B8L /* 826 */, 0x9AC0C330F826016EL /* 827 */, + 0xED152665953E7671L /* 828 */, 0x02D63194A6369570L /* 829 */, + 0x5074F08394B1C987L /* 830 */, 0x70BA598C90B25CE1L /* 831 */, + 0x794A15810B9742F6L /* 832 */, 0x0D5925E9FCAF8C6CL /* 833 */, + 0x3067716CD868744EL /* 834 */, 0x910AB077E8D7731BL /* 835 */, + 0x6A61BBDB5AC42F61L /* 836 */, 0x93513EFBF0851567L /* 837 */, + 0xF494724B9E83E9D5L /* 838 */, 0xE887E1985C09648DL /* 839 */, + 0x34B1D3C675370CFDL /* 840 */, 0xDC35E433BC0D255DL /* 841 */, + 0xD0AAB84234131BE0L /* 842 */, 0x08042A50B48B7EAFL /* 843 */, + 0x9997C4EE44A3AB35L /* 844 */, 0x829A7B49201799D0L /* 845 */, + 0x263B8307B7C54441L /* 846 */, 0x752F95F4FD6A6CA6L /* 847 */, + 0x927217402C08C6E5L /* 848 */, 0x2A8AB754A795D9EEL /* 849 */, + 0xA442F7552F72943DL /* 850 */, 0x2C31334E19781208L /* 851 */, + 0x4FA98D7CEAEE6291L /* 852 */, 0x55C3862F665DB309L /* 853 */, + 0xBD0610175D53B1F3L /* 854 */, 0x46FE6CB840413F27L /* 855 */, + 0x3FE03792DF0CFA59L /* 856 */, 0xCFE700372EB85E8FL /* 857 */, + 0xA7BE29E7ADBCE118L /* 858 */, 0xE544EE5CDE8431DDL /* 859 */, + 0x8A781B1B41F1873EL /* 860 */, 0xA5C94C78A0D2F0E7L /* 861 */, + 0x39412E2877B60728L /* 862 */, 0xA1265EF3AFC9A62CL /* 863 */, + 0xBCC2770C6A2506C5L /* 864 */, 0x3AB66DD5DCE1CE12L /* 865 */, + 0xE65499D04A675B37L /* 866 */, 0x7D8F523481BFD216L /* 867 */, + 0x0F6F64FCEC15F389L /* 868 */, 0x74EFBE618B5B13C8L /* 869 */, + 0xACDC82B714273E1DL /* 870 */, 0xDD40BFE003199D17L /* 871 */, + 0x37E99257E7E061F8L /* 872 */, 0xFA52626904775AAAL /* 873 */, + 0x8BBBF63A463D56F9L /* 874 */, 0xF0013F1543A26E64L /* 875 */, + 0xA8307E9F879EC898L /* 876 */, 0xCC4C27A4150177CCL /* 877 */, + 0x1B432F2CCA1D3348L /* 878 */, 0xDE1D1F8F9F6FA013L /* 879 */, + 0x606602A047A7DDD6L /* 880 */, 0xD237AB64CC1CB2C7L /* 881 */, + 0x9B938E7225FCD1D3L /* 882 */, 0xEC4E03708E0FF476L /* 883 */, + 0xFEB2FBDA3D03C12DL /* 884 */, 0xAE0BCED2EE43889AL /* 885 */, + 0x22CB8923EBFB4F43L /* 886 */, 0x69360D013CF7396DL /* 887 */, + 0x855E3602D2D4E022L /* 888 */, 0x073805BAD01F784CL /* 889 */, + 0x33E17A133852F546L /* 890 */, 0xDF4874058AC7B638L /* 891 */, + 0xBA92B29C678AA14AL /* 892 */, 0x0CE89FC76CFAADCDL /* 893 */, + 0x5F9D4E0908339E34L /* 894 */, 0xF1AFE9291F5923B9L /* 895 */, + 0x6E3480F60F4A265FL /* 896 */, 0xEEBF3A2AB29B841CL /* 897 */, + 0xE21938A88F91B4ADL /* 898 */, 0x57DFEFF845C6D3C3L /* 899 */, + 0x2F006B0BF62CAAF2L /* 900 */, 0x62F479EF6F75EE78L /* 901 */, + 0x11A55AD41C8916A9L /* 902 */, 0xF229D29084FED453L /* 903 */, + 0x42F1C27B16B000E6L /* 904 */, 0x2B1F76749823C074L /* 905 */, + 0x4B76ECA3C2745360L /* 906 */, 0x8C98F463B91691BDL /* 907 */, + 0x14BCC93CF1ADE66AL /* 908 */, 0x8885213E6D458397L /* 909 */, + 0x8E177DF0274D4711L /* 910 */, 0xB49B73B5503F2951L /* 911 */, + 0x10168168C3F96B6BL /* 912 */, 0x0E3D963B63CAB0AEL /* 913 */, + 0x8DFC4B5655A1DB14L /* 914 */, 0xF789F1356E14DE5CL /* 915 */, + 0x683E68AF4E51DAC1L /* 916 */, 0xC9A84F9D8D4B0FD9L /* 917 */, + 0x3691E03F52A0F9D1L /* 918 */, 0x5ED86E46E1878E80L /* 919 */, + 0x3C711A0E99D07150L /* 920 */, 0x5A0865B20C4E9310L /* 921 */, + 0x56FBFC1FE4F0682EL /* 922 */, 0xEA8D5DE3105EDF9BL /* 923 */, + 0x71ABFDB12379187AL /* 924 */, 0x2EB99DE1BEE77B9CL /* 925 */, + 0x21ECC0EA33CF4523L /* 926 */, 0x59A4D7521805C7A1L /* 927 */, + 0x3896F5EB56AE7C72L /* 928 */, 0xAA638F3DB18F75DCL /* 929 */, + 0x9F39358DABE9808EL /* 930 */, 0xB7DEFA91C00B72ACL /* 931 */, + 0x6B5541FD62492D92L /* 932 */, 0x6DC6DEE8F92E4D5BL /* 933 */, + 0x353F57ABC4BEEA7EL /* 934 */, 0x735769D6DA5690CEL /* 935 */, + 0x0A234AA642391484L /* 936 */, 0xF6F9508028F80D9DL /* 937 */, + 0xB8E319A27AB3F215L /* 938 */, 0x31AD9C1151341A4DL /* 939 */, + 0x773C22A57BEF5805L /* 940 */, 0x45C7561A07968633L /* 941 */, + 0xF913DA9E249DBE36L /* 942 */, 0xDA652D9B78A64C68L /* 943 */, + 0x4C27A97F3BC334EFL /* 944 */, 0x76621220E66B17F4L /* 945 */, + 0x967743899ACD7D0BL /* 946 */, 0xF3EE5BCAE0ED6782L /* 947 */, + 0x409F753600C879FCL /* 948 */, 0x06D09A39B5926DB6L /* 949 */, + 0x6F83AEB0317AC588L /* 950 */, 0x01E6CA4A86381F21L /* 951 */, + 0x66FF3462D19F3025L /* 952 */, 0x72207C24DDFD3BFBL /* 953 */, + 0x4AF6B6D3E2ECE2EBL /* 954 */, 0x9C994DBEC7EA08DEL /* 955 */, + 0x49ACE597B09A8BC4L /* 956 */, 0xB38C4766CF0797BAL /* 957 */, + 0x131B9373C57C2A75L /* 958 */, 0xB1822CCE61931E58L /* 959 */, + 0x9D7555B909BA1C0CL /* 960 */, 0x127FAFDD937D11D2L /* 961 */, + 0x29DA3BADC66D92E4L /* 962 */, 0xA2C1D57154C2ECBCL /* 963 */, + 0x58C5134D82F6FE24L /* 964 */, 0x1C3AE3515B62274FL /* 965 */, + 0xE907C82E01CB8126L /* 966 */, 0xF8ED091913E37FCBL /* 967 */, + 0x3249D8F9C80046C9L /* 968 */, 0x80CF9BEDE388FB63L /* 969 */, + 0x1881539A116CF19EL /* 970 */, 0x5103F3F76BD52457L /* 971 */, + 0x15B7E6F5AE47F7A8L /* 972 */, 0xDBD7C6DED47E9CCFL /* 973 */, + 0x44E55C410228BB1AL /* 974 */, 0xB647D4255EDB4E99L /* 975 */, + 0x5D11882BB8AAFC30L /* 976 */, 0xF5098BBB29D3212AL /* 977 */, + 0x8FB5EA14E90296B3L /* 978 */, 0x677B942157DD025AL /* 979 */, + 0xFB58E7C0A390ACB5L /* 980 */, 0x89D3674C83BD4A01L /* 981 */, + 0x9E2DA4DF4BF3B93BL /* 982 */, 0xFCC41E328CAB4829L /* 983 */, + 0x03F38C96BA582C52L /* 984 */, 0xCAD1BDBD7FD85DB2L /* 985 */, + 0xBBB442C16082AE83L /* 986 */, 0xB95FE86BA5DA9AB0L /* 987 */, + 0xB22E04673771A93FL /* 988 */, 0x845358C9493152D8L /* 989 */, + 0xBE2A488697B4541EL /* 990 */, 0x95A2DC2DD38E6966L /* 991 */, + 0xC02C11AC923C852BL /* 992 */, 0x2388B1990DF2A87BL /* 993 */, + 0x7C8008FA1B4F37BEL /* 994 */, 0x1F70D0C84D54E503L /* 995 */, + 0x5490ADEC7ECE57D4L /* 996 */, 0x002B3C27D9063A3AL /* 997 */, + 0x7EAEA3848030A2BFL /* 998 */, 0xC602326DED2003C0L /* 999 */, + 0x83A7287D69A94086L /* 1000 */, 0xC57A5FCB30F57A8AL /* 1001 */, + 0xB56844E479EBE779L /* 1002 */, 0xA373B40F05DCBCE9L /* 1003 */, + 0xD71A786E88570EE2L /* 1004 */, 0x879CBACDBDE8F6A0L /* 1005 */, + 0x976AD1BCC164A32FL /* 1006 */, 0xAB21E25E9666D78BL /* 1007 */, + 0x901063AAE5E5C33CL /* 1008 */, 0x9818B34448698D90L /* 1009 */, + 0xE36487AE3E1E8ABBL /* 1010 */, 0xAFBDF931893BDCB4L /* 1011 */, + 0x6345A0DC5FBBD519L /* 1012 */, 0x8628FE269B9465CAL /* 1013 */, + 0x1E5D01603F9C51ECL /* 1014 */, 0x4DE44006A15049B7L /* 1015 */, + 0xBF6C70E5F776CBB1L /* 1016 */, 0x411218F2EF552BEDL /* 1017 */, + 0xCB0C0708705A36A3L /* 1018 */, 0xE74D14754F986044L /* 1019 */, + 0xCD56D9430EA8280EL /* 1020 */, 0xC12591D7535F5065L /* 1021 */, + 0xC83223F1720AEF96L /* 1022 */, 0xC3A0396F7363A51FL /* 1023 */ + }; + + private static final int DIGEST_LENGTH = 24; + + // + // registers + // + private long a, b, c; + private long byteCount; + + // + // buffers + // + private byte[] buf = new byte[8]; + private int bOff = 0; + + private long[] x = new long[8]; + private int xOff = 0; + + /** + * Standard constructor + */ + public TigerDigest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public TigerDigest(TigerDigest t) + { + this.reset(t); + } + + public String getAlgorithmName() + { + return "Tiger"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + private void processWord( + byte[] b, + int off) + { + x[xOff++] = ((long)(b[off + 7] & 0xff) << 56) + | ((long)(b[off + 6] & 0xff) << 48) + | ((long)(b[off + 5] & 0xff) << 40) + | ((long)(b[off + 4] & 0xff) << 32) + | ((long)(b[off + 3] & 0xff) << 24) + | ((long)(b[off + 2] & 0xff) << 16) + | ((long)(b[off + 1] & 0xff) << 8) + | ((b[off + 0] & 0xff)); + + if (xOff == x.length) + { + processBlock(); + } + + bOff = 0; + } + + public void update( + byte in) + { + buf[bOff++] = in; + + if (bOff == buf.length) + { + processWord(buf, 0); + } + + byteCount++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((bOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > 8) + { + processWord(in, inOff); + + inOff += 8; + len -= 8; + byteCount += 8; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + private void roundABC( + long x, + long mul) + { + c ^= x ; + a -= t1[(int)c & 0xff] ^ t2[(int)(c >> 16) & 0xff] + ^ t3[(int)(c >> 32) & 0xff] ^ t4[(int)(c >> 48) & 0xff]; + b += t4[(int)(c >> 8) & 0xff] ^ t3[(int)(c >> 24) & 0xff] + ^ t2[(int)(c >> 40) & 0xff] ^ t1[(int)(c >> 56) & 0xff]; + b *= mul; + } + + private void roundBCA( + long x, + long mul) + { + a ^= x ; + b -= t1[(int)a & 0xff] ^ t2[(int)(a >> 16) & 0xff] + ^ t3[(int)(a >> 32) & 0xff] ^ t4[(int)(a >> 48) & 0xff]; + c += t4[(int)(a >> 8) & 0xff] ^ t3[(int)(a >> 24) & 0xff] + ^ t2[(int)(a >> 40) & 0xff] ^ t1[(int)(a >> 56) & 0xff]; + c *= mul; + } + + private void roundCAB( + long x, + long mul) + { + b ^= x ; + c -= t1[(int)b & 0xff] ^ t2[(int)(b >> 16) & 0xff] + ^ t3[(int)(b >> 32) & 0xff] ^ t4[(int)(b >> 48) & 0xff]; + a += t4[(int)(b >> 8) & 0xff] ^ t3[(int)(b >> 24) & 0xff] + ^ t2[(int)(b >> 40) & 0xff] ^ t1[(int)(b >> 56) & 0xff]; + a *= mul; + } + + private void keySchedule() + { + x[0] -= x[7] ^ 0xA5A5A5A5A5A5A5A5L; + x[1] ^= x[0]; + x[2] += x[1]; + x[3] -= x[2] ^ ((~x[1]) << 19); + x[4] ^= x[3]; + x[5] += x[4]; + x[6] -= x[5] ^ ((~x[4]) >>> 23); + x[7] ^= x[6]; + x[0] += x[7]; + x[1] -= x[0] ^ ((~x[7]) << 19); + x[2] ^= x[1]; + x[3] += x[2]; + x[4] -= x[3] ^ ((~x[2]) >>> 23); + x[5] ^= x[4]; + x[6] += x[5]; + x[7] -= x[6] ^ 0x0123456789ABCDEFL; + } + + private void processBlock() + { + // + // save abc + // + long aa = a; + long bb = b; + long cc = c; + + // + // rounds and schedule + // + roundABC(x[0], 5); + roundBCA(x[1], 5); + roundCAB(x[2], 5); + roundABC(x[3], 5); + roundBCA(x[4], 5); + roundCAB(x[5], 5); + roundABC(x[6], 5); + roundBCA(x[7], 5); + + keySchedule(); + + roundCAB(x[0], 7); + roundABC(x[1], 7); + roundBCA(x[2], 7); + roundCAB(x[3], 7); + roundABC(x[4], 7); + roundBCA(x[5], 7); + roundCAB(x[6], 7); + roundABC(x[7], 7); + + keySchedule(); + + roundBCA(x[0], 9); + roundCAB(x[1], 9); + roundABC(x[2], 9); + roundBCA(x[3], 9); + roundCAB(x[4], 9); + roundABC(x[5], 9); + roundBCA(x[6], 9); + roundCAB(x[7], 9); + + // + // feed forward + // + a ^= aa; + b -= bb; + c += cc; + + // + // clear the x buffer + // + xOff = 0; + for (int i = 0; i != x.length; i++) + { + x[i] = 0; + } + } + + public void unpackWord( + long r, + byte[] out, + int outOff) + { + out[outOff + 7] = (byte)(r >> 56); + out[outOff + 6] = (byte)(r >> 48); + out[outOff + 5] = (byte)(r >> 40); + out[outOff + 4] = (byte)(r >> 32); + out[outOff + 3] = (byte)(r >> 24); + out[outOff + 2] = (byte)(r >> 16); + out[outOff + 1] = (byte)(r >> 8); + out[outOff] = (byte)r; + } + + private void processLength( + long bitLength) + { + x[7] = bitLength; + } + + private void finish() + { + long bitLength = (byteCount << 3); + + update((byte)0x01); + + while (bOff != 0) + { + update((byte)0); + } + + processLength(bitLength); + + processBlock(); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(a, out, outOff); + unpackWord(b, out, outOff + 8); + unpackWord(c, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + a = 0x0123456789ABCDEFL; + b = 0xFEDCBA9876543210L; + c = 0xF096A5B4C3B2E187L; + + xOff = 0; + for (int i = 0; i != x.length; i++) + { + x[i] = 0; + } + + bOff = 0; + for (int i = 0; i != buf.length; i++) + { + buf[i] = 0; + } + + byteCount = 0; + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + public Memoable copy() + { + return new TigerDigest(this); + } + + public void reset(Memoable other) + { + TigerDigest t = (TigerDigest)other; + + a = t.a; + b = t.b; + c = t.c; + + System.arraycopy(t.x, 0, x, 0, t.x.length); + xOff = t.xOff; + + System.arraycopy(t.buf, 0, buf, 0, t.buf.length); + bOff = t.bOff; + + byteCount = t.byteCount; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/digests/WhirlpoolDigest.java b/core/src/main/java/org/bouncycastle/crypto/digests/WhirlpoolDigest.java new file mode 100644 index 00000000..11e884cd --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/digests/WhirlpoolDigest.java @@ -0,0 +1,409 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Memoable; + + +/** + * Implementation of WhirlpoolDigest, based on Java source published by Barreto + * and Rijmen. + * + */ +public final class WhirlpoolDigest + implements ExtendedDigest, Memoable +{ + private static final int BYTE_LENGTH = 64; + + private static final int DIGEST_LENGTH_BYTES = 512 / 8; + private static final int ROUNDS = 10; + private static final int REDUCTION_POLYNOMIAL = 0x011d; // 2^8 + 2^4 + 2^3 + 2 + 1; + + private static final int[] SBOX = { + 0x18, 0x23, 0xc6, 0xe8, 0x87, 0xb8, 0x01, 0x4f, 0x36, 0xa6, 0xd2, 0xf5, 0x79, 0x6f, 0x91, 0x52, + 0x60, 0xbc, 0x9b, 0x8e, 0xa3, 0x0c, 0x7b, 0x35, 0x1d, 0xe0, 0xd7, 0xc2, 0x2e, 0x4b, 0xfe, 0x57, + 0x15, 0x77, 0x37, 0xe5, 0x9f, 0xf0, 0x4a, 0xda, 0x58, 0xc9, 0x29, 0x0a, 0xb1, 0xa0, 0x6b, 0x85, + 0xbd, 0x5d, 0x10, 0xf4, 0xcb, 0x3e, 0x05, 0x67, 0xe4, 0x27, 0x41, 0x8b, 0xa7, 0x7d, 0x95, 0xd8, + 0xfb, 0xee, 0x7c, 0x66, 0xdd, 0x17, 0x47, 0x9e, 0xca, 0x2d, 0xbf, 0x07, 0xad, 0x5a, 0x83, 0x33, + 0x63, 0x02, 0xaa, 0x71, 0xc8, 0x19, 0x49, 0xd9, 0xf2, 0xe3, 0x5b, 0x88, 0x9a, 0x26, 0x32, 0xb0, + 0xe9, 0x0f, 0xd5, 0x80, 0xbe, 0xcd, 0x34, 0x48, 0xff, 0x7a, 0x90, 0x5f, 0x20, 0x68, 0x1a, 0xae, + 0xb4, 0x54, 0x93, 0x22, 0x64, 0xf1, 0x73, 0x12, 0x40, 0x08, 0xc3, 0xec, 0xdb, 0xa1, 0x8d, 0x3d, + 0x97, 0x00, 0xcf, 0x2b, 0x76, 0x82, 0xd6, 0x1b, 0xb5, 0xaf, 0x6a, 0x50, 0x45, 0xf3, 0x30, 0xef, + 0x3f, 0x55, 0xa2, 0xea, 0x65, 0xba, 0x2f, 0xc0, 0xde, 0x1c, 0xfd, 0x4d, 0x92, 0x75, 0x06, 0x8a, + 0xb2, 0xe6, 0x0e, 0x1f, 0x62, 0xd4, 0xa8, 0x96, 0xf9, 0xc5, 0x25, 0x59, 0x84, 0x72, 0x39, 0x4c, + 0x5e, 0x78, 0x38, 0x8c, 0xd1, 0xa5, 0xe2, 0x61, 0xb3, 0x21, 0x9c, 0x1e, 0x43, 0xc7, 0xfc, 0x04, + 0x51, 0x99, 0x6d, 0x0d, 0xfa, 0xdf, 0x7e, 0x24, 0x3b, 0xab, 0xce, 0x11, 0x8f, 0x4e, 0xb7, 0xeb, + 0x3c, 0x81, 0x94, 0xf7, 0xb9, 0x13, 0x2c, 0xd3, 0xe7, 0x6e, 0xc4, 0x03, 0x56, 0x44, 0x7f, 0xa9, + 0x2a, 0xbb, 0xc1, 0x53, 0xdc, 0x0b, 0x9d, 0x6c, 0x31, 0x74, 0xf6, 0x46, 0xac, 0x89, 0x14, 0xe1, + 0x16, 0x3a, 0x69, 0x09, 0x70, 0xb6, 0xd0, 0xed, 0xcc, 0x42, 0x98, 0xa4, 0x28, 0x5c, 0xf8, 0x86 + }; + + private static final long[] C0 = new long[256]; + private static final long[] C1 = new long[256]; + private static final long[] C2 = new long[256]; + private static final long[] C3 = new long[256]; + private static final long[] C4 = new long[256]; + private static final long[] C5 = new long[256]; + private static final long[] C6 = new long[256]; + private static final long[] C7 = new long[256]; + + private final long[] _rc = new long[ROUNDS + 1]; + + public WhirlpoolDigest() + { + for (int i = 0; i < 256; i++) + { + int v1 = SBOX[i]; + int v2 = maskWithReductionPolynomial(v1 << 1); + int v4 = maskWithReductionPolynomial(v2 << 1); + int v5 = v4 ^ v1; + int v8 = maskWithReductionPolynomial(v4 << 1); + int v9 = v8 ^ v1; + + C0[i] = packIntoLong(v1, v1, v4, v1, v8, v5, v2, v9); + C1[i] = packIntoLong(v9, v1, v1, v4, v1, v8, v5, v2); + C2[i] = packIntoLong(v2, v9, v1, v1, v4, v1, v8, v5); + C3[i] = packIntoLong(v5, v2, v9, v1, v1, v4, v1, v8); + C4[i] = packIntoLong(v8, v5, v2, v9, v1, v1, v4, v1); + C5[i] = packIntoLong(v1, v8, v5, v2, v9, v1, v1, v4); + C6[i] = packIntoLong(v4, v1, v8, v5, v2, v9, v1, v1); + C7[i] = packIntoLong(v1, v4, v1, v8, v5, v2, v9, v1); + + } + + _rc[0] = 0L; + for (int r = 1; r <= ROUNDS; r++) + { + int i = 8 * (r - 1); + _rc[r] = (C0[i ] & 0xff00000000000000L) ^ + (C1[i + 1] & 0x00ff000000000000L) ^ + (C2[i + 2] & 0x0000ff0000000000L) ^ + (C3[i + 3] & 0x000000ff00000000L) ^ + (C4[i + 4] & 0x00000000ff000000L) ^ + (C5[i + 5] & 0x0000000000ff0000L) ^ + (C6[i + 6] & 0x000000000000ff00L) ^ + (C7[i + 7] & 0x00000000000000ffL); + } + + } + + private long packIntoLong(int b7, int b6, int b5, int b4, int b3, int b2, int b1, int b0) + { + return + ((long)b7 << 56) ^ + ((long)b6 << 48) ^ + ((long)b5 << 40) ^ + ((long)b4 << 32) ^ + ((long)b3 << 24) ^ + ((long)b2 << 16) ^ + ((long)b1 << 8) ^ + b0; + } + + /* + * int's are used to prevent sign extension. The values that are really being used are + * actually just 0..255 + */ + private int maskWithReductionPolynomial(int input) + { + int rv = input; + if (rv >= 0x100L) // high bit set + { + rv ^= REDUCTION_POLYNOMIAL; // reduced by the polynomial + } + return rv; + } + + // --------------------------------------------------------------------------------------// + + // -- buffer information -- + private static final int BITCOUNT_ARRAY_SIZE = 32; + private byte[] _buffer = new byte[64]; + private int _bufferPos = 0; + private short[] _bitCount = new short[BITCOUNT_ARRAY_SIZE]; + + // -- internal hash state -- + private long[] _hash = new long[8]; + private long[] _K = new long[8]; // the round key + private long[] _L = new long[8]; + private long[] _block = new long[8]; // mu (buffer) + private long[] _state = new long[8]; // the current "cipher" state + + + + /** + * Copy constructor. This will copy the state of the provided message + * digest. + */ + public WhirlpoolDigest(WhirlpoolDigest originalDigest) + { + reset(originalDigest); + } + + public String getAlgorithmName() + { + return "Whirlpool"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH_BYTES; + } + + public int doFinal(byte[] out, int outOff) + { + // sets out[outOff] .. out[outOff+DIGEST_LENGTH_BYTES] + finish(); + + for (int i = 0; i < 8; i++) + { + convertLongToByteArray(_hash[i], out, outOff + (i * 8)); + } + + reset(); + return getDigestSize(); + } + + /** + * reset the chaining variables + */ + public void reset() + { + // set variables to null, blank, whatever + _bufferPos = 0; + Arrays.fill(_bitCount, (short)0); + Arrays.fill(_buffer, (byte)0); + Arrays.fill(_hash, 0); + Arrays.fill(_K, 0); + Arrays.fill(_L, 0); + Arrays.fill(_block, 0); + Arrays.fill(_state, 0); + } + + // this takes a buffer of information and fills the block + private void processFilledBuffer(byte[] in, int inOff) + { + // copies into the block... + for (int i = 0; i < _state.length; i++) + { + _block[i] = bytesToLongFromBuffer(_buffer, i * 8); + } + processBlock(); + _bufferPos = 0; + Arrays.fill(_buffer, (byte)0); + } + + private long bytesToLongFromBuffer(byte[] buffer, int startPos) + { + long rv = (((buffer[startPos + 0] & 0xffL) << 56) | + ((buffer[startPos + 1] & 0xffL) << 48) | + ((buffer[startPos + 2] & 0xffL) << 40) | + ((buffer[startPos + 3] & 0xffL) << 32) | + ((buffer[startPos + 4] & 0xffL) << 24) | + ((buffer[startPos + 5] & 0xffL) << 16) | + ((buffer[startPos + 6] & 0xffL) << 8) | + ((buffer[startPos + 7]) & 0xffL)); + + return rv; + } + + private void convertLongToByteArray(long inputLong, byte[] outputArray, int offSet) + { + for (int i = 0; i < 8; i++) + { + outputArray[offSet + i] = (byte)((inputLong >> (56 - (i * 8))) & 0xff); + } + } + + protected void processBlock() + { + // buffer contents have been transferred to the _block[] array via + // processFilledBuffer + + // compute and apply K^0 + for (int i = 0; i < 8; i++) + { + _state[i] = _block[i] ^ (_K[i] = _hash[i]); + } + + // iterate over the rounds + for (int round = 1; round <= ROUNDS; round++) + { + for (int i = 0; i < 8; i++) + { + _L[i] = 0; + _L[i] ^= C0[(int)(_K[(i - 0) & 7] >>> 56) & 0xff]; + _L[i] ^= C1[(int)(_K[(i - 1) & 7] >>> 48) & 0xff]; + _L[i] ^= C2[(int)(_K[(i - 2) & 7] >>> 40) & 0xff]; + _L[i] ^= C3[(int)(_K[(i - 3) & 7] >>> 32) & 0xff]; + _L[i] ^= C4[(int)(_K[(i - 4) & 7] >>> 24) & 0xff]; + _L[i] ^= C5[(int)(_K[(i - 5) & 7] >>> 16) & 0xff]; + _L[i] ^= C6[(int)(_K[(i - 6) & 7] >>> 8) & 0xff]; + _L[i] ^= C7[(int)(_K[(i - 7) & 7]) & 0xff]; + } + + System.arraycopy(_L, 0, _K, 0, _K.length); + + _K[0] ^= _rc[round]; + + // apply the round transformation + for (int i = 0; i < 8; i++) + { + _L[i] = _K[i]; + + _L[i] ^= C0[(int)(_state[(i - 0) & 7] >>> 56) & 0xff]; + _L[i] ^= C1[(int)(_state[(i - 1) & 7] >>> 48) & 0xff]; + _L[i] ^= C2[(int)(_state[(i - 2) & 7] >>> 40) & 0xff]; + _L[i] ^= C3[(int)(_state[(i - 3) & 7] >>> 32) & 0xff]; + _L[i] ^= C4[(int)(_state[(i - 4) & 7] >>> 24) & 0xff]; + _L[i] ^= C5[(int)(_state[(i - 5) & 7] >>> 16) & 0xff]; + _L[i] ^= C6[(int)(_state[(i - 6) & 7] >>> 8) & 0xff]; + _L[i] ^= C7[(int)(_state[(i - 7) & 7]) & 0xff]; + } + + // save the current state + System.arraycopy(_L, 0, _state, 0, _state.length); + } + + // apply Miuaguchi-Preneel compression + for (int i = 0; i < 8; i++) + { + _hash[i] ^= _state[i] ^ _block[i]; + } + + } + + public void update(byte in) + { + _buffer[_bufferPos] = in; + + //System.out.println("adding to buffer = "+_buffer[_bufferPos]); + + ++_bufferPos; + + if (_bufferPos == _buffer.length) + { + processFilledBuffer(_buffer, 0); + } + + increment(); + } + + /* + * increment() can be implemented in this way using 2 arrays or + * by having some temporary variables that are used to set the + * value provided by EIGHT[i] and carry within the loop. + * + * not having done any timing, this seems likely to be faster + * at the slight expense of 32*(sizeof short) bytes + */ + private static final short[] EIGHT = new short[BITCOUNT_ARRAY_SIZE]; + static + { + EIGHT[BITCOUNT_ARRAY_SIZE - 1] = 8; + } + + private void increment() + { + int carry = 0; + for (int i = _bitCount.length - 1; i >= 0; i--) + { + int sum = (_bitCount[i] & 0xff) + EIGHT[i] + carry; + + carry = sum >>> 8; + _bitCount[i] = (short)(sum & 0xff); + } + } + + public void update(byte[] in, int inOff, int len) + { + while (len > 0) + { + update(in[inOff]); + ++inOff; + --len; + } + + } + + private void finish() + { + /* + * this makes a copy of the current bit length. at the expense of an + * object creation of 32 bytes rather than providing a _stopCounting + * boolean which was the alternative I could think of. + */ + byte[] bitLength = copyBitLength(); + + _buffer[_bufferPos++] |= 0x80; + + if (_bufferPos == _buffer.length) + { + processFilledBuffer(_buffer, 0); + } + + /* + * Final block contains + * [ ... data .... ][0][0][0][ length ] + * + * if [ length ] cannot fit. Need to create a new block. + */ + if (_bufferPos > 32) + { + while (_bufferPos != 0) + { + update((byte)0); + } + } + + while (_bufferPos <= 32) + { + update((byte)0); + } + + // copy the length information to the final 32 bytes of the + // 64 byte block.... + System.arraycopy(bitLength, 0, _buffer, 32, bitLength.length); + + processFilledBuffer(_buffer, 0); + } + + private byte[] copyBitLength() + { + byte[] rv = new byte[BITCOUNT_ARRAY_SIZE]; + for (int i = 0; i < rv.length; i++) + { + rv[i] = (byte)(_bitCount[i] & 0xff); + } + return rv; + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + public Memoable copy() + { + return new WhirlpoolDigest(this); + } + + public void reset(Memoable other) + { + WhirlpoolDigest originalDigest = (WhirlpoolDigest)other; + + System.arraycopy(originalDigest._rc, 0, _rc, 0, _rc.length); + + System.arraycopy(originalDigest._buffer, 0, _buffer, 0, _buffer.length); + + this._bufferPos = originalDigest._bufferPos; + System.arraycopy(originalDigest._bitCount, 0, _bitCount, 0, _bitCount.length); + + // -- internal hash state -- + System.arraycopy(originalDigest._hash, 0, _hash, 0, _hash.length); + System.arraycopy(originalDigest._K, 0, _K, 0, _K.length); + System.arraycopy(originalDigest._L, 0, _L, 0, _L.length); + System.arraycopy(originalDigest._block, 0, _block, 0, _block.length); + System.arraycopy(originalDigest._state, 0, _state, 0, _state.length); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECDecryptor.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECDecryptor.java new file mode 100644 index 00000000..c4faf4c2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECDecryptor.java @@ -0,0 +1,11 @@ +package org.bouncycastle.crypto.ec; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.math.ec.ECPoint; + +public interface ECDecryptor +{ + void init(CipherParameters params); + + ECPoint decrypt(ECPair cipherText); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java new file mode 100644 index 00000000..c8c548ec --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto.ec; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.math.ec.ECPoint; + +/** + * this does your basic decryption ElGamal style using EC + */ +public class ECElGamalDecryptor + implements ECDecryptor +{ + private ECPrivateKeyParameters key; + + /** + * initialise the decryptor. + * + * @param param the necessary EC key parameters. + */ + public void init( + CipherParameters param) + { + if (!(param instanceof ECPrivateKeyParameters)) + { + throw new IllegalArgumentException("ECPrivateKeyParameters are required for decryption."); + } + + this.key = (ECPrivateKeyParameters)param; + } + + /** + * Decrypt an EC pair producing the original EC point. + * + * @param pair the EC point pair to process. + * @return the result of the Elgamal process. + */ + public ECPoint decrypt(ECPair pair) + { + if (key == null) + { + throw new IllegalStateException("ECElGamalDecryptor not initialised"); + } + + ECPoint tmp = pair.getX().multiply(key.getD()); + + return pair.getY().add(tmp.negate()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java new file mode 100644 index 00000000..e5569a8d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.ec; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +/** + * this does your basic ElGamal encryption algorithm using EC + */ +public class ECElGamalEncryptor + implements ECEncryptor +{ + private ECPublicKeyParameters key; + private SecureRandom random; + + /** + * initialise the encryptor. + * + * @param param the necessary EC key parameters. + */ + public void init( + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + if (!(p.getParameters() instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for encryption."); + } + this.key = (ECPublicKeyParameters)p.getParameters(); + this.random = p.getRandom(); + } + else + { + if (!(param instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for encryption."); + } + + this.key = (ECPublicKeyParameters)param; + this.random = new SecureRandom(); + } + } + + /** + * Process a single EC point using the basic ElGamal algorithm. + * + * @param point the EC point to process. + * @return the result of the Elgamal process. + */ + public ECPair encrypt(ECPoint point) + { + if (key == null) + { + throw new IllegalStateException("ECElGamalEncryptor not initialised"); + } + + BigInteger n = key.getParameters().getN(); + BigInteger k = ECUtil.generateK(n, random); + + ECPoint g = key.getParameters().getG(); + ECPoint gamma = g.multiply(k); + ECPoint phi = key.getQ().multiply(k).add(point); + + return new ECPair(gamma, phi); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECEncryptor.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECEncryptor.java new file mode 100644 index 00000000..39704b94 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECEncryptor.java @@ -0,0 +1,11 @@ +package org.bouncycastle.crypto.ec; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.math.ec.ECPoint; + +public interface ECEncryptor +{ + void init(CipherParameters params); + + ECPair encrypt(ECPoint point); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java new file mode 100644 index 00000000..32ba0706 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.ec; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECPoint; + +/** + * this does your basic Elgamal encryption algorithm using EC + */ +public class ECNewPublicKeyTransform + implements ECPairTransform +{ + private ECPublicKeyParameters key; + private SecureRandom random; + + /** + * initialise the EC Elgamal engine. + * + * @param param the necessary EC key parameters. + */ + public void init( + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + if (!(p.getParameters() instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for new public key transform."); + } + this.key = (ECPublicKeyParameters)p.getParameters(); + this.random = p.getRandom(); + } + else + { + if (!(param instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for new public key transform."); + } + + this.key = (ECPublicKeyParameters)param; + this.random = new SecureRandom(); + } + } + + /** + * Transform an existing cipher test pair using the ElGamal algorithm. Note: the input cipherText will + * need to be preserved in order to complete the transformation to the new public key. + * + * @param cipherText the EC point to process. + * @return returns a new ECPair representing the result of the process. + */ + public ECPair transform(ECPair cipherText) + { + if (key == null) + { + throw new IllegalStateException("ECNewPublicKeyTransform not initialised"); + } + + BigInteger n = key.getParameters().getN(); + BigInteger k = ECUtil.generateK(n, random); + + ECPoint g = key.getParameters().getG(); + ECPoint gamma = g.multiply(k); + ECPoint phi = key.getQ().multiply(k).add(cipherText.getY()); + + return new ECPair(gamma, phi); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java new file mode 100644 index 00000000..b0379841 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java @@ -0,0 +1,76 @@ +package org.bouncycastle.crypto.ec; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECPoint; + +/** + * this transforms the original randomness used for an ElGamal encryption. + */ +public class ECNewRandomnessTransform + implements ECPairTransform +{ + private ECPublicKeyParameters key; + private SecureRandom random; + + /** + * initialise the underlying EC ElGamal engine. + * + * @param param the necessary EC key parameters. + */ + public void init( + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + if (!(p.getParameters() instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for new randomness transform."); + } + + this.key = (ECPublicKeyParameters)p.getParameters(); + this.random = p.getRandom(); + } + else + { + if (!(param instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for new randomness transform."); + } + + this.key = (ECPublicKeyParameters)param; + this.random = new SecureRandom(); + } + } + + /** + * Transform an existing cipher test pair using the ElGamal algorithm. Note: it is assumed this + * transform has been initialised with the same public key that was used to create the original + * cipher text. + * + * @param cipherText the EC point to process. + * @return returns a new ECPair representing the result of the process. + */ + public ECPair transform(ECPair cipherText) + { + if (key == null) + { + throw new IllegalStateException("ECNewRandomnessTransform not initialised"); + } + + BigInteger n = key.getParameters().getN(); + BigInteger k = ECUtil.generateK(n, random); + + ECPoint g = key.getParameters().getG(); + ECPoint gamma = g.multiply(k); + ECPoint phi = key.getQ().multiply(k).add(cipherText.getY()); + + return new ECPair(cipherText.getX().add(gamma), phi); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECPair.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECPair.java new file mode 100644 index 00000000..d910f3c7 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECPair.java @@ -0,0 +1,38 @@ +package org.bouncycastle.crypto.ec; + +import org.bouncycastle.math.ec.ECPoint; + +public class ECPair +{ + private final ECPoint x; + private final ECPoint y; + + public ECPair(ECPoint x, ECPoint y) + { + this.x = x; + this.y = y; + } + + public ECPoint getX() + { + return x; + } + + public ECPoint getY() + { + return y; + } + + public byte[] getEncoded() + { + byte[] xEnc = x.getEncoded(); + byte[] yEnc = y.getEncoded(); + + byte[] full = new byte[xEnc.length + yEnc.length]; + + System.arraycopy(xEnc, 0, full, 0, xEnc.length); + System.arraycopy(yEnc, 0, full, xEnc.length, yEnc.length); + + return full; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECPairTransform.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECPairTransform.java new file mode 100644 index 00000000..e3f1787d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECPairTransform.java @@ -0,0 +1,10 @@ +package org.bouncycastle.crypto.ec; + +import org.bouncycastle.crypto.CipherParameters; + +public interface ECPairTransform +{ + void init(CipherParameters params); + + ECPair transform(ECPair cipherText); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/ec/ECUtil.java b/core/src/main/java/org/bouncycastle/crypto/ec/ECUtil.java new file mode 100644 index 00000000..d21d8fd3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/ec/ECUtil.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto.ec; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.math.ec.ECConstants; + +class ECUtil +{ + static BigInteger generateK(BigInteger n, SecureRandom random) + { + int nBitLength = n.bitLength(); + BigInteger k = new BigInteger(nBitLength, random); + + while (k.equals(ECConstants.ZERO) || (k.compareTo(n) >= 0)) + { + k = new BigInteger(nBitLength, random); + } + + return k; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java b/core/src/main/java/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java new file mode 100644 index 00000000..ec91e1ac --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java @@ -0,0 +1,287 @@ +package org.bouncycastle.crypto.encodings; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * ISO 9796-1 padding. Note in the light of recent results you should + * only use this with RSA (rather than the "simpler" Rabin keys) and you + * should never use it with anything other than a hash (ie. even if the + * message is small don't sign the message, sign it's hash) or some "random" + * value. See your favorite search engine for details. + */ +public class ISO9796d1Encoding + implements AsymmetricBlockCipher +{ + private static final BigInteger SIXTEEN = BigInteger.valueOf(16L); + private static final BigInteger SIX = BigInteger.valueOf(6L); + + private static byte[] shadows = { 0xe, 0x3, 0x5, 0x8, 0x9, 0x4, 0x2, 0xf, + 0x0, 0xd, 0xb, 0x6, 0x7, 0xa, 0xc, 0x1 }; + private static byte[] inverse = { 0x8, 0xf, 0x6, 0x1, 0x5, 0x2, 0xb, 0xc, + 0x3, 0x4, 0xd, 0xa, 0xe, 0x9, 0x0, 0x7 }; + + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private int bitSize; + private int padBits = 0; + private BigInteger modulus; + + public ISO9796d1Encoding( + AsymmetricBlockCipher cipher) + { + this.engine = cipher; + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + RSAKeyParameters kParam = null; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + kParam = (RSAKeyParameters)rParam.getParameters(); + } + else + { + kParam = (RSAKeyParameters)param; + } + + engine.init(forEncryption, param); + + modulus = kParam.getModulus(); + bitSize = modulus.bitLength(); + + this.forEncryption = forEncryption; + } + + /** + * return the input block size. The largest message we can process + * is (key_size_in_bits + 3)/16, which in our world comes to + * key_size_in_bytes / 2. + */ + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return (baseBlockSize + 1) / 2; + } + else + { + return baseBlockSize; + } + } + + /** + * return the maximum possible size for the output. + */ + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return (baseBlockSize + 1) / 2; + } + } + + /** + * set the number of bits in the next message to be treated as + * pad bits. + */ + public void setPadBits( + int padBits) + { + if (padBits > 7) + { + throw new IllegalArgumentException("padBits > 7"); + } + + this.padBits = padBits; + } + + /** + * retrieve the number of pad bits in the last decoded message. + */ + public int getPadBits() + { + return padBits; + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = new byte[(bitSize + 7) / 8]; + int r = padBits + 1; + int z = inLen; + int t = (bitSize + 13) / 16; + + for (int i = 0; i < t; i += z) + { + if (i > t - z) + { + System.arraycopy(in, inOff + inLen - (t - i), + block, block.length - t, t - i); + } + else + { + System.arraycopy(in, inOff, block, block.length - (i + z), z); + } + } + + for (int i = block.length - 2 * t; i != block.length; i += 2) + { + byte val = block[block.length - t + i / 2]; + + block[i] = (byte)((shadows[(val & 0xff) >>> 4] << 4) + | shadows[val & 0x0f]); + block[i + 1] = val; + } + + block[block.length - 2 * z] ^= r; + block[block.length - 1] = (byte)((block[block.length - 1] << 4) | 0x06); + + int maxBit = (8 - (bitSize - 1) % 8); + int offSet = 0; + + if (maxBit != 8) + { + block[0] &= 0xff >>> maxBit; + block[0] |= 0x80 >>> maxBit; + } + else + { + block[0] = 0x00; + block[1] |= 0x80; + offSet = 1; + } + + return engine.processBlock(block, offSet, block.length - offSet); + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not a valid ISO 9796 bit string + */ + private byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = engine.processBlock(in, inOff, inLen); + int r = 1; + int t = (bitSize + 13) / 16; + + BigInteger iS = new BigInteger(1, block); + BigInteger iR; + if (iS.mod(SIXTEEN).equals(SIX)) + { + iR = iS; + } + else if ((modulus.subtract(iS)).mod(SIXTEEN).equals(SIX)) + { + iR = modulus.subtract(iS); + } + else + { + throw new InvalidCipherTextException("resulting integer iS or (modulus - iS) is not congruent to 6 mod 16"); + } + + block = convertOutputDecryptOnly(iR); + + if ((block[block.length - 1] & 0x0f) != 0x6 ) + { + throw new InvalidCipherTextException("invalid forcing byte in block"); + } + + block[block.length - 1] = (byte)(((block[block.length - 1] & 0xff) >>> 4) | ((inverse[(block[block.length - 2] & 0xff) >> 4]) << 4)); + block[0] = (byte)((shadows[(block[1] & 0xff) >>> 4] << 4) + | shadows[block[1] & 0x0f]); + + boolean boundaryFound = false; + int boundary = 0; + + for (int i = block.length - 1; i >= block.length - 2 * t; i -= 2) + { + int val = ((shadows[(block[i] & 0xff) >>> 4] << 4) + | shadows[block[i] & 0x0f]); + + if (((block[i - 1] ^ val) & 0xff) != 0) + { + if (!boundaryFound) + { + boundaryFound = true; + r = (block[i - 1] ^ val) & 0xff; + boundary = i - 1; + } + else + { + throw new InvalidCipherTextException("invalid tsums in block"); + } + } + } + + block[boundary] = 0; + + byte[] nblock = new byte[(block.length - boundary) / 2]; + + for (int i = 0; i < nblock.length; i++) + { + nblock[i] = block[2 * i + boundary + 1]; + } + + padBits = r - 1; + + return nblock; + } + + private static byte[] convertOutputDecryptOnly(BigInteger result) + { + byte[] output = result.toByteArray(); + if (output[0] == 0) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + System.arraycopy(output, 1, tmp, 0, tmp.length); + return tmp; + } + return output; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java b/core/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java new file mode 100644 index 00000000..17d8e3b0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java @@ -0,0 +1,357 @@ +package org.bouncycastle.crypto.encodings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * Optimal Asymmetric Encryption Padding (OAEP) - see PKCS 1 V 2. + */ +public class OAEPEncoding + implements AsymmetricBlockCipher +{ + private byte[] defHash; + private Digest mgf1Hash; + + private AsymmetricBlockCipher engine; + private SecureRandom random; + private boolean forEncryption; + + public OAEPEncoding( + AsymmetricBlockCipher cipher) + { + this(cipher, new SHA1Digest(), null); + } + + public OAEPEncoding( + AsymmetricBlockCipher cipher, + Digest hash) + { + this(cipher, hash, null); + } + + public OAEPEncoding( + AsymmetricBlockCipher cipher, + Digest hash, + byte[] encodingParams) + { + this(cipher, hash, hash, encodingParams); + } + + public OAEPEncoding( + AsymmetricBlockCipher cipher, + Digest hash, + Digest mgf1Hash, + byte[] encodingParams) + { + this.engine = cipher; + this.mgf1Hash = mgf1Hash; + this.defHash = new byte[hash.getDigestSize()]; + + hash.reset(); + + if (encodingParams != null) + { + hash.update(encodingParams, 0, encodingParams.length); + } + + hash.doFinal(defHash, 0); + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + } + else + { + this.random = new SecureRandom(); + } + + engine.init(forEncryption, param); + + this.forEncryption = forEncryption; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - 1 - 2 * defHash.length; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - 1 - 2 * defHash.length; + } + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + public byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = new byte[getInputBlockSize() + 1 + 2 * defHash.length]; + + // + // copy in the message + // + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + // + // add sentinel + // + block[block.length - inLen - 1] = 0x01; + + // + // as the block is already zeroed - there's no need to add PS (the >= 0 pad of 0) + // + + // + // add the hash of the encoding params. + // + System.arraycopy(defHash, 0, block, defHash.length, defHash.length); + + // + // generate the seed. + // + byte[] seed = new byte[defHash.length]; + + random.nextBytes(seed); + + // + // mask the message block. + // + byte[] mask = maskGeneratorFunction1(seed, 0, seed.length, block.length - defHash.length); + + for (int i = defHash.length; i != block.length; i++) + { + block[i] ^= mask[i - defHash.length]; + } + + // + // add in the seed + // + System.arraycopy(seed, 0, block, 0, defHash.length); + + // + // mask the seed. + // + mask = maskGeneratorFunction1( + block, defHash.length, block.length - defHash.length, defHash.length); + + for (int i = 0; i != defHash.length; i++) + { + block[i] ^= mask[i]; + } + + return engine.processBlock(block, 0, block.length); + } + + /** + * @exception InvalidCipherTextException if the decrypted block turns out to + * be badly formatted. + */ + public byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] data = engine.processBlock(in, inOff, inLen); + byte[] block; + + // + // as we may have zeros in our leading bytes for the block we produced + // on encryption, we need to make sure our decrypted block comes back + // the same size. + // + if (data.length < engine.getOutputBlockSize()) + { + block = new byte[engine.getOutputBlockSize()]; + + System.arraycopy(data, 0, block, block.length - data.length, data.length); + } + else + { + block = data; + } + + if (block.length < (2 * defHash.length) + 1) + { + throw new InvalidCipherTextException("data too short"); + } + + // + // unmask the seed. + // + byte[] mask = maskGeneratorFunction1( + block, defHash.length, block.length - defHash.length, defHash.length); + + for (int i = 0; i != defHash.length; i++) + { + block[i] ^= mask[i]; + } + + // + // unmask the message block. + // + mask = maskGeneratorFunction1(block, 0, defHash.length, block.length - defHash.length); + + for (int i = defHash.length; i != block.length; i++) + { + block[i] ^= mask[i - defHash.length]; + } + + // + // check the hash of the encoding params. + // long check to try to avoid this been a source of a timing attack. + // + boolean defHashWrong = false; + + for (int i = 0; i != defHash.length; i++) + { + if (defHash[i] != block[defHash.length + i]) + { + defHashWrong = true; + } + } + + if (defHashWrong) + { + throw new InvalidCipherTextException("data hash wrong"); + } + + // + // find the data block + // + int start; + + for (start = 2 * defHash.length; start != block.length; start++) + { + if (block[start] != 0) + { + break; + } + } + + if (start >= (block.length - 1) || block[start] != 1) + { + throw new InvalidCipherTextException("data start wrong " + start); + } + + start++; + + // + // extract the data block + // + byte[] output = new byte[block.length - start]; + + System.arraycopy(block, start, output, 0, output.length); + + return output; + } + + /** + * 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); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[mgf1Hash.getDigestSize()]; + byte[] C = new byte[4]; + int counter = 0; + + mgf1Hash.reset(); + + while (counter < (length / hashBuf.length)) + { + ItoOSP(counter, C); + + mgf1Hash.update(Z, zOff, zLen); + mgf1Hash.update(C, 0, C.length); + mgf1Hash.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hashBuf.length, hashBuf.length); + + counter++; + } + + if ((counter * hashBuf.length) < length) + { + ItoOSP(counter, C); + + mgf1Hash.update(Z, zOff, zLen); + mgf1Hash.update(C, 0, C.length); + mgf1Hash.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hashBuf.length, mask.length - (counter * hashBuf.length)); + } + + return mask; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/core/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java new file mode 100644 index 00000000..09f1537e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java @@ -0,0 +1,257 @@ +package org.bouncycastle.crypto.encodings; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this + * depends on your application - see PKCS1 Version 2 for details. + */ +public class PKCS1Encoding + implements AsymmetricBlockCipher +{ + /** + * some providers fail to include the leading zero in PKCS1 encoded blocks. If you need to + * work with one of these set the system property org.bouncycastle.pkcs1.strict to false. + * <p> + * The system property is checked during construction of the encoding object, it is set to + * true by default. + * </p> + */ + public static final String STRICT_LENGTH_ENABLED_PROPERTY = "org.bouncycastle.pkcs1.strict"; + + private static final int HEADER_LENGTH = 10; + + private SecureRandom random; + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private boolean forPrivateKey; + private boolean useStrictLength; + + /** + * Basic constructor. + * @param cipher + */ + public PKCS1Encoding( + AsymmetricBlockCipher cipher) + { + this.engine = cipher; + this.useStrictLength = useStrict(); + } + + // + // for J2ME compatibility + // + private boolean useStrict() + { + // required if security manager has been installed. + String strict = (String)AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return System.getProperty(STRICT_LENGTH_ENABLED_PROPERTY); + } + }); + + return strict == null || strict.equals("true"); + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)param; + } + + engine.init(forEncryption, param); + + this.forPrivateKey = kParam.isPrivate(); + this.forEncryption = forEncryption; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - HEADER_LENGTH; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - HEADER_LENGTH; + } + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (inLen > getInputBlockSize()) + { + throw new IllegalArgumentException("input data too large"); + } + + byte[] block = new byte[engine.getInputBlockSize()]; + + if (forPrivateKey) + { + block[0] = 0x01; // type code 1 + + for (int i = 1; i != block.length - inLen - 1; i++) + { + block[i] = (byte)0xFF; + } + } + else + { + random.nextBytes(block); // random fill + + block[0] = 0x02; // type code 2 + + // + // a zero byte marks the end of the padding, so all + // the pad bytes must be non-zero. + // + for (int i = 1; i != block.length - inLen - 1; i++) + { + while (block[i] == 0) + { + block[i] = (byte)random.nextInt(); + } + } + } + + block[block.length - inLen - 1] = 0x00; // mark the end of the padding + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + return engine.processBlock(block, 0, block.length); + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not in PKCS1 format. + */ + private byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = engine.processBlock(in, inOff, inLen); + + if (block.length < getOutputBlockSize()) + { + throw new InvalidCipherTextException("block truncated"); + } + + byte type = block[0]; + + if (forPrivateKey) + { + if (type != 2) + { + throw new InvalidCipherTextException("unknown block type"); + } + } + else + { + if (type != 1) + { + throw new InvalidCipherTextException("unknown block type"); + } + } + + if (useStrictLength && block.length != engine.getOutputBlockSize()) + { + throw new InvalidCipherTextException("block incorrect size"); + } + + // + // find and extract the message block. + // + int start; + + for (start = 1; start != block.length; start++) + { + byte pad = block[start]; + + if (pad == 0) + { + break; + } + if (type == 1 && pad != (byte)0xff) + { + throw new InvalidCipherTextException("block padding incorrect"); + } + } + + start++; // data should start at the next byte + + if (start > block.length || start < HEADER_LENGTH) + { + throw new InvalidCipherTextException("no data in block"); + } + + byte[] result = new byte[block.length - start]; + + System.arraycopy(block, start, result, 0, result.length); + + return result; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java new file mode 100644 index 00000000..756197ce --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java @@ -0,0 +1,546 @@ +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; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + * <p> + * For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a> + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first. + * + * The slowest version uses no static tables at all and computes the values in each round. + * <p> + * This file contains the middle performance version with 2Kbytes of static tables for round precomputation. + * + */ +public class AESEngine + implements BlockCipher +{ + // The S box + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, + (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, + (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, + (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, + (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, + (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, + (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, + (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, + (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, + (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, + (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, + (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, + (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, + (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, + (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, + (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, + (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, + (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, + (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, + (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, + (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, + (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, + (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, + (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, + (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, + (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, + (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, + (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, + (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, + (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, + (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, + (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, + (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = + { + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, + 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, + 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, + 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41, + 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, + 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, + 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, + 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, + 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, + 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, + 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, + 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, + 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, + 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, + 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, + 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, + 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, + 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, + 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, + 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, + 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, + 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, + 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, + 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, + 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, + 0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, + 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, + 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, + 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, + 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, + 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, + 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, + 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, + 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, + 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, + 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, + 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, + 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, + 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, + 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, + 0x3a16162c}; + +private static final int[] Tinv0 = + { + 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, + 0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, + 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, + 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, + 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, + 0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, + 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, + 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, + 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, + 0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, + 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, + 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, + 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, + 0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd, + 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, + 0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, + 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, + 0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, + 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, + 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4, + 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, + 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e, + 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, + 0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, + 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, + 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, + 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, + 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, + 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, + 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, + 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, + 0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, + 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, + 0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, + 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, + 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, + 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, + 0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, + 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, + 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, + 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, + 0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, + 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, + 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, + 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, + 0x4257b8d0}; + + private static int shift(int r, int shift) + { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private static int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private static int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + private static int subWord(int x) + { + return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS+1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) + { + W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24); + i+=4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) + { + int temp = W[(i-1)>>2][(i-1)&3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC)-1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i>>2][i&3] = W[(i - KC)>>2][(i-KC)&3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESEngine() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "AES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + bytes[index++] = (byte)C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + + bytes[index++] = (byte)C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + + bytes[index++] = (byte)C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + + bytes[index++] = (byte)C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + + private void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + r = 1; + + while (r < ROUNDS - 1) + { + r0 = T0[C0&255] ^ shift(T0[(C1>>8)&255], 24) ^ shift(T0[(C2>>16)&255],16) ^ shift(T0[(C3>>24)&255],8) ^ KW[r][0]; + r1 = T0[C1&255] ^ shift(T0[(C2>>8)&255], 24) ^ shift(T0[(C3>>16)&255], 16) ^ shift(T0[(C0>>24)&255], 8) ^ KW[r][1]; + r2 = T0[C2&255] ^ shift(T0[(C3>>8)&255], 24) ^ shift(T0[(C0>>16)&255], 16) ^ shift(T0[(C1>>24)&255], 8) ^ KW[r][2]; + r3 = T0[C3&255] ^ shift(T0[(C0>>8)&255], 24) ^ shift(T0[(C1>>16)&255], 16) ^ shift(T0[(C2>>24)&255], 8) ^ KW[r++][3]; + C0 = T0[r0&255] ^ shift(T0[(r1>>8)&255], 24) ^ shift(T0[(r2>>16)&255], 16) ^ shift(T0[(r3>>24)&255], 8) ^ KW[r][0]; + C1 = T0[r1&255] ^ shift(T0[(r2>>8)&255], 24) ^ shift(T0[(r3>>16)&255], 16) ^ shift(T0[(r0>>24)&255], 8) ^ KW[r][1]; + C2 = T0[r2&255] ^ shift(T0[(r3>>8)&255], 24) ^ shift(T0[(r0>>16)&255], 16) ^ shift(T0[(r1>>24)&255], 8) ^ KW[r][2]; + C3 = T0[r3&255] ^ shift(T0[(r0>>8)&255], 24) ^ shift(T0[(r1>>16)&255], 16) ^ shift(T0[(r2>>24)&255], 8) ^ KW[r++][3]; + } + + r0 = T0[C0&255] ^ shift(T0[(C1>>8)&255], 24) ^ shift(T0[(C2>>16)&255], 16) ^ shift(T0[(C3>>24)&255], 8) ^ KW[r][0]; + r1 = T0[C1&255] ^ shift(T0[(C2>>8)&255], 24) ^ shift(T0[(C3>>16)&255], 16) ^ shift(T0[(C0>>24)&255], 8) ^ KW[r][1]; + r2 = T0[C2&255] ^ shift(T0[(C3>>8)&255], 24) ^ shift(T0[(C0>>16)&255], 16) ^ shift(T0[(C1>>24)&255], 8) ^ KW[r][2]; + r3 = T0[C3&255] ^ shift(T0[(C0>>8)&255], 24) ^ shift(T0[(C1>>16)&255], 16) ^ shift(T0[(C2>>24)&255], 8) ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0]; + C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1]; + C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2]; + C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + r = ROUNDS-1; + + while (r>1) + { + r0 = Tinv0[C0&255] ^ shift(Tinv0[(C3>>8)&255], 24) ^ shift(Tinv0[(C2>>16)&255], 16) ^ shift(Tinv0[(C1>>24)&255], 8) ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ shift(Tinv0[(C0>>8)&255], 24) ^ shift(Tinv0[(C3>>16)&255], 16) ^ shift(Tinv0[(C2>>24)&255], 8) ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ shift(Tinv0[(C1>>8)&255], 24) ^ shift(Tinv0[(C0>>16)&255], 16) ^ shift(Tinv0[(C3>>24)&255], 8) ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ shift(Tinv0[(C2>>8)&255], 24) ^ shift(Tinv0[(C1>>16)&255], 16) ^ shift(Tinv0[(C0>>24)&255], 8) ^ KW[r--][3]; + C0 = Tinv0[r0&255] ^ shift(Tinv0[(r3>>8)&255], 24) ^ shift(Tinv0[(r2>>16)&255], 16) ^ shift(Tinv0[(r1>>24)&255], 8) ^ KW[r][0]; + C1 = Tinv0[r1&255] ^ shift(Tinv0[(r0>>8)&255], 24) ^ shift(Tinv0[(r3>>16)&255], 16) ^ shift(Tinv0[(r2>>24)&255], 8) ^ KW[r][1]; + C2 = Tinv0[r2&255] ^ shift(Tinv0[(r1>>8)&255], 24) ^ shift(Tinv0[(r0>>16)&255], 16) ^ shift(Tinv0[(r3>>24)&255], 8) ^ KW[r][2]; + C3 = Tinv0[r3&255] ^ shift(Tinv0[(r2>>8)&255], 24) ^ shift(Tinv0[(r1>>16)&255], 16) ^ shift(Tinv0[(r0>>24)&255], 8) ^ KW[r--][3]; + } + + r0 = Tinv0[C0&255] ^ shift(Tinv0[(C3>>8)&255], 24) ^ shift(Tinv0[(C2>>16)&255], 16) ^ shift(Tinv0[(C1>>24)&255], 8) ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ shift(Tinv0[(C0>>8)&255], 24) ^ shift(Tinv0[(C3>>16)&255], 16) ^ shift(Tinv0[(C2>>24)&255], 8) ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ shift(Tinv0[(C1>>8)&255], 24) ^ shift(Tinv0[(C0>>16)&255], 16) ^ shift(Tinv0[(C3>>24)&255], 8) ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ shift(Tinv0[(C2>>8)&255], 24) ^ shift(Tinv0[(C1>>16)&255], 16) ^ shift(Tinv0[(C0>>24)&255], 8) ^ KW[r][3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + C0 = (Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0]; + C1 = (Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24) ^ KW[0][1]; + C2 = (Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24) ^ KW[0][2]; + C3 = (Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24) ^ KW[0][3]; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java new file mode 100644 index 00000000..ff4b2f8f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java @@ -0,0 +1,875 @@ +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; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + * <p> + * For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a> + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first + * + * The slowest version uses no static tables at all and computes the values in each round + * <p> + * This file contains the fast version with 8Kbytes of static tables for round precomputation + * + */ +public class AESFastEngine + implements BlockCipher +{ + // The S box + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, + (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, + (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, + (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, + (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, + (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, + (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, + (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, + (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, + (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, + (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, + (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, + (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, + (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, + (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, + (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, + (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, + (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, + (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, + (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, + (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, + (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, + (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, + (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, + (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, + (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, + (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, + (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, + (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, + (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, + (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, + (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, + (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = + { + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, + 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, + 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, + 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41, + 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, + 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, + 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, + 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, + 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, + 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, + 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, + 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, + 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, + 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, + 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, + 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, + 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, + 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, + 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, + 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, + 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, + 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, + 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, + 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, + 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, + 0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, + 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, + 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, + 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, + 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, + 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, + 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, + 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, + 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, + 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, + 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, + 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, + 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, + 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, + 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, + 0x3a16162c}; + + private static final int[] T1 = + { + 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, + 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, + 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, + 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, 0xadad41ec, + 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, + 0x7272e496, 0xc0c09b5b, 0xb7b775c2, 0xfdfde11c, 0x93933dae, + 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, + 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, 0x7171e293, + 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, + 0x23234665, 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, + 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, + 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, 0x6e6edcb2, + 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, + 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397, + 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, + 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, + 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, + 0xcfcf854a, 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, + 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, + 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, 0x404080c0, + 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, + 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, 0x10102030, + 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, + 0x13132635, 0xececc32f, 0x5f5fbee1, 0x979735a2, 0x444488cc, + 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, + 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, + 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, + 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, 0xdbdbad76, + 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, + 0x06060c0a, 0x2424486c, 0x5c5cb8e4, 0xc2c29f5d, 0xd3d3bd6e, + 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, + 0x7979f28b, 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7, + 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, + 0x5656acfa, 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, + 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, + 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, 0x4b4b96dd, + 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, + 0xb5b571c4, 0x6666ccaa, 0x484890d8, 0x03030605, 0xf6f6f701, + 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, + 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, + 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, + 0x8e8e0789, 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, + 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, + 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, 0x999929b0, + 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, + 0x16162c3a}; + + private static final int[] T2 = + { + 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, + 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, + 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, + 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, 0xad41ecad, + 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, + 0x72e49672, 0xc09b5bc0, 0xb775c2b7, 0xfde11cfd, 0x933dae93, + 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, + 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, 0x71e29371, + 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, + 0x23466523, 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, + 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, + 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, 0x6edcb26e, + 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, + 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784, + 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, + 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, + 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, + 0xcf854acf, 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, + 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, + 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, 0x4080c040, + 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, + 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, 0x10203010, + 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, + 0x13263513, 0xecc32fec, 0x5fbee15f, 0x9735a297, 0x4488cc44, + 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, + 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, + 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, + 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, 0xdbad76db, + 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, + 0x060c0a06, 0x24486c24, 0x5cb8e45c, 0xc29f5dc2, 0xd3bd6ed3, + 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, + 0x79f28b79, 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d, + 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, + 0x56acfa56, 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, + 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, + 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, 0x4b96dd4b, + 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, + 0xb571c4b5, 0x66ccaa66, 0x4890d848, 0x03060503, 0xf6f701f6, + 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, + 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, + 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, + 0x8e07898e, 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, + 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, + 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, 0x9929b099, + 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, + 0x162c3a16}; + + private static final int[] T3 = + { + 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, + 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, + 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, + 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, 0x41ecadad, + 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, + 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, + 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, + 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, 0xe2937171, + 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, + 0x46652323, 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, + 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, + 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, 0xdcb26e6e, + 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, + 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484, + 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, + 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, + 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, + 0x854acfcf, 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, + 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, + 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, 0x80c04040, + 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, + 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, 0x20301010, + 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, + 0x26351313, 0xc32fecec, 0xbee15f5f, 0x35a29797, 0x88cc4444, + 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, + 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, + 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, + 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb, + 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, + 0x0c0a0606, 0x486c2424, 0xb8e45c5c, 0x9f5dc2c2, 0xbd6ed3d3, + 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, + 0xf28b7979, 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d, + 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, + 0xacfa5656, 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, + 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, + 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, 0x96dd4b4b, + 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, + 0x71c4b5b5, 0xccaa6666, 0x90d84848, 0x06050303, 0xf701f6f6, + 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, + 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, + 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, + 0x07898e8e, 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, + 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, + 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, 0x29b09999, + 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, + 0x2c3a1616}; + + private static final int[] Tinv0 = + { + 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, + 0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, + 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, + 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, + 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, + 0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, + 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, + 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, + 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, + 0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, + 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, + 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, + 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, + 0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd, + 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, + 0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, + 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, + 0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, + 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, + 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4, + 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, + 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e, + 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, + 0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, + 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, + 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, + 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, + 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, + 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, + 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, + 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, + 0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, + 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, + 0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, + 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, + 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, + 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, + 0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, + 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, + 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, + 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, + 0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, + 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, + 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, + 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, + 0x4257b8d0}; + + private static final int[] Tinv1 = + { + 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, + 0x459d1ff1, 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, + 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, + 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, 0x5f8f03e7, + 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, + 0x69e04929, 0xc8c98e44, 0x89c2756a, 0x798ef478, 0x3e58996b, + 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, + 0x4adf6318, 0x311ae582, 0x33519760, 0x7f536245, 0x7764b1e0, + 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, + 0x6cde9487, 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, + 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, + 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, 0xbe0506d5, + 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, + 0xebf6a475, 0xec830b39, 0xef6040aa, 0x9f715e06, 0x106ebd51, + 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, + 0x5dc47105, 0xd406046f, 0x155060ff, 0xfb981924, 0xe9bdd697, + 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, + 0xeec879db, 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, + 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, + 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, 0x96eeb4d2, + 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, + 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, 0x171b121d, 0x0d090e0b, + 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, + 0xdd99eebb, 0x607fa3fd, 0x2601f79f, 0xf5725cbc, 0x3b6644c5, + 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, + 0xdc31d7ca, 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, + 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, + 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, 0x903322ef, + 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, + 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, 0x9d3a2ce4, 0x9278500d, + 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, + 0xafc382f5, 0x805d9fbe, 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, + 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, + 0x18596ef4, 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, + 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, + 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, 0x9804f14a, + 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, + 0x4daacc54, 0x0496e4df, 0xb5d19ee3, 0x886a4c1b, 0x1f2cc1b8, + 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, + 0x1d67b35a, 0xd2db9252, 0x5610e933, 0x47d66d13, 0x61d79a8c, + 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, + 0xe51ce1ed, 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, + 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, + 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, 0xb30c08de, + 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, + 0x57b8d042}; + + private static final int[] Tinv2 = + { + 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, + 0x9d1ff145, 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, + 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, + 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, 0x8f03e75f, + 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, + 0xe0492969, 0xc98e44c8, 0xc2756a89, 0x8ef47879, 0x58996b3e, + 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, + 0xdf63184a, 0x1ae58231, 0x51976033, 0x5362457f, 0x64b1e077, + 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, + 0xde94876c, 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, + 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, + 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, 0x0506d5be, + 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, + 0xf6a475eb, 0x830b39ec, 0x6040aaef, 0x715e069f, 0x6ebd5110, + 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, + 0xc471055d, 0x06046fd4, 0x5060ff15, 0x981924fb, 0xbdd697e9, + 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, + 0xc879dbee, 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, + 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, + 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, 0xeeb4d296, + 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, + 0x93e20aba, 0xa0c0e52a, 0x223c43e0, 0x1b121d17, 0x090e0b0d, + 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, + 0x99eebbdd, 0x7fa3fd60, 0x01f79f26, 0x725cbcf5, 0x6644c53b, + 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, + 0x31d7cadc, 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, + 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, + 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, 0x3322ef90, + 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, + 0x7aa528de, 0xb7da268e, 0xad3fa4bf, 0x3a2ce49d, 0x78500d92, + 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, + 0xc382f5af, 0x5d9fbe80, 0xd0697c93, 0xd56fa92d, 0x25cfb312, + 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, + 0x596ef418, 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, + 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, + 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, 0x04f14a98, + 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, + 0xaacc544d, 0x96e4df04, 0xd19ee3b5, 0x6a4c1b88, 0x2cc1b81f, + 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, + 0x67b35a1d, 0xdb9252d2, 0x10e93356, 0xd66d1347, 0xd79a8c61, + 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, + 0x1ce1ede5, 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, + 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, + 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, 0x0c08deb3, + 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, + 0xb8d04257}; + + private static final int[] Tinv3 = + { + 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, + 0x1ff1459d, 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, + 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, + 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, 0x03e75f8f, + 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, + 0x492969e0, 0x8e44c8c9, 0x756a89c2, 0xf478798e, 0x996b3e58, + 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, + 0x63184adf, 0xe582311a, 0x97603351, 0x62457f53, 0xb1e07764, + 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, + 0x94876cde, 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, + 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, + 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, 0x06d5be05, + 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, + 0xa475ebf6, 0x0b39ec83, 0x40aaef60, 0x5e069f71, 0xbd51106e, + 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, + 0x71055dc4, 0x046fd406, 0x60ff1550, 0x1924fb98, 0xd697e9bd, + 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, + 0x79dbeec8, 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, + 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, + 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, 0xb4d296ee, + 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, + 0xe20aba93, 0xc0e52aa0, 0x3c43e022, 0x121d171b, 0x0e0b0d09, + 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, + 0xeebbdd99, 0xa3fd607f, 0xf79f2601, 0x5cbcf572, 0x44c53b66, + 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, + 0xd7cadc31, 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, + 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, + 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, 0x22ef9033, + 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, + 0xa528de7a, 0xda268eb7, 0x3fa4bfad, 0x2ce49d3a, 0x500d9278, + 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, + 0x82f5afc3, 0x9fbe805d, 0x697c93d0, 0x6fa92dd5, 0xcfb31225, + 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, + 0x6ef41859, 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, + 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, + 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, 0xf14a9804, + 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, + 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, 0x4c1b886a, 0xc1b81f2c, + 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, + 0xb35a1d67, 0x9252d2db, 0xe9335610, 0x6d1347d6, 0x9a8c61d7, + 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, + 0xe1ede51c, 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, + 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, + 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, 0x08deb30c, + 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, + 0xd04257b8}; + + private static int shift(int r, int shift) + { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private static int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private static int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + + private static int subWord(int x) + { + return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS+1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) + { + W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24); + i+=4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) + { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESFastEngine() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "AES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + bytes[index++] = (byte)C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + + bytes[index++] = (byte)C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + + bytes[index++] = (byte)C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + + bytes[index++] = (byte)C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + private void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + r = 1; + while (r < ROUNDS - 1) + { + r0 = T0[C0&255] ^ T1[(C1>>8)&255] ^ T2[(C2>>16)&255] ^ T3[(C3>>24)&255] ^ KW[r][0]; + r1 = T0[C1&255] ^ T1[(C2>>8)&255] ^ T2[(C3>>16)&255] ^ T3[(C0>>24)&255] ^ KW[r][1]; + r2 = T0[C2&255] ^ T1[(C3>>8)&255] ^ T2[(C0>>16)&255] ^ T3[(C1>>24)&255] ^ KW[r][2]; + r3 = T0[C3&255] ^ T1[(C0>>8)&255] ^ T2[(C1>>16)&255] ^ T3[(C2>>24)&255] ^ KW[r++][3]; + C0 = T0[r0&255] ^ T1[(r1>>8)&255] ^ T2[(r2>>16)&255] ^ T3[(r3>>24)&255] ^ KW[r][0]; + C1 = T0[r1&255] ^ T1[(r2>>8)&255] ^ T2[(r3>>16)&255] ^ T3[(r0>>24)&255] ^ KW[r][1]; + C2 = T0[r2&255] ^ T1[(r3>>8)&255] ^ T2[(r0>>16)&255] ^ T3[(r1>>24)&255] ^ KW[r][2]; + C3 = T0[r3&255] ^ T1[(r0>>8)&255] ^ T2[(r1>>16)&255] ^ T3[(r2>>24)&255] ^ KW[r++][3]; + } + + r0 = T0[C0&255] ^ T1[(C1>>8)&255] ^ T2[(C2>>16)&255] ^ T3[(C3>>24)&255] ^ KW[r][0]; + r1 = T0[C1&255] ^ T1[(C2>>8)&255] ^ T2[(C3>>16)&255] ^ T3[(C0>>24)&255] ^ KW[r][1]; + r2 = T0[C2&255] ^ T1[(C3>>8)&255] ^ T2[(C0>>16)&255] ^ T3[(C1>>24)&255] ^ KW[r][2]; + r3 = T0[C3&255] ^ T1[(C0>>8)&255] ^ T2[(C1>>16)&255] ^ T3[(C2>>24)&255] ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0]; + C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1]; + C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2]; + C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) + { + int r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + int r = ROUNDS-1; + + while (r>1) + { + r0 = Tinv0[C0&255] ^ Tinv1[(C3>>8)&255] ^ Tinv2[(C2>>16)&255] ^ Tinv3[(C1>>24)&255] ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ Tinv1[(C0>>8)&255] ^ Tinv2[(C3>>16)&255] ^ Tinv3[(C2>>24)&255] ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ Tinv1[(C1>>8)&255] ^ Tinv2[(C0>>16)&255] ^ Tinv3[(C3>>24)&255] ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ Tinv1[(C2>>8)&255] ^ Tinv2[(C1>>16)&255] ^ Tinv3[(C0>>24)&255] ^ KW[r--][3]; + C0 = Tinv0[r0&255] ^ Tinv1[(r3>>8)&255] ^ Tinv2[(r2>>16)&255] ^ Tinv3[(r1>>24)&255] ^ KW[r][0]; + C1 = Tinv0[r1&255] ^ Tinv1[(r0>>8)&255] ^ Tinv2[(r3>>16)&255] ^ Tinv3[(r2>>24)&255] ^ KW[r][1]; + C2 = Tinv0[r2&255] ^ Tinv1[(r1>>8)&255] ^ Tinv2[(r0>>16)&255] ^ Tinv3[(r3>>24)&255] ^ KW[r][2]; + C3 = Tinv0[r3&255] ^ Tinv1[(r2>>8)&255] ^ Tinv2[(r1>>16)&255] ^ Tinv3[(r0>>24)&255] ^ KW[r--][3]; + } + + r0 = Tinv0[C0&255] ^ Tinv1[(C3>>8)&255] ^ Tinv2[(C2>>16)&255] ^ Tinv3[(C1>>24)&255] ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ Tinv1[(C0>>8)&255] ^ Tinv2[(C3>>16)&255] ^ Tinv3[(C2>>24)&255] ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ Tinv1[(C1>>8)&255] ^ Tinv2[(C0>>16)&255] ^ Tinv3[(C3>>24)&255] ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ Tinv1[(C2>>8)&255] ^ Tinv2[(C1>>16)&255] ^ Tinv3[(C0>>24)&255] ^ KW[r][3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + C0 = (Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0]; + C1 = (Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24) ^ KW[0][1]; + C2 = (Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24) ^ KW[0][2]; + C3 = (Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24) ^ KW[0][3]; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java new file mode 100644 index 00000000..df8444b9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java @@ -0,0 +1,439 @@ +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; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + * <p> + * For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a> + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first + * + * The slowest version uses no static tables at all and computes the values + * in each round. + * <p> + * This file contains the slowest performance version with no static tables + * for round precomputation, but it has the smallest foot print. + * + */ +public class AESLightEngine + implements BlockCipher +{ + // The S box + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, + (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, + (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, + (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, + (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, + (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, + (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, + (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, + (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, + (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, + (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, + (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, + (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, + (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, + (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, + (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, + (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, + (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, + (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, + (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, + (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, + (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, + (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, + (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, + (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, + (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, + (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, + (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, + (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, + (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, + (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, + (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, + (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + private static int shift(int r, int shift) + { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private static int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private static int mcol(int x) + { + int f2 = FFmulX(x); + return f2 ^ shift(x ^ f2, 8) ^ shift(x, 16) ^ shift(x, 24); + } + + private static int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + + private static int subWord(int x) + { + return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS+1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) + { + W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24); + i+=4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) + { + int temp = W[(i-1)>>2][(i-1)&3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC)-1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i>>2][i&3] = W[(i - KC)>>2][(i-KC)&3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESLightEngine() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "AES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + bytes[index++] = (byte)C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + + bytes[index++] = (byte)C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + + bytes[index++] = (byte)C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + + bytes[index++] = (byte)C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + private void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + for (r = 1; r < ROUNDS - 1;) + { + r0 = mcol((S[C0&255]&255) ^ ((S[(C1>>8)&255]&255)<<8) ^ ((S[(C2>>16)&255]&255)<<16) ^ (S[(C3>>24)&255]<<24)) ^ KW[r][0]; + r1 = mcol((S[C1&255]&255) ^ ((S[(C2>>8)&255]&255)<<8) ^ ((S[(C3>>16)&255]&255)<<16) ^ (S[(C0>>24)&255]<<24)) ^ KW[r][1]; + r2 = mcol((S[C2&255]&255) ^ ((S[(C3>>8)&255]&255)<<8) ^ ((S[(C0>>16)&255]&255)<<16) ^ (S[(C1>>24)&255]<<24)) ^ KW[r][2]; + r3 = mcol((S[C3&255]&255) ^ ((S[(C0>>8)&255]&255)<<8) ^ ((S[(C1>>16)&255]&255)<<16) ^ (S[(C2>>24)&255]<<24)) ^ KW[r++][3]; + C0 = mcol((S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24)) ^ KW[r][0]; + C1 = mcol((S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24)) ^ KW[r][1]; + C2 = mcol((S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24)) ^ KW[r][2]; + C3 = mcol((S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24)) ^ KW[r++][3]; + } + + r0 = mcol((S[C0&255]&255) ^ ((S[(C1>>8)&255]&255)<<8) ^ ((S[(C2>>16)&255]&255)<<16) ^ (S[(C3>>24)&255]<<24)) ^ KW[r][0]; + r1 = mcol((S[C1&255]&255) ^ ((S[(C2>>8)&255]&255)<<8) ^ ((S[(C3>>16)&255]&255)<<16) ^ (S[(C0>>24)&255]<<24)) ^ KW[r][1]; + r2 = mcol((S[C2&255]&255) ^ ((S[(C3>>8)&255]&255)<<8) ^ ((S[(C0>>16)&255]&255)<<16) ^ (S[(C1>>24)&255]<<24)) ^ KW[r][2]; + r3 = mcol((S[C3&255]&255) ^ ((S[(C0>>8)&255]&255)<<8) ^ ((S[(C1>>16)&255]&255)<<16) ^ (S[(C2>>24)&255]<<24)) ^ KW[r++][3]; + + // the final round is a simple function of S + + C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0]; + C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1]; + C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2]; + C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + for (r = ROUNDS-1; r>1;) + { + r0 = inv_mcol((Si[C0&255]&255) ^ ((Si[(C3>>8)&255]&255)<<8) ^ ((Si[(C2>>16)&255]&255)<<16) ^ (Si[(C1>>24)&255]<<24)) ^ KW[r][0]; + r1 = inv_mcol((Si[C1&255]&255) ^ ((Si[(C0>>8)&255]&255)<<8) ^ ((Si[(C3>>16)&255]&255)<<16) ^ (Si[(C2>>24)&255]<<24)) ^ KW[r][1]; + r2 = inv_mcol((Si[C2&255]&255) ^ ((Si[(C1>>8)&255]&255)<<8) ^ ((Si[(C0>>16)&255]&255)<<16) ^ (Si[(C3>>24)&255]<<24)) ^ KW[r][2]; + r3 = inv_mcol((Si[C3&255]&255) ^ ((Si[(C2>>8)&255]&255)<<8) ^ ((Si[(C1>>16)&255]&255)<<16) ^ (Si[(C0>>24)&255]<<24)) ^ KW[r--][3]; + C0 = inv_mcol((Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24)) ^ KW[r][0]; + C1 = inv_mcol((Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24)) ^ KW[r][1]; + C2 = inv_mcol((Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24)) ^ KW[r][2]; + C3 = inv_mcol((Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24)) ^ KW[r--][3]; + } + + r0 = inv_mcol((Si[C0&255]&255) ^ ((Si[(C3>>8)&255]&255)<<8) ^ ((Si[(C2>>16)&255]&255)<<16) ^ (Si[(C1>>24)&255]<<24)) ^ KW[r][0]; + r1 = inv_mcol((Si[C1&255]&255) ^ ((Si[(C0>>8)&255]&255)<<8) ^ ((Si[(C3>>16)&255]&255)<<16) ^ (Si[(C2>>24)&255]<<24)) ^ KW[r][1]; + r2 = inv_mcol((Si[C2&255]&255) ^ ((Si[(C1>>8)&255]&255)<<8) ^ ((Si[(C0>>16)&255]&255)<<16) ^ (Si[(C3>>24)&255]<<24)) ^ KW[r][2]; + r3 = inv_mcol((Si[C3&255]&255) ^ ((Si[(C2>>8)&255]&255)<<8) ^ ((Si[(C1>>16)&255]&255)<<16) ^ (Si[(C0>>24)&255]<<24)) ^ KW[r][3]; + + // the final round's table is a simple function of Si + + C0 = (Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0]; + C1 = (Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24) ^ KW[0][1]; + C2 = (Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24) ^ KW[0][2]; + C3 = (Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24) ^ KW[0][3]; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java new file mode 100644 index 00000000..5d316ac4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java @@ -0,0 +1,16 @@ +package org.bouncycastle.crypto.engines; + +/** + * an implementation of the AES Key Wrapper from the NIST Key Wrap + * Specification. + * <p> + * For further details see: <a href="http://csrc.nist.gov/encryption/kms/key-wrap.pdf">http://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>. + */ +public class AESWrapEngine + extends RFC3394WrapEngine +{ + public AESWrapEngine() + { + super(new AESEngine()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java new file mode 100644 index 00000000..cfe7f1ff --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java @@ -0,0 +1,577 @@ +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; + +/** + * A class that provides Blowfish key encryption operations, + * such as encoding data and generating keys. + * All the algorithms herein are from Applied Cryptography + * and implement a simplified cryptography interface. + */ +public final class BlowfishEngine +implements BlockCipher +{ + private final static int[] + KP = { + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, + 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, + 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, + 0x9216D5D9, 0x8979FB1B + }, + + KS0 = { + 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, + 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, + 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, + 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, + 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, + 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, + 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, + 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, + 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, + 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, + 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, + 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, + 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677, + 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, + 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, + 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, + 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, + 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, + 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, + 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, + 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88, + 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, + 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, + 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, + 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, + 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, + 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, + 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, + 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09, + 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, + 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, + 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, + 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, + 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, + 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, + 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, + 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, + 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, + 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, + 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, + 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, + 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, + 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, + 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, + 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, + 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, + 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, + 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, + 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, + 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, + 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, + 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, + 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41, + 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, + 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, + 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, + 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A + }, + + KS1 = { + 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, + 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, + 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, + 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6, + 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, + 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, + 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, + 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, + 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, + 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7, + 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, + 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, + 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, + 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, + 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, + 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16, + 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, + 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, + 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, + 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, + 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, + 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960, + 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, + 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, + 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, + 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, + 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, + 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50, + 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, + 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, + 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, + 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, + 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, + 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0, + 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, + 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, + 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, + 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, + 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, + 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735, + 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, + 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, + 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 + }, + + KS2 = { + 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, + 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, + 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, + 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45, + 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, + 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, + 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, + 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, + 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, + 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB, + 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, + 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, + 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, + 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, + 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, + 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B, + 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, + 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, + 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, + 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, + 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, + 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B, + 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, + 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, + 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, + 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, + 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, + 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D, + 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, + 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, + 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, + 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, + 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, + 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633, + 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, + 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, + 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, + 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, + 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, + 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24, + 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, + 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, + 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 + }, + + KS3 = { + 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, + 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, + 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, + 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8, + 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, + 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, + 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, + 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, + 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, + 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51, + 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, + 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, + 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, + 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, + 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, + 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB, + 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, + 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, + 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, + 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, + 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, + 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47, + 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, + 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, + 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, + 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, + 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, + 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38, + 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, + 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, + 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, + 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, + 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, + 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D, + 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, + 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, + 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, + 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, + 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, + 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0, + 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, + 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, + 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 + }; + + //==================================== + // Useful constants + //==================================== + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS+2; + + private final int[] S0, S1, S2, S3; // the s-boxes + private final int[] P; // the p-array + + private boolean encrypting = false; + + private byte[] workingKey = null; + + public BlowfishEngine() + { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + this.encrypting = encrypting; + this.workingKey = ((KeyParameter)params).getKey(); + setKey(this.workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Blowfish init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Blowfish"; + } + + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish 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() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + private int F(int x) + { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) + ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + */ + private void processTable( + int xl, + int xr, + int[] table) + { + int size = table.length; + + for (int s = 0; s < size; s += 2) + { + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + table[s] = xr; + table[s + 1] = xl; + + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) + { + /* + * - comments are from _Applied Crypto_, Schneier, p338 + * please be careful comparing the two, AC numbers the + * arrays from 1, the enclosed code from 0. + * + * (1) + * Initialise the S-boxes and the P-array, with a fixed string + * This string contains the hexadecimal digits of pi (3.141...) + */ + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + + System.arraycopy(KP, 0, P, 0, P_SZ); + + /* + * (2) + * Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with the + * second 32-bits of the key, and so on for all bits of the key + * (up to P[17]). Repeatedly cycle through the key bits until the + * entire P-array has been XOR-ed with the key bits + */ + int keyLength = key.length; + int keyIndex = 0; + + for (int i=0; i < P_SZ; i++) + { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + for (int j=0; j < 4; j++) + { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) + { + keyIndex = 0; + } + } + // XOR the newly created 32 bit chunk onto the P-array + P[i] ^= data; + } + + /* + * (3) + * Encrypt the all-zero string with the Blowfish algorithm, using + * the subkeys described in (1) and (2) + * + * (4) + * Replace P1 and P2 with the output of step (3) + * + * (5) + * Encrypt the output of step(3) using the Blowfish algorithm, + * with the modified subkeys. + * + * (6) + * Replace P3 and P4 with the output of step (5) + * + * (7) + * Continue the process, replacing all elements of the P-array + * and then all four S-boxes in order, with the output of the + * continuously changing Blowfish algorithm + */ + + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + */ + private void encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex+4); + + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + */ + private void decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + + xl ^= P[ROUNDS + 1]; + + for (int i = ROUNDS; i > 0 ; i -= 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i - 1]; + } + + xr ^= P[0]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex+4); + } + + private int BytesTo32bits(byte[] b, int i) + { + return ((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff)); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset + 3] = (byte)in; + b[offset + 2] = (byte)(in >> 8); + b[offset + 1] = (byte)(in >> 16); + b[offset] = (byte)(in >> 24); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/CAST5Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/CAST5Engine.java new file mode 100644 index 00000000..5a8c7806 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/CAST5Engine.java @@ -0,0 +1,831 @@ +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; + +/** + * A class that provides CAST key encryption operations, + * such as encoding data and generating keys. + * + * All the algorithms herein are from the Internet RFC's + * + * RFC2144 - CAST5 (64bit block, 40-128bit key) + * RFC2612 - CAST6 (128bit block, 128-256bit key) + * + * and implement a simplified cryptography interface. + */ +public class CAST5Engine + implements BlockCipher +{ + protected final static int M32 = 0xffffffff; + + protected final static int[] + S1 = { +0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949, +0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e, +0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d, +0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0, +0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7, +0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935, +0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d, +0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50, +0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe, +0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3, +0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167, +0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291, +0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779, +0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2, +0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511, +0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d, +0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5, +0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324, +0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c, +0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc, +0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d, +0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96, +0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a, +0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d, +0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd, +0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6, +0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9, +0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872, +0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c, +0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e, +0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9, +0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf + }, + S2 = { +0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651, +0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3, +0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb, +0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806, +0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b, +0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359, +0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b, +0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c, +0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34, +0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb, +0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd, +0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860, +0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b, +0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304, +0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b, +0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf, +0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c, +0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13, +0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f, +0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6, +0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6, +0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58, +0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906, +0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d, +0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6, +0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4, +0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6, +0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f, +0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249, +0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa, +0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9, +0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1 + }, + S3 = { +0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90, +0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5, +0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e, +0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240, +0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5, +0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b, +0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71, +0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04, +0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82, +0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15, +0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2, +0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176, +0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148, +0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc, +0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341, +0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e, +0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51, +0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f, +0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a, +0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b, +0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b, +0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5, +0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45, +0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536, +0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc, +0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0, +0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69, +0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2, +0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49, +0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d, +0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a, +0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783 + }, + S4 = { +0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1, +0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf, +0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15, +0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121, +0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25, +0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5, +0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb, +0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5, +0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d, +0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6, +0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23, +0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003, +0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6, +0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119, +0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24, +0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a, +0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79, +0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df, +0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26, +0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab, +0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7, +0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417, +0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2, +0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2, +0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a, +0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919, +0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef, +0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876, +0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab, +0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04, +0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282, +0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2 + }, + S5 = { +0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f, +0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a, +0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff, +0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02, +0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a, +0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7, +0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9, +0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981, +0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774, +0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655, +0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2, +0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910, +0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1, +0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da, +0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049, +0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f, +0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba, +0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be, +0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3, +0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840, +0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4, +0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2, +0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7, +0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5, +0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e, +0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e, +0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801, +0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad, +0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0, +0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20, +0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8, +0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4 + }, + S6 = { +0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac, +0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138, +0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367, +0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98, +0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072, +0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3, +0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd, +0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8, +0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9, +0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54, +0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387, +0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc, +0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf, +0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf, +0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f, +0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289, +0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950, +0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f, +0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b, +0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be, +0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13, +0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976, +0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0, +0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891, +0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da, +0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc, +0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084, +0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25, +0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121, +0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5, +0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd, +0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f + }, + S7 = { +0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f, +0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de, +0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43, +0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19, +0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2, +0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516, +0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88, +0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816, +0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756, +0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a, +0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264, +0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688, +0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28, +0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3, +0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7, +0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06, +0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033, +0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a, +0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566, +0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509, +0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962, +0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e, +0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c, +0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c, +0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285, +0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301, +0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be, +0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767, +0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647, +0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914, +0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c, +0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3 + }, + S8 = { +0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5, +0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc, +0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd, +0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d, +0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2, +0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862, +0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc, +0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c, +0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e, +0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039, +0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8, +0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42, +0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5, +0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472, +0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225, +0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c, +0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb, +0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054, +0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70, +0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc, +0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c, +0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3, +0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4, +0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101, +0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f, +0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e, +0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a, +0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c, +0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384, +0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c, +0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82, +0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e + }; + + //==================================== + // Useful constants + //==================================== + + protected static final int MAX_ROUNDS = 16; + protected static final int RED_ROUNDS = 12; + + protected static final int BLOCK_SIZE = 8; // bytes = 64 bits + + protected int _Kr[] = new int[17]; // the rotating round key + protected int _Km[] = new int[17]; // the masking round key + + private boolean _encrypting = false; + + private byte[] _workingKey = null; + private int _rounds = MAX_ROUNDS; + + public CAST5Engine() + { + } + + /** + * initialise a CAST cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + _encrypting = encrypting; + _workingKey = ((KeyParameter)params).getKey(); + + setKey(_workingKey); + + return; + } + + throw new IllegalArgumentException("Invalid parameter passed to "+getAlgorithmName()+" init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "CAST5"; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (_workingKey == null) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + int blockSize = getBlockSize(); + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("Input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new OutputLengthException("Output buffer too short"); + } + + if (_encrypting) + { + return encryptBlock(in, inOff, out, outOff); + } + else + { + return decryptBlock(in, inOff, out, outOff); + } + } + + public void reset() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + /* + * Creates the subkeys using the same nomenclature + * as described in RFC2144. + * + * See section 2.4 + */ + protected void setKey(byte[] key) + { + /* + * Determine the key size here, if required + * + * if keysize <= 80bits, use 12 rounds instead of 16 + * if keysize < 128bits, pad with 0 + * + * Typical key sizes => 40, 64, 80, 128 + */ + + if (key.length < 11) + { + _rounds = RED_ROUNDS; + } + + int z[] = new int[16]; + int x[] = new int[16]; + + int z03, z47, z8B, zCF; + int x03, x47, x8B, xCF; + + /* copy the key into x */ + for (int i=0; i< key.length; i++) + { + x[i] = key[i] & 0xff; + } + + /* + * This will look different because the selection of + * bytes from the input key I've already chosen the + * correct int. + */ + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Km[ 1]= S5[z[0x8]] ^ S6[z[0x9]] ^ S7[z[0x7]] ^ S8[z[0x6]] ^ S5[z[0x2]]; + _Km[ 2]= S5[z[0xA]] ^ S6[z[0xB]] ^ S7[z[0x5]] ^ S8[z[0x4]] ^ S6[z[0x6]]; + _Km[ 3]= S5[z[0xC]] ^ S6[z[0xD]] ^ S7[z[0x3]] ^ S8[z[0x2]] ^ S7[z[0x9]]; + _Km[ 4]= S5[z[0xE]] ^ S6[z[0xF]] ^ S7[z[0x1]] ^ S8[z[0x0]] ^ S8[z[0xC]]; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Km[ 5]= S5[x[0x3]] ^ S6[x[0x2]] ^ S7[x[0xC]] ^ S8[x[0xD]] ^ S5[x[0x8]]; + _Km[ 6]= S5[x[0x1]] ^ S6[x[0x0]] ^ S7[x[0xE]] ^ S8[x[0xF]] ^ S6[x[0xD]]; + _Km[ 7]= S5[x[0x7]] ^ S6[x[0x6]] ^ S7[x[0x8]] ^ S8[x[0x9]] ^ S7[x[0x3]]; + _Km[ 8]= S5[x[0x5]] ^ S6[x[0x4]] ^ S7[x[0xA]] ^ S8[x[0xB]] ^ S8[x[0x7]]; + + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Km[ 9]= S5[z[0x3]] ^ S6[z[0x2]] ^ S7[z[0xC]] ^ S8[z[0xD]] ^ S5[z[0x9]]; + _Km[10]= S5[z[0x1]] ^ S6[z[0x0]] ^ S7[z[0xE]] ^ S8[z[0xF]] ^ S6[z[0xc]]; + _Km[11]= S5[z[0x7]] ^ S6[z[0x6]] ^ S7[z[0x8]] ^ S8[z[0x9]] ^ S7[z[0x2]]; + _Km[12]= S5[z[0x5]] ^ S6[z[0x4]] ^ S7[z[0xA]] ^ S8[z[0xB]] ^ S8[z[0x6]]; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Km[13]= S5[x[0x8]] ^ S6[x[0x9]] ^ S7[x[0x7]] ^ S8[x[0x6]] ^ S5[x[0x3]]; + _Km[14]= S5[x[0xA]] ^ S6[x[0xB]] ^ S7[x[0x5]] ^ S8[x[0x4]] ^ S6[x[0x7]]; + _Km[15]= S5[x[0xC]] ^ S6[x[0xD]] ^ S7[x[0x3]] ^ S8[x[0x2]] ^ S7[x[0x8]]; + _Km[16]= S5[x[0xE]] ^ S6[x[0xF]] ^ S7[x[0x1]] ^ S8[x[0x0]] ^ S8[x[0xD]]; + + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Kr[ 1]=(S5[z[0x8]]^S6[z[0x9]]^S7[z[0x7]]^S8[z[0x6]] ^ S5[z[0x2]])&0x1f; + _Kr[ 2]=(S5[z[0xA]]^S6[z[0xB]]^S7[z[0x5]]^S8[z[0x4]] ^ S6[z[0x6]])&0x1f; + _Kr[ 3]=(S5[z[0xC]]^S6[z[0xD]]^S7[z[0x3]]^S8[z[0x2]] ^ S7[z[0x9]])&0x1f; + _Kr[ 4]=(S5[z[0xE]]^S6[z[0xF]]^S7[z[0x1]]^S8[z[0x0]] ^ S8[z[0xC]])&0x1f; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Kr[ 5]=(S5[x[0x3]]^S6[x[0x2]]^S7[x[0xC]]^S8[x[0xD]]^S5[x[0x8]])&0x1f; + _Kr[ 6]=(S5[x[0x1]]^S6[x[0x0]]^S7[x[0xE]]^S8[x[0xF]]^S6[x[0xD]])&0x1f; + _Kr[ 7]=(S5[x[0x7]]^S6[x[0x6]]^S7[x[0x8]]^S8[x[0x9]]^S7[x[0x3]])&0x1f; + _Kr[ 8]=(S5[x[0x5]]^S6[x[0x4]]^S7[x[0xA]]^S8[x[0xB]]^S8[x[0x7]])&0x1f; + + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Kr[ 9]=(S5[z[0x3]]^S6[z[0x2]]^S7[z[0xC]]^S8[z[0xD]]^S5[z[0x9]])&0x1f; + _Kr[10]=(S5[z[0x1]]^S6[z[0x0]]^S7[z[0xE]]^S8[z[0xF]]^S6[z[0xc]])&0x1f; + _Kr[11]=(S5[z[0x7]]^S6[z[0x6]]^S7[z[0x8]]^S8[z[0x9]]^S7[z[0x2]])&0x1f; + _Kr[12]=(S5[z[0x5]]^S6[z[0x4]]^S7[z[0xA]]^S8[z[0xB]]^S8[z[0x6]])&0x1f; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Kr[13]=(S5[x[0x8]]^S6[x[0x9]]^S7[x[0x7]]^S8[x[0x6]]^S5[x[0x3]])&0x1f; + _Kr[14]=(S5[x[0xA]]^S6[x[0xB]]^S7[x[0x5]]^S8[x[0x4]]^S6[x[0x7]])&0x1f; + _Kr[15]=(S5[x[0xC]]^S6[x[0xD]]^S7[x[0x3]]^S8[x[0x2]]^S7[x[0x8]])&0x1f; + _Kr[16]=(S5[x[0xE]]^S6[x[0xF]]^S7[x[0x1]]^S8[x[0x0]]^S8[x[0xD]])&0x1f; + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + + int result[] = new int[2]; + + // process the input block + // batch the units up into a 32 bit chunk and go for it + // the array is in bytes, the increment is 8x8 bits = 64 + + int L0 = BytesTo32bits(src, srcIndex); + int R0 = BytesTo32bits(src, srcIndex + 4); + + CAST_Encipher(L0, R0, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex + 4); + + return BLOCK_SIZE; + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int result[] = new int[2]; + + // process the input block + // batch the units up into a 32 bit chunk and go for it + // the array is in bytes, the increment is 8x8 bits = 64 + int L16 = BytesTo32bits(src, srcIndex); + int R16 = BytesTo32bits(src, srcIndex+4); + + CAST_Decipher(L16, R16, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex+4); + + return BLOCK_SIZE; + } + + /** + * The first of the three processing functions for the + * encryption and decryption. + * + * @param D the input to be processed + * @param Kmi the mask to be used from Km[n] + * @param Kri the rotation value to be used + * + */ + protected final int F1(int D, int Kmi, int Kri) + { + int I = Kmi + D; + I = I << Kri | I >>> (32-Kri); + return ((S1[(I>>>24)&0xff]^S2[(I>>>16)&0xff])-S3[(I>>> 8)&0xff])+ + S4[I & 0xff]; + } + + /** + * The second of the three processing functions for the + * encryption and decryption. + * + * @param D the input to be processed + * @param Kmi the mask to be used from Km[n] + * @param Kri the rotation value to be used + * + */ + protected final int F2(int D, int Kmi, int Kri) + { + int I = Kmi ^ D; + I = I << Kri | I >>> (32-Kri); + return ((S1[(I>>>24)&0xff]-S2[(I>>>16)&0xff])+S3[(I>>> 8)&0xff])^ + S4[I & 0xff]; + } + + /** + * The third of the three processing functions for the + * encryption and decryption. + * + * @param D the input to be processed + * @param Kmi the mask to be used from Km[n] + * @param Kri the rotation value to be used + * + */ + protected final int F3(int D, int Kmi, int Kri) + { + int I = Kmi - D; + I = I << Kri | I >>> (32-Kri); + return ((S1[(I>>>24)&0xff]+S2[(I>>>16)&0xff])^S3[(I>>> 8)&0xff])- + S4[I & 0xff]; + } + + /** + * Does the 16 rounds to encrypt the block. + * + * @param L0 the LH-32bits of the plaintext block + * @param R0 the RH-32bits of the plaintext block + */ + protected final void CAST_Encipher(int L0, int R0, int result[]) + { + int Lp = L0; // the previous value, equiv to L[i-1] + int Rp = R0; // equivalent to R[i-1] + + /* + * numbering consistent with paper to make + * checking and validating easier + */ + int Li = L0, Ri = R0; + + for (int i = 1; i<=_rounds ; i++) + { + Lp = Li; + Rp = Ri; + + Li = Rp; + switch (i) + { + case 1: + case 4: + case 7: + case 10: + case 13: + case 16: + Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]); + break; + case 2: + case 5: + case 8: + case 11: + case 14: + Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]); + break; + case 3: + case 6: + case 9: + case 12: + case 15: + Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]); + break; + } + } + + result[0] = Ri; + result[1] = Li; + + return; + } + + protected final void CAST_Decipher(int L16, int R16, int result[]) + { + int Lp = L16; // the previous value, equiv to L[i-1] + int Rp = R16; // equivalent to R[i-1] + + /* + * numbering consistent with paper to make + * checking and validating easier + */ + int Li = L16, Ri = R16; + + for (int i = _rounds; i > 0; i--) + { + Lp = Li; + Rp = Ri; + + Li = Rp; + switch (i) + { + case 1: + case 4: + case 7: + case 10: + case 13: + case 16: + Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]); + break; + case 2: + case 5: + case 8: + case 11: + case 14: + Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]); + break; + case 3: + case 6: + case 9: + case 12: + case 15: + Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]); + break; + } + } + + result[0] = Ri; + result[1] = Li; + + return; + } + + protected final void Bits32ToInts(int in, int[] b, int offset) + { + b[offset + 3] = (in & 0xff); + b[offset + 2] = ((in >>> 8) & 0xff); + b[offset + 1] = ((in >>> 16) & 0xff); + b[offset] = ((in >>> 24) & 0xff); + } + + protected final int IntsTo32bits(int[] b, int i) + { + int rv = 0; + + rv = ((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff)); + + return rv; + } + + protected final void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset + 3] = (byte)in; + b[offset + 2] = (byte)(in >>> 8); + b[offset + 1] = (byte)(in >>> 16); + b[offset] = (byte)(in >>> 24); + } + + protected final int BytesTo32bits(byte[] b, int i) + { + return ((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/CAST6Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/CAST6Engine.java new file mode 100644 index 00000000..db57b503 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/CAST6Engine.java @@ -0,0 +1,296 @@ +package org.bouncycastle.crypto.engines; + + +/** + * A class that provides CAST6 key encryption operations, + * such as encoding data and generating keys. + * + * All the algorithms herein are from the Internet RFC + * + * RFC2612 - CAST6 (128bit block, 128-256bit key) + * + * and implement a simplified cryptography interface. + */ +public final class CAST6Engine extends CAST5Engine +{ + //==================================== + // Useful constants + //==================================== + + protected static final int ROUNDS = 12; + + protected static final int BLOCK_SIZE = 16; // bytes = 128 bits + + /* + * Put the round and mask keys into an array. + * Kr0[i] => _Kr[i*4 + 0] + */ + protected int _Kr[] = new int[ROUNDS*4]; // the rotating round key(s) + protected int _Km[] = new int[ROUNDS*4]; // the masking round key(s) + + /* + * Key setup + */ + protected int _Tr[] = new int[24 * 8]; + protected int _Tm[] = new int[24 * 8]; + + private int[] _workingKey = new int[8]; + + public CAST6Engine() + { + } + + public String getAlgorithmName() + { + return "CAST6"; + } + + public void reset() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + /* + * Creates the subkeys using the same nomenclature + * as described in RFC2612. + * + * See section 2.4 + */ + protected void setKey(byte[] key) + { + int Cm = 0x5a827999; + int Mm = 0x6ed9eba1; + int Cr = 19; + int Mr = 17; + + /* + * Determine the key size here, if required + * + * if keysize < 256 bytes, pad with 0 + * + * Typical key sizes => 128, 160, 192, 224, 256 + */ + for (int i=0; i< 24; i++) + { + for (int j=0; j< 8; j++) + { + _Tm[i*8 + j] = Cm; + Cm = (Cm + Mm); // mod 2^32; + + _Tr[i*8 + j] = Cr; + Cr = (Cr + Mr) & 0x1f; // mod 32 + } + } + + byte[] tmpKey = new byte[64]; + int length = key.length; + System.arraycopy(key, 0, tmpKey, 0, length); + + // now create ABCDEFGH + for (int i=0; i< 8; i++) + { + _workingKey[i] = BytesTo32bits(tmpKey, i*4); + } + + // Generate the key schedule + for (int i=0; i< 12; i++) + { + // KAPPA <- W2i(KAPPA) + int i2 = i*2 *8; + _workingKey[6] ^= F1(_workingKey[7], _Tm[i2 ], _Tr[i2 ]); + _workingKey[5] ^= F2(_workingKey[6], _Tm[i2+1], _Tr[i2+1]); + _workingKey[4] ^= F3(_workingKey[5], _Tm[i2+2], _Tr[i2+2]); + _workingKey[3] ^= F1(_workingKey[4], _Tm[i2+3], _Tr[i2+3]); + _workingKey[2] ^= F2(_workingKey[3], _Tm[i2+4], _Tr[i2+4]); + _workingKey[1] ^= F3(_workingKey[2], _Tm[i2+5], _Tr[i2+5]); + _workingKey[0] ^= F1(_workingKey[1], _Tm[i2+6], _Tr[i2+6]); + _workingKey[7] ^= F2(_workingKey[0], _Tm[i2+7], _Tr[i2+7]); + + // KAPPA <- W2i+1(KAPPA) + i2 = (i*2 + 1)*8; + _workingKey[6] ^= F1(_workingKey[7], _Tm[i2 ], _Tr[i2 ]); + _workingKey[5] ^= F2(_workingKey[6], _Tm[i2+1], _Tr[i2+1]); + _workingKey[4] ^= F3(_workingKey[5], _Tm[i2+2], _Tr[i2+2]); + _workingKey[3] ^= F1(_workingKey[4], _Tm[i2+3], _Tr[i2+3]); + _workingKey[2] ^= F2(_workingKey[3], _Tm[i2+4], _Tr[i2+4]); + _workingKey[1] ^= F3(_workingKey[2], _Tm[i2+5], _Tr[i2+5]); + _workingKey[0] ^= F1(_workingKey[1], _Tm[i2+6], _Tr[i2+6]); + _workingKey[7] ^= F2(_workingKey[0], _Tm[i2+7], _Tr[i2+7]); + + // Kr_(i) <- KAPPA + _Kr[i*4 ] = _workingKey[0] & 0x1f; + _Kr[i*4 + 1] = _workingKey[2] & 0x1f; + _Kr[i*4 + 2] = _workingKey[4] & 0x1f; + _Kr[i*4 + 3] = _workingKey[6] & 0x1f; + + + // Km_(i) <- KAPPA + _Km[i*4 ] = _workingKey[7]; + _Km[i*4 + 1] = _workingKey[5]; + _Km[i*4 + 2] = _workingKey[3]; + _Km[i*4 + 3] = _workingKey[1]; + } + + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + + int result[] = new int[4]; + + // process the input block + // batch the units up into 4x32 bit chunks and go for it + + int A = BytesTo32bits(src, srcIndex); + int B = BytesTo32bits(src, srcIndex + 4); + int C = BytesTo32bits(src, srcIndex + 8); + int D = BytesTo32bits(src, srcIndex + 12); + + CAST_Encipher(A, B, C, D, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex + 4); + Bits32ToBytes(result[2], dst, dstIndex + 8); + Bits32ToBytes(result[3], dst, dstIndex + 12); + + return BLOCK_SIZE; + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int result[] = new int[4]; + + // process the input block + // batch the units up into 4x32 bit chunks and go for it + int A = BytesTo32bits(src, srcIndex); + int B = BytesTo32bits(src, srcIndex + 4); + int C = BytesTo32bits(src, srcIndex + 8); + int D = BytesTo32bits(src, srcIndex + 12); + + CAST_Decipher(A, B, C, D, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex + 4); + Bits32ToBytes(result[2], dst, dstIndex + 8); + Bits32ToBytes(result[3], dst, dstIndex + 12); + + return BLOCK_SIZE; + } + + /** + * Does the 12 quad rounds rounds to encrypt the block. + * + * @param A the 00-31 bits of the plaintext block + * @param B the 32-63 bits of the plaintext block + * @param C the 64-95 bits of the plaintext block + * @param D the 96-127 bits of the plaintext block + * @param result the resulting ciphertext + */ + protected final void CAST_Encipher(int A, int B, int C, int D,int result[]) + { + int x; + for (int i=0; i< 6; i++) + { + x = i*4; + // BETA <- Qi(BETA) + C ^= F1(D, _Km[x], _Kr[x]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + + } + + for (int i=6; i<12; i++) + { + x = i*4; + // BETA <- QBARi(BETA) + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + C ^= F1(D, _Km[x], _Kr[x]); + + } + + result[0] = A; + result[1] = B; + result[2] = C; + result[3] = D; + } + + /** + * Does the 12 quad rounds rounds to decrypt the block. + * + * @param A the 00-31 bits of the ciphertext block + * @param B the 32-63 bits of the ciphertext block + * @param C the 64-95 bits of the ciphertext block + * @param D the 96-127 bits of the ciphertext block + * @param result the resulting plaintext + */ + protected final void CAST_Decipher(int A, int B, int C, int D,int result[]) + { + int x; + for (int i=0; i< 6; i++) + { + x = (11-i)*4; + // BETA <- Qi(BETA) + C ^= F1(D, _Km[x], _Kr[x]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + + } + + for (int i=6; i<12; i++) + { + x = (11-i)*4; + // BETA <- QBARi(BETA) + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + C ^= F1(D, _Km[x], _Kr[x]); + + } + + result[0] = A; + result[1] = B; + result[2] = C; + result[3] = D; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaEngine.java new file mode 100644 index 00000000..a486e1b5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaEngine.java @@ -0,0 +1,684 @@ +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; + +/** + * Camellia - based on RFC 3713. + */ +public class CamelliaEngine + implements BlockCipher +{ + private boolean initialised = false; + private boolean _keyIs128; + + private static final int BLOCK_SIZE = 16; + private static final int MASK8 = 0xff; + + private int[] subkey = new int[24 * 4]; + private int[] kw = new int[4 * 2]; // for whitening + private int[] ke = new int[6 * 2]; // for FL and FL^(-1) + private int[] state = new int[4]; // for encryption and decryption + + private static final int SIGMA[] = { + 0xa09e667f, 0x3bcc908b, + 0xb67ae858, 0x4caa73b2, + 0xc6ef372f, 0xe94f82be, + 0x54ff53a5, 0xf1d36f1c, + 0x10e527fa, 0xde682d1d, + 0xb05688c2, 0xb3e6c1fd + }; + + /* + * + * S-box data + * + */ + private static final int SBOX1_1110[] = { + 0x70707000, 0x82828200, 0x2c2c2c00, 0xececec00, 0xb3b3b300, 0x27272700, + 0xc0c0c000, 0xe5e5e500, 0xe4e4e400, 0x85858500, 0x57575700, 0x35353500, + 0xeaeaea00, 0x0c0c0c00, 0xaeaeae00, 0x41414100, 0x23232300, 0xefefef00, + 0x6b6b6b00, 0x93939300, 0x45454500, 0x19191900, 0xa5a5a500, 0x21212100, + 0xededed00, 0x0e0e0e00, 0x4f4f4f00, 0x4e4e4e00, 0x1d1d1d00, 0x65656500, + 0x92929200, 0xbdbdbd00, 0x86868600, 0xb8b8b800, 0xafafaf00, 0x8f8f8f00, + 0x7c7c7c00, 0xebebeb00, 0x1f1f1f00, 0xcecece00, 0x3e3e3e00, 0x30303000, + 0xdcdcdc00, 0x5f5f5f00, 0x5e5e5e00, 0xc5c5c500, 0x0b0b0b00, 0x1a1a1a00, + 0xa6a6a600, 0xe1e1e100, 0x39393900, 0xcacaca00, 0xd5d5d500, 0x47474700, + 0x5d5d5d00, 0x3d3d3d00, 0xd9d9d900, 0x01010100, 0x5a5a5a00, 0xd6d6d600, + 0x51515100, 0x56565600, 0x6c6c6c00, 0x4d4d4d00, 0x8b8b8b00, 0x0d0d0d00, + 0x9a9a9a00, 0x66666600, 0xfbfbfb00, 0xcccccc00, 0xb0b0b000, 0x2d2d2d00, + 0x74747400, 0x12121200, 0x2b2b2b00, 0x20202000, 0xf0f0f000, 0xb1b1b100, + 0x84848400, 0x99999900, 0xdfdfdf00, 0x4c4c4c00, 0xcbcbcb00, 0xc2c2c200, + 0x34343400, 0x7e7e7e00, 0x76767600, 0x05050500, 0x6d6d6d00, 0xb7b7b700, + 0xa9a9a900, 0x31313100, 0xd1d1d100, 0x17171700, 0x04040400, 0xd7d7d700, + 0x14141400, 0x58585800, 0x3a3a3a00, 0x61616100, 0xdedede00, 0x1b1b1b00, + 0x11111100, 0x1c1c1c00, 0x32323200, 0x0f0f0f00, 0x9c9c9c00, 0x16161600, + 0x53535300, 0x18181800, 0xf2f2f200, 0x22222200, 0xfefefe00, 0x44444400, + 0xcfcfcf00, 0xb2b2b200, 0xc3c3c300, 0xb5b5b500, 0x7a7a7a00, 0x91919100, + 0x24242400, 0x08080800, 0xe8e8e800, 0xa8a8a800, 0x60606000, 0xfcfcfc00, + 0x69696900, 0x50505000, 0xaaaaaa00, 0xd0d0d000, 0xa0a0a000, 0x7d7d7d00, + 0xa1a1a100, 0x89898900, 0x62626200, 0x97979700, 0x54545400, 0x5b5b5b00, + 0x1e1e1e00, 0x95959500, 0xe0e0e000, 0xffffff00, 0x64646400, 0xd2d2d200, + 0x10101000, 0xc4c4c400, 0x00000000, 0x48484800, 0xa3a3a300, 0xf7f7f700, + 0x75757500, 0xdbdbdb00, 0x8a8a8a00, 0x03030300, 0xe6e6e600, 0xdadada00, + 0x09090900, 0x3f3f3f00, 0xdddddd00, 0x94949400, 0x87878700, 0x5c5c5c00, + 0x83838300, 0x02020200, 0xcdcdcd00, 0x4a4a4a00, 0x90909000, 0x33333300, + 0x73737300, 0x67676700, 0xf6f6f600, 0xf3f3f300, 0x9d9d9d00, 0x7f7f7f00, + 0xbfbfbf00, 0xe2e2e200, 0x52525200, 0x9b9b9b00, 0xd8d8d800, 0x26262600, + 0xc8c8c800, 0x37373700, 0xc6c6c600, 0x3b3b3b00, 0x81818100, 0x96969600, + 0x6f6f6f00, 0x4b4b4b00, 0x13131300, 0xbebebe00, 0x63636300, 0x2e2e2e00, + 0xe9e9e900, 0x79797900, 0xa7a7a700, 0x8c8c8c00, 0x9f9f9f00, 0x6e6e6e00, + 0xbcbcbc00, 0x8e8e8e00, 0x29292900, 0xf5f5f500, 0xf9f9f900, 0xb6b6b600, + 0x2f2f2f00, 0xfdfdfd00, 0xb4b4b400, 0x59595900, 0x78787800, 0x98989800, + 0x06060600, 0x6a6a6a00, 0xe7e7e700, 0x46464600, 0x71717100, 0xbababa00, + 0xd4d4d400, 0x25252500, 0xababab00, 0x42424200, 0x88888800, 0xa2a2a200, + 0x8d8d8d00, 0xfafafa00, 0x72727200, 0x07070700, 0xb9b9b900, 0x55555500, + 0xf8f8f800, 0xeeeeee00, 0xacacac00, 0x0a0a0a00, 0x36363600, 0x49494900, + 0x2a2a2a00, 0x68686800, 0x3c3c3c00, 0x38383800, 0xf1f1f100, 0xa4a4a400, + 0x40404000, 0x28282800, 0xd3d3d300, 0x7b7b7b00, 0xbbbbbb00, 0xc9c9c900, + 0x43434300, 0xc1c1c100, 0x15151500, 0xe3e3e300, 0xadadad00, 0xf4f4f400, + 0x77777700, 0xc7c7c700, 0x80808000, 0x9e9e9e00 + }; + + private static final int SBOX4_4404[] = { + 0x70700070, 0x2c2c002c, 0xb3b300b3, 0xc0c000c0, 0xe4e400e4, 0x57570057, + 0xeaea00ea, 0xaeae00ae, 0x23230023, 0x6b6b006b, 0x45450045, 0xa5a500a5, + 0xeded00ed, 0x4f4f004f, 0x1d1d001d, 0x92920092, 0x86860086, 0xafaf00af, + 0x7c7c007c, 0x1f1f001f, 0x3e3e003e, 0xdcdc00dc, 0x5e5e005e, 0x0b0b000b, + 0xa6a600a6, 0x39390039, 0xd5d500d5, 0x5d5d005d, 0xd9d900d9, 0x5a5a005a, + 0x51510051, 0x6c6c006c, 0x8b8b008b, 0x9a9a009a, 0xfbfb00fb, 0xb0b000b0, + 0x74740074, 0x2b2b002b, 0xf0f000f0, 0x84840084, 0xdfdf00df, 0xcbcb00cb, + 0x34340034, 0x76760076, 0x6d6d006d, 0xa9a900a9, 0xd1d100d1, 0x04040004, + 0x14140014, 0x3a3a003a, 0xdede00de, 0x11110011, 0x32320032, 0x9c9c009c, + 0x53530053, 0xf2f200f2, 0xfefe00fe, 0xcfcf00cf, 0xc3c300c3, 0x7a7a007a, + 0x24240024, 0xe8e800e8, 0x60600060, 0x69690069, 0xaaaa00aa, 0xa0a000a0, + 0xa1a100a1, 0x62620062, 0x54540054, 0x1e1e001e, 0xe0e000e0, 0x64640064, + 0x10100010, 0x00000000, 0xa3a300a3, 0x75750075, 0x8a8a008a, 0xe6e600e6, + 0x09090009, 0xdddd00dd, 0x87870087, 0x83830083, 0xcdcd00cd, 0x90900090, + 0x73730073, 0xf6f600f6, 0x9d9d009d, 0xbfbf00bf, 0x52520052, 0xd8d800d8, + 0xc8c800c8, 0xc6c600c6, 0x81810081, 0x6f6f006f, 0x13130013, 0x63630063, + 0xe9e900e9, 0xa7a700a7, 0x9f9f009f, 0xbcbc00bc, 0x29290029, 0xf9f900f9, + 0x2f2f002f, 0xb4b400b4, 0x78780078, 0x06060006, 0xe7e700e7, 0x71710071, + 0xd4d400d4, 0xabab00ab, 0x88880088, 0x8d8d008d, 0x72720072, 0xb9b900b9, + 0xf8f800f8, 0xacac00ac, 0x36360036, 0x2a2a002a, 0x3c3c003c, 0xf1f100f1, + 0x40400040, 0xd3d300d3, 0xbbbb00bb, 0x43430043, 0x15150015, 0xadad00ad, + 0x77770077, 0x80800080, 0x82820082, 0xecec00ec, 0x27270027, 0xe5e500e5, + 0x85850085, 0x35350035, 0x0c0c000c, 0x41410041, 0xefef00ef, 0x93930093, + 0x19190019, 0x21210021, 0x0e0e000e, 0x4e4e004e, 0x65650065, 0xbdbd00bd, + 0xb8b800b8, 0x8f8f008f, 0xebeb00eb, 0xcece00ce, 0x30300030, 0x5f5f005f, + 0xc5c500c5, 0x1a1a001a, 0xe1e100e1, 0xcaca00ca, 0x47470047, 0x3d3d003d, + 0x01010001, 0xd6d600d6, 0x56560056, 0x4d4d004d, 0x0d0d000d, 0x66660066, + 0xcccc00cc, 0x2d2d002d, 0x12120012, 0x20200020, 0xb1b100b1, 0x99990099, + 0x4c4c004c, 0xc2c200c2, 0x7e7e007e, 0x05050005, 0xb7b700b7, 0x31310031, + 0x17170017, 0xd7d700d7, 0x58580058, 0x61610061, 0x1b1b001b, 0x1c1c001c, + 0x0f0f000f, 0x16160016, 0x18180018, 0x22220022, 0x44440044, 0xb2b200b2, + 0xb5b500b5, 0x91910091, 0x08080008, 0xa8a800a8, 0xfcfc00fc, 0x50500050, + 0xd0d000d0, 0x7d7d007d, 0x89890089, 0x97970097, 0x5b5b005b, 0x95950095, + 0xffff00ff, 0xd2d200d2, 0xc4c400c4, 0x48480048, 0xf7f700f7, 0xdbdb00db, + 0x03030003, 0xdada00da, 0x3f3f003f, 0x94940094, 0x5c5c005c, 0x02020002, + 0x4a4a004a, 0x33330033, 0x67670067, 0xf3f300f3, 0x7f7f007f, 0xe2e200e2, + 0x9b9b009b, 0x26260026, 0x37370037, 0x3b3b003b, 0x96960096, 0x4b4b004b, + 0xbebe00be, 0x2e2e002e, 0x79790079, 0x8c8c008c, 0x6e6e006e, 0x8e8e008e, + 0xf5f500f5, 0xb6b600b6, 0xfdfd00fd, 0x59590059, 0x98980098, 0x6a6a006a, + 0x46460046, 0xbaba00ba, 0x25250025, 0x42420042, 0xa2a200a2, 0xfafa00fa, + 0x07070007, 0x55550055, 0xeeee00ee, 0x0a0a000a, 0x49490049, 0x68680068, + 0x38380038, 0xa4a400a4, 0x28280028, 0x7b7b007b, 0xc9c900c9, 0xc1c100c1, + 0xe3e300e3, 0xf4f400f4, 0xc7c700c7, 0x9e9e009e + }; + + private static final int SBOX2_0222[] = { + 0x00e0e0e0, 0x00050505, 0x00585858, 0x00d9d9d9, 0x00676767, 0x004e4e4e, + 0x00818181, 0x00cbcbcb, 0x00c9c9c9, 0x000b0b0b, 0x00aeaeae, 0x006a6a6a, + 0x00d5d5d5, 0x00181818, 0x005d5d5d, 0x00828282, 0x00464646, 0x00dfdfdf, + 0x00d6d6d6, 0x00272727, 0x008a8a8a, 0x00323232, 0x004b4b4b, 0x00424242, + 0x00dbdbdb, 0x001c1c1c, 0x009e9e9e, 0x009c9c9c, 0x003a3a3a, 0x00cacaca, + 0x00252525, 0x007b7b7b, 0x000d0d0d, 0x00717171, 0x005f5f5f, 0x001f1f1f, + 0x00f8f8f8, 0x00d7d7d7, 0x003e3e3e, 0x009d9d9d, 0x007c7c7c, 0x00606060, + 0x00b9b9b9, 0x00bebebe, 0x00bcbcbc, 0x008b8b8b, 0x00161616, 0x00343434, + 0x004d4d4d, 0x00c3c3c3, 0x00727272, 0x00959595, 0x00ababab, 0x008e8e8e, + 0x00bababa, 0x007a7a7a, 0x00b3b3b3, 0x00020202, 0x00b4b4b4, 0x00adadad, + 0x00a2a2a2, 0x00acacac, 0x00d8d8d8, 0x009a9a9a, 0x00171717, 0x001a1a1a, + 0x00353535, 0x00cccccc, 0x00f7f7f7, 0x00999999, 0x00616161, 0x005a5a5a, + 0x00e8e8e8, 0x00242424, 0x00565656, 0x00404040, 0x00e1e1e1, 0x00636363, + 0x00090909, 0x00333333, 0x00bfbfbf, 0x00989898, 0x00979797, 0x00858585, + 0x00686868, 0x00fcfcfc, 0x00ececec, 0x000a0a0a, 0x00dadada, 0x006f6f6f, + 0x00535353, 0x00626262, 0x00a3a3a3, 0x002e2e2e, 0x00080808, 0x00afafaf, + 0x00282828, 0x00b0b0b0, 0x00747474, 0x00c2c2c2, 0x00bdbdbd, 0x00363636, + 0x00222222, 0x00383838, 0x00646464, 0x001e1e1e, 0x00393939, 0x002c2c2c, + 0x00a6a6a6, 0x00303030, 0x00e5e5e5, 0x00444444, 0x00fdfdfd, 0x00888888, + 0x009f9f9f, 0x00656565, 0x00878787, 0x006b6b6b, 0x00f4f4f4, 0x00232323, + 0x00484848, 0x00101010, 0x00d1d1d1, 0x00515151, 0x00c0c0c0, 0x00f9f9f9, + 0x00d2d2d2, 0x00a0a0a0, 0x00555555, 0x00a1a1a1, 0x00414141, 0x00fafafa, + 0x00434343, 0x00131313, 0x00c4c4c4, 0x002f2f2f, 0x00a8a8a8, 0x00b6b6b6, + 0x003c3c3c, 0x002b2b2b, 0x00c1c1c1, 0x00ffffff, 0x00c8c8c8, 0x00a5a5a5, + 0x00202020, 0x00898989, 0x00000000, 0x00909090, 0x00474747, 0x00efefef, + 0x00eaeaea, 0x00b7b7b7, 0x00151515, 0x00060606, 0x00cdcdcd, 0x00b5b5b5, + 0x00121212, 0x007e7e7e, 0x00bbbbbb, 0x00292929, 0x000f0f0f, 0x00b8b8b8, + 0x00070707, 0x00040404, 0x009b9b9b, 0x00949494, 0x00212121, 0x00666666, + 0x00e6e6e6, 0x00cecece, 0x00ededed, 0x00e7e7e7, 0x003b3b3b, 0x00fefefe, + 0x007f7f7f, 0x00c5c5c5, 0x00a4a4a4, 0x00373737, 0x00b1b1b1, 0x004c4c4c, + 0x00919191, 0x006e6e6e, 0x008d8d8d, 0x00767676, 0x00030303, 0x002d2d2d, + 0x00dedede, 0x00969696, 0x00262626, 0x007d7d7d, 0x00c6c6c6, 0x005c5c5c, + 0x00d3d3d3, 0x00f2f2f2, 0x004f4f4f, 0x00191919, 0x003f3f3f, 0x00dcdcdc, + 0x00797979, 0x001d1d1d, 0x00525252, 0x00ebebeb, 0x00f3f3f3, 0x006d6d6d, + 0x005e5e5e, 0x00fbfbfb, 0x00696969, 0x00b2b2b2, 0x00f0f0f0, 0x00313131, + 0x000c0c0c, 0x00d4d4d4, 0x00cfcfcf, 0x008c8c8c, 0x00e2e2e2, 0x00757575, + 0x00a9a9a9, 0x004a4a4a, 0x00575757, 0x00848484, 0x00111111, 0x00454545, + 0x001b1b1b, 0x00f5f5f5, 0x00e4e4e4, 0x000e0e0e, 0x00737373, 0x00aaaaaa, + 0x00f1f1f1, 0x00dddddd, 0x00595959, 0x00141414, 0x006c6c6c, 0x00929292, + 0x00545454, 0x00d0d0d0, 0x00787878, 0x00707070, 0x00e3e3e3, 0x00494949, + 0x00808080, 0x00505050, 0x00a7a7a7, 0x00f6f6f6, 0x00777777, 0x00939393, + 0x00868686, 0x00838383, 0x002a2a2a, 0x00c7c7c7, 0x005b5b5b, 0x00e9e9e9, + 0x00eeeeee, 0x008f8f8f, 0x00010101, 0x003d3d3d + }; + + private static final int SBOX3_3033[] = { + 0x38003838, 0x41004141, 0x16001616, 0x76007676, 0xd900d9d9, 0x93009393, + 0x60006060, 0xf200f2f2, 0x72007272, 0xc200c2c2, 0xab00abab, 0x9a009a9a, + 0x75007575, 0x06000606, 0x57005757, 0xa000a0a0, 0x91009191, 0xf700f7f7, + 0xb500b5b5, 0xc900c9c9, 0xa200a2a2, 0x8c008c8c, 0xd200d2d2, 0x90009090, + 0xf600f6f6, 0x07000707, 0xa700a7a7, 0x27002727, 0x8e008e8e, 0xb200b2b2, + 0x49004949, 0xde00dede, 0x43004343, 0x5c005c5c, 0xd700d7d7, 0xc700c7c7, + 0x3e003e3e, 0xf500f5f5, 0x8f008f8f, 0x67006767, 0x1f001f1f, 0x18001818, + 0x6e006e6e, 0xaf00afaf, 0x2f002f2f, 0xe200e2e2, 0x85008585, 0x0d000d0d, + 0x53005353, 0xf000f0f0, 0x9c009c9c, 0x65006565, 0xea00eaea, 0xa300a3a3, + 0xae00aeae, 0x9e009e9e, 0xec00ecec, 0x80008080, 0x2d002d2d, 0x6b006b6b, + 0xa800a8a8, 0x2b002b2b, 0x36003636, 0xa600a6a6, 0xc500c5c5, 0x86008686, + 0x4d004d4d, 0x33003333, 0xfd00fdfd, 0x66006666, 0x58005858, 0x96009696, + 0x3a003a3a, 0x09000909, 0x95009595, 0x10001010, 0x78007878, 0xd800d8d8, + 0x42004242, 0xcc00cccc, 0xef00efef, 0x26002626, 0xe500e5e5, 0x61006161, + 0x1a001a1a, 0x3f003f3f, 0x3b003b3b, 0x82008282, 0xb600b6b6, 0xdb00dbdb, + 0xd400d4d4, 0x98009898, 0xe800e8e8, 0x8b008b8b, 0x02000202, 0xeb00ebeb, + 0x0a000a0a, 0x2c002c2c, 0x1d001d1d, 0xb000b0b0, 0x6f006f6f, 0x8d008d8d, + 0x88008888, 0x0e000e0e, 0x19001919, 0x87008787, 0x4e004e4e, 0x0b000b0b, + 0xa900a9a9, 0x0c000c0c, 0x79007979, 0x11001111, 0x7f007f7f, 0x22002222, + 0xe700e7e7, 0x59005959, 0xe100e1e1, 0xda00dada, 0x3d003d3d, 0xc800c8c8, + 0x12001212, 0x04000404, 0x74007474, 0x54005454, 0x30003030, 0x7e007e7e, + 0xb400b4b4, 0x28002828, 0x55005555, 0x68006868, 0x50005050, 0xbe00bebe, + 0xd000d0d0, 0xc400c4c4, 0x31003131, 0xcb00cbcb, 0x2a002a2a, 0xad00adad, + 0x0f000f0f, 0xca00caca, 0x70007070, 0xff00ffff, 0x32003232, 0x69006969, + 0x08000808, 0x62006262, 0x00000000, 0x24002424, 0xd100d1d1, 0xfb00fbfb, + 0xba00baba, 0xed00eded, 0x45004545, 0x81008181, 0x73007373, 0x6d006d6d, + 0x84008484, 0x9f009f9f, 0xee00eeee, 0x4a004a4a, 0xc300c3c3, 0x2e002e2e, + 0xc100c1c1, 0x01000101, 0xe600e6e6, 0x25002525, 0x48004848, 0x99009999, + 0xb900b9b9, 0xb300b3b3, 0x7b007b7b, 0xf900f9f9, 0xce00cece, 0xbf00bfbf, + 0xdf00dfdf, 0x71007171, 0x29002929, 0xcd00cdcd, 0x6c006c6c, 0x13001313, + 0x64006464, 0x9b009b9b, 0x63006363, 0x9d009d9d, 0xc000c0c0, 0x4b004b4b, + 0xb700b7b7, 0xa500a5a5, 0x89008989, 0x5f005f5f, 0xb100b1b1, 0x17001717, + 0xf400f4f4, 0xbc00bcbc, 0xd300d3d3, 0x46004646, 0xcf00cfcf, 0x37003737, + 0x5e005e5e, 0x47004747, 0x94009494, 0xfa00fafa, 0xfc00fcfc, 0x5b005b5b, + 0x97009797, 0xfe00fefe, 0x5a005a5a, 0xac00acac, 0x3c003c3c, 0x4c004c4c, + 0x03000303, 0x35003535, 0xf300f3f3, 0x23002323, 0xb800b8b8, 0x5d005d5d, + 0x6a006a6a, 0x92009292, 0xd500d5d5, 0x21002121, 0x44004444, 0x51005151, + 0xc600c6c6, 0x7d007d7d, 0x39003939, 0x83008383, 0xdc00dcdc, 0xaa00aaaa, + 0x7c007c7c, 0x77007777, 0x56005656, 0x05000505, 0x1b001b1b, 0xa400a4a4, + 0x15001515, 0x34003434, 0x1e001e1e, 0x1c001c1c, 0xf800f8f8, 0x52005252, + 0x20002020, 0x14001414, 0xe900e9e9, 0xbd00bdbd, 0xdd00dddd, 0xe400e4e4, + 0xa100a1a1, 0xe000e0e0, 0x8a008a8a, 0xf100f1f1, 0xd600d6d6, 0x7a007a7a, + 0xbb00bbbb, 0xe300e3e3, 0x40004040, 0x4f004f4f + }; + + private static int rightRotate(int x, int s) + { + return (((x) >>> (s)) + ((x) << (32 - s))); + } + + private static int leftRotate(int x, int s) + { + return ((x) << (s)) + ((x) >>> (32 - s)); + } + + private static void roldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[2 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[0 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private static void roldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[2 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[0 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private int bytes2int(byte[] src, int offset) + { + int word = 0; + + for (int i = 0; i < 4; i++) + { + word = (word << 8) + (src[i + offset] & MASK8); + } + return word; + } + + private void int2bytes(int word, byte[] dst, int offset) + { + for (int i = 0; i < 4; i++) + { + dst[(3 - i) + offset] = (byte)word; + word >>>= 8; + } + } + + private void camelliaF2(int[] s, int[] skey, int keyoff) + { + int t1, t2, u, v; + + t1 = s[0] ^ skey[0 + keyoff]; + u = SBOX4_4404[t1 & MASK8]; + u ^= SBOX3_3033[(t1 >>> 8) & MASK8]; + u ^= SBOX2_0222[(t1 >>> 16) & MASK8]; + u ^= SBOX1_1110[(t1 >>> 24) & MASK8]; + t2 = s[1] ^ skey[1 + keyoff]; + v = SBOX1_1110[t2 & MASK8]; + v ^= SBOX4_4404[(t2 >>> 8) & MASK8]; + v ^= SBOX3_3033[(t2 >>> 16) & MASK8]; + v ^= SBOX2_0222[(t2 >>> 24) & MASK8]; + + s[2] ^= u ^ v; + s[3] ^= u ^ v ^ rightRotate(u, 8); + + t1 = s[2] ^ skey[2 + keyoff]; + u = SBOX4_4404[t1 & MASK8]; + u ^= SBOX3_3033[(t1 >>> 8) & MASK8]; + u ^= SBOX2_0222[(t1 >>> 16) & MASK8]; + u ^= SBOX1_1110[(t1 >>> 24) & MASK8]; + t2 = s[3] ^ skey[3 + keyoff]; + v = SBOX1_1110[t2 & MASK8]; + v ^= SBOX4_4404[(t2 >>> 8) & MASK8]; + v ^= SBOX3_3033[(t2 >>> 16) & MASK8]; + v ^= SBOX2_0222[(t2 >>> 24) & MASK8]; + + s[0] ^= u ^ v; + s[1] ^= u ^ v ^ rightRotate(u, 8); + } + + private void camelliaFLs(int[] s, int[] fkey, int keyoff) + { + + s[1] ^= leftRotate(s[0] & fkey[0 + keyoff], 1); + s[0] ^= fkey[1 + keyoff] | s[1]; + + s[2] ^= fkey[3 + keyoff] | s[3]; + s[3] ^= leftRotate(fkey[2 + keyoff] & s[2], 1); + } + + private void setKey(boolean forEncryption, byte[] key) + { + int[] k = new int[8]; + int[] ka = new int[4]; + int[] kb = new int[4]; + int[] t = new int[4]; + + switch (key.length) + { + case 16: + _keyIs128 = true; + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = k[5] = k[6] = k[7] = 0; + break; + case 24: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = ~k[4]; + k[7] = ~k[5]; + _keyIs128 = false; + break; + case 32: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = bytes2int(key, 24); + k[7] = bytes2int(key, 28); + _keyIs128 = false; + break; + default: + throw new + IllegalArgumentException("key sizes are only 16/24/32 bytes."); + } + + for (int i = 0; i < 4; i++) + { + ka[i] = k[i] ^ k[i + 4]; + } + /* compute KA */ + camelliaF2(ka, SIGMA, 0); + for (int i = 0; i < 4; i++) + { + ka[i] ^= k[i]; + } + camelliaF2(ka, SIGMA, 4); + + if (_keyIs128) + { + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldq(15, k, 0, subkey, 4); + roldq(30, k, 0, subkey, 12); + roldq(15, k, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + roldq(17, k, 0, ke, 4); + roldq(17, k, 0, subkey, 24); + roldq(17, k, 0, subkey, 32); + /* KA dependant keys */ + subkey[0] = ka[0]; + subkey[1] = ka[1]; + subkey[2] = ka[2]; + subkey[3] = ka[3]; + roldq(15, ka, 0, subkey, 8); + roldq(15, ka, 0, ke, 0); + roldq(15, ka, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + roldq(15, ka, 0, subkey, 20); + roldqo32(34, ka, 0, subkey, 28); + roldq(17, ka, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldq(15, k, 0, subkey, 28); + decroldq(30, k, 0, subkey, 20); + decroldq(15, k, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + decroldq(17, k, 0, ke, 0); + decroldq(17, k, 0, subkey, 8); + decroldq(17, k, 0, subkey, 0); + /* KA dependant keys */ + subkey[34] = ka[0]; + subkey[35] = ka[1]; + subkey[32] = ka[2]; + subkey[33] = ka[3]; + decroldq(15, ka, 0, subkey, 24); + decroldq(15, ka, 0, ke, 4); + decroldq(15, ka, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + decroldq(15, ka, 0, subkey, 12); + decroldqo32(34, ka, 0, subkey, 4); + roldq(17, ka, 0, kw, 0); + } + } + else + { // 192bit or 256bit + /* compute KB */ + for (int i = 0; i < 4; i++) + { + kb[i] = ka[i] ^ k[i + 4]; + } + camelliaF2(kb, SIGMA, 8); + + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldqo32(45, k, 0, subkey, 16); + roldq(15, k, 0, ke, 4); + roldq(17, k, 0, subkey, 32); + roldqo32(34, k, 0, subkey, 44); + /* KR dependant keys */ + roldq(15, k, 4, subkey, 4); + roldq(15, k, 4, ke, 0); + roldq(30, k, 4, subkey, 24); + roldqo32(34, k, 4, subkey, 36); + /* KA dependant keys */ + roldq(15, ka, 0, subkey, 8); + roldq(30, ka, 0, subkey, 20); + /* 32bit rotation */ + ke[8] = ka[1]; + ke[9] = ka[2]; + ke[10] = ka[3]; + ke[11] = ka[0]; + roldqo32(49, ka, 0, subkey, 40); + + /* KB dependant keys */ + subkey[0] = kb[0]; + subkey[1] = kb[1]; + subkey[2] = kb[2]; + subkey[3] = kb[3]; + roldq(30, kb, 0, subkey, 12); + roldq(30, kb, 0, subkey, 28); + roldqo32(51, kb, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldqo32(45, k, 0, subkey, 28); + decroldq(15, k, 0, ke, 4); + decroldq(17, k, 0, subkey, 12); + decroldqo32(34, k, 0, subkey, 0); + /* KR dependant keys */ + decroldq(15, k, 4, subkey, 40); + decroldq(15, k, 4, ke, 8); + decroldq(30, k, 4, subkey, 20); + decroldqo32(34, k, 4, subkey, 8); + /* KA dependant keys */ + decroldq(15, ka, 0, subkey, 36); + decroldq(30, ka, 0, subkey, 24); + /* 32bit rotation */ + ke[2] = ka[1]; + ke[3] = ka[2]; + ke[0] = ka[3]; + ke[1] = ka[0]; + decroldqo32(49, ka, 0, subkey, 4); + + /* KB dependant keys */ + subkey[46] = kb[0]; + subkey[47] = kb[1]; + subkey[44] = kb[2]; + subkey[45] = kb[3]; + decroldq(30, kb, 0, subkey, 32); + decroldq(30, kb, 0, subkey, 16); + roldqo32(51, kb, 0, kw, 0); + } + } + } + + private int processBlock128(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + + return BLOCK_SIZE; + } + + private int processBlock192or256(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + camelliaFLs(state, ke, 8); + camelliaF2(state, subkey, 36); + camelliaF2(state, subkey, 40); + camelliaF2(state, subkey, 44); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + return BLOCK_SIZE; + } + + public CamelliaEngine() + { + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("only simple KeyParameter expected."); + } + + setKey(forEncryption, ((KeyParameter)params).getKey()); + initialised = true; + } + + public String getAlgorithmName() + { + return "Camellia"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (!initialised) + { + throw new IllegalStateException("Camellia engine 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 (_keyIs128) + { + return processBlock128(in, inOff, out, outOff); + } + else + { + return processBlock192or256(in, inOff, out, outOff); + } + } + + public void reset() + { + // nothing + + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaLightEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaLightEngine.java new file mode 100644 index 00000000..2b1e71b2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaLightEngine.java @@ -0,0 +1,592 @@ +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; + +/** + * Camellia - based on RFC 3713, smaller implementation, about half the size of CamelliaEngine. + */ + +public class CamelliaLightEngine + implements BlockCipher +{ + private static final int BLOCK_SIZE = 16; + private static final int MASK8 = 0xff; + private boolean initialized; + private boolean _keyis128; + + private int[] subkey = new int[24 * 4]; + private int[] kw = new int[4 * 2]; // for whitening + private int[] ke = new int[6 * 2]; // for FL and FL^(-1) + private int[] state = new int[4]; // for encryption and decryption + + private static final int SIGMA[] = { + 0xa09e667f, 0x3bcc908b, + 0xb67ae858, 0x4caa73b2, + 0xc6ef372f, 0xe94f82be, + 0x54ff53a5, 0xf1d36f1c, + 0x10e527fa, 0xde682d1d, + 0xb05688c2, 0xb3e6c1fd + }; + + /* + * + * S-box data + * + */ + private static final byte SBOX1[] = { + (byte)112, (byte)130, (byte)44, (byte)236, + (byte)179, (byte)39, (byte)192, (byte)229, + (byte)228, (byte)133, (byte)87, (byte)53, + (byte)234, (byte)12, (byte)174, (byte)65, + (byte)35, (byte)239, (byte)107, (byte)147, + (byte)69, (byte)25, (byte)165, (byte)33, + (byte)237, (byte)14, (byte)79, (byte)78, + (byte)29, (byte)101, (byte)146, (byte)189, + (byte)134, (byte)184, (byte)175, (byte)143, + (byte)124, (byte)235, (byte)31, (byte)206, + (byte)62, (byte)48, (byte)220, (byte)95, + (byte)94, (byte)197, (byte)11, (byte)26, + (byte)166, (byte)225, (byte)57, (byte)202, + (byte)213, (byte)71, (byte)93, (byte)61, + (byte)217, (byte)1, (byte)90, (byte)214, + (byte)81, (byte)86, (byte)108, (byte)77, + (byte)139, (byte)13, (byte)154, (byte)102, + (byte)251, (byte)204, (byte)176, (byte)45, + (byte)116, (byte)18, (byte)43, (byte)32, + (byte)240, (byte)177, (byte)132, (byte)153, + (byte)223, (byte)76, (byte)203, (byte)194, + (byte)52, (byte)126, (byte)118, (byte)5, + (byte)109, (byte)183, (byte)169, (byte)49, + (byte)209, (byte)23, (byte)4, (byte)215, + (byte)20, (byte)88, (byte)58, (byte)97, + (byte)222, (byte)27, (byte)17, (byte)28, + (byte)50, (byte)15, (byte)156, (byte)22, + (byte)83, (byte)24, (byte)242, (byte)34, + (byte)254, (byte)68, (byte)207, (byte)178, + (byte)195, (byte)181, (byte)122, (byte)145, + (byte)36, (byte)8, (byte)232, (byte)168, + (byte)96, (byte)252, (byte)105, (byte)80, + (byte)170, (byte)208, (byte)160, (byte)125, + (byte)161, (byte)137, (byte)98, (byte)151, + (byte)84, (byte)91, (byte)30, (byte)149, + (byte)224, (byte)255, (byte)100, (byte)210, + (byte)16, (byte)196, (byte)0, (byte)72, + (byte)163, (byte)247, (byte)117, (byte)219, + (byte)138, (byte)3, (byte)230, (byte)218, + (byte)9, (byte)63, (byte)221, (byte)148, + (byte)135, (byte)92, (byte)131, (byte)2, + (byte)205, (byte)74, (byte)144, (byte)51, + (byte)115, (byte)103, (byte)246, (byte)243, + (byte)157, (byte)127, (byte)191, (byte)226, + (byte)82, (byte)155, (byte)216, (byte)38, + (byte)200, (byte)55, (byte)198, (byte)59, + (byte)129, (byte)150, (byte)111, (byte)75, + (byte)19, (byte)190, (byte)99, (byte)46, + (byte)233, (byte)121, (byte)167, (byte)140, + (byte)159, (byte)110, (byte)188, (byte)142, + (byte)41, (byte)245, (byte)249, (byte)182, + (byte)47, (byte)253, (byte)180, (byte)89, + (byte)120, (byte)152, (byte)6, (byte)106, + (byte)231, (byte)70, (byte)113, (byte)186, + (byte)212, (byte)37, (byte)171, (byte)66, + (byte)136, (byte)162, (byte)141, (byte)250, + (byte)114, (byte)7, (byte)185, (byte)85, + (byte)248, (byte)238, (byte)172, (byte)10, + (byte)54, (byte)73, (byte)42, (byte)104, + (byte)60, (byte)56, (byte)241, (byte)164, + (byte)64, (byte)40, (byte)211, (byte)123, + (byte)187, (byte)201, (byte)67, (byte)193, + (byte)21, (byte)227, (byte)173, (byte)244, + (byte)119, (byte)199, (byte)128, (byte)158 + }; + + private static int rightRotate(int x, int s) + { + return (((x) >>> (s)) + ((x) << (32 - s))); + } + + private static int leftRotate(int x, int s) + { + return ((x) << (s)) + ((x) >>> (32 - s)); + } + + private static void roldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[2 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[0 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private static void roldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[2 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[0 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private int bytes2int(byte[] src, int offset) + { + int word = 0; + + for (int i = 0; i < 4; i++) + { + word = (word << 8) + (src[i + offset] & MASK8); + } + return word; + } + + private void int2bytes(int word, byte[] dst, int offset) + { + for (int i = 0; i < 4; i++) + { + dst[(3 - i) + offset] = (byte)word; + word >>>= 8; + } + } + + private byte lRot8(byte v, int rot) + { + return (byte)((v << rot) | ((v & 0xff) >>> (8 - rot))); + } + + private int sbox2(int x) + { + return (lRot8(SBOX1[x], 1) & MASK8); + } + + private int sbox3(int x) + { + return (lRot8(SBOX1[x], 7) & MASK8); + } + + private int sbox4(int x) + { + return (SBOX1[((int)lRot8((byte)x, 1) & MASK8)] & MASK8); + } + + private void camelliaF2(int[] s, int[] skey, int keyoff) + { + int t1, t2, u, v; + + t1 = s[0] ^ skey[0 + keyoff]; + u = sbox4((t1 & MASK8)); + u |= (sbox3(((t1 >>> 8) & MASK8)) << 8); + u |= (sbox2(((t1 >>> 16) & MASK8)) << 16); + u |= ((int)(SBOX1[((t1 >>> 24) & MASK8)] & MASK8) << 24); + + t2 = s[1] ^ skey[1 + keyoff]; + v = (int)SBOX1[(t2 & MASK8)] & MASK8; + v |= (sbox4(((t2 >>> 8) & MASK8)) << 8); + v |= (sbox3(((t2 >>> 16) & MASK8)) << 16); + v |= (sbox2(((t2 >>> 24) & MASK8)) << 24); + + v = leftRotate(v, 8); + u ^= v; + v = leftRotate(v, 8) ^ u; + u = rightRotate(u, 8) ^ v; + s[2] ^= leftRotate(v, 16) ^ u; + s[3] ^= leftRotate(u, 8); + + t1 = s[2] ^ skey[2 + keyoff]; + u = sbox4((t1 & MASK8)); + u |= sbox3(((t1 >>> 8) & MASK8)) << 8; + u |= sbox2(((t1 >>> 16) & MASK8)) << 16; + u |= ((int)SBOX1[((t1 >>> 24) & MASK8)] & MASK8) << 24; + + t2 = s[3] ^ skey[3 + keyoff]; + v = ((int)SBOX1[(t2 & MASK8)] & MASK8); + v |= sbox4(((t2 >>> 8) & MASK8)) << 8; + v |= sbox3(((t2 >>> 16) & MASK8)) << 16; + v |= sbox2(((t2 >>> 24) & MASK8)) << 24; + + v = leftRotate(v, 8); + u ^= v; + v = leftRotate(v, 8) ^ u; + u = rightRotate(u, 8) ^ v; + s[0] ^= leftRotate(v, 16) ^ u; + s[1] ^= leftRotate(u, 8); + } + + private void camelliaFLs(int[] s, int[] fkey, int keyoff) + { + + s[1] ^= leftRotate(s[0] & fkey[0 + keyoff], 1); + s[0] ^= fkey[1 + keyoff] | s[1]; + + s[2] ^= fkey[3 + keyoff] | s[3]; + s[3] ^= leftRotate(fkey[2 + keyoff] & s[2], 1); + } + + private void setKey(boolean forEncryption, byte[] key) + { + int[] k = new int[8]; + int[] ka = new int[4]; + int[] kb = new int[4]; + int[] t = new int[4]; + + switch (key.length) + { + case 16: + _keyis128 = true; + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = k[5] = k[6] = k[7] = 0; + break; + case 24: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = ~k[4]; + k[7] = ~k[5]; + _keyis128 = false; + break; + case 32: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = bytes2int(key, 24); + k[7] = bytes2int(key, 28); + _keyis128 = false; + break; + default: + throw new + IllegalArgumentException("key sizes are only 16/24/32 bytes."); + } + + for (int i = 0; i < 4; i++) + { + ka[i] = k[i] ^ k[i + 4]; + } + /* compute KA */ + camelliaF2(ka, SIGMA, 0); + for (int i = 0; i < 4; i++) + { + ka[i] ^= k[i]; + } + camelliaF2(ka, SIGMA, 4); + + if (_keyis128) + { + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldq(15, k, 0, subkey, 4); + roldq(30, k, 0, subkey, 12); + roldq(15, k, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + roldq(17, k, 0, ke, 4); + roldq(17, k, 0, subkey, 24); + roldq(17, k, 0, subkey, 32); + /* KA dependant keys */ + subkey[0] = ka[0]; + subkey[1] = ka[1]; + subkey[2] = ka[2]; + subkey[3] = ka[3]; + roldq(15, ka, 0, subkey, 8); + roldq(15, ka, 0, ke, 0); + roldq(15, ka, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + roldq(15, ka, 0, subkey, 20); + roldqo32(34, ka, 0, subkey, 28); + roldq(17, ka, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldq(15, k, 0, subkey, 28); + decroldq(30, k, 0, subkey, 20); + decroldq(15, k, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + decroldq(17, k, 0, ke, 0); + decroldq(17, k, 0, subkey, 8); + decroldq(17, k, 0, subkey, 0); + /* KA dependant keys */ + subkey[34] = ka[0]; + subkey[35] = ka[1]; + subkey[32] = ka[2]; + subkey[33] = ka[3]; + decroldq(15, ka, 0, subkey, 24); + decroldq(15, ka, 0, ke, 4); + decroldq(15, ka, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + decroldq(15, ka, 0, subkey, 12); + decroldqo32(34, ka, 0, subkey, 4); + roldq(17, ka, 0, kw, 0); + } + } + else + { // 192bit or 256bit + /* compute KB */ + for (int i = 0; i < 4; i++) + { + kb[i] = ka[i] ^ k[i + 4]; + } + camelliaF2(kb, SIGMA, 8); + + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldqo32(45, k, 0, subkey, 16); + roldq(15, k, 0, ke, 4); + roldq(17, k, 0, subkey, 32); + roldqo32(34, k, 0, subkey, 44); + /* KR dependant keys */ + roldq(15, k, 4, subkey, 4); + roldq(15, k, 4, ke, 0); + roldq(30, k, 4, subkey, 24); + roldqo32(34, k, 4, subkey, 36); + /* KA dependant keys */ + roldq(15, ka, 0, subkey, 8); + roldq(30, ka, 0, subkey, 20); + /* 32bit rotation */ + ke[8] = ka[1]; + ke[9] = ka[2]; + ke[10] = ka[3]; + ke[11] = ka[0]; + roldqo32(49, ka, 0, subkey, 40); + + /* KB dependant keys */ + subkey[0] = kb[0]; + subkey[1] = kb[1]; + subkey[2] = kb[2]; + subkey[3] = kb[3]; + roldq(30, kb, 0, subkey, 12); + roldq(30, kb, 0, subkey, 28); + roldqo32(51, kb, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldqo32(45, k, 0, subkey, 28); + decroldq(15, k, 0, ke, 4); + decroldq(17, k, 0, subkey, 12); + decroldqo32(34, k, 0, subkey, 0); + /* KR dependant keys */ + decroldq(15, k, 4, subkey, 40); + decroldq(15, k, 4, ke, 8); + decroldq(30, k, 4, subkey, 20); + decroldqo32(34, k, 4, subkey, 8); + /* KA dependant keys */ + decroldq(15, ka, 0, subkey, 36); + decroldq(30, ka, 0, subkey, 24); + /* 32bit rotation */ + ke[2] = ka[1]; + ke[3] = ka[2]; + ke[0] = ka[3]; + ke[1] = ka[0]; + decroldqo32(49, ka, 0, subkey, 4); + + /* KB dependant keys */ + subkey[46] = kb[0]; + subkey[47] = kb[1]; + subkey[44] = kb[2]; + subkey[45] = kb[3]; + decroldq(30, kb, 0, subkey, 32); + decroldq(30, kb, 0, subkey, 16); + roldqo32(51, kb, 0, kw, 0); + } + } + } + + private int processBlock128(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + + return BLOCK_SIZE; + } + + private int processBlock192or256(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + camelliaFLs(state, ke, 8); + camelliaF2(state, subkey, 36); + camelliaF2(state, subkey, 40); + camelliaF2(state, subkey, 44); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + return BLOCK_SIZE; + } + + public CamelliaLightEngine() + { + } + + public String getAlgorithmName() + { + return "Camellia"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public void init(boolean forEncryption, CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("only simple KeyParameter expected."); + } + + setKey(forEncryption, ((KeyParameter)params).getKey()); + initialized = true; + } + + public int processBlock(byte[] in, int inOff, + byte[] out, int outOff) + throws IllegalStateException + { + + if (!initialized) + { + throw new IllegalStateException("Camellia is not initialized"); + } + + 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 (_keyis128) + { + return processBlock128(in, inOff, out, outOff); + } + else + { + return processBlock192or256(in, inOff, out, outOff); + } + } + + public void reset() + { + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java new file mode 100644 index 00000000..5ca239a6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.engines; + +/** + * An implementation of the Camellia key wrapper based on RFC 3657/RFC 3394. + * <p> + * For further details see: <a href="http://www.ietf.org/rfc/rfc3657.txt">http://www.ietf.org/rfc/rfc3657.txt</a>. + */ +public class CamelliaWrapEngine + extends RFC3394WrapEngine +{ + public CamelliaWrapEngine() + { + super(new CamelliaEngine()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/DESEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/DESEngine.java new file mode 100644 index 00000000..9b1e404e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/DESEngine.java @@ -0,0 +1,495 @@ +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; + +/** + * a class that provides a basic DES engine. + */ +public class DESEngine + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 8; + + private int[] workingKey = null; + + /** + * standard constructor. + */ + public DESEngine() + { + } + + /** + * initialise a DES cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + if (((KeyParameter)params).getKey().length > 8) + { + throw new IllegalArgumentException("DES key too long - should be 8 bytes"); + } + + workingKey = generateWorkingKey(encrypting, + ((KeyParameter)params).getKey()); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to DES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "DES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("DES engine 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"); + } + + desFunc(workingKey, in, inOff, out, outOff); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + /** + * what follows is mainly taken from "Applied Cryptography", by + * Bruce Schneier, however it also bears great resemblance to Richard + * Outerbridge's D3DES... + */ + +// private static final short[] Df_Key = +// { +// 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, +// 0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10, +// 0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67 +// }; + + private static final short[] bytebit = + { + 0200, 0100, 040, 020, 010, 04, 02, 01 + }; + + private static final int[] bigbyte = + { + 0x800000, 0x400000, 0x200000, 0x100000, + 0x80000, 0x40000, 0x20000, 0x10000, + 0x8000, 0x4000, 0x2000, 0x1000, + 0x800, 0x400, 0x200, 0x100, + 0x80, 0x40, 0x20, 0x10, + 0x8, 0x4, 0x2, 0x1 + }; + + /* + * Use the key schedule specified in the Standard (ANSI X3.92-1981). + */ + + private static final byte[] pc1 = + { + 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 + }; + + private static final byte[] totrot = + { + 1, 2, 4, 6, 8, 10, 12, 14, + 15, 17, 19, 21, 23, 25, 27, 28 + }; + + private static final byte[] pc2 = + { + 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, + 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 + }; + + private static final int[] SP1 = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 + }; + + private static final int[] SP2 = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 + }; + + private static final int[] SP3 = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 + }; + + private static final int[] SP4 = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 + }; + + private static final int[] SP5 = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 + }; + + private static final int[] SP6 = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 + }; + + private static final int[] SP7 = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 + }; + + private static final int[] SP8 = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 + }; + + /** + * generate an integer based working key based on our secret key + * and what we processing we are planning to do. + * + * Acknowledgements for this routine go to James Gillogly & Phil Karn. + * (whoever, and wherever they are!). + */ + protected int[] generateWorkingKey( + boolean encrypting, + byte[] key) + { + int[] newKey = new int[32]; + boolean[] pc1m = new boolean[56], + pcr = new boolean[56]; + + for (int j = 0; j < 56; j++) + { + int l = pc1[j]; + + pc1m[j] = ((key[l >>> 3] & bytebit[l & 07]) != 0); + } + + for (int i = 0; i < 16; i++) + { + int l, m, n; + + if (encrypting) + { + m = i << 1; + } + else + { + m = (15 - i) << 1; + } + + n = m + 1; + newKey[m] = newKey[n] = 0; + + for (int j = 0; j < 28; j++) + { + l = j + totrot[i]; + if (l < 28) + { + pcr[j] = pc1m[l]; + } + else + { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 28; j < 56; j++) + { + l = j + totrot[i]; + if (l < 56) + { + pcr[j] = pc1m[l]; + } + else + { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 0; j < 24; j++) + { + if (pcr[pc2[j]]) + { + newKey[m] |= bigbyte[j]; + } + + if (pcr[pc2[j + 24]]) + { + newKey[n] |= bigbyte[j]; + } + } + } + + // + // store the processed key + // + for (int i = 0; i != 32; i += 2) + { + int i1, i2; + + i1 = newKey[i]; + i2 = newKey[i + 1]; + + newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) + | ((i2 & 0x00fc0000) >>> 10) | ((i2 & 0x00000fc0) >>> 6); + + newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) + | ((i2 & 0x0003f000) >>> 4) | (i2 & 0x0000003f); + } + + return newKey; + } + + /** + * the DES engine. + */ + protected void desFunc( + int[] wKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int work, right, left; + + left = (in[inOff + 0] & 0xff) << 24; + left |= (in[inOff + 1] & 0xff) << 16; + left |= (in[inOff + 2] & 0xff) << 8; + left |= (in[inOff + 3] & 0xff); + + right = (in[inOff + 4] & 0xff) << 24; + right |= (in[inOff + 5] & 0xff) << 16; + right |= (in[inOff + 6] & 0xff) << 8; + right |= (in[inOff + 7] & 0xff); + + work = ((left >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= (work << 4); + work = ((left >>> 16) ^ right) & 0x0000ffff; + right ^= work; + left ^= (work << 16); + work = ((right >>> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >>> 8) ^ left) & 0x00ff00ff; + left ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; + + for (int round = 0; round < 8; round++) + { + int fval; + + work = (right << 28) | (right >>> 4); + work ^= wKey[round * 4 + 0]; + fval = SP7[ work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = right ^ wKey[round * 4 + 1]; + fval |= SP8[ work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + left ^= fval; + work = (left << 28) | (left >>> 4); + work ^= wKey[round * 4 + 2]; + fval = SP7[ work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = left ^ wKey[round * 4 + 3]; + fval |= SP8[ work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 31) | (left >>> 1); + work = ((left >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + left ^= (work << 8); + work = ((left >>> 2) ^ right) & 0x33333333; + right ^= work; + left ^= (work << 2); + work = ((right >>> 16) ^ left) & 0x0000ffff; + left ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= (work << 4); + + out[outOff + 0] = (byte)((right >>> 24) & 0xff); + out[outOff + 1] = (byte)((right >>> 16) & 0xff); + out[outOff + 2] = (byte)((right >>> 8) & 0xff); + out[outOff + 3] = (byte)(right & 0xff); + out[outOff + 4] = (byte)((left >>> 24) & 0xff); + out[outOff + 5] = (byte)((left >>> 16) & 0xff); + out[outOff + 6] = (byte)((left >>> 8) & 0xff); + out[outOff + 7] = (byte)(left & 0xff); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/DESedeEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/DESedeEngine.java new file mode 100644 index 00000000..513eccdf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/DESedeEngine.java @@ -0,0 +1,127 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * a class that provides a basic DESede (or Triple DES) engine. + */ +public class DESedeEngine + extends DESEngine +{ + protected static final int BLOCK_SIZE = 8; + + private int[] workingKey1 = null; + private int[] workingKey2 = null; + private int[] workingKey3 = null; + + private boolean forEncryption; + + /** + * standard constructor. + */ + public DESedeEngine() + { + } + + /** + * initialise a DESede cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to DESede init - " + params.getClass().getName()); + } + + byte[] keyMaster = ((KeyParameter)params).getKey(); + + if (keyMaster.length != 24 && keyMaster.length != 16) + { + throw new IllegalArgumentException("key size must be 16 or 24 bytes."); + } + + this.forEncryption = encrypting; + + byte[] key1 = new byte[8]; + System.arraycopy(keyMaster, 0, key1, 0, key1.length); + workingKey1 = generateWorkingKey(encrypting, key1); + + byte[] key2 = new byte[8]; + System.arraycopy(keyMaster, 8, key2, 0, key2.length); + workingKey2 = generateWorkingKey(!encrypting, key2); + + if (keyMaster.length == 24) + { + byte[] key3 = new byte[8]; + System.arraycopy(keyMaster, 16, key3, 0, key3.length); + workingKey3 = generateWorkingKey(encrypting, key3); + } + else // 16 byte key + { + workingKey3 = workingKey1; + } + } + + public String getAlgorithmName() + { + return "DESede"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey1 == null) + { + throw new IllegalStateException("DESede engine 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"); + } + + byte[] temp = new byte[BLOCK_SIZE]; + + if (forEncryption) + { + desFunc(workingKey1, in, inOff, temp, 0); + desFunc(workingKey2, temp, 0, temp, 0); + desFunc(workingKey3, temp, 0, out, outOff); + } + else + { + desFunc(workingKey3, in, inOff, temp, 0); + desFunc(workingKey2, temp, 0, temp, 0); + desFunc(workingKey1, temp, 0, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java new file mode 100644 index 00000000..a3c72ccb --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java @@ -0,0 +1,348 @@ +package org.bouncycastle.crypto.engines; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Wrapper; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +/** + * Wrap keys according to + * <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-smime-key-wrap-01.txt"> + * draft-ietf-smime-key-wrap-01.txt</A>. + * <p> + * Note: + * <ul> + * <li>this is based on a draft, and as such is subject to change - don't use this class for anything requiring long term storage. + * <li>if you are using this to wrap triple-des keys you need to set the + * parity bits on the key and, if it's a two-key triple-des key, pad it + * yourself. + * </ul> + */ +public class DESedeWrapEngine + implements Wrapper +{ + /** Field engine */ + private CBCBlockCipher engine; + + /** Field param */ + private KeyParameter param; + + /** Field paramPlusIV */ + private ParametersWithIV paramPlusIV; + + /** Field iv */ + private byte[] iv; + + /** Field forWrapping */ + private boolean forWrapping; + + /** Field IV2 */ + private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, + (byte) 0x2c, (byte) 0x79, (byte) 0xe8, + (byte) 0x21, (byte) 0x05 }; + + // + // checksum digest + // + Digest sha1 = new SHA1Digest(); + byte[] digest = new byte[20]; + + /** + * Method init + * + * @param forWrapping + * @param param + */ + public void init(boolean forWrapping, CipherParameters param) + { + + this.forWrapping = forWrapping; + this.engine = new CBCBlockCipher(new DESedeEngine()); + + SecureRandom sr; + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom pr = (ParametersWithRandom) param; + param = pr.getParameters(); + sr = pr.getRandom(); + } + else + { + sr = new SecureRandom(); + } + + if (param instanceof KeyParameter) + { + this.param = (KeyParameter)param; + + if (this.forWrapping) + { + + // Hm, we have no IV but we want to wrap ?!? + // well, then we have to create our own IV. + this.iv = new byte[8]; + sr.nextBytes(iv); + + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + } + } + else if (param instanceof ParametersWithIV) + { + this.paramPlusIV = (ParametersWithIV)param; + this.iv = this.paramPlusIV.getIV(); + this.param = (KeyParameter)this.paramPlusIV.getParameters(); + + if (this.forWrapping) + { + if ((this.iv == null) || (this.iv.length != 8)) + { + throw new IllegalArgumentException("IV is not 8 octets"); + } + } + else + { + throw new IllegalArgumentException( + "You should not supply an IV for unwrapping"); + } + } + } + + /** + * Method getAlgorithmName + * + * @return the algorithm name "DESede". + */ + public String getAlgorithmName() + { + return "DESede"; + } + + /** + * Method wrap + * + * @param in + * @param inOff + * @param inLen + * @return the wrapped bytes. + */ + public byte[] wrap(byte[] in, int inOff, int inLen) + { + if (!forWrapping) + { + throw new IllegalStateException("Not initialized for wrapping"); + } + + byte keyToBeWrapped[] = new byte[inLen]; + + System.arraycopy(in, inOff, keyToBeWrapped, 0, inLen); + + // Compute the CMS Key Checksum, (section 5.6.1), call this CKS. + byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped); + + // Let WKCKS = WK || CKS where || is concatenation. + byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length]; + + System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length); + System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length); + + // Encrypt WKCKS in CBC mode using KEK as the key and IV as the + // initialization vector. Call the results TEMP1. + + int blockSize = engine.getBlockSize(); + + if (WKCKS.length % blockSize != 0) + { + throw new IllegalStateException("Not multiple of block length"); + } + + engine.init(true, paramPlusIV); + + byte TEMP1[] = new byte[WKCKS.length]; + + for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) + { + engine.processBlock(WKCKS, currentBytePos, TEMP1, currentBytePos); + } + + // Let TEMP2 = IV || TEMP1. + byte[] TEMP2 = new byte[this.iv.length + TEMP1.length]; + + System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length); + System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length); + + // Reverse the order of the octets in TEMP2 and call the result TEMP3. + byte[] TEMP3 = reverse(TEMP2); + + // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector + // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired + // result. It is 40 octets long if a 168 bit key is being wrapped. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(true, param2); + + for (int currentBytePos = 0; currentBytePos != TEMP3.length; currentBytePos += blockSize) + { + engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); + } + + return TEMP3; + } + + /** + * Method unwrap + * + * @param in + * @param inOff + * @param inLen + * @return the unwrapped bytes. + * @throws InvalidCipherTextException + */ + public byte[] unwrap(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException + { + if (forWrapping) + { + throw new IllegalStateException("Not set for unwrapping"); + } + + if (in == null) + { + throw new InvalidCipherTextException("Null pointer as ciphertext"); + } + + final int blockSize = engine.getBlockSize(); + if (inLen % blockSize != 0) + { + throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize); + } + + /* + // Check if the length of the cipher text is reasonable given the key + // type. It must be 40 bytes for a 168 bit key and either 32, 40, or + // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported + // or inconsistent with the algorithm for which the key is intended, + // return error. + // + // we do not accept 168 bit keys. it has to be 192 bit. + int lengthA = (estimatedKeyLengthInBit / 8) + 16; + int lengthB = estimatedKeyLengthInBit % 8; + + if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { + throw new XMLSecurityException("empty"); + } + */ + + // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK + // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(false, param2); + + byte TEMP3[] = new byte[inLen]; + + for (int currentBytePos = 0; currentBytePos != inLen; currentBytePos += blockSize) + { + engine.processBlock(in, inOff + currentBytePos, TEMP3, currentBytePos); + } + + // Reverse the order of the octets in TEMP3 and call the result TEMP2. + byte[] TEMP2 = reverse(TEMP3); + + // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets. + this.iv = new byte[8]; + + byte[] TEMP1 = new byte[TEMP2.length - 8]; + + System.arraycopy(TEMP2, 0, this.iv, 0, 8); + System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8); + + // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV + // found in the previous step. Call the result WKCKS. + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + + this.engine.init(false, this.paramPlusIV); + + byte[] WKCKS = new byte[TEMP1.length]; + + for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) + { + engine.processBlock(TEMP1, currentBytePos, WKCKS, currentBytePos); + } + + // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are + // those octets before the CKS. + byte[] result = new byte[WKCKS.length - 8]; + byte[] CKStoBeVerified = new byte[8]; + + System.arraycopy(WKCKS, 0, result, 0, WKCKS.length - 8); + System.arraycopy(WKCKS, WKCKS.length - 8, CKStoBeVerified, 0, 8); + + // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare + // with the CKS extracted in the above step. If they are not equal, return error. + if (!checkCMSKeyChecksum(result, CKStoBeVerified)) + { + throw new InvalidCipherTextException( + "Checksum inside ciphertext is corrupted"); + } + + // WK is the wrapped key, now extracted for use in data decryption. + return result; + } + + /** + * Some key wrap algorithms make use of the Key Checksum defined + * in CMS [CMS-Algorithms]. This is used to provide an integrity + * check value for the key being wrapped. The algorithm is + * + * - Compute the 20 octet SHA-1 hash on the key being wrapped. + * - Use the first 8 octets of this hash as the checksum value. + * + * @param key + * @return the CMS checksum. + * @throws RuntimeException + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private byte[] calculateCMSKeyChecksum( + byte[] key) + { + byte[] result = new byte[8]; + + sha1.update(key, 0, key.length); + sha1.doFinal(digest, 0); + + System.arraycopy(digest, 0, result, 0, 8); + + return result; + } + + /** + * @param key + * @param checksum + * @return true if okay, false otherwise. + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private boolean checkCMSKeyChecksum( + byte[] key, + byte[] checksum) + { + return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); + } + + private static byte[] reverse(byte[] bs) + { + byte[] result = new byte[bs.length]; + for (int i = 0; i < bs.length; i++) + { + result[i] = bs[bs.length - (i + 1)]; + } + return result; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/ElGamalEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/ElGamalEngine.java new file mode 100644 index 00000000..4bf8e75d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/ElGamalEngine.java @@ -0,0 +1,217 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ElGamalKeyParameters; +import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * this does your basic ElGamal algorithm. + */ +public class ElGamalEngine + implements AsymmetricBlockCipher +{ + private ElGamalKeyParameters key; + private SecureRandom random; + private boolean forEncryption; + private int bitSize; + + private static final BigInteger ZERO = BigInteger.valueOf(0); + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * initialise the ElGamal engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary ElGamal key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + this.key = (ElGamalKeyParameters)p.getParameters(); + this.random = p.getRandom(); + } + else + { + this.key = (ElGamalKeyParameters)param; + this.random = new SecureRandom(); + } + + this.forEncryption = forEncryption; + + BigInteger p = key.getParameters().getP(); + + bitSize = p.bitLength(); + + if (forEncryption) + { + if (!(key instanceof ElGamalPublicKeyParameters)) + { + throw new IllegalArgumentException("ElGamalPublicKeyParameters are required for encryption."); + } + } + else + { + if (!(key instanceof ElGamalPrivateKeyParameters)) + { + throw new IllegalArgumentException("ElGamalPrivateKeyParameters are required for decryption."); + } + } + } + + /** + * Return the maximum size for an input block to this engine. + * For ElGamal this is always one byte less than the size of P on + * encryption, and twice the length as the size of P on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + if (forEncryption) + { + return (bitSize - 1) / 8; + } + + return 2 * ((bitSize + 7) / 8); + } + + /** + * Return the maximum size for an output block to this engine. + * For ElGamal this is always one byte less than the size of P on + * decryption, and twice the length as the size of P on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + if (forEncryption) + { + return 2 * ((bitSize + 7) / 8); + } + + return (bitSize - 1) / 8; + } + + /** + * Process a single block using the basic ElGamal algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the ElGamal process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (key == null) + { + throw new IllegalStateException("ElGamal engine not initialised"); + } + + int maxLength = forEncryption + ? (bitSize - 1 + 7) / 8 + : getInputBlockSize(); + + if (inLen > maxLength) + { + throw new DataLengthException("input too large for ElGamal cipher.\n"); + } + + BigInteger p = key.getParameters().getP(); + + if (key instanceof ElGamalPrivateKeyParameters) // decryption + { + byte[] in1 = new byte[inLen / 2]; + byte[] in2 = new byte[inLen / 2]; + + System.arraycopy(in, inOff, in1, 0, in1.length); + System.arraycopy(in, inOff + in1.length, in2, 0, in2.length); + + BigInteger gamma = new BigInteger(1, in1); + BigInteger phi = new BigInteger(1, in2); + + ElGamalPrivateKeyParameters priv = (ElGamalPrivateKeyParameters)key; + // a shortcut, which generally relies on p being prime amongst other things. + // if a problem with this shows up, check the p and g values! + BigInteger m = gamma.modPow(p.subtract(ONE).subtract(priv.getX()), p).multiply(phi).mod(p); + + return BigIntegers.asUnsignedByteArray(m); + } + else // encryption + { + byte[] block; + if (inOff != 0 || inLen != in.length) + { + block = new byte[inLen]; + + System.arraycopy(in, inOff, block, 0, inLen); + } + else + { + block = in; + } + + BigInteger input = new BigInteger(1, block); + + if (input.bitLength() >= p.bitLength()) + { + throw new DataLengthException("input too large for ElGamal cipher.\n"); + } + + ElGamalPublicKeyParameters pub = (ElGamalPublicKeyParameters)key; + + int pBitLength = p.bitLength(); + BigInteger k = new BigInteger(pBitLength, random); + + while (k.equals(ZERO) || (k.compareTo(p.subtract(TWO)) > 0)) + { + k = new BigInteger(pBitLength, random); + } + + BigInteger g = key.getParameters().getG(); + BigInteger gamma = g.modPow(k, p); + BigInteger phi = input.multiply(pub.getY().modPow(k, p)).mod(p); + + byte[] out1 = gamma.toByteArray(); + byte[] out2 = phi.toByteArray(); + byte[] output = new byte[this.getOutputBlockSize()]; + + if (out1.length > output.length / 2) + { + System.arraycopy(out1, 1, output, output.length / 2 - (out1.length - 1), out1.length - 1); + } + else + { + System.arraycopy(out1, 0, output, output.length / 2 - out1.length, out1.length); + } + + if (out2.length > output.length / 2) + { + System.arraycopy(out2, 1, output, output.length - (out2.length - 1), out2.length - 1); + } + else + { + System.arraycopy(out2, 0, output, output.length - out2.length, out2.length); + } + + return output; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/GOST28147Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/GOST28147Engine.java new file mode 100644 index 00000000..5a88b7fe --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/GOST28147Engine.java @@ -0,0 +1,372 @@ +package org.bouncycastle.crypto.engines; + +import java.util.Hashtable; + +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.crypto.params.ParametersWithSBox; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Strings; + +/** + * implementation of GOST 28147-89 + */ +public class GOST28147Engine + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 8; + private int[] workingKey = null; + private boolean forEncryption; + + private byte[] S = Sbox_Default; + + // these are the S-boxes given in Applied Cryptography 2nd Ed., p. 333 + // This is default S-box! + private static byte Sbox_Default[] = { + 0x4,0xA,0x9,0x2,0xD,0x8,0x0,0xE,0x6,0xB,0x1,0xC,0x7,0xF,0x5,0x3, + 0xE,0xB,0x4,0xC,0x6,0xD,0xF,0xA,0x2,0x3,0x8,0x1,0x0,0x7,0x5,0x9, + 0x5,0x8,0x1,0xD,0xA,0x3,0x4,0x2,0xE,0xF,0xC,0x7,0x6,0x0,0x9,0xB, + 0x7,0xD,0xA,0x1,0x0,0x8,0x9,0xF,0xE,0x4,0x6,0xC,0xB,0x2,0x5,0x3, + 0x6,0xC,0x7,0x1,0x5,0xF,0xD,0x8,0x4,0xA,0x9,0xE,0x0,0x3,0xB,0x2, + 0x4,0xB,0xA,0x0,0x7,0x2,0x1,0xD,0x3,0x6,0x8,0x5,0x9,0xC,0xF,0xE, + 0xD,0xB,0x4,0x1,0x3,0xF,0x5,0x9,0x0,0xA,0xE,0x7,0x6,0x8,0x2,0xC, + 0x1,0xF,0xD,0x0,0x5,0x7,0xA,0x4,0x9,0x2,0x3,0xE,0x6,0xB,0x8,0xC + }; + + /* + * class content S-box parameters for encrypting + * getting from, see: http://tools.ietf.org/id/draft-popov-cryptopro-cpalgs-01.txt + * http://tools.ietf.org/id/draft-popov-cryptopro-cpalgs-02.txt + */ + private static byte[] ESbox_Test = { + 0x4,0x2,0xF,0x5,0x9,0x1,0x0,0x8,0xE,0x3,0xB,0xC,0xD,0x7,0xA,0x6, + 0xC,0x9,0xF,0xE,0x8,0x1,0x3,0xA,0x2,0x7,0x4,0xD,0x6,0x0,0xB,0x5, + 0xD,0x8,0xE,0xC,0x7,0x3,0x9,0xA,0x1,0x5,0x2,0x4,0x6,0xF,0x0,0xB, + 0xE,0x9,0xB,0x2,0x5,0xF,0x7,0x1,0x0,0xD,0xC,0x6,0xA,0x4,0x3,0x8, + 0x3,0xE,0x5,0x9,0x6,0x8,0x0,0xD,0xA,0xB,0x7,0xC,0x2,0x1,0xF,0x4, + 0x8,0xF,0x6,0xB,0x1,0x9,0xC,0x5,0xD,0x3,0x7,0xA,0x0,0xE,0x2,0x4, + 0x9,0xB,0xC,0x0,0x3,0x6,0x7,0x5,0x4,0x8,0xE,0xF,0x1,0xA,0x2,0xD, + 0xC,0x6,0x5,0x2,0xB,0x0,0x9,0xD,0x3,0xE,0x7,0xA,0xF,0x4,0x1,0x8 + }; + + private static byte[] ESbox_A = { + 0x9,0x6,0x3,0x2,0x8,0xB,0x1,0x7,0xA,0x4,0xE,0xF,0xC,0x0,0xD,0x5, + 0x3,0x7,0xE,0x9,0x8,0xA,0xF,0x0,0x5,0x2,0x6,0xC,0xB,0x4,0xD,0x1, + 0xE,0x4,0x6,0x2,0xB,0x3,0xD,0x8,0xC,0xF,0x5,0xA,0x0,0x7,0x1,0x9, + 0xE,0x7,0xA,0xC,0xD,0x1,0x3,0x9,0x0,0x2,0xB,0x4,0xF,0x8,0x5,0x6, + 0xB,0x5,0x1,0x9,0x8,0xD,0xF,0x0,0xE,0x4,0x2,0x3,0xC,0x7,0xA,0x6, + 0x3,0xA,0xD,0xC,0x1,0x2,0x0,0xB,0x7,0x5,0x9,0x4,0x8,0xF,0xE,0x6, + 0x1,0xD,0x2,0x9,0x7,0xA,0x6,0x0,0x8,0xC,0x4,0x5,0xF,0x3,0xB,0xE, + 0xB,0xA,0xF,0x5,0x0,0xC,0xE,0x8,0x6,0x2,0x3,0x9,0x1,0x7,0xD,0x4 + }; + + private static byte[] ESbox_B = { + 0x8,0x4,0xB,0x1,0x3,0x5,0x0,0x9,0x2,0xE,0xA,0xC,0xD,0x6,0x7,0xF, + 0x0,0x1,0x2,0xA,0x4,0xD,0x5,0xC,0x9,0x7,0x3,0xF,0xB,0x8,0x6,0xE, + 0xE,0xC,0x0,0xA,0x9,0x2,0xD,0xB,0x7,0x5,0x8,0xF,0x3,0x6,0x1,0x4, + 0x7,0x5,0x0,0xD,0xB,0x6,0x1,0x2,0x3,0xA,0xC,0xF,0x4,0xE,0x9,0x8, + 0x2,0x7,0xC,0xF,0x9,0x5,0xA,0xB,0x1,0x4,0x0,0xD,0x6,0x8,0xE,0x3, + 0x8,0x3,0x2,0x6,0x4,0xD,0xE,0xB,0xC,0x1,0x7,0xF,0xA,0x0,0x9,0x5, + 0x5,0x2,0xA,0xB,0x9,0x1,0xC,0x3,0x7,0x4,0xD,0x0,0x6,0xF,0x8,0xE, + 0x0,0x4,0xB,0xE,0x8,0x3,0x7,0x1,0xA,0x2,0x9,0x6,0xF,0xD,0x5,0xC + }; + + private static byte[] ESbox_C = { + 0x1,0xB,0xC,0x2,0x9,0xD,0x0,0xF,0x4,0x5,0x8,0xE,0xA,0x7,0x6,0x3, + 0x0,0x1,0x7,0xD,0xB,0x4,0x5,0x2,0x8,0xE,0xF,0xC,0x9,0xA,0x6,0x3, + 0x8,0x2,0x5,0x0,0x4,0x9,0xF,0xA,0x3,0x7,0xC,0xD,0x6,0xE,0x1,0xB, + 0x3,0x6,0x0,0x1,0x5,0xD,0xA,0x8,0xB,0x2,0x9,0x7,0xE,0xF,0xC,0x4, + 0x8,0xD,0xB,0x0,0x4,0x5,0x1,0x2,0x9,0x3,0xC,0xE,0x6,0xF,0xA,0x7, + 0xC,0x9,0xB,0x1,0x8,0xE,0x2,0x4,0x7,0x3,0x6,0x5,0xA,0x0,0xF,0xD, + 0xA,0x9,0x6,0x8,0xD,0xE,0x2,0x0,0xF,0x3,0x5,0xB,0x4,0x1,0xC,0x7, + 0x7,0x4,0x0,0x5,0xA,0x2,0xF,0xE,0xC,0x6,0x1,0xB,0xD,0x9,0x3,0x8 + }; + + private static byte[] ESbox_D = { + 0xF,0xC,0x2,0xA,0x6,0x4,0x5,0x0,0x7,0x9,0xE,0xD,0x1,0xB,0x8,0x3, + 0xB,0x6,0x3,0x4,0xC,0xF,0xE,0x2,0x7,0xD,0x8,0x0,0x5,0xA,0x9,0x1, + 0x1,0xC,0xB,0x0,0xF,0xE,0x6,0x5,0xA,0xD,0x4,0x8,0x9,0x3,0x7,0x2, + 0x1,0x5,0xE,0xC,0xA,0x7,0x0,0xD,0x6,0x2,0xB,0x4,0x9,0x3,0xF,0x8, + 0x0,0xC,0x8,0x9,0xD,0x2,0xA,0xB,0x7,0x3,0x6,0x5,0x4,0xE,0xF,0x1, + 0x8,0x0,0xF,0x3,0x2,0x5,0xE,0xB,0x1,0xA,0x4,0x7,0xC,0x9,0xD,0x6, + 0x3,0x0,0x6,0xF,0x1,0xE,0x9,0x2,0xD,0x8,0xC,0x4,0xB,0xA,0x5,0x7, + 0x1,0xA,0x6,0x8,0xF,0xB,0x0,0x4,0xC,0x3,0x5,0x9,0x7,0xD,0x2,0xE + }; + + //S-box for digest + private static byte DSbox_Test[] = { + 0x4,0xA,0x9,0x2,0xD,0x8,0x0,0xE,0x6,0xB,0x1,0xC,0x7,0xF,0x5,0x3, + 0xE,0xB,0x4,0xC,0x6,0xD,0xF,0xA,0x2,0x3,0x8,0x1,0x0,0x7,0x5,0x9, + 0x5,0x8,0x1,0xD,0xA,0x3,0x4,0x2,0xE,0xF,0xC,0x7,0x6,0x0,0x9,0xB, + 0x7,0xD,0xA,0x1,0x0,0x8,0x9,0xF,0xE,0x4,0x6,0xC,0xB,0x2,0x5,0x3, + 0x6,0xC,0x7,0x1,0x5,0xF,0xD,0x8,0x4,0xA,0x9,0xE,0x0,0x3,0xB,0x2, + 0x4,0xB,0xA,0x0,0x7,0x2,0x1,0xD,0x3,0x6,0x8,0x5,0x9,0xC,0xF,0xE, + 0xD,0xB,0x4,0x1,0x3,0xF,0x5,0x9,0x0,0xA,0xE,0x7,0x6,0x8,0x2,0xC, + 0x1,0xF,0xD,0x0,0x5,0x7,0xA,0x4,0x9,0x2,0x3,0xE,0x6,0xB,0x8,0xC + }; + + private static byte DSbox_A[] = { + 0xA,0x4,0x5,0x6,0x8,0x1,0x3,0x7,0xD,0xC,0xE,0x0,0x9,0x2,0xB,0xF, + 0x5,0xF,0x4,0x0,0x2,0xD,0xB,0x9,0x1,0x7,0x6,0x3,0xC,0xE,0xA,0x8, + 0x7,0xF,0xC,0xE,0x9,0x4,0x1,0x0,0x3,0xB,0x5,0x2,0x6,0xA,0x8,0xD, + 0x4,0xA,0x7,0xC,0x0,0xF,0x2,0x8,0xE,0x1,0x6,0x5,0xD,0xB,0x9,0x3, + 0x7,0x6,0x4,0xB,0x9,0xC,0x2,0xA,0x1,0x8,0x0,0xE,0xF,0xD,0x3,0x5, + 0x7,0x6,0x2,0x4,0xD,0x9,0xF,0x0,0xA,0x1,0x5,0xB,0x8,0xE,0xC,0x3, + 0xD,0xE,0x4,0x1,0x7,0x0,0x5,0xA,0x3,0xC,0x8,0xF,0x6,0x2,0x9,0xB, + 0x1,0x3,0xA,0x9,0x5,0xB,0x4,0xF,0x8,0x6,0x7,0xE,0xD,0x0,0x2,0xC + }; + + // + // pre-defined sbox table + // + private static Hashtable sBoxes = new Hashtable(); + + static + { + addSBox("Default", Sbox_Default); + addSBox("E-TEST", ESbox_Test); + addSBox("E-A", ESbox_A); + addSBox("E-B", ESbox_B); + addSBox("E-C", ESbox_C); + addSBox("E-D", ESbox_D); + addSBox("D-TEST", DSbox_Test); + addSBox("D-A", DSbox_A); + } + + private static void addSBox(String sBoxName, byte[] sBox) + { + sBoxes.put(Strings.toUpperCase(sBoxName), sBox); + } + + /** + * standard constructor. + */ + public GOST28147Engine() + { + } + + /** + * initialise an GOST28147 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof ParametersWithSBox) + { + ParametersWithSBox param = (ParametersWithSBox)params; + + // + // Set the S-Box + // + byte[] sBox = param.getSBox(); + if (sBox.length != Sbox_Default.length) + { + throw new IllegalArgumentException("invalid S-box passed to GOST28147 init"); + } + this.S = Arrays.clone(sBox); + + // + // set key if there is one + // + if (param.getParameters() != null) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)param.getParameters()).getKey()); + } + } + else if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)params).getKey()); + } + else if (params != null) + { + throw new IllegalArgumentException("invalid parameter passed to GOST28147 init - " + params.getClass().getName()); + } + } + + public String getAlgorithmName() + { + return "GOST28147"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("GOST28147 engine 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"); + } + + GOST28147Func(workingKey, in, inOff, out, outOff); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private int[] generateWorkingKey( + boolean forEncryption, + byte[] userKey) + { + this.forEncryption = forEncryption; + + if (userKey.length != 32) + { + throw new IllegalArgumentException("Key length invalid. Key needs to be 32 byte - 256 bit!!!"); + } + + int key[] = new int[8]; + for(int i=0; i!=8; i++) + { + key[i] = bytesToint(userKey,i*4); + } + + return key; + } + + private int GOST28147_mainStep(int n1, int key) + { + int cm = (key + n1); // CM1 + + // S-box replacing + + int om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); + om += S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); + om += S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); + om += S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); + om += S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); + om += S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); + om += S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); + om += S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); + + return om << 11 | om >>> (32-11); // 11-leftshift + } + + private void GOST28147Func( + int[] workingKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int N1, N2, tmp; //tmp -> for saving N1 + N1 = bytesToint(in, inOff); + N2 = bytesToint(in, inOff + 4); + + if (this.forEncryption) + { + for(int k = 0; k < 3; k++) // 1-24 steps + { + for(int j = 0; j < 8; j++) + { + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + for(int j = 7; j > 0; j--) // 25-31 steps + { + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + else //decrypt + { + for(int j = 0; j < 8; j++) // 1-8 steps + { + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + for(int k = 0; k < 3; k++) //9-31 steps + { + for(int j = 7; j >= 0; j--) + { + if ((k == 2) && (j==0)) + { + break; // break 32 step + } + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + } + + N2 = N2 ^ GOST28147_mainStep(N1, workingKey[0]); // 32 step (N1=N1) + + intTobytes(N1, out, outOff); + intTobytes(N2, out, outOff + 4); + } + + //array of bytes to type int + private int bytesToint( + byte[] in, + int inOff) + { + return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) + + ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] out, + int outOff) + { + out[outOff + 3] = (byte)(num >>> 24); + out[outOff + 2] = (byte)(num >>> 16); + out[outOff + 1] = (byte)(num >>> 8); + out[outOff] = (byte)num; + } + + /** + * Return the S-Box associated with SBoxName + * @param sBoxName name of the S-Box + * @return byte array representing the S-Box + */ + public static byte[] getSBox( + String sBoxName) + { + byte[] sBox = (byte[])sBoxes.get(Strings.toUpperCase(sBoxName)); + + if (sBox == null) + { + throw new IllegalArgumentException("Unknown S-Box - possible types: " + + "\"Default\", \"E-Test\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"D-Test\", \"D-A\"."); + } + + return Arrays.clone(sBox); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java new file mode 100644 index 00000000..6b3da1c3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java @@ -0,0 +1,303 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream + * cipher, Grain-128. + */ +public class Grain128Engine + implements StreamCipher +{ + + /** + * Constants + */ + private static final int STATE_SIZE = 4; + + /** + * Variables to hold the state of the engine during encryption and + * decryption + */ + private byte[] workingKey; + private byte[] workingIV; + private byte[] out; + private int[] lfsr; + private int[] nfsr; + private int output; + private int index = 4; + + private boolean initialised = false; + + public String getAlgorithmName() + { + return "Grain-128"; + } + + /** + * Initialize a Grain-128 cipher. + * + * @param forEncryption 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 forEncryption, CipherParameters params) + throws IllegalArgumentException + { + /** + * Grain encryption and decryption is completely symmetrical, so the + * 'forEncryption' is irrelevant. + */ + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "Grain-128 Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV)params; + + byte[] iv = ivParams.getIV(); + + if (iv == null || iv.length != 12) + { + throw new IllegalArgumentException( + "Grain-128 requires exactly 12 bytes of IV"); + } + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "Grain-128 Init parameters must include a key"); + } + + KeyParameter key = (KeyParameter)ivParams.getParameters(); + + /** + * Initialize variables. + */ + workingIV = new byte[key.getKey().length]; + workingKey = new byte[key.getKey().length]; + lfsr = new int[STATE_SIZE]; + nfsr = new int[STATE_SIZE]; + out = new byte[4]; + + System.arraycopy(iv, 0, workingIV, 0, iv.length); + System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length); + + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * 256 clocks initialization phase. + */ + private void initGrain() + { + for (int i = 0; i < 8; i++) + { + output = getOutput(); + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output); + lfsr = shift(lfsr, getOutputLFSR() ^ output); + } + initialised = true; + } + + /** + * Get output from non-linear function g(x). + * + * @return Output from NFSR. + */ + private int getOutputNFSR() + { + int b0 = nfsr[0]; + int b3 = nfsr[0] >>> 3 | nfsr[1] << 29; + int b11 = nfsr[0] >>> 11 | nfsr[1] << 21; + int b13 = nfsr[0] >>> 13 | nfsr[1] << 19; + int b17 = nfsr[0] >>> 17 | nfsr[1] << 15; + int b18 = nfsr[0] >>> 18 | nfsr[1] << 14; + int b26 = nfsr[0] >>> 26 | nfsr[1] << 6; + int b27 = nfsr[0] >>> 27 | nfsr[1] << 5; + int b40 = nfsr[1] >>> 8 | nfsr[2] << 24; + int b48 = nfsr[1] >>> 16 | nfsr[2] << 16; + int b56 = nfsr[1] >>> 24 | nfsr[2] << 8; + int b59 = nfsr[1] >>> 27 | nfsr[2] << 5; + int b61 = nfsr[1] >>> 29 | nfsr[2] << 3; + int b65 = nfsr[2] >>> 1 | nfsr[3] << 31; + int b67 = nfsr[2] >>> 3 | nfsr[3] << 29; + int b68 = nfsr[2] >>> 4 | nfsr[3] << 28; + int b84 = nfsr[2] >>> 20 | nfsr[3] << 12; + int b91 = nfsr[2] >>> 27 | nfsr[3] << 5; + int b96 = nfsr[3]; + + return b0 ^ b26 ^ b56 ^ b91 ^ b96 ^ b3 & b67 ^ b11 & b13 ^ b17 & b18 + ^ b27 & b59 ^ b40 & b48 ^ b61 & b65 ^ b68 & b84; + } + + /** + * Get output from linear function f(x). + * + * @return Output from LFSR. + */ + private int getOutputLFSR() + { + int s0 = lfsr[0]; + int s7 = lfsr[0] >>> 7 | lfsr[1] << 25; + int s38 = lfsr[1] >>> 6 | lfsr[2] << 26; + int s70 = lfsr[2] >>> 6 | lfsr[3] << 26; + int s81 = lfsr[2] >>> 17 | lfsr[3] << 15; + int s96 = lfsr[3]; + + return s0 ^ s7 ^ s38 ^ s70 ^ s81 ^ s96; + } + + /** + * Get output from output function h(x). + * + * @return Output from h(x). + */ + private int getOutput() + { + int b2 = nfsr[0] >>> 2 | nfsr[1] << 30; + int b12 = nfsr[0] >>> 12 | nfsr[1] << 20; + int b15 = nfsr[0] >>> 15 | nfsr[1] << 17; + int b36 = nfsr[1] >>> 4 | nfsr[2] << 28; + int b45 = nfsr[1] >>> 13 | nfsr[2] << 19; + int b64 = nfsr[2]; + int b73 = nfsr[2] >>> 9 | nfsr[3] << 23; + int b89 = nfsr[2] >>> 25 | nfsr[3] << 7; + int b95 = nfsr[2] >>> 31 | nfsr[3] << 1; + int s8 = lfsr[0] >>> 8 | lfsr[1] << 24; + int s13 = lfsr[0] >>> 13 | lfsr[1] << 19; + int s20 = lfsr[0] >>> 20 | lfsr[1] << 12; + int s42 = lfsr[1] >>> 10 | lfsr[2] << 22; + int s60 = lfsr[1] >>> 28 | lfsr[2] << 4; + int s79 = lfsr[2] >>> 15 | lfsr[3] << 17; + int s93 = lfsr[2] >>> 29 | lfsr[3] << 3; + int s95 = lfsr[2] >>> 31 | lfsr[3] << 1; + + return b12 & s8 ^ s13 & s20 ^ b95 & s42 ^ s60 & s79 ^ b12 & b95 & s95 ^ s93 + ^ b2 ^ b15 ^ b36 ^ b45 ^ b64 ^ b73 ^ b89; + } + + /** + * Shift array 32 bits and add val to index.length - 1. + * + * @param array The array to shift. + * @param val The value to shift in. + * @return The shifted array with val added to index.length - 1. + */ + private int[] shift(int[] array, int val) + { + array[0] = array[1]; + array[1] = array[2]; + array[2] = array[3]; + array[3] = val; + + return array; + } + + /** + * Set keys, reset cipher. + * + * @param keyBytes The key. + * @param ivBytes The IV. + */ + private void setKey(byte[] keyBytes, byte[] ivBytes) + { + ivBytes[12] = (byte)0xFF; + ivBytes[13] = (byte)0xFF; + ivBytes[14] = (byte)0xFF; + ivBytes[15] = (byte)0xFF; + workingKey = keyBytes; + workingIV = ivBytes; + + /** + * Load NFSR and LFSR + */ + int j = 0; + for (int i = 0; i < nfsr.length; i++) + { + nfsr[i] = ((workingKey[j + 3]) << 24) | ((workingKey[j + 2]) << 16) + & 0x00FF0000 | ((workingKey[j + 1]) << 8) & 0x0000FF00 + | ((workingKey[j]) & 0x000000FF); + + lfsr[i] = ((workingIV[j + 3]) << 24) | ((workingIV[j + 2]) << 16) + & 0x00FF0000 | ((workingIV[j + 1]) << 8) & 0x0000FF00 + | ((workingIV[j]) & 0x000000FF); + j += 4; + } + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) + throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream()); + } + } + + public void reset() + { + index = 4; + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * Run Grain one round(i.e. 32 bits). + */ + private void oneRound() + { + output = getOutput(); + out[0] = (byte)output; + out[1] = (byte)(output >> 8); + out[2] = (byte)(output >> 16); + out[3] = (byte)(output >> 24); + + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]); + lfsr = shift(lfsr, getOutputLFSR()); + } + + public byte returnByte(byte in) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + return (byte)(in ^ getKeyStream()); + } + + private byte getKeyStream() + { + if (index > 3) + { + oneRound(); + index = 0; + } + return out[index++]; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java new file mode 100644 index 00000000..c3baaec5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java @@ -0,0 +1,289 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream + * cipher, Grain v1. + */ +public class Grainv1Engine + implements StreamCipher +{ + + /** + * Constants + */ + private static final int STATE_SIZE = 5; + + /** + * Variables to hold the state of the engine during encryption and + * decryption + */ + private byte[] workingKey; + private byte[] workingIV; + private byte[] out; + private int[] lfsr; + private int[] nfsr; + private int output; + private int index = 2; + + private boolean initialised = false; + + public String getAlgorithmName() + { + return "Grain v1"; + } + + /** + * Initialize a Grain v1 cipher. + * + * @param forEncryption 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 forEncryption, CipherParameters params) + throws IllegalArgumentException + { + /** + * Grain encryption and decryption is completely symmetrical, so the + * 'forEncryption' is irrelevant. + */ + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "Grain v1 Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV)params; + + byte[] iv = ivParams.getIV(); + + if (iv == null || iv.length != 8) + { + throw new IllegalArgumentException( + "Grain v1 requires exactly 8 bytes of IV"); + } + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "Grain v1 Init parameters must include a key"); + } + + KeyParameter key = (KeyParameter)ivParams.getParameters(); + + /** + * Initialize variables. + */ + workingIV = new byte[key.getKey().length]; + workingKey = new byte[key.getKey().length]; + lfsr = new int[STATE_SIZE]; + nfsr = new int[STATE_SIZE]; + out = new byte[2]; + + System.arraycopy(iv, 0, workingIV, 0, iv.length); + System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length); + + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * 160 clocks initialization phase. + */ + private void initGrain() + { + for (int i = 0; i < 10; i++) + { + output = getOutput(); + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output); + lfsr = shift(lfsr, getOutputLFSR() ^ output); + } + initialised = true; + } + + /** + * Get output from non-linear function g(x). + * + * @return Output from NFSR. + */ + private int getOutputNFSR() + { + int b0 = nfsr[0]; + int b9 = nfsr[0] >>> 9 | nfsr[1] << 7; + int b14 = nfsr[0] >>> 14 | nfsr[1] << 2; + int b15 = nfsr[0] >>> 15 | nfsr[1] << 1; + int b21 = nfsr[1] >>> 5 | nfsr[2] << 11; + int b28 = nfsr[1] >>> 12 | nfsr[2] << 4; + int b33 = nfsr[2] >>> 1 | nfsr[3] << 15; + int b37 = nfsr[2] >>> 5 | nfsr[3] << 11; + int b45 = nfsr[2] >>> 13 | nfsr[3] << 3; + int b52 = nfsr[3] >>> 4 | nfsr[4] << 12; + int b60 = nfsr[3] >>> 12 | nfsr[4] << 4; + int b62 = nfsr[3] >>> 14 | nfsr[4] << 2; + int b63 = nfsr[3] >>> 15 | nfsr[4] << 1; + + return (b62 ^ b60 ^ b52 ^ b45 ^ b37 ^ b33 ^ b28 ^ b21 ^ b14 + ^ b9 ^ b0 ^ b63 & b60 ^ b37 & b33 ^ b15 & b9 ^ b60 & b52 & b45 + ^ b33 & b28 & b21 ^ b63 & b45 & b28 & b9 ^ b60 & b52 & b37 + & b33 ^ b63 & b60 & b21 & b15 ^ b63 & b60 & b52 & b45 & b37 + ^ b33 & b28 & b21 & b15 & b9 ^ b52 & b45 & b37 & b33 & b28 + & b21) & 0x0000FFFF; + } + + /** + * Get output from linear function f(x). + * + * @return Output from LFSR. + */ + private int getOutputLFSR() + { + int s0 = lfsr[0]; + int s13 = lfsr[0] >>> 13 | lfsr[1] << 3; + int s23 = lfsr[1] >>> 7 | lfsr[2] << 9; + int s38 = lfsr[2] >>> 6 | lfsr[3] << 10; + int s51 = lfsr[3] >>> 3 | lfsr[4] << 13; + int s62 = lfsr[3] >>> 14 | lfsr[4] << 2; + + return (s0 ^ s13 ^ s23 ^ s38 ^ s51 ^ s62) & 0x0000FFFF; + } + + /** + * Get output from output function h(x). + * + * @return Output from h(x). + */ + private int getOutput() + { + int b1 = nfsr[0] >>> 1 | nfsr[1] << 15; + int b2 = nfsr[0] >>> 2 | nfsr[1] << 14; + int b4 = nfsr[0] >>> 4 | nfsr[1] << 12; + int b10 = nfsr[0] >>> 10 | nfsr[1] << 6; + int b31 = nfsr[1] >>> 15 | nfsr[2] << 1; + int b43 = nfsr[2] >>> 11 | nfsr[3] << 5; + int b56 = nfsr[3] >>> 8 | nfsr[4] << 8; + int b63 = nfsr[3] >>> 15 | nfsr[4] << 1; + int s3 = lfsr[0] >>> 3 | lfsr[1] << 13; + int s25 = lfsr[1] >>> 9 | lfsr[2] << 7; + int s46 = lfsr[2] >>> 14 | lfsr[3] << 2; + int s64 = lfsr[4]; + + return (s25 ^ b63 ^ s3 & s64 ^ s46 & s64 ^ s64 & b63 ^ s3 + & s25 & s46 ^ s3 & s46 & s64 ^ s3 & s46 & b63 ^ s25 & s46 & b63 ^ s46 + & s64 & b63 ^ b1 ^ b2 ^ b4 ^ b10 ^ b31 ^ b43 ^ b56) & 0x0000FFFF; + } + + /** + * Shift array 16 bits and add val to index.length - 1. + * + * @param array The array to shift. + * @param val The value to shift in. + * @return The shifted array with val added to index.length - 1. + */ + private int[] shift(int[] array, int val) + { + array[0] = array[1]; + array[1] = array[2]; + array[2] = array[3]; + array[3] = array[4]; + array[4] = val; + + return array; + } + + /** + * Set keys, reset cipher. + * + * @param keyBytes The key. + * @param ivBytes The IV. + */ + private void setKey(byte[] keyBytes, byte[] ivBytes) + { + ivBytes[8] = (byte)0xFF; + ivBytes[9] = (byte)0xFF; + workingKey = keyBytes; + workingIV = ivBytes; + + /** + * Load NFSR and LFSR + */ + int j = 0; + for (int i = 0; i < nfsr.length; i++) + { + nfsr[i] = (workingKey[j + 1] << 8 | workingKey[j] & 0xFF) & 0x0000FFFF; + lfsr[i] = (workingIV[j + 1] << 8 | workingIV[j] & 0xFF) & 0x0000FFFF; + j += 2; + } + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) + throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream()); + } + } + + public void reset() + { + index = 2; + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * Run Grain one round(i.e. 16 bits). + */ + private void oneRound() + { + output = getOutput(); + out[0] = (byte)output; + out[1] = (byte)(output >> 8); + + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]); + lfsr = shift(lfsr, getOutputLFSR()); + } + + public byte returnByte(byte in) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + return (byte)(in ^ getKeyStream()); + } + + private byte getKeyStream() + { + if (index > 1) + { + oneRound(); + index = 0; + } + return out[index++]; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java new file mode 100644 index 00000000..69da0f03 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java @@ -0,0 +1,257 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * HC-128 is a software-efficient stream cipher created by Hongjun Wu. It + * generates keystream from a 128-bit secret key and a 128-bit initialization + * vector. + * <p> + * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf + * </p><p> + * It is a third phase candidate in the eStream contest, and is patent-free. + * No attacks are known as of today (April 2007). See + * + * http://www.ecrypt.eu.org/stream/hcp3.html + * </p> + */ +public class HC128Engine + implements StreamCipher +{ + private int[] p = new int[512]; + private int[] q = new int[512]; + private int cnt = 0; + + private static int f1(int x) + { + return rotateRight(x, 7) ^ rotateRight(x, 18) + ^ (x >>> 3); + } + + private static int f2(int x) + { + return rotateRight(x, 17) ^ rotateRight(x, 19) + ^ (x >>> 10); + } + + private int g1(int x, int y, int z) + { + return (rotateRight(x, 10) ^ rotateRight(z, 23)) + + rotateRight(y, 8); + } + + private int g2(int x, int y, int z) + { + return (rotateLeft(x, 10) ^ rotateLeft(z, 23)) + rotateLeft(y, 8); + } + + private static int rotateLeft( + int x, + int bits) + { + return (x << bits) | (x >>> -bits); + } + + private static int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } + + private int h1(int x) + { + return q[x & 0xFF] + q[((x >> 16) & 0xFF) + 256]; + } + + private int h2(int x) + { + return p[x & 0xFF] + p[((x >> 16) & 0xFF) + 256]; + } + + private static int mod1024(int x) + { + return x & 0x3FF; + } + + private static int mod512(int x) + { + return x & 0x1FF; + } + + private static int dim(int x, int y) + { + return mod512(x - y); + } + + private int step() + { + int j = mod512(cnt); + int ret; + if (cnt < 512) + { + p[j] += g1(p[dim(j, 3)], p[dim(j, 10)], p[dim(j, 511)]); + ret = h1(p[dim(j, 12)]) ^ p[j]; + } + else + { + q[j] += g2(q[dim(j, 3)], q[dim(j, 10)], q[dim(j, 511)]); + ret = h2(q[dim(j, 12)]) ^ q[j]; + } + cnt = mod1024(cnt + 1); + return ret; + } + + private byte[] key, iv; + private boolean initialised; + + private void init() + { + if (key.length != 16) + { + throw new java.lang.IllegalArgumentException( + "The key must be 128 bits long"); + } + + cnt = 0; + + int[] w = new int[1280]; + + for (int i = 0; i < 16; i++) + { + w[i >> 2] |= (key[i] & 0xff) << (8 * (i & 0x3)); + } + System.arraycopy(w, 0, w, 4, 4); + + for (int i = 0; i < iv.length && i < 16; i++) + { + w[(i >> 2) + 8] |= (iv[i] & 0xff) << (8 * (i & 0x3)); + } + System.arraycopy(w, 8, w, 12, 4); + + for (int i = 16; i < 1280; i++) + { + w[i] = f2(w[i - 2]) + w[i - 7] + f1(w[i - 15]) + w[i - 16] + i; + } + + System.arraycopy(w, 256, p, 0, 512); + System.arraycopy(w, 768, q, 0, 512); + + for (int i = 0; i < 512; i++) + { + p[i] = step(); + } + for (int i = 0; i < 512; i++) + { + q[i] = step(); + } + + cnt = 0; + } + + public String getAlgorithmName() + { + return "HC-128"; + } + + /** + * Initialise a HC-128 cipher. + * + * @param forEncryption whether or not we are for encryption. Irrelevant, as + * encryption and decryption are the same. + * @param params the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is + * inappropriate (ie. the key is not 128 bit long). + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + CipherParameters keyParam = params; + + if (params instanceof ParametersWithIV) + { + iv = ((ParametersWithIV)params).getIV(); + keyParam = ((ParametersWithIV)params).getParameters(); + } + else + { + iv = new byte[0]; + } + + if (keyParam instanceof KeyParameter) + { + key = ((KeyParameter)keyParam).getKey(); + init(); + } + else + { + throw new IllegalArgumentException( + "Invalid parameter passed to HC128 init - " + + params.getClass().getName()); + } + + initialised = true; + } + + private byte[] buf = new byte[4]; + private int idx = 0; + + private byte getByte() + { + if (idx == 0) + { + int step = step(); + buf[0] = (byte)(step & 0xFF); + step >>= 8; + buf[1] = (byte)(step & 0xFF); + step >>= 8; + buf[2] = (byte)(step & 0xFF); + step >>= 8; + buf[3] = (byte)(step & 0xFF); + } + byte ret = buf[idx]; + idx = idx + 1 & 0x3; + return ret; + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getByte()); + } + } + + public void reset() + { + idx = 0; + init(); + } + + public byte returnByte(byte in) + { + return (byte)(in ^ getByte()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java new file mode 100644 index 00000000..538d244d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java @@ -0,0 +1,244 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * HC-256 is a software-efficient stream cipher created by Hongjun Wu. It + * generates keystream from a 256-bit secret key and a 256-bit initialization + * vector. + * <p> + * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf + * </p><p> + * Its brother, HC-128, is a third phase candidate in the eStream contest. + * The algorithm is patent-free. No attacks are known as of today (April 2007). + * See + * + * http://www.ecrypt.eu.org/stream/hcp3.html + * </p> + */ +public class HC256Engine + implements StreamCipher +{ + private int[] p = new int[1024]; + private int[] q = new int[1024]; + private int cnt = 0; + + private int step() + { + int j = cnt & 0x3FF; + int ret; + if (cnt < 1024) + { + int x = p[(j - 3 & 0x3FF)]; + int y = p[(j - 1023 & 0x3FF)]; + p[j] += p[(j - 10 & 0x3FF)] + + (rotateRight(x, 10) ^ rotateRight(y, 23)) + + q[((x ^ y) & 0x3FF)]; + + x = p[(j - 12 & 0x3FF)]; + ret = (q[x & 0xFF] + q[((x >> 8) & 0xFF) + 256] + + q[((x >> 16) & 0xFF) + 512] + q[((x >> 24) & 0xFF) + 768]) + ^ p[j]; + } + else + { + int x = q[(j - 3 & 0x3FF)]; + int y = q[(j - 1023 & 0x3FF)]; + q[j] += q[(j - 10 & 0x3FF)] + + (rotateRight(x, 10) ^ rotateRight(y, 23)) + + p[((x ^ y) & 0x3FF)]; + + x = q[(j - 12 & 0x3FF)]; + ret = (p[x & 0xFF] + p[((x >> 8) & 0xFF) + 256] + + p[((x >> 16) & 0xFF) + 512] + p[((x >> 24) & 0xFF) + 768]) + ^ q[j]; + } + cnt = cnt + 1 & 0x7FF; + return ret; + } + + private byte[] key, iv; + private boolean initialised; + + private void init() + { + if (key.length != 32 && key.length != 16) + { + throw new IllegalArgumentException( + "The key must be 128/256 bits long"); + } + + if (iv.length < 16) + { + throw new IllegalArgumentException( + "The IV must be at least 128 bits long"); + } + + if (key.length != 32) + { + byte[] k = new byte[32]; + + System.arraycopy(key, 0, k, 0, key.length); + System.arraycopy(key, 0, k, 16, key.length); + + key = k; + } + + if (iv.length < 32) + { + byte[] newIV = new byte[32]; + + System.arraycopy(iv, 0, newIV, 0, iv.length); + System.arraycopy(iv, 0, newIV, iv.length, newIV.length - iv.length); + + iv = newIV; + } + + cnt = 0; + + int[] w = new int[2560]; + + for (int i = 0; i < 32; i++) + { + w[i >> 2] |= (key[i] & 0xff) << (8 * (i & 0x3)); + } + + for (int i = 0; i < 32; i++) + { + w[(i >> 2) + 8] |= (iv[i] & 0xff) << (8 * (i & 0x3)); + } + + for (int i = 16; i < 2560; i++) + { + int x = w[i - 2]; + int y = w[i - 15]; + w[i] = (rotateRight(x, 17) ^ rotateRight(x, 19) ^ (x >>> 10)) + + w[i - 7] + + (rotateRight(y, 7) ^ rotateRight(y, 18) ^ (y >>> 3)) + + w[i - 16] + i; + } + + System.arraycopy(w, 512, p, 0, 1024); + System.arraycopy(w, 1536, q, 0, 1024); + + for (int i = 0; i < 4096; i++) + { + step(); + } + + cnt = 0; + } + + public String getAlgorithmName() + { + return "HC-256"; + } + + /** + * Initialise a HC-256 cipher. + * + * @param forEncryption whether or not we are for encryption. Irrelevant, as + * encryption and decryption are the same. + * @param params the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is + * inappropriate (ie. the key is not 256 bit long). + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + CipherParameters keyParam = params; + + if (params instanceof ParametersWithIV) + { + iv = ((ParametersWithIV)params).getIV(); + keyParam = ((ParametersWithIV)params).getParameters(); + } + else + { + iv = new byte[0]; + } + + if (keyParam instanceof KeyParameter) + { + key = ((KeyParameter)keyParam).getKey(); + init(); + } + else + { + throw new IllegalArgumentException( + "Invalid parameter passed to HC256 init - " + + params.getClass().getName()); + } + + initialised = true; + } + + private byte[] buf = new byte[4]; + private int idx = 0; + + private byte getByte() + { + if (idx == 0) + { + int step = step(); + buf[0] = (byte)(step & 0xFF); + step >>= 8; + buf[1] = (byte)(step & 0xFF); + step >>= 8; + buf[2] = (byte)(step & 0xFF); + step >>= 8; + buf[3] = (byte)(step & 0xFF); + } + byte ret = buf[idx]; + idx = idx + 1 & 0x3; + return ret; + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getByte()); + } + } + + public void reset() + { + idx = 0; + init(); + } + + public byte returnByte(byte in) + { + return (byte)(in ^ getByte()); + } + + private static int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/IDEAEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/IDEAEngine.java new file mode 100644 index 00000000..fdf3f6d3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/IDEAEngine.java @@ -0,0 +1,367 @@ +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; + +/** + * A class that provides a basic International Data Encryption Algorithm (IDEA) engine. + * <p> + * This implementation is based on the "HOWTO: INTERNATIONAL DATA ENCRYPTION ALGORITHM" + * implementation summary by Fauzan Mirza (F.U.Mirza@sheffield.ac.uk). (baring 1 typo at the + * end of the mulinv function!). + * <p> + * It can be found at ftp://ftp.funet.fi/pub/crypt/cryptography/symmetric/idea/ + * <p> + * Note 1: This algorithm is patented in the USA, Japan, and Europe including + * at least Austria, France, Germany, Italy, Netherlands, Spain, Sweden, Switzerland + * and the United Kingdom. Non-commercial use is free, however any commercial + * products are liable for royalties. Please see + * <a href="http://www.mediacrypt.com">www.mediacrypt.com</a> for + * further details. This announcement has been included at the request of + * the patent holders. + * <p> + * Note 2: Due to the requests concerning the above, this algorithm is now only + * included in the extended Bouncy Castle provider and JCE signed jars. It is + * not included in the default distributions. + */ +public class IDEAEngine + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 8; + + private int[] workingKey = null; + + /** + * standard constructor. + */ + public IDEAEngine() + { + } + + /** + * initialise an IDEA cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)params).getKey()); + return; + } + + throw new IllegalArgumentException("invalid parameter passed to IDEA init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "IDEA"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("IDEA engine 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"); + } + + ideaFunc(workingKey, in, inOff, out, outOff); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private static final int MASK = 0xffff; + private static final int BASE = 0x10001; + + private int bytesToWord( + byte[] in, + int inOff) + { + return ((in[inOff] << 8) & 0xff00) + (in[inOff + 1] & 0xff); + } + + private void wordToBytes( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)(word >>> 8); + out[outOff + 1] = (byte)word; + } + + /** + * return x = x * y where the multiplication is done modulo + * 65537 (0x10001) (as defined in the IDEA specification) and + * a zero input is taken to be 65536 (0x10000). + * + * @param x the x value + * @param y the y value + * @return x = x * y + */ + private int mul( + int x, + int y) + { + if (x == 0) + { + x = (BASE - y); + } + else if (y == 0) + { + x = (BASE - x); + } + else + { + int p = x * y; + + y = p & MASK; + x = p >>> 16; + x = y - x + ((y < x) ? 1 : 0); + } + + return x & MASK; + } + + private void ideaFunc( + int[] workingKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int x0, x1, x2, x3, t0, t1; + int keyOff = 0; + + x0 = bytesToWord(in, inOff); + x1 = bytesToWord(in, inOff + 2); + x2 = bytesToWord(in, inOff + 4); + x3 = bytesToWord(in, inOff + 6); + + for (int round = 0; round < 8; round++) + { + x0 = mul(x0, workingKey[keyOff++]); + x1 += workingKey[keyOff++]; + x1 &= MASK; + x2 += workingKey[keyOff++]; + x2 &= MASK; + x3 = mul(x3, workingKey[keyOff++]); + + t0 = x1; + t1 = x2; + x2 ^= x0; + x1 ^= x3; + + x2 = mul(x2, workingKey[keyOff++]); + x1 += x2; + x1 &= MASK; + + x1 = mul(x1, workingKey[keyOff++]); + x2 += x1; + x2 &= MASK; + + x0 ^= x1; + x3 ^= x2; + x1 ^= t1; + x2 ^= t0; + } + + wordToBytes(mul(x0, workingKey[keyOff++]), out, outOff); + wordToBytes(x2 + workingKey[keyOff++], out, outOff + 2); /* NB: Order */ + wordToBytes(x1 + workingKey[keyOff++], out, outOff + 4); + wordToBytes(mul(x3, workingKey[keyOff]), out, outOff + 6); + } + + /** + * The following function is used to expand the user key to the encryption + * subkey. The first 16 bytes are the user key, and the rest of the subkey + * is calculated by rotating the previous 16 bytes by 25 bits to the left, + * and so on until the subkey is completed. + */ + private int[] expandKey( + byte[] uKey) + { + int[] key = new int[52]; + + if (uKey.length < 16) + { + byte[] tmp = new byte[16]; + + System.arraycopy(uKey, 0, tmp, tmp.length - uKey.length, uKey.length); + + uKey = tmp; + } + + for (int i = 0; i < 8; i++) + { + key[i] = bytesToWord(uKey, i * 2); + } + + for (int i = 8; i < 52; i++) + { + if ((i & 7) < 6) + { + key[i] = ((key[i - 7] & 127) << 9 | key[i - 6] >> 7) & MASK; + } + else if ((i & 7) == 6) + { + key[i] = ((key[i - 7] & 127) << 9 | key[i - 14] >> 7) & MASK; + } + else + { + key[i] = ((key[i - 15] & 127) << 9 | key[i - 14] >> 7) & MASK; + } + } + + return key; + } + + /** + * This function computes multiplicative inverse using Euclid's Greatest + * Common Divisor algorithm. Zero and one are self inverse. + * <p> + * i.e. x * mulInv(x) == 1 (modulo BASE) + */ + private int mulInv( + int x) + { + int t0, t1, q, y; + + if (x < 2) + { + return x; + } + + t0 = 1; + t1 = BASE / x; + y = BASE % x; + + while (y != 1) + { + q = x / y; + x = x % y; + t0 = (t0 + (t1 * q)) & MASK; + if (x == 1) + { + return t0; + } + q = y / x; + y = y % x; + t1 = (t1 + (t0 * q)) & MASK; + } + + return (1 - t1) & MASK; + } + + /** + * Return the additive inverse of x. + * <p> + * i.e. x + addInv(x) == 0 + */ + int addInv( + int x) + { + return (0 - x) & MASK; + } + + /** + * The function to invert the encryption subkey to the decryption subkey. + * It also involves the multiplicative inverse and the additive inverse functions. + */ + private int[] invertKey( + int[] inKey) + { + int t1, t2, t3, t4; + int p = 52; /* We work backwards */ + int[] key = new int[52]; + int inOff = 0; + + t1 = mulInv(inKey[inOff++]); + t2 = addInv(inKey[inOff++]); + t3 = addInv(inKey[inOff++]); + t4 = mulInv(inKey[inOff++]); + key[--p] = t4; + key[--p] = t3; + key[--p] = t2; + key[--p] = t1; + + for (int round = 1; round < 8; round++) + { + t1 = inKey[inOff++]; + t2 = inKey[inOff++]; + key[--p] = t2; + key[--p] = t1; + + t1 = mulInv(inKey[inOff++]); + t2 = addInv(inKey[inOff++]); + t3 = addInv(inKey[inOff++]); + t4 = mulInv(inKey[inOff++]); + key[--p] = t4; + key[--p] = t2; /* NB: Order */ + key[--p] = t3; + key[--p] = t1; + } + + t1 = inKey[inOff++]; + t2 = inKey[inOff++]; + key[--p] = t2; + key[--p] = t1; + + t1 = mulInv(inKey[inOff++]); + t2 = addInv(inKey[inOff++]); + t3 = addInv(inKey[inOff++]); + t4 = mulInv(inKey[inOff]); + key[--p] = t4; + key[--p] = t3; + key[--p] = t2; + key[--p] = t1; + + return key; + } + + private int[] generateWorkingKey( + boolean forEncryption, + byte[] userKey) + { + if (forEncryption) + { + return expandKey(userKey); + } + else + { + return invertKey(expandKey(userKey)); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/IESEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/IESEngine.java new file mode 100755 index 00000000..ea8556de --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/IESEngine.java @@ -0,0 +1,398 @@ +package org.bouncycastle.crypto.engines; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.EphemeralKeyPair; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.KeyParser; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.IESParameters; +import org.bouncycastle.crypto.params.IESWithCipherParameters; +import org.bouncycastle.crypto.params.KDFParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; + +/** + * 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 IESEngine +{ + BasicAgreement agree; + DerivationFunction kdf; + Mac mac; + BufferedBlockCipher cipher; + byte[] macBuf; + + boolean forEncryption; + CipherParameters privParam, pubParam; + IESParameters param; + + byte[] V; + private EphemeralKeyPairGenerator keyPairGenerator; + private KeyParser keyParser; + + + /** + * 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 IESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac) + { + this.agree = agree; + this.kdf = kdf; + this.mac = mac; + this.macBuf = new byte[mac.getMacSize()]; + this.cipher = null; + } + + + /** + * 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 IESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac, + BufferedBlockCipher cipher) + { + this.agree = agree; + this.kdf = kdf; + this.mac = mac; + this.macBuf = new byte[mac.getMacSize()]; + this.cipher = cipher; + } + + + /** + * Initialise the encryptor. + * + * @param forEncryption whether or not this is encryption/decryption. + * @param privParam our private key parameters + * @param pubParam the recipient's/sender's public key parameters + * @param param encoding and derivation parameters. + */ + public void init( + boolean forEncryption, + CipherParameters privParam, + CipherParameters pubParam, + CipherParameters param) + { + this.forEncryption = forEncryption; + this.privParam = privParam; + this.pubParam = pubParam; + this.param = (IESParameters)param; + this.V = new byte[0]; + } + + + /** + * Initialise the encryptor. + * + * @param publicKey the recipient's/sender's public key parameters + * @param params encoding and derivation parameters. + * @param ephemeralKeyPairGenerator the ephemeral key pair generator to use. + */ + public void init(AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator) + { + this.forEncryption = true; + this.pubParam = publicKey; + this.param = (IESParameters)params; + this.keyPairGenerator = ephemeralKeyPairGenerator; + } + + /** + * Initialise the encryptor. + * + * @param privateKey the recipient's private key. + * @param params encoding and derivation parameters. + * @param publicKeyParser the parser for reading the ephemeral public key. + */ + public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser) + { + this.forEncryption = false; + this.privParam = privateKey; + this.param = (IESParameters)params; + this.keyParser = publicKeyParser; + } + + public BufferedBlockCipher getCipher() + { + return cipher; + } + + public Mac getMac() + { + return mac; + } + + private byte[] encryptBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] C = null, K = null, K1 = null, K2 = null; + int len; + + if (cipher == null) + { + // Streaming mode. + K1 = new byte[inLen]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + + if (V.length != 0) + { + System.arraycopy(K, 0, K2, 0, K2.length); + System.arraycopy(K, K2.length, K1, 0, K1.length); + } + else + { + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, inLen, K2, 0, K2.length); + } + + C = new byte[inLen]; + + for (int i = 0; i != inLen; i++) + { + C[i] = (byte)(in[inOff + i] ^ K1[i]); + } + len = inLen; + } + else + { + // Block cipher mode. + K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, K1.length, K2, 0, K2.length); + + cipher.init(true, new KeyParameter(K1)); + C = new byte[cipher.getOutputSize(inLen)]; + len = cipher.processBytes(in, inOff, inLen, C, 0); + len += cipher.doFinal(C, len); + } + + + // Convert the length of the encoding vector into a byte array. + byte[] P2 = param.getEncodingV(); + byte[] L2 = new byte[4]; + if (V.length != 0 && P2 != null) + { + Pack.intToBigEndian(P2.length * 8, L2, 0); + } + + + // Apply the MAC. + byte[] T = new byte[mac.getMacSize()]; + + mac.init(new KeyParameter(K2)); + mac.update(C, 0, C.length); + if (P2 != null) + { + mac.update(P2, 0, P2.length); + } + if (V.length != 0) + { + mac.update(L2, 0, L2.length); + } + mac.doFinal(T, 0); + + + // Output the triple (V,C,T). + byte[] Output = new byte[V.length + len + T.length]; + System.arraycopy(V, 0, Output, 0, V.length); + System.arraycopy(C, 0, Output, V.length, len); + System.arraycopy(T, 0, Output, V.length + len, T.length); + return Output; + } + + private byte[] decryptBlock( + byte[] in_enc, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] M = null, K = null, K1 = null, K2 = null; + int len; + + if (cipher == null) + { + // Streaming mode. + K1 = new byte[inLen - V.length - mac.getMacSize()]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + + if (V.length != 0) + { + System.arraycopy(K, 0, K2, 0, K2.length); + System.arraycopy(K, K2.length, K1, 0, K1.length); + } + else + { + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, K1.length, K2, 0, K2.length); + } + + M = new byte[K1.length]; + + for (int i = 0; i != K1.length; i++) + { + M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]); + } + + len = K1.length; + } + else + { + // Block cipher mode. + K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, K1.length, K2, 0, K2.length); + + cipher.init(false, new KeyParameter(K1)); + + M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())]; + len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0); + len += cipher.doFinal(M, len); + } + + + // Convert the length of the encoding vector into a byte array. + byte[] P2 = param.getEncodingV(); + byte[] L2 = new byte[4]; + if (V.length != 0 && P2 != null) + { + Pack.intToBigEndian(P2.length * 8, L2, 0); + } + + + // Verify the MAC. + int end = inOff + inLen; + byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end); + + byte[] T2 = new byte[T1.length]; + mac.init(new KeyParameter(K2)); + mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length); + + if (P2 != null) + { + mac.update(P2, 0, P2.length); + } + if (V.length != 0) + { + mac.update(L2, 0, L2.length); + } + mac.doFinal(T2, 0); + + if (!Arrays.constantTimeAreEqual(T1, T2)) + { + throw new InvalidCipherTextException("Invalid MAC."); + } + + + // Output the message. + return Arrays.copyOfRange(M, 0, len); + } + + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + if (keyPairGenerator != null) + { + EphemeralKeyPair ephKeyPair = keyPairGenerator.generate(); + + this.privParam = ephKeyPair.getKeyPair().getPrivate(); + this.V = ephKeyPair.getEncodedPublicKey(); + } + } + else + { + if (keyParser != null) + { + ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen); + + try + { + this.pubParam = keyParser.readKey(bIn); + } + catch (IOException e) + { + throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e); + } + + int encLength = (inLen - bIn.available()); + this.V = Arrays.copyOfRange(in, inOff, inOff + encLength); + } + } + + // Compute the common value and convert to byte array. + agree.init(privParam); + BigInteger z = agree.calculateAgreement(pubParam); + byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z); + + // Create input to KDF. + byte[] VZ; + if (V.length != 0) + { + VZ = new byte[V.length + Z.length]; + System.arraycopy(V, 0, VZ, 0, V.length); + System.arraycopy(Z, 0, VZ, V.length, Z.length); + } + else + { + VZ = Z; + } + + // Initialise the KDF. + KDFParameters kdfParam = new KDFParameters(VZ, param.getDerivationV()); + kdf.init(kdfParam); + + return forEncryption + ? encryptBlock(in, inOff, inLen) + : decryptBlock(in, inOff, inLen); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java new file mode 100644 index 00000000..d6e1ae11 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java @@ -0,0 +1,219 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.util.Pack; + +/** + * Implementation of Bob Jenkin's ISAAC (Indirection Shift Accumulate Add and Count). + * see: http://www.burtleburtle.net/bob/rand/isaacafa.html +*/ +public class ISAACEngine + implements StreamCipher +{ + // Constants + private final int sizeL = 8, + stateArraySize = sizeL<<5; // 256 + + // Cipher's internal state + private int[] engineState = null, // mm + results = null; // randrsl + private int a = 0, b = 0, c = 0; + + // Engine state + private int index = 0; + private byte[] keyStream = new byte[stateArraySize<<2], // results expanded into bytes + workingKey = null; + private boolean initialised = false; + + /** + * initialise an ISAAC cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to ISAAC init - " + params.getClass().getName()); + } + /* + * ISAAC encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + KeyParameter p = (KeyParameter)params; + setKey(p.getKey()); + + return; + } + + public byte returnByte(byte in) + { + if (index == 0) + { + isaac(); + keyStream = Pack.intToBigEndian(results); + } + byte out = (byte)(keyStream[index]^in); + index = (index + 1) & 1023; + + return out; + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + if (index == 0) + { + isaac(); + keyStream = Pack.intToBigEndian(results); + } + out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]); + index = (index + 1) & 1023; + } + } + + public String getAlgorithmName() + { + return "ISAAC"; + } + + public void reset() + { + setKey(workingKey); + } + + // Private implementation + private void setKey(byte[] keyBytes) + { + workingKey = keyBytes; + + if (engineState == null) + { + engineState = new int[stateArraySize]; + } + + if (results == null) + { + results = new int[stateArraySize]; + } + + int i, j, k; + + // Reset state + for (i = 0; i < stateArraySize; i++) + { + engineState[i] = results[i] = 0; + } + a = b = c = 0; + + // Reset index counter for output + index = 0; + + // Convert the key bytes to ints and put them into results[] for initialization + byte[] t = new byte[keyBytes.length + (keyBytes.length & 3)]; + System.arraycopy(keyBytes, 0, t, 0, keyBytes.length); + for (i = 0; i < t.length; i+=4) + { + results[i >>> 2] = Pack.littleEndianToInt(t, i); + } + + // It has begun? + int[] abcdefgh = new int[sizeL]; + + for (i = 0; i < sizeL; i++) + { + abcdefgh[i] = 0x9e3779b9; // Phi (golden ratio) + } + + for (i = 0; i < 4; i++) + { + mix(abcdefgh); + } + + for (i = 0; i < 2; i++) + { + for (j = 0; j < stateArraySize; j+=sizeL) + { + for (k = 0; k < sizeL; k++) + { + abcdefgh[k] += (i<1) ? results[j+k] : engineState[j+k]; + } + + mix(abcdefgh); + + for (k = 0; k < sizeL; k++) + { + engineState[j+k] = abcdefgh[k]; + } + } + } + + isaac(); + + initialised = true; + } + + private void isaac() + { + int i, x, y; + + b += ++c; + for (i = 0; i < stateArraySize; i++) + { + x = engineState[i]; + switch (i & 3) + { + case 0: a ^= (a << 13); break; + case 1: a ^= (a >>> 6); break; + case 2: a ^= (a << 2); break; + case 3: a ^= (a >>> 16); break; + } + a += engineState[(i+128) & 0xFF]; + engineState[i] = y = engineState[(x >>> 2) & 0xFF] + a + b; + results[i] = b = engineState[(y >>> 10) & 0xFF] + x; + } + } + + private void mix(int[] x) + { + x[0]^=x[1]<< 11; x[3]+=x[0]; x[1]+=x[2]; + x[1]^=x[2]>>> 2; x[4]+=x[1]; x[2]+=x[3]; + x[2]^=x[3]<< 8; x[5]+=x[2]; x[3]+=x[4]; + x[3]^=x[4]>>>16; x[6]+=x[3]; x[4]+=x[5]; + x[4]^=x[5]<< 10; x[7]+=x[4]; x[5]+=x[6]; + x[5]^=x[6]>>> 4; x[0]+=x[5]; x[6]+=x[7]; + x[6]^=x[7]<< 8; x[1]+=x[6]; x[7]+=x[0]; + x[7]^=x[0]>>> 9; x[2]+=x[7]; x[0]+=x[1]; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java new file mode 100644 index 00000000..a5403fac --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java @@ -0,0 +1,437 @@ +package org.bouncycastle.crypto.engines; + +import java.math.BigInteger; +import java.util.Vector; +import org.bouncycastle.util.Arrays; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.params.NaccacheSternKeyParameters; +import org.bouncycastle.crypto.params.NaccacheSternPrivateKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * NaccacheStern Engine. For details on this cipher, please see + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternEngine + implements AsymmetricBlockCipher +{ + private boolean forEncryption; + + private NaccacheSternKeyParameters key; + + private Vector[] lookup = null; + + private boolean debug = false; + + private static BigInteger ZERO = BigInteger.valueOf(0); + private static BigInteger ONE = BigInteger.valueOf(1); + + /** + * Initializes this algorithm. Must be called before all other Functions. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#init(boolean, + * org.bouncycastle.crypto.CipherParameters) + */ + public void init(boolean forEncryption, CipherParameters param) + { + this.forEncryption = forEncryption; + + if (param instanceof ParametersWithRandom) + { + param = ((ParametersWithRandom) param).getParameters(); + } + + key = (NaccacheSternKeyParameters)param; + + // construct lookup table for faster decryption if necessary + if (!this.forEncryption) + { + if (debug) + { + System.out.println("Constructing lookup Array"); + } + NaccacheSternPrivateKeyParameters priv = (NaccacheSternPrivateKeyParameters)key; + Vector primes = priv.getSmallPrimes(); + lookup = new Vector[primes.size()]; + for (int i = 0; i < primes.size(); i++) + { + BigInteger actualPrime = (BigInteger)primes.elementAt(i); + int actualPrimeValue = actualPrime.intValue(); + + lookup[i] = new Vector(); + lookup[i].addElement(ONE); + + if (debug) + { + System.out.println("Constructing lookup ArrayList for " + actualPrimeValue); + } + + BigInteger accJ = ZERO; + + for (int j = 1; j < actualPrimeValue; j++) + { + accJ = accJ.add(priv.getPhi_n()); + BigInteger comp = accJ.divide(actualPrime); + lookup[i].addElement(priv.getG().modPow(comp, priv.getModulus())); + } + } + } + } + + public void setDebug(boolean debug) + { + this.debug = debug; + } + + /** + * Returns the input block size of this algorithm. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#getInputBlockSize() + */ + public int getInputBlockSize() + { + if (forEncryption) + { + // We can only encrypt values up to lowerSigmaBound + return (key.getLowerSigmaBound() + 7) / 8 - 1; + } + else + { + // We pad to modulus-size bytes for easier decryption. + return key.getModulus().toByteArray().length; + } + } + + /** + * Returns the output block size of this algorithm. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#getOutputBlockSize() + */ + public int getOutputBlockSize() + { + if (forEncryption) + { + // encrypted Data is always padded up to modulus size + return key.getModulus().toByteArray().length; + } + else + { + // decrypted Data has upper limit lowerSigmaBound + return (key.getLowerSigmaBound() + 7) / 8 - 1; + } + } + + /** + * Process a single Block using the Naccache-Stern algorithm. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#processBlock(byte[], + * int, int) + */ + public byte[] processBlock(byte[] in, int inOff, int len) throws InvalidCipherTextException + { + if (key == null) + { + throw new IllegalStateException("NaccacheStern engine not initialised"); + } + if (len > (getInputBlockSize() + 1)) + { + throw new DataLengthException("input too large for Naccache-Stern cipher.\n"); + } + + if (!forEncryption) + { + // At decryption make sure that we receive padded data blocks + if (len < getInputBlockSize()) + { + throw new InvalidCipherTextException("BlockLength does not match modulus for Naccache-Stern cipher.\n"); + } + } + + byte[] block; + + if (inOff != 0 || len != in.length) + { + block = new byte[len]; + System.arraycopy(in, inOff, block, 0, len); + } + else + { + block = in; + } + + // transform input into BigInteger + BigInteger input = new BigInteger(1, block); + if (debug) + { + System.out.println("input as BigInteger: " + input); + } + byte[] output; + if (forEncryption) + { + output = encrypt(input); + } + else + { + Vector plain = new Vector(); + NaccacheSternPrivateKeyParameters priv = (NaccacheSternPrivateKeyParameters)key; + Vector primes = priv.getSmallPrimes(); + // Get Chinese Remainders of CipherText + for (int i = 0; i < primes.size(); i++) + { + BigInteger exp = input.modPow(priv.getPhi_n().divide((BigInteger)primes.elementAt(i)), priv.getModulus()); + Vector al = lookup[i]; + if (lookup[i].size() != ((BigInteger)primes.elementAt(i)).intValue()) + { + if (debug) + { + System.out.println("Prime is " + primes.elementAt(i) + ", lookup table has size " + al.size()); + } + throw new InvalidCipherTextException("Error in lookup Array for " + + ((BigInteger)primes.elementAt(i)).intValue() + + ": Size mismatch. Expected ArrayList with length " + + ((BigInteger)primes.elementAt(i)).intValue() + " but found ArrayList of length " + + lookup[i].size()); + } + int lookedup = al.indexOf(exp); + + if (lookedup == -1) + { + if (debug) + { + System.out.println("Actual prime is " + primes.elementAt(i)); + System.out.println("Decrypted value is " + exp); + + System.out.println("LookupList for " + primes.elementAt(i) + " with size " + lookup[i].size() + + " is: "); + for (int j = 0; j < lookup[i].size(); j++) + { + System.out.println(lookup[i].elementAt(j)); + } + } + throw new InvalidCipherTextException("Lookup failed"); + } + plain.addElement(BigInteger.valueOf(lookedup)); + } + BigInteger test = chineseRemainder(plain, primes); + + // Should not be used as an oracle, so reencrypt output to see + // if it corresponds to input + + // this breaks probabilisic encryption, so disable it. Anyway, we do + // use the first n primes for key generation, so it is pretty easy + // to guess them. But as stated in the paper, this is not a security + // breach. So we can just work with the correct sigma. + + // if (debug) { + // System.out.println("Decryption is " + test); + // } + // if ((key.getG().modPow(test, key.getModulus())).equals(input)) { + // output = test.toByteArray(); + // } else { + // if(debug){ + // System.out.println("Engine seems to be used as an oracle, + // returning null"); + // } + // output = null; + // } + + output = test.toByteArray(); + + } + + return output; + } + + /** + * Encrypts a BigInteger aka Plaintext with the public key. + * + * @param plain + * The BigInteger to encrypt + * @return The byte[] representation of the encrypted BigInteger (i.e. + * crypted.toByteArray()) + */ + public byte[] encrypt(BigInteger plain) + { + // Always return modulus size values 0-padded at the beginning + // 0-padding at the beginning is correctly parsed by BigInteger :) + byte[] output = key.getModulus().toByteArray(); + Arrays.fill(output, (byte)0); + byte[] tmp = key.getG().modPow(plain, key.getModulus()).toByteArray(); + System + .arraycopy(tmp, 0, output, output.length - tmp.length, + tmp.length); + if (debug) + { + System.out + .println("Encrypted value is: " + new BigInteger(output)); + } + return output; + } + + /** + * Adds the contents of two encrypted blocks mod sigma + * + * @param block1 + * the first encrypted block + * @param block2 + * the second encrypted block + * @return encrypt((block1 + block2) mod sigma) + * @throws InvalidCipherTextException + */ + public byte[] addCryptedBlocks(byte[] block1, byte[] block2) + throws InvalidCipherTextException + { + // check for correct blocksize + if (forEncryption) + { + if ((block1.length > getOutputBlockSize()) + || (block2.length > getOutputBlockSize())) + { + throw new InvalidCipherTextException( + "BlockLength too large for simple addition.\n"); + } + } + else + { + if ((block1.length > getInputBlockSize()) + || (block2.length > getInputBlockSize())) + { + throw new InvalidCipherTextException( + "BlockLength too large for simple addition.\n"); + } + } + + // calculate resulting block + BigInteger m1Crypt = new BigInteger(1, block1); + BigInteger m2Crypt = new BigInteger(1, block2); + BigInteger m1m2Crypt = m1Crypt.multiply(m2Crypt); + m1m2Crypt = m1m2Crypt.mod(key.getModulus()); + if (debug) + { + System.out.println("c(m1) as BigInteger:....... " + m1Crypt); + System.out.println("c(m2) as BigInteger:....... " + m2Crypt); + System.out.println("c(m1)*c(m2)%n = c(m1+m2)%n: " + m1m2Crypt); + } + + byte[] output = key.getModulus().toByteArray(); + Arrays.fill(output, (byte)0); + System.arraycopy(m1m2Crypt.toByteArray(), 0, output, output.length + - m1m2Crypt.toByteArray().length, + m1m2Crypt.toByteArray().length); + + return output; + } + + /** + * Convenience Method for data exchange with the cipher. + * + * Determines blocksize and splits data to blocksize. + * + * @param data the data to be processed + * @return the data after it went through the NaccacheSternEngine. + * @throws InvalidCipherTextException + */ + public byte[] processData(byte[] data) throws InvalidCipherTextException + { + if (debug) + { + System.out.println(); + } + if (data.length > getInputBlockSize()) + { + int inBlocksize = getInputBlockSize(); + int outBlocksize = getOutputBlockSize(); + if (debug) + { + System.out.println("Input blocksize is: " + inBlocksize + " bytes"); + System.out.println("Output blocksize is: " + outBlocksize + " bytes"); + System.out.println("Data has length:.... " + data.length + " bytes"); + } + int datapos = 0; + int retpos = 0; + byte[] retval = new byte[(data.length / inBlocksize + 1) * outBlocksize]; + while (datapos < data.length) + { + byte[] tmp; + if (datapos + inBlocksize < data.length) + { + tmp = processBlock(data, datapos, inBlocksize); + datapos += inBlocksize; + } + else + { + tmp = processBlock(data, datapos, data.length - datapos); + datapos += data.length - datapos; + } + if (debug) + { + System.out.println("new datapos is " + datapos); + } + if (tmp != null) + { + System.arraycopy(tmp, 0, retval, retpos, tmp.length); + + retpos += tmp.length; + } + else + { + if (debug) + { + System.out.println("cipher returned null"); + } + throw new InvalidCipherTextException("cipher returned null"); + } + } + byte[] ret = new byte[retpos]; + System.arraycopy(retval, 0, ret, 0, retpos); + if (debug) + { + System.out.println("returning " + ret.length + " bytes"); + } + return ret; + } + else + { + if (debug) + { + System.out.println("data size is less then input block size, processing directly"); + } + return processBlock(data, 0, data.length); + } + } + + /** + * Computes the integer x that is expressed through the given primes and the + * congruences with the chinese remainder theorem (CRT). + * + * @param congruences + * the congruences c_i + * @param primes + * the primes p_i + * @return an integer x for that x % p_i == c_i + */ + private static BigInteger chineseRemainder(Vector congruences, Vector primes) + { + BigInteger retval = ZERO; + BigInteger all = ONE; + for (int i = 0; i < primes.size(); i++) + { + all = all.multiply((BigInteger)primes.elementAt(i)); + } + for (int i = 0; i < primes.size(); i++) + { + BigInteger a = (BigInteger)primes.elementAt(i); + BigInteger b = all.divide(a); + BigInteger b_ = b.modInverse(a); + BigInteger tmp = b.multiply(b_); + tmp = tmp.multiply((BigInteger)congruences.elementAt(i)); + retval = retval.add(tmp); + } + + return retval.mod(all); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java new file mode 100644 index 00000000..c4494c40 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java @@ -0,0 +1,263 @@ +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; + +/** + * A Noekeon engine, using direct-key mode. + */ + +public class NoekeonEngine + implements BlockCipher +{ + private static final int genericSize = 16; // Block and key size, as well as the amount of rounds. + + private static final int[] nullVector = + { + 0x00, 0x00, 0x00, 0x00 // Used in decryption + }, + + roundConstants = + { + 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, + 0xd4 + }; + + private int[] state = new int[4], // a + subKeys = new int[4], // k + decryptKeys = new int[4]; + + private boolean _initialised, + _forEncryption; + + /** + * Create an instance of the Noekeon encryption algorithm + * and set some defaults + */ + public NoekeonEngine() + { + _initialised = false; + } + + public String getAlgorithmName() + { + return "Noekeon"; + } + + public int getBlockSize() + { + return genericSize; + } + + /** + * initialise + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to Noekeon init - " + params.getClass().getName()); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (!_initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + genericSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + genericSize) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + return (_forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + * <p> + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + subKeys[0] = bytesToIntBig(key, 0); + subKeys[1] = bytesToIntBig(key, 4); + subKeys[2] = bytesToIntBig(key, 8); + subKeys[3] = bytesToIntBig(key, 12); + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + state[0] = bytesToIntBig(in, inOff); + state[1] = bytesToIntBig(in, inOff+4); + state[2] = bytesToIntBig(in, inOff+8); + state[3] = bytesToIntBig(in, inOff+12); + + int i; + for (i = 0; i < genericSize; i++) + { + state[0] ^= roundConstants[i]; + theta(state, subKeys); + pi1(state); + gamma(state); + pi2(state); + } + + state[0] ^= roundConstants[i]; + theta(state, subKeys); + + intToBytesBig(state[0], out, outOff); + intToBytesBig(state[1], out, outOff+4); + intToBytesBig(state[2], out, outOff+8); + intToBytesBig(state[3], out, outOff+12); + + return genericSize; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + state[0] = bytesToIntBig(in, inOff); + state[1] = bytesToIntBig(in, inOff+4); + state[2] = bytesToIntBig(in, inOff+8); + state[3] = bytesToIntBig(in, inOff+12); + + System.arraycopy(subKeys, 0, decryptKeys, 0, subKeys.length); + theta(decryptKeys, nullVector); + + int i; + for (i = genericSize; i > 0; i--) + { + theta(state, decryptKeys); + state[0] ^= roundConstants[i]; + pi1(state); + gamma(state); + pi2(state); + } + + theta(state, decryptKeys); + state[0] ^= roundConstants[i]; + + intToBytesBig(state[0], out, outOff); + intToBytesBig(state[1], out, outOff+4); + intToBytesBig(state[2], out, outOff+8); + intToBytesBig(state[3], out, outOff+12); + + return genericSize; + } + + private void gamma(int[] a) + { + a[1] ^= ~a[3] & ~a[2]; + a[0] ^= a[2] & a[1]; + + int tmp = a[3]; + a[3] = a[0]; + a[0] = tmp; + a[2] ^= a[0]^a[1]^a[3]; + + a[1] ^= ~a[3] & ~a[2]; + a[0] ^= a[2] & a[1]; + } + + private void theta(int[] a, int[] k) + { + int tmp; + + tmp = a[0]^a[2]; + tmp ^= rotl(tmp,8)^rotl(tmp,24); + a[1] ^= tmp; + a[3] ^= tmp; + + for (int i = 0; i < 4; i++) + { + a[i] ^= k[i]; + } + + tmp = a[1]^a[3]; + tmp ^= rotl(tmp,8)^rotl(tmp,24); + a[0] ^= tmp; + a[2] ^= tmp; + } + + private void pi1(int[] a) + { + a[1] = rotl(a[1], 1); + a[2] = rotl(a[2], 5); + a[3] = rotl(a[3], 2); + } + + private void pi2(int[] a) + { + a[1] = rotl(a[1], 31); + a[2] = rotl(a[2], 27); + a[3] = rotl(a[3], 30); + } + + // Helpers + + private int bytesToIntBig(byte[] in, int off) + { + return ((in[off++]) << 24) | + ((in[off++] & 0xff) << 16) | + ((in[off++] & 0xff) << 8) | + (in[off ] & 0xff); + } + + private void intToBytesBig(int x, byte[] out, int off) + { + out[off++] = (byte)(x >>> 24); + out[off++] = (byte)(x >>> 16); + out[off++] = (byte)(x >>> 8); + out[off ] = (byte)x; + } + + private int rotl(int x, int y) + { + return (x << y) | (x >>> (32-y)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java new file mode 100644 index 00000000..95a395a4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java @@ -0,0 +1,85 @@ +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; + +/** + * The no-op engine that just copies bytes through, irrespective of whether encrypting and decrypting. + * Provided for the sake of completeness. + */ +public class NullEngine implements BlockCipher +{ + private boolean initialised; + protected static final int BLOCK_SIZE = 1; + + /** + * Standard constructor. + */ + public NullEngine() + { + super(); + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#init(boolean, org.bouncycastle.crypto.CipherParameters) + */ + public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException + { + // we don't mind any parameters that may come in + this.initialised = true; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#getAlgorithmName() + */ + public String getAlgorithmName() + { + return "Null"; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#getBlockSize() + */ + public int getBlockSize() + { + return BLOCK_SIZE; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#processBlock(byte[], int, byte[], int) + */ + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + if (!initialised) + { + throw new IllegalStateException("Null engine 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"); + } + + for (int i = 0; i < BLOCK_SIZE; ++i) + { + out[outOff + i] = in[inOff + i]; + } + + return BLOCK_SIZE; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#reset() + */ + public void reset() + { + // nothing needs to be done + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RC2Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RC2Engine.java new file mode 100644 index 00000000..02cb8811 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RC2Engine.java @@ -0,0 +1,317 @@ +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.crypto.params.RC2Parameters; + +/** + * an implementation of RC2 as described in RFC 2268 + * "A Description of the RC2(r) Encryption Algorithm" R. Rivest. + */ +public class RC2Engine + implements BlockCipher +{ + // + // the values we use for key expansion (based on the digits of PI) + // + private static byte[] piTable = + { + (byte)0xd9, (byte)0x78, (byte)0xf9, (byte)0xc4, (byte)0x19, (byte)0xdd, (byte)0xb5, (byte)0xed, + (byte)0x28, (byte)0xe9, (byte)0xfd, (byte)0x79, (byte)0x4a, (byte)0xa0, (byte)0xd8, (byte)0x9d, + (byte)0xc6, (byte)0x7e, (byte)0x37, (byte)0x83, (byte)0x2b, (byte)0x76, (byte)0x53, (byte)0x8e, + (byte)0x62, (byte)0x4c, (byte)0x64, (byte)0x88, (byte)0x44, (byte)0x8b, (byte)0xfb, (byte)0xa2, + (byte)0x17, (byte)0x9a, (byte)0x59, (byte)0xf5, (byte)0x87, (byte)0xb3, (byte)0x4f, (byte)0x13, + (byte)0x61, (byte)0x45, (byte)0x6d, (byte)0x8d, (byte)0x9, (byte)0x81, (byte)0x7d, (byte)0x32, + (byte)0xbd, (byte)0x8f, (byte)0x40, (byte)0xeb, (byte)0x86, (byte)0xb7, (byte)0x7b, (byte)0xb, + (byte)0xf0, (byte)0x95, (byte)0x21, (byte)0x22, (byte)0x5c, (byte)0x6b, (byte)0x4e, (byte)0x82, + (byte)0x54, (byte)0xd6, (byte)0x65, (byte)0x93, (byte)0xce, (byte)0x60, (byte)0xb2, (byte)0x1c, + (byte)0x73, (byte)0x56, (byte)0xc0, (byte)0x14, (byte)0xa7, (byte)0x8c, (byte)0xf1, (byte)0xdc, + (byte)0x12, (byte)0x75, (byte)0xca, (byte)0x1f, (byte)0x3b, (byte)0xbe, (byte)0xe4, (byte)0xd1, + (byte)0x42, (byte)0x3d, (byte)0xd4, (byte)0x30, (byte)0xa3, (byte)0x3c, (byte)0xb6, (byte)0x26, + (byte)0x6f, (byte)0xbf, (byte)0xe, (byte)0xda, (byte)0x46, (byte)0x69, (byte)0x7, (byte)0x57, + (byte)0x27, (byte)0xf2, (byte)0x1d, (byte)0x9b, (byte)0xbc, (byte)0x94, (byte)0x43, (byte)0x3, + (byte)0xf8, (byte)0x11, (byte)0xc7, (byte)0xf6, (byte)0x90, (byte)0xef, (byte)0x3e, (byte)0xe7, + (byte)0x6, (byte)0xc3, (byte)0xd5, (byte)0x2f, (byte)0xc8, (byte)0x66, (byte)0x1e, (byte)0xd7, + (byte)0x8, (byte)0xe8, (byte)0xea, (byte)0xde, (byte)0x80, (byte)0x52, (byte)0xee, (byte)0xf7, + (byte)0x84, (byte)0xaa, (byte)0x72, (byte)0xac, (byte)0x35, (byte)0x4d, (byte)0x6a, (byte)0x2a, + (byte)0x96, (byte)0x1a, (byte)0xd2, (byte)0x71, (byte)0x5a, (byte)0x15, (byte)0x49, (byte)0x74, + (byte)0x4b, (byte)0x9f, (byte)0xd0, (byte)0x5e, (byte)0x4, (byte)0x18, (byte)0xa4, (byte)0xec, + (byte)0xc2, (byte)0xe0, (byte)0x41, (byte)0x6e, (byte)0xf, (byte)0x51, (byte)0xcb, (byte)0xcc, + (byte)0x24, (byte)0x91, (byte)0xaf, (byte)0x50, (byte)0xa1, (byte)0xf4, (byte)0x70, (byte)0x39, + (byte)0x99, (byte)0x7c, (byte)0x3a, (byte)0x85, (byte)0x23, (byte)0xb8, (byte)0xb4, (byte)0x7a, + (byte)0xfc, (byte)0x2, (byte)0x36, (byte)0x5b, (byte)0x25, (byte)0x55, (byte)0x97, (byte)0x31, + (byte)0x2d, (byte)0x5d, (byte)0xfa, (byte)0x98, (byte)0xe3, (byte)0x8a, (byte)0x92, (byte)0xae, + (byte)0x5, (byte)0xdf, (byte)0x29, (byte)0x10, (byte)0x67, (byte)0x6c, (byte)0xba, (byte)0xc9, + (byte)0xd3, (byte)0x0, (byte)0xe6, (byte)0xcf, (byte)0xe1, (byte)0x9e, (byte)0xa8, (byte)0x2c, + (byte)0x63, (byte)0x16, (byte)0x1, (byte)0x3f, (byte)0x58, (byte)0xe2, (byte)0x89, (byte)0xa9, + (byte)0xd, (byte)0x38, (byte)0x34, (byte)0x1b, (byte)0xab, (byte)0x33, (byte)0xff, (byte)0xb0, + (byte)0xbb, (byte)0x48, (byte)0xc, (byte)0x5f, (byte)0xb9, (byte)0xb1, (byte)0xcd, (byte)0x2e, + (byte)0xc5, (byte)0xf3, (byte)0xdb, (byte)0x47, (byte)0xe5, (byte)0xa5, (byte)0x9c, (byte)0x77, + (byte)0xa, (byte)0xa6, (byte)0x20, (byte)0x68, (byte)0xfe, (byte)0x7f, (byte)0xc1, (byte)0xad + }; + + private static final int BLOCK_SIZE = 8; + + private int[] workingKey; + private boolean encrypting; + + private int[] generateWorkingKey( + byte[] key, + int bits) + { + int x; + int[] xKey = new int[128]; + + for (int i = 0; i != key.length; i++) + { + xKey[i] = key[i] & 0xff; + } + + // Phase 1: Expand input key to 128 bytes + int len = key.length; + + if (len < 128) + { + int index = 0; + + x = xKey[len - 1]; + + do + { + x = piTable[(x + xKey[index++]) & 255] & 0xff; + xKey[len++] = x; + } + while (len < 128); + } + + // Phase 2 - reduce effective key size to "bits" + len = (bits + 7) >> 3; + x = piTable[xKey[128 - len] & (255 >> (7 & -bits))] & 0xff; + xKey[128 - len] = x; + + for (int i = 128 - len - 1; i >= 0; i--) + { + x = piTable[x ^ xKey[i + len]] & 0xff; + xKey[i] = x; + } + + // Phase 3 - copy to newKey in little-endian order + int[] newKey = new int[64]; + + for (int i = 0; i != newKey.length; i++) + { + newKey[i] = (xKey[2 * i] + (xKey[2 * i + 1] << 8)); + } + + return newKey; + } + + /** + * initialise a RC2 cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + this.encrypting = encrypting; + + if (params instanceof RC2Parameters) + { + RC2Parameters param = (RC2Parameters)params; + + workingKey = generateWorkingKey(param.getKey(), + param.getEffectiveKeyBits()); + } + else if (params instanceof KeyParameter) + { + byte[] key = ((KeyParameter)params).getKey(); + + workingKey = generateWorkingKey(key, key.length * 8); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to RC2 init - " + params.getClass().getName()); + } + + } + + public void reset() + { + } + + public String getAlgorithmName() + { + return "RC2"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("RC2 engine 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; + } + + /** + * return the result rotating the 16 bit number in x left by y + */ + private int rotateWordLeft( + int x, + int y) + { + x &= 0xffff; + return (x << y) | (x >> (16 - y)); + } + + private void encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int x76, x54, x32, x10; + + x76 = ((in[inOff + 7] & 0xff) << 8) + (in[inOff + 6] & 0xff); + x54 = ((in[inOff + 5] & 0xff) << 8) + (in[inOff + 4] & 0xff); + x32 = ((in[inOff + 3] & 0xff) << 8) + (in[inOff + 2] & 0xff); + x10 = ((in[inOff + 1] & 0xff) << 8) + (in[inOff + 0] & 0xff); + + for (int i = 0; i <= 16; i += 4) + { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i ], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5); + } + + x10 += workingKey[x76 & 63]; + x32 += workingKey[x10 & 63]; + x54 += workingKey[x32 & 63]; + x76 += workingKey[x54 & 63]; + + for (int i = 20; i <= 40; i += 4) + { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i ], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5); + } + + x10 += workingKey[x76 & 63]; + x32 += workingKey[x10 & 63]; + x54 += workingKey[x32 & 63]; + x76 += workingKey[x54 & 63]; + + for (int i = 44; i < 64; i += 4) + { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i ], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5); + } + + out[outOff + 0] = (byte)x10; + out[outOff + 1] = (byte)(x10 >> 8); + out[outOff + 2] = (byte)x32; + out[outOff + 3] = (byte)(x32 >> 8); + out[outOff + 4] = (byte)x54; + out[outOff + 5] = (byte)(x54 >> 8); + out[outOff + 6] = (byte)x76; + out[outOff + 7] = (byte)(x76 >> 8); + } + + private void decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int x76, x54, x32, x10; + + x76 = ((in[inOff + 7] & 0xff) << 8) + (in[inOff + 6] & 0xff); + x54 = ((in[inOff + 5] & 0xff) << 8) + (in[inOff + 4] & 0xff); + x32 = ((in[inOff + 3] & 0xff) << 8) + (in[inOff + 2] & 0xff); + x10 = ((in[inOff + 1] & 0xff) << 8) + (in[inOff + 0] & 0xff); + + for (int i = 60; i >= 44; i -= 4) + { + x76 = rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i+3]); + x54 = rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i+2]); + x32 = rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i+1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i ]); + } + + x76 -= workingKey[x54 & 63]; + x54 -= workingKey[x32 & 63]; + x32 -= workingKey[x10 & 63]; + x10 -= workingKey[x76 & 63]; + + for (int i = 40; i >= 20; i -= 4) + { + x76 = rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i+3]); + x54 = rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i+2]); + x32 = rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i+1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i ]); + } + + x76 -= workingKey[x54 & 63]; + x54 -= workingKey[x32 & 63]; + x32 -= workingKey[x10 & 63]; + x10 -= workingKey[x76 & 63]; + + for (int i = 16; i >= 0; i -= 4) + { + x76 = rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i+3]); + x54 = rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i+2]); + x32 = rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i+1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i ]); + } + + out[outOff + 0] = (byte)x10; + out[outOff + 1] = (byte)(x10 >> 8); + out[outOff + 2] = (byte)x32; + out[outOff + 3] = (byte)(x32 >> 8); + out[outOff + 4] = (byte)x54; + out[outOff + 5] = (byte)(x54 >> 8); + out[outOff + 6] = (byte)x76; + out[outOff + 7] = (byte)(x76 >> 8); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java new file mode 100644 index 00000000..185387d8 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java @@ -0,0 +1,383 @@ +package org.bouncycastle.crypto.engines; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Wrapper; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +/** + * Wrap keys according to RFC 3217 - RC2 mechanism + */ +public class RC2WrapEngine + implements Wrapper +{ + /** Field engine */ + private CBCBlockCipher engine; + + /** Field param */ + private CipherParameters param; + + /** Field paramPlusIV */ + private ParametersWithIV paramPlusIV; + + /** Field iv */ + private byte[] iv; + + /** Field forWrapping */ + private boolean forWrapping; + + private SecureRandom sr; + + /** Field IV2 */ + private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, + (byte) 0x2c, (byte) 0x79, (byte) 0xe8, + (byte) 0x21, (byte) 0x05 }; + + // + // checksum digest + // + Digest sha1 = new SHA1Digest(); + byte[] digest = new byte[20]; + + /** + * Method init + * + * @param forWrapping + * @param param + */ + public void init(boolean forWrapping, CipherParameters param) + { + this.forWrapping = forWrapping; + this.engine = new CBCBlockCipher(new RC2Engine()); + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom pWithR = (ParametersWithRandom)param; + sr = pWithR.getRandom(); + param = pWithR.getParameters(); + } + else + { + sr = new SecureRandom(); + } + + if (param instanceof ParametersWithIV) + { + this.paramPlusIV = (ParametersWithIV)param; + this.iv = this.paramPlusIV.getIV(); + this.param = this.paramPlusIV.getParameters(); + + if (this.forWrapping) + { + if ((this.iv == null) || (this.iv.length != 8)) + { + throw new IllegalArgumentException("IV is not 8 octets"); + } + } + else + { + throw new IllegalArgumentException( + "You should not supply an IV for unwrapping"); + } + } + else + { + this.param = param; + + if (this.forWrapping) + { + + // Hm, we have no IV but we want to wrap ?!? + // well, then we have to create our own IV. + this.iv = new byte[8]; + + sr.nextBytes(iv); + + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + } + } + + } + + /** + * Method getAlgorithmName + * + * @return the algorithm name "RC2". + */ + public String getAlgorithmName() + { + return "RC2"; + } + + /** + * Method wrap + * + * @param in + * @param inOff + * @param inLen + * @return the wrapped bytes. + */ + public byte[] wrap(byte[] in, int inOff, int inLen) + { + + if (!forWrapping) + { + throw new IllegalStateException("Not initialized for wrapping"); + } + + int length = inLen + 1; + if ((length % 8) != 0) + { + length += 8 - (length % 8); + } + + byte keyToBeWrapped[] = new byte[length]; + + keyToBeWrapped[0] = (byte)inLen; + System.arraycopy(in, inOff, keyToBeWrapped, 1, inLen); + + byte[] pad = new byte[keyToBeWrapped.length - inLen - 1]; + + if (pad.length > 0) + { + sr.nextBytes(pad); + System.arraycopy(pad, 0, keyToBeWrapped, inLen + 1, pad.length); + } + + // Compute the CMS Key Checksum, (section 5.6.1), call this CKS. + byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped); + + // Let WKCKS = WK || CKS where || is concatenation. + byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length]; + + System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length); + System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length); + + // Encrypt WKCKS in CBC mode using KEK as the key and IV as the + // initialization vector. Call the results TEMP1. + byte TEMP1[] = new byte[WKCKS.length]; + + System.arraycopy(WKCKS, 0, TEMP1, 0, WKCKS.length); + + int noOfBlocks = WKCKS.length / engine.getBlockSize(); + int extraBytes = WKCKS.length % engine.getBlockSize(); + + if (extraBytes != 0) + { + throw new IllegalStateException("Not multiple of block length"); + } + + engine.init(true, paramPlusIV); + + for (int i = 0; i < noOfBlocks; i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(TEMP1, currentBytePos, TEMP1, currentBytePos); + } + + // Left TEMP2 = IV || TEMP1. + byte[] TEMP2 = new byte[this.iv.length + TEMP1.length]; + + System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length); + System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length); + + // Reverse the order of the octets in TEMP2 and call the result TEMP3. + byte[] TEMP3 = new byte[TEMP2.length]; + + for (int i = 0; i < TEMP2.length; i++) + { + TEMP3[i] = TEMP2[TEMP2.length - (i + 1)]; + } + + // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector + // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the + // desired + // result. It is 40 octets long if a 168 bit key is being wrapped. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(true, param2); + + for (int i = 0; i < noOfBlocks + 1; i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); + } + + return TEMP3; + } + + /** + * Method unwrap + * + * @param in + * @param inOff + * @param inLen + * @return the unwrapped bytes. + * @throws InvalidCipherTextException + */ + public byte[] unwrap(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException + { + + if (forWrapping) + { + throw new IllegalStateException("Not set for unwrapping"); + } + + if (in == null) + { + throw new InvalidCipherTextException("Null pointer as ciphertext"); + } + + if (inLen % engine.getBlockSize() != 0) + { + throw new InvalidCipherTextException("Ciphertext not multiple of " + + engine.getBlockSize()); + } + + /* + * // Check if the length of the cipher text is reasonable given the key // + * type. It must be 40 bytes for a 168 bit key and either 32, 40, or // + * 48 bytes for a 128, 192, or 256 bit key. If the length is not + * supported // or inconsistent with the algorithm for which the key is + * intended, // return error. // // we do not accept 168 bit keys. it + * has to be 192 bit. int lengthA = (estimatedKeyLengthInBit / 8) + 16; + * int lengthB = estimatedKeyLengthInBit % 8; + * + * if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { throw + * new XMLSecurityException("empty"); } + */ + + // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK + // and an initialization vector (IV) of 0x4adda22c79e82105. Call the + // output TEMP3. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(false, param2); + + byte TEMP3[] = new byte[inLen]; + + System.arraycopy(in, inOff, TEMP3, 0, inLen); + + for (int i = 0; i < (TEMP3.length / engine.getBlockSize()); i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); + } + + // Reverse the order of the octets in TEMP3 and call the result TEMP2. + byte[] TEMP2 = new byte[TEMP3.length]; + + for (int i = 0; i < TEMP3.length; i++) + { + TEMP2[i] = TEMP3[TEMP3.length - (i + 1)]; + } + + // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining + // octets. + this.iv = new byte[8]; + + byte[] TEMP1 = new byte[TEMP2.length - 8]; + + System.arraycopy(TEMP2, 0, this.iv, 0, 8); + System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8); + + // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV + // found in the previous step. Call the result WKCKS. + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + + this.engine.init(false, this.paramPlusIV); + + byte[] LCEKPADICV = new byte[TEMP1.length]; + + System.arraycopy(TEMP1, 0, LCEKPADICV, 0, TEMP1.length); + + for (int i = 0; i < (LCEKPADICV.length / engine.getBlockSize()); i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(LCEKPADICV, currentBytePos, LCEKPADICV, + currentBytePos); + } + + // Decompose LCEKPADICV. CKS is the last 8 octets and WK, the wrapped + // key, are + // those octets before the CKS. + byte[] result = new byte[LCEKPADICV.length - 8]; + byte[] CKStoBeVerified = new byte[8]; + + System.arraycopy(LCEKPADICV, 0, result, 0, LCEKPADICV.length - 8); + System.arraycopy(LCEKPADICV, LCEKPADICV.length - 8, CKStoBeVerified, 0, + 8); + + // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and + // compare + // with the CKS extracted in the above step. If they are not equal, + // return error. + if (!checkCMSKeyChecksum(result, CKStoBeVerified)) + { + throw new InvalidCipherTextException( + "Checksum inside ciphertext is corrupted"); + } + + if ((result.length - ((result[0] & 0xff) + 1)) > 7) + { + throw new InvalidCipherTextException("too many pad bytes (" + + (result.length - ((result[0] & 0xff) + 1)) + ")"); + } + + // CEK is the wrapped key, now extracted for use in data decryption. + byte[] CEK = new byte[result[0]]; + System.arraycopy(result, 1, CEK, 0, CEK.length); + return CEK; + } + + /** + * Some key wrap algorithms make use of the Key Checksum defined + * in CMS [CMS-Algorithms]. This is used to provide an integrity + * check value for the key being wrapped. The algorithm is + * + * - Compute the 20 octet SHA-1 hash on the key being wrapped. + * - Use the first 8 octets of this hash as the checksum value. + * + * @param key + * @return + * @throws RuntimeException + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private byte[] calculateCMSKeyChecksum( + byte[] key) + { + byte[] result = new byte[8]; + + sha1.update(key, 0, key.length); + sha1.doFinal(digest, 0); + + System.arraycopy(digest, 0, result, 0, 8); + + return result; + } + + /** + * @param key + * @param checksum + * @return + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private boolean checkCMSKeyChecksum( + byte[] key, + byte[] checksum) + { + return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RC4Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RC4Engine.java new file mode 100644 index 00000000..4de7ea69 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RC4Engine.java @@ -0,0 +1,144 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; + +public class RC4Engine implements StreamCipher +{ + private final static int STATE_LENGTH = 256; + + /* + * variables to hold the state of the RC4 engine + * during encryption and decryption + */ + + private byte[] engineState = null; + private int x = 0; + private int y = 0; + private byte[] workingKey = null; + + /** + * initialise a RC4 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params + ) + { + if (params instanceof KeyParameter) + { + /* + * RC4 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + workingKey = ((KeyParameter)params).getKey(); + setKey(workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to RC4 init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "RC4"; + } + + public byte returnByte(byte in) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + return (byte)(in ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len ; i++) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + out[i+outOff] = (byte)(in[i + inOff] + ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + } + + public void reset() + { + setKey(workingKey); + } + + // Private implementation + + private void setKey(byte[] keyBytes) + { + workingKey = keyBytes; + + // System.out.println("the key length is ; "+ workingKey.length); + + x = 0; + y = 0; + + if (engineState == null) + { + engineState = new byte[STATE_LENGTH]; + } + + // reset the state of the engine + for (int i=0; i < STATE_LENGTH; i++) + { + engineState[i] = (byte)i; + } + + int i1 = 0; + int i2 = 0; + + for (int i=0; i < STATE_LENGTH; i++) + { + i2 = ((keyBytes[i1] & 0xff) + engineState[i] + i2) & 0xff; + // do the byte-swap inline + byte tmp = engineState[i]; + engineState[i] = engineState[i2]; + engineState[i2] = tmp; + i1 = (i1+1) % keyBytes.length; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java new file mode 100644 index 00000000..9fb6f550 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java @@ -0,0 +1,287 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.RC5Parameters; + +/** + * The specification for RC5 came from the <code>RC5 Encryption Algorithm</code> + * publication in RSA CryptoBytes, Spring of 1995. + * <em>http://www.rsasecurity.com/rsalabs/cryptobytes</em>. + * <p> + * This implementation has a word size of 32 bits. + * <p> + * Implementation courtesy of Tito Pena. + */ +public class RC532Engine + implements BlockCipher +{ + /* + * the number of rounds to perform + */ + private int _noRounds; + + /* + * the expanded key array of size 2*(rounds + 1) + */ + private int _S[]; + + /* + * our "magic constants" for 32 32 + * + * Pw = Odd((e-2) * 2^wordsize) + * Qw = Odd((o-2) * 2^wordsize) + * + * where e is the base of natural logarithms (2.718281828...) + * and o is the golden ratio (1.61803398...) + */ + private static final int P32 = 0xb7e15163; + private static final int Q32 = 0x9e3779b9; + + private boolean forEncryption; + + /** + * Create an instance of the RC5 encryption algorithm + * and set some defaults + */ + public RC532Engine() + { + _noRounds = 12; // the default + _S = null; + } + + public String getAlgorithmName() + { + return "RC5-32"; + } + + public int getBlockSize() + { + return 2 * 4; + } + + /** + * initialise a RC5-32 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof RC5Parameters) + { + RC5Parameters p = (RC5Parameters)params; + + _noRounds = p.getRounds(); + + setKey(p.getKey()); + } + else if (params instanceof KeyParameter) + { + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to RC532 init - " + params.getClass().getName()); + } + + this.forEncryption = forEncryption; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + * <p> + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + // + // KEY EXPANSION: + // + // There are 3 phases to the key expansion. + // + // Phase 1: + // Copy the secret key K[0...b-1] into an array L[0..c-1] of + // c = ceil(b/u), where u = 32/8 in little-endian order. + // In other words, we fill up L using u consecutive key bytes + // of K. Any unfilled byte positions in L are zeroed. In the + // case that b = c = 0, set c = 1 and L[0] = 0. + // + int[] L = new int[(key.length + (4 - 1)) / 4]; + + for (int i = 0; i != key.length; i++) + { + L[i / 4] += (key[i] & 0xff) << (8 * (i % 4)); + } + + // + // Phase 2: + // Initialize S to a particular fixed pseudo-random bit pattern + // using an arithmetic progression modulo 2^wordsize determined + // by the magic numbers, Pw & Qw. + // + _S = new int[2*(_noRounds + 1)]; + + _S[0] = P32; + for (int i=1; i < _S.length; i++) + { + _S[i] = (_S[i-1] + Q32); + } + + // + // Phase 3: + // Mix in the user's secret key in 3 passes over the arrays S & L. + // The max of the arrays sizes is used as the loop control + // + int iter; + + if (L.length > _S.length) + { + iter = 3 * L.length; + } + else + { + iter = 3 * _S.length; + } + + int A = 0, B = 0; + int i = 0, j = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[i] = rotateLeft(_S[i] + A + B, 3); + B = L[j] = rotateLeft(L[j] + A + B, A+B); + i = (i+1) % _S.length; + j = (j+1) % L.length; + } + } + + /** + * Encrypt the given block starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * <p> + * @param in in byte buffer containing data to encrypt + * @param inOff offset into src buffer + * @param out out buffer where encrypted data is written + * @param outOff offset into out buffer + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int A = bytesToWord(in, inOff) + _S[0]; + int B = bytesToWord(in, inOff + 4) + _S[1]; + + for (int i = 1; i <= _noRounds; i++) + { + A = rotateLeft(A ^ B, B) + _S[2*i]; + B = rotateLeft(B ^ A, A) + _S[2*i+1]; + } + + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + 4); + + return 2 * 4; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int A = bytesToWord(in, inOff); + int B = bytesToWord(in, inOff + 4); + + for (int i = _noRounds; i >= 1; i--) + { + B = rotateRight(B - _S[2*i+1], A) ^ A; + A = rotateRight(A - _S[2*i], B) ^ B; + } + + wordToBytes(A - _S[0], out, outOff); + wordToBytes(B - _S[1], out, outOff + 4); + + return 2 * 4; + } + + + ////////////////////////////////////////////////////////////// + // + // PRIVATE Helper Methods + // + ////////////////////////////////////////////////////////////// + + /** + * Perform a left "spin" of the word. The rotation of the given + * word <em>x</em> is rotated left by <em>y</em> bits. + * Only the <em>lg(32)</em> low-order bits of <em>y</em> + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + * <p> + * @param x word to rotate + * @param y number of bits to rotate % 32 + */ + private int rotateLeft(int x, int y) + { + return ((x << (y & (32-1))) | (x >>> (32 - (y & (32-1))))); + } + + /** + * Perform a right "spin" of the word. The rotation of the given + * word <em>x</em> is rotated left by <em>y</em> bits. + * Only the <em>lg(32)</em> low-order bits of <em>y</em> + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + * <p> + * @param x word to rotate + * @param y number of bits to rotate % 32 + */ + private int rotateRight(int x, int y) + { + return ((x >>> (y & (32-1))) | (x << (32 - (y & (32-1))))); + } + + private int bytesToWord( + byte[] src, + int srcOff) + { + return (src[srcOff] & 0xff) | ((src[srcOff + 1] & 0xff) << 8) + | ((src[srcOff + 2] & 0xff) << 16) | ((src[srcOff + 3] & 0xff) << 24); + } + + private void wordToBytes( + int word, + byte[] dst, + int dstOff) + { + dst[dstOff] = (byte)word; + dst[dstOff + 1] = (byte)(word >> 8); + dst[dstOff + 2] = (byte)(word >> 16); + dst[dstOff + 3] = (byte)(word >> 24); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java new file mode 100644 index 00000000..2121a4bc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java @@ -0,0 +1,288 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.RC5Parameters; + +/** + * The specification for RC5 came from the <code>RC5 Encryption Algorithm</code> + * publication in RSA CryptoBytes, Spring of 1995. + * <em>http://www.rsasecurity.com/rsalabs/cryptobytes</em>. + * <p> + * This implementation is set to work with a 64 bit word size. + * <p> + * Implementation courtesy of Tito Pena. + */ +public class RC564Engine + implements BlockCipher +{ + private static final int wordSize = 64; + private static final int bytesPerWord = wordSize / 8; + + /* + * the number of rounds to perform + */ + private int _noRounds; + + /* + * the expanded key array of size 2*(rounds + 1) + */ + private long _S[]; + + /* + * our "magic constants" for wordSize 62 + * + * Pw = Odd((e-2) * 2^wordsize) + * Qw = Odd((o-2) * 2^wordsize) + * + * where e is the base of natural logarithms (2.718281828...) + * and o is the golden ratio (1.61803398...) + */ + private static final long P64 = 0xb7e151628aed2a6bL; + private static final long Q64 = 0x9e3779b97f4a7c15L; + + private boolean forEncryption; + + /** + * Create an instance of the RC5 encryption algorithm + * and set some defaults + */ + public RC564Engine() + { + _noRounds = 12; + _S = null; + } + + public String getAlgorithmName() + { + return "RC5-64"; + } + + public int getBlockSize() + { + return 2 * bytesPerWord; + } + + /** + * initialise a RC5-64 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof RC5Parameters)) + { + throw new IllegalArgumentException("invalid parameter passed to RC564 init - " + params.getClass().getName()); + } + + RC5Parameters p = (RC5Parameters)params; + + this.forEncryption = forEncryption; + + _noRounds = p.getRounds(); + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + * <p> + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + // + // KEY EXPANSION: + // + // There are 3 phases to the key expansion. + // + // Phase 1: + // Copy the secret key K[0...b-1] into an array L[0..c-1] of + // c = ceil(b/u), where u = wordSize/8 in little-endian order. + // In other words, we fill up L using u consecutive key bytes + // of K. Any unfilled byte positions in L are zeroed. In the + // case that b = c = 0, set c = 1 and L[0] = 0. + // + long[] L = new long[(key.length + (bytesPerWord - 1)) / bytesPerWord]; + + for (int i = 0; i != key.length; i++) + { + L[i / bytesPerWord] += (long)(key[i] & 0xff) << (8 * (i % bytesPerWord)); + } + + // + // Phase 2: + // Initialize S to a particular fixed pseudo-random bit pattern + // using an arithmetic progression modulo 2^wordsize determined + // by the magic numbers, Pw & Qw. + // + _S = new long[2*(_noRounds + 1)]; + + _S[0] = P64; + for (int i=1; i < _S.length; i++) + { + _S[i] = (_S[i-1] + Q64); + } + + // + // Phase 3: + // Mix in the user's secret key in 3 passes over the arrays S & L. + // The max of the arrays sizes is used as the loop control + // + int iter; + + if (L.length > _S.length) + { + iter = 3 * L.length; + } + else + { + iter = 3 * _S.length; + } + + long A = 0, B = 0; + int i = 0, j = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[i] = rotateLeft(_S[i] + A + B, 3); + B = L[j] = rotateLeft(L[j] + A + B, A+B); + i = (i+1) % _S.length; + j = (j+1) % L.length; + } + } + + /** + * Encrypt the given block starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * <p> + * @param in in byte buffer containing data to encrypt + * @param inOff offset into src buffer + * @param out out buffer where encrypted data is written + * @param outOff offset into out buffer + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + long A = bytesToWord(in, inOff) + _S[0]; + long B = bytesToWord(in, inOff + bytesPerWord) + _S[1]; + + for (int i = 1; i <= _noRounds; i++) + { + A = rotateLeft(A ^ B, B) + _S[2*i]; + B = rotateLeft(B ^ A, A) + _S[2*i+1]; + } + + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + bytesPerWord); + + return 2 * bytesPerWord; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + long A = bytesToWord(in, inOff); + long B = bytesToWord(in, inOff + bytesPerWord); + + for (int i = _noRounds; i >= 1; i--) + { + B = rotateRight(B - _S[2*i+1], A) ^ A; + A = rotateRight(A - _S[2*i], B) ^ B; + } + + wordToBytes(A - _S[0], out, outOff); + wordToBytes(B - _S[1], out, outOff + bytesPerWord); + + return 2 * bytesPerWord; + } + + + ////////////////////////////////////////////////////////////// + // + // PRIVATE Helper Methods + // + ////////////////////////////////////////////////////////////// + + /** + * Perform a left "spin" of the word. The rotation of the given + * word <em>x</em> is rotated left by <em>y</em> bits. + * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em> + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + * <p> + * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private long rotateLeft(long x, long y) + { + return ((x << (y & (wordSize-1))) | (x >>> (wordSize - (y & (wordSize-1))))); + } + + /** + * Perform a right "spin" of the word. The rotation of the given + * word <em>x</em> is rotated left by <em>y</em> bits. + * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em> + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + * <p> + * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private long rotateRight(long x, long y) + { + return ((x >>> (y & (wordSize-1))) | (x << (wordSize - (y & (wordSize-1))))); + } + + private long bytesToWord( + byte[] src, + int srcOff) + { + long word = 0; + + for (int i = bytesPerWord - 1; i >= 0; i--) + { + word = (word << 8) + (src[i + srcOff] & 0xff); + } + + return word; + } + + private void wordToBytes( + long word, + byte[] dst, + int dstOff) + { + for (int i = 0; i < bytesPerWord; i++) + { + dst[i + dstOff] = (byte)word; + word >>>= 8; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RC6Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RC6Engine.java new file mode 100644 index 00000000..bbf5d306 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RC6Engine.java @@ -0,0 +1,363 @@ +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; + +/** + * An RC6 engine. + */ +public class RC6Engine + implements BlockCipher +{ + private static final int wordSize = 32; + private static final int bytesPerWord = wordSize / 8; + + /* + * the number of rounds to perform + */ + private static final int _noRounds = 20; + + /* + * the expanded key array of size 2*(rounds + 1) + */ + private int _S[]; + + /* + * our "magic constants" for wordSize 32 + * + * Pw = Odd((e-2) * 2^wordsize) + * Qw = Odd((o-2) * 2^wordsize) + * + * where e is the base of natural logarithms (2.718281828...) + * and o is the golden ratio (1.61803398...) + */ + private static final int P32 = 0xb7e15163; + private static final int Q32 = 0x9e3779b9; + + private static final int LGW = 5; // log2(32) + + private boolean forEncryption; + + /** + * Create an instance of the RC6 encryption algorithm + * and set some defaults + */ + public RC6Engine() + { + _S = null; + } + + public String getAlgorithmName() + { + return "RC6"; + } + + public int getBlockSize() + { + return 4 * bytesPerWord; + } + + /** + * initialise a RC5-32 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to RC6 init - " + params.getClass().getName()); + } + + KeyParameter p = (KeyParameter)params; + this.forEncryption = forEncryption; + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int blockSize = getBlockSize(); + if (_S == null) + { + throw new IllegalStateException("RC6 engine not initialised"); + } + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + if ((outOff + blockSize) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + return (forEncryption) + ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + * <p> + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + + // + // KEY EXPANSION: + // + // There are 3 phases to the key expansion. + // + // Phase 1: + // Copy the secret key K[0...b-1] into an array L[0..c-1] of + // c = ceil(b/u), where u = wordSize/8 in little-endian order. + // In other words, we fill up L using u consecutive key bytes + // of K. Any unfilled byte positions in L are zeroed. In the + // case that b = c = 0, set c = 1 and L[0] = 0. + // + // compute number of dwords + int c = (key.length + (bytesPerWord - 1)) / bytesPerWord; + if (c == 0) + { + c = 1; + } + int[] L = new int[(key.length + bytesPerWord - 1) / bytesPerWord]; + + // load all key bytes into array of key dwords + for (int i = key.length - 1; i >= 0; i--) + { + L[i / bytesPerWord] = (L[i / bytesPerWord] << 8) + (key[i] & 0xff); + } + + // + // Phase 2: + // Key schedule is placed in a array of 2+2*ROUNDS+2 = 44 dwords. + // Initialize S to a particular fixed pseudo-random bit pattern + // using an arithmetic progression modulo 2^wordsize determined + // by the magic numbers, Pw & Qw. + // + _S = new int[2+2*_noRounds+2]; + + _S[0] = P32; + for (int i=1; i < _S.length; i++) + { + _S[i] = (_S[i-1] + Q32); + } + + // + // Phase 3: + // Mix in the user's secret key in 3 passes over the arrays S & L. + // The max of the arrays sizes is used as the loop control + // + int iter; + + if (L.length > _S.length) + { + iter = 3 * L.length; + } + else + { + iter = 3 * _S.length; + } + + int A = 0; + int B = 0; + int i = 0, j = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[i] = rotateLeft(_S[i] + A + B, 3); + B = L[j] = rotateLeft(L[j] + A + B, A+B); + i = (i+1) % _S.length; + j = (j+1) % L.length; + } + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // load A,B,C and D registers from in. + int A = bytesToWord(in, inOff); + int B = bytesToWord(in, inOff + bytesPerWord); + int C = bytesToWord(in, inOff + bytesPerWord*2); + int D = bytesToWord(in, inOff + bytesPerWord*3); + + // Do pseudo-round #0: pre-whitening of B and D + B += _S[0]; + D += _S[1]; + + // perform round #1,#2 ... #ROUNDS of encryption + for (int i = 1; i <= _noRounds; i++) + { + int t = 0,u = 0; + + t = B*(2*B+1); + t = rotateLeft(t,5); + + u = D*(2*D+1); + u = rotateLeft(u,5); + + A ^= t; + A = rotateLeft(A,u); + A += _S[2*i]; + + C ^= u; + C = rotateLeft(C,t); + C += _S[2*i+1]; + + int temp = A; + A = B; + B = C; + C = D; + D = temp; + } + // do pseudo-round #(ROUNDS+1) : post-whitening of A and C + A += _S[2*_noRounds+2]; + C += _S[2*_noRounds+3]; + + // store A, B, C and D registers to out + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + bytesPerWord); + wordToBytes(C, out, outOff + bytesPerWord*2); + wordToBytes(D, out, outOff + bytesPerWord*3); + + return 4 * bytesPerWord; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // load A,B,C and D registers from out. + int A = bytesToWord(in, inOff); + int B = bytesToWord(in, inOff + bytesPerWord); + int C = bytesToWord(in, inOff + bytesPerWord*2); + int D = bytesToWord(in, inOff + bytesPerWord*3); + + // Undo pseudo-round #(ROUNDS+1) : post whitening of A and C + C -= _S[2*_noRounds+3]; + A -= _S[2*_noRounds+2]; + + // Undo round #ROUNDS, .., #2,#1 of encryption + for (int i = _noRounds; i >= 1; i--) + { + int t=0,u = 0; + + int temp = D; + D = C; + C = B; + B = A; + A = temp; + + t = B*(2*B+1); + t = rotateLeft(t, LGW); + + u = D*(2*D+1); + u = rotateLeft(u, LGW); + + C -= _S[2*i+1]; + C = rotateRight(C,t); + C ^= u; + + A -= _S[2*i]; + A = rotateRight(A,u); + A ^= t; + + } + // Undo pseudo-round #0: pre-whitening of B and D + D -= _S[1]; + B -= _S[0]; + + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + bytesPerWord); + wordToBytes(C, out, outOff + bytesPerWord*2); + wordToBytes(D, out, outOff + bytesPerWord*3); + + return 4 * bytesPerWord; + } + + + ////////////////////////////////////////////////////////////// + // + // PRIVATE Helper Methods + // + ////////////////////////////////////////////////////////////// + + /** + * Perform a left "spin" of the word. The rotation of the given + * word <em>x</em> is rotated left by <em>y</em> bits. + * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em> + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is 32. + * <p> + * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private int rotateLeft(int x, int y) + { + return (x << y) | (x >>> -y); + } + + /** + * Perform a right "spin" of the word. The rotation of the given + * word <em>x</em> is rotated left by <em>y</em> bits. + * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em> + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + * <p> + * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private int rotateRight(int x, int y) + { + return (x >>> y) | (x << -y); + } + + private int bytesToWord( + byte[] src, + int srcOff) + { + int word = 0; + + for (int i = bytesPerWord - 1; i >= 0; i--) + { + word = (word << 8) + (src[i + srcOff] & 0xff); + } + + return word; + } + + private void wordToBytes( + int word, + byte[] dst, + int dstOff) + { + for (int i = 0; i < bytesPerWord; i++) + { + dst[i + dstOff] = (byte)word; + word >>>= 8; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java new file mode 100644 index 00000000..0d10eeb5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java @@ -0,0 +1,175 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Wrapper; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +import java.security.SecureRandom; + +/** + * an implementation of the RFC 3211 Key Wrap + * Specification. + */ +public class RFC3211WrapEngine + implements Wrapper +{ + private CBCBlockCipher engine; + private ParametersWithIV param; + private boolean forWrapping; + private SecureRandom rand; + + public RFC3211WrapEngine(BlockCipher engine) + { + this.engine = new CBCBlockCipher(engine); + } + + public void init( + boolean forWrapping, + CipherParameters param) + { + this.forWrapping = forWrapping; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + rand = p.getRandom(); + this.param = (ParametersWithIV)p.getParameters(); + } + else + { + if (forWrapping) + { + rand = new SecureRandom(); + } + + this.param = (ParametersWithIV)param; + } + } + + public String getAlgorithmName() + { + return engine.getUnderlyingCipher().getAlgorithmName() + "/RFC3211Wrap"; + } + + public byte[] wrap( + byte[] in, + int inOff, + int inLen) + { + if (!forWrapping) + { + throw new IllegalStateException("not set for wrapping"); + } + + engine.init(true, param); + + int blockSize = engine.getBlockSize(); + byte[] cekBlock; + + if (inLen + 4 < blockSize * 2) + { + cekBlock = new byte[blockSize * 2]; + } + else + { + cekBlock = new byte[(inLen + 4) % blockSize == 0 ? inLen + 4 : ((inLen + 4) / blockSize + 1) * blockSize]; + } + + cekBlock[0] = (byte)inLen; + cekBlock[1] = (byte)~in[inOff]; + cekBlock[2] = (byte)~in[inOff + 1]; + cekBlock[3] = (byte)~in[inOff + 2]; + + System.arraycopy(in, inOff, cekBlock, 4, inLen); + + for (int i = inLen + 4; i < cekBlock.length; i++) + { + cekBlock[i] = (byte)rand.nextInt(); + } + + for (int i = 0; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + for (int i = 0; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + return cekBlock; + } + + public byte[] unwrap( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forWrapping) + { + throw new IllegalStateException("not set for unwrapping"); + } + + int blockSize = engine.getBlockSize(); + + if (inLen < 2 * blockSize) + { + throw new InvalidCipherTextException("input too short"); + } + + byte[] cekBlock = new byte[inLen]; + byte[] iv = new byte[blockSize]; + + System.arraycopy(in, inOff, cekBlock, 0, inLen); + System.arraycopy(in, inOff, iv, 0, iv.length); + + engine.init(false, new ParametersWithIV(param.getParameters(), iv)); + + for (int i = blockSize; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + System.arraycopy(cekBlock, cekBlock.length - iv.length, iv, 0, iv.length); + + engine.init(false, new ParametersWithIV(param.getParameters(), iv)); + + engine.processBlock(cekBlock, 0, cekBlock, 0); + + engine.init(false, param); + + for (int i = 0; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + if ((cekBlock[0] & 0xff) > cekBlock.length - 4) + { + throw new InvalidCipherTextException("wrapped key corrupted"); + } + + byte[] key = new byte[cekBlock[0] & 0xff]; + + System.arraycopy(cekBlock, 4, key, 0, cekBlock[0]); + + // Note: Using constant time comparison + int nonEqual = 0; + for (int i = 0; i != 3; i++) + { + byte check = (byte)~cekBlock[1 + i]; + nonEqual |= (check ^ key[i]); + } + if (nonEqual != 0) + { + throw new InvalidCipherTextException("wrapped key fails checksum"); + } + + return key; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java new file mode 100644 index 00000000..540bd25d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java @@ -0,0 +1,177 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Wrapper; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +/** + * an implementation of the AES Key Wrapper from the NIST Key Wrap + * Specification as described in RFC 3394. + * <p> + * For further details see: <a href="http://www.ietf.org/rfc/rfc3394.txt">http://www.ietf.org/rfc/rfc3394.txt</a> + * and <a href="http://csrc.nist.gov/encryption/kms/key-wrap.pdf">http://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>. + */ +public class RFC3394WrapEngine + implements Wrapper +{ + private BlockCipher engine; + private KeyParameter param; + private boolean forWrapping; + + private byte[] iv = { + (byte)0xa6, (byte)0xa6, (byte)0xa6, (byte)0xa6, + (byte)0xa6, (byte)0xa6, (byte)0xa6, (byte)0xa6 }; + + public RFC3394WrapEngine(BlockCipher engine) + { + this.engine = engine; + } + + public void init( + boolean forWrapping, + CipherParameters param) + { + this.forWrapping = forWrapping; + + if (param instanceof ParametersWithRandom) + { + param = ((ParametersWithRandom) param).getParameters(); + } + + if (param instanceof KeyParameter) + { + this.param = (KeyParameter)param; + } + else if (param instanceof ParametersWithIV) + { + this.iv = ((ParametersWithIV)param).getIV(); + this.param = (KeyParameter)((ParametersWithIV) param).getParameters(); + if (this.iv.length != 8) + { + throw new IllegalArgumentException("IV not equal to 8"); + } + } + } + + public String getAlgorithmName() + { + return engine.getAlgorithmName(); + } + + public byte[] wrap( + byte[] in, + int inOff, + int inLen) + { + if (!forWrapping) + { + throw new IllegalStateException("not set for wrapping"); + } + + int n = inLen / 8; + + if ((n * 8) != inLen) + { + throw new DataLengthException("wrap data must be a multiple of 8 bytes"); + } + + byte[] block = new byte[inLen + iv.length]; + byte[] buf = new byte[8 + iv.length]; + + System.arraycopy(iv, 0, block, 0, iv.length); + System.arraycopy(in, 0, block, iv.length, inLen); + + engine.init(true, param); + + for (int j = 0; j != 6; j++) + { + for (int i = 1; i <= n; i++) + { + System.arraycopy(block, 0, buf, 0, iv.length); + System.arraycopy(block, 8 * i, buf, iv.length, 8); + engine.processBlock(buf, 0, buf, 0); + + int t = n * j + i; + for (int k = 1; t != 0; k++) + { + byte v = (byte)t; + + buf[iv.length - k] ^= v; + + t >>>= 8; + } + + System.arraycopy(buf, 0, block, 0, 8); + System.arraycopy(buf, 8, block, 8 * i, 8); + } + } + + return block; + } + + public byte[] unwrap( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forWrapping) + { + throw new IllegalStateException("not set for unwrapping"); + } + + int n = inLen / 8; + + if ((n * 8) != inLen) + { + throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes"); + } + + byte[] block = new byte[inLen - iv.length]; + byte[] a = new byte[iv.length]; + byte[] buf = new byte[8 + iv.length]; + + System.arraycopy(in, 0, a, 0, iv.length); + System.arraycopy(in, iv.length, block, 0, inLen - iv.length); + + engine.init(false, param); + + n = n - 1; + + for (int j = 5; j >= 0; j--) + { + for (int i = n; i >= 1; i--) + { + System.arraycopy(a, 0, buf, 0, iv.length); + System.arraycopy(block, 8 * (i - 1), buf, iv.length, 8); + + int t = n * j + i; + for (int k = 1; t != 0; k++) + { + byte v = (byte)t; + + buf[iv.length - k] ^= v; + + t >>>= 8; + } + + engine.processBlock(buf, 0, buf, 0); + System.arraycopy(buf, 0, a, 0, 8); + System.arraycopy(buf, 8, block, 8 * (i - 1), 8); + } + } + + if (!Arrays.constantTimeAreEqual(a, iv)) + { + throw new InvalidCipherTextException("checksum failed"); + } + + return block; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java new file mode 100644 index 00000000..e7fb9432 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java @@ -0,0 +1,126 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * this does your basic RSA algorithm with blinding + */ +public class RSABlindedEngine + implements AsymmetricBlockCipher +{ + private static BigInteger ONE = BigInteger.valueOf(1); + + private RSACoreEngine core = new RSACoreEngine(); + private RSAKeyParameters key; + private SecureRandom random; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + core.init(forEncryption, param); + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RSAKeyParameters)rParam.getParameters(); + random = rParam.getRandom(); + } + else + { + key = (RSAKeyParameters)param; + random = new SecureRandom(); + } + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return core.getInputBlockSize(); + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + return core.getOutputBlockSize(); + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (key == null) + { + throw new IllegalStateException("RSA engine not initialised"); + } + + BigInteger input = core.convertInput(in, inOff, inLen); + + BigInteger result; + if (key instanceof RSAPrivateCrtKeyParameters) + { + RSAPrivateCrtKeyParameters k = (RSAPrivateCrtKeyParameters)key; + + BigInteger e = k.getPublicExponent(); + if (e != null) // can't do blinding without a public exponent + { + BigInteger m = k.getModulus(); + BigInteger r = BigIntegers.createRandomInRange(ONE, m.subtract(ONE), random); + + BigInteger blindedInput = r.modPow(e, m).multiply(input).mod(m); + BigInteger blindedResult = core.processBlock(blindedInput); + + BigInteger rInv = r.modInverse(m); + result = blindedResult.multiply(rInv).mod(m); + } + else + { + result = core.processBlock(input); + } + } + else + { + result = core.processBlock(input); + } + + return core.convertOutput(result); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java new file mode 100644 index 00000000..a8ecb9bf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java @@ -0,0 +1,137 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSABlindingParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +import java.math.BigInteger; + +/** + * This does your basic RSA Chaum's blinding and unblinding as outlined in + * "Handbook of Applied Cryptography", page 475. You need to use this if you are + * trying to get another party to generate signatures without them being aware + * of the message they are signing. + */ +public class RSABlindingEngine + implements AsymmetricBlockCipher +{ + private RSACoreEngine core = new RSACoreEngine(); + + private RSAKeyParameters key; + private BigInteger blindingFactor; + + private boolean forEncryption; + + /** + * Initialise the blinding engine. + * + * @param forEncryption true if we are encrypting (blinding), false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + RSABlindingParameters p; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + p = (RSABlindingParameters)rParam.getParameters(); + } + else + { + p = (RSABlindingParameters)param; + } + + core.init(forEncryption, p.getPublicKey()); + + this.forEncryption = forEncryption; + this.key = p.getPublicKey(); + this.blindingFactor = p.getBlindingFactor(); + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return core.getInputBlockSize(); + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + return core.getOutputBlockSize(); + } + + /** + * Process a single block using the RSA blinding algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @throws DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + BigInteger msg = core.convertInput(in, inOff, inLen); + + if (forEncryption) + { + msg = blindMessage(msg); + } + else + { + msg = unblindMessage(msg); + } + + return core.convertOutput(msg); + } + + /* + * Blind message with the blind factor. + */ + private BigInteger blindMessage( + BigInteger msg) + { + BigInteger blindMsg = blindingFactor; + blindMsg = msg.multiply(blindMsg.modPow(key.getExponent(), key.getModulus())); + blindMsg = blindMsg.mod(key.getModulus()); + + return blindMsg; + } + + /* + * Unblind the message blinded with the blind factor. + */ + private BigInteger unblindMessage( + BigInteger blindedMsg) + { + BigInteger m = key.getModulus(); + BigInteger msg = blindedMsg; + BigInteger blindFactorInverse = blindingFactor.modInverse(m); + msg = msg.multiply(blindFactorInverse); + msg = msg.mod(m); + + return msg; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RSACoreEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RSACoreEngine.java new file mode 100644 index 00000000..510cd5a5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RSACoreEngine.java @@ -0,0 +1,203 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.math.BigInteger; + +/** + * this does your basic RSA algorithm. + */ +class RSACoreEngine +{ + private RSAKeyParameters key; + private boolean forEncryption; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RSAKeyParameters)rParam.getParameters(); + } + else + { + key = (RSAKeyParameters)param; + } + + this.forEncryption = forEncryption; + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + int bitSize = key.getModulus().bitLength(); + + if (forEncryption) + { + return (bitSize + 7) / 8 - 1; + } + else + { + return (bitSize + 7) / 8; + } + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + int bitSize = key.getModulus().bitLength(); + + if (forEncryption) + { + return (bitSize + 7) / 8; + } + else + { + return (bitSize + 7) / 8 - 1; + } + } + + public BigInteger convertInput( + byte[] in, + int inOff, + int inLen) + { + if (inLen > (getInputBlockSize() + 1)) + { + throw new DataLengthException("input too large for RSA cipher."); + } + else if (inLen == (getInputBlockSize() + 1) && !forEncryption) + { + throw new DataLengthException("input too large for RSA cipher."); + } + + byte[] block; + + if (inOff != 0 || inLen != in.length) + { + block = new byte[inLen]; + + System.arraycopy(in, inOff, block, 0, inLen); + } + else + { + block = in; + } + + BigInteger res = new BigInteger(1, block); + if (res.compareTo(key.getModulus()) >= 0) + { + throw new DataLengthException("input too large for RSA cipher."); + } + + return res; + } + + public byte[] convertOutput( + BigInteger result) + { + byte[] output = result.toByteArray(); + + if (forEncryption) + { + if (output[0] == 0 && output.length > getOutputBlockSize()) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + + System.arraycopy(output, 1, tmp, 0, tmp.length); + + return tmp; + } + + if (output.length < getOutputBlockSize()) // have ended up with less bytes than normal, lengthen + { + byte[] tmp = new byte[getOutputBlockSize()]; + + System.arraycopy(output, 0, tmp, tmp.length - output.length, output.length); + + return tmp; + } + } + else + { + if (output[0] == 0) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + + System.arraycopy(output, 1, tmp, 0, tmp.length); + + return tmp; + } + } + + return output; + } + + public BigInteger processBlock(BigInteger input) + { + if (key instanceof RSAPrivateCrtKeyParameters) + { + // + // we have the extra factors, use the Chinese Remainder Theorem - the author + // wishes to express his thanks to Dirk Bonekaemper at rtsffm.com for + // advice regarding the expression of this. + // + RSAPrivateCrtKeyParameters crtKey = (RSAPrivateCrtKeyParameters)key; + + BigInteger p = crtKey.getP(); + BigInteger q = crtKey.getQ(); + BigInteger dP = crtKey.getDP(); + BigInteger dQ = crtKey.getDQ(); + BigInteger qInv = crtKey.getQInv(); + + BigInteger mP, mQ, h, m; + + // mP = ((input mod p) ^ dP)) mod p + mP = (input.remainder(p)).modPow(dP, p); + + // mQ = ((input mod q) ^ dQ)) mod q + mQ = (input.remainder(q)).modPow(dQ, q); + + // h = qInv * (mP - mQ) mod p + h = mP.subtract(mQ); + h = h.multiply(qInv); + h = h.mod(p); // mod (in Java) returns the positive residual + + // m = h * q + mQ + m = h.multiply(q); + m = m.add(mQ); + + return m; + } + else + { + return input.modPow( + key.getExponent(), key.getModulus()); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RSAEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RSAEngine.java new file mode 100644 index 00000000..009dcd43 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RSAEngine.java @@ -0,0 +1,78 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; + +/** + * this does your basic RSA algorithm. + */ +public class RSAEngine + implements AsymmetricBlockCipher +{ + private RSACoreEngine core; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + if (core == null) + { + core = new RSACoreEngine(); + } + + core.init(forEncryption, param); + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return core.getInputBlockSize(); + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + return core.getOutputBlockSize(); + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (core == null) + { + throw new IllegalStateException("RSA engine not initialised"); + } + + return core.convertOutput(core.processBlock(core.convertInput(in, inOff, inLen))); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RijndaelEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RijndaelEngine.java new file mode 100644 index 00000000..c80f6655 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/RijndaelEngine.java @@ -0,0 +1,725 @@ +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; + +/** + * an implementation of Rijndael, based on the documentation and reference implementation + * by Paulo Barreto, Vincent Rijmen, for v2.0 August '99. + * <p> + * Note: this implementation is based on information prior to final NIST publication. + */ +public class RijndaelEngine + implements BlockCipher +{ + private static final int MAXROUNDS = 14; + + private static final int MAXKC = (256/4); + + private static final byte[] logtable = { + (byte)0, (byte)0, (byte)25, (byte)1, (byte)50, (byte)2, (byte)26, (byte)198, + (byte)75, (byte)199, (byte)27, (byte)104, (byte)51, (byte)238, (byte)223, (byte)3, + (byte)100, (byte)4, (byte)224, (byte)14, (byte)52, (byte)141, (byte)129, (byte)239, + (byte)76, (byte)113, (byte)8, (byte)200, (byte)248, (byte)105, (byte)28, (byte)193, + (byte)125, (byte)194, (byte)29, (byte)181, (byte)249, (byte)185, (byte)39, (byte)106, + (byte)77, (byte)228, (byte)166, (byte)114, (byte)154, (byte)201, (byte)9, (byte)120, + (byte)101, (byte)47, (byte)138, (byte)5, (byte)33, (byte)15, (byte)225, (byte)36, + (byte)18, (byte)240, (byte)130, (byte)69, (byte)53, (byte)147, (byte)218, (byte)142, + (byte)150, (byte)143, (byte)219, (byte)189, (byte)54, (byte)208, (byte)206, (byte)148, + (byte)19, (byte)92, (byte)210, (byte)241, (byte)64, (byte)70, (byte)131, (byte)56, + (byte)102, (byte)221, (byte)253, (byte)48, (byte)191, (byte)6, (byte)139, (byte)98, + (byte)179, (byte)37, (byte)226, (byte)152, (byte)34, (byte)136, (byte)145, (byte)16, + (byte)126, (byte)110, (byte)72, (byte)195, (byte)163, (byte)182, (byte)30, (byte)66, + (byte)58, (byte)107, (byte)40, (byte)84, (byte)250, (byte)133, (byte)61, (byte)186, + (byte)43, (byte)121, (byte)10, (byte)21, (byte)155, (byte)159, (byte)94, (byte)202, + (byte)78, (byte)212, (byte)172, (byte)229, (byte)243, (byte)115, (byte)167, (byte)87, + (byte)175, (byte)88, (byte)168, (byte)80, (byte)244, (byte)234, (byte)214, (byte)116, + (byte)79, (byte)174, (byte)233, (byte)213, (byte)231, (byte)230, (byte)173, (byte)232, + (byte)44, (byte)215, (byte)117, (byte)122, (byte)235, (byte)22, (byte)11, (byte)245, + (byte)89, (byte)203, (byte)95, (byte)176, (byte)156, (byte)169, (byte)81, (byte)160, + (byte)127, (byte)12, (byte)246, (byte)111, (byte)23, (byte)196, (byte)73, (byte)236, + (byte)216, (byte)67, (byte)31, (byte)45, (byte)164, (byte)118, (byte)123, (byte)183, + (byte)204, (byte)187, (byte)62, (byte)90, (byte)251, (byte)96, (byte)177, (byte)134, + (byte)59, (byte)82, (byte)161, (byte)108, (byte)170, (byte)85, (byte)41, (byte)157, + (byte)151, (byte)178, (byte)135, (byte)144, (byte)97, (byte)190, (byte)220, (byte)252, + (byte)188, (byte)149, (byte)207, (byte)205, (byte)55, (byte)63, (byte)91, (byte)209, + (byte)83, (byte)57, (byte)132, (byte)60, (byte)65, (byte)162, (byte)109, (byte)71, + (byte)20, (byte)42, (byte)158, (byte)93, (byte)86, (byte)242, (byte)211, (byte)171, + (byte)68, (byte)17, (byte)146, (byte)217, (byte)35, (byte)32, (byte)46, (byte)137, + (byte)180, (byte)124, (byte)184, (byte)38, (byte)119, (byte)153, (byte)227, (byte)165, + (byte)103, (byte)74, (byte)237, (byte)222, (byte)197, (byte)49, (byte)254, (byte)24, + (byte)13, (byte)99, (byte)140, (byte)128, (byte)192, (byte)247, (byte)112, (byte)7 + }; + + private static final byte[] aLogtable = { + (byte)0, (byte)3, (byte)5, (byte)15, (byte)17, (byte)51, (byte)85, (byte)255, (byte)26, (byte)46, (byte)114, (byte)150, (byte)161, (byte)248, (byte)19, (byte)53, + (byte)95, (byte)225, (byte)56, (byte)72, (byte)216, (byte)115, (byte)149, (byte)164, (byte)247, (byte)2, (byte)6, (byte)10, (byte)30, (byte)34, (byte)102, (byte)170, + (byte)229, (byte)52, (byte)92, (byte)228, (byte)55, (byte)89, (byte)235, (byte)38, (byte)106, (byte)190, (byte)217, (byte)112, (byte)144, (byte)171, (byte)230, (byte)49, + (byte)83, (byte)245, (byte)4, (byte)12, (byte)20, (byte)60, (byte)68, (byte)204, (byte)79, (byte)209, (byte)104, (byte)184, (byte)211, (byte)110, (byte)178, (byte)205, + (byte)76, (byte)212, (byte)103, (byte)169, (byte)224, (byte)59, (byte)77, (byte)215, (byte)98, (byte)166, (byte)241, (byte)8, (byte)24, (byte)40, (byte)120, (byte)136, + (byte)131, (byte)158, (byte)185, (byte)208, (byte)107, (byte)189, (byte)220, (byte)127, (byte)129, (byte)152, (byte)179, (byte)206, (byte)73, (byte)219, (byte)118, (byte)154, + (byte)181, (byte)196, (byte)87, (byte)249, (byte)16, (byte)48, (byte)80, (byte)240, (byte)11, (byte)29, (byte)39, (byte)105, (byte)187, (byte)214, (byte)97, (byte)163, + (byte)254, (byte)25, (byte)43, (byte)125, (byte)135, (byte)146, (byte)173, (byte)236, (byte)47, (byte)113, (byte)147, (byte)174, (byte)233, (byte)32, (byte)96, (byte)160, + (byte)251, (byte)22, (byte)58, (byte)78, (byte)210, (byte)109, (byte)183, (byte)194, (byte)93, (byte)231, (byte)50, (byte)86, (byte)250, (byte)21, (byte)63, (byte)65, + (byte)195, (byte)94, (byte)226, (byte)61, (byte)71, (byte)201, (byte)64, (byte)192, (byte)91, (byte)237, (byte)44, (byte)116, (byte)156, (byte)191, (byte)218, (byte)117, + (byte)159, (byte)186, (byte)213, (byte)100, (byte)172, (byte)239, (byte)42, (byte)126, (byte)130, (byte)157, (byte)188, (byte)223, (byte)122, (byte)142, (byte)137, (byte)128, + (byte)155, (byte)182, (byte)193, (byte)88, (byte)232, (byte)35, (byte)101, (byte)175, (byte)234, (byte)37, (byte)111, (byte)177, (byte)200, (byte)67, (byte)197, (byte)84, + (byte)252, (byte)31, (byte)33, (byte)99, (byte)165, (byte)244, (byte)7, (byte)9, (byte)27, (byte)45, (byte)119, (byte)153, (byte)176, (byte)203, (byte)70, (byte)202, + (byte)69, (byte)207, (byte)74, (byte)222, (byte)121, (byte)139, (byte)134, (byte)145, (byte)168, (byte)227, (byte)62, (byte)66, (byte)198, (byte)81, (byte)243, (byte)14, + (byte)18, (byte)54, (byte)90, (byte)238, (byte)41, (byte)123, (byte)141, (byte)140, (byte)143, (byte)138, (byte)133, (byte)148, (byte)167, (byte)242, (byte)13, (byte)23, + (byte)57, (byte)75, (byte)221, (byte)124, (byte)132, (byte)151, (byte)162, (byte)253, (byte)28, (byte)36, (byte)108, (byte)180, (byte)199, (byte)82, (byte)246, (byte)1, + (byte)3, (byte)5, (byte)15, (byte)17, (byte)51, (byte)85, (byte)255, (byte)26, (byte)46, (byte)114, (byte)150, (byte)161, (byte)248, (byte)19, (byte)53, + (byte)95, (byte)225, (byte)56, (byte)72, (byte)216, (byte)115, (byte)149, (byte)164, (byte)247, (byte)2, (byte)6, (byte)10, (byte)30, (byte)34, (byte)102, (byte)170, + (byte)229, (byte)52, (byte)92, (byte)228, (byte)55, (byte)89, (byte)235, (byte)38, (byte)106, (byte)190, (byte)217, (byte)112, (byte)144, (byte)171, (byte)230, (byte)49, + (byte)83, (byte)245, (byte)4, (byte)12, (byte)20, (byte)60, (byte)68, (byte)204, (byte)79, (byte)209, (byte)104, (byte)184, (byte)211, (byte)110, (byte)178, (byte)205, + (byte)76, (byte)212, (byte)103, (byte)169, (byte)224, (byte)59, (byte)77, (byte)215, (byte)98, (byte)166, (byte)241, (byte)8, (byte)24, (byte)40, (byte)120, (byte)136, + (byte)131, (byte)158, (byte)185, (byte)208, (byte)107, (byte)189, (byte)220, (byte)127, (byte)129, (byte)152, (byte)179, (byte)206, (byte)73, (byte)219, (byte)118, (byte)154, + (byte)181, (byte)196, (byte)87, (byte)249, (byte)16, (byte)48, (byte)80, (byte)240, (byte)11, (byte)29, (byte)39, (byte)105, (byte)187, (byte)214, (byte)97, (byte)163, + (byte)254, (byte)25, (byte)43, (byte)125, (byte)135, (byte)146, (byte)173, (byte)236, (byte)47, (byte)113, (byte)147, (byte)174, (byte)233, (byte)32, (byte)96, (byte)160, + (byte)251, (byte)22, (byte)58, (byte)78, (byte)210, (byte)109, (byte)183, (byte)194, (byte)93, (byte)231, (byte)50, (byte)86, (byte)250, (byte)21, (byte)63, (byte)65, + (byte)195, (byte)94, (byte)226, (byte)61, (byte)71, (byte)201, (byte)64, (byte)192, (byte)91, (byte)237, (byte)44, (byte)116, (byte)156, (byte)191, (byte)218, (byte)117, + (byte)159, (byte)186, (byte)213, (byte)100, (byte)172, (byte)239, (byte)42, (byte)126, (byte)130, (byte)157, (byte)188, (byte)223, (byte)122, (byte)142, (byte)137, (byte)128, + (byte)155, (byte)182, (byte)193, (byte)88, (byte)232, (byte)35, (byte)101, (byte)175, (byte)234, (byte)37, (byte)111, (byte)177, (byte)200, (byte)67, (byte)197, (byte)84, + (byte)252, (byte)31, (byte)33, (byte)99, (byte)165, (byte)244, (byte)7, (byte)9, (byte)27, (byte)45, (byte)119, (byte)153, (byte)176, (byte)203, (byte)70, (byte)202, + (byte)69, (byte)207, (byte)74, (byte)222, (byte)121, (byte)139, (byte)134, (byte)145, (byte)168, (byte)227, (byte)62, (byte)66, (byte)198, (byte)81, (byte)243, (byte)14, + (byte)18, (byte)54, (byte)90, (byte)238, (byte)41, (byte)123, (byte)141, (byte)140, (byte)143, (byte)138, (byte)133, (byte)148, (byte)167, (byte)242, (byte)13, (byte)23, + (byte)57, (byte)75, (byte)221, (byte)124, (byte)132, (byte)151, (byte)162, (byte)253, (byte)28, (byte)36, (byte)108, (byte)180, (byte)199, (byte)82, (byte)246, (byte)1, + }; + + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + static byte[][] shifts0 = + { + { 0, 8, 16, 24 }, + { 0, 8, 16, 24 }, + { 0, 8, 16, 24 }, + { 0, 8, 16, 32 }, + { 0, 8, 24, 32 } + }; + + static byte[][] shifts1 = + { + { 0, 24, 16, 8 }, + { 0, 32, 24, 16 }, + { 0, 40, 32, 24 }, + { 0, 48, 40, 24 }, + { 0, 56, 40, 32 } + }; + + /** + * multiply two elements of GF(2^m) + * needed for MixColumn and InvMixColumn + */ + private byte mul0x2( + int b) + { + if (b != 0) + { + return aLogtable[25 + (logtable[b] & 0xff)]; + } + else + { + return 0; + } + } + + private byte mul0x3( + int b) + { + if (b != 0) + { + return aLogtable[1 + (logtable[b] & 0xff)]; + } + else + { + return 0; + } + } + + private byte mul0x9( + int b) + { + if (b >= 0) + { + return aLogtable[199 + b]; + } + else + { + return 0; + } + } + + private byte mul0xb( + int b) + { + if (b >= 0) + { + return aLogtable[104 + b]; + } + else + { + return 0; + } + } + + private byte mul0xd( + int b) + { + if (b >= 0) + { + return aLogtable[238 + b]; + } + else + { + return 0; + } + } + + private byte mul0xe( + int b) + { + if (b >= 0) + { + return aLogtable[223 + b]; + } + else + { + return 0; + } + } + + /** + * xor corresponding text input and round key input bytes + */ + private void KeyAddition( + long[] rk) + { + A0 ^= rk[0]; + A1 ^= rk[1]; + A2 ^= rk[2]; + A3 ^= rk[3]; + } + + private long shift( + long r, + int shift) + { + return (((r >>> shift) | (r << (BC - shift)))) & BC_MASK; + } + + /** + * Row 0 remains unchanged + * The other three rows are shifted a variable amount + */ + private void ShiftRow( + byte[] shiftsSC) + { + A1 = shift(A1, shiftsSC[1]); + A2 = shift(A2, shiftsSC[2]); + A3 = shift(A3, shiftsSC[3]); + } + + private long applyS( + long r, + byte[] box) + { + long res = 0; + + for (int j = 0; j < BC; j += 8) + { + res |= (long)(box[(int)((r >> j) & 0xff)] & 0xff) << j; + } + + return res; + } + + /** + * Replace every byte of the input by the byte at that place + * in the nonlinear S-box + */ + private void Substitution( + byte[] box) + { + A0 = applyS(A0, box); + A1 = applyS(A1, box); + A2 = applyS(A2, box); + A3 = applyS(A3, box); + } + + /** + * Mix the bytes of every column in a linear way + */ + private void MixColumn() + { + long r0, r1, r2, r3; + + r0 = r1 = r2 = r3 = 0; + + for (int j = 0; j < BC; j += 8) + { + int a0 = (int)((A0 >> j) & 0xff); + int a1 = (int)((A1 >> j) & 0xff); + int a2 = (int)((A2 >> j) & 0xff); + int a3 = (int)((A3 >> j) & 0xff); + + r0 |= (long)((mul0x2(a0) ^ mul0x3(a1) ^ a2 ^ a3) & 0xff) << j; + + r1 |= (long)((mul0x2(a1) ^ mul0x3(a2) ^ a3 ^ a0) & 0xff) << j; + + r2 |= (long)((mul0x2(a2) ^ mul0x3(a3) ^ a0 ^ a1) & 0xff) << j; + + r3 |= (long)((mul0x2(a3) ^ mul0x3(a0) ^ a1 ^ a2) & 0xff) << j; + } + + A0 = r0; + A1 = r1; + A2 = r2; + A3 = r3; + } + + /** + * Mix the bytes of every column in a linear way + * This is the opposite operation of Mixcolumn + */ + private void InvMixColumn() + { + long r0, r1, r2, r3; + + r0 = r1 = r2 = r3 = 0; + for (int j = 0; j < BC; j += 8) + { + int a0 = (int)((A0 >> j) & 0xff); + int a1 = (int)((A1 >> j) & 0xff); + int a2 = (int)((A2 >> j) & 0xff); + int a3 = (int)((A3 >> j) & 0xff); + + // + // pre-lookup the log table + // + a0 = (a0 != 0) ? (logtable[a0 & 0xff] & 0xff) : -1; + a1 = (a1 != 0) ? (logtable[a1 & 0xff] & 0xff) : -1; + a2 = (a2 != 0) ? (logtable[a2 & 0xff] & 0xff) : -1; + a3 = (a3 != 0) ? (logtable[a3 & 0xff] & 0xff) : -1; + + r0 |= (long)((mul0xe(a0) ^ mul0xb(a1) ^ mul0xd(a2) ^ mul0x9(a3)) & 0xff) << j; + + r1 |= (long)((mul0xe(a1) ^ mul0xb(a2) ^ mul0xd(a3) ^ mul0x9(a0)) & 0xff) << j; + + r2 |= (long)((mul0xe(a2) ^ mul0xb(a3) ^ mul0xd(a0) ^ mul0x9(a1)) & 0xff) << j; + + r3 |= (long)((mul0xe(a3) ^ mul0xb(a0) ^ mul0xd(a1) ^ mul0x9(a2)) & 0xff) << j; + } + + A0 = r0; + A1 = r1; + A2 = r2; + A3 = r3; + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on keyBits and blockBits + */ + private long[][] generateWorkingKey( + byte[] key) + { + int KC; + int t, rconpointer = 0; + int keyBits = key.length * 8; + byte[][] tk = new byte[4][MAXKC]; + long[][] W = new long[MAXROUNDS+1][4]; + + switch (keyBits) + { + case 128: + KC = 4; + break; + case 160: + KC = 5; + break; + case 192: + KC = 6; + break; + case 224: + KC = 7; + break; + case 256: + KC = 8; + break; + default : + throw new IllegalArgumentException("Key length not 128/160/192/224/256 bits."); + } + + if (keyBits >= blockBits) + { + ROUNDS = KC + 6; + } + else + { + ROUNDS = (BC / 8) + 6; + } + + // + // copy the key into the processing area + // + int index = 0; + + for (int i = 0; i < key.length; i++) + { + tk[i % 4][i / 4] = key[index++]; + } + + t = 0; + + // + // copy values into round key array + // + for (int j = 0; (j < KC) && (t < (ROUNDS+1)*(BC / 8)); j++, t++) + { + for (int i = 0; i < 4; i++) + { + W[t / (BC / 8)][i] |= (long)(tk[i][j] & 0xff) << ((t * 8) % BC); + } + } + + // + // while not enough round key material calculated + // calculate new values + // + while (t < (ROUNDS+1)*(BC/8)) + { + for (int i = 0; i < 4; i++) + { + tk[i][0] ^= S[tk[(i+1)%4][KC-1] & 0xff]; + } + tk[0][0] ^= rcon[rconpointer++]; + + if (KC <= 6) + { + for (int j = 1; j < KC; j++) + { + for (int i = 0; i < 4; i++) + { + tk[i][j] ^= tk[i][j-1]; + } + } + } + else + { + for (int j = 1; j < 4; j++) + { + for (int i = 0; i < 4; i++) + { + tk[i][j] ^= tk[i][j-1]; + } + } + for (int i = 0; i < 4; i++) + { + tk[i][4] ^= S[tk[i][3] & 0xff]; + } + for (int j = 5; j < KC; j++) + { + for (int i = 0; i < 4; i++) + { + tk[i][j] ^= tk[i][j-1]; + } + } + } + + // + // copy values into round key array + // + for (int j = 0; (j < KC) && (t < (ROUNDS+1)*(BC/8)); j++, t++) + { + for (int i = 0; i < 4; i++) + { + W[t / (BC/8)][i] |= (long)(tk[i][j] & 0xff) << ((t * 8) % (BC)); + } + } + } + + return W; + } + + private int BC; + private long BC_MASK; + private int ROUNDS; + private int blockBits; + private long[][] workingKey; + private long A0, A1, A2, A3; + private boolean forEncryption; + private byte[] shifts0SC; + private byte[] shifts1SC; + + /** + * default constructor - 128 bit block size. + */ + public RijndaelEngine() + { + this(128); + } + + /** + * basic constructor - set the cipher up for a given blocksize + * + * @param blockBits the blocksize in bits, must be 128, 192, or 256. + */ + public RijndaelEngine( + int blockBits) + { + switch (blockBits) + { + case 128: + BC = 32; + BC_MASK = 0xffffffffL; + shifts0SC = shifts0[0]; + shifts1SC = shifts1[0]; + break; + case 160: + BC = 40; + BC_MASK = 0xffffffffffL; + shifts0SC = shifts0[1]; + shifts1SC = shifts1[1]; + break; + case 192: + BC = 48; + BC_MASK = 0xffffffffffffL; + shifts0SC = shifts0[2]; + shifts1SC = shifts1[2]; + break; + case 224: + BC = 56; + BC_MASK = 0xffffffffffffffL; + shifts0SC = shifts0[3]; + shifts1SC = shifts1[3]; + break; + case 256: + BC = 64; + BC_MASK = 0xffffffffffffffffL; + shifts0SC = shifts0[4]; + shifts1SC = shifts1[4]; + break; + default: + throw new IllegalArgumentException("unknown blocksize to Rijndael"); + } + + this.blockBits = blockBits; + } + + /** + * initialise a Rijndael cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(((KeyParameter)params).getKey()); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Rijndael init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Rijndael"; + } + + public int getBlockSize() + { + return BC / 2; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Rijndael engine not initialised"); + } + + if ((inOff + (BC / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (BC / 2)) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(workingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(workingKey); + packBlock(out, outOff); + } + + return BC / 2; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + A0 = (bytes[index++] & 0xff); + A1 = (bytes[index++] & 0xff); + A2 = (bytes[index++] & 0xff); + A3 = (bytes[index++] & 0xff); + + for (int j = 8; j != BC; j += 8) + { + A0 |= (long)(bytes[index++] & 0xff) << j; + A1 |= (long)(bytes[index++] & 0xff) << j; + A2 |= (long)(bytes[index++] & 0xff) << j; + A3 |= (long)(bytes[index++] & 0xff) << j; + } + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + for (int j = 0; j != BC; j += 8) + { + bytes[index++] = (byte)(A0 >> j); + bytes[index++] = (byte)(A1 >> j); + bytes[index++] = (byte)(A2 >> j); + bytes[index++] = (byte)(A3 >> j); + } + } + + private void encryptBlock( + long[][] rk) + { + int r; + + // + // begin with a key addition + // + KeyAddition(rk[0]); + + // + // ROUNDS-1 ordinary rounds + // + for (r = 1; r < ROUNDS; r++) + { + Substitution(S); + ShiftRow(shifts0SC); + MixColumn(); + KeyAddition(rk[r]); + } + + // + // Last round is special: there is no MixColumn + // + Substitution(S); + ShiftRow(shifts0SC); + KeyAddition(rk[ROUNDS]); + } + + private void decryptBlock( + long[][] rk) + { + int r; + + // To decrypt: apply the inverse operations of the encrypt routine, + // in opposite order + // + // (KeyAddition is an involution: it 's equal to its inverse) + // (the inverse of Substitution with table S is Substitution with the inverse table of S) + // (the inverse of Shiftrow is Shiftrow over a suitable distance) + // + + // First the special round: + // without InvMixColumn + // with extra KeyAddition + // + KeyAddition(rk[ROUNDS]); + Substitution(Si); + ShiftRow(shifts1SC); + + // + // ROUNDS-1 ordinary rounds + // + for (r = ROUNDS-1; r > 0; r--) + { + KeyAddition(rk[r]); + InvMixColumn(); + Substitution(Si); + ShiftRow(shifts1SC); + } + + // + // End with the extra key addition + // + KeyAddition(rk[0]); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/SEEDEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/SEEDEngine.java new file mode 100644 index 00000000..43872ed3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/SEEDEngine.java @@ -0,0 +1,346 @@ +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; + +/** + * Implementation of the SEED algorithm as described in RFC 4009 + */ +public class SEEDEngine + implements BlockCipher +{ + private final int BLOCK_SIZE = 16; + + private static final int[] SS0 = + { + 0x2989a1a8, 0x05858184, 0x16c6d2d4, 0x13c3d3d0, 0x14445054, 0x1d0d111c, 0x2c8ca0ac, 0x25052124, + 0x1d4d515c, 0x03434340, 0x18081018, 0x1e0e121c, 0x11415150, 0x3cccf0fc, 0x0acac2c8, 0x23436360, + 0x28082028, 0x04444044, 0x20002020, 0x1d8d919c, 0x20c0e0e0, 0x22c2e2e0, 0x08c8c0c8, 0x17071314, + 0x2585a1a4, 0x0f8f838c, 0x03030300, 0x3b4b7378, 0x3b8bb3b8, 0x13031310, 0x12c2d2d0, 0x2ecee2ec, + 0x30407070, 0x0c8c808c, 0x3f0f333c, 0x2888a0a8, 0x32023230, 0x1dcdd1dc, 0x36c6f2f4, 0x34447074, + 0x2ccce0ec, 0x15859194, 0x0b0b0308, 0x17475354, 0x1c4c505c, 0x1b4b5358, 0x3d8db1bc, 0x01010100, + 0x24042024, 0x1c0c101c, 0x33437370, 0x18889098, 0x10001010, 0x0cccc0cc, 0x32c2f2f0, 0x19c9d1d8, + 0x2c0c202c, 0x27c7e3e4, 0x32427270, 0x03838380, 0x1b8b9398, 0x11c1d1d0, 0x06868284, 0x09c9c1c8, + 0x20406060, 0x10405050, 0x2383a3a0, 0x2bcbe3e8, 0x0d0d010c, 0x3686b2b4, 0x1e8e929c, 0x0f4f434c, + 0x3787b3b4, 0x1a4a5258, 0x06c6c2c4, 0x38487078, 0x2686a2a4, 0x12021210, 0x2f8fa3ac, 0x15c5d1d4, + 0x21416160, 0x03c3c3c0, 0x3484b0b4, 0x01414140, 0x12425250, 0x3d4d717c, 0x0d8d818c, 0x08080008, + 0x1f0f131c, 0x19899198, 0x00000000, 0x19091118, 0x04040004, 0x13435350, 0x37c7f3f4, 0x21c1e1e0, + 0x3dcdf1fc, 0x36467274, 0x2f0f232c, 0x27072324, 0x3080b0b0, 0x0b8b8388, 0x0e0e020c, 0x2b8ba3a8, + 0x2282a2a0, 0x2e4e626c, 0x13839390, 0x0d4d414c, 0x29496168, 0x3c4c707c, 0x09090108, 0x0a0a0208, + 0x3f8fb3bc, 0x2fcfe3ec, 0x33c3f3f0, 0x05c5c1c4, 0x07878384, 0x14041014, 0x3ecef2fc, 0x24446064, + 0x1eced2dc, 0x2e0e222c, 0x0b4b4348, 0x1a0a1218, 0x06060204, 0x21012120, 0x2b4b6368, 0x26466264, + 0x02020200, 0x35c5f1f4, 0x12829290, 0x0a8a8288, 0x0c0c000c, 0x3383b3b0, 0x3e4e727c, 0x10c0d0d0, + 0x3a4a7278, 0x07474344, 0x16869294, 0x25c5e1e4, 0x26062224, 0x00808080, 0x2d8da1ac, 0x1fcfd3dc, + 0x2181a1a0, 0x30003030, 0x37073334, 0x2e8ea2ac, 0x36063234, 0x15051114, 0x22022220, 0x38083038, + 0x34c4f0f4, 0x2787a3a4, 0x05454144, 0x0c4c404c, 0x01818180, 0x29c9e1e8, 0x04848084, 0x17879394, + 0x35053134, 0x0bcbc3c8, 0x0ecec2cc, 0x3c0c303c, 0x31417170, 0x11011110, 0x07c7c3c4, 0x09898188, + 0x35457174, 0x3bcbf3f8, 0x1acad2d8, 0x38c8f0f8, 0x14849094, 0x19495158, 0x02828280, 0x04c4c0c4, + 0x3fcff3fc, 0x09494148, 0x39093138, 0x27476364, 0x00c0c0c0, 0x0fcfc3cc, 0x17c7d3d4, 0x3888b0b8, + 0x0f0f030c, 0x0e8e828c, 0x02424240, 0x23032320, 0x11819190, 0x2c4c606c, 0x1bcbd3d8, 0x2484a0a4, + 0x34043034, 0x31c1f1f0, 0x08484048, 0x02c2c2c0, 0x2f4f636c, 0x3d0d313c, 0x2d0d212c, 0x00404040, + 0x3e8eb2bc, 0x3e0e323c, 0x3c8cb0bc, 0x01c1c1c0, 0x2a8aa2a8, 0x3a8ab2b8, 0x0e4e424c, 0x15455154, + 0x3b0b3338, 0x1cccd0dc, 0x28486068, 0x3f4f737c, 0x1c8c909c, 0x18c8d0d8, 0x0a4a4248, 0x16465254, + 0x37477374, 0x2080a0a0, 0x2dcde1ec, 0x06464244, 0x3585b1b4, 0x2b0b2328, 0x25456164, 0x3acaf2f8, + 0x23c3e3e0, 0x3989b1b8, 0x3181b1b0, 0x1f8f939c, 0x1e4e525c, 0x39c9f1f8, 0x26c6e2e4, 0x3282b2b0, + 0x31013130, 0x2acae2e8, 0x2d4d616c, 0x1f4f535c, 0x24c4e0e4, 0x30c0f0f0, 0x0dcdc1cc, 0x08888088, + 0x16061214, 0x3a0a3238, 0x18485058, 0x14c4d0d4, 0x22426260, 0x29092128, 0x07070304, 0x33033330, + 0x28c8e0e8, 0x1b0b1318, 0x05050104, 0x39497178, 0x10809090, 0x2a4a6268, 0x2a0a2228, 0x1a8a9298 + }; + + private static final int[] SS1 = + { + + 0x38380830, 0xe828c8e0, 0x2c2d0d21, 0xa42686a2, 0xcc0fcfc3, 0xdc1eced2, 0xb03383b3, 0xb83888b0, + 0xac2f8fa3, 0x60204060, 0x54154551, 0xc407c7c3, 0x44044440, 0x6c2f4f63, 0x682b4b63, 0x581b4b53, + 0xc003c3c3, 0x60224262, 0x30330333, 0xb43585b1, 0x28290921, 0xa02080a0, 0xe022c2e2, 0xa42787a3, + 0xd013c3d3, 0x90118191, 0x10110111, 0x04060602, 0x1c1c0c10, 0xbc3c8cb0, 0x34360632, 0x480b4b43, + 0xec2fcfe3, 0x88088880, 0x6c2c4c60, 0xa82888a0, 0x14170713, 0xc404c4c0, 0x14160612, 0xf434c4f0, + 0xc002c2c2, 0x44054541, 0xe021c1e1, 0xd416c6d2, 0x3c3f0f33, 0x3c3d0d31, 0x8c0e8e82, 0x98188890, + 0x28280820, 0x4c0e4e42, 0xf436c6f2, 0x3c3e0e32, 0xa42585a1, 0xf839c9f1, 0x0c0d0d01, 0xdc1fcfd3, + 0xd818c8d0, 0x282b0b23, 0x64264662, 0x783a4a72, 0x24270723, 0x2c2f0f23, 0xf031c1f1, 0x70324272, + 0x40024242, 0xd414c4d0, 0x40014141, 0xc000c0c0, 0x70334373, 0x64274763, 0xac2c8ca0, 0x880b8b83, + 0xf437c7f3, 0xac2d8da1, 0x80008080, 0x1c1f0f13, 0xc80acac2, 0x2c2c0c20, 0xa82a8aa2, 0x34340430, + 0xd012c2d2, 0x080b0b03, 0xec2ecee2, 0xe829c9e1, 0x5c1d4d51, 0x94148490, 0x18180810, 0xf838c8f0, + 0x54174753, 0xac2e8ea2, 0x08080800, 0xc405c5c1, 0x10130313, 0xcc0dcdc1, 0x84068682, 0xb83989b1, + 0xfc3fcff3, 0x7c3d4d71, 0xc001c1c1, 0x30310131, 0xf435c5f1, 0x880a8a82, 0x682a4a62, 0xb03181b1, + 0xd011c1d1, 0x20200020, 0xd417c7d3, 0x00020202, 0x20220222, 0x04040400, 0x68284860, 0x70314171, + 0x04070703, 0xd81bcbd3, 0x9c1d8d91, 0x98198991, 0x60214161, 0xbc3e8eb2, 0xe426c6e2, 0x58194951, + 0xdc1dcdd1, 0x50114151, 0x90108090, 0xdc1cccd0, 0x981a8a92, 0xa02383a3, 0xa82b8ba3, 0xd010c0d0, + 0x80018181, 0x0c0f0f03, 0x44074743, 0x181a0a12, 0xe023c3e3, 0xec2ccce0, 0x8c0d8d81, 0xbc3f8fb3, + 0x94168692, 0x783b4b73, 0x5c1c4c50, 0xa02282a2, 0xa02181a1, 0x60234363, 0x20230323, 0x4c0d4d41, + 0xc808c8c0, 0x9c1e8e92, 0x9c1c8c90, 0x383a0a32, 0x0c0c0c00, 0x2c2e0e22, 0xb83a8ab2, 0x6c2e4e62, + 0x9c1f8f93, 0x581a4a52, 0xf032c2f2, 0x90128292, 0xf033c3f3, 0x48094941, 0x78384870, 0xcc0cccc0, + 0x14150511, 0xf83bcbf3, 0x70304070, 0x74354571, 0x7c3f4f73, 0x34350531, 0x10100010, 0x00030303, + 0x64244460, 0x6c2d4d61, 0xc406c6c2, 0x74344470, 0xd415c5d1, 0xb43484b0, 0xe82acae2, 0x08090901, + 0x74364672, 0x18190911, 0xfc3ecef2, 0x40004040, 0x10120212, 0xe020c0e0, 0xbc3d8db1, 0x04050501, + 0xf83acaf2, 0x00010101, 0xf030c0f0, 0x282a0a22, 0x5c1e4e52, 0xa82989a1, 0x54164652, 0x40034343, + 0x84058581, 0x14140410, 0x88098981, 0x981b8b93, 0xb03080b0, 0xe425c5e1, 0x48084840, 0x78394971, + 0x94178793, 0xfc3cccf0, 0x1c1e0e12, 0x80028282, 0x20210121, 0x8c0c8c80, 0x181b0b13, 0x5c1f4f53, + 0x74374773, 0x54144450, 0xb03282b2, 0x1c1d0d11, 0x24250521, 0x4c0f4f43, 0x00000000, 0x44064642, + 0xec2dcde1, 0x58184850, 0x50124252, 0xe82bcbe3, 0x7c3e4e72, 0xd81acad2, 0xc809c9c1, 0xfc3dcdf1, + 0x30300030, 0x94158591, 0x64254561, 0x3c3c0c30, 0xb43686b2, 0xe424c4e0, 0xb83b8bb3, 0x7c3c4c70, + 0x0c0e0e02, 0x50104050, 0x38390931, 0x24260622, 0x30320232, 0x84048480, 0x68294961, 0x90138393, + 0x34370733, 0xe427c7e3, 0x24240420, 0xa42484a0, 0xc80bcbc3, 0x50134353, 0x080a0a02, 0x84078783, + 0xd819c9d1, 0x4c0c4c40, 0x80038383, 0x8c0f8f83, 0xcc0ecec2, 0x383b0b33, 0x480a4a42, 0xb43787b3 + }; + + private static final int[] SS2 = + { + + 0xa1a82989, 0x81840585, 0xd2d416c6, 0xd3d013c3, 0x50541444, 0x111c1d0d, 0xa0ac2c8c, 0x21242505, + 0x515c1d4d, 0x43400343, 0x10181808, 0x121c1e0e, 0x51501141, 0xf0fc3ccc, 0xc2c80aca, 0x63602343, + 0x20282808, 0x40440444, 0x20202000, 0x919c1d8d, 0xe0e020c0, 0xe2e022c2, 0xc0c808c8, 0x13141707, + 0xa1a42585, 0x838c0f8f, 0x03000303, 0x73783b4b, 0xb3b83b8b, 0x13101303, 0xd2d012c2, 0xe2ec2ece, + 0x70703040, 0x808c0c8c, 0x333c3f0f, 0xa0a82888, 0x32303202, 0xd1dc1dcd, 0xf2f436c6, 0x70743444, + 0xe0ec2ccc, 0x91941585, 0x03080b0b, 0x53541747, 0x505c1c4c, 0x53581b4b, 0xb1bc3d8d, 0x01000101, + 0x20242404, 0x101c1c0c, 0x73703343, 0x90981888, 0x10101000, 0xc0cc0ccc, 0xf2f032c2, 0xd1d819c9, + 0x202c2c0c, 0xe3e427c7, 0x72703242, 0x83800383, 0x93981b8b, 0xd1d011c1, 0x82840686, 0xc1c809c9, + 0x60602040, 0x50501040, 0xa3a02383, 0xe3e82bcb, 0x010c0d0d, 0xb2b43686, 0x929c1e8e, 0x434c0f4f, + 0xb3b43787, 0x52581a4a, 0xc2c406c6, 0x70783848, 0xa2a42686, 0x12101202, 0xa3ac2f8f, 0xd1d415c5, + 0x61602141, 0xc3c003c3, 0xb0b43484, 0x41400141, 0x52501242, 0x717c3d4d, 0x818c0d8d, 0x00080808, + 0x131c1f0f, 0x91981989, 0x00000000, 0x11181909, 0x00040404, 0x53501343, 0xf3f437c7, 0xe1e021c1, + 0xf1fc3dcd, 0x72743646, 0x232c2f0f, 0x23242707, 0xb0b03080, 0x83880b8b, 0x020c0e0e, 0xa3a82b8b, + 0xa2a02282, 0x626c2e4e, 0x93901383, 0x414c0d4d, 0x61682949, 0x707c3c4c, 0x01080909, 0x02080a0a, + 0xb3bc3f8f, 0xe3ec2fcf, 0xf3f033c3, 0xc1c405c5, 0x83840787, 0x10141404, 0xf2fc3ece, 0x60642444, + 0xd2dc1ece, 0x222c2e0e, 0x43480b4b, 0x12181a0a, 0x02040606, 0x21202101, 0x63682b4b, 0x62642646, + 0x02000202, 0xf1f435c5, 0x92901282, 0x82880a8a, 0x000c0c0c, 0xb3b03383, 0x727c3e4e, 0xd0d010c0, + 0x72783a4a, 0x43440747, 0x92941686, 0xe1e425c5, 0x22242606, 0x80800080, 0xa1ac2d8d, 0xd3dc1fcf, + 0xa1a02181, 0x30303000, 0x33343707, 0xa2ac2e8e, 0x32343606, 0x11141505, 0x22202202, 0x30383808, + 0xf0f434c4, 0xa3a42787, 0x41440545, 0x404c0c4c, 0x81800181, 0xe1e829c9, 0x80840484, 0x93941787, + 0x31343505, 0xc3c80bcb, 0xc2cc0ece, 0x303c3c0c, 0x71703141, 0x11101101, 0xc3c407c7, 0x81880989, + 0x71743545, 0xf3f83bcb, 0xd2d81aca, 0xf0f838c8, 0x90941484, 0x51581949, 0x82800282, 0xc0c404c4, + 0xf3fc3fcf, 0x41480949, 0x31383909, 0x63642747, 0xc0c000c0, 0xc3cc0fcf, 0xd3d417c7, 0xb0b83888, + 0x030c0f0f, 0x828c0e8e, 0x42400242, 0x23202303, 0x91901181, 0x606c2c4c, 0xd3d81bcb, 0xa0a42484, + 0x30343404, 0xf1f031c1, 0x40480848, 0xc2c002c2, 0x636c2f4f, 0x313c3d0d, 0x212c2d0d, 0x40400040, + 0xb2bc3e8e, 0x323c3e0e, 0xb0bc3c8c, 0xc1c001c1, 0xa2a82a8a, 0xb2b83a8a, 0x424c0e4e, 0x51541545, + 0x33383b0b, 0xd0dc1ccc, 0x60682848, 0x737c3f4f, 0x909c1c8c, 0xd0d818c8, 0x42480a4a, 0x52541646, + 0x73743747, 0xa0a02080, 0xe1ec2dcd, 0x42440646, 0xb1b43585, 0x23282b0b, 0x61642545, 0xf2f83aca, + 0xe3e023c3, 0xb1b83989, 0xb1b03181, 0x939c1f8f, 0x525c1e4e, 0xf1f839c9, 0xe2e426c6, 0xb2b03282, + 0x31303101, 0xe2e82aca, 0x616c2d4d, 0x535c1f4f, 0xe0e424c4, 0xf0f030c0, 0xc1cc0dcd, 0x80880888, + 0x12141606, 0x32383a0a, 0x50581848, 0xd0d414c4, 0x62602242, 0x21282909, 0x03040707, 0x33303303, + 0xe0e828c8, 0x13181b0b, 0x01040505, 0x71783949, 0x90901080, 0x62682a4a, 0x22282a0a, 0x92981a8a + }; + + + private static final int[] SS3 = + { + + 0x08303838, 0xc8e0e828, 0x0d212c2d, 0x86a2a426, 0xcfc3cc0f, 0xced2dc1e, 0x83b3b033, 0x88b0b838, + 0x8fa3ac2f, 0x40606020, 0x45515415, 0xc7c3c407, 0x44404404, 0x4f636c2f, 0x4b63682b, 0x4b53581b, + 0xc3c3c003, 0x42626022, 0x03333033, 0x85b1b435, 0x09212829, 0x80a0a020, 0xc2e2e022, 0x87a3a427, + 0xc3d3d013, 0x81919011, 0x01111011, 0x06020406, 0x0c101c1c, 0x8cb0bc3c, 0x06323436, 0x4b43480b, + 0xcfe3ec2f, 0x88808808, 0x4c606c2c, 0x88a0a828, 0x07131417, 0xc4c0c404, 0x06121416, 0xc4f0f434, + 0xc2c2c002, 0x45414405, 0xc1e1e021, 0xc6d2d416, 0x0f333c3f, 0x0d313c3d, 0x8e828c0e, 0x88909818, + 0x08202828, 0x4e424c0e, 0xc6f2f436, 0x0e323c3e, 0x85a1a425, 0xc9f1f839, 0x0d010c0d, 0xcfd3dc1f, + 0xc8d0d818, 0x0b23282b, 0x46626426, 0x4a72783a, 0x07232427, 0x0f232c2f, 0xc1f1f031, 0x42727032, + 0x42424002, 0xc4d0d414, 0x41414001, 0xc0c0c000, 0x43737033, 0x47636427, 0x8ca0ac2c, 0x8b83880b, + 0xc7f3f437, 0x8da1ac2d, 0x80808000, 0x0f131c1f, 0xcac2c80a, 0x0c202c2c, 0x8aa2a82a, 0x04303434, + 0xc2d2d012, 0x0b03080b, 0xcee2ec2e, 0xc9e1e829, 0x4d515c1d, 0x84909414, 0x08101818, 0xc8f0f838, + 0x47535417, 0x8ea2ac2e, 0x08000808, 0xc5c1c405, 0x03131013, 0xcdc1cc0d, 0x86828406, 0x89b1b839, + 0xcff3fc3f, 0x4d717c3d, 0xc1c1c001, 0x01313031, 0xc5f1f435, 0x8a82880a, 0x4a62682a, 0x81b1b031, + 0xc1d1d011, 0x00202020, 0xc7d3d417, 0x02020002, 0x02222022, 0x04000404, 0x48606828, 0x41717031, + 0x07030407, 0xcbd3d81b, 0x8d919c1d, 0x89919819, 0x41616021, 0x8eb2bc3e, 0xc6e2e426, 0x49515819, + 0xcdd1dc1d, 0x41515011, 0x80909010, 0xccd0dc1c, 0x8a92981a, 0x83a3a023, 0x8ba3a82b, 0xc0d0d010, + 0x81818001, 0x0f030c0f, 0x47434407, 0x0a12181a, 0xc3e3e023, 0xcce0ec2c, 0x8d818c0d, 0x8fb3bc3f, + 0x86929416, 0x4b73783b, 0x4c505c1c, 0x82a2a022, 0x81a1a021, 0x43636023, 0x03232023, 0x4d414c0d, + 0xc8c0c808, 0x8e929c1e, 0x8c909c1c, 0x0a32383a, 0x0c000c0c, 0x0e222c2e, 0x8ab2b83a, 0x4e626c2e, + 0x8f939c1f, 0x4a52581a, 0xc2f2f032, 0x82929012, 0xc3f3f033, 0x49414809, 0x48707838, 0xccc0cc0c, + 0x05111415, 0xcbf3f83b, 0x40707030, 0x45717435, 0x4f737c3f, 0x05313435, 0x00101010, 0x03030003, + 0x44606424, 0x4d616c2d, 0xc6c2c406, 0x44707434, 0xc5d1d415, 0x84b0b434, 0xcae2e82a, 0x09010809, + 0x46727436, 0x09111819, 0xcef2fc3e, 0x40404000, 0x02121012, 0xc0e0e020, 0x8db1bc3d, 0x05010405, + 0xcaf2f83a, 0x01010001, 0xc0f0f030, 0x0a22282a, 0x4e525c1e, 0x89a1a829, 0x46525416, 0x43434003, + 0x85818405, 0x04101414, 0x89818809, 0x8b93981b, 0x80b0b030, 0xc5e1e425, 0x48404808, 0x49717839, + 0x87939417, 0xccf0fc3c, 0x0e121c1e, 0x82828002, 0x01212021, 0x8c808c0c, 0x0b13181b, 0x4f535c1f, + 0x47737437, 0x44505414, 0x82b2b032, 0x0d111c1d, 0x05212425, 0x4f434c0f, 0x00000000, 0x46424406, + 0xcde1ec2d, 0x48505818, 0x42525012, 0xcbe3e82b, 0x4e727c3e, 0xcad2d81a, 0xc9c1c809, 0xcdf1fc3d, + 0x00303030, 0x85919415, 0x45616425, 0x0c303c3c, 0x86b2b436, 0xc4e0e424, 0x8bb3b83b, 0x4c707c3c, + 0x0e020c0e, 0x40505010, 0x09313839, 0x06222426, 0x02323032, 0x84808404, 0x49616829, 0x83939013, + 0x07333437, 0xc7e3e427, 0x04202424, 0x84a0a424, 0xcbc3c80b, 0x43535013, 0x0a02080a, 0x87838407, + 0xc9d1d819, 0x4c404c0c, 0x83838003, 0x8f838c0f, 0xcec2cc0e, 0x0b33383b, 0x4a42480a, 0x87b3b437 + }; + + + private static final int[] KC = + { + 0x9e3779b9, 0x3c6ef373, 0x78dde6e6, 0xf1bbcdcc, + 0xe3779b99, 0xc6ef3733, 0x8dde6e67, 0x1bbcdccf, + 0x3779b99e, 0x6ef3733c, 0xdde6e678, 0xbbcdccf1, + 0x779b99e3, 0xef3733c6, 0xde6e678d, 0xbcdccf1b + }; + + private int[] wKey; + private boolean forEncryption; + + public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException + { + this.forEncryption = forEncryption; + wKey = createWorkingKey(((KeyParameter)params).getKey()); + } + + public String getAlgorithmName() + { + return "SEED"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException + { + if (wKey == null) + { + throw new IllegalStateException("SEED engine 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"); + } + + long l = bytesToLong(in, inOff + 0); + long r = bytesToLong(in, inOff + 8); + + if (forEncryption) + { + for (int i = 0; i < 16; i++) + { + long nl = r; + + r = l ^ F(wKey[2 * i], wKey[(2 * i) + 1], r); + l = nl; + } + } + else + { + for (int i = 15; i >= 0; i--) + { + long nl = r; + + r = l ^ F(wKey[2 * i], wKey[(2 * i) + 1], r); + l = nl; + } + } + + longToBytes(out, outOff + 0, r); + longToBytes(out, outOff + 8, l); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private int[] createWorkingKey(byte[] inKey) + { + int[] key = new int[32]; + long lower = bytesToLong(inKey, 0); + long upper = bytesToLong(inKey, 8); + + int key0 = extractW0(lower); + int key1 = extractW1(lower); + int key2 = extractW0(upper); + int key3 = extractW1(upper); + + for (int i = 0; i < 16; i++) + { + key[2 * i] = G(key0 + key2 - KC[i]); + key[2 * i + 1] = G(key1 - key3 + KC[i]); + + if (i % 2 == 0) + { + lower = rotateRight8(lower); + key0 = extractW0(lower); + key1 = extractW1(lower); + } + else + { + upper = rotateLeft8(upper); + key2 = extractW0(upper); + key3 = extractW1(upper); + } + } + + return key; + } + + private int extractW1(long lVal) + { + return (int)lVal; + } + + private int extractW0(long lVal) + { + return (int)(lVal >> 32); + } + + private long rotateLeft8(long x) + { + return (x << 8) | (x >>> 56); + } + + private long rotateRight8(long x) + { + return (x >>> 8) | (x << 56); + } + + private long bytesToLong( + byte[] src, + int srcOff) + { + long word = 0; + + for (int i = 0; i <= 7; i++) + { + word = (word << 8) + (src[i + srcOff] & 0xff); + } + + return word; + } + + private void longToBytes( + byte[] dest, + int destOff, + long value) + { + for (int i = 0; i < 8; i++) + { + dest[i + destOff] = (byte)(value >> ((7 - i) * 8)); + } + } + + private int G(int x) + { + return SS0[x & 0xff] ^ SS1[(x >> 8) & 0xff] ^ SS2[(x >> 16) & 0xff] ^ SS3[(x >> 24) & 0xff]; + } + + private long F(int ki0, int ki1, long r) + { + int r0 = (int)(r >> 32); + int r1 = (int)r; + int rd1 = phaseCalc2(r0, ki0, r1, ki1); + int rd0 = rd1 + phaseCalc1(r0, ki0, r1, ki1); + + return ((long)rd0 << 32) | (rd1 & 0xffffffffL); + } + + private int phaseCalc1(int r0, int ki0, int r1, int ki1) + { + return G(G((r0 ^ ki0) ^ (r1 ^ ki1)) + (r0 ^ ki0)); + } + + private int phaseCalc2(int r0, int ki0, int r1, int ki1) + { + return G(phaseCalc1(r0, ki0, r1, ki1) + G((r0 ^ ki0) ^ (r1 ^ ki1))); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java new file mode 100644 index 00000000..5b65b00c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.engines; + +/** + * An implementation of the SEED key wrapper based on RFC 4010/RFC 3394. + * <p> + * For further details see: <a href="http://www.ietf.org/rfc/rfc4010.txt">http://www.ietf.org/rfc/rfc4010.txt</a>. + */ +public class SEEDWrapEngine + extends RFC3394WrapEngine +{ + public SEEDWrapEngine() + { + super(new SEEDEngine()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java new file mode 100644 index 00000000..6d4210d6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java @@ -0,0 +1,321 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.MaxBytesExceededException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Strings; + +/** + * Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 + */ + +public class Salsa20Engine + implements StreamCipher +{ + /** Constants */ + private final static int STATE_SIZE = 16; // 16, 32 bit ints = 64 bytes + + private final static byte[] + sigma = Strings.toByteArray("expand 32-byte k"), + tau = Strings.toByteArray("expand 16-byte k"); + + /* + * variables to hold the state of the engine + * during encryption and decryption + */ + private int index = 0; + private int[] engineState = new int[STATE_SIZE]; // state + private int[] x = new int[STATE_SIZE] ; // internal buffer + private byte[] keyStream = new byte[STATE_SIZE * 4], // expanded state, 64 bytes + workingKey = null, + workingIV = null; + private boolean initialised = false; + + /* + * internal counter + */ + private int cW0, cW1, cW2; + + /** + * initialise a Salsa20 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + /* + * Salsa20 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. (Like 90% of stream ciphers) + */ + + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException("Salsa20 Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV) params; + + byte[] iv = ivParams.getIV(); + + if (iv == null || iv.length != 8) + { + throw new IllegalArgumentException("Salsa20 requires exactly 8 bytes of IV"); + } + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException("Salsa20 Init parameters must include a key"); + } + + KeyParameter key = (KeyParameter) ivParams.getParameters(); + + workingKey = key.getKey(); + workingIV = iv; + + setKey(workingKey, workingIV); + } + + public String getAlgorithmName() + { + return "Salsa20"; + } + + public byte returnByte(byte in) + { + if (limitExceeded()) + { + throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV"); + } + + if (index == 0) + { + generateKeyStream(keyStream); + + if (++engineState[8] == 0) + { + ++engineState[9]; + } + } + + byte out = (byte)(keyStream[index]^in); + index = (index + 1) & 63; + + return out; + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (limitExceeded(len)) + { + throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV"); + } + + for (int i = 0; i < len; i++) + { + if (index == 0) + { + generateKeyStream(keyStream); + + if (++engineState[8] == 0) + { + ++engineState[9]; + } + } + + out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]); + index = (index + 1) & 63; + } + } + + public void reset() + { + setKey(workingKey, workingIV); + } + + // Private implementation + + private void setKey(byte[] keyBytes, byte[] ivBytes) + { + workingKey = keyBytes; + workingIV = ivBytes; + + index = 0; + resetCounter(); + int offset = 0; + byte[] constants; + + // Key + engineState[1] = Pack.littleEndianToInt(workingKey, 0); + engineState[2] = Pack.littleEndianToInt(workingKey, 4); + engineState[3] = Pack.littleEndianToInt(workingKey, 8); + engineState[4] = Pack.littleEndianToInt(workingKey, 12); + + if (workingKey.length == 32) + { + constants = sigma; + offset = 16; + } + else + { + constants = tau; + } + + engineState[11] = Pack.littleEndianToInt(workingKey, offset); + engineState[12] = Pack.littleEndianToInt(workingKey, offset+4); + engineState[13] = Pack.littleEndianToInt(workingKey, offset+8); + engineState[14] = Pack.littleEndianToInt(workingKey, offset+12); + engineState[0 ] = Pack.littleEndianToInt(constants, 0); + engineState[5 ] = Pack.littleEndianToInt(constants, 4); + engineState[10] = Pack.littleEndianToInt(constants, 8); + engineState[15] = Pack.littleEndianToInt(constants, 12); + + // IV + engineState[6] = Pack.littleEndianToInt(workingIV, 0); + engineState[7] = Pack.littleEndianToInt(workingIV, 4); + engineState[8] = engineState[9] = 0; + + initialised = true; + } + + private void generateKeyStream(byte[] output) + { + salsaCore(20, engineState, x); + Pack.intToLittleEndian(x, output, 0); + } + + /** + * Salsa20 function + * + * @param input input data + * + * @return keystream + */ + public static void salsaCore(int rounds, int[] input, int[] x) + { + // TODO Exception if rounds odd? + + System.arraycopy(input, 0, x, 0, input.length); + + for (int i = rounds; i > 0; i -= 2) + { + x[ 4] ^= rotl((x[ 0]+x[12]), 7); + x[ 8] ^= rotl((x[ 4]+x[ 0]), 9); + x[12] ^= rotl((x[ 8]+x[ 4]),13); + x[ 0] ^= rotl((x[12]+x[ 8]),18); + x[ 9] ^= rotl((x[ 5]+x[ 1]), 7); + x[13] ^= rotl((x[ 9]+x[ 5]), 9); + x[ 1] ^= rotl((x[13]+x[ 9]),13); + x[ 5] ^= rotl((x[ 1]+x[13]),18); + x[14] ^= rotl((x[10]+x[ 6]), 7); + x[ 2] ^= rotl((x[14]+x[10]), 9); + x[ 6] ^= rotl((x[ 2]+x[14]),13); + x[10] ^= rotl((x[ 6]+x[ 2]),18); + x[ 3] ^= rotl((x[15]+x[11]), 7); + x[ 7] ^= rotl((x[ 3]+x[15]), 9); + x[11] ^= rotl((x[ 7]+x[ 3]),13); + x[15] ^= rotl((x[11]+x[ 7]),18); + x[ 1] ^= rotl((x[ 0]+x[ 3]), 7); + x[ 2] ^= rotl((x[ 1]+x[ 0]), 9); + x[ 3] ^= rotl((x[ 2]+x[ 1]),13); + x[ 0] ^= rotl((x[ 3]+x[ 2]),18); + x[ 6] ^= rotl((x[ 5]+x[ 4]), 7); + x[ 7] ^= rotl((x[ 6]+x[ 5]), 9); + x[ 4] ^= rotl((x[ 7]+x[ 6]),13); + x[ 5] ^= rotl((x[ 4]+x[ 7]),18); + x[11] ^= rotl((x[10]+x[ 9]), 7); + x[ 8] ^= rotl((x[11]+x[10]), 9); + x[ 9] ^= rotl((x[ 8]+x[11]),13); + x[10] ^= rotl((x[ 9]+x[ 8]),18); + x[12] ^= rotl((x[15]+x[14]), 7); + x[13] ^= rotl((x[12]+x[15]), 9); + x[14] ^= rotl((x[13]+x[12]),13); + x[15] ^= rotl((x[14]+x[13]),18); + } + + for (int i = 0; i < STATE_SIZE; ++i) + { + x[i] += input[i]; + } + } + + /** + * Rotate left + * + * @param x value to rotate + * @param y amount to rotate x + * + * @return rotated x + */ + private static int rotl(int x, int y) + { + return (x << y) | (x >>> -y); + } + + private void resetCounter() + { + cW0 = 0; + cW1 = 0; + cW2 = 0; + } + + private boolean limitExceeded() + { + if (++cW0 == 0) + { + if (++cW1 == 0) + { + return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) + } + } + + return false; + } + + /* + * this relies on the fact len will always be positive. + */ + private boolean limitExceeded(int len) + { + cW0 += len; + if (cW0 < len && cW0 >= 0) + { + if (++cW1 == 0) + { + return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) + } + } + + return false; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java new file mode 100644 index 00000000..9da23013 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java @@ -0,0 +1,783 @@ +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; + +/** + * Serpent 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> + * Serpent was designed by Ross Anderson, Eli Biham and Lars Knudsen as a + * candidate algorithm for the NIST AES Quest.> + * <p> + * For full details see the <a href="http://www.cl.cam.ac.uk/~rja14/serpent.html">The Serpent home page</a> + */ +public class SerpentEngine + implements BlockCipher +{ + private static final int BLOCK_SIZE = 16; + + static final int ROUNDS = 32; + static final int PHI = 0x9E3779B9; // (sqrt(5) - 1) * 2**31 + + private boolean encrypting; + private int[] wKey; + + private int X0, X1, X2, X3; // registers + + /** + * initialise a Serpent cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception 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 Serpent 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (wKey == null) + { + throw new IllegalStateException("Serpent 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() + { + } + + /** + * Expand a user-supplied key material into a session key. + * + * @param key The user-key bytes (multiples of 4) to use. + * @exception IllegalArgumentException + */ + private 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++] = bytesToWord(key, off); + } + + if (off == 0) + { + kPad[length++] = bytesToWord(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; + } + + private int rotateLeft( + int x, + int bits) + { + return (x << bits) | (x >>> -bits); + } + + private int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } + + private int bytesToWord( + byte[] src, + int srcOff) + { + return (((src[srcOff] & 0xff) << 24) | ((src[srcOff + 1] & 0xff) << 16) | + ((src[srcOff + 2] & 0xff) << 8) | ((src[srcOff + 3] & 0xff))); + } + + private void wordToBytes( + int word, + byte[] dst, + int dstOff) + { + dst[dstOff + 3] = (byte)(word); + dst[dstOff + 2] = (byte)(word >>> 8); + dst[dstOff + 1] = (byte)(word >>> 16); + dst[dstOff] = (byte)(word >>> 24); + } + + /** + * Encrypt one block of plaintext. + * + * @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. + */ + private void encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + X3 = bytesToWord(in, inOff); + X2 = bytesToWord(in, inOff + 4); + X1 = bytesToWord(in, inOff + 8); + X0 = bytesToWord(in, 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); + + wordToBytes(wKey[131] ^ X3, out, outOff); + wordToBytes(wKey[130] ^ X2, out, outOff + 4); + wordToBytes(wKey[129] ^ X1, out, outOff + 8); + wordToBytes(wKey[128] ^ X0, out, outOff + 12); + } + + /** + * Decrypt one block of ciphertext. + * + * @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. + */ + private void decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + X3 = wKey[131] ^ bytesToWord(in, inOff); + X2 = wKey[130] ^ bytesToWord(in, inOff + 4); + X1 = wKey[129] ^ bytesToWord(in, inOff + 8); + X0 = wKey[128] ^ bytesToWord(in, 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); + + wordToBytes(X3 ^ wKey[3], out, outOff); + wordToBytes(X2 ^ wKey[2], out, outOff + 4); + wordToBytes(X1 ^ wKey[1], out, outOff + 8); + wordToBytes(X0 ^ wKey[0], out, outOff + 12); + } + + /** + * 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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 + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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. + */ + private 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); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/SkipjackEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/SkipjackEngine.java new file mode 100644 index 00000000..1fac5362 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/SkipjackEngine.java @@ -0,0 +1,260 @@ +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; + +/** + * a class that provides a basic SKIPJACK engine. + */ +public class SkipjackEngine + implements BlockCipher +{ + static final int BLOCK_SIZE = 8; + + static short ftable[] = + { + 0xa3, 0xd7, 0x09, 0x83, 0xf8, 0x48, 0xf6, 0xf4, 0xb3, 0x21, 0x15, 0x78, 0x99, 0xb1, 0xaf, 0xf9, + 0xe7, 0x2d, 0x4d, 0x8a, 0xce, 0x4c, 0xca, 0x2e, 0x52, 0x95, 0xd9, 0x1e, 0x4e, 0x38, 0x44, 0x28, + 0x0a, 0xdf, 0x02, 0xa0, 0x17, 0xf1, 0x60, 0x68, 0x12, 0xb7, 0x7a, 0xc3, 0xe9, 0xfa, 0x3d, 0x53, + 0x96, 0x84, 0x6b, 0xba, 0xf2, 0x63, 0x9a, 0x19, 0x7c, 0xae, 0xe5, 0xf5, 0xf7, 0x16, 0x6a, 0xa2, + 0x39, 0xb6, 0x7b, 0x0f, 0xc1, 0x93, 0x81, 0x1b, 0xee, 0xb4, 0x1a, 0xea, 0xd0, 0x91, 0x2f, 0xb8, + 0x55, 0xb9, 0xda, 0x85, 0x3f, 0x41, 0xbf, 0xe0, 0x5a, 0x58, 0x80, 0x5f, 0x66, 0x0b, 0xd8, 0x90, + 0x35, 0xd5, 0xc0, 0xa7, 0x33, 0x06, 0x65, 0x69, 0x45, 0x00, 0x94, 0x56, 0x6d, 0x98, 0x9b, 0x76, + 0x97, 0xfc, 0xb2, 0xc2, 0xb0, 0xfe, 0xdb, 0x20, 0xe1, 0xeb, 0xd6, 0xe4, 0xdd, 0x47, 0x4a, 0x1d, + 0x42, 0xed, 0x9e, 0x6e, 0x49, 0x3c, 0xcd, 0x43, 0x27, 0xd2, 0x07, 0xd4, 0xde, 0xc7, 0x67, 0x18, + 0x89, 0xcb, 0x30, 0x1f, 0x8d, 0xc6, 0x8f, 0xaa, 0xc8, 0x74, 0xdc, 0xc9, 0x5d, 0x5c, 0x31, 0xa4, + 0x70, 0x88, 0x61, 0x2c, 0x9f, 0x0d, 0x2b, 0x87, 0x50, 0x82, 0x54, 0x64, 0x26, 0x7d, 0x03, 0x40, + 0x34, 0x4b, 0x1c, 0x73, 0xd1, 0xc4, 0xfd, 0x3b, 0xcc, 0xfb, 0x7f, 0xab, 0xe6, 0x3e, 0x5b, 0xa5, + 0xad, 0x04, 0x23, 0x9c, 0x14, 0x51, 0x22, 0xf0, 0x29, 0x79, 0x71, 0x7e, 0xff, 0x8c, 0x0e, 0xe2, + 0x0c, 0xef, 0xbc, 0x72, 0x75, 0x6f, 0x37, 0xa1, 0xec, 0xd3, 0x8e, 0x62, 0x8b, 0x86, 0x10, 0xe8, + 0x08, 0x77, 0x11, 0xbe, 0x92, 0x4f, 0x24, 0xc5, 0x32, 0x36, 0x9d, 0xcf, 0xf3, 0xa6, 0xbb, 0xac, + 0x5e, 0x6c, 0xa9, 0x13, 0x57, 0x25, 0xb5, 0xe3, 0xbd, 0xa8, 0x3a, 0x01, 0x05, 0x59, 0x2a, 0x46 + }; + + private int[] key0, key1, key2, key3; + private boolean encrypting; + + /** + * initialise a SKIPJACK cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to SKIPJACK init - " + params.getClass().getName()); + } + + byte[] keyBytes = ((KeyParameter)params).getKey(); + + this.encrypting = encrypting; + this.key0 = new int[32]; + this.key1 = new int[32]; + this.key2 = new int[32]; + this.key3 = new int[32]; + + // + // expand the key to 128 bytes in 4 parts (saving us a modulo, multiply + // and an addition). + // + for (int i = 0; i < 32; i ++) + { + key0[i] = keyBytes[(i * 4) % 10] & 0xff; + key1[i] = keyBytes[(i * 4 + 1) % 10] & 0xff; + key2[i] = keyBytes[(i * 4 + 2) % 10] & 0xff; + key3[i] = keyBytes[(i * 4 + 3) % 10] & 0xff; + } + } + + public String getAlgorithmName() + { + return "SKIPJACK"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (key1 == null) + { + throw new IllegalStateException("SKIPJACK engine 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() + { + } + + /** + * The G permutation + */ + private int g( + int k, + int w) + { + int g1, g2, g3, g4, g5, g6; + + g1 = (w >> 8) & 0xff; + g2 = w & 0xff; + + g3 = ftable[g2 ^ key0[k]] ^ g1; + g4 = ftable[g3 ^ key1[k]] ^ g2; + g5 = ftable[g4 ^ key2[k]] ^ g3; + g6 = ftable[g5 ^ key3[k]] ^ g4; + + return ((g5 << 8) + g6); + } + + public int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int w1 = (in[inOff + 0] << 8) + (in[inOff + 1] & 0xff); + int w2 = (in[inOff + 2] << 8) + (in[inOff + 3] & 0xff); + int w3 = (in[inOff + 4] << 8) + (in[inOff + 5] & 0xff); + int w4 = (in[inOff + 6] << 8) + (in[inOff + 7] & 0xff); + + int k = 0; + + for (int t = 0; t < 2; t++) + { + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w2; + w2 = g(k, w1); + w1 = w2 ^ tmp ^ (k + 1); + k++; + } + + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w1 ^ w2 ^ (k + 1); + w2 = g(k, w1); + w1 = tmp; + k++; + } + } + + out[outOff + 0] = (byte)((w1 >> 8)); + out[outOff + 1] = (byte)(w1); + out[outOff + 2] = (byte)((w2 >> 8)); + out[outOff + 3] = (byte)(w2); + out[outOff + 4] = (byte)((w3 >> 8)); + out[outOff + 5] = (byte)(w3); + out[outOff + 6] = (byte)((w4 >> 8)); + out[outOff + 7] = (byte)(w4); + + return BLOCK_SIZE; + } + + /** + * the inverse of the G permutation. + */ + private int h( + int k, + int w) + { + int h1, h2, h3, h4, h5, h6; + + h1 = w & 0xff; + h2 = (w >> 8) & 0xff; + + h3 = ftable[h2 ^ key3[k]] ^ h1; + h4 = ftable[h3 ^ key2[k]] ^ h2; + h5 = ftable[h4 ^ key1[k]] ^ h3; + h6 = ftable[h5 ^ key0[k]] ^ h4; + + return ((h6 << 8) + h5); + } + + public int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int w2 = (in[inOff + 0] << 8) + (in[inOff + 1] & 0xff); + int w1 = (in[inOff + 2] << 8) + (in[inOff + 3] & 0xff); + int w4 = (in[inOff + 4] << 8) + (in[inOff + 5] & 0xff); + int w3 = (in[inOff + 6] << 8) + (in[inOff + 7] & 0xff); + + int k = 31; + + for (int t = 0; t < 2; t++) + { + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w2; + w2 = h(k, w1); + w1 = w2 ^ tmp ^ (k + 1); + k--; + } + + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w1 ^ w2 ^ (k + 1); + w2 = h(k, w1); + w1 = tmp; + k--; + } + } + + out[outOff + 0] = (byte)((w2 >> 8)); + out[outOff + 1] = (byte)(w2); + out[outOff + 2] = (byte)((w1 >> 8)); + out[outOff + 3] = (byte)(w1); + out[outOff + 4] = (byte)((w4 >> 8)); + out[outOff + 5] = (byte)(w4); + out[outOff + 6] = (byte)((w3 >> 8)); + out[outOff + 7] = (byte)(w3); + + return BLOCK_SIZE; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java new file mode 100644 index 00000000..b09f1892 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java @@ -0,0 +1,179 @@ +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; + +/** + * An TEA engine. + */ +public class TEAEngine + implements BlockCipher +{ + private static final int rounds = 32, + block_size = 8, +// key_size = 16, + delta = 0x9E3779B9, + d_sum = 0xC6EF3720; // sum on decrypt + /* + * the expanded key array of 4 subkeys + */ + private int _a, _b, _c, _d; + private boolean _initialised; + private boolean _forEncryption; + + /** + * Create an instance of the TEA encryption algorithm + * and set some defaults + */ + public TEAEngine() + { + _initialised = false; + } + + public String getAlgorithmName() + { + return "TEA"; + } + + public int getBlockSize() + { + return block_size; + } + + /** + * initialise + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to TEA init - " + params.getClass().getName()); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (!_initialised) + { + 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"); + } + + return (_forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + * <p> + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + _a = bytesToInt(key, 0); + _b = bytesToInt(key, 4); + _c = bytesToInt(key, 8); + _d = bytesToInt(key, 12); + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + int sum = 0; + + for (int i = 0; i != rounds; i++) + { + sum += delta; + v0 += ((v1 << 4) + _a) ^ (v1 + sum) ^ ((v1 >>> 5) + _b); + v1 += ((v0 << 4) + _c) ^ (v0 + sum) ^ ((v0 >>> 5) + _d); + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + int sum = d_sum; + + for (int i = 0; i != rounds; i++) + { + v1 -= ((v0 << 4) + _c) ^ (v0 + sum) ^ ((v0 >>> 5) + _d); + v0 -= ((v1 << 4) + _a) ^ (v1 + sum) ^ ((v1 >>> 5) + _b); + sum -= delta; + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int bytesToInt(byte[] in, int inOff) + { + return ((in[inOff++]) << 24) | + ((in[inOff++] & 255) << 16) | + ((in[inOff++] & 255) << 8) | + ((in[inOff] & 255)); + } + + private void unpackInt(int v, byte[] out, int outOff) + { + out[outOff++] = (byte)(v >>> 24); + out[outOff++] = (byte)(v >>> 16); + out[outOff++] = (byte)(v >>> 8); + out[outOff ] = (byte)v; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/TwofishEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/TwofishEngine.java new file mode 100644 index 00000000..31ac0878 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/TwofishEngine.java @@ -0,0 +1,680 @@ +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; + +/** + * A class that provides Twofish encryption operations. + * + * This Java implementation is based on the Java reference + * implementation provided by Bruce Schneier and developed + * by Raif S. Naffah. + */ +public final class TwofishEngine + implements BlockCipher +{ + private static final byte[][] P = { + { // p0 + (byte) 0xA9, (byte) 0x67, (byte) 0xB3, (byte) 0xE8, + (byte) 0x04, (byte) 0xFD, (byte) 0xA3, (byte) 0x76, + (byte) 0x9A, (byte) 0x92, (byte) 0x80, (byte) 0x78, + (byte) 0xE4, (byte) 0xDD, (byte) 0xD1, (byte) 0x38, + (byte) 0x0D, (byte) 0xC6, (byte) 0x35, (byte) 0x98, + (byte) 0x18, (byte) 0xF7, (byte) 0xEC, (byte) 0x6C, + (byte) 0x43, (byte) 0x75, (byte) 0x37, (byte) 0x26, + (byte) 0xFA, (byte) 0x13, (byte) 0x94, (byte) 0x48, + (byte) 0xF2, (byte) 0xD0, (byte) 0x8B, (byte) 0x30, + (byte) 0x84, (byte) 0x54, (byte) 0xDF, (byte) 0x23, + (byte) 0x19, (byte) 0x5B, (byte) 0x3D, (byte) 0x59, + (byte) 0xF3, (byte) 0xAE, (byte) 0xA2, (byte) 0x82, + (byte) 0x63, (byte) 0x01, (byte) 0x83, (byte) 0x2E, + (byte) 0xD9, (byte) 0x51, (byte) 0x9B, (byte) 0x7C, + (byte) 0xA6, (byte) 0xEB, (byte) 0xA5, (byte) 0xBE, + (byte) 0x16, (byte) 0x0C, (byte) 0xE3, (byte) 0x61, + (byte) 0xC0, (byte) 0x8C, (byte) 0x3A, (byte) 0xF5, + (byte) 0x73, (byte) 0x2C, (byte) 0x25, (byte) 0x0B, + (byte) 0xBB, (byte) 0x4E, (byte) 0x89, (byte) 0x6B, + (byte) 0x53, (byte) 0x6A, (byte) 0xB4, (byte) 0xF1, + (byte) 0xE1, (byte) 0xE6, (byte) 0xBD, (byte) 0x45, + (byte) 0xE2, (byte) 0xF4, (byte) 0xB6, (byte) 0x66, + (byte) 0xCC, (byte) 0x95, (byte) 0x03, (byte) 0x56, + (byte) 0xD4, (byte) 0x1C, (byte) 0x1E, (byte) 0xD7, + (byte) 0xFB, (byte) 0xC3, (byte) 0x8E, (byte) 0xB5, + (byte) 0xE9, (byte) 0xCF, (byte) 0xBF, (byte) 0xBA, + (byte) 0xEA, (byte) 0x77, (byte) 0x39, (byte) 0xAF, + (byte) 0x33, (byte) 0xC9, (byte) 0x62, (byte) 0x71, + (byte) 0x81, (byte) 0x79, (byte) 0x09, (byte) 0xAD, + (byte) 0x24, (byte) 0xCD, (byte) 0xF9, (byte) 0xD8, + (byte) 0xE5, (byte) 0xC5, (byte) 0xB9, (byte) 0x4D, + (byte) 0x44, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0xA1, (byte) 0x1D, (byte) 0xAA, (byte) 0xED, + (byte) 0x06, (byte) 0x70, (byte) 0xB2, (byte) 0xD2, + (byte) 0x41, (byte) 0x7B, (byte) 0xA0, (byte) 0x11, + (byte) 0x31, (byte) 0xC2, (byte) 0x27, (byte) 0x90, + (byte) 0x20, (byte) 0xF6, (byte) 0x60, (byte) 0xFF, + (byte) 0x96, (byte) 0x5C, (byte) 0xB1, (byte) 0xAB, + (byte) 0x9E, (byte) 0x9C, (byte) 0x52, (byte) 0x1B, + (byte) 0x5F, (byte) 0x93, (byte) 0x0A, (byte) 0xEF, + (byte) 0x91, (byte) 0x85, (byte) 0x49, (byte) 0xEE, + (byte) 0x2D, (byte) 0x4F, (byte) 0x8F, (byte) 0x3B, + (byte) 0x47, (byte) 0x87, (byte) 0x6D, (byte) 0x46, + (byte) 0xD6, (byte) 0x3E, (byte) 0x69, (byte) 0x64, + (byte) 0x2A, (byte) 0xCE, (byte) 0xCB, (byte) 0x2F, + (byte) 0xFC, (byte) 0x97, (byte) 0x05, (byte) 0x7A, + (byte) 0xAC, (byte) 0x7F, (byte) 0xD5, (byte) 0x1A, + (byte) 0x4B, (byte) 0x0E, (byte) 0xA7, (byte) 0x5A, + (byte) 0x28, (byte) 0x14, (byte) 0x3F, (byte) 0x29, + (byte) 0x88, (byte) 0x3C, (byte) 0x4C, (byte) 0x02, + (byte) 0xB8, (byte) 0xDA, (byte) 0xB0, (byte) 0x17, + (byte) 0x55, (byte) 0x1F, (byte) 0x8A, (byte) 0x7D, + (byte) 0x57, (byte) 0xC7, (byte) 0x8D, (byte) 0x74, + (byte) 0xB7, (byte) 0xC4, (byte) 0x9F, (byte) 0x72, + (byte) 0x7E, (byte) 0x15, (byte) 0x22, (byte) 0x12, + (byte) 0x58, (byte) 0x07, (byte) 0x99, (byte) 0x34, + (byte) 0x6E, (byte) 0x50, (byte) 0xDE, (byte) 0x68, + (byte) 0x65, (byte) 0xBC, (byte) 0xDB, (byte) 0xF8, + (byte) 0xC8, (byte) 0xA8, (byte) 0x2B, (byte) 0x40, + (byte) 0xDC, (byte) 0xFE, (byte) 0x32, (byte) 0xA4, + (byte) 0xCA, (byte) 0x10, (byte) 0x21, (byte) 0xF0, + (byte) 0xD3, (byte) 0x5D, (byte) 0x0F, (byte) 0x00, + (byte) 0x6F, (byte) 0x9D, (byte) 0x36, (byte) 0x42, + (byte) 0x4A, (byte) 0x5E, (byte) 0xC1, (byte) 0xE0 }, + { // p1 + (byte) 0x75, (byte) 0xF3, (byte) 0xC6, (byte) 0xF4, + (byte) 0xDB, (byte) 0x7B, (byte) 0xFB, (byte) 0xC8, + (byte) 0x4A, (byte) 0xD3, (byte) 0xE6, (byte) 0x6B, + (byte) 0x45, (byte) 0x7D, (byte) 0xE8, (byte) 0x4B, + (byte) 0xD6, (byte) 0x32, (byte) 0xD8, (byte) 0xFD, + (byte) 0x37, (byte) 0x71, (byte) 0xF1, (byte) 0xE1, + (byte) 0x30, (byte) 0x0F, (byte) 0xF8, (byte) 0x1B, + (byte) 0x87, (byte) 0xFA, (byte) 0x06, (byte) 0x3F, + (byte) 0x5E, (byte) 0xBA, (byte) 0xAE, (byte) 0x5B, + (byte) 0x8A, (byte) 0x00, (byte) 0xBC, (byte) 0x9D, + (byte) 0x6D, (byte) 0xC1, (byte) 0xB1, (byte) 0x0E, + (byte) 0x80, (byte) 0x5D, (byte) 0xD2, (byte) 0xD5, + (byte) 0xA0, (byte) 0x84, (byte) 0x07, (byte) 0x14, + (byte) 0xB5, (byte) 0x90, (byte) 0x2C, (byte) 0xA3, + (byte) 0xB2, (byte) 0x73, (byte) 0x4C, (byte) 0x54, + (byte) 0x92, (byte) 0x74, (byte) 0x36, (byte) 0x51, + (byte) 0x38, (byte) 0xB0, (byte) 0xBD, (byte) 0x5A, + (byte) 0xFC, (byte) 0x60, (byte) 0x62, (byte) 0x96, + (byte) 0x6C, (byte) 0x42, (byte) 0xF7, (byte) 0x10, + (byte) 0x7C, (byte) 0x28, (byte) 0x27, (byte) 0x8C, + (byte) 0x13, (byte) 0x95, (byte) 0x9C, (byte) 0xC7, + (byte) 0x24, (byte) 0x46, (byte) 0x3B, (byte) 0x70, + (byte) 0xCA, (byte) 0xE3, (byte) 0x85, (byte) 0xCB, + (byte) 0x11, (byte) 0xD0, (byte) 0x93, (byte) 0xB8, + (byte) 0xA6, (byte) 0x83, (byte) 0x20, (byte) 0xFF, + (byte) 0x9F, (byte) 0x77, (byte) 0xC3, (byte) 0xCC, + (byte) 0x03, (byte) 0x6F, (byte) 0x08, (byte) 0xBF, + (byte) 0x40, (byte) 0xE7, (byte) 0x2B, (byte) 0xE2, + (byte) 0x79, (byte) 0x0C, (byte) 0xAA, (byte) 0x82, + (byte) 0x41, (byte) 0x3A, (byte) 0xEA, (byte) 0xB9, + (byte) 0xE4, (byte) 0x9A, (byte) 0xA4, (byte) 0x97, + (byte) 0x7E, (byte) 0xDA, (byte) 0x7A, (byte) 0x17, + (byte) 0x66, (byte) 0x94, (byte) 0xA1, (byte) 0x1D, + (byte) 0x3D, (byte) 0xF0, (byte) 0xDE, (byte) 0xB3, + (byte) 0x0B, (byte) 0x72, (byte) 0xA7, (byte) 0x1C, + (byte) 0xEF, (byte) 0xD1, (byte) 0x53, (byte) 0x3E, + (byte) 0x8F, (byte) 0x33, (byte) 0x26, (byte) 0x5F, + (byte) 0xEC, (byte) 0x76, (byte) 0x2A, (byte) 0x49, + (byte) 0x81, (byte) 0x88, (byte) 0xEE, (byte) 0x21, + (byte) 0xC4, (byte) 0x1A, (byte) 0xEB, (byte) 0xD9, + (byte) 0xC5, (byte) 0x39, (byte) 0x99, (byte) 0xCD, + (byte) 0xAD, (byte) 0x31, (byte) 0x8B, (byte) 0x01, + (byte) 0x18, (byte) 0x23, (byte) 0xDD, (byte) 0x1F, + (byte) 0x4E, (byte) 0x2D, (byte) 0xF9, (byte) 0x48, + (byte) 0x4F, (byte) 0xF2, (byte) 0x65, (byte) 0x8E, + (byte) 0x78, (byte) 0x5C, (byte) 0x58, (byte) 0x19, + (byte) 0x8D, (byte) 0xE5, (byte) 0x98, (byte) 0x57, + (byte) 0x67, (byte) 0x7F, (byte) 0x05, (byte) 0x64, + (byte) 0xAF, (byte) 0x63, (byte) 0xB6, (byte) 0xFE, + (byte) 0xF5, (byte) 0xB7, (byte) 0x3C, (byte) 0xA5, + (byte) 0xCE, (byte) 0xE9, (byte) 0x68, (byte) 0x44, + (byte) 0xE0, (byte) 0x4D, (byte) 0x43, (byte) 0x69, + (byte) 0x29, (byte) 0x2E, (byte) 0xAC, (byte) 0x15, + (byte) 0x59, (byte) 0xA8, (byte) 0x0A, (byte) 0x9E, + (byte) 0x6E, (byte) 0x47, (byte) 0xDF, (byte) 0x34, + (byte) 0x35, (byte) 0x6A, (byte) 0xCF, (byte) 0xDC, + (byte) 0x22, (byte) 0xC9, (byte) 0xC0, (byte) 0x9B, + (byte) 0x89, (byte) 0xD4, (byte) 0xED, (byte) 0xAB, + (byte) 0x12, (byte) 0xA2, (byte) 0x0D, (byte) 0x52, + (byte) 0xBB, (byte) 0x02, (byte) 0x2F, (byte) 0xA9, + (byte) 0xD7, (byte) 0x61, (byte) 0x1E, (byte) 0xB4, + (byte) 0x50, (byte) 0x04, (byte) 0xF6, (byte) 0xC2, + (byte) 0x16, (byte) 0x25, (byte) 0x86, (byte) 0x56, + (byte) 0x55, (byte) 0x09, (byte) 0xBE, (byte) 0x91 } + }; + + /** + * Define the fixed p0/p1 permutations used in keyed S-box lookup. + * By changing the following constant definitions, the S-boxes will + * automatically get changed in the Twofish engine. + */ + private static final int P_00 = 1; + private static final int P_01 = 0; + private static final int P_02 = 0; + private static final int P_03 = P_01 ^ 1; + private static final int P_04 = 1; + + private static final int P_10 = 0; + private static final int P_11 = 0; + private static final int P_12 = 1; + private static final int P_13 = P_11 ^ 1; + private static final int P_14 = 0; + + private static final int P_20 = 1; + private static final int P_21 = 1; + private static final int P_22 = 0; + private static final int P_23 = P_21 ^ 1; + private static final int P_24 = 0; + + private static final int P_30 = 0; + private static final int P_31 = 1; + private static final int P_32 = 1; + private static final int P_33 = P_31 ^ 1; + private static final int P_34 = 1; + + /* Primitive polynomial for GF(256) */ + private static final int GF256_FDBK = 0x169; + private static final int GF256_FDBK_2 = GF256_FDBK / 2; + private static final int GF256_FDBK_4 = GF256_FDBK / 4; + + private static final int RS_GF_FDBK = 0x14D; // field generator + + //==================================== + // Useful constants + //==================================== + + private static final int ROUNDS = 16; + private static final int MAX_ROUNDS = 16; // bytes = 128 bits + private static final int BLOCK_SIZE = 16; // bytes = 128 bits + private static final int MAX_KEY_BITS = 256; + + private static final int INPUT_WHITEN=0; + private static final int OUTPUT_WHITEN=INPUT_WHITEN+BLOCK_SIZE/4; // 4 + private static final int ROUND_SUBKEYS=OUTPUT_WHITEN+BLOCK_SIZE/4;// 8 + + private static final int TOTAL_SUBKEYS=ROUND_SUBKEYS+2*MAX_ROUNDS;// 40 + + private static final int SK_STEP = 0x02020202; + private static final int SK_BUMP = 0x01010101; + private static final int SK_ROTL = 9; + + private boolean encrypting = false; + + private int[] gMDS0 = new int[MAX_KEY_BITS]; + private int[] gMDS1 = new int[MAX_KEY_BITS]; + private int[] gMDS2 = new int[MAX_KEY_BITS]; + private int[] gMDS3 = new int[MAX_KEY_BITS]; + + /** + * gSubKeys[] and gSBox[] are eventually used in the + * encryption and decryption methods. + */ + private int[] gSubKeys; + private int[] gSBox; + + private int k64Cnt = 0; + + private byte[] workingKey = null; + + public TwofishEngine() + { + // calculate the MDS matrix + int[] m1 = new int[2]; + int[] mX = new int[2]; + int[] mY = new int[2]; + int j; + + for (int i=0; i< MAX_KEY_BITS ; i++) + { + j = P[0][i] & 0xff; + m1[0] = j; + mX[0] = Mx_X(j) & 0xff; + mY[0] = Mx_Y(j) & 0xff; + + j = P[1][i] & 0xff; + m1[1] = j; + mX[1] = Mx_X(j) & 0xff; + mY[1] = Mx_Y(j) & 0xff; + + gMDS0[i] = m1[P_00] | mX[P_00] << 8 | + mY[P_00] << 16 | mY[P_00] << 24; + + gMDS1[i] = mY[P_10] | mY[P_10] << 8 | + mX[P_10] << 16 | m1[P_10] << 24; + + gMDS2[i] = mX[P_20] | mY[P_20] << 8 | + m1[P_20] << 16 | mY[P_20] << 24; + + gMDS3[i] = mX[P_30] | m1[P_30] << 8 | + mY[P_30] << 16 | mX[P_30] << 24; + } + } + + /** + * initialise a Twofish cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + this.encrypting = encrypting; + this.workingKey = ((KeyParameter)params).getKey(); + this.k64Cnt = (this.workingKey.length / 8); // pre-padded ? + setKey(this.workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Twofish init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Twofish"; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Twofish 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() + { + if (this.workingKey != null) + { + setKey(this.workingKey); + } + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + private void setKey(byte[] key) + { + int[] k32e = new int[MAX_KEY_BITS/64]; // 4 + int[] k32o = new int[MAX_KEY_BITS/64]; // 4 + + int[] sBoxKeys = new int[MAX_KEY_BITS/64]; // 4 + gSubKeys = new int[TOTAL_SUBKEYS]; + + if (k64Cnt < 1) + { + throw new IllegalArgumentException("Key size less than 64 bits"); + } + + if (k64Cnt > 4) + { + throw new IllegalArgumentException("Key size larger than 256 bits"); + } + + /* + * k64Cnt is the number of 8 byte blocks (64 chunks) + * that are in the input key. The input key is a + * maximum of 32 bytes (256 bits), so the range + * for k64Cnt is 1..4 + */ + for (int i=0; i<k64Cnt ; i++) + { + int p = i* 8; + + k32e[i] = BytesTo32Bits(key, p); + k32o[i] = BytesTo32Bits(key, p+4); + + sBoxKeys[k64Cnt-1-i] = RS_MDS_Encode(k32e[i], k32o[i]); + } + + int q,A,B; + for (int i=0; i < TOTAL_SUBKEYS / 2 ; i++) + { + q = i*SK_STEP; + A = F32(q, k32e); + B = F32(q+SK_BUMP, k32o); + B = B << 8 | B >>> 24; + A += B; + gSubKeys[i*2] = A; + A += B; + gSubKeys[i*2 + 1] = A << SK_ROTL | A >>> (32-SK_ROTL); + } + + /* + * fully expand the table for speed + */ + int k0 = sBoxKeys[0]; + int k1 = sBoxKeys[1]; + int k2 = sBoxKeys[2]; + int k3 = sBoxKeys[3]; + int b0, b1, b2, b3; + gSBox = new int[4*MAX_KEY_BITS]; + for (int i=0; i<MAX_KEY_BITS; i++) + { + b0 = b1 = b2 = b3 = i; + switch (k64Cnt & 3) + { + case 1: + gSBox[i*2] = gMDS0[(P[P_01][b0] & 0xff) ^ b0(k0)]; + gSBox[i*2+1] = gMDS1[(P[P_11][b1] & 0xff) ^ b1(k0)]; + gSBox[i*2+0x200] = gMDS2[(P[P_21][b2] & 0xff) ^ b2(k0)]; + gSBox[i*2+0x201] = gMDS3[(P[P_31][b3] & 0xff) ^ b3(k0)]; + break; + case 0: // 256 bits of key + b0 = (P[P_04][b0] & 0xff) ^ b0(k3); + b1 = (P[P_14][b1] & 0xff) ^ b1(k3); + b2 = (P[P_24][b2] & 0xff) ^ b2(k3); + b3 = (P[P_34][b3] & 0xff) ^ b3(k3); + // fall through, having pre-processed b[0]..b[3] with k32[3] + case 3: // 192 bits of key + b0 = (P[P_03][b0] & 0xff) ^ b0(k2); + b1 = (P[P_13][b1] & 0xff) ^ b1(k2); + b2 = (P[P_23][b2] & 0xff) ^ b2(k2); + b3 = (P[P_33][b3] & 0xff) ^ b3(k2); + // fall through, having pre-processed b[0]..b[3] with k32[2] + case 2: // 128 bits of key + gSBox[i*2] = gMDS0[(P[P_01] + [(P[P_02][b0] & 0xff) ^ b0(k1)] & 0xff) ^ b0(k0)]; + gSBox[i*2+1] = gMDS1[(P[P_11] + [(P[P_12][b1] & 0xff) ^ b1(k1)] & 0xff) ^ b1(k0)]; + gSBox[i*2+0x200] = gMDS2[(P[P_21] + [(P[P_22][b2] & 0xff) ^ b2(k1)] & 0xff) ^ b2(k0)]; + gSBox[i*2+0x201] = gMDS3[(P[P_31] + [(P[P_32][b3] & 0xff) ^ b3(k1)] & 0xff) ^ b3(k0)]; + break; + } + } + + /* + * the function exits having setup the gSBox with the + * input key material. + */ + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + * + * encryptBlock uses the pre-calculated gSBox[] and subKey[] + * arrays. + */ + private void encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int x0 = BytesTo32Bits(src, srcIndex) ^ gSubKeys[INPUT_WHITEN]; + int x1 = BytesTo32Bits(src, srcIndex + 4) ^ gSubKeys[INPUT_WHITEN + 1]; + int x2 = BytesTo32Bits(src, srcIndex + 8) ^ gSubKeys[INPUT_WHITEN + 2]; + int x3 = BytesTo32Bits(src, srcIndex + 12) ^ gSubKeys[INPUT_WHITEN + 3]; + + int k = ROUND_SUBKEYS; + int t0, t1; + for (int r = 0; r < ROUNDS; r +=2) + { + t0 = Fe32_0(x0); + t1 = Fe32_3(x1); + x2 ^= t0 + t1 + gSubKeys[k++]; + x2 = x2 >>>1 | x2 << 31; + x3 = (x3 << 1 | x3 >>> 31) ^ (t0 + 2*t1 + gSubKeys[k++]); + + t0 = Fe32_0(x2); + t1 = Fe32_3(x3); + x0 ^= t0 + t1 + gSubKeys[k++]; + x0 = x0 >>>1 | x0 << 31; + x1 = (x1 << 1 | x1 >>> 31) ^ (t0 + 2*t1 + gSubKeys[k++]); + } + + Bits32ToBytes(x2 ^ gSubKeys[OUTPUT_WHITEN], dst, dstIndex); + Bits32ToBytes(x3 ^ gSubKeys[OUTPUT_WHITEN + 1], dst, dstIndex + 4); + Bits32ToBytes(x0 ^ gSubKeys[OUTPUT_WHITEN + 2], dst, dstIndex + 8); + Bits32ToBytes(x1 ^ gSubKeys[OUTPUT_WHITEN + 3], dst, dstIndex + 12); + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + */ + private void decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int x2 = BytesTo32Bits(src, srcIndex) ^ gSubKeys[OUTPUT_WHITEN]; + int x3 = BytesTo32Bits(src, srcIndex+4) ^ gSubKeys[OUTPUT_WHITEN + 1]; + int x0 = BytesTo32Bits(src, srcIndex+8) ^ gSubKeys[OUTPUT_WHITEN + 2]; + int x1 = BytesTo32Bits(src, srcIndex+12) ^ gSubKeys[OUTPUT_WHITEN + 3]; + + int k = ROUND_SUBKEYS + 2 * ROUNDS -1 ; + int t0, t1; + for (int r = 0; r< ROUNDS ; r +=2) + { + t0 = Fe32_0(x2); + t1 = Fe32_3(x3); + x1 ^= t0 + 2*t1 + gSubKeys[k--]; + x0 = (x0 << 1 | x0 >>> 31) ^ (t0 + t1 + gSubKeys[k--]); + x1 = x1 >>>1 | x1 << 31; + + t0 = Fe32_0(x0); + t1 = Fe32_3(x1); + x3 ^= t0 + 2*t1 + gSubKeys[k--]; + x2 = (x2 << 1 | x2 >>> 31) ^ (t0 + t1 + gSubKeys[k--]); + x3 = x3 >>>1 | x3 << 31; + } + + Bits32ToBytes(x0 ^ gSubKeys[INPUT_WHITEN], dst, dstIndex); + Bits32ToBytes(x1 ^ gSubKeys[INPUT_WHITEN + 1], dst, dstIndex + 4); + Bits32ToBytes(x2 ^ gSubKeys[INPUT_WHITEN + 2], dst, dstIndex + 8); + Bits32ToBytes(x3 ^ gSubKeys[INPUT_WHITEN + 3], dst, dstIndex + 12); + } + + /* + * TODO: This can be optimised and made cleaner by combining + * the functionality in this function and applying it appropriately + * to the creation of the subkeys during key setup. + */ + private int F32(int x, int[] k32) + { + int b0 = b0(x); + int b1 = b1(x); + int b2 = b2(x); + int b3 = b3(x); + int k0 = k32[0]; + int k1 = k32[1]; + int k2 = k32[2]; + int k3 = k32[3]; + + int result = 0; + switch (k64Cnt & 3) + { + case 1: + result = gMDS0[(P[P_01][b0] & 0xff) ^ b0(k0)] ^ + gMDS1[(P[P_11][b1] & 0xff) ^ b1(k0)] ^ + gMDS2[(P[P_21][b2] & 0xff) ^ b2(k0)] ^ + gMDS3[(P[P_31][b3] & 0xff) ^ b3(k0)]; + break; + case 0: /* 256 bits of key */ + b0 = (P[P_04][b0] & 0xff) ^ b0(k3); + b1 = (P[P_14][b1] & 0xff) ^ b1(k3); + b2 = (P[P_24][b2] & 0xff) ^ b2(k3); + b3 = (P[P_34][b3] & 0xff) ^ b3(k3); + case 3: + b0 = (P[P_03][b0] & 0xff) ^ b0(k2); + b1 = (P[P_13][b1] & 0xff) ^ b1(k2); + b2 = (P[P_23][b2] & 0xff) ^ b2(k2); + b3 = (P[P_33][b3] & 0xff) ^ b3(k2); + case 2: + result = + gMDS0[(P[P_01][(P[P_02][b0]&0xff)^b0(k1)]&0xff)^b0(k0)] ^ + gMDS1[(P[P_11][(P[P_12][b1]&0xff)^b1(k1)]&0xff)^b1(k0)] ^ + gMDS2[(P[P_21][(P[P_22][b2]&0xff)^b2(k1)]&0xff)^b2(k0)] ^ + gMDS3[(P[P_31][(P[P_32][b3]&0xff)^b3(k1)]&0xff)^b3(k0)]; + break; + } + return result; + } + + /** + * Use (12, 8) Reed-Solomon code over GF(256) to produce + * a key S-box 32-bit entity from 2 key material 32-bit + * entities. + * + * @param k0 first 32-bit entity + * @param k1 second 32-bit entity + * @return Remainder polynomial generated using RS code + */ + private int RS_MDS_Encode(int k0, int k1) + { + int r = k1; + for (int i = 0 ; i < 4 ; i++) // shift 1 byte at a time + { + r = RS_rem(r); + } + r ^= k0; + for (int i=0 ; i < 4 ; i++) + { + r = RS_rem(r); + } + + return r; + } + + /** + * Reed-Solomon code parameters: (12,8) reversible code:<p> + * <pre> + * g(x) = x^4 + (a+1/a)x^3 + ax^2 + (a+1/a)x + 1 + * </pre> + * where a = primitive root of field generator 0x14D + */ + private int RS_rem(int x) + { + int b = (x >>> 24) & 0xff; + int g2 = ((b << 1) ^ + ((b & 0x80) != 0 ? RS_GF_FDBK : 0)) & 0xff; + int g3 = ((b >>> 1) ^ + ((b & 0x01) != 0 ? (RS_GF_FDBK >>> 1) : 0)) ^ g2 ; + return ((x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b); + } + + private int LFSR1(int x) + { + return (x >> 1) ^ + (((x & 0x01) != 0) ? GF256_FDBK_2 : 0); + } + + private int LFSR2(int x) + { + return (x >> 2) ^ + (((x & 0x02) != 0) ? GF256_FDBK_2 : 0) ^ + (((x & 0x01) != 0) ? GF256_FDBK_4 : 0); + } + + private int Mx_X(int x) + { + return x ^ LFSR2(x); + } // 5B + + private int Mx_Y(int x) + { + return x ^ LFSR1(x) ^ LFSR2(x); + } // EF + + private int b0(int x) + { + return x & 0xff; + } + + private int b1(int x) + { + return (x >>> 8) & 0xff; + } + + private int b2(int x) + { + return (x >>> 16) & 0xff; + } + + private int b3(int x) + { + return (x >>> 24) & 0xff; + } + + private int Fe32_0(int x) + { + return gSBox[ 0x000 + 2*(x & 0xff) ] ^ + gSBox[ 0x001 + 2*((x >>> 8) & 0xff) ] ^ + gSBox[ 0x200 + 2*((x >>> 16) & 0xff) ] ^ + gSBox[ 0x201 + 2*((x >>> 24) & 0xff) ]; + } + + private int Fe32_3(int x) + { + return gSBox[ 0x000 + 2*((x >>> 24) & 0xff) ] ^ + gSBox[ 0x001 + 2*(x & 0xff) ] ^ + gSBox[ 0x200 + 2*((x >>> 8) & 0xff) ] ^ + gSBox[ 0x201 + 2*((x >>> 16) & 0xff) ]; + } + + private int BytesTo32Bits(byte[] b, int p) + { + return ((b[p] & 0xff)) | + ((b[p+1] & 0xff) << 8) | + ((b[p+2] & 0xff) << 16) | + ((b[p+3] & 0xff) << 24); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset] = (byte)in; + b[offset + 1] = (byte)(in >> 8); + b[offset + 2] = (byte)(in >> 16); + b[offset + 3] = (byte)(in >> 24); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java new file mode 100644 index 00000000..0703fd68 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java @@ -0,0 +1,139 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +public class VMPCEngine implements StreamCipher +{ + /* + * variables to hold the state of the VMPC engine during encryption and + * decryption + */ + protected byte n = 0; + protected byte[] P = null; + protected byte s = 0; + + protected byte[] workingIV; + protected byte[] workingKey; + + public String getAlgorithmName() + { + return "VMPC"; + } + + /** + * initialise a VMPC cipher. + * + * @param forEncryption + * whether or not we are for encryption. + * @param params + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + { + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "VMPC init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV) params; + KeyParameter key = (KeyParameter) ivParams.getParameters(); + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "VMPC init parameters must include a key"); + } + + this.workingIV = ivParams.getIV(); + + if (workingIV == null || workingIV.length < 1 || workingIV.length > 768) + { + throw new IllegalArgumentException("VMPC requires 1 to 768 bytes of IV"); + } + + this.workingKey = key.getKey(); + + initKey(this.workingKey, this.workingIV); + } + + protected void initKey(byte[] keyBytes, byte[] ivBytes) + { + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + n = 0; + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + // encryption + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + + // xor + out[i + outOff] = (byte) (in[i + inOff] ^ z); + } + } + + public void reset() + { + initKey(this.workingKey, this.workingIV); + } + + public byte returnByte(byte in) + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + // encryption + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + + // xor + return (byte) (in ^ z); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/VMPCKSA3Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/VMPCKSA3Engine.java new file mode 100644 index 00000000..9e40272b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/VMPCKSA3Engine.java @@ -0,0 +1,45 @@ +package org.bouncycastle.crypto.engines; + +public class VMPCKSA3Engine extends VMPCEngine +{ + public String getAlgorithmName() + { + return "VMPC-KSA3"; + } + + protected void initKey(byte[] keyBytes, byte[] ivBytes) + { + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + n = 0; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java new file mode 100644 index 00000000..f037da4d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java @@ -0,0 +1,183 @@ +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; + +/** + * An XTEA engine. + */ +public class XTEAEngine + implements BlockCipher +{ + private static final int rounds = 32, + block_size = 8, +// key_size = 16, + delta = 0x9E3779B9; + + /* + * the expanded key array of 4 subkeys + */ + private int[] _S = new int[4], + _sum0 = new int[32], + _sum1 = new int[32]; + private boolean _initialised, + _forEncryption; + + /** + * Create an instance of the TEA encryption algorithm + * and set some defaults + */ + public XTEAEngine() + { + _initialised = false; + } + + public String getAlgorithmName() + { + return "XTEA"; + } + + public int getBlockSize() + { + return block_size; + } + + /** + * initialise + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to TEA init - " + params.getClass().getName()); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (!_initialised) + { + 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"); + } + + return (_forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + * <p> + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + int i, j; + for (i = j = 0; i < 4; i++,j+=4) + { + _S[i] = bytesToInt(key, j); + } + + for (i = j = 0; i < rounds; i++) + { + _sum0[i] = (j + _S[j & 3]); + j += delta; + _sum1[i] = (j + _S[j >>> 11 & 3]); + } + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + for (int i = 0; i < rounds; i++) + { + v0 += ((v1 << 4 ^ v1 >>> 5) + v1) ^ _sum0[i]; + v1 += ((v0 << 4 ^ v0 >>> 5) + v0) ^ _sum1[i]; + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + for (int i = rounds-1; i >= 0; i--) + { + v1 -= ((v0 << 4 ^ v0 >>> 5) + v0) ^ _sum1[i]; + v0 -= ((v1 << 4 ^ v1 >>> 5) + v1) ^ _sum0[i]; + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int bytesToInt(byte[] in, int inOff) + { + return ((in[inOff++]) << 24) | + ((in[inOff++] & 255) << 16) | + ((in[inOff++] & 255) << 8) | + ((in[inOff] & 255)); + } + + private void unpackInt(int v, byte[] out, int outOff) + { + out[outOff++] = (byte)(v >>> 24); + out[outOff++] = (byte)(v >>> 16); + out[outOff++] = (byte)(v >>> 8); + out[outOff ] = (byte)v; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/examples/DESExample.java b/core/src/main/java/org/bouncycastle/crypto/examples/DESExample.java new file mode 100644 index 00000000..16989971 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/examples/DESExample.java @@ -0,0 +1,419 @@ +package org.bouncycastle.crypto.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.engines.DESedeEngine; +import org.bouncycastle.crypto.generators.DESedeKeyGenerator; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.bouncycastle.crypto.params.DESedeParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.encoders.Hex; + +/** + * DESExample is a simple DES based encryptor/decryptor. + * <p> + * The program is command line driven, with the input + * and output files specified on the command line. + * <pre> + * java org.bouncycastle.crypto.examples.DESExample infile outfile [keyfile] + * </pre> + * A new key is generated for each encryption, if key is not specified, + * then the example will assume encryption is required, and as output + * create deskey.dat in the current directory. This key is a hex + * encoded byte-stream that is used for the decryption. The output + * file is Hex encoded, 60 characters wide text file. + * <p> + * When encrypting; + * <ul> + * <li>the infile is expected to be a byte stream (text or binary) + * <li>there is no keyfile specified on the input line + * </ul> + * <p> + * When decrypting; + * <li>the infile is expected to be the 60 character wide base64 + * encoded file + * <li>the keyfile is expected to be a base64 encoded file + * <p> + * This example shows how to use the light-weight API, DES and + * the filesystem for message encryption and decryption. + * + */ +public class DESExample extends Object +{ + // Encrypting or decrypting ? + private boolean encrypt = true; + + // To hold the initialised DESede cipher + private PaddedBufferedBlockCipher cipher = null; + + // The input stream of bytes to be processed for encryption + private BufferedInputStream in = null; + + // The output stream of bytes to be procssed + private BufferedOutputStream out = null; + + // The key + private byte[] key = null; + + /* + * start the application + */ + public static void main(String[] args) + { + boolean encrypt = true; + String infile = null; + String outfile = null; + String keyfile = null; + + if (args.length < 2) + { + DESExample de = new DESExample(); + System.err.println("Usage: java "+de.getClass().getName()+ + " infile outfile [keyfile]"); + System.exit(1); + } + + keyfile = "deskey.dat"; + infile = args[0]; + outfile = args[1]; + + if (args.length > 2) + { + encrypt = false; + keyfile = args[2]; + } + + DESExample de = new DESExample(infile, outfile, keyfile, encrypt); + de.process(); + } + + // Default constructor, used for the usage message + public DESExample() + { + } + + /* + * Constructor, that takes the arguments appropriate for + * processing the command line directives. + */ + public DESExample( + String infile, + String outfile, + String keyfile, + boolean encrypt) + { + /* + * First, determine that infile & keyfile exist as appropriate. + * + * This will also create the BufferedInputStream as required + * for reading the input file. All input files are treated + * as if they are binary, even if they contain text, it's the + * bytes that are encrypted. + */ + this.encrypt = encrypt; + try + { + in = new BufferedInputStream(new FileInputStream(infile)); + } + catch (FileNotFoundException fnf) + { + System.err.println("Input file not found ["+infile+"]"); + System.exit(1); + } + + try + { + out = new BufferedOutputStream(new FileOutputStream(outfile)); + } + catch (IOException fnf) + { + System.err.println("Output file not created ["+outfile+"]"); + System.exit(1); + } + + if (encrypt) + { + try + { + /* + * The process of creating a new key requires a + * number of steps. + * + * First, create the parameters for the key generator + * which are a secure random number generator, and + * the length of the key (in bits). + */ + SecureRandom sr = null; + try + { + sr = new SecureRandom(); + /* + * This following call to setSeed() makes the + * initialisation of the SecureRandom object + * _very_ fast, but not secure AT ALL. + * + * Remove the line, recreate the class file and + * then run DESExample again to see the difference. + * + * The initialisation of a SecureRandom object + * can take 5 or more seconds depending on the + * CPU that the program is running on. That can + * be annoying during unit testing. + * -- jon + */ + sr.setSeed("www.bouncycastle.org".getBytes()); + } + catch (Exception nsa) + { + System.err.println("Hmmm, no SHA1PRNG, you need the "+ + "Sun implementation"); + System.exit(1); + } + KeyGenerationParameters kgp = new KeyGenerationParameters( + sr, + DESedeParameters.DES_EDE_KEY_LENGTH*8); + + /* + * Second, initialise the key generator with the parameters + */ + DESedeKeyGenerator kg = new DESedeKeyGenerator(); + kg.init(kgp); + + /* + * Third, and finally, generate the key + */ + key = kg.generateKey(); + + /* + * We can now output the key to the file, but first + * hex encode the key so that we can have a look + * at it with a text editor if we so desire + */ + BufferedOutputStream keystream = + new BufferedOutputStream(new FileOutputStream(keyfile)); + byte[] keyhex = Hex.encode(key); + keystream.write(keyhex, 0, keyhex.length); + keystream.flush(); + keystream.close(); + } + catch (IOException createKey) + { + System.err.println("Could not decryption create key file "+ + "["+keyfile+"]"); + System.exit(1); + } + } + else + { + try + { + // read the key, and decode from hex encoding + BufferedInputStream keystream = + new BufferedInputStream(new FileInputStream(keyfile)); + int len = keystream.available(); + byte[] keyhex = new byte[len]; + keystream.read(keyhex, 0, len); + key = Hex.decode(keyhex); + } + catch (IOException ioe) + { + System.err.println("Decryption key file not found, "+ + "or not valid ["+keyfile+"]"); + System.exit(1); + } + } + } + + private void process() + { + /* + * Setup the DESede cipher engine, create a PaddedBufferedBlockCipher + * in CBC mode. + */ + cipher = new PaddedBufferedBlockCipher( + new CBCBlockCipher(new DESedeEngine())); + + /* + * The input and output streams are currently set up + * appropriately, and the key bytes are ready to be + * used. + * + */ + + if (encrypt) + { + performEncrypt(key); + } + else + { + performDecrypt(key); + } + + // after processing clean up the files + try + { + in.close(); + out.flush(); + out.close(); + } + catch (IOException closing) + { + + } + } + + /* + * This method performs all the encryption and writes + * the cipher text to the buffered output stream created + * previously. + */ + private void performEncrypt(byte[] key) + { + // initialise the cipher with the key bytes, for encryption + cipher.init(true, new KeyParameter(key)); + + /* + * Create some temporary byte arrays for use in + * encryption, make them a reasonable size so that + * we don't spend forever reading small chunks from + * a file. + * + * There is no particular reason for using getBlockSize() + * to determine the size of the input chunk. It just + * was a convenient number for the example. + */ + // int inBlockSize = cipher.getBlockSize() * 5; + int inBlockSize = 47; + int outBlockSize = cipher.getOutputSize(inBlockSize); + + byte[] inblock = new byte[inBlockSize]; + byte[] outblock = new byte[outBlockSize]; + + /* + * now, read the file, and output the chunks + */ + try + { + int inL; + int outL; + byte[] rv = null; + while ((inL=in.read(inblock, 0, inBlockSize)) > 0) + { + outL = cipher.processBytes(inblock, 0, inL, outblock, 0); + /* + * Before we write anything out, we need to make sure + * that we've got something to write out. + */ + if (outL > 0) + { + rv = Hex.encode(outblock, 0, outL); + out.write(rv, 0, rv.length); + out.write('\n'); + } + } + + try + { + /* + * Now, process the bytes that are still buffered + * within the cipher. + */ + outL = cipher.doFinal(outblock, 0); + if (outL > 0) + { + rv = Hex.encode(outblock, 0, outL); + out.write(rv, 0, rv.length); + out.write('\n'); + } + } + catch (CryptoException ce) + { + + } + } + catch (IOException ioeread) + { + ioeread.printStackTrace(); + } + } + + /* + * This method performs all the decryption and writes + * the plain text to the buffered output stream created + * previously. + */ + private void performDecrypt(byte[] key) + { + // initialise the cipher for decryption + cipher.init(false, new KeyParameter(key)); + + /* + * As the decryption is from our preformatted file, + * and we know that it's a hex encoded format, then + * we wrap the InputStream with a BufferedReader + * so that we can read it easily. + */ + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + /* + * now, read the file, and output the chunks + */ + try + { + int outL; + byte[] inblock = null; + byte[] outblock = null; + String rv = null; + while ((rv = br.readLine()) != null) + { + inblock = Hex.decode(rv); + outblock = new byte[cipher.getOutputSize(inblock.length)]; + + outL = cipher.processBytes(inblock, 0, inblock.length, + outblock, 0); + /* + * Before we write anything out, we need to make sure + * that we've got something to write out. + */ + if (outL > 0) + { + out.write(outblock, 0, outL); + } + } + + try + { + /* + * Now, process the bytes that are still buffered + * within the cipher. + */ + outL = cipher.doFinal(outblock, 0); + if (outL > 0) + { + out.write(outblock, 0, outL); + } + } + catch (CryptoException ce) + { + + } + } + catch (IOException ioeread) + { + ioeread.printStackTrace(); + } + } + +} + diff --git a/core/src/main/java/org/bouncycastle/crypto/examples/JPAKEExample.java b/core/src/main/java/org/bouncycastle/crypto/examples/JPAKEExample.java new file mode 100644 index 00000000..f0065f4c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/examples/JPAKEExample.java @@ -0,0 +1,214 @@ +package org.bouncycastle.crypto.examples; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroup; +import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroups; +import org.bouncycastle.crypto.agreement.jpake.JPAKEParticipant; +import org.bouncycastle.crypto.agreement.jpake.JPAKERound1Payload; +import org.bouncycastle.crypto.agreement.jpake.JPAKERound2Payload; +import org.bouncycastle.crypto.agreement.jpake.JPAKERound3Payload; +import org.bouncycastle.crypto.digests.SHA256Digest; + +/** + * An example of a J-PAKE exchange. + * <p> + * + * In this example, both Alice and Bob are on the same computer (in the same JVM, in fact). + * In reality, Alice and Bob would be in different locations, + * and would be sending their generated payloads to each other. + */ +public class JPAKEExample +{ + + public static void main(String args[]) throws CryptoException + { + /* + * Initialization + * + * Pick an appropriate prime order group to use throughout the exchange. + * Note that both participants must use the same group. + */ + JPAKEPrimeOrderGroup group = JPAKEPrimeOrderGroups.NIST_3072; + + BigInteger p = group.getP(); + BigInteger q = group.getQ(); + BigInteger g = group.getG(); + + String alicePassword = "password"; + String bobPassword = "password"; + + System.out.println("********* Initialization **********"); + System.out.println("Public parameters for the cyclic group:"); + System.out.println("p (" + p.bitLength() + " bits): " + p.toString(16)); + System.out.println("q (" + q.bitLength() + " bits): " + q.toString(16)); + System.out.println("g (" + p.bitLength() + " bits): " + g.toString(16)); + System.out.println("p mod q = " + p.mod(q).toString(16)); + System.out.println("g^{q} mod p = " + g.modPow(q, p).toString(16)); + System.out.println(""); + + System.out.println("(Secret passwords used by Alice and Bob: " + + "\"" + alicePassword + "\" and \"" + bobPassword + "\")\n"); + + /* + * Both participants must use the same hashing algorithm. + */ + Digest digest = new SHA256Digest(); + SecureRandom random = new SecureRandom(); + + JPAKEParticipant alice = new JPAKEParticipant("alice", alicePassword.toCharArray(), group, digest, random); + JPAKEParticipant bob = new JPAKEParticipant("bob", bobPassword.toCharArray(), group, digest, random); + + /* + * Round 1 + * + * Alice and Bob each generate a round 1 payload, and send it to each other. + */ + + JPAKERound1Payload aliceRound1Payload = alice.createRound1PayloadToSend(); + JPAKERound1Payload bobRound1Payload = bob.createRound1PayloadToSend(); + + System.out.println("************ Round 1 **************"); + System.out.println("Alice sends to Bob: "); + System.out.println("g^{x1}=" + aliceRound1Payload.getGx1().toString(16)); + System.out.println("g^{x2}=" + aliceRound1Payload.getGx2().toString(16)); + System.out.println("KP{x1}={" + aliceRound1Payload.getKnowledgeProofForX1()[0].toString(16) + "};{" + aliceRound1Payload.getKnowledgeProofForX1()[1].toString(16) + "}"); + System.out.println("KP{x2}={" + aliceRound1Payload.getKnowledgeProofForX2()[0].toString(16) + "};{" + aliceRound1Payload.getKnowledgeProofForX2()[1].toString(16) + "}"); + System.out.println(""); + + System.out.println("Bob sends to Alice: "); + System.out.println("g^{x3}=" + bobRound1Payload.getGx1().toString(16)); + System.out.println("g^{x4}=" + bobRound1Payload.getGx2().toString(16)); + System.out.println("KP{x3}={" + bobRound1Payload.getKnowledgeProofForX1()[0].toString(16) + "};{" + bobRound1Payload.getKnowledgeProofForX1()[1].toString(16) + "}"); + System.out.println("KP{x4}={" + bobRound1Payload.getKnowledgeProofForX2()[0].toString(16) + "};{" + bobRound1Payload.getKnowledgeProofForX2()[1].toString(16) + "}"); + System.out.println(""); + + /* + * Each participant must then validate the received payload for round 1 + */ + + alice.validateRound1PayloadReceived(bobRound1Payload); + System.out.println("Alice checks g^{x4}!=1: OK"); + System.out.println("Alice checks KP{x3}: OK"); + System.out.println("Alice checks KP{x4}: OK"); + System.out.println(""); + + bob.validateRound1PayloadReceived(aliceRound1Payload); + System.out.println("Bob checks g^{x2}!=1: OK"); + System.out.println("Bob checks KP{x1},: OK"); + System.out.println("Bob checks KP{x2},: OK"); + System.out.println(""); + + /* + * Round 2 + * + * Alice and Bob each generate a round 2 payload, and send it to each other. + */ + + JPAKERound2Payload aliceRound2Payload = alice.createRound2PayloadToSend(); + JPAKERound2Payload bobRound2Payload = bob.createRound2PayloadToSend(); + + System.out.println("************ Round 2 **************"); + System.out.println("Alice sends to Bob: "); + System.out.println("A=" + aliceRound2Payload.getA().toString(16)); + System.out.println("KP{x2*s}={" + aliceRound2Payload.getKnowledgeProofForX2s()[0].toString(16) + "},{" + aliceRound2Payload.getKnowledgeProofForX2s()[1].toString(16) + "}"); + System.out.println(""); + + System.out.println("Bob sends to Alice"); + System.out.println("B=" + bobRound2Payload.getA().toString(16)); + System.out.println("KP{x4*s}={" + bobRound2Payload.getKnowledgeProofForX2s()[0].toString(16) + "},{" + bobRound2Payload.getKnowledgeProofForX2s()[1].toString(16) + "}"); + System.out.println(""); + + /* + * Each participant must then validate the received payload for round 2 + */ + + alice.validateRound2PayloadReceived(bobRound2Payload); + System.out.println("Alice checks KP{x4*s}: OK\n"); + + bob.validateRound2PayloadReceived(aliceRound2Payload); + System.out.println("Bob checks KP{x2*s}: OK\n"); + + /* + * After round 2, each participant computes the keying material. + */ + + BigInteger aliceKeyingMaterial = alice.calculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.calculateKeyingMaterial(); + + System.out.println("********* After round 2 ***********"); + System.out.println("Alice computes key material \t K=" + aliceKeyingMaterial.toString(16)); + System.out.println("Bob computes key material \t K=" + bobKeyingMaterial.toString(16)); + System.out.println(); + + + /* + * You must derive a session key from the keying material applicable + * to whatever encryption algorithm you want to use. + */ + + BigInteger aliceKey = deriveSessionKey(aliceKeyingMaterial); + BigInteger bobKey = deriveSessionKey(bobKeyingMaterial); + + /* + * At this point, you can stop and use the session keys if you want. + * This is implicit key confirmation. + * + * If you want to explicitly confirm that the key material matches, + * you can continue on and perform round 3. + */ + + /* + * Round 3 + * + * Alice and Bob each generate a round 3 payload, and send it to each other. + */ + + JPAKERound3Payload aliceRound3Payload = alice.createRound3PayloadToSend(aliceKeyingMaterial); + JPAKERound3Payload bobRound3Payload = bob.createRound3PayloadToSend(bobKeyingMaterial); + + System.out.println("************ Round 3 **************"); + System.out.println("Alice sends to Bob: "); + System.out.println("MacTag=" + aliceRound3Payload.getMacTag().toString(16)); + System.out.println(""); + System.out.println("Bob sends to Alice: "); + System.out.println("MacTag=" + bobRound3Payload.getMacTag().toString(16)); + System.out.println(""); + + /* + * Each participant must then validate the received payload for round 3 + */ + + alice.validateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + System.out.println("Alice checks MacTag: OK\n"); + + bob.validateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + System.out.println("Bob checks MacTag: OK\n"); + + System.out.println(); + System.out.println("MacTags validated, therefore the keying material matches."); + } + + private static BigInteger deriveSessionKey(BigInteger keyingMaterial) + { + /* + * You should use a secure key derivation function (KDF) to derive the session key. + * + * For the purposes of this example, I'm just going to use a hash of the keying material. + */ + SHA256Digest digest = new SHA256Digest(); + + byte[] keyByteArray = keyingMaterial.toByteArray(); + + byte[] output = new byte[digest.getDigestSize()]; + + digest.update(keyByteArray, 0, keyByteArray.length); + + digest.doFinal(output, 0); + + return new BigInteger(output); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java new file mode 100644 index 00000000..2ef8dd2b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java @@ -0,0 +1,142 @@ +package org.bouncycastle.crypto.generators; + +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.ISO18033KDFParameters; +import org.bouncycastle.crypto.params.KDFParameters; +import org.bouncycastle.crypto.util.Pack; + +/** + * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO + * 18033 <br> + * This implementation is based on ISO 18033/P1363a. + */ +public class BaseKDFBytesGenerator implements DerivationFunction +{ + private int counterStart; + private Digest digest; + private byte[] shared; + private byte[] iv; + + /** + * Construct a KDF Parameters generator. + * <p> + * + * @param counterStart + * value of counter. + * @param digest + * the digest to be used as the source of derived keys. + */ + protected BaseKDFBytesGenerator(int counterStart, Digest digest) + { + this.counterStart = counterStart; + this.digest = digest; + } + + public void init(DerivationParameters param) + { + if (param instanceof KDFParameters) + { + KDFParameters p = (KDFParameters)param; + + shared = p.getSharedSecret(); + iv = p.getIV(); + } + else if (param instanceof ISO18033KDFParameters) + { + ISO18033KDFParameters p = (ISO18033KDFParameters)param; + + shared = p.getSeed(); + iv = null; + } + else + { + throw new IllegalArgumentException("KDF parameters required for KDF2Generator"); + } + } + + /** + * return the underlying digest. + */ + public Digest getDigest() + { + return digest; + } + + /** + * fill len bytes of the output buffer with bytes generated from the + * derivation function. + * + * @throws IllegalArgumentException + * if the size of the request will cause an overflow. + * @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"); + } + + long oBytes = len; + int outLen = digest.getDigestSize(); + + // + // this is at odds with the standard implementation, the + // maximum value should be hBits * (2^32 - 1) where hBits + // is the digest output size in bits. We can't have an + // array with a long index at the moment... + // + if (oBytes > ((2L << 32) - 1)) + { + throw new IllegalArgumentException("Output length too large"); + } + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = new byte[digest.getDigestSize()]; + + byte[] C = new byte[4]; + Pack.intToBigEndian(counterStart, C, 0); + + int counterBase = counterStart & ~0xFF; + + for (int i = 0; i < cThreshold; i++) + { + digest.update(shared, 0, shared.length); + digest.update(C, 0, C.length); + + if (iv != null) + { + digest.update(iv, 0, iv.length); + } + + digest.doFinal(dig, 0); + + if (len > outLen) + { + System.arraycopy(dig, 0, out, outOff, outLen); + outOff += outLen; + len -= outLen; + } + else + { + System.arraycopy(dig, 0, out, outOff, len); + } + + if (++C[3] == 0) + { + counterBase += 0x100; + Pack.intToBigEndian(counterBase, C, 0); + } + } + + digest.reset(); + + return (int)oBytes; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DESKeyGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DESKeyGenerator.java new file mode 100644 index 00000000..7111118b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DESKeyGenerator.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherKeyGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.DESParameters; + +public class DESKeyGenerator + extends CipherKeyGenerator +{ + /** + * initialise the key generator - if strength is set to zero + * the key generated will be 64 bits in size, otherwise + * strength can be 64 or 56 bits (if you don't count the parity bits). + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + super.init(param); + + if (strength == 0 || strength == (56 / 8)) + { + strength = DESParameters.DES_KEY_LENGTH; + } + else if (strength != DESParameters.DES_KEY_LENGTH) + { + throw new IllegalArgumentException("DES key must be " + + (DESParameters.DES_KEY_LENGTH * 8) + + " bits long."); + } + } + + public byte[] generateKey() + { + byte[] newKey = new byte[DESParameters.DES_KEY_LENGTH]; + + do + { + random.nextBytes(newKey); + + DESParameters.setOddParity(newKey); + } + while (DESParameters.isWeakKey(newKey, 0)); + + return newKey; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DESedeKeyGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DESedeKeyGenerator.java new file mode 100644 index 00000000..3cab983d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DESedeKeyGenerator.java @@ -0,0 +1,56 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.DESedeParameters; + +public class DESedeKeyGenerator + extends DESKeyGenerator +{ + /** + * initialise the key generator - if strength is set to zero + * the key generated will be 192 bits in size, otherwise + * strength can be 128 or 192 (or 112 or 168 if you don't count + * parity bits), depending on whether you wish to do 2-key or 3-key + * triple DES. + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + this.random = param.getRandom(); + this.strength = (param.getStrength() + 7) / 8; + + if (strength == 0 || strength == (168 / 8)) + { + strength = DESedeParameters.DES_EDE_KEY_LENGTH; + } + else if (strength == (112 / 8)) + { + strength = 2 * DESedeParameters.DES_KEY_LENGTH; + } + else if (strength != DESedeParameters.DES_EDE_KEY_LENGTH + && strength != (2 * DESedeParameters.DES_KEY_LENGTH)) + { + throw new IllegalArgumentException("DESede key must be " + + (DESedeParameters.DES_EDE_KEY_LENGTH * 8) + " or " + + (2 * 8 * DESedeParameters.DES_KEY_LENGTH) + + " bits long."); + } + } + + public byte[] generateKey() + { + byte[] newKey = new byte[strength]; + + do + { + random.nextBytes(newKey); + + DESedeParameters.setOddParity(newKey); + } + while (DESedeParameters.isWeakKey(newKey, 0, newKey.length)); + + return newKey; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java new file mode 100644 index 00000000..f93428ef --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; + +import java.math.BigInteger; + +/** + * a basic Diffie-Hellman key pair generator. + * + * This generates keys consistent for use with the basic algorithm for + * Diffie-Hellman. + */ +public class DHBasicKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private DHKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (DHKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE; + DHParameters dhp = param.getParameters(); + + BigInteger x = helper.calculatePrivate(dhp, param.getRandom()); + BigInteger y = helper.calculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java b/core/src/main/java/org/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java new file mode 100644 index 00000000..e0d86fc3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java @@ -0,0 +1,51 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.util.BigIntegers; + +class DHKeyGeneratorHelper +{ + static final DHKeyGeneratorHelper INSTANCE = new DHKeyGeneratorHelper(); + + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + private DHKeyGeneratorHelper() + { + } + + BigInteger calculatePrivate(DHParameters dhParams, SecureRandom random) + { + BigInteger p = dhParams.getP(); + int limit = dhParams.getL(); + + if (limit != 0) + { + return new BigInteger(limit, random).setBit(limit - 1); + } + + BigInteger min = TWO; + int m = dhParams.getM(); + if (m != 0) + { + min = ONE.shiftLeft(m - 1); + } + + BigInteger max = p.subtract(TWO); + BigInteger q = dhParams.getQ(); + if (q != null) + { + max = q.subtract(TWO); + } + + return BigIntegers.createRandomInRange(min, max, random); + } + + BigInteger calculatePublic(DHParameters dhParams, BigInteger x) + { + return dhParams.getG().modPow(x, dhParams.getP()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DHKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DHKeyPairGenerator.java new file mode 100644 index 00000000..d07ca80d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DHKeyPairGenerator.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; + +import java.math.BigInteger; + +/** + * a Diffie-Hellman key pair generator. + * + * This generates keys consistent for use in the MTI/A0 key agreement protocol + * as described in "Handbook of Applied Cryptography", Pages 516-519. + */ +public class DHKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private DHKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (DHKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE; + DHParameters dhp = param.getParameters(); + + BigInteger x = helper.calculatePrivate(dhp, param.getRandom()); + BigInteger y = helper.calculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DHParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DHParametersGenerator.java new file mode 100644 index 00000000..f5d42642 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DHParametersGenerator.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.params.DHParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +public class DHParametersGenerator +{ + private int size; + private int certainty; + private SecureRandom random; + + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * Initialise the parameters generator. + * + * @param size bit length for the prime p + * @param certainty level of certainty for the prime number tests + * @param random a source of randomness + */ + public void init( + int size, + int certainty, + SecureRandom random) + { + this.size = size; + this.certainty = certainty; + this.random = random; + } + + /** + * which generates the p and g values from the given parameters, + * returning the DHParameters object. + * <p> + * Note: can take a while... + */ + public DHParameters generateParameters() + { + // + // find a safe prime p where p = 2*q + 1, where p and q are prime. + // + BigInteger[] safePrimes = DHParametersHelper.generateSafePrimes(size, certainty, random); + + BigInteger p = safePrimes[0]; + BigInteger q = safePrimes[1]; + BigInteger g = DHParametersHelper.selectGenerator(p, q, random); + + return new DHParameters(p, g, q, TWO, null); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DHParametersHelper.java b/core/src/main/java/org/bouncycastle/crypto/generators/DHParametersHelper.java new file mode 100644 index 00000000..118bc9cc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DHParametersHelper.java @@ -0,0 +1,73 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.util.BigIntegers; + +class DHParametersHelper +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + /* + * Finds a pair of prime BigInteger's {p, q: p = 2q + 1} + * + * (see: Handbook of Applied Cryptography 4.86) + */ + static BigInteger[] generateSafePrimes(int size, int certainty, SecureRandom random) + { + BigInteger p, q; + int qLength = size - 1; + + for (;;) + { + q = new BigInteger(qLength, 2, random); + + // p <- 2q + 1 + p = q.shiftLeft(1).add(ONE); + + if (p.isProbablePrime(certainty) && (certainty <= 2 || q.isProbablePrime(certainty))) + { + break; + } + } + + return new BigInteger[] { p, q }; + } + + /* + * Select a high order element of the multiplicative group Zp* + * + * p and q must be s.t. p = 2*q + 1, where p and q are prime (see generateSafePrimes) + */ + static BigInteger selectGenerator(BigInteger p, BigInteger q, SecureRandom random) + { + BigInteger pMinusTwo = p.subtract(TWO); + BigInteger g; + + /* + * (see: Handbook of Applied Cryptography 4.80) + */ +// do +// { +// g = BigIntegers.createRandomInRange(TWO, pMinusTwo, random); +// } +// while (g.modPow(TWO, p).equals(ONE) || g.modPow(q, p).equals(ONE)); + + + /* + * RFC 2631 2.2.1.2 (and see: Handbook of Applied Cryptography 4.81) + */ + do + { + BigInteger h = BigIntegers.createRandomInRange(TWO, pMinusTwo, random); + + g = h.modPow(TWO, p); + } + while (g.equals(ONE)); + + + return g; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java new file mode 100644 index 00000000..93f49cfa --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java @@ -0,0 +1,61 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.DSAKeyGenerationParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * a DSA key pair generator. + * + * This generates DSA keys in line with the method described + * in <i>FIPS 186-3 B.1 FFC Key Pair Generation</i>. + */ +public class DSAKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private DSAKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (DSAKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DSAParameters dsaParams = param.getParameters(); + + BigInteger x = generatePrivateKey(dsaParams.getQ(), param.getRandom()); + BigInteger y = calculatePublicKey(dsaParams.getP(), dsaParams.getG(), x); + + return new AsymmetricCipherKeyPair( + new DSAPublicKeyParameters(y, dsaParams), + new DSAPrivateKeyParameters(x, dsaParams)); + } + + private static BigInteger generatePrivateKey(BigInteger q, SecureRandom random) + { + // TODO Prefer this method? (change test cases that used fixed random) + // B.1.1 Key Pair Generation Using Extra Random Bits +// BigInteger c = new BigInteger(q.bitLength() + 64, random); +// return c.mod(q.subtract(ONE)).add(ONE); + + // B.1.2 Key Pair Generation by Testing Candidates + return BigIntegers.createRandomInRange(ONE, q.subtract(ONE), random); + } + + private static BigInteger calculatePublicKey(BigInteger p, BigInteger g, BigInteger x) + { + return g.modPow(x, p); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java new file mode 100644 index 00000000..749b0cc5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java @@ -0,0 +1,387 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.params.DSAParameterGenerationParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAValidationParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; + +/** + * Generate suitable parameters for DSA, in line with FIPS 186-2, or FIPS 186-3. + */ +public class DSAParametersGenerator +{ + private Digest digest; + private int L, N; + private int certainty; + private SecureRandom random; + + private static final BigInteger ZERO = BigInteger.valueOf(0); + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + private boolean use186_3; + private int usageIndex; + + public DSAParametersGenerator() + { + this(new SHA1Digest()); + } + + public DSAParametersGenerator(Digest digest) + { + this.digest = digest; + } + + /** + * initialise the key generator. + * + * @param size size of the key (range 2^512 -> 2^1024 - 64 bit increments) + * @param certainty measure of robustness of prime (for FIPS 186-2 compliance this should be at least 80). + * @param random random byte source. + */ + public void init( + int size, + int certainty, + SecureRandom random) + { + this.use186_3 = false; + this.L = size; + this.N = getDefaultN(size); + this.certainty = certainty; + this.random = random; + } + + /** + * Initialise the key generator for DSA 2. + * <p> + * Use this init method if you need to generate parameters for DSA 2 keys. + * </p> + * + * @param params DSA 2 key generation parameters. + */ + public void init( + DSAParameterGenerationParameters params) + { + // TODO Should we enforce the minimum 'certainty' values as per C.3 Table C.1? + this.use186_3 = true; + this.L = params.getL(); + this.N = params.getN(); + this.certainty = params.getCertainty(); + this.random = params.getRandom(); + this.usageIndex = params.getUsageIndex(); + + if ((L < 1024 || L > 3072) || L % 1024 != 0) + { + throw new IllegalArgumentException("L values must be between 1024 and 3072 and a multiple of 1024"); + } + else if (L == 1024 && N != 160) + { + throw new IllegalArgumentException("N must be 160 for L = 1024"); + } + else if (L == 2048 && (N != 224 && N != 256)) + { + throw new IllegalArgumentException("N must be 224 or 256 for L = 2048"); + } + else if (L == 3072 && N != 256) + { + throw new IllegalArgumentException("N must be 256 for L = 3072"); + } + + if (digest.getDigestSize() * 8 < N) + { + throw new IllegalStateException("Digest output size too small for value of N"); + } + } + + /** + * which generates the p and g values from the given parameters, + * returning the DSAParameters object. + * <p> + * Note: can take a while... + */ + public DSAParameters generateParameters() + { + return (use186_3) + ? generateParameters_FIPS186_3() + : generateParameters_FIPS186_2(); + } + + private DSAParameters generateParameters_FIPS186_2() + { + byte[] seed = new byte[20]; + byte[] part1 = new byte[20]; + byte[] part2 = new byte[20]; + byte[] u = new byte[20]; + int n = (L - 1) / 160; + byte[] w = new byte[L / 8]; + + if (!(digest instanceof SHA1Digest)) + { + throw new IllegalStateException("can only use SHA-1 for generating FIPS 186-2 parameters"); + } + + for (;;) + { + random.nextBytes(seed); + + hash(digest, seed, part1); + System.arraycopy(seed, 0, part2, 0, seed.length); + inc(part2); + hash(digest, part2, part2); + + for (int i = 0; i != u.length; i++) + { + u[i] = (byte)(part1[i] ^ part2[i]); + } + + u[0] |= (byte)0x80; + u[19] |= (byte)0x01; + + BigInteger q = new BigInteger(1, u); + + if (!q.isProbablePrime(certainty)) + { + continue; + } + + byte[] offset = Arrays.clone(seed); + inc(offset); + + for (int counter = 0; counter < 4096; ++counter) + { + for (int k = 0; k < n; k++) + { + inc(offset); + hash(digest, offset, part1); + System.arraycopy(part1, 0, w, w.length - (k + 1) * part1.length, part1.length); + } + + inc(offset); + hash(digest, offset, part1); + System.arraycopy(part1, part1.length - ((w.length - (n) * part1.length)), w, 0, w.length - n * part1.length); + + w[0] |= (byte)0x80; + + BigInteger x = new BigInteger(1, w); + + BigInteger c = x.mod(q.shiftLeft(1)); + + BigInteger p = x.subtract(c.subtract(ONE)); + + if (p.bitLength() != L) + { + continue; + } + + if (p.isProbablePrime(certainty)) + { + BigInteger g = calculateGenerator_FIPS186_2(p, q, random); + + return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter)); + } + } + } + } + + private static BigInteger calculateGenerator_FIPS186_2(BigInteger p, BigInteger q, SecureRandom r) + { + BigInteger e = p.subtract(ONE).divide(q); + BigInteger pSub2 = p.subtract(TWO); + + for (;;) + { + BigInteger h = BigIntegers.createRandomInRange(TWO, pSub2, r); + BigInteger g = h.modPow(e, p); + if (g.bitLength() > 1) + { + return g; + } + } + } + + /** + * generate suitable parameters for DSA, in line with + * <i>FIPS 186-3 A.1 Generation of the FFC Primes p and q</i>. + */ + private DSAParameters generateParameters_FIPS186_3() + { +// A.1.1.2 Generation of the Probable Primes p and q Using an Approved Hash Function + // FIXME This should be configurable (digest size in bits must be >= N) + Digest d = digest; + int outlen = d.getDigestSize() * 8; + +// 1. Check that the (L, N) pair is in the list of acceptable (L, N pairs) (see Section 4.2). If +// the pair is not in the list, then return INVALID. + // Note: checked at initialisation + +// 2. If (seedlen < N), then return INVALID. + // FIXME This should be configurable (must be >= N) + int seedlen = N; + byte[] seed = new byte[seedlen / 8]; + +// 3. n = ceiling(L ⁄ outlen) – 1. + int n = (L - 1) / outlen; + +// 4. b = L – 1 – (n ∗ outlen). + int b = (L - 1) % outlen; + + byte[] output = new byte[d.getDigestSize()]; + for (;;) + { +// 5. Get an arbitrary sequence of seedlen bits as the domain_parameter_seed. + random.nextBytes(seed); + +// 6. U = Hash (domain_parameter_seed) mod 2^(N–1). + hash(d, seed, output); + + BigInteger U = new BigInteger(1, output).mod(ONE.shiftLeft(N - 1)); + +// 7. q = 2^(N–1) + U + 1 – ( U mod 2). + BigInteger q = ONE.shiftLeft(N - 1).add(U).add(ONE).subtract(U.mod(TWO)); + +// 8. Test whether or not q is prime as specified in Appendix C.3. + // TODO Review C.3 for primality checking + if (!q.isProbablePrime(certainty)) + { +// 9. If q is not a prime, then go to step 5. + continue; + } + +// 10. offset = 1. + // Note: 'offset' value managed incrementally + byte[] offset = Arrays.clone(seed); + +// 11. For counter = 0 to (4L – 1) do + int counterLimit = 4 * L; + for (int counter = 0; counter < counterLimit; ++counter) + { +// 11.1 For j = 0 to n do +// Vj = Hash ((domain_parameter_seed + offset + j) mod 2^seedlen). +// 11.2 W = V0 + (V1 ∗ 2^outlen) + ... + (V^(n–1) ∗ 2^((n–1) ∗ outlen)) + ((Vn mod 2^b) ∗ 2^(n ∗ outlen)). + // TODO Assemble w as a byte array + BigInteger W = ZERO; + for (int j = 0, exp = 0; j <= n; ++j, exp += outlen) + { + inc(offset); + hash(d, offset, output); + + BigInteger Vj = new BigInteger(1, output); + if (j == n) + { + Vj = Vj.mod(ONE.shiftLeft(b)); + } + + W = W.add(Vj.shiftLeft(exp)); + } + +// 11.3 X = W + 2^(L–1). Comment: 0 ≤ W < 2L–1; hence, 2L–1 ≤ X < 2L. + BigInteger X = W.add(ONE.shiftLeft(L - 1)); + +// 11.4 c = X mod 2q. + BigInteger c = X.mod(q.shiftLeft(1)); + +// 11.5 p = X - (c - 1). Comment: p ≡ 1 (mod 2q). + BigInteger p = X.subtract(c.subtract(ONE)); + +// 11.6 If (p < 2^(L - 1)), then go to step 11.9 + if (p.bitLength() != L) + { + continue; + } + +// 11.7 Test whether or not p is prime as specified in Appendix C.3. + // TODO Review C.3 for primality checking + if (p.isProbablePrime(certainty)) + { +// 11.8 If p is determined to be prime, then return VALID and the values of p, q and +// (optionally) the values of domain_parameter_seed and counter. + if (usageIndex >= 0) + { + BigInteger g = calculateGenerator_FIPS186_3_Verifiable(d, p, q, seed, usageIndex); + if (g != null) + { + return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter, usageIndex)); + } + } + + BigInteger g = calculateGenerator_FIPS186_3_Unverifiable(p, q, random); + + return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter)); + } + +// 11.9 offset = offset + n + 1. Comment: Increment offset; then, as part of +// the loop in step 11, increment counter; if +// counter < 4L, repeat steps 11.1 through 11.8. + // Note: 'offset' value already incremented in inner loop + } +// 12. Go to step 5. + } + } + + private static BigInteger calculateGenerator_FIPS186_3_Unverifiable(BigInteger p, BigInteger q, + SecureRandom r) + { + return calculateGenerator_FIPS186_2(p, q, r); + } + + private static BigInteger calculateGenerator_FIPS186_3_Verifiable(Digest d, BigInteger p, BigInteger q, + byte[] seed, int index) + { +// A.2.3 Verifiable Canonical Generation of the Generator g + BigInteger e = p.subtract(ONE).divide(q); + byte[] ggen = Hex.decode("6767656E"); + + // 7. U = domain_parameter_seed || "ggen" || index || count. + byte[] U = new byte[seed.length + ggen.length + 1 + 2]; + System.arraycopy(seed, 0, U, 0, seed.length); + System.arraycopy(ggen, 0, U, seed.length, ggen.length); + U[U.length - 3] = (byte)index; + + byte[] w = new byte[d.getDigestSize()]; + for (int count = 1; count < (1 << 16); ++count) + { + inc(U); + hash(d, U, w); + BigInteger W = new BigInteger(1, w); + BigInteger g = W.modPow(e, p); + if (g.compareTo(TWO) >= 0) + { + return g; + } + } + + return null; + } + + private static void hash(Digest d, byte[] input, byte[] output) + { + d.update(input, 0, input.length); + d.doFinal(output, 0); + } + + private static int getDefaultN(int L) + { + return L > 1024 ? 256 : 160; + } + + private static void inc(byte[] buf) + { + for (int i = buf.length - 1; i >= 0; --i) + { + byte b = (byte)((buf[i] + 1) & 0xff); + buf[i] = b; + + if (b != 0) + { + break; + } + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/DSTU4145KeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/DSTU4145KeyPairGenerator.java new file mode 100644 index 00000000..3f931b2c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/DSTU4145KeyPairGenerator.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; + +public class DSTU4145KeyPairGenerator + extends ECKeyPairGenerator +{ + public AsymmetricCipherKeyPair generateKeyPair() + { + AsymmetricCipherKeyPair pair = super.generateKeyPair(); + + ECPublicKeyParameters pub = (ECPublicKeyParameters)pair.getPublic(); + ECPrivateKeyParameters priv = (ECPrivateKeyParameters)pair.getPrivate(); + + pub = new ECPublicKeyParameters(pub.getQ().negate(), pub.getParameters()); + + return new AsymmetricCipherKeyPair(pub, priv); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java new file mode 100644 index 00000000..d77bd747 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +public class ECKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator, ECConstants +{ + ECDomainParameters params; + SecureRandom random; + + public void init( + KeyGenerationParameters param) + { + ECKeyGenerationParameters ecP = (ECKeyGenerationParameters)param; + + this.random = ecP.getRandom(); + this.params = ecP.getDomainParameters(); + } + + /** + * Given the domain parameters this routine generates an EC key + * pair in accordance with X9.62 section 5.2.1 pages 26, 27. + */ + public AsymmetricCipherKeyPair generateKeyPair() + { + BigInteger n = params.getN(); + int nBitLength = n.bitLength(); + BigInteger d; + + do + { + d = new BigInteger(nBitLength, random); + } + while (d.equals(ZERO) || (d.compareTo(n) >= 0)); + + ECPoint Q = params.getG().multiply(d); + + return new AsymmetricCipherKeyPair( + new ECPublicKeyParameters(Q, params), + new ECPrivateKeyParameters(d, params)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java new file mode 100644 index 00000000..f23b6976 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java @@ -0,0 +1,44 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.ElGamalKeyGenerationParameters; +import org.bouncycastle.crypto.params.ElGamalParameters; +import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters; + +/** + * a ElGamal key pair generator. + * <p> + * This generates keys consistent for use with ElGamal as described in + * page 164 of "Handbook of Applied Cryptography". + */ +public class ElGamalKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private ElGamalKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (ElGamalKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE; + ElGamalParameters egp = param.getParameters(); + DHParameters dhp = new DHParameters(egp.getP(), egp.getG(), null, egp.getL()); + + BigInteger x = helper.calculatePrivate(dhp, param.getRandom()); + BigInteger y = helper.calculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new ElGamalPublicKeyParameters(y, egp), + new ElGamalPrivateKeyParameters(x, egp)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/ElGamalParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/ElGamalParametersGenerator.java new file mode 100644 index 00000000..21e8c2a0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/ElGamalParametersGenerator.java @@ -0,0 +1,43 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.params.ElGamalParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +public class ElGamalParametersGenerator +{ + private int size; + private int certainty; + private SecureRandom random; + + public void init( + int size, + int certainty, + SecureRandom random) + { + this.size = size; + this.certainty = certainty; + this.random = random; + } + + /** + * which generates the p and g values from the given parameters, + * returning the ElGamalParameters object. + * <p> + * Note: can take a while... + */ + public ElGamalParameters generateParameters() + { + // + // find a safe prime p where p = 2*q + 1, where p and q are prime. + // + BigInteger[] safePrimes = DHParametersHelper.generateSafePrimes(size, certainty, random); + + BigInteger p = safePrimes[0]; + BigInteger q = safePrimes[1]; + BigInteger g = DHParametersHelper.selectGenerator(p, q, random); + + return new ElGamalParameters(p, g); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/EphemeralKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/EphemeralKeyPairGenerator.java new file mode 100644 index 00000000..1004f23e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/EphemeralKeyPairGenerator.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.EphemeralKeyPair; +import org.bouncycastle.crypto.KeyEncoder; + +public class EphemeralKeyPairGenerator +{ + private AsymmetricCipherKeyPairGenerator gen; + private KeyEncoder keyEncoder; + + public EphemeralKeyPairGenerator(AsymmetricCipherKeyPairGenerator gen, KeyEncoder keyEncoder) + { + this.gen = gen; + this.keyEncoder = keyEncoder; + } + + public EphemeralKeyPair generate() + { + AsymmetricCipherKeyPair eph = gen.generateKeyPair(); + + // Encode the ephemeral public key + return new EphemeralKeyPair(eph, keyEncoder); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java new file mode 100644 index 00000000..3e13c212 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java @@ -0,0 +1,57 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.GOST3410KeyGenerationParameters; +import org.bouncycastle.crypto.params.GOST3410Parameters; +import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters; +import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * a GOST3410 key pair generator. + * This generates GOST3410 keys in line with the method described + * in GOST R 34.10-94. + */ +public class GOST3410KeyPairGenerator + implements AsymmetricCipherKeyPairGenerator + { + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private GOST3410KeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (GOST3410KeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + BigInteger p, q, a, x, y; + GOST3410Parameters GOST3410Params = param.getParameters(); + SecureRandom random = param.getRandom(); + + q = GOST3410Params.getQ(); + p = GOST3410Params.getP(); + a = GOST3410Params.getA(); + + do + { + x = new BigInteger(256, random); + } + while (x.equals(ZERO) || x.compareTo(q) >= 0); + + // + // calculate the public key. + // + y = a.modPow(x, p); + + return new AsymmetricCipherKeyPair( + new GOST3410PublicKeyParameters(y, GOST3410Params), + new GOST3410PrivateKeyParameters(x, GOST3410Params)); + } + } diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java new file mode 100644 index 00000000..1c7cecf8 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java @@ -0,0 +1,541 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.params.GOST3410Parameters; +import org.bouncycastle.crypto.params.GOST3410ValidationParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * generate suitable parameters for GOST3410. + */ +public class GOST3410ParametersGenerator +{ + private int size; + private int typeproc; + private SecureRandom init_random; + + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * initialise the key generator. + * + * @param size size of the key + * @param typeproc type procedure A,B = 1; A',B' - else + * @param random random byte source. + */ + public void init( + int size, + int typeproc, + SecureRandom random) + { + this.size = size; + this.typeproc = typeproc; + this.init_random = random; + } + + //Procedure A + private int procedure_A(int x0, int c, BigInteger[] pq, int size) + { + //Verify and perform condition: 0<x<2^16; 0<c<2^16; c - odd. + while(x0<0 || x0>65536) + { + x0 = init_random.nextInt()/32768; + } + + while((c<0 || c>65536) || (c/2==0)) + { + c = init_random.nextInt()/32768 + 1; + } + + BigInteger C = new BigInteger(Integer.toString(c)); + BigInteger constA16 = new BigInteger("19381"); + + //step1 + BigInteger[] y = new BigInteger[1]; // begin length = 1 + y[0] = new BigInteger(Integer.toString(x0)); + + //step 2 + int[] t = new int[1]; // t - orders; begin length = 1 + t[0] = size; + int s = 0; + for (int i=0; t[i]>=17; i++) + { + // extension array t + int tmp_t[] = new int[t.length + 1]; /////////////// + System.arraycopy(t,0,tmp_t,0,t.length); // extension + t = new int[tmp_t.length]; // array t + System.arraycopy(tmp_t, 0, t, 0, tmp_t.length); /////////////// + + t[i+1] = t[i]/2; + s = i+1; + } + + //step3 + BigInteger p[] = new BigInteger[s+1]; + p[s] = new BigInteger("8003",16); //set min prime number length 16 bit + + int m = s-1; //step4 + + for (int i=0; i<s; i++) + { + int rm = t[m]/16; //step5 + + step6: for(;;) + { + //step 6 + BigInteger tmp_y[] = new BigInteger[y.length]; //////////////// + System.arraycopy(y,0,tmp_y,0,y.length); // extension + y = new BigInteger[rm+1]; // array y + System.arraycopy(tmp_y,0,y,0,tmp_y.length); //////////////// + + for (int j=0; j<rm; j++) + { + y[j+1] = (y[j].multiply(constA16).add(C)).mod(TWO.pow(16)); + } + + //step 7 + BigInteger Ym = new BigInteger("0"); + for (int j=0; j<rm; j++) + { + Ym = Ym.add(y[j].multiply(TWO.pow(16*j))); + } + + y[0] = y[rm]; //step 8 + + //step 9 + BigInteger N = TWO.pow(t[m]-1).divide(p[m+1]). + add((TWO.pow(t[m]-1).multiply(Ym)). + divide(p[m+1].multiply(TWO.pow(16*rm)))); + + if (N.mod(TWO).compareTo(ONE)==0) + { + N = N.add(ONE); + } + + int k = 0; //step 10 + + step11: for(;;) + { + //step 11 + p[m] = p[m+1].multiply(N.add(BigInteger.valueOf(k))).add(ONE); + + if (p[m].compareTo(TWO.pow(t[m]))==1) + { + continue step6; //step 12 + } + + //step13 + if ((TWO.modPow(p[m+1].multiply(N.add(BigInteger.valueOf(k))),p[m]).compareTo(ONE)==0) && + (TWO.modPow(N.add(BigInteger.valueOf(k)),p[m]).compareTo(ONE)!=0)) + { + m -= 1; + break; + } + else + { + k += 2; + continue step11; + } + } + + if (m>=0) + { + break; //step 14 + } + else + { + pq[0] = p[0]; + pq[1] = p[1]; + return y[0].intValue(); //return for procedure B step 2 + } + } + } + return y[0].intValue(); + } + + //Procedure A' + private long procedure_Aa(long x0, long c, BigInteger[] pq, int size) + { + //Verify and perform condition: 0<x<2^32; 0<c<2^32; c - odd. + while(x0<0 || x0>4294967296L) + { + x0 = init_random.nextInt()*2; + } + + while((c<0 || c>4294967296L) || (c/2==0)) + { + c = init_random.nextInt()*2+1; + } + + BigInteger C = new BigInteger(Long.toString(c)); + BigInteger constA32 = new BigInteger("97781173"); + + //step1 + BigInteger[] y = new BigInteger[1]; // begin length = 1 + y[0] = new BigInteger(Long.toString(x0)); + + //step 2 + int[] t = new int[1]; // t - orders; begin length = 1 + t[0] = size; + int s = 0; + for (int i=0; t[i]>=33; i++) + { + // extension array t + int tmp_t[] = new int[t.length + 1]; /////////////// + System.arraycopy(t,0,tmp_t,0,t.length); // extension + t = new int[tmp_t.length]; // array t + System.arraycopy(tmp_t, 0, t, 0, tmp_t.length); /////////////// + + t[i+1] = t[i]/2; + s = i+1; + } + + //step3 + BigInteger p[] = new BigInteger[s+1]; + p[s] = new BigInteger("8000000B",16); //set min prime number length 32 bit + + int m = s-1; //step4 + + for (int i=0; i<s; i++) + { + int rm = t[m]/32; //step5 + + step6: for(;;) + { + //step 6 + BigInteger tmp_y[] = new BigInteger[y.length]; //////////////// + System.arraycopy(y,0,tmp_y,0,y.length); // extension + y = new BigInteger[rm+1]; // array y + System.arraycopy(tmp_y,0,y,0,tmp_y.length); //////////////// + + for (int j=0; j<rm; j++) + { + y[j+1] = (y[j].multiply(constA32).add(C)).mod(TWO.pow(32)); + } + + //step 7 + BigInteger Ym = new BigInteger("0"); + for (int j=0; j<rm; j++) + { + Ym = Ym.add(y[j].multiply(TWO.pow(32*j))); + } + + y[0] = y[rm]; //step 8 + + //step 9 + BigInteger N = TWO.pow(t[m]-1).divide(p[m+1]). + add((TWO.pow(t[m]-1).multiply(Ym)). + divide(p[m+1].multiply(TWO.pow(32*rm)))); + + if (N.mod(TWO).compareTo(ONE)==0) + { + N = N.add(ONE); + } + + int k = 0; //step 10 + + step11: for(;;) + { + //step 11 + p[m] = p[m+1].multiply(N.add(BigInteger.valueOf(k))).add(ONE); + + if (p[m].compareTo(TWO.pow(t[m]))==1) + { + continue step6; //step 12 + } + + //step13 + if ((TWO.modPow(p[m+1].multiply(N.add(BigInteger.valueOf(k))),p[m]).compareTo(ONE)==0) && + (TWO.modPow(N.add(BigInteger.valueOf(k)),p[m]).compareTo(ONE)!=0)) + { + m -= 1; + break; + } + else + { + k += 2; + continue step11; + } + } + + if (m>=0) + { + break; //step 14 + } + else + { + pq[0] = p[0]; + pq[1] = p[1]; + return y[0].longValue(); //return for procedure B' step 2 + } + } + } + return y[0].longValue(); + } + + //Procedure B + private void procedure_B(int x0, int c, BigInteger[] pq) + { + //Verify and perform condition: 0<x<2^16; 0<c<2^16; c - odd. + while(x0<0 || x0>65536) + { + x0 = init_random.nextInt()/32768; + } + + while((c<0 || c>65536) || (c/2==0)) + { + c = init_random.nextInt()/32768 + 1; + } + + BigInteger [] qp = new BigInteger[2]; + BigInteger q = null, Q = null, p = null; + BigInteger C = new BigInteger(Integer.toString(c)); + BigInteger constA16 = new BigInteger("19381"); + + //step1 + x0 = procedure_A(x0, c, qp, 256); + q = qp[0]; + + //step2 + x0 = procedure_A(x0, c, qp, 512); + Q = qp[0]; + + BigInteger[] y = new BigInteger[65]; + y[0] = new BigInteger(Integer.toString(x0)); + + int tp = 1024; + + step3: for(;;) + { + //step 3 + for (int j=0; j<64; j++) + { + y[j+1] = (y[j].multiply(constA16).add(C)).mod(TWO.pow(16)); + } + + //step 4 + BigInteger Y = new BigInteger("0"); + + for (int j=0; j<64; j++) + { + Y = Y.add(y[j].multiply(TWO.pow(16*j))); + } + + y[0] = y[64]; //step 5 + + //step 6 + BigInteger N = TWO.pow(tp-1).divide(q.multiply(Q)). + add((TWO.pow(tp-1).multiply(Y)). + divide(q.multiply(Q).multiply(TWO.pow(1024)))); + + if (N.mod(TWO).compareTo(ONE)==0) + { + N = N.add(ONE); + } + + int k = 0; //step 7 + + step8: for(;;) + { + //step 11 + p = q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))).add(ONE); + + if (p.compareTo(TWO.pow(tp))==1) + { + continue step3; //step 9 + } + + //step10 + if ((TWO.modPow(q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)==0) && + (TWO.modPow(q.multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)!=0)) + { + pq[0] = p; + pq[1] = q; + return; + } + else + { + k += 2; + continue step8; + } + } + } + } + + //Procedure B' + private void procedure_Bb(long x0, long c, BigInteger[] pq) + { + //Verify and perform condition: 0<x<2^32; 0<c<2^32; c - odd. + while(x0<0 || x0>4294967296L) + { + x0 = init_random.nextInt()*2; + } + + while((c<0 || c>4294967296L) || (c/2==0)) + { + c = init_random.nextInt()*2+1; + } + + BigInteger [] qp = new BigInteger[2]; + BigInteger q = null, Q = null, p = null; + BigInteger C = new BigInteger(Long.toString(c)); + BigInteger constA32 = new BigInteger("97781173"); + + //step1 + x0 = procedure_Aa(x0, c, qp, 256); + q = qp[0]; + + //step2 + x0 = procedure_Aa(x0, c, qp, 512); + Q = qp[0]; + + BigInteger[] y = new BigInteger[33]; + y[0] = new BigInteger(Long.toString(x0)); + + int tp = 1024; + + step3: for(;;) + { + //step 3 + for (int j=0; j<32; j++) + { + y[j+1] = (y[j].multiply(constA32).add(C)).mod(TWO.pow(32)); + } + + //step 4 + BigInteger Y = new BigInteger("0"); + for (int j=0; j<32; j++) + { + Y = Y.add(y[j].multiply(TWO.pow(32*j))); + } + + y[0] = y[32]; //step 5 + + //step 6 + BigInteger N = TWO.pow(tp-1).divide(q.multiply(Q)). + add((TWO.pow(tp-1).multiply(Y)). + divide(q.multiply(Q).multiply(TWO.pow(1024)))); + + if (N.mod(TWO).compareTo(ONE)==0) + { + N = N.add(ONE); + } + + int k = 0; //step 7 + + step8: for(;;) + { + //step 11 + p = q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))).add(ONE); + + if (p.compareTo(TWO.pow(tp))==1) + { + continue step3; //step 9 + } + + //step10 + if ((TWO.modPow(q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)==0) && + (TWO.modPow(q.multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)!=0)) + { + pq[0] = p; + pq[1] = q; + return; + } + else + { + k += 2; + continue step8; + } + } + } + } + + + /** + * Procedure C + * procedure generates the a value from the given p,q, + * returning the a value. + */ + private BigInteger procedure_C(BigInteger p, BigInteger q) + { + BigInteger pSub1 = p.subtract(ONE); + BigInteger pSub1DivQ = pSub1.divide(q); + int length = p.bitLength(); + + for(;;) + { + BigInteger d = new BigInteger(length, init_random); + + // 1 < d < p-1 + if (d.compareTo(ONE) > 0 && d.compareTo(pSub1) < 0) + { + BigInteger a = d.modPow(pSub1DivQ, p); + + if (a.compareTo(ONE) != 0) + { + return a; + } + } + } + } + + /** + * which generates the p , q and a values from the given parameters, + * returning the GOST3410Parameters object. + */ + public GOST3410Parameters generateParameters() + { + BigInteger [] pq = new BigInteger[2]; + BigInteger q = null, p = null, a = null; + + int x0, c; + long x0L, cL; + + if (typeproc==1) + { + x0 = init_random.nextInt(); + c = init_random.nextInt(); + + switch(size) + { + case 512: + procedure_A(x0, c, pq, 512); + break; + case 1024: + procedure_B(x0, c, pq); + break; + default: + throw new IllegalArgumentException("Ooops! key size 512 or 1024 bit."); + } + p = pq[0]; q = pq[1]; + a = procedure_C(p, q); + //System.out.println("p:"+p.toString(16)+"\n"+"q:"+q.toString(16)+"\n"+"a:"+a.toString(16)); + //System.out.println("p:"+p+"\n"+"q:"+q+"\n"+"a:"+a); + return new GOST3410Parameters(p, q, a, new GOST3410ValidationParameters(x0, c)); + } + else + { + x0L = init_random.nextLong(); + cL = init_random.nextLong(); + + switch(size) + { + case 512: + procedure_Aa(x0L, cL, pq, 512); + break; + case 1024: + procedure_Bb(x0L, cL, pq); + break; + default: + throw new IllegalStateException("Ooops! key size 512 or 1024 bit."); + } + p = pq[0]; q = pq[1]; + a = procedure_C(p, q); + //System.out.println("p:"+p.toString(16)+"\n"+"q:"+q.toString(16)+"\n"+"a:"+a.toString(16)); + //System.out.println("p:"+p+"\n"+"q:"+q+"\n"+"a:"+a); + return new GOST3410Parameters(p, q, a, new GOST3410ValidationParameters(x0L, cL)); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java new file mode 100644 index 00000000..8e93e6b0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java @@ -0,0 +1,161 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.HKDFParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) implemented + * according to IETF RFC 5869, May 2010 as specified by H. Krawczyk, IBM + * Research & P. Eronen, Nokia. It uses a HMac internally to compute de OKM + * (output keying material) and is likely to have better security properties + * than KDF's based on just a hash function. + */ +public class HKDFBytesGenerator + implements DerivationFunction +{ + + private HMac hMacHash; + private int hashLen; + + private byte[] info; + private byte[] currentT; + + private int generatedBytes; + + /** + * Creates a HKDFBytesGenerator based on the given hash function. + * + * @param hash the digest to be used as the source of generatedBytes bytes + */ + public HKDFBytesGenerator(Digest hash) + { + this.hMacHash = new HMac(hash); + this.hashLen = hash.getDigestSize(); + } + + public void init(DerivationParameters param) + { + if (!(param instanceof HKDFParameters)) + { + throw new IllegalArgumentException( + "HKDF parameters required for HKDFBytesGenerator"); + } + + HKDFParameters params = (HKDFParameters)param; + if (params.skipExtract()) + { + // use IKM directly as PRK + hMacHash.init(new KeyParameter(params.getIKM())); + } + else + { + hMacHash.init(extract(params.getSalt(), params.getIKM())); + } + + info = params.getInfo(); + + generatedBytes = 0; + currentT = new byte[hashLen]; + } + + /** + * Performs the extract part of the key derivation function. + * + * @param salt the salt to use + * @param ikm the input keying material + * @return the PRK as KeyParameter + */ + private KeyParameter extract(byte[] salt, byte[] ikm) + { + hMacHash.init(new KeyParameter(ikm)); + if (salt == null) + { + // TODO check if hashLen is indeed same as HMAC size + hMacHash.init(new KeyParameter(new byte[hashLen])); + } + else + { + hMacHash.init(new KeyParameter(salt)); + } + + hMacHash.update(ikm, 0, ikm.length); + + byte[] prk = new byte[hashLen]; + hMacHash.doFinal(prk, 0); + return new KeyParameter(prk); + } + + /** + * Performs the expand part of the key derivation function, using currentT + * as input and output buffer. + * + * @throws DataLengthException if the total number of bytes generated is larger than the one + * specified by RFC 5869 (255 * HashLen) + */ + private void expandNext() + throws DataLengthException + { + int n = generatedBytes / hashLen + 1; + if (n >= 256) + { + throw new DataLengthException( + "HKDF cannot generate more than 255 blocks of HashLen size"); + } + // special case for T(0): T(0) is empty, so no update + if (generatedBytes != 0) + { + hMacHash.update(currentT, 0, hashLen); + } + hMacHash.update(info, 0, info.length); + hMacHash.update((byte)n); + hMacHash.doFinal(currentT, 0); + } + + public Digest getDigest() + { + return hMacHash.getUnderlyingDigest(); + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + + if (generatedBytes + len > 255 * hashLen) + { + throw new DataLengthException( + "HKDF may only be used for 255 * HashLen bytes of output"); + } + + if (generatedBytes % hashLen == 0) + { + expandNext(); + } + + // copy what is left in the currentT (1..hash + int toGenerate = len; + int posInT = generatedBytes % hashLen; + int leftInT = hashLen - generatedBytes % hashLen; + int toCopy = Math.min(leftInT, toGenerate); + System.arraycopy(currentT, posInT, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + + while (toGenerate > 0) + { + expandNext(); + toCopy = Math.min(hashLen, toGenerate); + System.arraycopy(currentT, 0, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + } + + return len; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/KDF1BytesGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/KDF1BytesGenerator.java new file mode 100644 index 00000000..7789b7b5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/KDF1BytesGenerator.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.Digest; + +/** + * KDF1 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 + * <br> + * This implementation is based on ISO 18033/IEEE P1363a. + */ +public class KDF1BytesGenerator + extends BaseKDFBytesGenerator +{ + /** + * Construct a KDF1 byte generator. + * <p> + * @param digest the digest to be used as the source of derived keys. + */ + public KDF1BytesGenerator( + Digest digest) + { + super(0, digest); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java new file mode 100644 index 00000000..ac0c64a4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.Digest; + +/** + * KDF2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 + * <br> + * This implementation is based on IEEE P1363/ISO 18033. + */ +public class KDF2BytesGenerator + extends BaseKDFBytesGenerator +{ + /** + * Construct a KDF2 bytes generator. Generates key material + * according to IEEE P1363 or ISO 18033 depending on the initialisation. + * <p> + * @param digest the digest to be used as the source of derived keys. + */ + public KDF2BytesGenerator( + Digest digest) + { + super(1, digest); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/MGF1BytesGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/MGF1BytesGenerator.java new file mode 100644 index 00000000..e93c0d73 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/MGF1BytesGenerator.java @@ -0,0 +1,114 @@ +package org.bouncycastle.crypto.generators; + +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.MGFParameters; + +/** + * Generator for MGF1 as defined in PKCS 1v2 + */ +public class MGF1BytesGenerator + implements DerivationFunction +{ + private Digest digest; + private byte[] seed; + private int hLen; + + /** + * @param digest the digest to be used as the source of generated bytes + */ + public MGF1BytesGenerator( + Digest digest) + { + this.digest = digest; + this.hLen = digest.getDigestSize(); + } + + public void init( + DerivationParameters param) + { + if (!(param instanceof MGFParameters)) + { + throw new IllegalArgumentException("MGF parameters required for MGF1Generator"); + } + + MGFParameters p = (MGFParameters)param; + + seed = p.getSeed(); + } + + /** + * 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 = 0; + + digest.reset(); + + if (len > hLen) + { + do + { + ItoOSP(counter, C); + + digest.update(seed, 0, seed.length); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, out, outOff + counter * hLen, hLen); + } + while (++counter < (len / hLen)); + } + + if ((counter * hLen) < len) + { + ItoOSP(counter, C); + + digest.update(seed, 0, seed.length); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, out, outOff + counter * hLen, len - (counter * hLen)); + } + + return len; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java new file mode 100644 index 00000000..ceb39407 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java @@ -0,0 +1,365 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.NaccacheSternKeyGenerationParameters; +import org.bouncycastle.crypto.params.NaccacheSternKeyParameters; +import org.bouncycastle.crypto.params.NaccacheSternPrivateKeyParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Vector; + +/** + * Key generation parameters for NaccacheStern cipher. For details on this cipher, please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + + private static int[] smallPrimes = + { + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, + 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, + 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, + 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, + 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, + 541, 547, 557 + }; + + private NaccacheSternKeyGenerationParameters param; + + private static final BigInteger ONE = BigInteger.valueOf(1); // JDK 1.1 compatibility + + /* + * (non-Javadoc) + * + * @see org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator#init(org.bouncycastle.crypto.KeyGenerationParameters) + */ + public void init(KeyGenerationParameters param) + { + this.param = (NaccacheSternKeyGenerationParameters)param; + } + + /* + * (non-Javadoc) + * + * @see org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator#generateKeyPair() + */ + public AsymmetricCipherKeyPair generateKeyPair() + { + int strength = param.getStrength(); + SecureRandom rand = param.getRandom(); + int certainty = param.getCertainty(); + boolean debug = param.isDebug(); + + if (debug) + { + System.out.println("Fetching first " + param.getCntSmallPrimes() + " primes."); + } + + Vector smallPrimes = findFirstPrimes(param.getCntSmallPrimes()); + smallPrimes = permuteList(smallPrimes, rand); + + BigInteger u = ONE; + BigInteger v = ONE; + + for (int i = 0; i < smallPrimes.size() / 2; i++) + { + u = u.multiply((BigInteger)smallPrimes.elementAt(i)); + } + for (int i = smallPrimes.size() / 2; i < smallPrimes.size(); i++) + { + v = v.multiply((BigInteger)smallPrimes.elementAt(i)); + } + + BigInteger sigma = u.multiply(v); + + // n = (2 a u p_ + 1 ) ( 2 b v q_ + 1) + // -> |n| = strength + // |2| = 1 in bits + // -> |a| * |b| = |n| - |u| - |v| - |p_| - |q_| - |2| -|2| + // remainingStrength = strength - sigma.bitLength() - p_.bitLength() - + // q_.bitLength() - 1 -1 + int remainingStrength = strength - sigma.bitLength() - 48; + BigInteger a = generatePrime(remainingStrength / 2 + 1, certainty, rand); + BigInteger b = generatePrime(remainingStrength / 2 + 1, certainty, rand); + + BigInteger p_; + BigInteger q_; + BigInteger p; + BigInteger q; + long tries = 0; + if (debug) + { + System.out.println("generating p and q"); + } + + BigInteger _2au = a.multiply(u).shiftLeft(1); + BigInteger _2bv = b.multiply(v).shiftLeft(1); + + for (;;) + { + tries++; + + p_ = generatePrime(24, certainty, rand); + + p = p_.multiply(_2au).add(ONE); + + if (!p.isProbablePrime(certainty)) + { + continue; + } + + for (;;) + { + q_ = generatePrime(24, certainty, rand); + + if (p_.equals(q_)) + { + continue; + } + + q = q_.multiply(_2bv).add(ONE); + + if (q.isProbablePrime(certainty)) + { + break; + } + } + + if (!sigma.gcd(p_.multiply(q_)).equals(ONE)) + { + // System.out.println("sigma.gcd(p_.mult(q_)) != 1!\n p_: " + p_ + // +"\n q_: "+ q_ ); + continue; + } + + if (p.multiply(q).bitLength() < strength) + { + if (debug) + { + System.out.println("key size too small. Should be " + strength + " but is actually " + + p.multiply(q).bitLength()); + } + continue; + } + break; + } + + if (debug) + { + System.out.println("needed " + tries + " tries to generate p and q."); + } + + BigInteger n = p.multiply(q); + BigInteger phi_n = p.subtract(ONE).multiply(q.subtract(ONE)); + BigInteger g; + tries = 0; + if (debug) + { + System.out.println("generating g"); + } + for (;;) + { + + Vector gParts = new Vector(); + for (int ind = 0; ind != smallPrimes.size(); ind++) + { + BigInteger i = (BigInteger)smallPrimes.elementAt(ind); + BigInteger e = phi_n.divide(i); + + for (;;) + { + tries++; + g = new BigInteger(strength, certainty, rand); + if (g.modPow(e, n).equals(ONE)) + { + continue; + } + gParts.addElement(g); + break; + } + } + g = ONE; + for (int i = 0; i < smallPrimes.size(); i++) + { + g = g.multiply(((BigInteger)gParts.elementAt(i)).modPow(sigma.divide((BigInteger)smallPrimes.elementAt(i)), n)).mod(n); + } + + // make sure that g is not divisible by p_i or q_i + boolean divisible = false; + for (int i = 0; i < smallPrimes.size(); i++) + { + if (g.modPow(phi_n.divide((BigInteger)smallPrimes.elementAt(i)), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/" + smallPrimes.elementAt(i) + "\n g: " + g); + } + divisible = true; + break; + } + } + + if (divisible) + { + continue; + } + + // make sure that g has order > phi_n/4 + + if (g.modPow(phi_n.divide(BigInteger.valueOf(4)), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/4\n g:" + g); + } + continue; + } + + if (g.modPow(phi_n.divide(p_), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/p'\n g: " + g); + } + continue; + } + if (g.modPow(phi_n.divide(q_), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/q'\n g: " + g); + } + continue; + } + if (g.modPow(phi_n.divide(a), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/a\n g: " + g); + } + continue; + } + if (g.modPow(phi_n.divide(b), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/b\n g: " + g); + } + continue; + } + break; + } + if (debug) + { + System.out.println("needed " + tries + " tries to generate g"); + System.out.println(); + System.out.println("found new NaccacheStern cipher variables:"); + System.out.println("smallPrimes: " + smallPrimes); + System.out.println("sigma:...... " + sigma + " (" + sigma.bitLength() + " bits)"); + System.out.println("a:.......... " + a); + System.out.println("b:.......... " + b); + System.out.println("p':......... " + p_); + System.out.println("q':......... " + q_); + System.out.println("p:.......... " + p); + System.out.println("q:.......... " + q); + System.out.println("n:.......... " + n); + System.out.println("phi(n):..... " + phi_n); + System.out.println("g:.......... " + g); + System.out.println(); + } + + return new AsymmetricCipherKeyPair(new NaccacheSternKeyParameters(false, g, n, sigma.bitLength()), + new NaccacheSternPrivateKeyParameters(g, n, sigma.bitLength(), smallPrimes, phi_n)); + } + + private static BigInteger generatePrime( + int bitLength, + int certainty, + SecureRandom rand) + { + BigInteger p_ = new BigInteger(bitLength, certainty, rand); + while (p_.bitLength() != bitLength) + { + p_ = new BigInteger(bitLength, certainty, rand); + } + return p_; + } + + /** + * Generates a permuted ArrayList from the original one. The original List + * is not modified + * + * @param arr + * the ArrayList to be permuted + * @param rand + * the source of Randomness for permutation + * @return a new ArrayList with the permuted elements. + */ + private static Vector permuteList( + Vector arr, + SecureRandom rand) + { + Vector retval = new Vector(); + Vector tmp = new Vector(); + for (int i = 0; i < arr.size(); i++) + { + tmp.addElement(arr.elementAt(i)); + } + retval.addElement(tmp.elementAt(0)); + tmp.removeElementAt(0); + while (tmp.size() != 0) + { + retval.insertElementAt(tmp.elementAt(0), getInt(rand, retval.size() + 1)); + tmp.removeElementAt(0); + } + return retval; + } + + private static int getInt( + SecureRandom rand, + int n) + { + if ((n & -n) == n) + { + return (int)((n * (long)(rand.nextInt() & 0x7fffffff)) >> 31); + } + + int bits, val; + do + { + bits = rand.nextInt() & 0x7fffffff; + val = bits % n; + } + while (bits - val + (n-1) < 0); + + return val; + } + + /** + * Finds the first 'count' primes starting with 3 + * + * @param count + * the number of primes to find + * @return a vector containing the found primes as Integer + */ + private static Vector findFirstPrimes( + int count) + { + Vector primes = new Vector(count); + + for (int i = 0; i != count; i++) + { + primes.addElement(BigInteger.valueOf(smallPrimes[i])); + } + + return primes; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java new file mode 100644 index 00000000..8a4d28ab --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java @@ -0,0 +1,131 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.digests.MD5Digest; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as usd by OpenSSL. + * <p> + * The scheme is a simple extension of PKCS 5 V2.0 Scheme 1 using MD5 with an + * iteration count of 1. + * <p> + */ +public class OpenSSLPBEParametersGenerator + extends PBEParametersGenerator +{ + private Digest digest = new MD5Digest(); + + /** + * Construct a OpenSSL Parameters generator. + */ + public OpenSSLPBEParametersGenerator() + { + } + + /** + * Initialise - note the iteration count for this algorithm is fixed at 1. + * + * @param password password to use. + * @param salt salt to use. + */ + public void init( + byte[] password, + byte[] salt) + { + super.init(password, salt, 1); + } + + /** + * the derived key function, the ith hash of the password and the salt. + */ + private byte[] generateDerivedKey( + int bytesNeeded) + { + byte[] buf = new byte[digest.getDigestSize()]; + byte[] key = new byte[bytesNeeded]; + int offset = 0; + + for (;;) + { + digest.update(password, 0, password.length); + digest.update(salt, 0, salt.length); + + digest.doFinal(buf, 0); + + int len = (bytesNeeded > buf.length) ? buf.length : bytesNeeded; + System.arraycopy(buf, 0, key, offset, len); + offset += len; + + // check if we need any more + bytesNeeded -= len; + if (bytesNeeded == 0) + { + break; + } + + // do another round + digest.reset(); + digest.update(buf, 0, buf.length); + } + + return key; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + * @exception IllegalArgumentException if keySize + ivSize is larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + byte[] dKey = generateDerivedKey(keySize + ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + return generateDerivedParameters(keySize); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java new file mode 100644 index 00000000..d9b82c32 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java @@ -0,0 +1,220 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as defined by PKCS 12 V1.0. + * <p> + * The document this implementation is based on can be found at + * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-12/index.html> + * RSA's PKCS12 Page</a> + */ +public class PKCS12ParametersGenerator + extends PBEParametersGenerator +{ + public static final int KEY_MATERIAL = 1; + public static final int IV_MATERIAL = 2; + public static final int MAC_MATERIAL = 3; + + private Digest digest; + + private int u; + private int v; + + /** + * Construct a PKCS 12 Parameters generator. This constructor will + * accept any digest which also implements ExtendedDigest. + * + * @param digest the digest to be used as the source of derived keys. + * @exception IllegalArgumentException if an unknown digest is passed in. + */ + public PKCS12ParametersGenerator( + Digest digest) + { + this.digest = digest; + if (digest instanceof ExtendedDigest) + { + u = digest.getDigestSize(); + v = ((ExtendedDigest)digest).getByteLength(); + } + else + { + throw new IllegalArgumentException("Digest " + digest.getAlgorithmName() + " unsupported"); + } + } + + /** + * add a + b + 1, returning the result in a. The a value is treated + * as a BigInteger of length (b.length * 8) bits. The result is + * modulo 2^b.length in case of overflow. + */ + private void adjust( + byte[] a, + int aOff, + byte[] b) + { + int x = (b[b.length - 1] & 0xff) + (a[aOff + b.length - 1] & 0xff) + 1; + + a[aOff + b.length - 1] = (byte)x; + x >>>= 8; + + for (int i = b.length - 2; i >= 0; i--) + { + x += (b[i] & 0xff) + (a[aOff + i] & 0xff); + a[aOff + i] = (byte)x; + x >>>= 8; + } + } + + /** + * generation of a derived key ala PKCS12 V1.0. + */ + private byte[] generateDerivedKey( + int idByte, + int n) + { + byte[] D = new byte[v]; + byte[] dKey = new byte[n]; + + for (int i = 0; i != D.length; i++) + { + D[i] = (byte)idByte; + } + + byte[] S; + + if ((salt != null) && (salt.length != 0)) + { + S = new byte[v * ((salt.length + v - 1) / v)]; + + for (int i = 0; i != S.length; i++) + { + S[i] = salt[i % salt.length]; + } + } + else + { + S = new byte[0]; + } + + byte[] P; + + if ((password != null) && (password.length != 0)) + { + P = new byte[v * ((password.length + v - 1) / v)]; + + for (int i = 0; i != P.length; i++) + { + P[i] = password[i % password.length]; + } + } + else + { + P = new byte[0]; + } + + byte[] I = new byte[S.length + P.length]; + + System.arraycopy(S, 0, I, 0, S.length); + System.arraycopy(P, 0, I, S.length, P.length); + + byte[] B = new byte[v]; + int c = (n + u - 1) / u; + byte[] A = new byte[u]; + + for (int i = 1; i <= c; i++) + { + digest.update(D, 0, D.length); + digest.update(I, 0, I.length); + digest.doFinal(A, 0); + for (int j = 1; j < iterationCount; j++) + { + digest.update(A, 0, A.length); + digest.doFinal(A, 0); + } + + for (int j = 0; j != B.length; j++) + { + B[j] = A[j % A.length]; + } + + for (int j = 0; j != I.length / v; j++) + { + adjust(I, j * v, B); + } + + if (i == c) + { + System.arraycopy(A, 0, dKey, (i - 1) * u, dKey.length - ((i - 1) * u)); + } + else + { + System.arraycopy(A, 0, dKey, (i - 1) * u, A.length); + } + } + + return dKey; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(KEY_MATERIAL, keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + byte[] dKey = generateDerivedKey(KEY_MATERIAL, keySize); + + byte[] iv = generateDerivedKey(IV_MATERIAL, ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), iv, 0, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(MAC_MATERIAL, keySize); + + return new KeyParameter(dKey, 0, keySize); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java new file mode 100644 index 00000000..1c62eccc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java @@ -0,0 +1,119 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as defined by PKCS 5 V2.0 Scheme 1. + * Note this generator is limited to the size of the hash produced by the + * digest used to drive it. + * <p> + * The document this implementation is based on can be found at + * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html> + * RSA's PKCS5 Page</a> + */ +public class PKCS5S1ParametersGenerator + extends PBEParametersGenerator +{ + private Digest digest; + + /** + * Construct a PKCS 5 Scheme 1 Parameters generator. + * + * @param digest the digest to be used as the source of derived keys. + */ + public PKCS5S1ParametersGenerator( + Digest digest) + { + this.digest = digest; + } + + /** + * the derived key function, the ith hash of the password and the salt. + */ + private byte[] generateDerivedKey() + { + byte[] digestBytes = new byte[digest.getDigestSize()]; + + digest.update(password, 0, password.length); + digest.update(salt, 0, salt.length); + + digest.doFinal(digestBytes, 0); + for (int i = 1; i < iterationCount; i++) + { + digest.update(digestBytes, 0, digestBytes.length); + digest.doFinal(digestBytes, 0); + } + + return digestBytes; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + if (keySize > digest.getDigestSize()) + { + throw new IllegalArgumentException( + "Can't generate a derived key " + keySize + " bytes long."); + } + + byte[] dKey = generateDerivedKey(); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + * @exception IllegalArgumentException if keySize + ivSize is larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + if ((keySize + ivSize) > digest.getDigestSize()) + { + throw new IllegalArgumentException( + "Can't generate a derived key " + (keySize + ivSize) + " bytes long."); + } + + byte[] dKey = generateDerivedKey(); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + return generateDerivedParameters(keySize); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java new file mode 100644 index 00000000..640ead46 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java @@ -0,0 +1,153 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as defined by PKCS 5 V2.0 Scheme 2. + * This generator uses a SHA-1 HMac as the calculation function. + * <p> + * The document this implementation is based on can be found at + * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html> + * RSA's PKCS5 Page</a> + */ +public class PKCS5S2ParametersGenerator + extends PBEParametersGenerator +{ + private Mac hMac; + private byte[] state; + + /** + * construct a PKCS5 Scheme 2 Parameters generator. + */ + public PKCS5S2ParametersGenerator() + { + this(new SHA1Digest()); + } + + public PKCS5S2ParametersGenerator(Digest digest) + { + hMac = new HMac(digest); + state = new byte[hMac.getMacSize()]; + } + + private void F( + byte[] S, + int c, + byte[] iBuf, + byte[] out, + int outOff) + { + if (c == 0) + { + throw new IllegalArgumentException("iteration count must be at least 1."); + } + + if (S != null) + { + hMac.update(S, 0, S.length); + } + + hMac.update(iBuf, 0, iBuf.length); + hMac.doFinal(state, 0); + + System.arraycopy(state, 0, out, outOff, state.length); + + for (int count = 1; count < c; count++) + { + hMac.update(state, 0, state.length); + hMac.doFinal(state, 0); + + for (int j = 0; j != state.length; j++) + { + out[outOff + j] ^= state[j]; + } + } + } + + private byte[] generateDerivedKey( + int dkLen) + { + int hLen = hMac.getMacSize(); + int l = (dkLen + hLen - 1) / hLen; + byte[] iBuf = new byte[4]; + byte[] outBytes = new byte[l * hLen]; + int outPos = 0; + + CipherParameters param = new KeyParameter(password); + + hMac.init(param); + + for (int i = 1; i <= l; i++) + { + // Increment the value in 'iBuf' + int pos = 3; + while (++iBuf[pos] == 0) + { + --pos; + } + + F(salt, iterationCount, iBuf, outBytes, outPos); + outPos += hLen; + } + + return outBytes; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + byte[] dKey = generateDerivedKey(keySize + ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + return generateDerivedParameters(keySize); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java new file mode 100644 index 00000000..add67143 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java @@ -0,0 +1,77 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Generate a random factor suitable for use with RSA blind signatures + * as outlined in Chaum's blinding and unblinding as outlined in + * "Handbook of Applied Cryptography", page 475. + */ +public class RSABlindingFactorGenerator +{ + private static BigInteger ZERO = BigInteger.valueOf(0); + private static BigInteger ONE = BigInteger.valueOf(1); + + private RSAKeyParameters key; + private SecureRandom random; + + /** + * Initialise the factor generator + * + * @param param the necessary RSA key parameters. + */ + public void init( + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RSAKeyParameters)rParam.getParameters(); + random = rParam.getRandom(); + } + else + { + key = (RSAKeyParameters)param; + random = new SecureRandom(); + } + + if (key instanceof RSAPrivateCrtKeyParameters) + { + throw new IllegalArgumentException("generator requires RSA public key"); + } + } + + /** + * Generate a suitable blind factor for the public key the generator was initialised with. + * + * @return a random blind factor + */ + public BigInteger generateBlindingFactor() + { + if (key == null) + { + throw new IllegalStateException("generator not initialised"); + } + + BigInteger m = key.getModulus(); + int length = m.bitLength() - 1; // must be less than m.bitLength() + BigInteger factor; + BigInteger gcd; + + do + { + factor = new BigInteger(length, random); + gcd = factor.gcd(m); + } + while (factor.equals(ZERO) || factor.equals(ONE) || !gcd.equals(ONE)); + + return factor; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java b/core/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java new file mode 100644 index 00000000..f58069d5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java @@ -0,0 +1,147 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.math.BigInteger; + +/** + * an RSA key pair generator. + */ +public class RSAKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private RSAKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (RSAKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + BigInteger p, q, n, d, e, pSub1, qSub1, phi; + + // + // p and q values should have a length of half the strength in bits + // + int strength = param.getStrength(); + int pbitlength = (strength + 1) / 2; + int qbitlength = strength - pbitlength; + int mindiffbits = strength / 3; + + e = param.getPublicExponent(); + + // TODO Consider generating safe primes for p, q (see DHParametersHelper.generateSafePrimes) + // (then p-1 and q-1 will not consist of only small factors - see "Pollard's algorithm") + + // + // generate p, prime and (p-1) relatively prime to e + // + for (;;) + { + p = new BigInteger(pbitlength, 1, param.getRandom()); + + if (p.mod(e).equals(ONE)) + { + continue; + } + + if (!p.isProbablePrime(param.getCertainty())) + { + continue; + } + + if (e.gcd(p.subtract(ONE)).equals(ONE)) + { + break; + } + } + + // + // generate a modulus of the required length + // + for (;;) + { + // generate q, prime and (q-1) relatively prime to e, + // and not equal to p + // + for (;;) + { + q = new BigInteger(qbitlength, 1, param.getRandom()); + + if (q.subtract(p).abs().bitLength() < mindiffbits) + { + continue; + } + + if (q.mod(e).equals(ONE)) + { + continue; + } + + if (!q.isProbablePrime(param.getCertainty())) + { + continue; + } + + if (e.gcd(q.subtract(ONE)).equals(ONE)) + { + break; + } + } + + // + // calculate the modulus + // + n = p.multiply(q); + + if (n.bitLength() == param.getStrength()) + { + break; + } + + // + // if we get here our primes aren't big enough, make the largest + // of the two p and try again + // + p = p.max(q); + } + + if (p.compareTo(q) < 0) + { + phi = p; + p = q; + q = phi; + } + + pSub1 = p.subtract(ONE); + qSub1 = q.subtract(ONE); + phi = pSub1.multiply(qSub1); + + // + // calculate the private exponent + // + d = e.modInverse(phi); + + // + // calculate the CRT factors + // + BigInteger dP, dQ, qInv; + + dP = d.remainder(pSub1); + dQ = d.remainder(qSub1); + qInv = q.modInverse(p); + + return new AsymmetricCipherKeyPair( + new RSAKeyParameters(false, n, e), + new RSAPrivateCrtKeyParameters(n, e, d, p, q, dP, dQ, qInv)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java b/core/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java new file mode 100644 index 00000000..da22fa48 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java @@ -0,0 +1,147 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.engines.Salsa20Engine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; + +public class SCrypt +{ + // TODO Validate arguments + public static byte[] generate(byte[] P, byte[] S, int N, int r, int p, int dkLen) + { + return MFcrypt(P, S, N, r, p, dkLen); + } + + private static byte[] MFcrypt(byte[] P, byte[] S, int N, int r, int p, int dkLen) + { + int MFLenBytes = r * 128; + byte[] bytes = SingleIterationPBKDF2(P, S, p * MFLenBytes); + + int[] B = null; + + try + { + int BLen = bytes.length >>> 2; + B = new int[BLen]; + + Pack.littleEndianToInt(bytes, 0, B); + + int MFLenWords = MFLenBytes >>> 2; + for (int BOff = 0; BOff < BLen; BOff += MFLenWords) + { + // TODO These can be done in parallel threads + SMix(B, BOff, N, r); + } + + Pack.intToLittleEndian(B, bytes, 0); + + return SingleIterationPBKDF2(P, bytes, dkLen); + } + finally + { + Clear(bytes); + Clear(B); + } + } + + private static byte[] SingleIterationPBKDF2(byte[] P, byte[] S, int dkLen) + { + PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(new SHA256Digest()); + pGen.init(P, S, 1); + KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(dkLen * 8); + return key.getKey(); + } + + private static void SMix(int[] B, int BOff, int N, int r) + { + int BCount = r * 32; + + int[] blockX1 = new int[16]; + int[] blockX2 = new int[16]; + int[] blockY = new int[BCount]; + + int[] X = new int[BCount]; + int[][] V = new int[N][]; + + try + { + System.arraycopy(B, BOff, X, 0, BCount); + + for (int i = 0; i < N; ++i) + { + V[i] = Arrays.clone(X); + BlockMix(X, blockX1, blockX2, blockY, r); + } + + int mask = N - 1; + for (int i = 0; i < N; ++i) + { + int j = X[BCount - 16] & mask; + Xor(X, V[j], 0, X); + BlockMix(X, blockX1, blockX2, blockY, r); + } + + System.arraycopy(X, 0, B, BOff, BCount); + } + finally + { + ClearAll(V); + ClearAll(new int[][]{ X, blockX1, blockX2, blockY }); + } + } + + private static void BlockMix(int[] B, int[] X1, int[] X2, int[] Y, int r) + { + System.arraycopy(B, B.length - 16, X1, 0, 16); + + int BOff = 0, YOff = 0, halfLen = B.length >>> 1; + + for (int i = 2 * r; i > 0; --i) + { + Xor(X1, B, BOff, X2); + + Salsa20Engine.salsaCore(8, X2, X1); + System.arraycopy(X1, 0, Y, YOff, 16); + + YOff = halfLen + BOff - YOff; + BOff += 16; + } + + System.arraycopy(Y, 0, B, 0, Y.length); + } + + private static void Xor(int[] a, int[] b, int bOff, int[] output) + { + for (int i = output.length - 1; i >= 0; --i) + { + output[i] = a[i] ^ b[bOff + i]; + } + } + + private static void Clear(byte[] array) + { + if (array != null) + { + Arrays.fill(array, (byte)0); + } + } + + private static void Clear(int[] array) + { + if (array != null) + { + Arrays.fill(array, 0); + } + } + + private static void ClearAll(int[][] arrays) + { + for (int i = 0; i < arrays.length; ++i) + { + Clear(arrays[i]); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java new file mode 100644 index 00000000..bb09a76b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java @@ -0,0 +1,244 @@ +package org.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.StreamCipher; + +/** + * A CipherInputStream is composed of an InputStream and a BufferedBlockCipher so + * that read() methods return data that are read in from the + * underlying InputStream but have been additionally processed by the + * Cipher. The Cipher must be fully initialized before being used by + * a CipherInputStream. + * <p> + * For example, if the Cipher is initialized for decryption, the + * CipherInputStream will attempt to read in data and decrypt them, + * before returning the decrypted data. + */ +public class CipherInputStream + extends FilterInputStream +{ + private BufferedBlockCipher bufferedBlockCipher; + private StreamCipher streamCipher; + + private byte[] buf; + private byte[] inBuf; + + private int bufOff; + private int maxBuf; + private boolean finalized; + + private static final int INPUT_BUF_SIZE = 2048; + + /** + * Constructs a CipherInputStream from an InputStream and a + * BufferedBlockCipher. + */ + public CipherInputStream( + InputStream is, + BufferedBlockCipher cipher) + { + super(is); + + this.bufferedBlockCipher = cipher; + + buf = new byte[cipher.getOutputSize(INPUT_BUF_SIZE)]; + inBuf = new byte[INPUT_BUF_SIZE]; + } + + public CipherInputStream( + InputStream is, + StreamCipher cipher) + { + super(is); + + this.streamCipher = cipher; + + buf = new byte[INPUT_BUF_SIZE]; + inBuf = new byte[INPUT_BUF_SIZE]; + } + + /** + * grab the next chunk of input from the underlying input stream + */ + private int nextChunk() + throws IOException + { + int available = super.available(); + + // must always try to read 1 byte! + // some buggy InputStreams return < 0! + if (available <= 0) + { + available = 1; + } + + if (available > inBuf.length) + { + available = super.read(inBuf, 0, inBuf.length); + } + else + { + available = super.read(inBuf, 0, available); + } + + if (available < 0) + { + if (finalized) + { + return -1; + } + + try + { + if (bufferedBlockCipher != null) + { + maxBuf = bufferedBlockCipher.doFinal(buf, 0); + } + else + { + maxBuf = 0; // a stream cipher + } + } + catch (Exception e) + { + throw new IOException("error processing stream: " + e.toString()); + } + + bufOff = 0; + + finalized = true; + + if (bufOff == maxBuf) + { + return -1; + } + } + else + { + bufOff = 0; + + try + { + if (bufferedBlockCipher != null) + { + maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, available, buf, 0); + } + else + { + streamCipher.processBytes(inBuf, 0, available, buf, 0); + maxBuf = available; + } + } + catch (Exception e) + { + throw new IOException("error processing stream: " + e.toString()); + } + + if (maxBuf == 0) // not enough bytes read for first block... + { + return nextChunk(); + } + } + + return maxBuf; + } + + public int read() + throws IOException + { + if (bufOff == maxBuf) + { + if (nextChunk() < 0) + { + return -1; + } + } + + return buf[bufOff++] & 0xff; + } + + public int read( + byte[] b) + throws IOException + { + return read(b, 0, b.length); + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + if (bufOff == maxBuf) + { + if (nextChunk() < 0) + { + return -1; + } + } + + int available = maxBuf - bufOff; + + if (len > available) + { + System.arraycopy(buf, bufOff, b, off, available); + bufOff = maxBuf; + + return available; + } + else + { + System.arraycopy(buf, bufOff, b, off, len); + bufOff += len; + + return len; + } + } + + public long skip( + long n) + throws IOException + { + if (n <= 0) + { + return 0; + } + + int available = maxBuf - bufOff; + + if (n > available) + { + bufOff = maxBuf; + + return available; + } + else + { + bufOff += (int)n; + + return (int)n; + } + } + + public int available() + throws IOException + { + return maxBuf - bufOff; + } + + public void close() + throws IOException + { + super.close(); + } + + public boolean markSupported() + { + return false; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java new file mode 100644 index 00000000..17a7b6d8 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java @@ -0,0 +1,188 @@ +package org.bouncycastle.crypto.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.StreamCipher; + +public class CipherOutputStream + extends FilterOutputStream +{ + private BufferedBlockCipher bufferedBlockCipher; + private StreamCipher streamCipher; + + private byte[] oneByte = new byte[1]; + private byte[] buf; + + /** + * Constructs a CipherOutputStream from an OutputStream and a + * BufferedBlockCipher. + */ + public CipherOutputStream( + OutputStream os, + BufferedBlockCipher cipher) + { + super(os); + this.bufferedBlockCipher = cipher; + this.buf = new byte[cipher.getBlockSize()]; + } + + /** + * Constructs a CipherOutputStream from an OutputStream and a + * BufferedBlockCipher. + */ + public CipherOutputStream( + OutputStream os, + StreamCipher cipher) + { + super(os); + this.streamCipher = cipher; + } + + /** + * Writes the specified byte to this output stream. + * + * @param b the <code>byte</code>. + * @exception java.io.IOException if an I/O error occurs. + */ + public void write( + int b) + throws IOException + { + oneByte[0] = (byte)b; + + if (bufferedBlockCipher != null) + { + int len = bufferedBlockCipher.processBytes(oneByte, 0, 1, buf, 0); + + if (len != 0) + { + out.write(buf, 0, len); + } + } + else + { + out.write(streamCipher.returnByte((byte)b)); + } + } + + /** + * Writes <code>b.length</code> bytes from the specified byte array + * to this output stream. + * <p> + * The <code>write</code> method of + * <code>CipherOutputStream</code> calls the <code>write</code> + * method of three arguments with the three arguments + * <code>b</code>, <code>0</code>, and <code>b.length</code>. + * + * @param b the data. + * @exception java.io.IOException if an I/O error occurs. + * @see #write(byte[], int, int) + */ + public void write( + byte[] b) + throws IOException + { + write(b, 0, b.length); + } + + /** + * Writes <code>len</code> bytes from the specified byte array + * starting at offset <code>off</code> to this output stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @exception java.io.IOException if an I/O error occurs. + */ + public void write( + byte[] b, + int off, + int len) + throws IOException + { + if (bufferedBlockCipher != null) + { + byte[] buf = new byte[bufferedBlockCipher.getOutputSize(len)]; + + int outLen = bufferedBlockCipher.processBytes(b, off, len, buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + else + { + byte[] buf = new byte[len]; + + streamCipher.processBytes(b, off, len, buf, 0); + + out.write(buf, 0, len); + } + } + + /** + * Flushes this output stream by forcing any buffered output bytes + * that have already been processed by the encapsulated cipher object + * to be written out. + * + * <p> + * Any bytes buffered by the encapsulated cipher + * and waiting to be processed by it will not be written out. For example, + * if the encapsulated cipher is a block cipher, and the total number of + * bytes written using one of the <code>write</code> methods is less than + * the cipher's block size, no bytes will be written out. + * + * @exception java.io.IOException if an I/O error occurs. + */ + public void flush() + throws IOException + { + super.flush(); + } + + /** + * Closes this output stream and releases any system resources + * associated with this stream. + * <p> + * This method invokes the <code>doFinal</code> method of the encapsulated + * cipher object, which causes any bytes buffered by the encapsulated + * cipher to be processed. The result is written out by calling the + * <code>flush</code> method of this output stream. + * <p> + * This method resets the encapsulated cipher object to its initial state + * and calls the <code>close</code> method of the underlying output + * stream. + * + * @exception java.io.IOException if an I/O error occurs. + */ + public void close() + throws IOException + { + try + { + if (bufferedBlockCipher != null) + { + byte[] buf = new byte[bufferedBlockCipher.getOutputSize(0)]; + + int outLen = bufferedBlockCipher.doFinal(buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + } + catch (Exception e) + { + throw new IOException("Error closing stream: " + e.toString()); + } + + flush(); + + super.close(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/DigestInputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/DigestInputStream.java new file mode 100644 index 00000000..ef0b03e2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/DigestInputStream.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.crypto.Digest; + +public class DigestInputStream + extends FilterInputStream +{ + protected Digest digest; + + public DigestInputStream( + InputStream stream, + Digest digest) + { + super(stream); + this.digest = digest; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + digest.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n > 0) + { + digest.update(b, off, n); + } + return n; + } + + public Digest getDigest() + { + return digest; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/DigestOutputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/DigestOutputStream.java new file mode 100644 index 00000000..23c7e539 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/DigestOutputStream.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.bouncycastle.crypto.Digest; + +public class DigestOutputStream + extends OutputStream +{ + protected Digest digest; + + public DigestOutputStream( + Digest Digest) + { + this.digest = Digest; + } + + public void write(int b) + throws IOException + { + digest.update((byte)b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + digest.update(b, off, len); + } + + public byte[] getDigest() + { + byte[] res = new byte[digest.getDigestSize()]; + + digest.doFinal(res, 0); + + return res; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/MacInputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/MacInputStream.java new file mode 100644 index 00000000..b78548c6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/MacInputStream.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.crypto.Mac; + +public class MacInputStream + extends FilterInputStream +{ + protected Mac mac; + + public MacInputStream( + InputStream stream, + Mac mac) + { + super(stream); + this.mac = mac; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + mac.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n >= 0) + { + mac.update(b, off, n); + } + return n; + } + + public Mac getMac() + { + return mac; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/MacOutputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/MacOutputStream.java new file mode 100644 index 00000000..0f0e7dbe --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/MacOutputStream.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.bouncycastle.crypto.Mac; + +public class MacOutputStream + extends OutputStream +{ + protected Mac mac; + + public MacOutputStream( + Mac mac) + { + this.mac = mac; + } + + public void write(int b) + throws IOException + { + mac.update((byte)b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + mac.update(b, off, len); + } + + public byte[] getMac() + { + byte[] res = new byte[mac.getMacSize()]; + + mac.doFinal(res, 0); + + return res; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/SignerInputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/SignerInputStream.java new file mode 100644 index 00000000..9583e4cf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/SignerInputStream.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.crypto.Signer; + +public class SignerInputStream + extends FilterInputStream +{ + protected Signer signer; + + public SignerInputStream( + InputStream stream, + Signer signer) + { + super(stream); + this.signer = signer; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + signer.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n > 0) + { + signer.update(b, off, n); + } + return n; + } + + public Signer getSigner() + { + return signer; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/io/SignerOutputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/SignerOutputStream.java new file mode 100644 index 00000000..1c21b5dc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/io/SignerOutputStream.java @@ -0,0 +1,38 @@ +package org.bouncycastle.crypto.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.bouncycastle.crypto.Signer; + +public class SignerOutputStream + extends OutputStream +{ + protected Signer signer; + + public SignerOutputStream( + Signer Signer) + { + this.signer = Signer; + } + + public void write(int b) + throws IOException + { + signer.update((byte)b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + signer.update(b, off, len); + } + + public Signer getSigner() + { + return signer; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java b/core/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java new file mode 100755 index 00000000..f4dfc6ed --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java @@ -0,0 +1,256 @@ +package org.bouncycastle.crypto.kems; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.KeyEncapsulation; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.KDFParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.BigIntegers; + +/** + * The ECIES Key Encapsulation Mechanism (ECIES-KEM) from ISO 18033-2. + */ +public class ECIESKeyEncapsulation + implements KeyEncapsulation +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private DerivationFunction kdf; + private SecureRandom rnd; + private ECKeyParameters key; + private boolean CofactorMode; + private boolean OldCofactorMode; + private boolean SingleHashMode; + + /** + * Set up the ECIES-KEM. + * + * @param kdf the key derivation function to be used. + * @param rnd the random source for the session key. + */ + public ECIESKeyEncapsulation( + DerivationFunction kdf, + SecureRandom rnd) + { + this.kdf = kdf; + this.rnd = rnd; + this.CofactorMode = false; + this.OldCofactorMode = false; + this.SingleHashMode = false; + } + + /** + * Set up the ECIES-KEM. + * + * @param kdf the key derivation function to be used. + * @param rnd the random source for the session key. + * @param cofactorMode true to use the new cofactor ECDH. + * @param oldCofactorMode true to use the old cofactor ECDH. + * @param singleHashMode true to use single hash mode. + */ + public ECIESKeyEncapsulation( + DerivationFunction kdf, + SecureRandom rnd, + boolean cofactorMode, + boolean oldCofactorMode, + boolean singleHashMode) + { + this.kdf = kdf; + this.rnd = rnd; + + // If both cofactorMode and oldCofactorMode are set to true + // then the implementation will use the new cofactor ECDH + this.CofactorMode = cofactorMode; + this.OldCofactorMode = oldCofactorMode; + this.SingleHashMode = singleHashMode; + } + + /** + * Initialise the ECIES-KEM. + * + * @param key the recipient's public (for encryption) or private (for decryption) key. + */ + public void init(CipherParameters key) + throws IllegalArgumentException + { + if (!(key instanceof ECKeyParameters)) + { + throw new IllegalArgumentException("EC key required"); + } + else + { + this.key = (ECKeyParameters)key; + } + } + + /** + * Generate and encapsulate a random session key. + * + * @param out the output buffer for the encapsulated key. + * @param outOff the offset for the output buffer. + * @param keyLen the length of the session key. + * @return the random session key. + */ + public CipherParameters encrypt(byte[] out, int outOff, int keyLen) + throws IllegalArgumentException + { + if (!(key instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("Public key required for encryption"); + } + + BigInteger n = key.getParameters().getN(); + BigInteger h = key.getParameters().getH(); + + // Generate the ephemeral key pair + BigInteger r = BigIntegers.createRandomInRange(ONE, n, rnd); + ECPoint gTilde = key.getParameters().getG().multiply(r); + + // Encode the ephemeral public key + byte[] C = gTilde.getEncoded(); + System.arraycopy(C, 0, out, outOff, C.length); + + // Compute the static-ephemeral key agreement + BigInteger rPrime; + if (CofactorMode) + { + rPrime = r.multiply(h).mod(n); + } + else + { + rPrime = r; + } + + ECPoint hTilde = ((ECPublicKeyParameters)key).getQ().multiply(rPrime); + + // Encode the shared secret value + int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8; + byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger()); + + // Initialise the KDF + byte[] kdfInput; + if (SingleHashMode) + { + kdfInput = new byte[C.length + PEH.length]; + System.arraycopy(C, 0, kdfInput, 0, C.length); + System.arraycopy(PEH, 0, kdfInput, C.length, PEH.length); + } + else + { + kdfInput = PEH; + } + + kdf.init(new KDFParameters(kdfInput, null)); + + // Generate the secret key + byte[] K = new byte[keyLen]; + kdf.generateBytes(K, 0, K.length); + + // Return the ciphertext + return new KeyParameter(K); + } + + /** + * Generate and encapsulate a random session key. + * + * @param out the output buffer for the encapsulated key. + * @param keyLen the length of the session key. + * @return the random session key. + */ + public CipherParameters encrypt(byte[] out, int keyLen) + { + return encrypt(out, 0, keyLen); + } + + /** + * Decrypt an encapsulated session key. + * + * @param in the input buffer for the encapsulated key. + * @param inOff the offset for the input buffer. + * @param inLen the length of the encapsulated key. + * @param keyLen the length of the session key. + * @return the session key. + */ + public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen) + throws IllegalArgumentException + { + if (!(key instanceof ECPrivateKeyParameters)) + { + throw new IllegalArgumentException("Private key required for encryption"); + } + + BigInteger n = key.getParameters().getN(); + BigInteger h = key.getParameters().getH(); + + // Decode the ephemeral public key + byte[] C = new byte[inLen]; + System.arraycopy(in, inOff, C, 0, inLen); + ECPoint gTilde = key.getParameters().getCurve().decodePoint(C); + + // Compute the static-ephemeral key agreement + ECPoint gHat; + if ((CofactorMode) || (OldCofactorMode)) + { + gHat = gTilde.multiply(h); + } + else + { + gHat = gTilde; + } + + BigInteger xHat; + if (CofactorMode) + { + xHat = ((ECPrivateKeyParameters)key).getD().multiply(h.modInverse(n)).mod(n); + } + else + { + xHat = ((ECPrivateKeyParameters)key).getD(); + } + + ECPoint hTilde = gHat.multiply(xHat); + + // Encode the shared secret value + int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8; + byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger()); + + // Initialise the KDF + byte[] kdfInput; + if (SingleHashMode) + { + kdfInput = new byte[C.length + PEH.length]; + System.arraycopy(C, 0, kdfInput, 0, C.length); + System.arraycopy(PEH, 0, kdfInput, C.length, PEH.length); + } + else + { + kdfInput = PEH; + } + kdf.init(new KDFParameters(kdfInput, null)); + + // Generate the secret key + byte[] K = new byte[keyLen]; + kdf.generateBytes(K, 0, K.length); + + return new KeyParameter(K); + } + + /** + * Decrypt an encapsulated session key. + * + * @param in the input buffer for the encapsulated key. + * @param keyLen the length of the session key. + * @return the session key. + */ + public CipherParameters decrypt(byte[] in, int keyLen) + { + return decrypt(in, 0, in.length, keyLen); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/kems/RSAKeyEncapsulation.java b/core/src/main/java/org/bouncycastle/crypto/kems/RSAKeyEncapsulation.java new file mode 100755 index 00000000..8c1a1724 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/kems/RSAKeyEncapsulation.java @@ -0,0 +1,164 @@ +package org.bouncycastle.crypto.kems; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.KeyEncapsulation; +import org.bouncycastle.crypto.params.KDFParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.util.BigIntegers; + +/** + * The RSA Key Encapsulation Mechanism (RSA-KEM) from ISO 18033-2. + */ +public class RSAKeyEncapsulation + implements KeyEncapsulation +{ + private static final BigInteger ZERO = BigInteger.valueOf(0); + private static final BigInteger ONE = BigInteger.valueOf(1); + + private DerivationFunction kdf; + private SecureRandom rnd; + private RSAKeyParameters key; + + /** + * Set up the RSA-KEM. + * + * @param kdf the key derivation function to be used. + * @param rnd the random source for the session key. + */ + public RSAKeyEncapsulation( + DerivationFunction kdf, + SecureRandom rnd) + { + this.kdf = kdf; + this.rnd = rnd; + } + + + /** + * Initialise the RSA-KEM. + * + * @param key the recipient's public (for encryption) or private (for decryption) key. + */ + public void init(CipherParameters key) + throws IllegalArgumentException + { + if (!(key instanceof RSAKeyParameters)) + { + throw new IllegalArgumentException("RSA key required"); + } + else + { + this.key = (RSAKeyParameters)key; + } + } + + + /** + * Generate and encapsulate a random session key. + * + * @param out the output buffer for the encapsulated key. + * @param outOff the offset for the output buffer. + * @param keyLen the length of the random session key. + * @return the random session key. + */ + public CipherParameters encrypt(byte[] out, int outOff, int keyLen) + throws IllegalArgumentException + { + if (key.isPrivate()) + { + throw new IllegalArgumentException("Public key required for encryption"); + } + + BigInteger n = key.getModulus(); + BigInteger e = key.getExponent(); + + // Generate the ephemeral random and encode it + BigInteger r = BigIntegers.createRandomInRange(ZERO, n.subtract(ONE), rnd); + byte[] R = BigIntegers.asUnsignedByteArray((n.bitLength() + 7) / 8, r); + + // Encrypt the random and encode it + BigInteger c = r.modPow(e, n); + byte[] C = BigIntegers.asUnsignedByteArray((n.bitLength() + 7) / 8, c); + System.arraycopy(C, 0, out, outOff, C.length); + + + // Initialise the KDF + kdf.init(new KDFParameters(R, null)); + + // Generate the secret key + byte[] K = new byte[keyLen]; + kdf.generateBytes(K, 0, K.length); + + return new KeyParameter(K); + } + + + /** + * Generate and encapsulate a random session key. + * + * @param out the output buffer for the encapsulated key. + * @param keyLen the length of the random session key. + * @return the random session key. + */ + public CipherParameters encrypt(byte[] out, int keyLen) + { + return encrypt(out, 0, keyLen); + } + + + /** + * Decrypt an encapsulated session key. + * + * @param in the input buffer for the encapsulated key. + * @param inOff the offset for the input buffer. + * @param inLen the length of the encapsulated key. + * @param keyLen the length of the session key. + * @return the session key. + */ + public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen) + throws IllegalArgumentException + { + if (!key.isPrivate()) + { + throw new IllegalArgumentException("Private key required for decryption"); + } + + BigInteger n = key.getModulus(); + BigInteger d = key.getExponent(); + + // Decode the input + byte[] C = new byte[inLen]; + System.arraycopy(in, inOff, C, 0, C.length); + BigInteger c = new BigInteger(1, C); + + // Decrypt the ephemeral random and encode it + BigInteger r = c.modPow(d, n); + byte[] R = BigIntegers.asUnsignedByteArray((n.bitLength() + 7) / 8, r); + + // Initialise the KDF + kdf.init(new KDFParameters(R, null)); + + // Generate the secret key + byte[] K = new byte[keyLen]; + kdf.generateBytes(K, 0, K.length); + + return new KeyParameter(K); + } + + /** + * Decrypt an encapsulated session key. + * + * @param in the input buffer for the encapsulated key. + * @param keyLen the length of the session key. + * @return the session key. + */ + public CipherParameters decrypt(byte[] in, int keyLen) + { + return decrypt(in, 0, in.length, keyLen); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/BlockCipherMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/BlockCipherMac.java new file mode 100644 index 00000000..6de39a85 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/BlockCipherMac.java @@ -0,0 +1,174 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.modes.CBCBlockCipher; + +public class BlockCipherMac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + + private int macSize; + + /** + * create a standard MAC based on a block cipher. This will produce an + * authentication code half the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @deprecated use CBCBlockCipherMac + */ + public BlockCipherMac( + BlockCipher cipher) + { + this(cipher, (cipher.getBlockSize() * 8) / 2); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. + * <p> + * Note: the size of the MAC must be at least 16 bits (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @deprecated use CBCBlockCipherMac + */ + public BlockCipherMac( + BlockCipher cipher, + int macSizeInBits) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + public void init( + CipherParameters params) + { + reset(); + + cipher.init(true, params); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + // + // pad with zeroes + // + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + + cipher.processBlock(buf, 0, mac, 0); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java new file mode 100644 index 00000000..9bf6cb0e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java @@ -0,0 +1,229 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.paddings.BlockCipherPadding; + +/** + * standard CBC Block Cipher MAC - if no padding is specified the default of + * pad of zeroes is used. + */ +public class CBCBlockCipherMac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + private BlockCipherPadding padding; + + private int macSize; + + /** + * create a standard MAC based on a CBC block cipher. This will produce an + * authentication code half the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + */ + public CBCBlockCipherMac( + BlockCipher cipher) + { + this(cipher, (cipher.getBlockSize() * 8) / 2, null); + } + + /** + * create a standard MAC based on a CBC block cipher. This will produce an + * authentication code half the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param padding the padding to be used to complete the last block. + */ + public CBCBlockCipherMac( + BlockCipher cipher, + BlockCipherPadding padding) + { + this(cipher, (cipher.getBlockSize() * 8) / 2, padding); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CBC mode as the basis for the + * MAC generation. + * <p> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + */ + public CBCBlockCipherMac( + BlockCipher cipher, + int macSizeInBits) + { + this(cipher, macSizeInBits, null); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CBC mode as the basis for the + * MAC generation. + * <p> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @param padding the padding to be used to complete the last block. + */ + public CBCBlockCipherMac( + BlockCipher cipher, + int macSizeInBits, + BlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + public void init( + CipherParameters params) + { + reset(); + + cipher.init(true, params); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + if (padding == null) + { + // + // pad with zeroes + // + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + } + else + { + if (bufOff == blockSize) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + padding.addPadding(buf, bufOff); + } + + cipher.processBlock(buf, 0, mac, 0); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/CFBBlockCipherMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/CFBBlockCipherMac.java new file mode 100644 index 00000000..d7ad6126 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/CFBBlockCipherMac.java @@ -0,0 +1,388 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.paddings.BlockCipherPadding; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ +class MacCFBBlockCipher +{ + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + + private int blockSize; + private BlockCipher cipher = null; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param blockSize the block size in bits (note: a multiple of 8) + */ + public MacCFBBlockCipher( + BlockCipher cipher, + int bitBlockSize) + { + this.cipher = cipher; + this.blockSize = bitBlockSize / 8; + + this.IV = new byte[cipher.getBlockSize()]; + this.cfbV = new byte[cipher.getBlockSize()]; + this.cfbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param param the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CFB" + (blockSize * 8); + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(cfbV, 0, cfbOutV, 0); + + // + // XOR the cfbV with the plaintext producing the cipher text + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize); + System.arraycopy(out, outOff, cfbV, cfbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, cfbV, 0, IV.length); + + cipher.reset(); + } + + void getMacBlock( + byte[] mac) + { + cipher.processBlock(cfbV, 0, mac, 0); + } +} + +public class CFBBlockCipherMac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private MacCFBBlockCipher cipher; + private BlockCipherPadding padding = null; + + + private int macSize; + + /** + * create a standard MAC based on a CFB block cipher. This will produce an + * authentication code half the length of the block size of the cipher, with + * the CFB mode set to 8 bits. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + */ + public CFBBlockCipherMac( + BlockCipher cipher) + { + this(cipher, 8, (cipher.getBlockSize() * 8) / 2, null); + } + + /** + * create a standard MAC based on a CFB block cipher. This will produce an + * authentication code half the length of the block size of the cipher, with + * the CFB mode set to 8 bits. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param padding the padding to be used. + */ + public CFBBlockCipherMac( + BlockCipher cipher, + BlockCipherPadding padding) + { + this(cipher, 8, (cipher.getBlockSize() * 8) / 2, padding); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CFB mode as the basis for the + * MAC generation. + * <p> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param cfbBitSize the size of an output block produced by the CFB mode. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + */ + public CFBBlockCipherMac( + BlockCipher cipher, + int cfbBitSize, + int macSizeInBits) + { + this(cipher, cfbBitSize, macSizeInBits, null); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CFB mode as the basis for the + * MAC generation. + * <p> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param cfbBitSize the size of an output block produced by the CFB mode. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @param padding a padding to be used. + */ + public CFBBlockCipherMac( + BlockCipher cipher, + int cfbBitSize, + int macSizeInBits, + BlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + mac = new byte[cipher.getBlockSize()]; + + this.cipher = new MacCFBBlockCipher(cipher, cfbBitSize); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + buf = new byte[this.cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + public void init( + CipherParameters params) + { + reset(); + + cipher.init(params); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + // + // pad with zeroes + // + if (this.padding == null) + { + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + } + else + { + padding.addPadding(buf, bufOff); + } + + cipher.processBlock(buf, 0, mac, 0); + + cipher.getMacBlock(mac); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/CMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/CMac.java new file mode 100644 index 00000000..8a3b5bbf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/CMac.java @@ -0,0 +1,238 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.paddings.ISO7816d4Padding; + +/** + * CMAC - as specified at www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html + * <p> + * CMAC is analogous to OMAC1 - see also en.wikipedia.org/wiki/CMAC + * </p><p> + * CMAC is a NIST recomendation - see + * csrc.nist.gov/CryptoToolkit/modes/800-38_Series_Publications/SP800-38B.pdf + * </p><p> + * CMAC/OMAC1 is a blockcipher-based message authentication code designed and + * analyzed by Tetsu Iwata and Kaoru Kurosawa. + * </p><p> + * CMAC/OMAC1 is a simple variant of the CBC MAC (Cipher Block Chaining Message + * Authentication Code). OMAC stands for One-Key CBC MAC. + * </p><p> + * It supports 128- or 64-bits block ciphers, with any key size, and returns + * a MAC with dimension less or equal to the block size of the underlying + * cipher. + * </p> + */ +public class CMac implements Mac +{ + private static final byte CONSTANT_128 = (byte)0x87; + private static final byte CONSTANT_64 = (byte)0x1b; + + private byte[] ZEROES; + + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + + private int macSize; + + private byte[] L, Lu, Lu2; + + /** + * create a standard MAC based on a CBC block cipher (64 or 128 bit block). + * This will produce an authentication code the length of the block size + * of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + */ + public CMac(BlockCipher cipher) + { + this(cipher, cipher.getBlockSize() * 8); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. + * <p/> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8 and <= 128. + */ + public CMac(BlockCipher cipher, int macSizeInBits) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + if (macSizeInBits > (cipher.getBlockSize() * 8)) + { + throw new IllegalArgumentException( + "MAC size must be less or equal to " + + (cipher.getBlockSize() * 8)); + } + + if (cipher.getBlockSize() != 8 && cipher.getBlockSize() != 16) + { + throw new IllegalArgumentException( + "Block size must be either 64 or 128 bits"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + + ZEROES = new byte[cipher.getBlockSize()]; + + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + private static byte[] doubleLu(byte[] in) + { + int FirstBit = (in[0] & 0xFF) >> 7; + byte[] ret = new byte[in.length]; + for (int i = 0; i < in.length - 1; i++) + { + ret[i] = (byte)((in[i] << 1) + ((in[i + 1] & 0xFF) >> 7)); + } + ret[in.length - 1] = (byte)(in[in.length - 1] << 1); + if (FirstBit == 1) + { + ret[in.length - 1] ^= in.length == 16 ? CONSTANT_128 : CONSTANT_64; + } + return ret; + } + + public void init(CipherParameters params) + { + if (params != null) + { + cipher.init(true, params); + + //initializes the L, Lu, Lu2 numbers + L = new byte[ZEROES.length]; + cipher.processBlock(ZEROES, 0, L, 0); + Lu = doubleLu(L); + Lu2 = doubleLu(Lu); + } + + reset(); + } + + public int getMacSize() + { + return macSize; + } + + public void update(byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update(byte[] in, int inOff, int len) + { + if (len < 0) + { + throw new IllegalArgumentException( + "Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal(byte[] out, int outOff) + { + int blockSize = cipher.getBlockSize(); + + byte[] lu; + if (bufOff == blockSize) + { + lu = Lu; + } + else + { + new ISO7816d4Padding().addPadding(buf, bufOff); + lu = Lu2; + } + + for (int i = 0; i < mac.length; i++) + { + buf[i] ^= lu[i]; + } + + cipher.processBlock(buf, 0, mac, 0); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/GMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/GMac.java new file mode 100644 index 00000000..8aae1e26 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/GMac.java @@ -0,0 +1,114 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * The GMAC specialisation of Galois/Counter mode (GCM) detailed in NIST Special Publication + * 800-38D. + * <p> + * GMac is an invocation of the GCM mode where no data is encrypted (i.e. all input data to the Mac + * is processed as additional authenticated data with the underlying GCM block cipher). + */ +public class GMac implements Mac +{ + private final GCMBlockCipher cipher; + private final int macSizeBits; + + /** + * Creates a GMAC based on the operation of a block cipher in GCM mode. + * <p/> + * This will produce an authentication code the length of the block size of the cipher. + * + * @param cipher + * the cipher to be used in GCM mode to generate the MAC. + */ + public GMac(final GCMBlockCipher cipher) + { + // use of this confused flow analyser in some earlier JDKs + this.cipher = cipher; + this.macSizeBits = 128; + } + + /** + * Creates a GMAC based on the operation of a 128 bit block cipher in GCM mode. + * + * @param macSizeBits + * the mac size to generate, in bits. Must be a multiple of 8 and >= 96 and <= 128. + * @param cipher + * the cipher to be used in GCM mode to generate the MAC. + */ + public GMac(final GCMBlockCipher cipher, final int macSizeBits) + { + this.cipher = cipher; + this.macSizeBits = macSizeBits; + } + + /** + * Initialises the GMAC - requires a {@link ParametersWithIV} providing a {@link KeyParameter} + * and a nonce. + */ + public void init(final CipherParameters params) throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + final ParametersWithIV param = (ParametersWithIV)params; + + final byte[] iv = param.getIV(); + final KeyParameter keyParam = (KeyParameter)param.getParameters(); + + // GCM is always operated in encrypt mode to calculate MAC + cipher.init(true, new AEADParameters(keyParam, macSizeBits, iv)); + } + else + { + throw new IllegalArgumentException("GMAC requires ParametersWithIV"); + } + } + + public String getAlgorithmName() + { + return cipher.getUnderlyingCipher().getAlgorithmName() + "-GMAC"; + } + + public int getMacSize() + { + return macSizeBits / 8; + } + + public void update(byte in) throws IllegalStateException + { + cipher.processAADByte(in); + } + + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException + { + cipher.processAADBytes(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + try + { + return cipher.doFinal(out, outOff); + } + catch (InvalidCipherTextException e) + { + // Impossible in encrypt mode + throw new IllegalStateException(e.toString()); + } + } + + public void reset() + { + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/GOST28147Mac.java b/core/src/main/java/org/bouncycastle/crypto/macs/GOST28147Mac.java new file mode 100644 index 00000000..b71975b8 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/GOST28147Mac.java @@ -0,0 +1,298 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithSBox; + +/** + * implementation of GOST 28147-89 MAC + */ +public class GOST28147Mac + implements Mac +{ + private int blockSize = 8; + private int macSize = 4; + private int bufOff; + private byte[] buf; + private byte[] mac; + private boolean firstStep = true; + private int[] workingKey = null; + + // + // This is default S-box - E_A. + private byte S[] = { + 0x9,0x6,0x3,0x2,0x8,0xB,0x1,0x7,0xA,0x4,0xE,0xF,0xC,0x0,0xD,0x5, + 0x3,0x7,0xE,0x9,0x8,0xA,0xF,0x0,0x5,0x2,0x6,0xC,0xB,0x4,0xD,0x1, + 0xE,0x4,0x6,0x2,0xB,0x3,0xD,0x8,0xC,0xF,0x5,0xA,0x0,0x7,0x1,0x9, + 0xE,0x7,0xA,0xC,0xD,0x1,0x3,0x9,0x0,0x2,0xB,0x4,0xF,0x8,0x5,0x6, + 0xB,0x5,0x1,0x9,0x8,0xD,0xF,0x0,0xE,0x4,0x2,0x3,0xC,0x7,0xA,0x6, + 0x3,0xA,0xD,0xC,0x1,0x2,0x0,0xB,0x7,0x5,0x9,0x4,0x8,0xF,0xE,0x6, + 0x1,0xD,0x2,0x9,0x7,0xA,0x6,0x0,0x8,0xC,0x4,0x5,0xF,0x3,0xB,0xE, + 0xB,0xA,0xF,0x5,0x0,0xC,0xE,0x8,0x6,0x2,0x3,0x9,0x1,0x7,0xD,0x4 + }; + + public GOST28147Mac() + { + mac = new byte[blockSize]; + + buf = new byte[blockSize]; + bufOff = 0; + } + + private int[] generateWorkingKey( + byte[] userKey) + { + if (userKey.length != 32) + { + throw new IllegalArgumentException("Key length invalid. Key needs to be 32 byte - 256 bit!!!"); + } + + int key[] = new int[8]; + for(int i=0; i!=8; i++) + { + key[i] = bytesToint(userKey,i*4); + } + + return key; + } + + public void init( + CipherParameters params) + throws IllegalArgumentException + { + reset(); + buf = new byte[blockSize]; + if (params instanceof ParametersWithSBox) + { + ParametersWithSBox param = (ParametersWithSBox)params; + + // + // Set the S-Box + // + System.arraycopy(param.getSBox(), 0, this.S, 0, param.getSBox().length); + + // + // set key if there is one + // + if (param.getParameters() != null) + { + workingKey = generateWorkingKey(((KeyParameter)param.getParameters()).getKey()); + } + } + else if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(((KeyParameter)params).getKey()); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to GOST28147 init - " + params.getClass().getName()); + } + } + + public String getAlgorithmName() + { + return "GOST28147Mac"; + } + + public int getMacSize() + { + return macSize; + } + + private int gost28147_mainStep(int n1, int key) + { + int cm = (key + n1); // CM1 + + // S-box replacing + + int om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); + om += S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); + om += S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); + om += S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); + om += S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); + om += S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); + om += S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); + om += S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); + + return om << 11 | om >>> (32-11); // 11-leftshift + } + + private void gost28147MacFunc( + int[] workingKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int N1, N2, tmp; //tmp -> for saving N1 + N1 = bytesToint(in, inOff); + N2 = bytesToint(in, inOff + 4); + + for(int k = 0; k < 2; k++) // 1-16 steps + { + for(int j = 0; j < 8; j++) + { + tmp = N1; + N1 = N2 ^ gost28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + + intTobytes(N1, out, outOff); + intTobytes(N2, out, outOff + 4); + } + + //array of bytes to type int + private int bytesToint( + byte[] in, + int inOff) + { + return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) + + ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] out, + int outOff) + { + out[outOff + 3] = (byte)(num >>> 24); + out[outOff + 2] = (byte)(num >>> 16); + out[outOff + 1] = (byte)(num >>> 8); + out[outOff] = (byte)num; + } + + private byte[] CM5func(byte[] buf, int bufOff, byte[] mac) + { + byte[] sum = new byte[buf.length - bufOff]; + + System.arraycopy(buf, bufOff, sum, 0, mac.length); + + for (int i = 0; i != mac.length; i++) + { + sum[i] = (byte)(sum[i] ^ mac[i]); + } + + return sum; + } + + public void update(byte in) + throws IllegalStateException + { + if (bufOff == buf.length) + { + byte[] sumbuf = new byte[buf.length]; + System.arraycopy(buf, 0, sumbuf, 0, mac.length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + byte[] sumbuf = new byte[buf.length]; + System.arraycopy(buf, 0, sumbuf, 0, mac.length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + sumbuf = CM5func(in, inOff, mac); + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + //padding with zero + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + + byte[] sumbuf = new byte[buf.length]; + System.arraycopy(buf, 0, sumbuf, 0, mac.length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + System.arraycopy(mac, (mac.length/2)-macSize, out, outOff, macSize); + + reset(); + + return macSize; + } + + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + firstStep = true; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/HMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/HMac.java new file mode 100644 index 00000000..d4345d9b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/HMac.java @@ -0,0 +1,231 @@ +package org.bouncycastle.crypto.macs; + +import java.util.Hashtable; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Integers; +import org.bouncycastle.util.Memoable; + +/** + * HMAC implementation based on RFC2104 + * + * H(K XOR opad, H(K XOR ipad, text)) + */ +public class HMac + implements Mac +{ + private final static byte IPAD = (byte)0x36; + private final static byte OPAD = (byte)0x5C; + + private Digest digest; + private int digestSize; + private int blockLength; + private Memoable ipadState; + private Memoable opadState; + + private byte[] inputPad; + private byte[] outputBuf; + + private static Hashtable blockLengths; + + static + { + blockLengths = new Hashtable(); + + blockLengths.put("GOST3411", Integers.valueOf(32)); + + blockLengths.put("MD2", Integers.valueOf(16)); + blockLengths.put("MD4", Integers.valueOf(64)); + blockLengths.put("MD5", Integers.valueOf(64)); + + blockLengths.put("RIPEMD128", Integers.valueOf(64)); + blockLengths.put("RIPEMD160", Integers.valueOf(64)); + + blockLengths.put("SHA-1", Integers.valueOf(64)); + blockLengths.put("SHA-224", Integers.valueOf(64)); + blockLengths.put("SHA-256", Integers.valueOf(64)); + blockLengths.put("SHA-384", Integers.valueOf(128)); + blockLengths.put("SHA-512", Integers.valueOf(128)); + + blockLengths.put("Tiger", Integers.valueOf(64)); + blockLengths.put("Whirlpool", Integers.valueOf(64)); + } + + private static int getByteLength( + Digest digest) + { + if (digest instanceof ExtendedDigest) + { + return ((ExtendedDigest)digest).getByteLength(); + } + + Integer b = (Integer)blockLengths.get(digest.getAlgorithmName()); + + if (b == null) + { + throw new IllegalArgumentException("unknown digest passed: " + digest.getAlgorithmName()); + } + + return b.intValue(); + } + + /** + * Base constructor for one of the standard digest algorithms that the + * byteLength of the algorithm is know for. + * + * @param digest the digest. + */ + public HMac( + Digest digest) + { + this(digest, getByteLength(digest)); + } + + private HMac( + Digest digest, + int byteLength) + { + this.digest = digest; + this.digestSize = digest.getDigestSize(); + this.blockLength = byteLength; + this.inputPad = new byte[blockLength]; + this.outputBuf = new byte[blockLength + digestSize]; + } + + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "/HMAC"; + } + + public Digest getUnderlyingDigest() + { + return digest; + } + + public void init( + CipherParameters params) + { + digest.reset(); + + byte[] key = ((KeyParameter)params).getKey(); + int keyLength = key.length; + + if (keyLength > blockLength) + { + digest.update(key, 0, keyLength); + digest.doFinal(inputPad, 0); + + keyLength = digestSize; + } + else + { + System.arraycopy(key, 0, inputPad, 0, keyLength); + } + + for (int i = keyLength; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + + System.arraycopy(inputPad, 0, outputBuf, 0, blockLength); + + xorPad(inputPad, blockLength, IPAD); + xorPad(outputBuf, blockLength, OPAD); + + if (digest instanceof Memoable) + { + opadState = ((Memoable)digest).copy(); + + ((Digest)opadState).update(outputBuf, 0, blockLength); + } + + digest.update(inputPad, 0, inputPad.length); + + if (digest instanceof Memoable) + { + ipadState = ((Memoable)digest).copy(); + } + } + + public int getMacSize() + { + return digestSize; + } + + public void update( + byte in) + { + digest.update(in); + } + + public void update( + byte[] in, + int inOff, + int len) + { + digest.update(in, inOff, len); + } + + public int doFinal( + byte[] out, + int outOff) + { + digest.doFinal(outputBuf, blockLength); + + if (opadState != null) + { + ((Memoable)digest).reset(opadState); + digest.update(outputBuf, blockLength, digest.getDigestSize()); + } + else + { + digest.update(outputBuf, 0, outputBuf.length); + } + + int len = digest.doFinal(out, outOff); + + for (int i = blockLength; i < outputBuf.length; i++) + { + outputBuf[i] = 0; + } + + if (ipadState != null) + { + ((Memoable)digest).reset(ipadState); + } + else + { + digest.update(inputPad, 0, inputPad.length); + } + + return len; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * reset the underlying digest. + */ + digest.reset(); + + /* + * reinitialize the digest. + */ + digest.update(inputPad, 0, inputPad.length); + } + + private static void xorPad(byte[] pad, int len, byte n) + { + for (int i = 0; i < len; ++i) + { + pad[i] ^= n; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java b/core/src/main/java/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java new file mode 100644 index 00000000..330b39e7 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java @@ -0,0 +1,305 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.engines.DESEngine; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.paddings.BlockCipherPadding; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * DES based CBC Block Cipher MAC according to ISO9797, algorithm 3 (ANSI X9.19 Retail MAC) + * + * This could as well be derived from CBCBlockCipherMac, but then the property mac in the base + * class must be changed to protected + */ + +public class ISO9797Alg3Mac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + private BlockCipherPadding padding; + + private int macSize; + private KeyParameter lastKey2; + private KeyParameter lastKey3; + + /** + * create a Retail-MAC based on a CBC block cipher. This will produce an + * authentication code of the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. This must + * be DESEngine. + */ + public ISO9797Alg3Mac( + BlockCipher cipher) + { + this(cipher, cipher.getBlockSize() * 8, null); + } + + /** + * create a Retail-MAC based on a CBC block cipher. This will produce an + * authentication code of the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param padding the padding to be used to complete the last block. + */ + public ISO9797Alg3Mac( + BlockCipher cipher, + BlockCipherPadding padding) + { + this(cipher, cipher.getBlockSize() * 8, padding); + } + + /** + * create a Retail-MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses single DES CBC mode as the basis for the + * MAC generation. + * <p> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + */ + public ISO9797Alg3Mac( + BlockCipher cipher, + int macSizeInBits) + { + this(cipher, macSizeInBits, null); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses single DES CBC mode as the basis for the + * MAC generation. The final block is decrypted and then encrypted using the + * middle and right part of the key. + * <p> + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @param padding the padding to be used to complete the last block. + */ + public ISO9797Alg3Mac( + BlockCipher cipher, + int macSizeInBits, + BlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + if (!(cipher instanceof DESEngine)) + { + throw new IllegalArgumentException("cipher must be instance of DESEngine"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return "ISO9797Alg3"; + } + + public void init(CipherParameters params) + { + reset(); + + if (!(params instanceof KeyParameter || params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "params must be an instance of KeyParameter or ParametersWithIV"); + } + + // KeyParameter must contain a double or triple length DES key, + // however the underlying cipher is a single DES. The middle and + // right key are used only in the final step. + + KeyParameter kp; + + if (params instanceof KeyParameter) + { + kp = (KeyParameter)params; + } + else + { + kp = (KeyParameter)((ParametersWithIV)params).getParameters(); + } + + KeyParameter key1; + byte[] keyvalue = kp.getKey(); + + if (keyvalue.length == 16) + { // Double length DES key + key1 = new KeyParameter(keyvalue, 0, 8); + this.lastKey2 = new KeyParameter(keyvalue, 8, 8); + this.lastKey3 = key1; + } + else if (keyvalue.length == 24) + { // Triple length DES key + key1 = new KeyParameter(keyvalue, 0, 8); + this.lastKey2 = new KeyParameter(keyvalue, 8, 8); + this.lastKey3 = new KeyParameter(keyvalue, 16, 8); + } + else + { + throw new IllegalArgumentException( + "Key must be either 112 or 168 bit long"); + } + + if (params instanceof ParametersWithIV) + { + cipher.init(true, new ParametersWithIV(key1, ((ParametersWithIV)params).getIV())); + } + else + { + cipher.init(true, key1); + } + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + if (padding == null) + { + // + // pad with zeroes + // + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + } + else + { + if (bufOff == blockSize) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + padding.addPadding(buf, bufOff); + } + + cipher.processBlock(buf, 0, mac, 0); + + // Added to code from base class + DESEngine deseng = new DESEngine(); + + deseng.init(false, this.lastKey2); + deseng.processBlock(mac, 0, mac, 0); + + deseng.init(true, this.lastKey3); + deseng.processBlock(mac, 0, mac, 0); + // **** + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/OldHMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/OldHMac.java new file mode 100644 index 00000000..7463afd3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/OldHMac.java @@ -0,0 +1,138 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * HMAC implementation based on RFC2104 + * + * H(K XOR opad, H(K XOR ipad, text)) + */ +public class OldHMac +implements Mac +{ + private final static int BLOCK_LENGTH = 64; + + private final static byte IPAD = (byte)0x36; + private final static byte OPAD = (byte)0x5C; + + private Digest digest; + private int digestSize; + private byte[] inputPad = new byte[BLOCK_LENGTH]; + private byte[] outputPad = new byte[BLOCK_LENGTH]; + + /** + * @deprecated uses incorrect pad for SHA-512 and SHA-384 use HMac. + */ + public OldHMac( + Digest digest) + { + this.digest = digest; + digestSize = digest.getDigestSize(); + } + + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "/HMAC"; + } + + public Digest getUnderlyingDigest() + { + return digest; + } + + public void init( + CipherParameters params) + { + digest.reset(); + + byte[] key = ((KeyParameter)params).getKey(); + + if (key.length > BLOCK_LENGTH) + { + digest.update(key, 0, key.length); + digest.doFinal(inputPad, 0); + for (int i = digestSize; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + } + else + { + System.arraycopy(key, 0, inputPad, 0, key.length); + for (int i = key.length; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + } + + outputPad = new byte[inputPad.length]; + System.arraycopy(inputPad, 0, outputPad, 0, inputPad.length); + + for (int i = 0; i < inputPad.length; i++) + { + inputPad[i] ^= IPAD; + } + + for (int i = 0; i < outputPad.length; i++) + { + outputPad[i] ^= OPAD; + } + + digest.update(inputPad, 0, inputPad.length); + } + + public int getMacSize() + { + return digestSize; + } + + public void update( + byte in) + { + digest.update(in); + } + + public void update( + byte[] in, + int inOff, + int len) + { + digest.update(in, inOff, len); + } + + public int doFinal( + byte[] out, + int outOff) + { + byte[] tmp = new byte[digestSize]; + digest.doFinal(tmp, 0); + + digest.update(outputPad, 0, outputPad.length); + digest.update(tmp, 0, tmp.length); + + int len = digest.doFinal(out, outOff); + + reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * reset the underlying digest. + */ + digest.reset(); + + /* + * reinitialize the digest. + */ + digest.update(inputPad, 0, inputPad.length); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/SipHash.java b/core/src/main/java/org/bouncycastle/crypto/macs/SipHash.java new file mode 100644 index 00000000..527c8040 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/SipHash.java @@ -0,0 +1,192 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; + +/** + * Implementation of SipHash as specified in "SipHash: a fast short-input PRF", by Jean-Philippe + * Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf). + * <p/> + * "SipHash is a family of PRFs SipHash-c-d where the integer parameters c and d are the number of + * compression rounds and the number of finalization rounds. A compression round is identical to a + * finalization round and this round function is called SipRound. Given a 128-bit key k and a + * (possibly empty) byte string m, SipHash-c-d returns a 64-bit value..." + */ +public class SipHash + implements Mac +{ + + protected final int c, d; + + protected long k0, k1; + protected long v0, v1, v2, v3, v4; + + protected byte[] buf = new byte[8]; + protected int bufPos = 0; + protected int wordCount = 0; + + /** + * SipHash-2-4 + */ + public SipHash() + { + // use of this confuses flow analyser on earlier JDKs. + this.c = 2; + this.d = 4; + } + + /** + * SipHash-c-d + * + * @param c the number of compression rounds + * @param d the number of finalization rounds + */ + public SipHash(int c, int d) + { + this.c = c; + this.d = d; + } + + public String getAlgorithmName() + { + return "SipHash-" + c + "-" + d; + } + + public int getMacSize() + { + return 8; + } + + public void init(CipherParameters params) + throws IllegalArgumentException + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("'params' must be an instance of KeyParameter"); + } + KeyParameter keyParameter = (KeyParameter)params; + byte[] key = keyParameter.getKey(); + if (key.length != 16) + { + throw new IllegalArgumentException("'params' must be a 128-bit key"); + } + + this.k0 = Pack.littleEndianToLong(key, 0); + this.k1 = Pack.littleEndianToLong(key, 8); + + reset(); + } + + public void update(byte input) + throws IllegalStateException + { + + buf[bufPos] = input; + if (++bufPos == buf.length) + { + processMessageWord(); + bufPos = 0; + } + } + + public void update(byte[] input, int offset, int length) + throws DataLengthException, + IllegalStateException + { + + for (int i = 0; i < length; ++i) + { + buf[bufPos] = input[offset + i]; + if (++bufPos == buf.length) + { + processMessageWord(); + bufPos = 0; + } + } + } + + public long doFinal() + throws DataLengthException, IllegalStateException + { + + buf[7] = (byte)(((wordCount << 3) + bufPos) & 0xff); + while (bufPos < 7) + { + buf[bufPos++] = 0; + } + + processMessageWord(); + + v2 ^= 0xffL; + + applySipRounds(d); + + long result = v0 ^ v1 ^ v2 ^ v3; + + reset(); + + return result; + } + + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + + long result = doFinal(); + Pack.longToLittleEndian(result, out, outOff); + return 8; + } + + public void reset() + { + + v0 = k0 ^ 0x736f6d6570736575L; + v1 = k1 ^ 0x646f72616e646f6dL; + v2 = k0 ^ 0x6c7967656e657261L; + v3 = k1 ^ 0x7465646279746573L; + + Arrays.fill(buf, (byte)0); + bufPos = 0; + wordCount = 0; + } + + protected void processMessageWord() + { + + ++wordCount; + long m = Pack.littleEndianToLong(buf, 0); + v3 ^= m; + applySipRounds(c); + v0 ^= m; + } + + protected void applySipRounds(int n) + { + for (int r = 0; r < n; ++r) + { + v0 += v1; + v2 += v3; + v1 = rotateLeft(v1, 13); + v3 = rotateLeft(v3, 16); + v1 ^= v0; + v3 ^= v2; + v0 = rotateLeft(v0, 32); + v2 += v1; + v0 += v3; + v1 = rotateLeft(v1, 17); + v3 = rotateLeft(v3, 21); + v1 ^= v2; + v3 ^= v0; + v2 = rotateLeft(v2, 32); + } + } + + protected static long rotateLeft(long x, int n) + { + return (x << n) | (x >>> (64 - n)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/macs/VMPCMac.java b/core/src/main/java/org/bouncycastle/crypto/macs/VMPCMac.java new file mode 100644 index 00000000..58d06d08 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/macs/VMPCMac.java @@ -0,0 +1,186 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; + +public class VMPCMac implements Mac +{ + private byte g; + + private byte n = 0; + private byte[] P = null; + private byte s = 0; + + private byte[] T; + private byte[] workingIV; + + private byte[] workingKey; + + private byte x1, x2, x3, x4; + + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + // Execute the Post-Processing Phase + for (int r = 1; r < 25; r++) + { + s = P[(s + P[n & 0xff]) & 0xff]; + + x4 = P[(x4 + x3 + r) & 0xff]; + x3 = P[(x3 + x2 + r) & 0xff]; + x2 = P[(x2 + x1 + r) & 0xff]; + x1 = P[(x1 + s + r) & 0xff]; + T[g & 0x1f] = (byte) (T[g & 0x1f] ^ x1); + T[(g + 1) & 0x1f] = (byte) (T[(g + 1) & 0x1f] ^ x2); + T[(g + 2) & 0x1f] = (byte) (T[(g + 2) & 0x1f] ^ x3); + T[(g + 3) & 0x1f] = (byte) (T[(g + 3) & 0x1f] ^ x4); + g = (byte) ((g + 4) & 0x1f); + + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + + // Input T to the IV-phase of the VMPC KSA + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + T[m & 0x1f]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + // Store 20 new outputs of the VMPC Stream Cipher in table M + byte[] M = new byte[20]; + for (int i = 0; i < 20; i++) + { + s = P[(s + P[i & 0xff]) & 0xff]; + M[i] = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + + byte temp = P[i & 0xff]; + P[i & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + System.arraycopy(M, 0, out, outOff, M.length); + reset(); + + return M.length; + } + + public String getAlgorithmName() + { + return "VMPC-MAC"; + } + + public int getMacSize() + { + return 20; + } + + public void init(CipherParameters params) throws IllegalArgumentException + { + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "VMPC-MAC Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV) params; + KeyParameter key = (KeyParameter) ivParams.getParameters(); + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "VMPC-MAC Init parameters must include a key"); + } + + this.workingIV = ivParams.getIV(); + + if (workingIV == null || workingIV.length < 1 || workingIV.length > 768) + { + throw new IllegalArgumentException( + "VMPC-MAC requires 1 to 768 bytes of IV"); + } + + this.workingKey = key.getKey(); + + reset(); + + } + + private void initKey(byte[] keyBytes, byte[] ivBytes) + { + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + n = 0; + } + + public void reset() + { + initKey(this.workingKey, this.workingIV); + g = x1 = x2 = x3 = x4 = n = 0; + T = new byte[32]; + for (int i = 0; i < 32; i++) + { + T[i] = 0; + } + } + + public void update(byte in) throws IllegalStateException + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte c = (byte) (in ^ P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]); + + x4 = P[(x4 + x3) & 0xff]; + x3 = P[(x3 + x2) & 0xff]; + x2 = P[(x2 + x1) & 0xff]; + x1 = P[(x1 + s + c) & 0xff]; + T[g & 0x1f] = (byte) (T[g & 0x1f] ^ x1); + T[(g + 1) & 0x1f] = (byte) (T[(g + 1) & 0x1f] ^ x2); + T[(g + 2) & 0x1f] = (byte) (T[(g + 2) & 0x1f] ^ x3); + T[(g + 3) & 0x1f] = (byte) (T[(g + 3) & 0x1f] ^ x4); + g = (byte) ((g + 4) & 0x1f); + + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + for (int i = 0; i < len; i++) + { + update(in[i]); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java new file mode 100644 index 00000000..71b75954 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java @@ -0,0 +1,126 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A block cipher mode that includes authenticated encryption with a streaming mode and optional associated data. + * @see org.bouncycastle.crypto.params.AEADParameters + */ +public interface AEADBlockCipher +{ + /** + * initialise the underlying cipher. Parameter can either be an AEADParameters or a ParametersWithIV object. + * + * @param forEncryption true if we are setting up for encryption, false otherwise. + * @param params the necessary parameters for the underlying cipher to be initialised. + * @exception IllegalArgumentException if the params argument is inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm. + * + * @return the algorithm name. + */ + public String getAlgorithmName(); + + /** + * return the cipher this object wraps. + * + * @return the cipher this object wraps. + */ + public BlockCipher getUnderlyingCipher(); + + /** + * Add a single byte to the associated data check. + * <br>If the implementation supports it, this will be an online operation and will not retain the associated data. + * + * @param in the byte to be processed. + */ + public void processAADByte(byte in); + + /** + * Add a sequence of bytes to the associated data check. + * <br>If the implementation supports it, this will be an online operation and will not retain the associated data. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + */ + public void processAADBytes(byte[] in, int inOff, int len); + + /** + * encrypt/decrypt a single byte. + * + * @param in the byte to be processed. + * @param out the output buffer the processed byte goes into. + * @param outOff the offset into the output byte array the processed data starts at. + * @return the number of bytes written to out. + * @exception DataLengthException if the output buffer is too small. + */ + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException; + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data starts at. + * @return the number of bytes written to out. + * @exception DataLengthException if the output buffer is too small. + */ + public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException; + + /** + * Finish the operation either appending or verifying the MAC at the end of the data. + * + * @param out space for any resulting output data. + * @param outOff offset into out to start copying the data at. + * @return number of bytes written into out. + * @throws IllegalStateException if the cipher is in an inappropriate state. + * @throws org.bouncycastle.crypto.InvalidCipherTextException if the MAC fails to match. + */ + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException; + + /** + * Return the value of the MAC associated with the last stream processed. + * + * @return MAC for plaintext data. + */ + public byte[] getMac(); + + /** + * return the size of the output buffer required for a processBytes + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to processBytes + * with len bytes of input. + */ + public int getUpdateOutputSize(int len); + + /** + * return the size of the output buffer required for a processBytes plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to processBytes and doFinal + * with len bytes of input. + */ + public int getOutputSize(int len); + + /** + * Reset the cipher. After resetting the cipher is in the same state + * as it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java new file mode 100644 index 00000000..d4800e62 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java @@ -0,0 +1,253 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.util.Arrays; + +/** + * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher. + */ +public class CBCBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] cbcV; + private byte[] cbcNextV; + + private int blockSize; + private BlockCipher cipher = null; + private boolean encrypting; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of chaining. + */ + public CBCBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + this.blockSize = cipher.getBlockSize(); + + this.IV = new byte[blockSize]; + this.cbcV = new byte[blockSize]; + this.cbcNextV = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + throws IllegalArgumentException + { + boolean oldEncrypting = this.encrypting; + + this.encrypting = encrypting; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length != blockSize) + { + throw new IllegalArgumentException("initialisation vector must be the same length as block size"); + } + + System.arraycopy(iv, 0, IV, 0, iv.length); + + reset(); + + // if null it's an IV changed only. + if (ivParam.getParameters() != null) + { + cipher.init(encrypting, ivParam.getParameters()); + } + else if (oldEncrypting != encrypting) + { + throw new IllegalArgumentException("cannot change encrypting state without providing key."); + } + } + else + { + reset(); + + // if it's null, key is to be reused. + if (params != null) + { + cipher.init(encrypting, params); + } + else if (oldEncrypting != encrypting) + { + throw new IllegalArgumentException("cannot change encrypting state without providing key."); + } + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CBC". + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CBC"; + } + + /** + * return the block size of the underlying cipher. + * + * @return the block size of the underlying cipher. + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, cbcV, 0, IV.length); + Arrays.fill(cbcNextV, (byte)0); + + cipher.reset(); + } + + /** + * Do the appropriate chaining step for CBC mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + /* + * XOR the cbcV and the input, + * then encrypt the cbcV + */ + for (int i = 0; i < blockSize; i++) + { + cbcV[i] ^= in[inOff + i]; + } + + int length = cipher.processBlock(cbcV, 0, out, outOff); + + /* + * copy ciphertext to cbcV + */ + System.arraycopy(out, outOff, cbcV, 0, cbcV.length); + + return length; + } + + /** + * Do the appropriate chaining step for CBC mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the decrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + System.arraycopy(in, inOff, cbcNextV, 0, blockSize); + + int length = cipher.processBlock(in, inOff, out, outOff); + + /* + * XOR the cbcV and the output + */ + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] ^= cbcV[i]; + } + + /* + * swap the back up buffer into next position + */ + byte[] tmp; + + tmp = cbcV; + cbcV = cbcNextV; + cbcNextV = tmp; + + return length; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java new file mode 100644 index 00000000..9a6e2e0c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java @@ -0,0 +1,378 @@ +package org.bouncycastle.crypto.modes; + +import java.io.ByteArrayOutputStream; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.CBCBlockCipherMac; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.util.Arrays; + +/** + * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in + * NIST Special Publication 800-38C. + * <p> + * <b>Note</b>: this mode is a packet mode - it needs all the data up front. + */ +public class CCMBlockCipher + implements AEADBlockCipher +{ + private BlockCipher cipher; + private int blockSize; + private boolean forEncryption; + private byte[] nonce; + private byte[] initialAssociatedText; + private int macSize; + private CipherParameters keyParam; + private byte[] macBlock; + private ByteArrayOutputStream associatedText = new ByteArrayOutputStream(); + private ByteArrayOutputStream data = new ByteArrayOutputStream(); + + /** + * Basic constructor. + * + * @param c the block cipher to be used. + */ + public CCMBlockCipher(BlockCipher c) + { + this.cipher = c; + this.blockSize = c.getBlockSize(); + this.macBlock = new byte[blockSize]; + + if (blockSize != 16) + { + throw new IllegalArgumentException("cipher required with a block size of 16."); + } + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + if (params instanceof AEADParameters) + { + AEADParameters param = (AEADParameters)params; + + nonce = param.getNonce(); + initialAssociatedText = param.getAssociatedText(); + macSize = param.getMacSize() / 8; + keyParam = param.getKey(); + } + else if (params instanceof ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)params; + + nonce = param.getIV(); + initialAssociatedText = null; + macSize = macBlock.length / 2; + keyParam = param.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to CCM"); + } + + if (nonce == null || nonce.length < 7 || nonce.length > 13) + { + throw new IllegalArgumentException("nonce must have length from 7 to 13 octets"); + } + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CCM"; + } + + public void processAADByte(byte in) + { + associatedText.write(in); + } + + public void processAADBytes(byte[] in, int inOff, int len) + { + // TODO: Process AAD online + associatedText.write(in, inOff, len); + } + + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + data.write(in); + + return 0; + } + + public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + data.write(in, inOff, inLen); + + return 0; + } + + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException + { + byte[] text = data.toByteArray(); + byte[] enc = processPacket(text, 0, text.length); + + System.arraycopy(enc, 0, out, outOff, enc.length); + + reset(); + + return enc.length; + } + + public void reset() + { + cipher.reset(); + associatedText.reset(); + data.reset(); + } + + /** + * Returns a byte array containing the mac calculated as part of the + * last encrypt or decrypt operation. + * + * @return the last mac calculated. + */ + public byte[] getMac() + { + byte[] mac = new byte[macSize]; + + System.arraycopy(macBlock, 0, mac, 0, mac.length); + + return mac; + } + + public int getUpdateOutputSize(int len) + { + return 0; + } + + public int getOutputSize(int len) + { + int totalData = len + data.size(); + + if (forEncryption) + { + return totalData + macSize; + } + + return totalData < macSize ? 0 : totalData - macSize; + } + + public byte[] processPacket(byte[] in, int inOff, int inLen) + throws IllegalStateException, InvalidCipherTextException + { + // TODO: handle null keyParam (e.g. via RepeatedKeySpec) + // Need to keep the CTR and CBC Mac parts around and reset + if (keyParam == null) + { + throw new IllegalStateException("CCM cipher unitialized."); + } + + int n = nonce.length; + int q = 15 - n; + if (q < 4) + { + int limitLen = 1 << (8 * q); + if (inLen >= limitLen) + { + throw new IllegalStateException("CCM packet too large for choice of q."); + } + } + + byte[] iv = new byte[blockSize]; + iv[0] = (byte)((q - 1) & 0x7); + System.arraycopy(nonce, 0, iv, 1, nonce.length); + + BlockCipher ctrCipher = new SICBlockCipher(cipher); + ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); + + int index = inOff; + int outOff = 0; + byte[] output; + + if (forEncryption) + { + output = new byte[inLen + macSize]; + + calculateMac(in, inOff, inLen, macBlock); + + ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0 + + while (index < inLen - blockSize) // S1... + { + ctrCipher.processBlock(in, index, output, outOff); + outOff += blockSize; + index += blockSize; + } + + byte[] block = new byte[blockSize]; + + System.arraycopy(in, index, block, 0, inLen - index); + + ctrCipher.processBlock(block, 0, block, 0); + + System.arraycopy(block, 0, output, outOff, inLen - index); + + outOff += inLen - index; + + System.arraycopy(macBlock, 0, output, outOff, output.length - outOff); + } + else + { + output = new byte[inLen - macSize]; + + System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize); + + ctrCipher.processBlock(macBlock, 0, macBlock, 0); + + for (int i = macSize; i != macBlock.length; i++) + { + macBlock[i] = 0; + } + + while (outOff < output.length - blockSize) + { + ctrCipher.processBlock(in, index, output, outOff); + outOff += blockSize; + index += blockSize; + } + + byte[] block = new byte[blockSize]; + + System.arraycopy(in, index, block, 0, output.length - outOff); + + ctrCipher.processBlock(block, 0, block, 0); + + System.arraycopy(block, 0, output, outOff, output.length - outOff); + + byte[] calculatedMacBlock = new byte[blockSize]; + + calculateMac(output, 0, output.length, calculatedMacBlock); + + if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) + { + throw new InvalidCipherTextException("mac check in CCM failed"); + } + } + + return output; + } + + private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) + { + Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8); + + cMac.init(keyParam); + + // + // build b0 + // + byte[] b0 = new byte[16]; + + if (hasAssociatedText()) + { + b0[0] |= 0x40; + } + + b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3; + + b0[0] |= ((15 - nonce.length) - 1) & 0x7; + + System.arraycopy(nonce, 0, b0, 1, nonce.length); + + int q = dataLen; + int count = 1; + while (q > 0) + { + b0[b0.length - count] = (byte)(q & 0xff); + q >>>= 8; + count++; + } + + cMac.update(b0, 0, b0.length); + + // + // process associated text + // + if (hasAssociatedText()) + { + int extra; + + int textLength = getAssociatedTextLength(); + if (textLength < ((1 << 16) - (1 << 8))) + { + cMac.update((byte)(textLength >> 8)); + cMac.update((byte)textLength); + + extra = 2; + } + else // can't go any higher than 2^32 + { + cMac.update((byte)0xff); + cMac.update((byte)0xfe); + cMac.update((byte)(textLength >> 24)); + cMac.update((byte)(textLength >> 16)); + cMac.update((byte)(textLength >> 8)); + cMac.update((byte)textLength); + + extra = 6; + } + + if (initialAssociatedText != null) + { + cMac.update(initialAssociatedText, 0, initialAssociatedText.length); + } + if (associatedText.size() > 0) + { + byte[] tmp = associatedText.toByteArray(); + cMac.update(tmp, 0, tmp.length); + } + + extra = (extra + textLength) % 16; + if (extra != 0) + { + for (int i = extra; i != 16; i++) + { + cMac.update((byte)0x00); + } + } + } + + // + // add the text + // + cMac.update(data, dataOff, dataLen); + + return cMac.doFinal(macBlock, 0); + } + + private int getAssociatedTextLength() + { + return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length); + } + + private boolean hasAssociatedText() + { + return getAssociatedTextLength() > 0; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java new file mode 100644 index 00000000..d0fb9bb6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java @@ -0,0 +1,258 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ +public class CFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + + private int blockSize; + private BlockCipher cipher = null; + private boolean encrypting; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param bitBlockSize the block size in bits (note: a multiple of 8) + */ + public CFBBlockCipher( + BlockCipher cipher, + int bitBlockSize) + { + this.cipher = cipher; + this.blockSize = bitBlockSize / 8; + + this.IV = new byte[cipher.getBlockSize()]; + this.cfbV = new byte[cipher.getBlockSize()]; + this.cfbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + throws IllegalArgumentException + { + this.encrypting = encrypting; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + // if null it's an IV changed only. + if (ivParam.getParameters() != null) + { + cipher.init(true, ivParam.getParameters()); + } + } + else + { + reset(); + + // if it's null, key is to be reused. + if (params != null) + { + cipher.init(true, params); + } + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CFB" + (blockSize * 8); + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + + /** + * Do the appropriate processing for CFB mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(cfbV, 0, cfbOutV, 0); + + // + // XOR the cfbV with the plaintext producing the ciphertext + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize); + System.arraycopy(out, outOff, cfbV, cfbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * Do the appropriate processing for CFB mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(cfbV, 0, cfbOutV, 0); + + // + // change over the input block. + // + System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize); + System.arraycopy(in, inOff, cfbV, cfbV.length - blockSize, blockSize); + + // + // XOR the cfbV with the ciphertext producing the plaintext + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]); + } + + return blockSize; + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, cfbV, 0, IV.length); + + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java new file mode 100644 index 00000000..b8e5b610 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java @@ -0,0 +1,265 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to + * be used to produce cipher text which is the same length as the plain text. + */ +public class CTSBlockCipher + extends BufferedBlockCipher +{ + private int blockSize; + + /** + * Create a buffered block cipher that uses Cipher Text Stealing + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public CTSBlockCipher( + BlockCipher cipher) + { + if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher)) + { + throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers"); + } + + this.cipher = cipher; + + blockSize = cipher.getBlockSize(); + + buf = new byte[blockSize * 2]; + bufOff = 0; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + return len + bufOff; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + System.arraycopy(in, inOff, buf, bufOff, blockSize); + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if cipher text decrypts wrongly (in + * case the exception will never get thrown). + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + if (bufOff + outOff > out.length) + { + throw new DataLengthException("output buffer to small in doFinal"); + } + + int blockSize = cipher.getBlockSize(); + int len = bufOff - blockSize; + byte[] block = new byte[blockSize]; + + if (forEncryption) + { + cipher.processBlock(buf, 0, block, 0); + + if (bufOff < blockSize) + { + throw new DataLengthException("need at least one block of input for CTS"); + } + + for (int i = bufOff; i != buf.length; i++) + { + buf[i] = block[i - blockSize]; + } + + for (int i = blockSize; i != bufOff; i++) + { + buf[i] ^= block[i - blockSize]; + } + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, blockSize, out, outOff); + } + else + { + cipher.processBlock(buf, blockSize, out, outOff); + } + + System.arraycopy(block, 0, out, outOff + blockSize, len); + } + else + { + byte[] lastBlock = new byte[blockSize]; + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, 0, block, 0); + } + else + { + cipher.processBlock(buf, 0, block, 0); + } + + for (int i = blockSize; i != bufOff; i++) + { + lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); + } + + System.arraycopy(buf, blockSize, block, 0, len); + + cipher.processBlock(block, 0, out, outOff); + System.arraycopy(lastBlock, 0, out, outOff + blockSize, len); + } + + int offset = bufOff; + + reset(); + + return offset; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java new file mode 100644 index 00000000..4999caae --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java @@ -0,0 +1,368 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.CMac; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.util.Arrays; + +/** + * A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and + * Efficiency - by M. Bellare, P. Rogaway, D. Wagner. + * + * http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf + * + * EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block + * cipher to encrypt and authenticate data. It's on-line (the length of a + * message isn't needed to begin processing it), has good performances, it's + * simple and provably secure (provided the underlying block cipher is secure). + * + * Of course, this implementations is NOT thread-safe. + */ +public class EAXBlockCipher + implements AEADBlockCipher +{ + private static final byte nTAG = 0x0; + + private static final byte hTAG = 0x1; + + private static final byte cTAG = 0x2; + + private SICBlockCipher cipher; + + private boolean forEncryption; + + private int blockSize; + + private Mac mac; + + private byte[] nonceMac; + private byte[] associatedTextMac; + private byte[] macBlock; + + private int macSize; + private byte[] bufBlock; + private int bufOff; + + private boolean cipherInitialized; + private byte[] initialAssociatedText; + + /** + * Constructor that accepts an instance of a block cipher engine. + * + * @param cipher the engine to use + */ + public EAXBlockCipher(BlockCipher cipher) + { + blockSize = cipher.getBlockSize(); + mac = new CMac(cipher); + macBlock = new byte[blockSize]; + bufBlock = new byte[blockSize * 2]; + associatedTextMac = new byte[mac.getMacSize()]; + nonceMac = new byte[mac.getMacSize()]; + this.cipher = new SICBlockCipher(cipher); + } + + public String getAlgorithmName() + { + return cipher.getUnderlyingCipher().getAlgorithmName() + "/EAX"; + } + + public BlockCipher getUnderlyingCipher() + { + return cipher.getUnderlyingCipher(); + } + + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + byte[] nonce; + CipherParameters keyParam; + + if (params instanceof AEADParameters) + { + AEADParameters param = (AEADParameters)params; + + nonce = param.getNonce(); + initialAssociatedText = param.getAssociatedText(); + macSize = param.getMacSize() / 8; + keyParam = param.getKey(); + } + else if (params instanceof ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)params; + + nonce = param.getIV(); + initialAssociatedText = null; + macSize = mac.getMacSize() / 2; + keyParam = param.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to EAX"); + } + + byte[] tag = new byte[blockSize]; + + // Key reuse implemented in CBC mode of underlying CMac + mac.init(keyParam); + + tag[blockSize - 1] = nTAG; + mac.update(tag, 0, blockSize); + mac.update(nonce, 0, nonce.length); + mac.doFinal(nonceMac, 0); + + tag[blockSize - 1] = hTAG; + mac.update(tag, 0, blockSize); + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + + // Same BlockCipher underlies this and the mac, so reuse last key on cipher + cipher.init(true, new ParametersWithIV(null, nonceMac)); + } + + private void initCipher() + { + if (cipherInitialized) + { + return; + } + + cipherInitialized = true; + + mac.doFinal(associatedTextMac, 0); + + byte[] tag = new byte[blockSize]; + tag[blockSize - 1] = cTAG; + mac.update(tag, 0, blockSize); + } + + private void calculateMac() + { + byte[] outC = new byte[blockSize]; + mac.doFinal(outC, 0); + + for (int i = 0; i < macBlock.length; i++) + { + macBlock[i] = (byte)(nonceMac[i] ^ associatedTextMac[i] ^ outC[i]); + } + } + + public void reset() + { + reset(true); + } + + private void reset( + boolean clearMac) + { + cipher.reset(); // TODO Redundant since the mac will reset it? + mac.reset(); + + bufOff = 0; + Arrays.fill(bufBlock, (byte)0); + + if (clearMac) + { + Arrays.fill(macBlock, (byte)0); + } + + byte[] tag = new byte[blockSize]; + tag[blockSize - 1] = hTAG; + mac.update(tag, 0, blockSize); + + cipherInitialized = false; + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + } + + public void processAADByte(byte in) + { + if (cipherInitialized) + { + throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun."); + } + mac.update(in); + } + + public void processAADBytes(byte[] in, int inOff, int len) + { + if (cipherInitialized) + { + throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun."); + } + mac.update(in, inOff, len); + } + + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException + { + initCipher(); + + return process(in, out, outOff); + } + + public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException + { + initCipher(); + + int resultLen = 0; + + for (int i = 0; i != len; i++) + { + resultLen += process(in[inOff + i], out, outOff + resultLen); + } + + return resultLen; + } + + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException + { + initCipher(); + + int extra = bufOff; + byte[] tmp = new byte[bufBlock.length]; + + bufOff = 0; + + if (forEncryption) + { + cipher.processBlock(bufBlock, 0, tmp, 0); + cipher.processBlock(bufBlock, blockSize, tmp, blockSize); + + System.arraycopy(tmp, 0, out, outOff, extra); + + mac.update(tmp, 0, extra); + + calculateMac(); + + System.arraycopy(macBlock, 0, out, outOff + extra, macSize); + + reset(false); + + return extra + macSize; + } + else + { + if (extra > macSize) + { + mac.update(bufBlock, 0, extra - macSize); + + cipher.processBlock(bufBlock, 0, tmp, 0); + cipher.processBlock(bufBlock, blockSize, tmp, blockSize); + + System.arraycopy(tmp, 0, out, outOff, extra - macSize); + } + + calculateMac(); + + if (!verifyMac(bufBlock, extra - macSize)) + { + throw new InvalidCipherTextException("mac check in EAX failed"); + } + + reset(false); + + return extra - macSize; + } + } + + public byte[] getMac() + { + byte[] mac = new byte[macSize]; + + System.arraycopy(macBlock, 0, mac, 0, macSize); + + return mac; + } + + public int getUpdateOutputSize(int len) + { + int totalData = len + bufOff; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % blockSize; + } + + public int getOutputSize(int len) + { + int totalData = len + bufOff; + + if (forEncryption) + { + return totalData + macSize; + } + + return totalData < macSize ? 0 : totalData - macSize; + } + + private int process(byte b, byte[] out, int outOff) + { + bufBlock[bufOff++] = b; + + if (bufOff == bufBlock.length) + { + // TODO Could move the processByte(s) calls to here +// initCipher(); + + int size; + + if (forEncryption) + { + size = cipher.processBlock(bufBlock, 0, out, outOff); + + mac.update(out, outOff, blockSize); + } + else + { + mac.update(bufBlock, 0, blockSize); + + size = cipher.processBlock(bufBlock, 0, out, outOff); + } + + bufOff = blockSize; + System.arraycopy(bufBlock, blockSize, bufBlock, 0, blockSize); + + return size; + } + + return 0; + } + + private boolean verifyMac(byte[] mac, int off) + { + int nonEqual = 0; + + for (int i = 0; i < macSize; i++) + { + nonEqual |= (macBlock[i] ^ mac[off + i]); + } + + return nonEqual == 0; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java new file mode 100644 index 00000000..9e617ec9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java @@ -0,0 +1,574 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.modes.gcm.GCMExponentiator; +import org.bouncycastle.crypto.modes.gcm.GCMMultiplier; +import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator; +import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; + +/** + * Implements the Galois/Counter mode (GCM) detailed in + * NIST Special Publication 800-38D. + */ +public class GCMBlockCipher + implements AEADBlockCipher +{ + private static final int BLOCK_SIZE = 16; + + // not final due to a compiler bug + private BlockCipher cipher; + private GCMMultiplier multiplier; + private GCMExponentiator exp; + + // These fields are set by init and not modified by processing + private boolean forEncryption; + private int macSize; + private byte[] nonce; + private byte[] initialAssociatedText; + private byte[] H; + private byte[] J0; + + // These fields are modified during processing + private byte[] bufBlock; + private byte[] macBlock; + private byte[] S, S_at, S_atPre; + private byte[] counter; + private int bufOff; + private long totalLength; + private byte[] atBlock; + private int atBlockPos; + private long atLength; + private long atLengthPre; + + public GCMBlockCipher(BlockCipher c) + { + this(c, null); + } + + public GCMBlockCipher(BlockCipher c, GCMMultiplier m) + { + if (c.getBlockSize() != BLOCK_SIZE) + { + throw new IllegalArgumentException( + "cipher required with a block size of " + BLOCK_SIZE + "."); + } + + if (m == null) + { + // TODO Consider a static property specifying default multiplier + m = new Tables8kGCMMultiplier(); + } + + this.cipher = c; + this.multiplier = m; + } + + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/GCM"; + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + this.macBlock = null; + + KeyParameter keyParam; + + if (params instanceof AEADParameters) + { + AEADParameters param = (AEADParameters)params; + + nonce = param.getNonce(); + initialAssociatedText = param.getAssociatedText(); + + int macSizeBits = param.getMacSize(); + if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0) + { + throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits); + } + + macSize = macSizeBits / 8; + keyParam = param.getKey(); + } + else if (params instanceof ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)params; + + nonce = param.getIV(); + initialAssociatedText = null; + macSize = 16; + keyParam = (KeyParameter)param.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to GCM"); + } + + int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize); + this.bufBlock = new byte[bufLength]; + + if (nonce == null || nonce.length < 1) + { + throw new IllegalArgumentException("IV must be at least 1 byte"); + } + + // TODO This should be configurable by init parameters + // (but must be 16 if nonce length not 12) (BLOCK_SIZE?) +// this.tagLength = 16; + + // Cipher always used in forward mode + // if keyParam is null we're reusing the last key. + if (keyParam != null) + { + cipher.init(true, keyParam); + + this.H = new byte[BLOCK_SIZE]; + cipher.processBlock(H, 0, H, 0); + + // GCMMultiplier tables don't change unless the key changes (and are expensive to init) + multiplier.init(H); + exp = null; + } + + this.J0 = new byte[BLOCK_SIZE]; + + if (nonce.length == 12) + { + System.arraycopy(nonce, 0, J0, 0, nonce.length); + this.J0[BLOCK_SIZE - 1] = 0x01; + } + else + { + gHASH(J0, nonce, nonce.length); + byte[] X = new byte[BLOCK_SIZE]; + Pack.longToBigEndian((long)nonce.length * 8, X, 8); + gHASHBlock(J0, X); + } + + this.S = new byte[BLOCK_SIZE]; + this.S_at = new byte[BLOCK_SIZE]; + this.S_atPre = new byte[BLOCK_SIZE]; + this.atBlock = new byte[BLOCK_SIZE]; + this.atBlockPos = 0; + this.atLength = 0; + this.atLengthPre = 0; + this.counter = Arrays.clone(J0); + this.bufOff = 0; + this.totalLength = 0; + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + } + + public byte[] getMac() + { + return Arrays.clone(macBlock); + } + + public int getOutputSize(int len) + { + int totalData = len + bufOff; + + if (forEncryption) + { + return totalData + macSize; + } + + return totalData < macSize ? 0 : totalData - macSize; + } + + public int getUpdateOutputSize(int len) + { + int totalData = len + bufOff; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % BLOCK_SIZE; + } + + public void processAADByte(byte in) + { + atBlock[atBlockPos] = in; + if (++atBlockPos == BLOCK_SIZE) + { + // Hash each block as it fills + gHASHBlock(S_at, atBlock); + atBlockPos = 0; + atLength += BLOCK_SIZE; + } + } + + public void processAADBytes(byte[] in, int inOff, int len) + { + for (int i = 0; i < len; ++i) + { + atBlock[atBlockPos] = in[inOff + i]; + if (++atBlockPos == BLOCK_SIZE) + { + // Hash each block as it fills + gHASHBlock(S_at, atBlock); + atBlockPos = 0; + atLength += BLOCK_SIZE; + } + } + } + + private void initCipher() + { + if (atLength > 0) + { + System.arraycopy(S_at, 0, S_atPre, 0, BLOCK_SIZE); + atLengthPre = atLength; + } + + // Finish hash for partial AAD block + if (atBlockPos > 0) + { + gHASHPartial(S_atPre, atBlock, 0, atBlockPos); + atLengthPre += atBlockPos; + } + + if (atLengthPre > 0) + { + System.arraycopy(S_atPre, 0, S, 0, BLOCK_SIZE); + } + } + + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException + { + bufBlock[bufOff] = in; + if (++bufOff == bufBlock.length) + { + outputBlock(out, outOff); + return BLOCK_SIZE; + } + return 0; + } + + public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException + { + int resultLen = 0; + + for (int i = 0; i < len; ++i) + { + bufBlock[bufOff] = in[inOff + i]; + if (++bufOff == bufBlock.length) + { + outputBlock(out, outOff + resultLen); + resultLen += BLOCK_SIZE; + } + } + + return resultLen; + } + + private void outputBlock(byte[] output, int offset) + { + if (totalLength == 0) + { + initCipher(); + } + gCTRBlock(bufBlock, output, offset); + if (forEncryption) + { + bufOff = 0; + } + else + { + System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); + bufOff = macSize; + } + } + + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException + { + if (totalLength == 0) + { + initCipher(); + } + + int extra = bufOff; + if (!forEncryption) + { + if (extra < macSize) + { + throw new InvalidCipherTextException("data too short"); + } + extra -= macSize; + } + + if (extra > 0) + { + gCTRPartial(bufBlock, 0, extra, out, outOff); + } + + atLength += atBlockPos; + + if (atLength > atLengthPre) + { + /* + * Some AAD was sent after the cipher started. We determine the difference b/w the hash value + * we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at). + * Then we carry this difference forward by multiplying by H^c, where c is the number of (full or + * partial) cipher-text blocks produced, and adjust the current hash. + */ + + // Finish hash for partial AAD block + if (atBlockPos > 0) + { + gHASHPartial(S_at, atBlock, 0, atBlockPos); + } + + // Find the difference between the AAD hashes + if (atLengthPre > 0) + { + xor(S_at, S_atPre); + } + + // Number of cipher-text blocks produced + long c = ((totalLength * 8) + 127) >>> 7; + + // Calculate the adjustment factor + byte[] H_c = new byte[16]; + if (exp == null) + { + exp = new Tables1kGCMExponentiator(); + exp.init(H); + } + exp.exponentiateX(c, H_c); + + // Carry the difference forward + multiply(S_at, H_c); + + // Adjust the current hash + xor(S, S_at); + } + + // Final gHASH + byte[] X = new byte[BLOCK_SIZE]; + Pack.longToBigEndian(atLength * 8, X, 0); + Pack.longToBigEndian(totalLength * 8, X, 8); + + gHASHBlock(S, X); + + // TODO Fix this if tagLength becomes configurable + // T = MSBt(GCTRk(J0,S)) + byte[] tag = new byte[BLOCK_SIZE]; + cipher.processBlock(J0, 0, tag, 0); + xor(tag, S); + + int resultLen = extra; + + // We place into macBlock our calculated value for T + this.macBlock = new byte[macSize]; + System.arraycopy(tag, 0, macBlock, 0, macSize); + + if (forEncryption) + { + // Append T to the message + System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize); + resultLen += macSize; + } + else + { + // Retrieve the T value from the message and compare to calculated one + byte[] msgMac = new byte[macSize]; + System.arraycopy(bufBlock, extra, msgMac, 0, macSize); + if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac)) + { + throw new InvalidCipherTextException("mac check in GCM failed"); + } + } + + reset(false); + + return resultLen; + } + + public void reset() + { + reset(true); + } + + private void reset( + boolean clearMac) + { + cipher.reset(); + + S = new byte[BLOCK_SIZE]; + S_at = new byte[BLOCK_SIZE]; + S_atPre = new byte[BLOCK_SIZE]; + atBlock = new byte[BLOCK_SIZE]; + atBlockPos = 0; + atLength = 0; + atLengthPre = 0; + counter = Arrays.clone(J0); + bufOff = 0; + totalLength = 0; + + if (bufBlock != null) + { + Arrays.fill(bufBlock, (byte)0); + } + + if (clearMac) + { + macBlock = null; + } + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + } + + private void gCTRBlock(byte[] block, byte[] out, int outOff) + { + byte[] tmp = getNextCounterBlock(); + + xor(tmp, block); + System.arraycopy(tmp, 0, out, outOff, BLOCK_SIZE); + + gHASHBlock(S, forEncryption ? tmp : block); + + totalLength += BLOCK_SIZE; + } + + private void gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff) + { + byte[] tmp = getNextCounterBlock(); + + xor(tmp, buf, off, len); + System.arraycopy(tmp, 0, out, outOff, len); + + gHASHPartial(S, forEncryption ? tmp : buf, 0, len); + + totalLength += len; + } + + private void gHASH(byte[] Y, byte[] b, int len) + { + for (int pos = 0; pos < len; pos += BLOCK_SIZE) + { + int num = Math.min(len - pos, BLOCK_SIZE); + gHASHPartial(Y, b, pos, num); + } + } + + private void gHASHBlock(byte[] Y, byte[] b) + { + xor(Y, b); + multiplier.multiplyH(Y); + } + + private void gHASHPartial(byte[] Y, byte[] b, int off, int len) + { + xor(Y, b, off, len); + multiplier.multiplyH(Y); + } + + private byte[] getNextCounterBlock() + { + for (int i = 15; i >= 12; --i) + { + byte b = (byte)((counter[i] + 1) & 0xff); + counter[i] = b; + + if (b != 0) + { + break; + } + } + + byte[] tmp = new byte[BLOCK_SIZE]; + // TODO Sure would be nice if ciphers could operate on int[] + cipher.processBlock(counter, 0, tmp, 0); + return tmp; + } + + private static void multiply(byte[] block, byte[] val) + { + byte[] tmp = Arrays.clone(block); + byte[] c = new byte[16]; + + for (int i = 0; i < 16; ++i) + { + byte bits = val[i]; + for (int j = 7; j >= 0; --j) + { + if ((bits & (1 << j)) != 0) + { + xor(c, tmp); + } + + boolean lsb = (tmp[15] & 1) != 0; + shiftRight(tmp); + if (lsb) + { + // R = new byte[]{ 0xe1, ... }; +// xor(v, R); + tmp[0] ^= (byte)0xe1; + } + } + } + + System.arraycopy(c, 0, block, 0, 16); + } + + private static void shiftRight(byte[] block) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i] & 0xff; + block[i] = (byte) ((b >>> 1) | bit); + if (++i == 16) + { + break; + } + bit = (b & 1) << 7; + } + } + + private static void xor(byte[] block, byte[] val) + { + for (int i = 15; i >= 0; --i) + { + block[i] ^= val[i]; + } + } + + private static void xor(byte[] block, byte[] val, int off, int len) + { + while (len-- > 0) + { + block[len] ^= val[off + len]; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java new file mode 100644 index 00000000..1178974f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java @@ -0,0 +1,234 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements the GOST 28147 OFB counter mode (GCTR). + */ +public class GOFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + + private final int blockSize; + private final BlockCipher cipher; + + boolean firstStep = true; + int N3; + int N4; + static final int C1 = 16843012; //00000001000000010000000100000100 + static final int C2 = 16843009; //00000001000000010000000100000001 + + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * counter mode (must have a 64 bit block size). + */ + public GOFBBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + this.blockSize = cipher.getBlockSize(); + + if (blockSize != 8) + { + throw new IllegalArgumentException("GCTR only for 64 bit block ciphers"); + } + + this.IV = new byte[cipher.getBlockSize()]; + this.ofbV = new byte[cipher.getBlockSize()]; + this.ofbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, //ignored by this CTR mode + CipherParameters params) + throws IllegalArgumentException + { + firstStep = true; + N3 = 0; + N4 = 0; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + // if params is null we reuse the current working key. + if (ivParam.getParameters() != null) + { + cipher.init(true, ivParam.getParameters()); + } + } + else + { + reset(); + + // if params is null we reuse the current working key. + if (params != null) + { + cipher.init(true, params); + } + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/GCTR" + * and the block size in bits + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/GCTR"; + } + + + /** + * return the block size we are operating at (in bytes). + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (firstStep) + { + firstStep = false; + cipher.processBlock(ofbV, 0, ofbOutV, 0); + N3 = bytesToint(ofbOutV, 0); + N4 = bytesToint(ofbOutV, 4); + } + N3 += C2; + N4 += C1; + intTobytes(N3, ofbV, 0); + intTobytes(N4, ofbV, 4); + + cipher.processBlock(ofbV, 0, ofbOutV, 0); + + // + // XOR the ofbV with the plaintext producing the cipher text (and + // the next input block). + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize); + System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the feedback vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, ofbV, 0, IV.length); + + cipher.reset(); + } + + //array of bytes to type int + private int bytesToint( + byte[] in, + int inOff) + { + return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) + + ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] out, + int outOff) + { + out[outOff + 3] = (byte)(num >>> 24); + out[outOff + 2] = (byte)(num >>> 16); + out[outOff + 1] = (byte)(num >>> 8); + out[outOff] = (byte)num; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java new file mode 100644 index 00000000..d4d29104 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java @@ -0,0 +1,581 @@ +package org.bouncycastle.crypto.modes; + +import java.util.Vector; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.util.Arrays; + +/** + * An implementation of the "work in progress" Internet-Draft <a + * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-00">The OCB Authenticated-Encryption + * Algorithm</a>, licensed per: + * <p/> + * <blockquote> <a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for + * Open-Source Software Implementations of OCB</a> (Jan 9, 2013) — “License 1” <br> + * Under this license, you are authorized to make, use, and distribute open-source software + * implementations of OCB. This license terminates for you if you sue someone over their open-source + * software implementation of OCB claiming that you have a patent covering their implementation. + * <p/> + * This is a non-binding summary of a legal document (the link above). The parameters of the license + * are specified in the license document and that document is controlling. </blockquote> + */ +public class OCBBlockCipher + implements AEADBlockCipher +{ + + private static final int BLOCK_SIZE = 16; + + private BlockCipher hashCipher; + private BlockCipher mainCipher; + + /* + * CONFIGURATION + */ + private boolean forEncryption; + private int macSize; + private byte[] initialAssociatedText; + + /* + * KEY-DEPENDENT + */ + // NOTE: elements are lazily calculated + private Vector L; + private byte[] L_Asterisk, L_Dollar; + + /* + * NONCE-DEPENDENT + */ + private byte[] OffsetMAIN_0; + + /* + * PER-ENCRYPTION/DECRYPTION + */ + private byte[] hashBlock, mainBlock; + private int hashBlockPos, mainBlockPos; + private long hashBlockCount, mainBlockCount; + private byte[] OffsetHASH; + private byte[] Sum; + private byte[] OffsetMAIN; + private byte[] Checksum; + + // NOTE: The MAC value is preserved after doFinal + private byte[] macBlock; + + public OCBBlockCipher(BlockCipher hashCipher, BlockCipher mainCipher) + { + if (hashCipher == null) + { + throw new IllegalArgumentException("'hashCipher' cannot be null"); + } + if (hashCipher.getBlockSize() != BLOCK_SIZE) + { + throw new IllegalArgumentException("'hashCipher' must have a block size of " + + BLOCK_SIZE); + } + if (mainCipher == null) + { + throw new IllegalArgumentException("'mainCipher' cannot be null"); + } + if (mainCipher.getBlockSize() != BLOCK_SIZE) + { + throw new IllegalArgumentException("'mainCipher' must have a block size of " + + BLOCK_SIZE); + } + + if (!hashCipher.getAlgorithmName().equals(mainCipher.getAlgorithmName())) + { + throw new IllegalArgumentException( + "'hashCipher' and 'mainCipher' must be the same algorithm"); + } + + this.hashCipher = hashCipher; + this.mainCipher = mainCipher; + } + + public BlockCipher getUnderlyingCipher() + { + return mainCipher; + } + + public String getAlgorithmName() + { + return mainCipher.getAlgorithmName() + "/OCB"; + } + + public void init(boolean forEncryption, CipherParameters parameters) + throws IllegalArgumentException + { + + this.forEncryption = forEncryption; + this.macBlock = null; + + KeyParameter keyParameter; + + byte[] N; + if (parameters instanceof AEADParameters) + { + AEADParameters aeadParameters = (AEADParameters)parameters; + + N = aeadParameters.getNonce(); + initialAssociatedText = aeadParameters.getAssociatedText(); + + int macSizeBits = aeadParameters.getMacSize(); + if (macSizeBits < 64 || macSizeBits > 128 || macSizeBits % 8 != 0) + { + throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits); + } + + macSize = macSizeBits / 8; + keyParameter = aeadParameters.getKey(); + } + else if (parameters instanceof ParametersWithIV) + { + ParametersWithIV parametersWithIV = (ParametersWithIV)parameters; + + N = parametersWithIV.getIV(); + initialAssociatedText = null; + macSize = 16; + keyParameter = (KeyParameter)parametersWithIV.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to OCB"); + } + + this.hashBlock = new byte[16]; + this.mainBlock = new byte[forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize)]; + + if (N == null) + { + N = new byte[0]; + } + + if (N.length > 16 || (N.length == 16 && (N[0] & 0x80) != 0)) + { + /* + * NOTE: We don't just ignore bit 128 because it would hide from the caller the fact + * that two nonces differing only in bit 128 are not different. + */ + throw new IllegalArgumentException("IV must be no more than 127 bits"); + } + + /* + * KEY-DEPENDENT INITIALISATION + */ + + // if keyParam is null we're reusing the last key. + if (keyParameter != null) + { + // TODO + } + + // hashCipher always used in forward mode + hashCipher.init(true, keyParameter); + mainCipher.init(forEncryption, keyParameter); + + this.L_Asterisk = new byte[16]; + hashCipher.processBlock(L_Asterisk, 0, L_Asterisk, 0); + + this.L_Dollar = OCB_double(L_Asterisk); + + this.L = new Vector(); + this.L.addElement(OCB_double(L_Dollar)); + + /* + * NONCE-DEPENDENT AND PER-ENCRYPTION/DECRYPTION INITIALISATION + */ + + byte[] nonce = new byte[16]; + System.arraycopy(N, 0, nonce, nonce.length - N.length, N.length); + if (N.length == 16) + { + nonce[0] &= 0x80; + } + else + { + nonce[15 - N.length] = 1; + } + + int bottom = nonce[15] & 0x3F; + // System.out.println("bottom: " + bottom); + + byte[] Ktop = new byte[16]; + nonce[15] &= 0xC0; + hashCipher.processBlock(nonce, 0, Ktop, 0); + + byte[] Stretch = new byte[24]; + System.arraycopy(Ktop, 0, Stretch, 0, 16); + for (int i = 0; i < 8; ++i) + { + Stretch[16 + i] = (byte)(Ktop[i] ^ Ktop[i + 1]); + } + + this.OffsetMAIN_0 = new byte[16]; + int bits = bottom % 8, bytes = bottom / 8; + if (bits == 0) + { + System.arraycopy(Stretch, bytes, OffsetMAIN_0, 0, 16); + } + else + { + for (int i = 0; i < 16; ++i) + { + int b1 = Stretch[bytes] & 0xff; + int b2 = Stretch[++bytes] & 0xff; + this.OffsetMAIN_0[i] = (byte)((b1 << bits) | (b2 >>> (8 - bits))); + } + } + + this.hashBlockPos = 0; + this.mainBlockPos = 0; + + this.hashBlockCount = 0; + this.mainBlockCount = 0; + + this.OffsetHASH = new byte[16]; + this.Sum = new byte[16]; + this.OffsetMAIN = Arrays.clone(this.OffsetMAIN_0); + this.Checksum = new byte[16]; + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + } + + public byte[] getMac() + { + return Arrays.clone(macBlock); + } + + public int getOutputSize(int len) + { + int totalData = len + mainBlockPos; + if (forEncryption) + { + return totalData + macSize; + } + return totalData < macSize ? 0 : totalData - macSize; + } + + public int getUpdateOutputSize(int len) + { + int totalData = len + mainBlockPos; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % BLOCK_SIZE; + } + + public void processAADByte(byte input) + { + hashBlock[hashBlockPos] = input; + if (++hashBlockPos == hashBlock.length) + { + processHashBlock(); + } + } + + public void processAADBytes(byte[] input, int off, int len) + { + for (int i = 0; i < len; ++i) + { + hashBlock[hashBlockPos] = input[off + i]; + if (++hashBlockPos == hashBlock.length) + { + processHashBlock(); + } + } + } + + public int processByte(byte input, byte[] output, int outOff) + throws DataLengthException + { + mainBlock[mainBlockPos] = input; + if (++mainBlockPos == mainBlock.length) + { + processMainBlock(output, outOff); + return BLOCK_SIZE; + } + return 0; + } + + public int processBytes(byte[] input, int inOff, int len, byte[] output, int outOff) + throws DataLengthException + { + + int resultLen = 0; + + for (int i = 0; i < len; ++i) + { + mainBlock[mainBlockPos] = input[inOff + i]; + if (++mainBlockPos == mainBlock.length) + { + processMainBlock(output, outOff + resultLen); + resultLen += BLOCK_SIZE; + } + } + + return resultLen; + } + + public int doFinal(byte[] output, int outOff) + throws IllegalStateException, + InvalidCipherTextException + { + + /* + * For decryption, get the tag from the end of the message + */ + byte[] tag = null; + if (!forEncryption) + { + if (mainBlockPos < macSize) + { + throw new InvalidCipherTextException("data too short"); + } + mainBlockPos -= macSize; + tag = new byte[macSize]; + System.arraycopy(mainBlock, mainBlockPos, tag, 0, macSize); + } + + /* + * HASH: Process any final partial block; compute final hash value + */ + if (hashBlockPos > 0) + { + OCB_extend(hashBlock, hashBlockPos); + updateHASH(L_Asterisk); + } + + /* + * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block + */ + if (mainBlockPos > 0) + { + if (forEncryption) + { + OCB_extend(mainBlock, mainBlockPos); + xor(Checksum, mainBlock); + } + + xor(OffsetMAIN, L_Asterisk); + + byte[] Pad = new byte[16]; + hashCipher.processBlock(OffsetMAIN, 0, Pad, 0); + + xor(mainBlock, Pad); + + System.arraycopy(mainBlock, 0, output, outOff, mainBlockPos); + + if (!forEncryption) + { + OCB_extend(mainBlock, mainBlockPos); + xor(Checksum, mainBlock); + } + } + + /* + * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag + */ + xor(Checksum, OffsetMAIN); + xor(Checksum, L_Dollar); + hashCipher.processBlock(Checksum, 0, Checksum, 0); + xor(Checksum, Sum); + + this.macBlock = new byte[macSize]; + System.arraycopy(Checksum, 0, macBlock, 0, macSize); + + /* + * Validate or append tag and reset this cipher for the next run + */ + int resultLen = mainBlockPos; + + if (forEncryption) + { + // Append tag to the message + System.arraycopy(macBlock, 0, output, outOff + resultLen, macSize); + resultLen += macSize; + } + else + { + // Compare the tag from the message with the calculated one + if (!Arrays.constantTimeAreEqual(macBlock, tag)) + { + throw new InvalidCipherTextException("mac check in OCB failed"); + } + } + + reset(false); + + return resultLen; + } + + public void reset() + { + reset(true); + } + + protected void clear(byte[] bs) + { + if (bs != null) + { + Arrays.fill(bs, (byte)0); + } + } + + protected byte[] getLSub(int n) + { + while (n >= L.size()) + { + L.addElement(OCB_double((byte[])L.lastElement())); + } + return (byte[])L.elementAt(n); + } + + protected void processHashBlock() + { + /* + * HASH: Process any whole blocks + */ + updateHASH(getLSub(OCB_ntz(++hashBlockCount))); + hashBlockPos = 0; + } + + protected void processMainBlock(byte[] output, int outOff) + { + /* + * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks + */ + + if (forEncryption) + { + xor(Checksum, mainBlock); + mainBlockPos = 0; + } + + xor(OffsetMAIN, getLSub(OCB_ntz(++mainBlockCount))); + + xor(mainBlock, OffsetMAIN); + mainCipher.processBlock(mainBlock, 0, mainBlock, 0); + xor(mainBlock, OffsetMAIN); + + System.arraycopy(mainBlock, 0, output, outOff, 16); + + if (!forEncryption) + { + xor(Checksum, mainBlock); + System.arraycopy(mainBlock, BLOCK_SIZE, mainBlock, 0, macSize); + mainBlockPos = macSize; + } + } + + protected void reset(boolean clearMac) + { + + hashCipher.reset(); + mainCipher.reset(); + + clear(hashBlock); + clear(mainBlock); + + hashBlockPos = 0; + mainBlockPos = 0; + + hashBlockCount = 0; + mainBlockCount = 0; + + clear(OffsetHASH); + clear(Sum); + System.arraycopy(OffsetMAIN_0, 0, OffsetMAIN, 0, 16); + clear(Checksum); + + if (clearMac) + { + macBlock = null; + } + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + } + + protected void updateHASH(byte[] LSub) + { + xor(OffsetHASH, LSub); + xor(hashBlock, OffsetHASH); + hashCipher.processBlock(hashBlock, 0, hashBlock, 0); + xor(Sum, hashBlock); + } + + protected static byte[] OCB_double(byte[] block) + { + byte[] result = new byte[16]; + int carry = shiftLeft(block, result); + + /* + * NOTE: This construction is an attempt at a constant-time implementation. + */ + result[15] ^= (0x87 >>> ((1 - carry) << 3)); + + return result; + } + + protected static void OCB_extend(byte[] block, int pos) + { + block[pos] = (byte)0x80; + while (++pos < 16) + { + block[pos] = 0; + } + } + + protected static int OCB_ntz(long x) + { + if (x == 0) + { + return 64; + } + + int n = 0; + while ((x & 1L) == 0L) + { + ++n; + x >>= 1; + } + return n; + } + + protected static int shiftLeft(byte[] block, byte[] output) + { + int i = 16; + int bit = 0; + while (--i >= 0) + { + int b = block[i] & 0xff; + output[i] = (byte)((b << 1) | bit); + bit = (b >>> 7) & 1; + } + return bit; + } + + protected static void xor(byte[] block, byte[] val) + { + for (int i = 15; i >= 0; --i) + { + block[i] ^= val[i]; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java new file mode 100644 index 00000000..5297698f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java @@ -0,0 +1,187 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements a Output-FeedBack (OFB) mode on top of a simple cipher. + */ +public class OFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + + private final int blockSize; + private final BlockCipher cipher; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param blockSize the block size in bits (note: a multiple of 8) + */ + public OFBBlockCipher( + BlockCipher cipher, + int blockSize) + { + this.cipher = cipher; + this.blockSize = blockSize / 8; + + this.IV = new byte[cipher.getBlockSize()]; + this.ofbV = new byte[cipher.getBlockSize()]; + this.ofbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, //ignored by this OFB mode + CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + // if null it's an IV changed only. + if (ivParam.getParameters() != null) + { + cipher.init(true, ivParam.getParameters()); + } + } + else + { + reset(); + + // if it's null, key is to be reused. + if (params != null) + { + cipher.init(true, params); + } + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/OFB" + * and the block size in bits + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/OFB" + (blockSize * 8); + } + + + /** + * return the block size we are operating at (in bytes). + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(ofbV, 0, ofbOutV, 0); + + // + // XOR the ofbV with the plaintext producing the cipher text (and + // the next input block). + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize); + System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the feedback vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, ofbV, 0, IV.length); + + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java new file mode 100644 index 00000000..e48731b3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java @@ -0,0 +1,312 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; + +/** + * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode + * on top of a simple cipher. This class assumes the IV has been prepended + * to the data stream already, and just accomodates the reset after + * (blockSize + 2) bytes have been read. + * <p> + * For further info see <a href="http://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>. + */ +public class OpenPGPCFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] FR; + private byte[] FRE; + + private BlockCipher cipher; + + private int count; + private int blockSize; + private boolean forEncryption; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + */ + public OpenPGPCFBBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + + this.blockSize = cipher.getBlockSize(); + this.IV = new byte[blockSize]; + this.FR = new byte[blockSize]; + this.FRE = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/PGPCFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/OpenPGPCFB"; + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + count = 0; + + System.arraycopy(IV, 0, FR, 0, FR.length); + + cipher.reset(); + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + reset(); + + cipher.init(true, params); + } + + /** + * Encrypt one byte of data according to CFB mode. + * @param data the byte to encrypt + * @param blockOff offset in the current block + * @return the encrypted byte + */ + private byte encryptByte(byte data, int blockOff) + { + return (byte)(FRE[blockOff] ^ data); + } + + /** + * Do the appropriate processing for CFB IV mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count > blockSize) + { + FR[blockSize - 2] = out[outOff] = encryptByte(in[inOff], blockSize - 2); + FR[blockSize - 1] = out[outOff + 1] = encryptByte(in[inOff + 1], blockSize - 1); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + FR[n - 2] = out[outOff + n] = encryptByte(in[inOff + n], n - 2); + } + } + else if (count == 0) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + FR[n] = out[outOff + n] = encryptByte(in[inOff + n], n); + } + + count += blockSize; + } + else if (count == blockSize) + { + cipher.processBlock(FR, 0, FRE, 0); + + out[outOff] = encryptByte(in[inOff], 0); + out[outOff + 1] = encryptByte(in[inOff + 1], 1); + + // + // do reset + // + System.arraycopy(FR, 2, FR, 0, blockSize - 2); + System.arraycopy(out, outOff, FR, blockSize - 2, 2); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + FR[n - 2] = out[outOff + n] = encryptByte(in[inOff + n], n - 2); + } + + count += blockSize; + } + + return blockSize; + } + + /** + * Do the appropriate processing for CFB IV mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count > blockSize) + { + byte inVal = in[inOff]; + FR[blockSize - 2] = inVal; + out[outOff] = encryptByte(inVal, blockSize - 2); + + inVal = in[inOff + 1]; + FR[blockSize - 1] = inVal; + out[outOff + 1] = encryptByte(inVal, blockSize - 1); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + inVal = in[inOff + n]; + FR[n - 2] = inVal; + out[outOff + n] = encryptByte(inVal, n - 2); + } + } + else if (count == 0) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + FR[n] = in[inOff + n]; + out[n] = encryptByte(in[inOff + n], n); + } + + count += blockSize; + } + else if (count == blockSize) + { + cipher.processBlock(FR, 0, FRE, 0); + + byte inVal1 = in[inOff]; + byte inVal2 = in[inOff + 1]; + out[outOff ] = encryptByte(inVal1, 0); + out[outOff + 1] = encryptByte(inVal2, 1); + + System.arraycopy(FR, 2, FR, 0, blockSize - 2); + + FR[blockSize - 2] = inVal1; + FR[blockSize - 1] = inVal2; + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + byte inVal = in[inOff + n]; + FR[n - 2] = inVal; + out[outOff + n] = encryptByte(inVal, n - 2); + } + + count += blockSize; + } + + return blockSize; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java new file mode 100644 index 00000000..18e612b3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java @@ -0,0 +1,450 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode on top of a simple cipher. For further info see <a href="http://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>. + */ +public class PGPCFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] FR; + private byte[] FRE; + private byte[] tmp; + + private BlockCipher cipher; + + private int count; + private int blockSize; + private boolean forEncryption; + + private boolean inlineIv; // if false we don't need to prepend an IV + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param inlineIv if true this is for PGP CFB with a prepended iv. + */ + public PGPCFBBlockCipher( + BlockCipher cipher, + boolean inlineIv) + { + this.cipher = cipher; + this.inlineIv = inlineIv; + + this.blockSize = cipher.getBlockSize(); + this.IV = new byte[blockSize]; + this.FR = new byte[blockSize]; + this.FRE = new byte[blockSize]; + this.tmp = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/PGPCFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + if (inlineIv) + { + return cipher.getAlgorithmName() + "/PGPCFBwithIV"; + } + else + { + return cipher.getAlgorithmName() + "/PGPCFB"; + } + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * 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. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (inlineIv) + { + return (forEncryption) ? encryptBlockWithIV(in, inOff, out, outOff) : decryptBlockWithIV(in, inOff, out, outOff); + } + else + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + count = 0; + + for (int i = 0; i != FR.length; i++) + { + if (inlineIv) + { + FR[i] = 0; + } + else + { + FR[i] = IV[i]; // if simple mode, key is IV (even if this is zero) + } + } + + cipher.reset(); + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * Encrypt one byte of data according to CFB mode. + * @param data the byte to encrypt + * @param blockOff where am i in the current block, determines when to resync the block + * @returns the encrypted byte + */ + private byte encryptByte(byte data, int blockOff) + { + return (byte)(FRE[blockOff] ^ data); + } + + /** + * Do the appropriate processing for CFB IV mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlockWithIV( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count == 0) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(IV[n], n); + } + + System.arraycopy(out, outOff, FR, 0, blockSize); + + cipher.processBlock(FR, 0, FRE, 0); + + out[outOff + blockSize] = encryptByte(IV[blockSize - 2], 0); + out[outOff + blockSize + 1] = encryptByte(IV[blockSize - 1], 1); + + System.arraycopy(out, outOff + 2, FR, 0, blockSize); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + out[outOff + blockSize + 2 + n] = encryptByte(in[inOff + n], n); + } + + System.arraycopy(out, outOff + blockSize + 2, FR, 0, blockSize); + + count += 2 * blockSize + 2; + + return 2 * blockSize + 2; + } + else if (count >= blockSize + 2) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(in[inOff + n], n); + } + + System.arraycopy(out, outOff, FR, 0, blockSize); + } + + return blockSize; + } + + /** + * Do the appropriate processing for CFB IV mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlockWithIV( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count == 0) + { + for (int n = 0; n < blockSize; n++) + { + FR[n] = in[inOff + n]; + } + + cipher.processBlock(FR, 0, FRE, 0); + + count += blockSize; + + return 0; + } + else if (count == blockSize) + { + // copy in buffer so that this mode works if in and out are the same + System.arraycopy(in, inOff, tmp, 0, blockSize); + + System.arraycopy(FR, 2, FR, 0, blockSize - 2); + + FR[blockSize - 2] = tmp[0]; + FR[blockSize - 1] = tmp[1]; + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize - 2; n++) + { + out[outOff + n] = encryptByte(tmp[n + 2], n); + } + + System.arraycopy(tmp, 2, FR, 0, blockSize - 2); + + count += 2; + + return blockSize - 2; + } + else if (count >= blockSize + 2) + { + // copy in buffer so that this mode works if in and out are the same + System.arraycopy(in, inOff, tmp, 0, blockSize); + + out[outOff + 0] = encryptByte(tmp[0], blockSize - 2); + out[outOff + 1] = encryptByte(tmp[1], blockSize - 1); + + System.arraycopy(tmp, 0, FR, blockSize - 2, 2); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize - 2; n++) + { + out[outOff + n + 2] = encryptByte(tmp[n + 2], n); + } + + System.arraycopy(tmp, 2, FR, 0, blockSize - 2); + + } + + return blockSize; + } + + /** + * Do the appropriate processing for CFB mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(FR, 0, FRE, 0); + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(in[inOff + n], n); + } + + for (int n = 0; n < blockSize; n++) + { + FR[n] = out[outOff + n]; + } + + return blockSize; + + } + + /** + * Do the appropriate processing for CFB mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(FR, 0, FRE, 0); + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(in[inOff + n], n); + } + + for (int n = 0; n < blockSize; n++) + { + FR[n] = in[inOff + n]; + } + + return blockSize; + + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java new file mode 100644 index 00000000..f15ed673 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java @@ -0,0 +1,253 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A wrapper class that allows block ciphers to be used to process data in + * a piecemeal fashion with PKCS5/PKCS7 padding. The PaddedBlockCipher + * outputs a block only when the buffer is full and more data is being added, + * or on a doFinal (unless the current block in the buffer is a pad block). + * The padding mechanism used is the one outlined in PKCS5/PKCS7. + * + * @deprecated use org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher instead. + */ +public class PaddedBlockCipher + extends BufferedBlockCipher +{ + /** + * Create a buffered block cipher with, or without, padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public PaddedBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + if (forEncryption) + { + return total + buf.length; + } + + return total; + } + + return total - leftOver + buf.length; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > buf.length) + { + resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. If the buffer is currently + * full and padding needs to be added a call to doFinal will produce + * 2 * getBlockSize() bytes. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @exception DataLengthException if there is insufficient space in out for + * the output or we are decrypting and the input is not block size aligned. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + + if (forEncryption) + { + if (bufOff == blockSize) + { + if ((outOff + 2 * blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + // + // add PKCS7 padding + // + byte code = (byte)(blockSize - bufOff); + + while (bufOff < blockSize) + { + buf[bufOff] = code; + bufOff++; + } + + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + } + else + { + if (bufOff == blockSize) + { + resultLen = cipher.processBlock(buf, 0, buf, 0); + bufOff = 0; + } + else + { + throw new DataLengthException("last block incomplete in decryption"); + } + + // + // remove PKCS7 padding + // + int count = buf[blockSize - 1] & 0xff; + + if ((count < 0) || (count > blockSize)) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + resultLen -= count; + + System.arraycopy(buf, 0, out, outOff, resultLen); + } + + reset(); + + return resultLen; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java new file mode 100644 index 00000000..da8c4ae1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java @@ -0,0 +1,113 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implements the Segmented Integer Counter (SIC) mode on top of a simple + * block cipher. This mode is also known as CTR mode. + */ +public class SICBlockCipher + implements BlockCipher +{ + private final BlockCipher cipher; + private final int blockSize; + + private byte[] IV; + private byte[] counter; + private byte[] counterOut; + + + /** + * Basic constructor. + * + * @param c the block cipher to be used. + */ + public SICBlockCipher(BlockCipher c) + { + this.cipher = c; + this.blockSize = cipher.getBlockSize(); + this.IV = new byte[blockSize]; + this.counter = new byte[blockSize]; + this.counterOut = new byte[blockSize]; + } + + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + + public void init( + boolean forEncryption, //ignored by this CTR mode + CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + System.arraycopy(iv, 0, IV, 0, IV.length); + + reset(); + + // if null it's an IV changed only. + if (ivParam.getParameters() != null) + { + cipher.init(true, ivParam.getParameters()); + } + } + else + { + throw new IllegalArgumentException("SIC mode requires ParametersWithIV"); + } + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/SIC"; + } + + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + cipher.processBlock(counter, 0, counterOut, 0); + + // + // XOR the counterOut with the plaintext producing the cipher text + // + for (int i = 0; i < counterOut.length; i++) + { + out[outOff + i] = (byte)(counterOut[i] ^ in[inOff + i]); + } + + // increment counter by 1. + for (int i = counter.length - 1; i >= 0 && ++counter[i] == 0; i--) + { + ; // do nothing - pre-increment and test for 0 in counter does the job. + } + + return counter.length; + } + + + public void reset() + { + System.arraycopy(IV, 0, counter, 0, counter.length); + cipher.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java new file mode 100644 index 00000000..f2be2fc4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java @@ -0,0 +1,36 @@ +package org.bouncycastle.crypto.modes.gcm; + +import org.bouncycastle.util.Arrays; + +public class BasicGCMExponentiator implements GCMExponentiator +{ + private byte[] x; + + public void init(byte[] x) + { + this.x = Arrays.clone(x); + } + + public void exponentiateX(long pow, byte[] output) + { + // Initial value is little-endian 1 + byte[] y = GCMUtil.oneAsBytes(); + + if (pow > 0) + { + byte[] powX = Arrays.clone(x); + do + { + if ((pow & 1L) != 0) + { + GCMUtil.multiply(y, powX); + } + GCMUtil.multiply(powX, powX); + pow >>>= 1; + } + while (pow > 0); + } + + System.arraycopy(y, 0, output, 0, 16); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java new file mode 100644 index 00000000..a98d5b2a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java @@ -0,0 +1,18 @@ +package org.bouncycastle.crypto.modes.gcm; + +import org.bouncycastle.util.Arrays; + +public class BasicGCMMultiplier implements GCMMultiplier +{ + private byte[] H; + + public void init(byte[] H) + { + this.H = Arrays.clone(H); + } + + public void multiplyH(byte[] x) + { + GCMUtil.multiply(x, H); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java new file mode 100644 index 00000000..e1cc5c76 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java @@ -0,0 +1,7 @@ +package org.bouncycastle.crypto.modes.gcm; + +public interface GCMExponentiator +{ + void init(byte[] x); + void exponentiateX(long pow, byte[] output); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java new file mode 100644 index 00000000..f52f6105 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java @@ -0,0 +1,7 @@ +package org.bouncycastle.crypto.modes.gcm; + +public interface GCMMultiplier +{ + void init(byte[] H); + void multiplyH(byte[] x); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java new file mode 100644 index 00000000..48753011 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java @@ -0,0 +1,260 @@ +package org.bouncycastle.crypto.modes.gcm; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; + +abstract class GCMUtil +{ + static byte[] oneAsBytes() + { + byte[] tmp = new byte[16]; + tmp[0] = (byte)0x80; + return tmp; + } + + static int[] oneAsInts() + { + int[] tmp = new int[4]; + tmp[0] = 0x80000000; + return tmp; + } + + static byte[] asBytes(int[] ns) + { + byte[] output = new byte[16]; + Pack.intToBigEndian(ns, output, 0); + return output; + } + + static int[] asInts(byte[] bs) + { + int[] output = new int[4]; + Pack.bigEndianToInt(bs, 0, output); + return output; + } + + static void asInts(byte[] bs, int[] output) + { + Pack.bigEndianToInt(bs, 0, output); + } + + static void multiply(byte[] block, byte[] val) + { + byte[] tmp = Arrays.clone(block); + byte[] c = new byte[16]; + + for (int i = 0; i < 16; ++i) + { + byte bits = val[i]; + for (int j = 7; j >= 0; --j) + { + if ((bits & (1 << j)) != 0) + { + xor(c, tmp); + } + + boolean lsb = (tmp[15] & 1) != 0; + shiftRight(tmp); + if (lsb) + { + // R = new byte[]{ 0xe1, ... }; +// GCMUtil.xor(v, R); + tmp[0] ^= (byte)0xe1; + } + } + } + + System.arraycopy(c, 0, block, 0, 16); + } + + // P is the value with only bit i=1 set + static void multiplyP(int[] x) + { + boolean lsb = (x[3] & 1) != 0; + shiftRight(x); + if (lsb) + { + // R = new int[]{ 0xe1000000, 0, 0, 0 }; +// xor(v, R); + x[0] ^= 0xe1000000; + } + } + + static void multiplyP(int[] x, int[] output) + { + boolean lsb = (x[3] & 1) != 0; + shiftRight(x, output); + if (lsb) + { + output[0] ^= 0xe1000000; + } + } + + // P is the value with only bit i=1 set + static void multiplyP8(int[] x) + { +// for (int i = 8; i != 0; --i) +// { +// multiplyP(x); +// } + + int lsw = x[3]; + shiftRightN(x, 8); + for (int i = 7; i >= 0; --i) + { + if ((lsw & (1 << i)) != 0) + { + x[0] ^= (0xe1000000 >>> (7 - i)); + } + } + } + + static void multiplyP8(int[] x, int[] output) + { + int lsw = x[3]; + shiftRightN(x, 8, output); + for (int i = 7; i >= 0; --i) + { + if ((lsw & (1 << i)) != 0) + { + output[0] ^= (0xe1000000 >>> (7 - i)); + } + } + } + + static void shiftRight(byte[] block) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i] & 0xff; + block[i] = (byte) ((b >>> 1) | bit); + if (++i == 16) + { + break; + } + bit = (b & 1) << 7; + } + } + + static void shiftRight(byte[] block, byte[] output) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i] & 0xff; + output[i] = (byte) ((b >>> 1) | bit); + if (++i == 16) + { + break; + } + bit = (b & 1) << 7; + } + } + + static void shiftRight(int[] block) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i]; + block[i] = (b >>> 1) | bit; + if (++i == 4) + { + break; + } + bit = b << 31; + } + } + + static void shiftRight(int[] block, int[] output) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i]; + output[i] = (b >>> 1) | bit; + if (++i == 4) + { + break; + } + bit = b << 31; + } + } + + static void shiftRightN(int[] block, int n) + { + int i = 0; + int bits = 0; + for (;;) + { + int b = block[i]; + block[i] = (b >>> n) | bits; + if (++i == 4) + { + break; + } + bits = b << (32 - n); + } + } + + static void shiftRightN(int[] block, int n, int[] output) + { + int i = 0; + int bits = 0; + for (;;) + { + int b = block[i]; + output[i] = (b >>> n) | bits; + if (++i == 4) + { + break; + } + bits = b << (32 - n); + } + } + + static void xor(byte[] block, byte[] val) + { + for (int i = 15; i >= 0; --i) + { + block[i] ^= val[i]; + } + } + + static void xor(byte[] block, byte[] val, int off, int len) + { + while (len-- > 0) + { + block[len] ^= val[off + len]; + } + } + + static void xor(byte[] block, byte[] val, byte[] output) + { + for (int i = 15; i >= 0; --i) + { + output[i] = (byte)(block[i] ^ val[i]); + } + } + + static void xor(int[] block, int[] val) + { + for (int i = 3; i >= 0; --i) + { + block[i] ^= val[i]; + } + } + + static void xor(int[] block, int[] val, int[] output) + { + for (int i = 3; i >= 0; --i) + { + output[i] = block[i] ^ val[i]; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java new file mode 100644 index 00000000..a0512086 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java @@ -0,0 +1,57 @@ +package org.bouncycastle.crypto.modes.gcm; + +import java.util.Vector; + +import org.bouncycastle.util.Arrays; + +public class Tables1kGCMExponentiator implements GCMExponentiator +{ + // A lookup table of the power-of-two powers of 'x' + // - lookupPowX2[i] = x^(2^i) + private Vector lookupPowX2; + + public void init(byte[] x) + { + if (lookupPowX2 != null && Arrays.areEqual(x, (byte[])lookupPowX2.elementAt(0))) + { + return; + } + + lookupPowX2 = new Vector(8); + lookupPowX2.addElement(Arrays.clone(x)); + } + + public void exponentiateX(long pow, byte[] output) + { + byte[] y = GCMUtil.oneAsBytes(); + int bit = 0; + while (pow > 0) + { + if ((pow & 1L) != 0) + { + ensureAvailable(bit); + GCMUtil.multiply(y, (byte[])lookupPowX2.elementAt(bit)); + } + ++bit; + pow >>>= 1; + } + + System.arraycopy(y, 0, output, 0, 16); + } + + private void ensureAvailable(int bit) + { + int count = lookupPowX2.size(); + if (count <= bit) + { + byte[] tmp = (byte[])lookupPowX2.elementAt(count - 1); + do + { + tmp = Arrays.clone(tmp); + GCMUtil.multiply(tmp, tmp); + lookupPowX2.addElement(tmp); + } + while (++count <= bit); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java new file mode 100644 index 00000000..a34a6ea4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java @@ -0,0 +1,73 @@ +package org.bouncycastle.crypto.modes.gcm; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; + +public class Tables64kGCMMultiplier implements GCMMultiplier +{ + private byte[] H; + private int[][][] M; + + public void init(byte[] H) + { + if (M == null) + { + M = new int[16][256][4]; + } + else if (Arrays.areEqual(this.H, H)) + { + return; + } + + this.H = Arrays.clone(H); + + // M[0][0] is ZEROES; + GCMUtil.asInts(H, M[0][128]); + + for (int j = 64; j >= 1; j >>= 1) + { + GCMUtil.multiplyP(M[0][j + j], M[0][j]); + } + + int i = 0; + for (;;) + { + for (int j = 2; j < 256; j += j) + { + for (int k = 1; k < j; ++k) + { + GCMUtil.xor(M[i][j], M[i][k], M[i][j + k]); + } + } + + if (++i == 16) + { + return; + } + + // M[i][0] is ZEROES; + for (int j = 128; j > 0; j >>= 1) + { + GCMUtil.multiplyP8(M[i - 1][j], M[i][j]); + } + } + } + + public void multiplyH(byte[] x) + { +// assert x.Length == 16; + + int[] z = new int[4]; + for (int i = 15; i >= 0; --i) + { +// GCMUtil.xor(z, M[i][x[i] & 0xff]); + int[] m = M[i][x[i] & 0xff]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; + } + + Pack.intToBigEndian(z, x, 0); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java new file mode 100644 index 00000000..8535db5a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java @@ -0,0 +1,90 @@ +package org.bouncycastle.crypto.modes.gcm; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Arrays; + +public class Tables8kGCMMultiplier implements GCMMultiplier +{ + private byte[] H; + private int[][][] M; + + public void init(byte[] H) + { + if (M == null) + { + M = new int[32][16][4]; + } + else if (Arrays.areEqual(this.H, H)) + { + return; + } + + this.H = Arrays.clone(H); + + // M[0][0] is ZEROES; + // M[1][0] is ZEROES; + GCMUtil.asInts(H, M[1][8]); + + for (int j = 4; j >= 1; j >>= 1) + { + GCMUtil.multiplyP(M[1][j + j], M[1][j]); + } + + GCMUtil.multiplyP(M[1][1], M[0][8]); + + for (int j = 4; j >= 1; j >>= 1) + { + GCMUtil.multiplyP(M[0][j + j], M[0][j]); + } + + int i = 0; + for (;;) + { + for (int j = 2; j < 16; j += j) + { + for (int k = 1; k < j; ++k) + { + GCMUtil.xor(M[i][j], M[i][k], M[i][j + k]); + } + } + + if (++i == 32) + { + return; + } + + if (i > 1) + { + // M[i][0] is ZEROES; + for(int j = 8; j > 0; j >>= 1) + { + GCMUtil.multiplyP8(M[i - 2][j], M[i][j]); + } + } + } + } + + public void multiplyH(byte[] x) + { +// assert x.Length == 16; + + int[] z = new int[4]; + for (int i = 15; i >= 0; --i) + { +// GCMUtil.xor(z, M[i + i][x[i] & 0x0f]); + int[] m = M[i + i][x[i] & 0x0f]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; +// GCMUtil.xor(z, M[i + i + 1][(x[i] & 0xf0) >>> 4]); + m = M[i + i + 1][(x[i] & 0xf0) >>> 4]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; + } + + Pack.intToBigEndian(z, x, 0); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/BlockCipherPadding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/BlockCipherPadding.java new file mode 100644 index 00000000..7c4f0aee --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/BlockCipherPadding.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * Block cipher padders are expected to conform to this interface + */ +public interface BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random the source of randomness for the padding, if required. + */ + public void init(SecureRandom random) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getPaddingName(); + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + * <p> + * Note: this assumes that the last block of plain text is always + * passed to it inside in. i.e. if inOff is zero, indicating the + * entire block is to be overwritten with padding the value of in + * should be the same as the last block of plain text. The reason + * for this is that some modes such as "trailing bit compliment" + * base the padding on the last byte of plain text. + * </p> + */ + public int addPadding(byte[] in, int inOff); + + /** + * return the number of pad bytes present in the block. + * @exception InvalidCipherTextException if the padding is badly formed + * or invalid. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java new file mode 100644 index 00000000..63e29d84 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java @@ -0,0 +1,79 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds ISO10126-2 padding to a block. + */ +public class ISO10126d2Padding + implements BlockCipherPadding +{ + SecureRandom random; + + /** + * Initialise the padder. + * + * @param random a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + if (random != null) + { + this.random = random; + } + else + { + this.random = new SecureRandom(); + } + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "ISO10126-2"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + byte code = (byte)(in.length - inOff); + + while (inOff < (in.length - 1)) + { + in[inOff] = (byte)random.nextInt(); + inOff++; + } + + in[inOff] = code; + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in[in.length - 1] & 0xff; + + if (count > in.length) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/ISO7816d4Padding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/ISO7816d4Padding.java new file mode 100644 index 00000000..54c31a9b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/ISO7816d4Padding.java @@ -0,0 +1,77 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds the padding according to the scheme referenced in + * ISO 7814-4 - scheme 2 from ISO 9797-1. The first byte is 0x80, rest is 0x00 + */ +public class ISO7816d4Padding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "ISO7816-4"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + int added = (in.length - inOff); + + in [inOff]= (byte) 0x80; + inOff ++; + + while (inOff < in.length) + { + in[inOff] = (byte) 0; + inOff++; + } + + return added; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in.length - 1; + + while (count > 0 && in[count] == 0) + { + count--; + } + + if (in[count] != (byte)0x80) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return in.length - count; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/PKCS7Padding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/PKCS7Padding.java new file mode 100644 index 00000000..93b149fa --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/PKCS7Padding.java @@ -0,0 +1,76 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds PKCS7/PKCS5 padding to a block. + */ +public class PKCS7Padding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "PKCS7"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + byte code = (byte)(in.length - inOff); + + while (inOff < in.length) + { + in[inOff] = code; + inOff++; + } + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in[in.length - 1] & 0xff; + + if (count > in.length || count == 0) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + for (int i = 1; i <= count; i++) + { + if (in[in.length - i] != count) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + } + + return count; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java new file mode 100644 index 00000000..ee3fd60e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java @@ -0,0 +1,299 @@ +package org.bouncycastle.crypto.paddings; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * A wrapper class that allows block ciphers to be used to process data in + * a piecemeal fashion with padding. The PaddedBufferedBlockCipher + * outputs a block only when the buffer is full and more data is being added, + * or on a doFinal (unless the current block in the buffer is a pad block). + * The default padding mechanism used is the one outlined in PKCS5/PKCS7. + */ +public class PaddedBufferedBlockCipher + extends BufferedBlockCipher +{ + BlockCipherPadding padding; + + /** + * Create a buffered block cipher with the desired padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + * @param padding the padding type. + */ + public PaddedBufferedBlockCipher( + BlockCipher cipher, + BlockCipherPadding padding) + { + this.cipher = cipher; + this.padding = padding; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + /** + * Create a buffered block cipher PKCS7 padding + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public PaddedBufferedBlockCipher( + BlockCipher cipher) + { + this(cipher, new PKCS7Padding()); + } + + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + reset(); + + if (params instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)params; + + padding.init(p.getRandom()); + + cipher.init(forEncryption, p.getParameters()); + } + else + { + padding.init(null); + + cipher.init(forEncryption, params); + } + } + + /** + * return the minimum size of the output buffer required for an update + * plus a doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + if (forEncryption) + { + return total + buf.length; + } + + return total; + } + + return total - leftOver + buf.length; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > buf.length) + { + resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. If the buffer is currently + * full and padding needs to be added a call to doFinal will produce + * 2 * getBlockSize() bytes. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output or we are decrypting and the input is not block size aligned. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + + if (forEncryption) + { + if (bufOff == blockSize) + { + if ((outOff + 2 * blockSize) > out.length) + { + reset(); + + throw new OutputLengthException("output buffer too short"); + } + + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + padding.addPadding(buf, bufOff); + + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + + reset(); + } + else + { + if (bufOff == blockSize) + { + resultLen = cipher.processBlock(buf, 0, buf, 0); + bufOff = 0; + } + else + { + reset(); + + throw new DataLengthException("last block incomplete in decryption"); + } + + try + { + resultLen -= padding.padCount(buf); + + System.arraycopy(buf, 0, out, outOff, resultLen); + } + finally + { + reset(); + } + } + + return resultLen; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/TBCPadding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/TBCPadding.java new file mode 100644 index 00000000..219912fe --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/TBCPadding.java @@ -0,0 +1,89 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds Trailing-Bit-Compliment padding to a block. + * <p> + * This padding pads the block out with the compliment of the last bit + * of the plain text. + * </p> + */ +public class TBCPadding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "TBC"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + * <p> + * Note: this assumes that the last block of plain text is always + * passed to it inside in. i.e. if inOff is zero, indicating the + * entire block is to be overwritten with padding the value of in + * should be the same as the last block of plain text. + * </p> + */ + public int addPadding( + byte[] in, + int inOff) + { + int count = in.length - inOff; + byte code; + + if (inOff > 0) + { + code = (byte)((in[inOff - 1] & 0x01) == 0 ? 0xff : 0x00); + } + else + { + code = (byte)((in[in.length - 1] & 0x01) == 0 ? 0xff : 0x00); + } + + while (inOff < in.length) + { + in[inOff] = code; + inOff++; + } + + return count; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + byte code = in[in.length - 1]; + + int index = in.length - 1; + while (index > 0 && in[index - 1] == code) + { + index--; + } + + return in.length - index; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/X923Padding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/X923Padding.java new file mode 100644 index 00000000..d4d34aad --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/X923Padding.java @@ -0,0 +1,80 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds X9.23 padding to a block - if a SecureRandom is + * passed in random padding is assumed, otherwise padding with zeros is used. + */ +public class X923Padding + implements BlockCipherPadding +{ + SecureRandom random = null; + + /** + * Initialise the padder. + * + * @param random a SecureRandom if one is available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + this.random = random; + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "X9.23"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + byte code = (byte)(in.length - inOff); + + while (inOff < in.length - 1) + { + if (random == null) + { + in[inOff] = 0; + } + else + { + in[inOff] = (byte)random.nextInt(); + } + inOff++; + } + + in[inOff] = code; + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in[in.length - 1] & 0xff; + + if (count > in.length) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/paddings/ZeroBytePadding.java b/core/src/main/java/org/bouncycastle/crypto/paddings/ZeroBytePadding.java new file mode 100644 index 00000000..c7560286 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/paddings/ZeroBytePadding.java @@ -0,0 +1,73 @@ +package org.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds NULL byte padding to a block. + */ +public class ZeroBytePadding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "ZeroByte"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + int added = (in.length - inOff); + + while (inOff < in.length) + { + in[inOff] = (byte) 0; + inOff++; + } + + return added; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in.length; + + while (count > 0) + { + if (in[count - 1] != 0) + { + break; + } + + count--; + } + + return in.length - count; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/AEADParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/AEADParameters.java new file mode 100644 index 00000000..9a9272ba --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/AEADParameters.java @@ -0,0 +1,60 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class AEADParameters + implements CipherParameters +{ + private byte[] associatedText; + private byte[] nonce; + private KeyParameter key; + private int macSize; + + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + */ + public AEADParameters(KeyParameter key, int macSize, byte[] nonce) + { + this(key, macSize, nonce, null); + } + + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + * @param associatedText initial associated text, if any + */ + public AEADParameters(KeyParameter key, int macSize, byte[] nonce, byte[] associatedText) + { + this.key = key; + this.nonce = nonce; + this.macSize = macSize; + this.associatedText = associatedText; + } + + public KeyParameter getKey() + { + return key; + } + + public int getMacSize() + { + return macSize; + } + + public byte[] getAssociatedText() + { + return associatedText; + } + + public byte[] getNonce() + { + return nonce; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java b/core/src/main/java/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java new file mode 100644 index 00000000..03ba7253 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java @@ -0,0 +1,20 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class AsymmetricKeyParameter + implements CipherParameters +{ + boolean privateKey; + + public AsymmetricKeyParameter( + boolean privateKey) + { + this.privateKey = privateKey; + } + + public boolean isPrivate() + { + return privateKey; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/CCMParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/CCMParameters.java new file mode 100644 index 00000000..4924dcca --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/CCMParameters.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.params; + +/** + * @deprecated use AEADParameters + */ +public class CCMParameters + extends AEADParameters +{ + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + * @param associatedText associated text, if any + */ + public CCMParameters(KeyParameter key, int macSize, byte[] nonce, byte[] associatedText) + { + super(key, macSize, nonce, associatedText); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DESParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DESParameters.java new file mode 100644 index 00000000..5bee3604 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DESParameters.java @@ -0,0 +1,107 @@ +package org.bouncycastle.crypto.params; + +public class DESParameters + extends KeyParameter +{ + public DESParameters( + byte[] key) + { + super(key); + + if (isWeakKey(key, 0)) + { + throw new IllegalArgumentException("attempt to create weak DES key"); + } + } + + /* + * DES Key length in bytes. + */ + static public final int DES_KEY_LENGTH = 8; + + /* + * Table of weak and semi-weak keys taken from Schneier pp281 + */ + static private final int N_DES_WEAK_KEYS = 16; + + static private byte[] DES_weak_keys = + { + /* weak keys */ + (byte)0x01,(byte)0x01,(byte)0x01,(byte)0x01, (byte)0x01,(byte)0x01,(byte)0x01,(byte)0x01, + (byte)0x1f,(byte)0x1f,(byte)0x1f,(byte)0x1f, (byte)0x0e,(byte)0x0e,(byte)0x0e,(byte)0x0e, + (byte)0xe0,(byte)0xe0,(byte)0xe0,(byte)0xe0, (byte)0xf1,(byte)0xf1,(byte)0xf1,(byte)0xf1, + (byte)0xfe,(byte)0xfe,(byte)0xfe,(byte)0xfe, (byte)0xfe,(byte)0xfe,(byte)0xfe,(byte)0xfe, + + /* semi-weak keys */ + (byte)0x01,(byte)0xfe,(byte)0x01,(byte)0xfe, (byte)0x01,(byte)0xfe,(byte)0x01,(byte)0xfe, + (byte)0x1f,(byte)0xe0,(byte)0x1f,(byte)0xe0, (byte)0x0e,(byte)0xf1,(byte)0x0e,(byte)0xf1, + (byte)0x01,(byte)0xe0,(byte)0x01,(byte)0xe0, (byte)0x01,(byte)0xf1,(byte)0x01,(byte)0xf1, + (byte)0x1f,(byte)0xfe,(byte)0x1f,(byte)0xfe, (byte)0x0e,(byte)0xfe,(byte)0x0e,(byte)0xfe, + (byte)0x01,(byte)0x1f,(byte)0x01,(byte)0x1f, (byte)0x01,(byte)0x0e,(byte)0x01,(byte)0x0e, + (byte)0xe0,(byte)0xfe,(byte)0xe0,(byte)0xfe, (byte)0xf1,(byte)0xfe,(byte)0xf1,(byte)0xfe, + (byte)0xfe,(byte)0x01,(byte)0xfe,(byte)0x01, (byte)0xfe,(byte)0x01,(byte)0xfe,(byte)0x01, + (byte)0xe0,(byte)0x1f,(byte)0xe0,(byte)0x1f, (byte)0xf1,(byte)0x0e,(byte)0xf1,(byte)0x0e, + (byte)0xe0,(byte)0x01,(byte)0xe0,(byte)0x01, (byte)0xf1,(byte)0x01,(byte)0xf1,(byte)0x01, + (byte)0xfe,(byte)0x1f,(byte)0xfe,(byte)0x1f, (byte)0xfe,(byte)0x0e,(byte)0xfe,(byte)0x0e, + (byte)0x1f,(byte)0x01,(byte)0x1f,(byte)0x01, (byte)0x0e,(byte)0x01,(byte)0x0e,(byte)0x01, + (byte)0xfe,(byte)0xe0,(byte)0xfe,(byte)0xe0, (byte)0xfe,(byte)0xf1,(byte)0xfe,(byte)0xf1 + }; + + /** + * DES has 16 weak keys. This method will check + * if the given DES key material is weak or semi-weak. + * Key material that is too short is regarded as weak. + * <p> + * See <a href="http://www.counterpane.com/applied.html">"Applied + * Cryptography"</a> by Bruce Schneier for more information. + * + * @return true if the given DES key material is weak or semi-weak, + * false otherwise. + */ + public static boolean isWeakKey( + byte[] key, + int offset) + { + if (key.length - offset < DES_KEY_LENGTH) + { + throw new IllegalArgumentException("key material too short."); + } + + nextkey: for (int i = 0; i < N_DES_WEAK_KEYS; i++) + { + for (int j = 0; j < DES_KEY_LENGTH; j++) + { + if (key[j + offset] != DES_weak_keys[i * DES_KEY_LENGTH + j]) + { + continue nextkey; + } + } + + return true; + } + return false; + } + + /** + * DES Keys use the LSB as the odd parity bit. This can + * be used to check for corrupt keys. + * + * @param bytes the byte array to set the parity on. + */ + public static void setOddParity( + byte[] bytes) + { + for (int i = 0; i < bytes.length; i++) + { + int b = bytes[i]; + bytes[i] = (byte)((b & 0xfe) | + ((((b >> 1) ^ + (b >> 2) ^ + (b >> 3) ^ + (b >> 4) ^ + (b >> 5) ^ + (b >> 6) ^ + (b >> 7)) ^ 0x01) & 0x01)); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DESedeParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DESedeParameters.java new file mode 100644 index 00000000..3a4bbfca --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DESedeParameters.java @@ -0,0 +1,57 @@ +package org.bouncycastle.crypto.params; + +public class DESedeParameters + extends DESParameters +{ + /* + * DES-EDE Key length in bytes. + */ + static public final int DES_EDE_KEY_LENGTH = 24; + + public DESedeParameters( + byte[] key) + { + super(key); + + if (isWeakKey(key, 0, key.length)) + { + throw new IllegalArgumentException("attempt to create weak DESede key"); + } + } + + /** + * return true if the passed in key is a DES-EDE weak key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + * @param length number of bytes making up the key + */ + public static boolean isWeakKey( + byte[] key, + int offset, + int length) + { + for (int i = offset; i < length; i += DES_KEY_LENGTH) + { + if (DESParameters.isWeakKey(key, i)) + { + return true; + } + } + + return false; + } + + /** + * return true if the passed in key is a DES-EDE weak key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + */ + public static boolean isWeakKey( + byte[] key, + int offset) + { + return isWeakKey(key, offset, key.length - offset); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DHKeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DHKeyGenerationParameters.java new file mode 100644 index 00000000..34c730eb --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DHKeyGenerationParameters.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class DHKeyGenerationParameters + extends KeyGenerationParameters +{ + private DHParameters params; + + public DHKeyGenerationParameters( + SecureRandom random, + DHParameters params) + { + super(random, getStrength(params)); + + this.params = params; + } + + public DHParameters getParameters() + { + return params; + } + + static int getStrength(DHParameters params) + { + return params.getL() != 0 ? params.getL() : params.getP().bitLength(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DHKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DHKeyParameters.java new file mode 100644 index 00000000..e686f357 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DHKeyParameters.java @@ -0,0 +1,54 @@ +package org.bouncycastle.crypto.params; + + +public class DHKeyParameters + extends AsymmetricKeyParameter +{ + private DHParameters params; + + protected DHKeyParameters( + boolean isPrivate, + DHParameters params) + { + super(isPrivate); + + this.params = params; + } + + public DHParameters getParameters() + { + return params; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHKeyParameters)) + { + return false; + } + + DHKeyParameters dhKey = (DHKeyParameters)obj; + + if (params == null) + { + return dhKey.getParameters() == null; + } + else + { + return params.equals(dhKey.getParameters()); + } + } + + public int hashCode() + { + int code = isPrivate() ? 0 : 1; + + if (params != null) + { + code ^= params.hashCode(); + } + + return code; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DHParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DHParameters.java new file mode 100644 index 00000000..b679287c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DHParameters.java @@ -0,0 +1,189 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.CipherParameters; + +public class DHParameters + implements CipherParameters +{ + private static final int DEFAULT_MINIMUM_LENGTH = 160; + + // not final due to compiler bug in "simpler" JDKs + private BigInteger g; + private BigInteger p; + private BigInteger q; + private BigInteger j; + private int m; + private int l; + private DHValidationParameters validation; + + private static int getDefaultMParam( + int lParam) + { + if (lParam == 0) + { + return DEFAULT_MINIMUM_LENGTH; + } + + return lParam < DEFAULT_MINIMUM_LENGTH ? lParam : DEFAULT_MINIMUM_LENGTH; + } + + public DHParameters( + BigInteger p, + BigInteger g) + { + this(p, g, null, 0); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q) + { + this(p, g, q, 0); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int l) + { + this(p, g, q, getDefaultMParam(l), l, null, null); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int m, + int l) + { + this(p, g, q, m, l, null, null); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + BigInteger j, + DHValidationParameters validation) + { + this(p, g, q, DEFAULT_MINIMUM_LENGTH, 0, j, validation); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int m, + int l, + BigInteger j, + DHValidationParameters validation) + { + if (l != 0) + { + BigInteger bigL = BigInteger.valueOf(2L ^ (l - 1)); + if (bigL.compareTo(p) == 1) + { + throw new IllegalArgumentException("when l value specified, it must satisfy 2^(l-1) <= p"); + } + if (l < m) + { + throw new IllegalArgumentException("when l value specified, it may not be less than m value"); + } + } + + this.g = g; + this.p = p; + this.q = q; + this.m = m; + this.l = l; + this.j = j; + this.validation = validation; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getG() + { + return g; + } + + public BigInteger getQ() + { + return q; + } + + /** + * Return the subgroup factor J. + * + * @return subgroup factor + */ + public BigInteger getJ() + { + return j; + } + + /** + * Return the minimum length of the private value. + * + * @return the minimum length of the private value in bits. + */ + public int getM() + { + return m; + } + + /** + * Return the private value length in bits - if set, zero otherwise + * + * @return the private value length in bits, zero otherwise. + */ + public int getL() + { + return l; + } + + public DHValidationParameters getValidationParameters() + { + return validation; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHParameters)) + { + return false; + } + + DHParameters pm = (DHParameters)obj; + + if (this.getQ() != null) + { + if (!this.getQ().equals(pm.getQ())) + { + return false; + } + } + else + { + if (pm.getQ() != null) + { + return false; + } + } + + return pm.getP().equals(p) && pm.getG().equals(g); + } + + public int hashCode() + { + return getP().hashCode() ^ getG().hashCode() ^ (getQ() != null ? getQ().hashCode() : 0); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DHPrivateKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DHPrivateKeyParameters.java new file mode 100644 index 00000000..ee1b34f9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DHPrivateKeyParameters.java @@ -0,0 +1,41 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DHPrivateKeyParameters + extends DHKeyParameters +{ + private BigInteger x; + + public DHPrivateKeyParameters( + BigInteger x, + DHParameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } + + public int hashCode() + { + return x.hashCode() ^ super.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHPrivateKeyParameters)) + { + return false; + } + + DHPrivateKeyParameters other = (DHPrivateKeyParameters)obj; + + return other.getX().equals(this.x) && super.equals(obj); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java new file mode 100644 index 00000000..ed531603 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java @@ -0,0 +1,41 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DHPublicKeyParameters + extends DHKeyParameters +{ + private BigInteger y; + + public DHPublicKeyParameters( + BigInteger y, + DHParameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } + + public int hashCode() + { + return y.hashCode() ^ super.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHPublicKeyParameters)) + { + return false; + } + + DHPublicKeyParameters other = (DHPublicKeyParameters)obj; + + return other.getY().equals(y) && super.equals(obj); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DHValidationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DHValidationParameters.java new file mode 100644 index 00000000..b22f7a03 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DHValidationParameters.java @@ -0,0 +1,50 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.util.Arrays; + +public class DHValidationParameters +{ + private byte[] seed; + private int counter; + + public DHValidationParameters( + byte[] seed, + int counter) + { + this.seed = seed; + this.counter = counter; + } + + public int getCounter() + { + return counter; + } + + public byte[] getSeed() + { + return seed; + } + + public boolean equals( + Object o) + { + if (!(o instanceof DHValidationParameters)) + { + return false; + } + + DHValidationParameters other = (DHValidationParameters)o; + + if (other.counter != this.counter) + { + return false; + } + + return Arrays.areEqual(this.seed, other.seed); + } + + public int hashCode() + { + return counter ^ Arrays.hashCode(seed); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAKeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAKeyGenerationParameters.java new file mode 100644 index 00000000..29fa91e6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAKeyGenerationParameters.java @@ -0,0 +1,25 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class DSAKeyGenerationParameters + extends KeyGenerationParameters +{ + private DSAParameters params; + + public DSAKeyGenerationParameters( + SecureRandom random, + DSAParameters params) + { + super(random, params.getP().bitLength() - 1); + + this.params = params; + } + + public DSAParameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAKeyParameters.java new file mode 100644 index 00000000..11bb9d97 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAKeyParameters.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.params; + +public class DSAKeyParameters + extends AsymmetricKeyParameter +{ + private DSAParameters params; + + public DSAKeyParameters( + boolean isPrivate, + DSAParameters params) + { + super(isPrivate); + + this.params = params; + } + + public DSAParameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java new file mode 100644 index 00000000..ba841b8e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java @@ -0,0 +1,80 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +public class DSAParameterGenerationParameters +{ + public static final int DIGITAL_SIGNATURE_USAGE = 1; + public static final int KEY_ESTABLISHMENT_USAGE = 2; + + private final int l; + private final int n; + private final int usageIndex; + private final int certainty; + private final SecureRandom random; + + /** + * Construct without a usage index, this will do a random construction of G. + * + * @param L desired length of prime P in bits (the effective key size). + * @param N desired length of prime Q in bits. + * @param certainty certainty level for prime number generation. + * @param random the source of randomness to use. + */ + public DSAParameterGenerationParameters( + int L, + int N, + int certainty, + SecureRandom random) + { + this(L, N, certainty, random, -1); + } + + /** + * Construct for a specific usage index - this has the effect of using verifiable canonical generation of G. + * + * @param L desired length of prime P in bits (the effective key size). + * @param N desired length of prime Q in bits. + * @param certainty certainty level for prime number generation. + * @param random the source of randomness to use. + * @param usageIndex a valid usage index. + */ + public DSAParameterGenerationParameters( + int L, + int N, + int certainty, + SecureRandom random, + int usageIndex) + { + this.l = L; + this.n = N; + this.certainty = certainty; + this.usageIndex = usageIndex; + this.random = random; + } + + public int getL() + { + return l; + } + + public int getN() + { + return n; + } + + public int getCertainty() + { + return certainty; + } + + public SecureRandom getRandom() + { + return random; + } + + public int getUsageIndex() + { + return usageIndex; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAParameters.java new file mode 100644 index 00000000..7f76d117 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAParameters.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.CipherParameters; + +public class DSAParameters + implements CipherParameters +{ + private BigInteger g; + private BigInteger q; + private BigInteger p; + private DSAValidationParameters validation; + + public DSAParameters( + BigInteger p, + BigInteger q, + BigInteger g) + { + this.g = g; + this.p = p; + this.q = q; + } + + public DSAParameters( + BigInteger p, + BigInteger q, + BigInteger g, + DSAValidationParameters params) + { + this.g = g; + this.p = p; + this.q = q; + this.validation = params; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getG() + { + return g; + } + + public DSAValidationParameters getValidationParameters() + { + return validation; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DSAParameters)) + { + return false; + } + + DSAParameters pm = (DSAParameters)obj; + + return (pm.getP().equals(p) && pm.getQ().equals(q) && pm.getG().equals(g)); + } + + public int hashCode() + { + return getP().hashCode() ^ getQ().hashCode() ^ getG().hashCode(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAPrivateKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAPrivateKeyParameters.java new file mode 100644 index 00000000..3bef3f40 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAPrivateKeyParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DSAPrivateKeyParameters + extends DSAKeyParameters +{ + private BigInteger x; + + public DSAPrivateKeyParameters( + BigInteger x, + DSAParameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAPublicKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAPublicKeyParameters.java new file mode 100644 index 00000000..c0066568 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAPublicKeyParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DSAPublicKeyParameters + extends DSAKeyParameters +{ + private BigInteger y; + + public DSAPublicKeyParameters( + BigInteger y, + DSAParameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/DSAValidationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/DSAValidationParameters.java new file mode 100644 index 00000000..07d93d07 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/DSAValidationParameters.java @@ -0,0 +1,65 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.util.Arrays; + +public class DSAValidationParameters +{ + private int usageIndex; + private byte[] seed; + private int counter; + + public DSAValidationParameters( + byte[] seed, + int counter) + { + this(seed, counter, -1); + } + + public DSAValidationParameters( + byte[] seed, + int counter, + int usageIndex) + { + this.seed = seed; + this.counter = counter; + this.usageIndex = usageIndex; + } + + public int getCounter() + { + return counter; + } + + public byte[] getSeed() + { + return seed; + } + + public int getUsageIndex() + { + return usageIndex; + } + + public int hashCode() + { + return counter ^ Arrays.hashCode(seed); + } + + public boolean equals( + Object o) + { + if (!(o instanceof DSAValidationParameters)) + { + return false; + } + + DSAValidationParameters other = (DSAValidationParameters)o; + + if (other.counter != this.counter) + { + return false; + } + + return Arrays.areEqual(this.seed, other.seed); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java new file mode 100644 index 00000000..05a13274 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; + +public class ECDomainParameters + implements ECConstants +{ + private ECCurve curve; + private byte[] seed; + private ECPoint G; + private BigInteger n; + private BigInteger h; + + public ECDomainParameters( + ECCurve curve, + ECPoint G, + BigInteger n) + { + this(curve, G, n, ONE, null); + } + + public ECDomainParameters( + ECCurve curve, + ECPoint G, + BigInteger n, + BigInteger h) + { + this(curve, G, n, h, null); + } + + public ECDomainParameters( + ECCurve curve, + ECPoint G, + BigInteger n, + BigInteger h, + byte[] seed) + { + this.curve = curve; + this.G = G; + this.n = n; + this.h = h; + this.seed = seed; + } + + public ECCurve getCurve() + { + return curve; + } + + public ECPoint getG() + { + return G; + } + + public BigInteger getN() + { + return n; + } + + public BigInteger getH() + { + return h; + } + + public byte[] getSeed() + { + return Arrays.clone(seed); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ECKeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ECKeyGenerationParameters.java new file mode 100644 index 00000000..be3f20f5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ECKeyGenerationParameters.java @@ -0,0 +1,25 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class ECKeyGenerationParameters + extends KeyGenerationParameters +{ + private ECDomainParameters domainParams; + + public ECKeyGenerationParameters( + ECDomainParameters domainParams, + SecureRandom random) + { + super(random, domainParams.getN().bitLength()); + + this.domainParams = domainParams; + } + + public ECDomainParameters getDomainParameters() + { + return domainParams; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ECKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ECKeyParameters.java new file mode 100644 index 00000000..19825c5b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ECKeyParameters.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.params; + +public class ECKeyParameters + extends AsymmetricKeyParameter +{ + ECDomainParameters params; + + protected ECKeyParameters( + boolean isPrivate, + ECDomainParameters params) + { + super(isPrivate); + + this.params = params; + } + + public ECDomainParameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ECPrivateKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ECPrivateKeyParameters.java new file mode 100644 index 00000000..3e49983e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ECPrivateKeyParameters.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class ECPrivateKeyParameters + extends ECKeyParameters +{ + BigInteger d; + + public ECPrivateKeyParameters( + BigInteger d, + ECDomainParameters params) + { + super(true, params); + this.d = d; + } + + public BigInteger getD() + { + return d; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java new file mode 100644 index 00000000..5fbea19e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.math.ec.ECPoint; + +public class ECPublicKeyParameters + extends ECKeyParameters +{ + ECPoint Q; + + public ECPublicKeyParameters( + ECPoint Q, + ECDomainParameters params) + { + super(false, params); + this.Q = Q; + } + + public ECPoint getQ() + { + return Q; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java new file mode 100644 index 00000000..f5fbabd5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class ElGamalKeyGenerationParameters + extends KeyGenerationParameters +{ + private ElGamalParameters params; + + public ElGamalKeyGenerationParameters( + SecureRandom random, + ElGamalParameters params) + { + super(random, getStrength(params)); + + this.params = params; + } + + public ElGamalParameters getParameters() + { + return params; + } + + static int getStrength(ElGamalParameters params) + { + return params.getL() != 0 ? params.getL() : params.getP().bitLength(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ElGamalKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalKeyParameters.java new file mode 100644 index 00000000..72506931 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalKeyParameters.java @@ -0,0 +1,47 @@ +package org.bouncycastle.crypto.params; + + +public class ElGamalKeyParameters + extends AsymmetricKeyParameter +{ + private ElGamalParameters params; + + protected ElGamalKeyParameters( + boolean isPrivate, + ElGamalParameters params) + { + super(isPrivate); + + this.params = params; + } + + public ElGamalParameters getParameters() + { + return params; + } + + public int hashCode() + { + return (params != null) ? params.hashCode() : 0; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalKeyParameters)) + { + return false; + } + + ElGamalKeyParameters dhKey = (ElGamalKeyParameters)obj; + + if (params == null) + { + return dhKey.getParameters() == null; + } + else + { + return params.equals(dhKey.getParameters()); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ElGamalParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalParameters.java new file mode 100644 index 00000000..166eff35 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalParameters.java @@ -0,0 +1,69 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.CipherParameters; + +public class ElGamalParameters + implements CipherParameters +{ + private BigInteger g; + private BigInteger p; + private int l; + + public ElGamalParameters( + BigInteger p, + BigInteger g) + { + this(p, g, 0); + } + + public ElGamalParameters( + BigInteger p, + BigInteger g, + int l) + { + this.g = g; + this.p = p; + this.l = l; + } + + public BigInteger getP() + { + return p; + } + + /** + * return the generator - g + */ + public BigInteger getG() + { + return g; + } + + /** + * return private value limit - l + */ + public int getL() + { + return l; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalParameters)) + { + return false; + } + + ElGamalParameters pm = (ElGamalParameters)obj; + + return pm.getP().equals(p) && pm.getG().equals(g) && pm.getL() == l; + } + + public int hashCode() + { + return (getP().hashCode() ^ getG().hashCode()) + l; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java new file mode 100644 index 00000000..b8fb529f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java @@ -0,0 +1,46 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class ElGamalPrivateKeyParameters + extends ElGamalKeyParameters +{ + private BigInteger x; + + public ElGamalPrivateKeyParameters( + BigInteger x, + ElGamalParameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalPrivateKeyParameters)) + { + return false; + } + + ElGamalPrivateKeyParameters pKey = (ElGamalPrivateKeyParameters)obj; + + if (!pKey.getX().equals(x)) + { + return false; + } + + return super.equals(obj); + } + + public int hashCode() + { + return getX().hashCode(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java new file mode 100644 index 00000000..d7da7a93 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java @@ -0,0 +1,41 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class ElGamalPublicKeyParameters + extends ElGamalKeyParameters +{ + private BigInteger y; + + public ElGamalPublicKeyParameters( + BigInteger y, + ElGamalParameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } + + public int hashCode() + { + return y.hashCode() ^ super.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalPublicKeyParameters)) + { + return false; + } + + ElGamalPublicKeyParameters other = (ElGamalPublicKeyParameters)obj; + + return other.getY().equals(y) && super.equals(obj); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java new file mode 100644 index 00000000..74e05a96 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java @@ -0,0 +1,25 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +import java.security.SecureRandom; + +public class GOST3410KeyGenerationParameters + extends KeyGenerationParameters +{ + private GOST3410Parameters params; + + public GOST3410KeyGenerationParameters( + SecureRandom random, + GOST3410Parameters params) + { + super(random, params.getP().bitLength() - 1); + + this.params = params; + } + + public GOST3410Parameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/GOST3410KeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410KeyParameters.java new file mode 100644 index 00000000..6716924a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410KeyParameters.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.params; + +public class GOST3410KeyParameters + extends AsymmetricKeyParameter +{ + private GOST3410Parameters params; + + public GOST3410KeyParameters( + boolean isPrivate, + GOST3410Parameters params) + { + super(isPrivate); + + this.params = params; + } + + public GOST3410Parameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/GOST3410Parameters.java b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410Parameters.java new file mode 100644 index 00000000..07450f61 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410Parameters.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +import java.math.BigInteger; + +public class GOST3410Parameters + implements CipherParameters +{ + private BigInteger p; + private BigInteger q; + private BigInteger a; + private GOST3410ValidationParameters validation; + + public GOST3410Parameters( + BigInteger p, + BigInteger q, + BigInteger a) + { + this.p = p; + this.q = q; + this.a = a; + } + + public GOST3410Parameters( + BigInteger p, + BigInteger q, + BigInteger a, + GOST3410ValidationParameters params) + { + this.a = a; + this.p = p; + this.q = q; + this.validation = params; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getA() + { + return a; + } + + public GOST3410ValidationParameters getValidationParameters() + { + return validation; + } + + public int hashCode() + { + return p.hashCode() ^ q.hashCode() ^ a.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof GOST3410Parameters)) + { + return false; + } + + GOST3410Parameters pm = (GOST3410Parameters)obj; + + return (pm.getP().equals(p) && pm.getQ().equals(q) && pm.getA().equals(a)); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java new file mode 100644 index 00000000..408e0657 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class GOST3410PrivateKeyParameters + extends GOST3410KeyParameters +{ + private BigInteger x; + + public GOST3410PrivateKeyParameters( + BigInteger x, + GOST3410Parameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java new file mode 100644 index 00000000..9dfd2d98 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class GOST3410PublicKeyParameters + extends GOST3410KeyParameters +{ + private BigInteger y; + + public GOST3410PublicKeyParameters( + BigInteger y, + GOST3410Parameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/GOST3410ValidationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410ValidationParameters.java new file mode 100644 index 00000000..c2a4fb57 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/GOST3410ValidationParameters.java @@ -0,0 +1,84 @@ +package org.bouncycastle.crypto.params; + +public class GOST3410ValidationParameters +{ + private int x0; + private int c; + private long x0L; + private long cL; + + + public GOST3410ValidationParameters( + int x0, + int c) + { + this.x0 = x0; + this.c = c; + } + + public GOST3410ValidationParameters( + long x0L, + long cL) + { + this.x0L = x0L; + this.cL = cL; + } + + public int getC() + { + return c; + } + + public int getX0() + { + return x0; + } + + public long getCL() + { + return cL; + } + + public long getX0L() + { + return x0L; + } + + public boolean equals( + Object o) + { + if (!(o instanceof GOST3410ValidationParameters)) + { + return false; + } + + GOST3410ValidationParameters other = (GOST3410ValidationParameters)o; + + if (other.c != this.c) + { + return false; + } + + if (other.x0 != this.x0) + { + return false; + } + + if (other.cL != this.cL) + { + return false; + } + + if (other.x0L != this.x0L) + { + return false; + } + + return true; + } + + public int hashCode() + { + return x0 ^ c ^ (int) x0L ^ (int)(x0L >> 32) ^ (int) cL ^ (int)(cL >> 32); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/HKDFParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/HKDFParameters.java new file mode 100644 index 00000000..2db3ce61 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/HKDFParameters.java @@ -0,0 +1,123 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.util.Arrays; + +/** + * Parameter class for the HKDFBytesGenerator class. + */ +public class HKDFParameters + implements DerivationParameters +{ + private final byte[] ikm; + private final boolean skipExpand; + private final byte[] salt; + private final byte[] info; + + private HKDFParameters(final byte[] ikm, final boolean skip, + final byte[] salt, final byte[] info) + { + if (ikm == null) + { + throw new IllegalArgumentException( + "IKM (input keying material) should not be null"); + } + + this.ikm = Arrays.clone(ikm); + + this.skipExpand = skip; + + if (salt == null || salt.length == 0) + { + this.salt = null; + } + else + { + this.salt = Arrays.clone(salt); + } + + if (info == null) + { + this.info = new byte[0]; + } + else + { + this.info = Arrays.clone(info); + } + } + + /** + * Generates parameters for HKDF, specifying both the optional salt and + * optional info. Step 1: Extract won't be skipped. + * + * @param ikm the input keying material or seed + * @param salt the salt to use, may be null for a salt for hashLen zeros + * @param info the info to use, may be null for an info field of zero bytes + */ + public HKDFParameters(final byte[] ikm, final byte[] salt, final byte[] info) + { + this(ikm, false, salt, info); + } + + /** + * Factory method that makes the HKDF skip the extract part of the key + * derivation function. + * + * @param ikm the input keying material or seed, directly used for step 2: + * Expand + * @param info the info to use, may be null for an info field of zero bytes + * @return HKDFParameters that makes the implementation skip step 1 + */ + public static HKDFParameters skipExtractParameters(final byte[] ikm, + final byte[] info) + { + + return new HKDFParameters(ikm, true, null, info); + } + + public static HKDFParameters defaultParameters(final byte[] ikm) + { + return new HKDFParameters(ikm, false, null, null); + } + + /** + * Returns the input keying material or seed. + * + * @return the keying material + */ + public byte[] getIKM() + { + return Arrays.clone(ikm); + } + + /** + * Returns if step 1: extract has to be skipped or not + * + * @return true for skipping, false for no skipping of step 1 + */ + public boolean skipExtract() + { + return skipExpand; + } + + /** + * Returns the salt, or null if the salt should be generated as a byte array + * of HashLen zeros. + * + * @return the salt, or null + */ + public byte[] getSalt() + { + return Arrays.clone(salt); + } + + /** + * Returns the info field, which may be empty (null is converted to empty). + * + * @return the info field, never null + */ + public byte[] getInfo() + { + return Arrays.clone(info); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/IESParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/IESParameters.java new file mode 100644 index 00000000..0600b346 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/IESParameters.java @@ -0,0 +1,44 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +/** + * parameters for using an integrated cipher in stream mode. + */ +public class IESParameters + implements CipherParameters +{ + private byte[] derivation; + private byte[] encoding; + private int macKeySize; + + /** + * @param derivation the derivation parameter for the KDF function. + * @param encoding the encoding parameter for the KDF function. + * @param macKeySize the size of the MAC key (in bits). + */ + public IESParameters( + byte[] derivation, + byte[] encoding, + int macKeySize) + { + this.derivation = derivation; + this.encoding = encoding; + this.macKeySize = macKeySize; + } + + public byte[] getDerivationV() + { + return derivation; + } + + public byte[] getEncodingV() + { + return encoding; + } + + public int getMacKeySize() + { + return macKeySize; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/IESWithCipherParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/IESWithCipherParameters.java new file mode 100644 index 00000000..ef61b2cc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/IESWithCipherParameters.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto.params; + + +public class IESWithCipherParameters + extends IESParameters +{ + private int cipherKeySize; + + /** + * @param derivation the derivation parameter for the KDF function. + * @param encoding the encoding parameter for the KDF function. + * @param macKeySize the size of the MAC key (in bits). + * @param cipherKeySize the size of the associated Cipher key (in bits). + */ + public IESWithCipherParameters( + byte[] derivation, + byte[] encoding, + int macKeySize, + int cipherKeySize) + { + super(derivation, encoding, macKeySize); + + this.cipherKeySize = cipherKeySize; + } + + public int getCipherKeySize() + { + return cipherKeySize; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ISO18033KDFParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/ISO18033KDFParameters.java new file mode 100644 index 00000000..8dffe2ee --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ISO18033KDFParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; + +/** + * parameters for Key derivation functions for ISO-18033 + */ +public class ISO18033KDFParameters + implements DerivationParameters +{ + byte[] seed; + + public ISO18033KDFParameters( + byte[] seed) + { + this.seed = seed; + } + + public byte[] getSeed() + { + return seed; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/KDFParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/KDFParameters.java new file mode 100644 index 00000000..f3bac647 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/KDFParameters.java @@ -0,0 +1,31 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; + +/** + * parameters for Key derivation functions for IEEE P1363a + */ +public class KDFParameters + implements DerivationParameters +{ + byte[] iv; + byte[] shared; + + public KDFParameters( + byte[] shared, + byte[] iv) + { + this.shared = shared; + this.iv = iv; + } + + public byte[] getSharedSecret() + { + return shared; + } + + public byte[] getIV() + { + return iv; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/KeyParameter.java b/core/src/main/java/org/bouncycastle/crypto/params/KeyParameter.java new file mode 100644 index 00000000..5c4fe0e0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/KeyParameter.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class KeyParameter + implements CipherParameters +{ + private byte[] key; + + public KeyParameter( + byte[] key) + { + this(key, 0, key.length); + } + + public KeyParameter( + byte[] key, + int keyOff, + int keyLen) + { + this.key = new byte[keyLen]; + + System.arraycopy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] getKey() + { + return key; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/MGFParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/MGFParameters.java new file mode 100644 index 00000000..847bd989 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/MGFParameters.java @@ -0,0 +1,32 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; + +/** + * parameters for mask derivation functions. + */ +public class MGFParameters + implements DerivationParameters +{ + byte[] seed; + + public MGFParameters( + byte[] seed) + { + this(seed, 0, seed.length); + } + + public MGFParameters( + byte[] seed, + int off, + int len) + { + this.seed = new byte[len]; + System.arraycopy(seed, off, this.seed, 0, len); + } + + public byte[] getSeed() + { + return seed; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/MQVPrivateParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/MQVPrivateParameters.java new file mode 100644 index 00000000..832c07fd --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/MQVPrivateParameters.java @@ -0,0 +1,43 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class MQVPrivateParameters + implements CipherParameters +{ + private ECPrivateKeyParameters staticPrivateKey; + private ECPrivateKeyParameters ephemeralPrivateKey; + private ECPublicKeyParameters ephemeralPublicKey; + + public MQVPrivateParameters( + ECPrivateKeyParameters staticPrivateKey, + ECPrivateKeyParameters ephemeralPrivateKey) + { + this(staticPrivateKey, ephemeralPrivateKey, null); + } + + public MQVPrivateParameters( + ECPrivateKeyParameters staticPrivateKey, + ECPrivateKeyParameters ephemeralPrivateKey, + ECPublicKeyParameters ephemeralPublicKey) + { + this.staticPrivateKey = staticPrivateKey; + this.ephemeralPrivateKey = ephemeralPrivateKey; + this.ephemeralPublicKey = ephemeralPublicKey; + } + + public ECPrivateKeyParameters getStaticPrivateKey() + { + return staticPrivateKey; + } + + public ECPrivateKeyParameters getEphemeralPrivateKey() + { + return ephemeralPrivateKey; + } + + public ECPublicKeyParameters getEphemeralPublicKey() + { + return ephemeralPublicKey; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/MQVPublicParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/MQVPublicParameters.java new file mode 100644 index 00000000..b3b24671 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/MQVPublicParameters.java @@ -0,0 +1,28 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class MQVPublicParameters + implements CipherParameters +{ + private ECPublicKeyParameters staticPublicKey; + private ECPublicKeyParameters ephemeralPublicKey; + + public MQVPublicParameters( + ECPublicKeyParameters staticPublicKey, + ECPublicKeyParameters ephemeralPublicKey) + { + this.staticPublicKey = staticPublicKey; + this.ephemeralPublicKey = ephemeralPublicKey; + } + + public ECPublicKeyParameters getStaticPublicKey() + { + return staticPublicKey; + } + + public ECPublicKeyParameters getEphemeralPublicKey() + { + return ephemeralPublicKey; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java new file mode 100644 index 00000000..758fcd79 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java @@ -0,0 +1,97 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +/** + * Parameters for NaccacheStern public private key generation. For details on + * this cipher, please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternKeyGenerationParameters extends KeyGenerationParameters +{ + + // private BigInteger publicExponent; + private int certainty; + + private int cntSmallPrimes; + + private boolean debug = false; + + /** + * Parameters for generating a NaccacheStern KeyPair. + * + * @param random + * The source of randomness + * @param strength + * The desired strength of the Key in Bits + * @param certainty + * the probability that the generated primes are not really prime + * as integer: 2^(-certainty) is then the probability + * @param cntSmallPrimes + * How many small key factors are desired + */ + public NaccacheSternKeyGenerationParameters(SecureRandom random, int strength, int certainty, int cntSmallPrimes) + { + this(random, strength, certainty, cntSmallPrimes, false); + } + + /** + * Parameters for a NaccacheStern KeyPair. + * + * @param random + * The source of randomness + * @param strength + * The desired strength of the Key in Bits + * @param certainty + * the probability that the generated primes are not really prime + * as integer: 2^(-certainty) is then the probability + * @param cntSmallPrimes + * How many small key factors are desired + * @param debug + * Turn debugging on or off (reveals secret information, use with + * caution) + */ + public NaccacheSternKeyGenerationParameters(SecureRandom random, + int strength, int certainty, int cntSmallPrimes, boolean debug) + { + super(random, strength); + + this.certainty = certainty; + if (cntSmallPrimes % 2 == 1) + { + throw new IllegalArgumentException("cntSmallPrimes must be a multiple of 2"); + } + if (cntSmallPrimes < 30) + { + throw new IllegalArgumentException("cntSmallPrimes must be >= 30 for security reasons"); + } + this.cntSmallPrimes = cntSmallPrimes; + + this.debug = debug; + } + + /** + * @return Returns the certainty. + */ + public int getCertainty() + { + return certainty; + } + + /** + * @return Returns the cntSmallPrimes. + */ + public int getCntSmallPrimes() + { + return cntSmallPrimes; + } + + public boolean isDebug() + { + return debug; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java new file mode 100644 index 00000000..21b6a286 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +/** + * Public key parameters for NaccacheStern cipher. For details on this cipher, + * please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternKeyParameters extends AsymmetricKeyParameter +{ + + private BigInteger g, n; + + int lowerSigmaBound; + + /** + * @param privateKey + */ + public NaccacheSternKeyParameters(boolean privateKey, BigInteger g, BigInteger n, int lowerSigmaBound) + { + super(privateKey); + this.g = g; + this.n = n; + this.lowerSigmaBound = lowerSigmaBound; + } + + /** + * @return Returns the g. + */ + public BigInteger getG() + { + return g; + } + + /** + * @return Returns the lowerSigmaBound. + */ + public int getLowerSigmaBound() + { + return lowerSigmaBound; + } + + /** + * @return Returns the n. + */ + public BigInteger getModulus() + { + return n; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java new file mode 100644 index 00000000..6d0ec486 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java @@ -0,0 +1,50 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; +import java.util.Vector; + +/** + * Private key parameters for NaccacheStern cipher. For details on this cipher, + * please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternPrivateKeyParameters extends NaccacheSternKeyParameters +{ + private BigInteger phi_n; + private Vector smallPrimes; + + /** + * Constructs a NaccacheSternPrivateKey + * + * @param g + * the public enryption parameter g + * @param n + * the public modulus n = p*q + * @param lowerSigmaBound + * the public lower sigma bound up to which data can be encrypted + * @param smallPrimes + * the small primes, of which sigma is constructed in the right + * order + * @param phi_n + * the private modulus phi(n) = (p-1)(q-1) + */ + public NaccacheSternPrivateKeyParameters(BigInteger g, BigInteger n, + int lowerSigmaBound, Vector smallPrimes, + BigInteger phi_n) + { + super(true, g, n, lowerSigmaBound); + this.smallPrimes = smallPrimes; + this.phi_n = phi_n; + } + + public BigInteger getPhi_n() + { + return phi_n; + } + + public Vector getSmallPrimes() + { + return smallPrimes; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithIV.java b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithIV.java new file mode 100644 index 00000000..4a1e6e9a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithIV.java @@ -0,0 +1,39 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class ParametersWithIV + implements CipherParameters +{ + private byte[] iv; + private CipherParameters parameters; + + public ParametersWithIV( + CipherParameters parameters, + byte[] iv) + { + this(parameters, iv, 0, iv.length); + } + + public ParametersWithIV( + CipherParameters parameters, + byte[] iv, + int ivOff, + int ivLen) + { + this.iv = new byte[ivLen]; + this.parameters = parameters; + + System.arraycopy(iv, ivOff, this.iv, 0, ivLen); + } + + public byte[] getIV() + { + return iv; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java new file mode 100644 index 00000000..a7b18e51 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java @@ -0,0 +1,36 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +import java.security.SecureRandom; + +public class ParametersWithRandom + implements CipherParameters +{ + private SecureRandom random; + private CipherParameters parameters; + + public ParametersWithRandom( + CipherParameters parameters, + SecureRandom random) + { + this.random = random; + this.parameters = parameters; + } + + public ParametersWithRandom( + CipherParameters parameters) + { + this(parameters, new SecureRandom()); + } + + public SecureRandom getRandom() + { + return random; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithSBox.java b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithSBox.java new file mode 100644 index 00000000..b226a9d9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithSBox.java @@ -0,0 +1,28 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class ParametersWithSBox + implements CipherParameters +{ + private CipherParameters parameters; + private byte[] sBox; + + public ParametersWithSBox( + CipherParameters parameters, + byte[] sBox) + { + this.parameters = parameters; + this.sBox = sBox; + } + + public byte[] getSBox() + { + return sBox; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithSalt.java b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithSalt.java new file mode 100644 index 00000000..73765dd5 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/ParametersWithSalt.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +/** + * Cipher parameters with a fixed salt value associated with them. + */ +public class ParametersWithSalt + implements CipherParameters +{ + private byte[] salt; + private CipherParameters parameters; + + public ParametersWithSalt( + CipherParameters parameters, + byte[] salt) + { + this(parameters, salt, 0, salt.length); + } + + public ParametersWithSalt( + CipherParameters parameters, + byte[] salt, + int saltOff, + int saltLen) + { + this.salt = new byte[saltLen]; + this.parameters = parameters; + + System.arraycopy(salt, saltOff, this.salt, 0, saltLen); + } + + public byte[] getSalt() + { + return salt; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RC2Parameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RC2Parameters.java new file mode 100644 index 00000000..dc33ec5b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/RC2Parameters.java @@ -0,0 +1,36 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class RC2Parameters + implements CipherParameters +{ + private byte[] key; + private int bits; + + public RC2Parameters( + byte[] key) + { + this(key, (key.length > 128) ? 1024 : (key.length * 8)); + } + + public RC2Parameters( + byte[] key, + int bits) + { + this.key = new byte[key.length]; + this.bits = bits; + + System.arraycopy(key, 0, this.key, 0, key.length); + } + + public byte[] getKey() + { + return key; + } + + public int getEffectiveKeyBits() + { + return bits; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RC5Parameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RC5Parameters.java new file mode 100644 index 00000000..6cbd57f1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/RC5Parameters.java @@ -0,0 +1,35 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class RC5Parameters + implements CipherParameters +{ + private byte[] key; + private int rounds; + + public RC5Parameters( + byte[] key, + int rounds) + { + if (key.length > 255) + { + throw new IllegalArgumentException("RC5 key length can be no greater than 255"); + } + + this.key = new byte[key.length]; + this.rounds = rounds; + + System.arraycopy(key, 0, this.key, 0, key.length); + } + + public byte[] getKey() + { + return key; + } + + public int getRounds() + { + return rounds; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RSABlindingParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RSABlindingParameters.java new file mode 100644 index 00000000..c7fa6ba6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/RSABlindingParameters.java @@ -0,0 +1,35 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +import java.math.BigInteger; + +public class RSABlindingParameters + implements CipherParameters +{ + private RSAKeyParameters publicKey; + private BigInteger blindingFactor; + + public RSABlindingParameters( + RSAKeyParameters publicKey, + BigInteger blindingFactor) + { + if (publicKey instanceof RSAPrivateCrtKeyParameters) + { + throw new IllegalArgumentException("RSA parameters should be for a public key"); + } + + this.publicKey = publicKey; + this.blindingFactor = blindingFactor; + } + + public RSAKeyParameters getPublicKey() + { + return publicKey; + } + + public BigInteger getBlindingFactor() + { + return blindingFactor; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyGenerationParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyGenerationParameters.java new file mode 100644 index 00000000..38b55fcd --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyGenerationParameters.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class RSAKeyGenerationParameters + extends KeyGenerationParameters +{ + private BigInteger publicExponent; + private int certainty; + + public RSAKeyGenerationParameters( + BigInteger publicExponent, + SecureRandom random, + int strength, + int certainty) + { + super(random, strength); + + if (strength < 12) + { + throw new IllegalArgumentException("key strength too small"); + } + + // + // public exponent cannot be even + // + if (!publicExponent.testBit(0)) + { + throw new IllegalArgumentException("public exponent cannot be even"); + } + + this.publicExponent = publicExponent; + this.certainty = certainty; + } + + public BigInteger getPublicExponent() + { + return publicExponent; + } + + public int getCertainty() + { + return certainty; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java new file mode 100644 index 00000000..4a2d9354 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java @@ -0,0 +1,31 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class RSAKeyParameters + extends AsymmetricKeyParameter +{ + private BigInteger modulus; + private BigInteger exponent; + + public RSAKeyParameters( + boolean isPrivate, + BigInteger modulus, + BigInteger exponent) + { + super(isPrivate); + + this.modulus = modulus; + this.exponent = exponent; + } + + public BigInteger getModulus() + { + return modulus; + } + + public BigInteger getExponent() + { + return exponent; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java new file mode 100644 index 00000000..b61cb5c4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java @@ -0,0 +1,67 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class RSAPrivateCrtKeyParameters + extends RSAKeyParameters +{ + private BigInteger e; + private BigInteger p; + private BigInteger q; + private BigInteger dP; + private BigInteger dQ; + private BigInteger qInv; + + /** + * + */ + public RSAPrivateCrtKeyParameters( + BigInteger modulus, + BigInteger publicExponent, + BigInteger privateExponent, + BigInteger p, + BigInteger q, + BigInteger dP, + BigInteger dQ, + BigInteger qInv) + { + super(true, modulus, privateExponent); + + this.e = publicExponent; + this.p = p; + this.q = q; + this.dP = dP; + this.dQ = dQ; + this.qInv = qInv; + } + + public BigInteger getPublicExponent() + { + return e; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getDP() + { + return dP; + } + + public BigInteger getDQ() + { + return dQ; + } + + public BigInteger getQInv() + { + return qInv; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/parsers/DHIESPublicKeyParser.java b/core/src/main/java/org/bouncycastle/crypto/parsers/DHIESPublicKeyParser.java new file mode 100644 index 00000000..44f5b571 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/parsers/DHIESPublicKeyParser.java @@ -0,0 +1,31 @@ +package org.bouncycastle.crypto.parsers; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +import org.bouncycastle.crypto.KeyParser; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; + +public class DHIESPublicKeyParser + implements KeyParser +{ + private DHParameters dhParams; + + public DHIESPublicKeyParser(DHParameters dhParams) + { + this.dhParams = dhParams; + } + + public AsymmetricKeyParameter readKey(InputStream stream) + throws IOException + { + byte[] V = new byte[(dhParams.getP().bitLength() + 7) / 8]; + + stream.read(V, 0, V.length); + + return new DHPublicKeyParameters(new BigInteger(1, V), dhParams); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/parsers/ECIESPublicKeyParser.java b/core/src/main/java/org/bouncycastle/crypto/parsers/ECIESPublicKeyParser.java new file mode 100644 index 00000000..1880a506 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/parsers/ECIESPublicKeyParser.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto.parsers; + +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.crypto.KeyParser; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; + +public class ECIESPublicKeyParser + implements KeyParser +{ + private ECDomainParameters ecParams; + + public ECIESPublicKeyParser(ECDomainParameters ecParams) + { + this.ecParams = ecParams; + } + + public AsymmetricKeyParameter readKey(InputStream stream) + throws IOException + { + byte[] V; + int first = stream.read(); + + // Decode the public ephemeral key + switch (first) + { + case 0x00: // infinity + throw new IOException("Sender's public key invalid."); + + case 0x02: // compressed + case 0x03: // Byte length calculated as in ECPoint.getEncoded(); + V = new byte[1 + (ecParams.getCurve().getFieldSize()+7)/8]; + break; + + case 0x04: // uncompressed or + case 0x06: // hybrid + case 0x07: // Byte length calculated as in ECPoint.getEncoded(); + V = new byte[1 + 2*((ecParams.getCurve().getFieldSize()+7)/8)]; + break; + + default: + throw new IOException("Sender's public key has invalid point encoding 0x" + Integer.toString(first, 16)); + } + + V[0] = (byte)first; + stream.read(V, 1, V.length - 1); + + return new ECPublicKeyParameters(ecParams.getCurve().decodePoint(V), ecParams); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java b/core/src/main/java/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java new file mode 100644 index 00000000..9f1d0427 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto.prng; + +import java.security.SecureRandom; + +/** + * An EntropySourceProvider where entropy generation is based on a SecureRandom output using SecureRandom.generateSeed(). + */ +public class BasicEntropySourceProvider + implements EntropySourceProvider +{ + private final SecureRandom _sr; + private final boolean _predictionResistant; + + /** + * Create a entropy source provider based on the passed in SecureRandom. + * + * @param random the SecureRandom to base EntropySource construction on. + * @param isPredictionResistant boolean indicating if the SecureRandom is based on prediction resistant entropy or not (true if it is). + */ + public BasicEntropySourceProvider(SecureRandom random, boolean isPredictionResistant) + { + _sr = random; + _predictionResistant = isPredictionResistant; + } + + /** + * Return an entropy source that will create bitsRequired bits of entropy on + * each invocation of getEntropy(). + * + * @param bitsRequired size (in bits) of entropy to be created by the provided source. + * @return an EntropySource that generates bitsRequired bits of entropy on each call to its getEntropy() method. + */ + public EntropySource get(final int bitsRequired) + { + return new EntropySource() + { + public boolean isPredictionResistant() + { + return _predictionResistant; + } + + public byte[] getEntropy() + { + return _sr.generateSeed((bitsRequired + 7) / 8); + } + + public int entropySize() + { + return bitsRequired; + } + }; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java b/core/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java new file mode 100644 index 00000000..c39760c9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java @@ -0,0 +1,8 @@ +package org.bouncycastle.crypto.prng; + +import org.bouncycastle.crypto.prng.drbg.SP80090DRBG; + +interface DRBGProvider +{ + SP80090DRBG get(EntropySource entropySource); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/DigestRandomGenerator.java b/core/src/main/java/org/bouncycastle/crypto/prng/DigestRandomGenerator.java new file mode 100644 index 00000000..f36b62c2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/DigestRandomGenerator.java @@ -0,0 +1,123 @@ +package org.bouncycastle.crypto.prng; + +import org.bouncycastle.crypto.Digest; + +/** + * Random generation based on the digest with counter. Calling addSeedMaterial will + * always increase the entropy of the hash. + * <p> + * Internal access to the digest is synchronized so a single one of these can be shared. + * </p> + */ +public class DigestRandomGenerator + implements RandomGenerator +{ + private static long CYCLE_COUNT = 10; + + private long stateCounter; + private long seedCounter; + private Digest digest; + private byte[] state; + private byte[] seed; + + // public constructors + public DigestRandomGenerator( + Digest digest) + { + this.digest = digest; + + this.seed = new byte[digest.getDigestSize()]; + this.seedCounter = 1; + + this.state = new byte[digest.getDigestSize()]; + this.stateCounter = 1; + } + + public void addSeedMaterial(byte[] inSeed) + { + synchronized (this) + { + digestUpdate(inSeed); + digestUpdate(seed); + digestDoFinal(seed); + } + } + + public void addSeedMaterial(long rSeed) + { + synchronized (this) + { + digestAddCounter(rSeed); + digestUpdate(seed); + + digestDoFinal(seed); + } + } + + public void nextBytes(byte[] bytes) + { + nextBytes(bytes, 0, bytes.length); + } + + public void nextBytes(byte[] bytes, int start, int len) + { + synchronized (this) + { + int stateOff = 0; + + generateState(); + + int end = start + len; + for (int i = start; i != end; i++) + { + if (stateOff == state.length) + { + generateState(); + stateOff = 0; + } + bytes[i] = state[stateOff++]; + } + } + } + + private void cycleSeed() + { + digestUpdate(seed); + digestAddCounter(seedCounter++); + + digestDoFinal(seed); + } + + private void generateState() + { + digestAddCounter(stateCounter++); + digestUpdate(state); + digestUpdate(seed); + + digestDoFinal(state); + + if ((stateCounter % CYCLE_COUNT) == 0) + { + cycleSeed(); + } + } + + private void digestAddCounter(long seed) + { + for (int i = 0; i != 8; i++) + { + digest.update((byte)seed); + seed >>>= 8; + } + } + + private void digestUpdate(byte[] inSeed) + { + digest.update(inSeed, 0, inSeed.length); + } + + private void digestDoFinal(byte[] result) + { + digest.doFinal(result, 0); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/EntropySource.java b/core/src/main/java/org/bouncycastle/crypto/prng/EntropySource.java new file mode 100644 index 00000000..53bc549d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/EntropySource.java @@ -0,0 +1,25 @@ +package org.bouncycastle.crypto.prng; + +public interface EntropySource +{ + /** + * Return whether or not this entropy source is regarded as prediction resistant. + * + * @return true if it is, false otherwise. + */ + boolean isPredictionResistant(); + + /** + * Return a byte array of entropy. + * + * @return entropy bytes. + */ + byte[] getEntropy(); + + /** + * Return the number of bits of entropy this source can produce. + * + * @return size in bits of the return value of getEntropy. + */ + int entropySize(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/EntropySourceProvider.java b/core/src/main/java/org/bouncycastle/crypto/prng/EntropySourceProvider.java new file mode 100644 index 00000000..190bf624 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/EntropySourceProvider.java @@ -0,0 +1,6 @@ +package org.bouncycastle.crypto.prng; + +public interface EntropySourceProvider +{ + EntropySource get(final int bitsRequired); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java b/core/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java new file mode 100644 index 00000000..209b5e21 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java @@ -0,0 +1,135 @@ +package org.bouncycastle.crypto.prng; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; + +public class FixedSecureRandom + extends SecureRandom +{ + private byte[] _data; + + private int _index; + private int _intPad; + + public FixedSecureRandom(byte[] value) + { + this(false, new byte[][] { value }); + } + + public FixedSecureRandom( + byte[][] values) + { + this(false, values); + } + + /** + * Pad the data on integer boundaries. This is necessary for the classpath project's BigInteger + * implementation. + */ + public FixedSecureRandom( + boolean intPad, + byte[] value) + { + this(intPad, new byte[][] { value }); + } + + /** + * Pad the data on integer boundaries. This is necessary for the classpath project's BigInteger + * implementation. + */ + public FixedSecureRandom( + boolean intPad, + byte[][] values) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + for (int i = 0; i != values.length; i++) + { + try + { + bOut.write(values[i]); + } + catch (IOException e) + { + throw new IllegalArgumentException("can't save value array."); + } + } + + _data = bOut.toByteArray(); + + if (intPad) + { + _intPad = _data.length % 4; + } + } + + public void nextBytes(byte[] bytes) + { + System.arraycopy(_data, _index, bytes, 0, bytes.length); + + _index += bytes.length; + } + + // + // classpath's implementation of SecureRandom doesn't currently go back to nextBytes + // when next is called. We can't override next as it's a final method. + // + public int nextInt() + { + int val = 0; + + val |= nextValue() << 24; + val |= nextValue() << 16; + + if (_intPad == 2) + { + _intPad--; + } + else + { + val |= nextValue() << 8; + } + + if (_intPad == 1) + { + _intPad--; + } + else + { + val |= nextValue(); + } + + return val; + } + + // + // classpath's implementation of SecureRandom doesn't currently go back to nextBytes + // when next is called. We can't override next as it's a final method. + // + public long nextLong() + { + long val = 0; + + val |= (long)nextValue() << 56; + val |= (long)nextValue() << 48; + val |= (long)nextValue() << 40; + val |= (long)nextValue() << 32; + val |= (long)nextValue() << 24; + val |= (long)nextValue() << 16; + val |= (long)nextValue() << 8; + val |= (long)nextValue(); + + return val; + } + + public boolean isExhausted() + { + return _index == _data.length; + } + + private int nextValue() + { + return _data[_index++] & 0xff; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/RandomGenerator.java b/core/src/main/java/org/bouncycastle/crypto/prng/RandomGenerator.java new file mode 100644 index 00000000..47ff68e3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/RandomGenerator.java @@ -0,0 +1,38 @@ +package org.bouncycastle.crypto.prng; + +/** + * Generic interface for objects generating random bytes. + */ +public interface RandomGenerator +{ + /** + * Add more seed material to the generator. + * + * @param seed a byte array to be mixed into the generator's state. + */ + void addSeedMaterial(byte[] seed); + + /** + * Add more seed material to the generator. + * + * @param seed a long value to be mixed into the generator's state. + */ + void addSeedMaterial(long seed); + + /** + * Fill bytes with random values. + * + * @param bytes byte array to be filled. + */ + void nextBytes(byte[] bytes); + + /** + * Fill part of bytes with random values. + * + * @param bytes byte array to be filled. + * @param start index to start filling at. + * @param len length of segment to fill. + */ + void nextBytes(byte[] bytes, int start, int len); + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/ReversedWindowGenerator.java b/core/src/main/java/org/bouncycastle/crypto/prng/ReversedWindowGenerator.java new file mode 100644 index 00000000..fbb2639c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/ReversedWindowGenerator.java @@ -0,0 +1,111 @@ +package org.bouncycastle.crypto.prng; + +/** + * Takes bytes generated by an underling RandomGenerator and reverses the order in + * each small window (of configurable size). + * <p> + * Access to internals is synchronized so a single one of these can be shared. + * </p> + */ +public class ReversedWindowGenerator + implements RandomGenerator +{ + private final RandomGenerator generator; + + private byte[] window; + private int windowCount; + + public ReversedWindowGenerator( + RandomGenerator generator, + int windowSize) + { + if (generator == null) + { + throw new IllegalArgumentException("generator cannot be null"); + } + if (windowSize < 2) + { + throw new IllegalArgumentException("windowSize must be at least 2"); + } + + this.generator = generator; + this.window = new byte[windowSize]; + } + + /** + * Add more seed material to the generator. + * + * @param seed a byte array to be mixed into the generator's state. + */ + public void addSeedMaterial( + byte[] seed) + { + synchronized (this) + { + windowCount = 0; + generator.addSeedMaterial(seed); + } + } + + /** + * Add more seed material to the generator. + * + * @param seed a long value to be mixed into the generator's state. + */ + public void addSeedMaterial( + long seed) + { + synchronized (this) + { + windowCount = 0; + generator.addSeedMaterial(seed); + } + } + + /** + * Fill bytes with random values. + * + * @param bytes byte array to be filled. + */ + public void nextBytes( + byte[] bytes) + { + doNextBytes(bytes, 0, bytes.length); + } + + /** + * Fill part of bytes with random values. + * + * @param bytes byte array to be filled. + * @param start index to start filling at. + * @param len length of segment to fill. + */ + public void nextBytes( + byte[] bytes, + int start, + int len) + { + doNextBytes(bytes, start, len); + } + + private void doNextBytes( + byte[] bytes, + int start, + int len) + { + synchronized (this) + { + int done = 0; + while (done < len) + { + if (windowCount < 1) + { + generator.nextBytes(window, 0, window.length); + windowCount = window.length; + } + + bytes[start + done++] = window[--windowCount]; + } + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java b/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java new file mode 100644 index 00000000..e1ec6c28 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java @@ -0,0 +1,74 @@ +package org.bouncycastle.crypto.prng; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.prng.drbg.SP80090DRBG; + +public class SP800SecureRandom + extends SecureRandom +{ + private final DRBGProvider drbgProvider; + private final boolean predictionResistant; + private final SecureRandom randomSource; + private final EntropySource entropySource; + + private SP80090DRBG drbg; + + SP800SecureRandom(SecureRandom randomSource, EntropySource entropySource, DRBGProvider drbgProvider, boolean predictionResistant) + { + this.randomSource = randomSource; + this.entropySource = entropySource; + this.drbgProvider = drbgProvider; + this.predictionResistant = predictionResistant; + } + + public void setSeed(byte[] seed) + { + synchronized (this) + { + if (randomSource != null) + { + this.randomSource.setSeed(seed); + } + } + } + + public void setSeed(long seed) + { + synchronized (this) + { + // this will happen when SecureRandom() is created + if (randomSource != null) + { + this.randomSource.setSeed(seed); + } + } + } + + public void nextBytes(byte[] bytes) + { + synchronized (this) + { + if (drbg == null) + { + drbg = drbgProvider.get(entropySource); + } + + // check if a reseed is required... + if (drbg.generate(bytes, null, predictionResistant) < 0) + { + drbg.reseed(entropySource.getEntropy()); + drbg.generate(bytes, null, predictionResistant); + } + } + } + + public byte[] generateSeed(int numBytes) + { + byte[] bytes = new byte[numBytes]; + + this.nextBytes(bytes); + + return bytes; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java b/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java new file mode 100644 index 00000000..66f05c5f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java @@ -0,0 +1,249 @@ +package org.bouncycastle.crypto.prng; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG; +import org.bouncycastle.crypto.prng.drbg.DualECSP800DRBG; +import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG; +import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG; +import org.bouncycastle.crypto.prng.drbg.SP80090DRBG; + +/** + * Builder class for making SecureRandom objects based on SP 800-90A Deterministic Random Bit Generators (DRBG). + */ +public class SP800SecureRandomBuilder +{ + private final SecureRandom random; + private final EntropySourceProvider entropySourceProvider; + + private byte[] personalizationString; + private int securityStrength = 256; + private int entropyBitsRequired = 256; + + /** + * Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with + * predictionResistant set to false. + * <p> + * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + * the default SecureRandom does for its generateSeed() call. + * </p> + */ + public SP800SecureRandomBuilder() + { + this(new SecureRandom(), false); + } + + /** + * Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value + * for prediction resistance. + * <p> + * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + * the passed in SecureRandom does for its generateSeed() call. + * </p> + * @param entropySource + * @param predictionResistant + */ + public SP800SecureRandomBuilder(SecureRandom entropySource, boolean predictionResistant) + { + this.random = entropySource; + this.entropySourceProvider = new BasicEntropySourceProvider(random, predictionResistant); + } + + /** + * Create a builder which makes creates the SecureRandom objects from a specified entropy source provider. + * <p> + * <b>Note:</b> If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored. + * </p> + * @param entropySourceProvider a provider of EntropySource objects. + */ + public SP800SecureRandomBuilder(EntropySourceProvider entropySourceProvider) + { + this.random = null; + this.entropySourceProvider = entropySourceProvider; + } + + /** + * Set the personalization string for DRBG SecureRandoms created by this builder + * @param personalizationString the personalisation string for the underlying DRBG. + * @return the current builder. + */ + public SP800SecureRandomBuilder setPersonalizationString(byte[] personalizationString) + { + this.personalizationString = personalizationString; + + return this; + } + + /** + * Set the security strength required for DRBGs used in building SecureRandom objects. + * + * @param securityStrength the security strength (in bits) + * @return the current builder. + */ + public SP800SecureRandomBuilder setSecurityStrength(int securityStrength) + { + this.securityStrength = securityStrength; + + return this; + } + + /** + * Set the amount of entropy bits required for seeding and reseeding DRBGs used in building SecureRandom objects. + * + * @param entropyBitsRequired the number of bits of entropy to be requested from the entropy source on each seed/reseed. + * @return the current builder. + */ + public SP800SecureRandomBuilder setEntropyBitsRequired(int entropyBitsRequired) + { + this.entropyBitsRequired = entropyBitsRequired; + + return this; + } + + /** + * Build a SecureRandom based on a SP 800-90A Hash DRBG. + * + * @param digest digest algorithm to use in the DRBG underneath the SecureRandom. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a Hash DRBG. + */ + public SP800SecureRandom buildHash(Digest digest, byte[] nonce, boolean predictionResistant) + { + return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new HashDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant); + } + + /** + * Build a SecureRandom based on a SP 800-90A CTR DRBG. + * + * @param cipher the block cipher to base the DRBG on. + * @param keySizeInBits key size in bits to be used with the block cipher. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a CTR DRBG. + */ + public SP800SecureRandom buildCTR(BlockCipher cipher, int keySizeInBits, byte[] nonce, boolean predictionResistant) + { + return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new CTRDRBGProvider(cipher, keySizeInBits, nonce, personalizationString, securityStrength), predictionResistant); + } + + /** + * Build a SecureRandom based on a SP 800-90A HMAC DRBG. + * + * @param hMac HMAC algorithm to use in the DRBG underneath the SecureRandom. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a HMAC DRBG. + */ + public SP800SecureRandom buildHMAC(Mac hMac, byte[] nonce, boolean predictionResistant) + { + return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new HMacDRBGProvider(hMac, nonce, personalizationString, securityStrength), predictionResistant); + } + + /** + * Build a SecureRandom based on a SP 800-90A Dual EC DRBG. + * + * @param digest digest algorithm to use in the DRBG underneath the SecureRandom. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a Dual EC DRBG. + */ + public SP800SecureRandom buildDualEC(Digest digest, byte[] nonce, boolean predictionResistant) + { + return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new DualECDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant); + } + + private static class HashDRBGProvider + implements DRBGProvider + { + private final Digest digest; + private final byte[] nonce; + private final byte[] personalizationString; + private final int securityStrength; + + public HashDRBGProvider(Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.digest = digest; + this.nonce = nonce; + this.personalizationString = personalizationString; + this.securityStrength = securityStrength; + } + + public SP80090DRBG get(EntropySource entropySource) + { + return new HashSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce); + } + } + + private static class DualECDRBGProvider + implements DRBGProvider + { + private final Digest digest; + private final byte[] nonce; + private final byte[] personalizationString; + private final int securityStrength; + + public DualECDRBGProvider(Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.digest = digest; + this.nonce = nonce; + this.personalizationString = personalizationString; + this.securityStrength = securityStrength; + } + + public SP80090DRBG get(EntropySource entropySource) + { + return new DualECSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce); + } + } + + private static class HMacDRBGProvider + implements DRBGProvider + { + private final Mac hMac; + private final byte[] nonce; + private final byte[] personalizationString; + private final int securityStrength; + + public HMacDRBGProvider(Mac hMac, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.hMac = hMac; + this.nonce = nonce; + this.personalizationString = personalizationString; + this.securityStrength = securityStrength; + } + + public SP80090DRBG get(EntropySource entropySource) + { + return new HMacSP800DRBG(hMac, securityStrength, entropySource, personalizationString, nonce); + } + } + + private static class CTRDRBGProvider + implements DRBGProvider + { + + private final BlockCipher blockCipher; + private final int keySizeInBits; + private final byte[] nonce; + private final byte[] personalizationString; + private final int securityStrength; + + public CTRDRBGProvider(BlockCipher blockCipher, int keySizeInBits, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.blockCipher = blockCipher; + this.keySizeInBits = keySizeInBits; + this.nonce = nonce; + this.personalizationString = personalizationString; + this.securityStrength = securityStrength; + } + + public SP80090DRBG get(EntropySource entropySource) + { + return new CTRSP800DRBG(blockCipher, keySizeInBits, securityStrength, entropySource, personalizationString, nonce); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/ThreadedSeedGenerator.java b/core/src/main/java/org/bouncycastle/crypto/prng/ThreadedSeedGenerator.java new file mode 100644 index 00000000..6b2d5ec2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/ThreadedSeedGenerator.java @@ -0,0 +1,95 @@ +package org.bouncycastle.crypto.prng; + +/** + * A thread based seed generator - one source of randomness. + * <p> + * Based on an idea from Marcus Lippert. + * </p> + */ +public class ThreadedSeedGenerator +{ + private class SeedGenerator + implements Runnable + { + private volatile int counter = 0; + private volatile boolean stop = false; + + public void run() + { + while (!this.stop) + { + this.counter++; + } + + } + + public byte[] generateSeed( + int numbytes, + boolean fast) + { + Thread t = new Thread(this); + byte[] result = new byte[numbytes]; + this.counter = 0; + this.stop = false; + int last = 0; + int end; + + t.start(); + if(fast) + { + end = numbytes; + } + else + { + end = numbytes * 8; + } + for (int i = 0; i < end; i++) + { + while (this.counter == last) + { + try + { + Thread.sleep(1); + } + catch (InterruptedException e) + { + // ignore + } + } + last = this.counter; + if (fast) + { + result[i] = (byte) (last & 0xff); + } + else + { + int bytepos = i/8; + result[bytepos] = (byte) ((result[bytepos] << 1) | (last & 1)); + } + + } + stop = true; + return result; + } + } + + /** + * Generate seed bytes. Set fast to false for best quality. + * <p> + * If fast is set to true, the code should be round about 8 times faster when + * generating a long sequence of random bytes. 20 bytes of random values using + * the fast mode take less than half a second on a Nokia e70. If fast is set to false, + * it takes round about 2500 ms. + * </p> + * @param numBytes the number of bytes to generate + * @param fast true if fast mode should be used + */ + public byte[] generateSeed( + int numBytes, + boolean fast) + { + SeedGenerator gen = new SeedGenerator(); + + return gen.generateSeed(numBytes, fast); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java b/core/src/main/java/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java new file mode 100644 index 00000000..2146af7f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java @@ -0,0 +1,127 @@ +package org.bouncycastle.crypto.prng; + +import org.bouncycastle.crypto.util.Pack; + +public class VMPCRandomGenerator implements RandomGenerator +{ + private byte n = 0; + + /** + * Permutation generated by code: <code> + * // First 1850 fractional digit of Pi number. + * byte[] key = new BigInteger("14159265358979323846...5068006422512520511").toByteArray(); + * s = 0; + * P = new byte[256]; + * for (int i = 0; i < 256; i++) { + * P[i] = (byte) i; + * } + * for (int m = 0; m < 768; m++) { + * s = P[(s + P[m & 0xff] + key[m % key.length]) & 0xff]; + * byte temp = P[m & 0xff]; + * P[m & 0xff] = P[s & 0xff]; + * P[s & 0xff] = temp; + * } </code> + */ + private byte[] P = + { + (byte) 0xbb, (byte) 0x2c, (byte) 0x62, (byte) 0x7f, + (byte) 0xb5, (byte) 0xaa, (byte) 0xd4, (byte) 0x0d, (byte) 0x81, + (byte) 0xfe, (byte) 0xb2, (byte) 0x82, (byte) 0xcb, (byte) 0xa0, + (byte) 0xa1, (byte) 0x08, (byte) 0x18, (byte) 0x71, (byte) 0x56, + (byte) 0xe8, (byte) 0x49, (byte) 0x02, (byte) 0x10, (byte) 0xc4, + (byte) 0xde, (byte) 0x35, (byte) 0xa5, (byte) 0xec, (byte) 0x80, + (byte) 0x12, (byte) 0xb8, (byte) 0x69, (byte) 0xda, (byte) 0x2f, + (byte) 0x75, (byte) 0xcc, (byte) 0xa2, (byte) 0x09, (byte) 0x36, + (byte) 0x03, (byte) 0x61, (byte) 0x2d, (byte) 0xfd, (byte) 0xe0, + (byte) 0xdd, (byte) 0x05, (byte) 0x43, (byte) 0x90, (byte) 0xad, + (byte) 0xc8, (byte) 0xe1, (byte) 0xaf, (byte) 0x57, (byte) 0x9b, + (byte) 0x4c, (byte) 0xd8, (byte) 0x51, (byte) 0xae, (byte) 0x50, + (byte) 0x85, (byte) 0x3c, (byte) 0x0a, (byte) 0xe4, (byte) 0xf3, + (byte) 0x9c, (byte) 0x26, (byte) 0x23, (byte) 0x53, (byte) 0xc9, + (byte) 0x83, (byte) 0x97, (byte) 0x46, (byte) 0xb1, (byte) 0x99, + (byte) 0x64, (byte) 0x31, (byte) 0x77, (byte) 0xd5, (byte) 0x1d, + (byte) 0xd6, (byte) 0x78, (byte) 0xbd, (byte) 0x5e, (byte) 0xb0, + (byte) 0x8a, (byte) 0x22, (byte) 0x38, (byte) 0xf8, (byte) 0x68, + (byte) 0x2b, (byte) 0x2a, (byte) 0xc5, (byte) 0xd3, (byte) 0xf7, + (byte) 0xbc, (byte) 0x6f, (byte) 0xdf, (byte) 0x04, (byte) 0xe5, + (byte) 0x95, (byte) 0x3e, (byte) 0x25, (byte) 0x86, (byte) 0xa6, + (byte) 0x0b, (byte) 0x8f, (byte) 0xf1, (byte) 0x24, (byte) 0x0e, + (byte) 0xd7, (byte) 0x40, (byte) 0xb3, (byte) 0xcf, (byte) 0x7e, + (byte) 0x06, (byte) 0x15, (byte) 0x9a, (byte) 0x4d, (byte) 0x1c, + (byte) 0xa3, (byte) 0xdb, (byte) 0x32, (byte) 0x92, (byte) 0x58, + (byte) 0x11, (byte) 0x27, (byte) 0xf4, (byte) 0x59, (byte) 0xd0, + (byte) 0x4e, (byte) 0x6a, (byte) 0x17, (byte) 0x5b, (byte) 0xac, + (byte) 0xff, (byte) 0x07, (byte) 0xc0, (byte) 0x65, (byte) 0x79, + (byte) 0xfc, (byte) 0xc7, (byte) 0xcd, (byte) 0x76, (byte) 0x42, + (byte) 0x5d, (byte) 0xe7, (byte) 0x3a, (byte) 0x34, (byte) 0x7a, + (byte) 0x30, (byte) 0x28, (byte) 0x0f, (byte) 0x73, (byte) 0x01, + (byte) 0xf9, (byte) 0xd1, (byte) 0xd2, (byte) 0x19, (byte) 0xe9, + (byte) 0x91, (byte) 0xb9, (byte) 0x5a, (byte) 0xed, (byte) 0x41, + (byte) 0x6d, (byte) 0xb4, (byte) 0xc3, (byte) 0x9e, (byte) 0xbf, + (byte) 0x63, (byte) 0xfa, (byte) 0x1f, (byte) 0x33, (byte) 0x60, + (byte) 0x47, (byte) 0x89, (byte) 0xf0, (byte) 0x96, (byte) 0x1a, + (byte) 0x5f, (byte) 0x93, (byte) 0x3d, (byte) 0x37, (byte) 0x4b, + (byte) 0xd9, (byte) 0xa8, (byte) 0xc1, (byte) 0x1b, (byte) 0xf6, + (byte) 0x39, (byte) 0x8b, (byte) 0xb7, (byte) 0x0c, (byte) 0x20, + (byte) 0xce, (byte) 0x88, (byte) 0x6e, (byte) 0xb6, (byte) 0x74, + (byte) 0x8e, (byte) 0x8d, (byte) 0x16, (byte) 0x29, (byte) 0xf2, + (byte) 0x87, (byte) 0xf5, (byte) 0xeb, (byte) 0x70, (byte) 0xe3, + (byte) 0xfb, (byte) 0x55, (byte) 0x9f, (byte) 0xc6, (byte) 0x44, + (byte) 0x4a, (byte) 0x45, (byte) 0x7d, (byte) 0xe2, (byte) 0x6b, + (byte) 0x5c, (byte) 0x6c, (byte) 0x66, (byte) 0xa9, (byte) 0x8c, + (byte) 0xee, (byte) 0x84, (byte) 0x13, (byte) 0xa7, (byte) 0x1e, + (byte) 0x9d, (byte) 0xdc, (byte) 0x67, (byte) 0x48, (byte) 0xba, + (byte) 0x2e, (byte) 0xe6, (byte) 0xa4, (byte) 0xab, (byte) 0x7c, + (byte) 0x94, (byte) 0x00, (byte) 0x21, (byte) 0xef, (byte) 0xea, + (byte) 0xbe, (byte) 0xca, (byte) 0x72, (byte) 0x4f, (byte) 0x52, + (byte) 0x98, (byte) 0x3f, (byte) 0xc2, (byte) 0x14, (byte) 0x7b, + (byte) 0x3b, (byte) 0x54 }; + + /** + * Value generated in the same way as {@link VMPCRandomGenerator#P}; + */ + private byte s = (byte) 0xbe; + + public VMPCRandomGenerator() + { + } + + public void addSeedMaterial(byte[] seed) + { + for (int m = 0; m < seed.length; m++) + { + s = P[(s + P[n & 0xff] + seed[m]) & 0xff]; + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + } + + public void addSeedMaterial(long seed) + { + addSeedMaterial(Pack.longToBigEndian(seed)); + } + + public void nextBytes(byte[] bytes) + { + nextBytes(bytes, 0, bytes.length); + } + + public void nextBytes(byte[] bytes, int start, int len) + { + synchronized (P) + { + int end = start + len; + for (int i = start; i != end; i++) + { + s = P[(s + P[n & 0xff]) & 0xff]; + bytes[i] = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/drbg/CTRSP800DRBG.java b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/CTRSP800DRBG.java new file mode 100644 index 00000000..84fe4a40 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/CTRSP800DRBG.java @@ -0,0 +1,468 @@ +package org.bouncycastle.crypto.prng.drbg; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.prng.EntropySource; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +/** + * A SP800-90A CTR DRBG. + */ +public class CTRSP800DRBG + implements SP80090DRBG +{ + private static final long TDEA_RESEED_MAX = 1L << (32 - 1); + private static final long AES_RESEED_MAX = 1L << (48 - 1); + private static final int TDEA_MAX_BITS_REQUEST = 1 << (13 - 1); + private static final int AES_MAX_BITS_REQUEST = 1 << (19 - 1); + + private EntropySource _entropySource; + private BlockCipher _engine; + private int _keySizeInBits; + private int _seedLength; + + // internal state + private byte[] _Key; + private byte[] _V; + private long _reseedCounter = 0; + private boolean _isTDEA = false; + + /** + * Construct a SP800-90A CTR DRBG. + * <p> + * Minimum entropy requirement is the security strength requested. + * </p> + * @param engine underlying block cipher to use to support DRBG + * @param keySizeInBits size of the key to use with the block cipher. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public CTRSP800DRBG(BlockCipher engine, int keySizeInBits, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) + { + _entropySource = entropySource; + _engine = engine; + + _keySizeInBits = keySizeInBits; + _seedLength = keySizeInBits + engine.getBlockSize() * 8; + _isTDEA = isTDEA(engine); + + if (securityStrength > 256) + { + throw new IllegalArgumentException("Requested security strength is not supported by the derivation function"); + } + + if (getMaxSecurityStrength(engine, keySizeInBits) < securityStrength) + { + throw new IllegalArgumentException("Requested security strength is not supported by block cipher and key size"); + } + + if (entropySource.entropySize() < securityStrength) + { + throw new IllegalArgumentException("Not enough entropy for security strength required"); + } + + byte[] entropy = entropySource.getEntropy(); // Get_entropy_input + + CTR_DRBG_Instantiate_algorithm(entropy, nonce, personalizationString); + } + + private void CTR_DRBG_Instantiate_algorithm(byte[] entropy, byte[] nonce, + byte[] personalisationString) + { + byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalisationString); + byte[] seed = Block_Cipher_df(seedMaterial, _seedLength); + + int outlen = _engine.getBlockSize(); + + _Key = new byte[(_keySizeInBits + 7) / 8]; + _V = new byte[outlen]; + + // _Key & _V are modified by this call + CTR_DRBG_Update(seed, _Key, _V); + + _reseedCounter = 1; + } + + private void CTR_DRBG_Update(byte[] seed, byte[] key, byte[] v) + { + byte[] temp = new byte[seed.length]; + byte[] outputBlock = new byte[_engine.getBlockSize()]; + + int i=0; + int outLen = _engine.getBlockSize(); + + _engine.init(true, new KeyParameter(expandKey(key))); + while (i*outLen < seed.length) + { + addOneTo(v); + _engine.processBlock(v, 0, outputBlock, 0); + + int bytesToCopy = ((temp.length - i * outLen) > outLen) + ? outLen : (temp.length - i * outLen); + + System.arraycopy(outputBlock, 0, temp, i * outLen, bytesToCopy); + ++i; + } + + XOR(temp, seed, temp, 0); + + System.arraycopy(temp, 0, key, 0, key.length); + System.arraycopy(temp, key.length, v, 0, v.length); + } + + private void CTR_DRBG_Reseed_algorithm(EntropySource entropy, byte[] additionalInput) + { + byte[] seedMaterial = Arrays.concatenate(entropy.getEntropy(), additionalInput); + + seedMaterial = Block_Cipher_df(seedMaterial, _seedLength); + + CTR_DRBG_Update(seedMaterial, _Key, _V); + + _reseedCounter = 1; + } + + private void XOR(byte[] out, byte[] a, byte[] b, int bOff) + { + for (int i=0; i< out.length; i++) + { + out[i] = (byte)(a[i] ^ b[i+bOff]); + } + } + + private void addOneTo(byte[] longer) + { + int carry = 1; + for (int i = 1; i <= longer.length; i++) // warning + { + int res = (longer[longer.length - i] & 0xff) + carry; + carry = (res > 0xff) ? 1 : 0; + longer[longer.length - i] = (byte)res; + } + } + + // -- Internal state migration --- + + private static final byte[] K_BITS = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + + // 1. If (number_of_bits_to_return > max_number_of_bits), then return an + // ERROR_FLAG. + // 2. L = len (input_string)/8. + // 3. N = number_of_bits_to_return/8. + // Comment: L is the bitstring represention of + // the integer resulting from len (input_string)/8. + // L shall be represented as a 32-bit integer. + // + // Comment : N is the bitstring represention of + // the integer resulting from + // number_of_bits_to_return/8. N shall be + // represented as a 32-bit integer. + // + // 4. S = L || N || input_string || 0x80. + // 5. While (len (S) mod outlen) + // Comment : Pad S with zeros, if necessary. + // 0, S = S || 0x00. + // + // Comment : Compute the starting value. + // 6. temp = the Null string. + // 7. i = 0. + // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F. + // 9. While len (temp) < keylen + outlen, do + // + // IV = i || 0outlen - len (i). + // + // 9.1 + // + // temp = temp || BCC (K, (IV || S)). + // + // 9.2 + // + // i = i + 1. + // + // 9.3 + // + // Comment : i shall be represented as a 32-bit + // integer, i.e., len (i) = 32. + // + // Comment: The 32-bit integer represenation of + // i is padded with zeros to outlen bits. + // + // Comment: Compute the requested number of + // bits. + // + // 10. K = Leftmost keylen bits of temp. + // + // 11. X = Next outlen bits of temp. + // + // 12. temp = the Null string. + // + // 13. While len (temp) < number_of_bits_to_return, do + // + // 13.1 X = Block_Encrypt (K, X). + // + // 13.2 temp = temp || X. + // + // 14. requested_bits = Leftmost number_of_bits_to_return of temp. + // + // 15. Return SUCCESS and requested_bits. + private byte[] Block_Cipher_df(byte[] inputString, int bitLength) + { + int outLen = _engine.getBlockSize(); + int L = inputString.length; // already in bytes + int N = bitLength / 8; + // 4 S = L || N || inputstring || 0x80 + int sLen = 4 + 4 + L + 1; + int blockLen = ((sLen + outLen - 1) / outLen) * outLen; + byte[] S = new byte[blockLen]; + copyIntToByteArray(S, L, 0); + copyIntToByteArray(S, N, 4); + System.arraycopy(inputString, 0, S, 8, L); + S[8 + L] = (byte)0x80; + // S already padded with zeros + + byte[] temp = new byte[_keySizeInBits / 8 + outLen]; + byte[] bccOut = new byte[outLen]; + + byte[] IV = new byte[outLen]; + + int i = 0; + byte[] K = new byte[_keySizeInBits / 8]; + System.arraycopy(K_BITS, 0, K, 0, K.length); + + while (i*outLen*8 < _keySizeInBits + outLen *8) + { + copyIntToByteArray(IV, i, 0); + BCC(bccOut, K, IV, S); + + int bytesToCopy = ((temp.length - i * outLen) > outLen) + ? outLen + : (temp.length - i * outLen); + + System.arraycopy(bccOut, 0, temp, i * outLen, bytesToCopy); + ++i; + } + + byte[] X = new byte[outLen]; + System.arraycopy(temp, 0, K, 0, K.length); + System.arraycopy(temp, K.length, X, 0, X.length); + + temp = new byte[bitLength / 2]; + + i = 0; + _engine.init(true, new KeyParameter(expandKey(K))); + + while (i * outLen < temp.length) + { + _engine.processBlock(X, 0, X, 0); + + int bytesToCopy = ((temp.length - i * outLen) > outLen) + ? outLen + : (temp.length - i * outLen); + + System.arraycopy(X, 0, temp, i * outLen, bytesToCopy); + i++; + } + + return temp; + } + + /* + * 1. chaining_value = 0^outlen + * . Comment: Set the first chaining value to outlen zeros. + * 2. n = len (data)/outlen. + * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits + * each, forming block(1) to block(n). + * 4. For i = 1 to n do + * 4.1 input_block = chaining_value ^ block(i) . + * 4.2 chaining_value = Block_Encrypt (Key, input_block). + * 5. output_block = chaining_value. + * 6. Return output_block. + */ + private void BCC(byte[] bccOut, byte[] k, byte[] iV, byte[] data) + { + int outlen = _engine.getBlockSize(); + byte[] chainingValue = new byte[outlen]; // initial values = 0 + int n = data.length / outlen; + + byte[] inputBlock = new byte[outlen]; + + _engine.init(true, new KeyParameter(expandKey(k))); + + _engine.processBlock(iV, 0, chainingValue, 0); + + for (int i = 0; i < n; i++) + { + XOR(inputBlock, chainingValue, data, i*outlen); + _engine.processBlock(inputBlock, 0, chainingValue, 0); + } + + System.arraycopy(chainingValue, 0, bccOut, 0, bccOut.length); + } + + private void copyIntToByteArray(byte[] buf, int value, int offSet) + { + buf[offSet + 0] = ((byte)(value >> 24)); + buf[offSet + 1] = ((byte)(value >> 16)); + buf[offSet + 2] = ((byte)(value >> 8)); + buf[offSet + 3] = ((byte)(value)); + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant) + { + if (_isTDEA) + { + if (_reseedCounter > TDEA_RESEED_MAX) + { + return -1; + } + + if (Utils.isTooLarge(output, TDEA_MAX_BITS_REQUEST / 8)) + { + throw new IllegalArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST); + } + } + else + { + if (_reseedCounter > AES_RESEED_MAX) + { + return -1; + } + + if (Utils.isTooLarge(output, AES_MAX_BITS_REQUEST / 8)) + { + throw new IllegalArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST); + } + } + + if (predictionResistant) + { + CTR_DRBG_Reseed_algorithm(_entropySource, additionalInput); + additionalInput = null; + } + + if (additionalInput != null) + { + additionalInput = Block_Cipher_df(additionalInput, _seedLength); + CTR_DRBG_Update(additionalInput, _Key, _V); + } + else + { + additionalInput = new byte[_seedLength]; + } + + byte[] out = new byte[_V.length]; + + _engine.init(true, new KeyParameter(expandKey(_Key))); + + for (int i = 0; i < output.length / out.length; i++) + { + addOneTo(_V); + + _engine.processBlock(_V, 0, out, 0); + + int bytesToCopy = ((output.length - i * out.length) > out.length) + ? out.length + : (output.length - i * _V.length); + + System.arraycopy(out, 0, output, i * out.length, bytesToCopy); + } + + CTR_DRBG_Update(additionalInput, _Key, _V); + + _reseedCounter++; + + return output.length * 8; + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void reseed(byte[] additionalInput) + { + CTR_DRBG_Reseed_algorithm(_entropySource, additionalInput); + } + + private boolean isTDEA(BlockCipher cipher) + { + return cipher.getAlgorithmName().equals("DESede") || cipher.getAlgorithmName().equals("TDEA"); + } + + private int getMaxSecurityStrength(BlockCipher cipher, int keySizeInBits) + { + if (isTDEA(cipher) && keySizeInBits == 168) + { + return 112; + } + if (cipher.getAlgorithmName().equals("AES")) + { + return keySizeInBits; + } + + return -1; + } + + byte[] expandKey(byte[] key) + { + if (_isTDEA) + { + // expand key to 192 bits. + byte[] tmp = new byte[24]; + + padKey(key, 0, tmp, 0); + padKey(key, 7, tmp, 8); + padKey(key, 14, tmp, 16); + + return tmp; + } + else + { + return key; + } + } + + /** + * Pad out a key for TDEA, setting odd parity for each byte. + * + * @param keyMaster + * @param keyOff + * @param tmp + * @param tmpOff + */ + private void padKey(byte[] keyMaster, int keyOff, byte[] tmp, int tmpOff) + { + tmp[tmpOff + 0] = (byte)(keyMaster[keyOff + 0] & 0xfe); + tmp[tmpOff + 1] = (byte)((keyMaster[keyOff + 0] << 7) | ((keyMaster[keyOff + 1] & 0xfc) >>> 1)); + tmp[tmpOff + 2] = (byte)((keyMaster[keyOff + 1] << 6) | ((keyMaster[keyOff + 2] & 0xf8) >>> 2)); + tmp[tmpOff + 3] = (byte)((keyMaster[keyOff + 2] << 5) | ((keyMaster[keyOff + 3] & 0xf0) >>> 3)); + tmp[tmpOff + 4] = (byte)((keyMaster[keyOff + 3] << 4) | ((keyMaster[keyOff + 4] & 0xe0) >>> 4)); + tmp[tmpOff + 5] = (byte)((keyMaster[keyOff + 4] << 3) | ((keyMaster[keyOff + 5] & 0xc0) >>> 5)); + tmp[tmpOff + 6] = (byte)((keyMaster[keyOff + 5] << 2) | ((keyMaster[keyOff + 6] & 0x80) >>> 6)); + tmp[tmpOff + 7] = (byte)(keyMaster[keyOff + 6] << 1); + + for (int i = tmpOff; i <= tmpOff + 7; i++) + { + int b = tmp[i]; + tmp[i] = (byte)((b & 0xfe) | + ((((b >> 1) ^ + (b >> 2) ^ + (b >> 3) ^ + (b >> 4) ^ + (b >> 5) ^ + (b >> 6) ^ + (b >> 7)) ^ 0x01) & 0x01)); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java new file mode 100644 index 00000000..3cee39cb --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java @@ -0,0 +1,267 @@ +package org.bouncycastle.crypto.prng.drbg; + +import java.math.BigInteger; + +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.prng.EntropySource; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; + +/** + * A SP800-90A Dual EC DRBG. + */ +public class DualECSP800DRBG + implements SP80090DRBG +{ + /* + * Default P, Q values for each curve + */ + private static final BigInteger p256_Px = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16); + private static final BigInteger p256_Py = new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16); + private static final BigInteger p256_Qx = new BigInteger("c97445f45cdef9f0d3e05e1e585fc297235b82b5be8ff3efca67c59852018192", 16); + private static final BigInteger p256_Qy = new BigInteger("b28ef557ba31dfcbdd21ac46e2a91e3c304f44cb87058ada2cb815151e610046", 16); + + private static final BigInteger p384_Px = new BigInteger("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", 16); + private static final BigInteger p384_Py = new BigInteger("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f", 16); + private static final BigInteger p384_Qx = new BigInteger("8e722de3125bddb05580164bfe20b8b432216a62926c57502ceede31c47816edd1e89769124179d0b695106428815065", 16); + private static final BigInteger p384_Qy = new BigInteger("023b1660dd701d0839fd45eec36f9ee7b32e13b315dc02610aa1b636e346df671f790f84c5e09b05674dbb7e45c803dd", 16); + + private static final BigInteger p521_Px = new BigInteger("c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", 16); + private static final BigInteger p521_Py = new BigInteger("11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650", 16); + private static final BigInteger p521_Qx = new BigInteger("1b9fa3e518d683c6b65763694ac8efbaec6fab44f2276171a42726507dd08add4c3b3f4c1ebc5b1222ddba077f722943b24c3edfa0f85fe24d0c8c01591f0be6f63", 16); + private static final BigInteger p521_Qy = new BigInteger("1f3bdba585295d9a1110d1df1f9430ef8442c5018976ff3437ef91b81dc0b8132c8d5c39c32d0e004a3092b7d327c0e7a4d26d2c7b69b58f9066652911e457779de", 16); + + private static final long RESEED_MAX = 1L << (32 - 1); + private static final int MAX_ADDITIONAL_INPUT = 1 << (13 - 1); + private static final int MAX_ENTROPY_LENGTH = 1 << (13 - 1); + private static final int MAX_PERSONALIZATION_STRING = 1 << (13 -1); + + private Digest _digest; + private long _reseedCounter; + private EntropySource _entropySource; + private int _securityStrength; + private int _seedlen; + private int _outlen; + private ECCurve.Fp _curve; + private ECPoint _P; + private ECPoint _Q; + private byte[] _s; + private int _sLength; + + /** + * Construct a SP800-90A Dual EC DRBG. + * <p> + * Minimum entropy requirement is the security strength requested. + * </p> + * @param digest source digest to use with the DRB stream. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public DualECSP800DRBG(Digest digest, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) + { + _digest = digest; + _entropySource = entropySource; + _securityStrength = securityStrength; + + if (Utils.isTooLarge(personalizationString, MAX_PERSONALIZATION_STRING / 8)) + { + throw new IllegalArgumentException("Personalization string too large"); + } + + if (entropySource.entropySize() < securityStrength || entropySource.entropySize() > MAX_ENTROPY_LENGTH) + { + throw new IllegalArgumentException("EntropySource must provide between " + securityStrength + " and " + MAX_ENTROPY_LENGTH + " bits"); + } + + byte[] entropy = entropySource.getEntropy(); + byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString); + + if (securityStrength <= 128) + { + if (Utils.getMaxSecurityStrength(digest) < 128) + { + throw new IllegalArgumentException("Requested security strength is not supported by digest"); + } + _seedlen = 256; + _outlen = 240 / 8; + _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-256").getCurve(); + _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p256_Px), new ECFieldElement.Fp(_curve.getQ(), p256_Py)); + _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p256_Qx), new ECFieldElement.Fp(_curve.getQ(), p256_Qy)); + } + else if (securityStrength <= 192) + { + if (Utils.getMaxSecurityStrength(digest) < 192) + { + throw new IllegalArgumentException("Requested security strength is not supported by digest"); + } + _seedlen = 384; + _outlen = 368 / 8; + _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-384").getCurve(); + _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p384_Px), new ECFieldElement.Fp(_curve.getQ(), p384_Py)); + _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p384_Qx), new ECFieldElement.Fp(_curve.getQ(), p384_Qy)); + } + else if (securityStrength <= 256) + { + if (Utils.getMaxSecurityStrength(digest) < 256) + { + throw new IllegalArgumentException("Requested security strength is not supported by digest"); + } + _seedlen = 521; + _outlen = 504 / 8; + _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-521").getCurve(); + _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p521_Px), new ECFieldElement.Fp(_curve.getQ(), p521_Py)); + _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p521_Qx), new ECFieldElement.Fp(_curve.getQ(), p521_Qy)); + } + else + { + throw new IllegalArgumentException("security strength cannot be greater than 256 bits"); + } + + _s = Utils.hash_df(_digest, seedMaterial, _seedlen); + _sLength = _s.length; + + _reseedCounter = 0; + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant) + { + int numberOfBits = output.length*8; + int m = output.length / _outlen; + + if (Utils.isTooLarge(additionalInput, MAX_ADDITIONAL_INPUT / 8)) + { + throw new IllegalArgumentException("Additional input too large"); + } + + if (_reseedCounter + m > RESEED_MAX) + { + return -1; + } + + if (predictionResistant) + { + reseed(additionalInput); + additionalInput = null; + } + + if (additionalInput != null) + { + // Note: we ignore the use of pad8 on the additional input as we mandate byte arrays for it. + additionalInput = Utils.hash_df(_digest, additionalInput, _seedlen); + } + + for (int i = 0; i < m; i++) + { + BigInteger t = new BigInteger(1, xor(_s, additionalInput)); + + _s = _P.multiply(t).getX().toBigInteger().toByteArray(); + + //System.err.println("S: " + new String(Hex.encode(_s))); + + byte[] r = _Q.multiply(new BigInteger(1, _s)).getX().toBigInteger().toByteArray(); + + if (r.length > _outlen) + { + System.arraycopy(r, r.length - _outlen, output, i * _outlen, _outlen); + } + else + { + System.arraycopy(r, 0, output, i * _outlen + (_outlen - r.length), r.length); + } + + //System.err.println("R: " + new String(Hex.encode(r))); + additionalInput = null; + + _reseedCounter++; + } + + if (m * _outlen < output.length) + { + BigInteger t = new BigInteger(1, xor(_s, additionalInput)); + + _s = _P.multiply(t).getX().toBigInteger().toByteArray(); + + byte[] r = _Q.multiply(new BigInteger(1, _s)).getX().toBigInteger().toByteArray(); + + System.arraycopy(r, 0, output, m * _outlen, output.length - (m * _outlen)); + } + + // Need to preserve length of S as unsigned int. + _s = BigIntegers.asUnsignedByteArray(_sLength, _P.multiply(new BigInteger(1, _s)).getX().toBigInteger()); + + return numberOfBits; + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void reseed(byte[] additionalInput) + { + if (Utils.isTooLarge(additionalInput, MAX_ADDITIONAL_INPUT / 8)) + { + throw new IllegalArgumentException("Additional input string too large"); + } + + byte[] entropy = _entropySource.getEntropy(); + byte[] seedMaterial = Arrays.concatenate(pad8(_s, _seedlen), entropy, additionalInput); + + _s = Utils.hash_df(_digest, seedMaterial, _seedlen); + + _reseedCounter = 0; + } + + private byte[] xor(byte[] a, byte[] b) + { + if (b == null) + { + return a; + } + + byte[] rv = new byte[a.length]; + + for (int i = 0; i != rv.length; i++) + { + rv[i] = (byte)(a[i] ^ b[i]); + } + + return rv; + } + + // Note: works in place + private byte[] pad8(byte[] s, int seedlen) + { + if (seedlen % 8 == 0) + { + return s; + } + + int shift = 8 - (seedlen % 8); + int carry = 0; + + for (int i = s.length - 1; i >= 0; i--) + { + int b = s[i] & 0xff; + s[i] = (byte)((b << shift) | (carry >> (8 - shift))); + carry = b; + } + + return s; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/drbg/HMacSP800DRBG.java b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/HMacSP800DRBG.java new file mode 100644 index 00000000..3ddeaac6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/HMacSP800DRBG.java @@ -0,0 +1,171 @@ +package org.bouncycastle.crypto.prng.drbg; + +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.prng.EntropySource; +import org.bouncycastle.util.Arrays; + +/** + * A SP800-90A HMAC DRBG. + */ +public class HMacSP800DRBG + implements SP80090DRBG +{ + private final static long RESEED_MAX = 1L << (48 - 1); + private final static int MAX_BITS_REQUEST = 1 << (19 - 1); + + private byte[] _K; + private byte[] _V; + private long _reseedCounter; + private EntropySource _entropySource; + private Mac _hMac; + + /** + * Construct a SP800-90A Hash DRBG. + * <p> + * Minimum entropy requirement is the security strength requested. + * </p> + * @param hMac Hash MAC to base the DRBG on. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public HMacSP800DRBG(Mac hMac, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) + { + if (securityStrength > Utils.getMaxSecurityStrength(hMac)) + { + throw new IllegalArgumentException("Requested security strength is not supported by the derivation function"); + } + + if (entropySource.entropySize() < securityStrength) + { + throw new IllegalArgumentException("Not enough entropy for security strength required"); + } + + _entropySource = entropySource; + _hMac = hMac; + + byte[] entropy = entropySource.getEntropy(); + byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString); + + _K = new byte[hMac.getMacSize()]; + _V = new byte[_K.length]; + Arrays.fill(_V, (byte)1); + + hmac_DRBG_Update(seedMaterial); + + _reseedCounter = 1; + } + + private void hmac_DRBG_Update(byte[] seedMaterial) + { + hmac_DRBG_Update_Func(seedMaterial, (byte)0x00); + if (seedMaterial != null) + { + hmac_DRBG_Update_Func(seedMaterial, (byte)0x01); + } + } + + private void hmac_DRBG_Update_Func(byte[] seedMaterial, byte vValue) + { + _hMac.init(new KeyParameter(_K)); + + _hMac.update(_V, 0, _V.length); + _hMac.update(vValue); + + if (seedMaterial != null) + { + _hMac.update(seedMaterial, 0, seedMaterial.length); + } + + _hMac.doFinal(_K, 0); + + _hMac.init(new KeyParameter(_K)); + _hMac.update(_V, 0, _V.length); + + _hMac.doFinal(_V, 0); + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant) + { + int numberOfBits = output.length * 8; + + if (numberOfBits > MAX_BITS_REQUEST) + { + throw new IllegalArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST); + } + + if (_reseedCounter > RESEED_MAX) + { + return -1; + } + + if (predictionResistant) + { + reseed(additionalInput); + additionalInput = null; + } + + // 2. + if (additionalInput != null) + { + hmac_DRBG_Update(additionalInput); + } + + // 3. + byte[] rv = new byte[output.length]; + + int m = output.length / _V.length; + + _hMac.init(new KeyParameter(_K)); + + for (int i = 0; i < m; i++) + { + _hMac.update(_V, 0, _V.length); + _hMac.doFinal(_V, 0); + + System.arraycopy(_V, 0, rv, i * _V.length, _V.length); + } + + if (m * _V.length < rv.length) + { + _hMac.update(_V, 0, _V.length); + _hMac.doFinal(_V, 0); + + System.arraycopy(_V, 0, rv, m * _V.length, rv.length - (m * _V.length)); + } + + hmac_DRBG_Update(additionalInput); + + _reseedCounter++; + + System.arraycopy(rv, 0, output, 0, output.length); + + return numberOfBits; + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void reseed(byte[] additionalInput) + { + byte[] entropy = _entropySource.getEntropy(); + byte[] seedMaterial = Arrays.concatenate(entropy, additionalInput); + + hmac_DRBG_Update(seedMaterial); + + _reseedCounter = 1; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/drbg/HashSP800DRBG.java b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/HashSP800DRBG.java new file mode 100644 index 00000000..4ed57163 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/HashSP800DRBG.java @@ -0,0 +1,269 @@ +package org.bouncycastle.crypto.prng.drbg; + +import java.util.Hashtable; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.prng.EntropySource; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; + +/** + * A SP800-90A Hash DRBG. + */ +public class HashSP800DRBG + implements SP80090DRBG +{ + private final static byte[] ONE = { 0x01 }; + + private final static long RESEED_MAX = 1L << (48 - 1); + private final static int MAX_BITS_REQUEST = 1 << (19 - 1); + + private final static Hashtable seedlens = new Hashtable(); + + static + { + seedlens.put("SHA-1", Integers.valueOf(440)); + seedlens.put("SHA-224", Integers.valueOf(440)); + seedlens.put("SHA-256", Integers.valueOf(440)); + seedlens.put("SHA-512/256", Integers.valueOf(440)); + seedlens.put("SHA-512/224", Integers.valueOf(440)); + seedlens.put("SHA-384", Integers.valueOf(888)); + seedlens.put("SHA-512", Integers.valueOf(888)); + } + + private Digest _digest; + private byte[] _V; + private byte[] _C; + private long _reseedCounter; + private EntropySource _entropySource; + private int _securityStrength; + private int _seedLength; + + /** + * Construct a SP800-90A Hash DRBG. + * <p> + * Minimum entropy requirement is the security strength requested. + * </p> + * @param digest source digest to use for DRB stream. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public HashSP800DRBG(Digest digest, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) + { + if (securityStrength > Utils.getMaxSecurityStrength(digest)) + { + throw new IllegalArgumentException("Requested security strength is not supported by the derivation function"); + } + + if (entropySource.entropySize() < securityStrength) + { + throw new IllegalArgumentException("Not enough entropy for security strength required"); + } + + _digest = digest; + _entropySource = entropySource; + _securityStrength = securityStrength; + _seedLength = ((Integer)seedlens.get(digest.getAlgorithmName())).intValue(); + + // 1. seed_material = entropy_input || nonce || personalization_string. + // 2. seed = Hash_df (seed_material, seedlen). + // 3. V = seed. + // 4. C = Hash_df ((0x00 || V), seedlen). Comment: Preceed V with a byte + // of zeros. + // 5. reseed_counter = 1. + // 6. Return V, C, and reseed_counter as the initial_working_state + + byte[] entropy = entropySource.getEntropy(); + byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString); + byte[] seed = Utils.hash_df(_digest, seedMaterial, _seedLength); + + _V = seed; + byte[] subV = new byte[_V.length + 1]; + System.arraycopy(_V, 0, subV, 1, _V.length); + _C = Utils.hash_df(_digest, subV, _seedLength); + + _reseedCounter = 1; + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant) + { + // 1. If reseed_counter > reseed_interval, then return an indication that a + // reseed is required. + // 2. If (additional_input != Null), then do + // 2.1 w = Hash (0x02 || V || additional_input). + // 2.2 V = (V + w) mod 2^seedlen + // . + // 3. (returned_bits) = Hashgen (requested_number_of_bits, V). + // 4. H = Hash (0x03 || V). + // 5. V = (V + H + C + reseed_counter) mod 2^seedlen + // . + // 6. reseed_counter = reseed_counter + 1. + // 7. Return SUCCESS, returned_bits, and the new values of V, C, and + // reseed_counter for the new_working_state. + int numberOfBits = output.length*8; + + if (numberOfBits > MAX_BITS_REQUEST) + { + throw new IllegalArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST); + } + + if (_reseedCounter > RESEED_MAX) + { + return -1; + } + + if (predictionResistant) + { + reseed(additionalInput); + additionalInput = null; + } + + // 2. + if (additionalInput != null) + { + byte[] newInput = new byte[1 + _V.length + additionalInput.length]; + newInput[0] = 0x02; + System.arraycopy(_V, 0, newInput, 1, _V.length); + // TODO: inOff / inLength + System.arraycopy(additionalInput, 0, newInput, 1 + _V.length, additionalInput.length); + byte[] w = hash(newInput); + + addTo(_V, w); + } + + // 3. + byte[] rv = hashgen(_V, numberOfBits); + + // 4. + byte[] subH = new byte[_V.length + 1]; + System.arraycopy(_V, 0, subH, 1, _V.length); + subH[0] = 0x03; + + byte[] H = hash(subH); + + // 5. + addTo(_V, H); + addTo(_V, _C); + byte[] c = new byte[4]; + c[0] = (byte)(_reseedCounter >> 24); + c[1] = (byte)(_reseedCounter >> 16); + c[2] = (byte)(_reseedCounter >> 8); + c[3] = (byte)_reseedCounter; + + addTo(_V, c); + + _reseedCounter++; + + System.arraycopy(rv, 0, output, 0, output.length); + + return numberOfBits; + } + + // this will always add the shorter length byte array mathematically to the + // longer length byte array. + // be careful.... + private void addTo(byte[] longer, byte[] shorter) + { + int carry = 0; + for (int i=1;i <= shorter.length; i++) // warning + { + int res = (longer[longer.length-i] & 0xff) + (shorter[shorter.length-i] & 0xff) + carry; + carry = (res > 0xff) ? 1 : 0; + longer[longer.length-i] = (byte)res; + } + + for (int i=shorter.length+1;i <= longer.length; i++) // warning + { + int res = (longer[longer.length-i] & 0xff) + carry; + carry = (res > 0xff) ? 1 : 0; + longer[longer.length-i] = (byte)res; + } + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void reseed(byte[] additionalInput) + { + // 1. seed_material = 0x01 || V || entropy_input || additional_input. + // + // 2. seed = Hash_df (seed_material, seedlen). + // + // 3. V = seed. + // + // 4. C = Hash_df ((0x00 || V), seedlen). + // + // 5. reseed_counter = 1. + // + // 6. Return V, C, and reseed_counter for the new_working_state. + // + // Comment: Precede with a byte of all zeros. + byte[] entropy = _entropySource.getEntropy(); + byte[] seedMaterial = Arrays.concatenate(ONE, _V, entropy, additionalInput); + byte[] seed = Utils.hash_df(_digest, seedMaterial, _seedLength); + + _V = seed; + byte[] subV = new byte[_V.length + 1]; + subV[0] = 0x00; + System.arraycopy(_V, 0, subV, 1, _V.length); + _C = Utils.hash_df(_digest, subV, _seedLength); + + _reseedCounter = 1; + } + + private byte[] hash(byte[] input) + { + _digest.update(input, 0, input.length); + byte[] hash = new byte[_digest.getDigestSize()]; + _digest.doFinal(hash, 0); + return hash; + } + + // 1. m = [requested_number_of_bits / outlen] + // 2. data = V. + // 3. W = the Null string. + // 4. For i = 1 to m + // 4.1 wi = Hash (data). + // 4.2 W = W || wi. + // 4.3 data = (data + 1) mod 2^seedlen + // . + // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W. + private byte[] hashgen(byte[] input, int lengthInBits) + { + int digestSize = _digest.getDigestSize(); + int m = (lengthInBits / 8) / digestSize; + + byte[] data = new byte[input.length]; + System.arraycopy(input, 0, data, 0, input.length); + + byte[] W = new byte[lengthInBits / 8]; + + byte[] dig; + for (int i = 0; i <= m; i++) + { + dig = hash(data); + + int bytesToCopy = ((W.length - i * dig.length) > dig.length) + ? dig.length + : (W.length - i * dig.length); + System.arraycopy(dig, 0, W, i * dig.length, bytesToCopy); + + addTo(data, ONE); + } + + return W; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/drbg/SP80090DRBG.java b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/SP80090DRBG.java new file mode 100644 index 00000000..93bc8945 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/SP80090DRBG.java @@ -0,0 +1,25 @@ +package org.bouncycastle.crypto.prng.drbg; + +/** + * Interface to SP800-90A deterministic random bit generators. + */ +public interface SP80090DRBG +{ + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + int generate(byte[] output, byte[] additionalInput, boolean predictionResistant); + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + void reseed(byte[] additionalInput); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/prng/drbg/Utils.java b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/Utils.java new file mode 100644 index 00000000..f7a41176 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/prng/drbg/Utils.java @@ -0,0 +1,103 @@ +package org.bouncycastle.crypto.prng.drbg; + +import java.util.Hashtable; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.util.Integers; + +class Utils +{ + static final Hashtable maxSecurityStrengths = new Hashtable(); + + static + { + maxSecurityStrengths.put("SHA-1", Integers.valueOf(128)); + + maxSecurityStrengths.put("SHA-224", Integers.valueOf(192)); + maxSecurityStrengths.put("SHA-256", Integers.valueOf(256)); + maxSecurityStrengths.put("SHA-384", Integers.valueOf(256)); + maxSecurityStrengths.put("SHA-512", Integers.valueOf(256)); + + maxSecurityStrengths.put("SHA-512/224", Integers.valueOf(192)); + maxSecurityStrengths.put("SHA-512/256", Integers.valueOf(256)); + } + + static int getMaxSecurityStrength(Digest d) + { + return ((Integer)maxSecurityStrengths.get(d.getAlgorithmName())).intValue(); + } + + static int getMaxSecurityStrength(Mac m) + { + String name = m.getAlgorithmName(); + + return ((Integer)maxSecurityStrengths.get(name.substring(0, name.indexOf("/")))).intValue(); + } + + /** + * Used by both Dual EC and Hash. + */ + static byte[] hash_df(Digest digest, byte[] seedMaterial, int seedLength) + { + // 1. temp = the Null string. + // 2. . + // 3. counter = an 8-bit binary value representing the integer "1". + // 4. For i = 1 to len do + // Comment : In step 4.1, no_of_bits_to_return + // is used as a 32-bit string. + // 4.1 temp = temp || Hash (counter || no_of_bits_to_return || + // input_string). + // 4.2 counter = counter + 1. + // 5. requested_bits = Leftmost (no_of_bits_to_return) of temp. + // 6. Return SUCCESS and requested_bits. + byte[] temp = new byte[(seedLength + 7) / 8]; + + int len = temp.length / digest.getDigestSize(); + int counter = 1; + + byte[] dig = new byte[digest.getDigestSize()]; + + for (int i = 0; i <= len; i++) + { + digest.update((byte)counter); + + digest.update((byte)(seedLength >> 24)); + digest.update((byte)(seedLength >> 16)); + digest.update((byte)(seedLength >> 8)); + digest.update((byte)seedLength); + + digest.update(seedMaterial, 0, seedMaterial.length); + + digest.doFinal(dig, 0); + + int bytesToCopy = ((temp.length - i * dig.length) > dig.length) + ? dig.length + : (temp.length - i * dig.length); + System.arraycopy(dig, 0, temp, i * dig.length, bytesToCopy); + + counter++; + } + + // do a left shift to get rid of excess bits. + if (seedLength % 8 != 0) + { + int shift = 8 - (seedLength % 8); + int carry = 0; + + for (int i = 0; i != temp.length; i++) + { + int b = temp[i] & 0xff; + temp[i] = (byte)((b >>> shift) | (carry << (8 - shift))); + carry = b; + } + } + + return temp; + } + + static boolean isTooLarge(byte[] bytes, int maxBytes) + { + return bytes != null && bytes.length > maxBytes; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/DSADigestSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/DSADigestSigner.java new file mode 100644 index 00000000..2e4c48d3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/DSADigestSigner.java @@ -0,0 +1,163 @@ +package org.bouncycastle.crypto.signers; + +import java.io.IOException; +import java.math.BigInteger; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERInteger; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +public class DSADigestSigner + implements Signer +{ + private final Digest digest; + private final DSA dsaSigner; + private boolean forSigning; + + public DSADigestSigner( + DSA signer, + Digest digest) + { + this.digest = digest; + this.dsaSigner = signer; + } + + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("Signing Requires Private Key."); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("Verification Requires Public Key."); + } + + reset(); + + dsaSigner.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + { + if (!forSigning) + { + throw new IllegalStateException("DSADigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + BigInteger[] sig = dsaSigner.generateSignature(hash); + + try + { + return derEncode(sig[0], sig[1]); + } + catch (IOException e) + { + throw new IllegalStateException("unable to encode signature"); + } + } + + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("DSADigestSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + BigInteger[] sig = derDecode(signature); + return dsaSigner.verifySignature(hash, sig[0], sig[1]); + } + catch (IOException e) + { + return false; + } + } + + public void reset() + { + digest.reset(); + } + + private byte[] derEncode( + BigInteger r, + BigInteger s) + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new DERInteger(r)); + v.add(new DERInteger(s)); + + return new DERSequence(v).getEncoded(ASN1Encoding.DER); + } + + private BigInteger[] derDecode( + byte[] encoding) + throws IOException + { + ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding); + + return new BigInteger[] + { + ((DERInteger)s.getObjectAt(0)).getValue(), + ((DERInteger)s.getObjectAt(1)).getValue() + }; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java new file mode 100644 index 00000000..a96cef07 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java @@ -0,0 +1,138 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.DSAKeyParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * The Digital Signature Algorithm - as described in "Handbook of Applied + * Cryptography", pages 452 - 453. + */ +public class DSASigner + implements DSA +{ + DSAKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (DSAPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (DSAPrivateKeyParameters)param; + } + } + else + { + this.key = (DSAPublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional DSA the message should be a SHA-1 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + DSAParameters params = key.getParameters(); + BigInteger m = calculateE(params.getQ(), message); + BigInteger k; + int qBitLength = params.getQ().bitLength(); + + do + { + k = new BigInteger(qBitLength, random); + } + while (k.compareTo(params.getQ()) >= 0); + + BigInteger r = params.getG().modPow(k, params.getP()).mod(params.getQ()); + + k = k.modInverse(params.getQ()).multiply( + m.add(((DSAPrivateKeyParameters)key).getX().multiply(r))); + + BigInteger s = k.mod(params.getQ()); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a DSA signature for + * the passed in message for standard DSA the message should be a + * SHA-1 hash of the real message to be verified. + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + DSAParameters params = key.getParameters(); + BigInteger m = calculateE(params.getQ(), message); + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(r) >= 0 || params.getQ().compareTo(r) <= 0) + { + return false; + } + + if (zero.compareTo(s) >= 0 || params.getQ().compareTo(s) <= 0) + { + return false; + } + + BigInteger w = s.modInverse(params.getQ()); + + BigInteger u1 = m.multiply(w).mod(params.getQ()); + BigInteger u2 = r.multiply(w).mod(params.getQ()); + + u1 = params.getG().modPow(u1, params.getP()); + u2 = ((DSAPublicKeyParameters)key).getY().modPow(u2, params.getP()); + + BigInteger v = u1.multiply(u2).mod(params.getP()).mod(params.getQ()); + + return v.equals(r); + } + + private BigInteger calculateE(BigInteger n, byte[] message) + { + if (n.bitLength() >= message.length * 8) + { + return new BigInteger(1, message); + } + else + { + byte[] trunc = new byte[n.bitLength() / 8]; + + System.arraycopy(message, 0, trunc, 0, trunc.length); + + return new BigInteger(1, trunc); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java new file mode 100644 index 00000000..a8fc194e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java @@ -0,0 +1,163 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; + +/** + * DSTU 4145-2002 + * <p> + * National Ukrainian standard of digital signature based on elliptic curves (DSTU 4145-2002). + * </p> + */ +public class DSTU4145Signer + implements DSA +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private ECKeyParameters key; + private SecureRandom random; + + public void init(boolean forSigning, CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + param = rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + } + + this.key = (ECPrivateKeyParameters)param; + } + else + { + this.key = (ECPublicKeyParameters)param; + } + + } + + public BigInteger[] generateSignature(byte[] message) + { + ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message); + if (h.toBigInteger().signum() == 0) + { + h = key.getParameters().getCurve().fromBigInteger(ONE); + } + + BigInteger e, r, s; + ECFieldElement Fe, y; + + do + { + do + { + do + { + e = generateRandomInteger(key.getParameters().getN(), random); + Fe = key.getParameters().getG().multiply(e).getX(); + } + while (Fe.toBigInteger().signum() == 0); + + y = h.multiply(Fe); + r = fieldElement2Integer(key.getParameters().getN(), y); + } + while (r.signum() == 0); + + s = r.multiply(((ECPrivateKeyParameters)key).getD()).add(e).mod(key.getParameters().getN()); + } + while (s.signum() == 0); + + return new BigInteger[]{r, s}; + } + + public boolean verifySignature(byte[] message, BigInteger r, BigInteger s) + { + if (r.signum() == 0 || s.signum() == 0) + { + return false; + } + if (r.compareTo(key.getParameters().getN()) >= 0 || s.compareTo(key.getParameters().getN()) >= 0) + { + return false; + } + + ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message); + if (h.toBigInteger().signum() == 0) + { + h = key.getParameters().getCurve().fromBigInteger(ONE); + } + + ECPoint R = ECAlgorithms.sumOfTwoMultiplies(key.getParameters().getG(), s, ((ECPublicKeyParameters)key).getQ(), r); + + // components must be bogus. + if (R.isInfinity()) + { + return false; + } + + ECFieldElement y = h.multiply(R.getX()); + return fieldElement2Integer(key.getParameters().getN(), y).compareTo(r) == 0; + } + + /** + * Generates random integer such, than its bit length is less than that of n + */ + private static BigInteger generateRandomInteger(BigInteger n, SecureRandom random) + { + return new BigInteger(n.bitLength() - 1, random); + } + + private static void reverseBytes(byte[] bytes) + { + byte tmp; + + for (int i=0; i<bytes.length/2; i++) + { + tmp=bytes[i]; + bytes[i]=bytes[bytes.length-1-i]; + bytes[bytes.length-1-i]=tmp; + } + } + + private static ECFieldElement hash2FieldElement(ECCurve curve, byte[] hash) + { + byte[] data = Arrays.clone(hash); + reverseBytes(data); + BigInteger num = new BigInteger(1, data); + while (num.bitLength() >= curve.getFieldSize()) + { + num = num.clearBit(num.bitLength() - 1); + } + + return curve.fromBigInteger(num); + } + + private static BigInteger fieldElement2Integer(BigInteger n, ECFieldElement fieldElement) + { + BigInteger num = fieldElement.toBigInteger(); + while (num.bitLength() >= n.bitLength()) + { + num = num.clearBit(num.bitLength() - 1); + } + + return num; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java new file mode 100644 index 00000000..a80c574b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java @@ -0,0 +1,169 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +/** + * EC-DSA as described in X9.62 + */ +public class ECDSASigner + implements ECConstants, DSA +{ + ECKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + // 5.3 pg 28 + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional DSA the message should be a SHA-1 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + BigInteger n = key.getParameters().getN(); + BigInteger e = calculateE(n, message); + BigInteger r = null; + BigInteger s = null; + + // 5.3.2 + do // generate s + { + BigInteger k = null; + int nBitLength = n.bitLength(); + + do // generate r + { + do + { + k = new BigInteger(nBitLength, random); + } + while (k.equals(ZERO) || k.compareTo(n) >= 0); + + ECPoint p = key.getParameters().getG().multiply(k); + + // 5.3.3 + BigInteger x = p.getX().toBigInteger(); + + r = x.mod(n); + } + while (r.equals(ZERO)); + + BigInteger d = ((ECPrivateKeyParameters)key).getD(); + + s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); + } + while (s.equals(ZERO)); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + // 5.4 pg 29 + /** + * return true if the value r and s represent a DSA signature for + * the passed in message (for standard DSA the message should be + * a SHA-1 hash of the real message to be verified). + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + BigInteger n = key.getParameters().getN(); + BigInteger e = calculateE(n, message); + + // r in the range [1,n-1] + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + BigInteger c = s.modInverse(n); + + BigInteger u1 = e.multiply(c).mod(n); + BigInteger u2 = r.multiply(c).mod(n); + + ECPoint G = key.getParameters().getG(); + ECPoint Q = ((ECPublicKeyParameters)key).getQ(); + + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2); + + // components must be bogus. + if (point.isInfinity()) + { + return false; + } + + BigInteger v = point.getX().toBigInteger().mod(n); + + return v.equals(r); + } + + private BigInteger calculateE(BigInteger n, byte[] message) + { + int log2n = n.bitLength(); + int messageBitLength = message.length * 8; + + if (log2n >= messageBitLength) + { + return new BigInteger(1, message); + } + else + { + BigInteger trunc = new BigInteger(1, message); + + trunc = trunc.shiftRight(messageBitLength - log2n); + + return trunc; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java new file mode 100644 index 00000000..7256d353 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java @@ -0,0 +1,158 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * GOST R 34.10-2001 Signature Algorithm + */ +public class ECGOST3410Signer + implements DSA +{ + ECKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional GOST3410 the message should be a GOST3411 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger e = new BigInteger(1, mRev); + BigInteger n = key.getParameters().getN(); + + BigInteger r = null; + BigInteger s = null; + + do // generate s + { + BigInteger k = null; + + do // generate r + { + do + { + k = new BigInteger(n.bitLength(), random); + } + while (k.equals(ECConstants.ZERO)); + + ECPoint p = key.getParameters().getG().multiply(k); + + BigInteger x = p.getX().toBigInteger(); + + r = x.mod(n); + } + while (r.equals(ECConstants.ZERO)); + + BigInteger d = ((ECPrivateKeyParameters)key).getD(); + + s = (k.multiply(e)).add(d.multiply(r)).mod(n); + } + while (s.equals(ECConstants.ZERO)); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a GOST3410 signature for + * the passed in message (for standard GOST3410 the message should be + * a GOST3411 hash of the real message to be verified). + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger e = new BigInteger(1, mRev); + BigInteger n = key.getParameters().getN(); + + // r in the range [1,n-1] + if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.compareTo(ECConstants.ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + BigInteger v = e.modInverse(n); + + BigInteger z1 = s.multiply(v).mod(n); + BigInteger z2 = (n.subtract(r)).multiply(v).mod(n); + + ECPoint G = key.getParameters().getG(); // P + ECPoint Q = ((ECPublicKeyParameters)key).getQ(); + + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2); + + // components must be bogus. + if (point.isInfinity()) + { + return false; + } + + BigInteger R = point.getX().toBigInteger().mod(n); + + return R.equals(r); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java new file mode 100644 index 00000000..07e8ca7b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java @@ -0,0 +1,188 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * EC-NR as described in IEEE 1363-2000 + */ +public class ECNRSigner + implements DSA +{ + private boolean forSigning; + private ECKeyParameters key; + private SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + this.forSigning = forSigning; + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + // Section 7.2.5 ECSP-NR, pg 34 + /** + * generate a signature for the given message using the key we were + * initialised with. Generally, the order of the curve should be at + * least as long as the hash of the message of interest, and with + * ECNR it *must* be at least as long. + * + * @param digest the digest to be signed. + * @exception DataLengthException if the digest is longer than the key allows + */ + public BigInteger[] generateSignature( + byte[] digest) + { + if (! this.forSigning) + { + throw new IllegalStateException("not initialised for signing"); + } + + BigInteger n = ((ECPrivateKeyParameters)this.key).getParameters().getN(); + int nBitLength = n.bitLength(); + + BigInteger e = new BigInteger(1, digest); + int eBitLength = e.bitLength(); + + ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)key; + + if (eBitLength > nBitLength) + { + throw new DataLengthException("input too large for ECNR key."); + } + + BigInteger r = null; + BigInteger s = null; + + AsymmetricCipherKeyPair tempPair; + do // generate r + { + // generate another, but very temporary, key pair using + // the same EC parameters + ECKeyPairGenerator keyGen = new ECKeyPairGenerator(); + + keyGen.init(new ECKeyGenerationParameters(privKey.getParameters(), this.random)); + + tempPair = keyGen.generateKeyPair(); + + // BigInteger Vx = tempPair.getPublic().getW().getAffineX(); + ECPublicKeyParameters V = (ECPublicKeyParameters)tempPair.getPublic(); // get temp's public key + BigInteger Vx = V.getQ().getX().toBigInteger(); // get the point's x coordinate + + r = Vx.add(e).mod(n); + } + while (r.equals(ECConstants.ZERO)); + + // generate s + BigInteger x = privKey.getD(); // private key value + BigInteger u = ((ECPrivateKeyParameters)tempPair.getPrivate()).getD(); // temp's private key value + s = u.subtract(r.multiply(x)).mod(n); + + BigInteger[] res = new BigInteger[2]; + res[0] = r; + res[1] = s; + + return res; + } + + // Section 7.2.6 ECVP-NR, pg 35 + /** + * return true if the value r and s represent a signature for the + * message passed in. Generally, the order of the curve should be at + * least as long as the hash of the message of interest, and with + * ECNR, it *must* be at least as long. But just in case the signer + * applied mod(n) to the longer digest, this implementation will + * apply mod(n) during verification. + * + * @param digest the digest to be verified. + * @param r the r value of the signature. + * @param s the s value of the signature. + * @exception DataLengthException if the digest is longer than the key allows + */ + public boolean verifySignature( + byte[] digest, + BigInteger r, + BigInteger s) + { + if (this.forSigning) + { + throw new IllegalStateException("not initialised for verifying"); + } + + ECPublicKeyParameters pubKey = (ECPublicKeyParameters)key; + BigInteger n = pubKey.getParameters().getN(); + int nBitLength = n.bitLength(); + + BigInteger e = new BigInteger(1, digest); + int eBitLength = e.bitLength(); + + if (eBitLength > nBitLength) + { + throw new DataLengthException("input too large for ECNR key."); + } + + // r in the range [1,n-1] + if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [0,n-1] NB: ECNR spec says 0 + if (s.compareTo(ECConstants.ZERO) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + // compute P = sG + rW + + ECPoint G = pubKey.getParameters().getG(); + ECPoint W = pubKey.getQ(); + // calculate P using Bouncy math + ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, r); + + // components must be bogus. + if (P.isInfinity()) + { + return false; + } + + BigInteger x = P.getX().toBigInteger(); + BigInteger t = r.subtract(x).mod(n); + + return t.equals(e); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/GOST3410Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/GOST3410Signer.java new file mode 100644 index 00000000..9fcc41b9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/GOST3410Signer.java @@ -0,0 +1,127 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.*; + +import java.security.SecureRandom; +import java.math.BigInteger; + +/** + * GOST R 34.10-94 Signature Algorithm + */ +public class GOST3410Signer + implements DSA +{ + GOST3410KeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (GOST3410PrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (GOST3410PrivateKeyParameters)param; + } + } + else + { + this.key = (GOST3410PublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional GOST3410 the message should be a GOST3411 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger m = new BigInteger(1, mRev); + GOST3410Parameters params = key.getParameters(); + BigInteger k; + + do + { + k = new BigInteger(params.getQ().bitLength(), random); + } + while (k.compareTo(params.getQ()) >= 0); + + BigInteger r = params.getA().modPow(k, params.getP()).mod(params.getQ()); + + BigInteger s = k.multiply(m). + add(((GOST3410PrivateKeyParameters)key).getX().multiply(r)). + mod(params.getQ()); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a GOST3410 signature for + * the passed in message for standard GOST3410 the message should be a + * GOST3411 hash of the real message to be verified. + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger m = new BigInteger(1, mRev); + GOST3410Parameters params = key.getParameters(); + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(r) >= 0 || params.getQ().compareTo(r) <= 0) + { + return false; + } + + if (zero.compareTo(s) >= 0 || params.getQ().compareTo(s) <= 0) + { + return false; + } + + BigInteger v = m.modPow(params.getQ().subtract(new BigInteger("2")),params.getQ()); + + BigInteger z1 = s.multiply(v).mod(params.getQ()); + BigInteger z2 = (params.getQ().subtract(r)).multiply(v).mod(params.getQ()); + + z1 = params.getA().modPow(z1, params.getP()); + z2 = ((GOST3410PublicKeyParameters)key).getY().modPow(z2, params.getP()); + + BigInteger u = z1.multiply(z2).mod(params.getP()).mod(params.getQ()); + + return u.equals(r); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/GenericSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/GenericSigner.java new file mode 100644 index 00000000..6819e141 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/GenericSigner.java @@ -0,0 +1,136 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +public class GenericSigner + implements Signer +{ + private final AsymmetricBlockCipher engine; + private final Digest digest; + private boolean forSigning; + + public GenericSigner( + AsymmetricBlockCipher engine, + Digest digest) + { + this.engine = engine; + this.digest = digest; + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("signing requires private key"); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("verification requires public key"); + } + + reset(); + + engine.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + if (!forSigning) + { + throw new IllegalStateException("GenericSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + return engine.processBlock(hash, 0, hash.length); + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("GenericSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + byte[] sig = engine.processBlock(signature, 0, signature.length); + + return Arrays.constantTimeAreEqual(sig, hash); + } + catch (Exception e) + { + return false; + } + } + + public void reset() + { + digest.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java new file mode 100644 index 00000000..e3dcc08e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java @@ -0,0 +1,668 @@ +package org.bouncycastle.crypto.signers; + +import java.security.SecureRandom; +import java.util.Hashtable; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.SignerWithRecovery; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.ParametersWithSalt; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; + +/** + * ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3). + * <p/> + * Note: the usual length for the salt is the length of the hash + * function used in bytes. + */ +public class ISO9796d2PSSSigner + implements SignerWithRecovery +{ + 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; + + private static Hashtable trailerMap = new Hashtable(); + + static + { + trailerMap.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128)); + trailerMap.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160)); + + trailerMap.put("SHA-1", Integers.valueOf(TRAILER_SHA1)); + trailerMap.put("SHA-256", Integers.valueOf(TRAILER_SHA256)); + trailerMap.put("SHA-384", Integers.valueOf(TRAILER_SHA384)); + trailerMap.put("SHA-512", Integers.valueOf(TRAILER_SHA512)); + + trailerMap.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL)); + } + + private Digest digest; + private AsymmetricBlockCipher cipher; + + private SecureRandom random; + private byte[] standardSalt; + + private int hLen; + private int trailer; + private int keyBits; + private byte[] block; + private byte[] mBuf; + private int messageLength; + private int saltLength; + private boolean fullMessage; + private byte[] recoveredMessage; + + private byte[] preSig; + private byte[] preBlock; + private int preMStart; + private int preTLength; + + /** + * Generate a signer for the with either implicit or explicit trailers + * for ISO9796-2, scheme 2 or 3. + * + * @param cipher base cipher to use for signature creation/verification + * @param digest digest to use. + * @param saltLength length of salt in bytes. + * @param implicit whether or not the trailer is implicit or gives the hash. + */ + public ISO9796d2PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int saltLength, + boolean implicit) + { + this.cipher = cipher; + this.digest = digest; + this.hLen = digest.getDigestSize(); + this.saltLength = saltLength; + + if (implicit) + { + trailer = TRAILER_IMPLICIT; + } + else + { + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + trailer = trailerObj.intValue(); + } + else + { + throw new IllegalArgumentException("no valid trailer for digest"); + } + } + } + + /** + * Constructor for a signer with an explicit digest trailer. + * + * @param cipher cipher to use. + * @param digest digest to sign with. + * @param saltLength length of salt in bytes. + */ + public ISO9796d2PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int saltLength) + { + this(cipher, digest, saltLength, false); + } + + /** + * Initialise the signer. + * + * @param forSigning true if for signing, false if for verification. + * @param param parameters for signature generation/verification. If the + * parameters are for generation they should be a ParametersWithRandom, + * a ParametersWithSalt, or just an RSAKeyParameters object. If RSAKeyParameters + * are passed in a SecureRandom will be created. + * @throws IllegalArgumentException if wrong parameter type or a fixed + * salt is passed in which is the wrong length. + */ + public void init( + boolean forSigning, + CipherParameters param) + { + RSAKeyParameters kParam; + int lengthOfSalt = saltLength; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + kParam = (RSAKeyParameters)p.getParameters(); + if (forSigning) + { + random = p.getRandom(); + } + } + else if (param instanceof ParametersWithSalt) + { + ParametersWithSalt p = (ParametersWithSalt)param; + + kParam = (RSAKeyParameters)p.getParameters(); + standardSalt = p.getSalt(); + lengthOfSalt = standardSalt.length; + if (standardSalt.length != saltLength) + { + throw new IllegalArgumentException("Fixed salt is of wrong length"); + } + } + else + { + kParam = (RSAKeyParameters)param; + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.init(forSigning, kParam); + + keyBits = kParam.getModulus().bitLength(); + + block = new byte[(keyBits + 7) / 8]; + + if (trailer == TRAILER_IMPLICIT) + { + mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 1]; + } + else + { + mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 2]; + } + + reset(); + } + + /** + * compare two byte arrays - constant time + */ + private boolean isSameAs( + byte[] a, + byte[] b) + { + boolean isOkay = true; + + if (messageLength != b.length) + { + isOkay = false; + } + + for (int i = 0; i != b.length; i++) + { + if (a[i] != b[i]) + { + isOkay = false; + } + } + + return isOkay; + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + public void updateWithRecoveredMessage(byte[] signature) + throws InvalidCipherTextException + { + byte[] block = cipher.processBlock(signature, 0, signature.length); + + // + // adjust block size for leading zeroes if necessary + // + if (block.length < (keyBits + 7) / 8) + { + byte[] tmp = new byte[(keyBits + 7) / 8]; + + System.arraycopy(block, 0, tmp, tmp.length - block.length, block.length); + clearBlock(block); + block = tmp; + } + + int tLength; + + if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0) + { + tLength = 1; + } + else + { + int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF); + + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + if (sigTrail != trailerObj.intValue()) + { + throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail); + } + } + else + { + throw new IllegalArgumentException("unrecognised hash in signature"); + } + + tLength = 2; + } + + // + // calculate H(m2) + // + byte[] m2Hash = new byte[hLen]; + digest.doFinal(m2Hash, 0); + + // + // remove the mask + // + byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - tLength, hLen, block.length - hLen - tLength); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= 0x7f; + + // + // find out how much padding we've got + // + int mStart = 0; + for (; mStart != block.length; mStart++) + { + if (block[mStart] == 0x01) + { + break; + } + } + + mStart++; + + if (mStart >= block.length) + { + clearBlock(block); + } + + fullMessage = (mStart > 1); + + recoveredMessage = new byte[dbMask.length - mStart - saltLength]; + + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length); + + preSig = signature; + preBlock = block; + preMStart = mStart; + preTLength = tLength; + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + if (preSig == null && messageLength < mBuf.length) + { + mBuf[messageLength++] = b; + } + else + { + digest.update(b); + } + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + if (preSig == null) + { + while (len > 0 && messageLength < mBuf.length) + { + this.update(in[off]); + off++; + len--; + } + } + + if (len > 0) + { + digest.update(in, off, len); + } + } + + /** + * reset the internal state + */ + public void reset() + { + digest.reset(); + messageLength = 0; + if (mBuf != null) + { + clearBlock(mBuf); + } + if (recoveredMessage != null) + { + clearBlock(recoveredMessage); + recoveredMessage = null; + } + fullMessage = false; + if (preSig != null) + { + preSig = null; + clearBlock(preBlock); + preBlock = null; + } + } + + /** + * generate a signature for the loaded message using the key we were + * initialised with. + */ + public byte[] generateSignature() + throws CryptoException + { + int digSize = digest.getDigestSize(); + + byte[] m2Hash = new byte[digSize]; + + digest.doFinal(m2Hash, 0); + + byte[] C = new byte[8]; + LtoOSP(messageLength * 8, C); + + digest.update(C, 0, C.length); + + digest.update(mBuf, 0, messageLength); + + digest.update(m2Hash, 0, m2Hash.length); + + byte[] salt; + + if (standardSalt != null) + { + salt = standardSalt; + } + else + { + salt = new byte[saltLength]; + random.nextBytes(salt); + } + + digest.update(salt, 0, salt.length); + + byte[] hash = new byte[digest.getDigestSize()]; + + digest.doFinal(hash, 0); + + int tLength = 2; + if (trailer == TRAILER_IMPLICIT) + { + tLength = 1; + } + + int off = block.length - messageLength - salt.length - hLen - tLength - 1; + + block[off] = 0x01; + + System.arraycopy(mBuf, 0, block, off + 1, messageLength); + System.arraycopy(salt, 0, block, off + 1 + messageLength, salt.length); + + byte[] dbMask = maskGeneratorFunction1(hash, 0, hash.length, block.length - hLen - tLength); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + System.arraycopy(hash, 0, block, block.length - hLen - tLength, hLen); + + if (trailer == TRAILER_IMPLICIT) + { + block[block.length - 1] = (byte)TRAILER_IMPLICIT; + } + else + { + block[block.length - 2] = (byte)(trailer >>> 8); + block[block.length - 1] = (byte)trailer; + } + + block[0] &= 0x7f; + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(mBuf); + clearBlock(block); + messageLength = 0; + + return b; + } + + /** + * return true if the signature represents a ISO9796-2 signature + * for the passed in message. + */ + public boolean verifySignature( + byte[] signature) + { + // + // calculate H(m2) + // + byte[] m2Hash = new byte[hLen]; + digest.doFinal(m2Hash, 0); + + byte[] block; + int tLength; + int mStart = 0; + + if (preSig == null) + { + try + { + updateWithRecoveredMessage(signature); + } + catch (Exception e) + { + return false; + } + } + else + { + if (!Arrays.areEqual(preSig, signature)) + { + throw new IllegalStateException("updateWithRecoveredMessage called on different signature"); + } + } + + block = preBlock; + mStart = preMStart; + tLength = preTLength; + + preSig = null; + preBlock = null; + + // + // check the hashes + // + byte[] C = new byte[8]; + LtoOSP(recoveredMessage.length * 8, C); + + digest.update(C, 0, C.length); + + if (recoveredMessage.length != 0) + { + digest.update(recoveredMessage, 0, recoveredMessage.length); + } + + digest.update(m2Hash, 0, m2Hash.length); + + // Update for the salt + digest.update(block, mStart + recoveredMessage.length, saltLength); + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + int off = block.length - tLength - hash.length; + + boolean isOkay = true; + + for (int i = 0; i != hash.length; i++) + { + if (hash[i] != block[off + i]) + { + isOkay = false; + } + } + + clearBlock(block); + clearBlock(hash); + + if (!isOkay) + { + fullMessage = false; + clearBlock(recoveredMessage); + return false; + } + + // + // if they've input a message check what we've recovered against + // what was input. + // + if (messageLength != 0) + { + if (!isSameAs(mBuf, recoveredMessage)) + { + clearBlock(mBuf); + return false; + } + messageLength = 0; + } + + clearBlock(mBuf); + return true; + } + + /** + * Return true if the full message was recoveredMessage. + * + * @return true on full message recovery, false otherwise, or if not sure. + * @see org.bouncycastle.crypto.SignerWithRecovery#hasFullMessage() + */ + public boolean hasFullMessage() + { + return fullMessage; + } + + /** + * Return a reference to the recoveredMessage message. + * + * @return the full/partial recoveredMessage message. + * @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage() + */ + public byte[] getRecoveredMessage() + { + return recoveredMessage; + } + + /** + * 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); + } + + /** + * long to octet string. + */ + private void LtoOSP( + long l, + byte[] sp) + { + sp[0] = (byte)(l >>> 56); + sp[1] = (byte)(l >>> 48); + sp[2] = (byte)(l >>> 40); + sp[3] = (byte)(l >>> 32); + sp[4] = (byte)(l >>> 24); + sp[5] = (byte)(l >>> 16); + sp[6] = (byte)(l >>> 8); + sp[7] = (byte)(l >>> 0); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 0; + + digest.reset(); + + while (counter < (length / hLen)) + { + ItoOSP(counter, C); + + digest.update(Z, zOff, zLen); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, hLen); + + counter++; + } + + if ((counter * hLen) < length) + { + ItoOSP(counter, C); + + digest.update(Z, zOff, zLen); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, mask.length - (counter * hLen)); + } + + return mask; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2Signer.java new file mode 100644 index 00000000..563fae63 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2Signer.java @@ -0,0 +1,618 @@ +package org.bouncycastle.crypto.signers; + +import java.util.Hashtable; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.SignerWithRecovery; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; + +/** + * ISO9796-2 - mechanism using a hash function with recovery (scheme 1) + */ +public class ISO9796d2Signer + implements SignerWithRecovery +{ + 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; + + private static Hashtable trailerMap = new Hashtable(); + + static + { + trailerMap.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128)); + trailerMap.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160)); + + trailerMap.put("SHA-1", Integers.valueOf(TRAILER_SHA1)); + trailerMap.put("SHA-256", Integers.valueOf(TRAILER_SHA256)); + trailerMap.put("SHA-384", Integers.valueOf(TRAILER_SHA384)); + trailerMap.put("SHA-512", Integers.valueOf(TRAILER_SHA512)); + + trailerMap.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL)); + } + + private Digest digest; + private AsymmetricBlockCipher cipher; + + private int trailer; + private int keyBits; + private byte[] block; + private byte[] mBuf; + private int messageLength; + private boolean fullMessage; + private byte[] recoveredMessage; + + private byte[] preSig; + private byte[] preBlock; + + /** + * Generate a signer for the with either implicit or explicit trailers + * for ISO9796-2. + * + * @param cipher base cipher to use for signature creation/verification + * @param digest digest to use. + * @param implicit whether or not the trailer is implicit or gives the hash. + */ + public ISO9796d2Signer( + AsymmetricBlockCipher cipher, + Digest digest, + boolean implicit) + { + this.cipher = cipher; + this.digest = digest; + + if (implicit) + { + trailer = TRAILER_IMPLICIT; + } + else + { + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + trailer = trailerObj.intValue(); + } + else + { + throw new IllegalArgumentException("no valid trailer for digest"); + } + } + } + + /** + * Constructor for a signer with an explicit digest trailer. + * + * @param cipher cipher to use. + * @param digest digest to sign with. + */ + public ISO9796d2Signer( + AsymmetricBlockCipher cipher, + Digest digest) + { + this(cipher, digest, false); + } + + public void init( + boolean forSigning, + CipherParameters param) + { + RSAKeyParameters kParam = (RSAKeyParameters)param; + + cipher.init(forSigning, kParam); + + keyBits = kParam.getModulus().bitLength(); + + block = new byte[(keyBits + 7) / 8]; + + if (trailer == TRAILER_IMPLICIT) + { + mBuf = new byte[block.length - digest.getDigestSize() - 2]; + } + else + { + mBuf = new byte[block.length - digest.getDigestSize() - 3]; + } + + reset(); + } + + /** + * compare two byte arrays - constant time + */ + private boolean isSameAs( + byte[] a, + byte[] b) + { + boolean isOkay = true; + + if (messageLength > mBuf.length) + { + if (mBuf.length > b.length) + { + isOkay = false; + } + + for (int i = 0; i != mBuf.length; i++) + { + if (a[i] != b[i]) + { + isOkay = false; + } + } + } + else + { + if (messageLength != b.length) + { + isOkay = false; + } + + for (int i = 0; i != b.length; i++) + { + if (a[i] != b[i]) + { + isOkay = false; + } + } + } + + return isOkay; + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + public void updateWithRecoveredMessage(byte[] signature) + throws InvalidCipherTextException + { + byte[] block = cipher.processBlock(signature, 0, signature.length); + + if (((block[0] & 0xC0) ^ 0x40) != 0) + { + throw new InvalidCipherTextException("malformed signature"); + } + + if (((block[block.length - 1] & 0xF) ^ 0xC) != 0) + { + throw new InvalidCipherTextException("malformed signature"); + } + + int delta = 0; + + if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0) + { + delta = 1; + } + else + { + int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF); + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + if (sigTrail != trailerObj.intValue()) + { + throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail); + } + } + else + { + throw new IllegalArgumentException("unrecognised hash in signature"); + } + + delta = 2; + } + + // + // find out how much padding we've got + // + int mStart = 0; + + for (mStart = 0; mStart != block.length; mStart++) + { + if (((block[mStart] & 0x0f) ^ 0x0a) == 0) + { + break; + } + } + + mStart++; + + int off = block.length - delta - digest.getDigestSize(); + + // + // there must be at least one byte of message string + // + if ((off - mStart) <= 0) + { + throw new InvalidCipherTextException("malformed block"); + } + + // + // if we contain the whole message as well, check the hash of that. + // + if ((block[0] & 0x20) == 0) + { + fullMessage = true; + + recoveredMessage = new byte[off - mStart]; + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + } + else + { + fullMessage = false; + + recoveredMessage = new byte[off - mStart]; + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + } + + preSig = signature; + preBlock = block; + + digest.update(recoveredMessage, 0, recoveredMessage.length); + messageLength = recoveredMessage.length; + System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + digest.update(b); + + if (messageLength < mBuf.length) + { + mBuf[messageLength] = b; + } + + messageLength++; + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + while (len > 0 && messageLength < mBuf.length) + { + this.update(in[off]); + off++; + len--; + } + + digest.update(in, off, len); + messageLength += len; + } + + /** + * reset the internal state + */ + public void reset() + { + digest.reset(); + messageLength = 0; + clearBlock(mBuf); + + if (recoveredMessage != null) + { + clearBlock(recoveredMessage); + } + + recoveredMessage = null; + fullMessage = false; + + if (preSig != null) + { + preSig = null; + clearBlock(preBlock); + preBlock = null; + } + } + + /** + * generate a signature for the loaded message using the key we were + * initialised with. + */ + public byte[] generateSignature() + throws CryptoException + { + int digSize = digest.getDigestSize(); + + int t = 0; + int delta = 0; + + if (trailer == TRAILER_IMPLICIT) + { + t = 8; + delta = block.length - digSize - 1; + digest.doFinal(block, delta); + block[block.length - 1] = (byte)TRAILER_IMPLICIT; + } + else + { + t = 16; + delta = block.length - digSize - 2; + digest.doFinal(block, delta); + block[block.length - 2] = (byte)(trailer >>> 8); + block[block.length - 1] = (byte)trailer; + } + + byte header = 0; + int x = (digSize + messageLength) * 8 + t + 4 - keyBits; + + if (x > 0) + { + int mR = messageLength - ((x + 7) / 8); + header = 0x60; + + delta -= mR; + + System.arraycopy(mBuf, 0, block, delta, mR); + } + else + { + header = 0x40; + delta -= messageLength; + + System.arraycopy(mBuf, 0, block, delta, messageLength); + } + + if ((delta - 1) > 0) + { + for (int i = delta - 1; i != 0; i--) + { + block[i] = (byte)0xbb; + } + block[delta - 1] ^= (byte)0x01; + block[0] = (byte)0x0b; + block[0] |= header; + } + else + { + block[0] = (byte)0x0a; + block[0] |= header; + } + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(mBuf); + clearBlock(block); + + return b; + } + + /** + * return true if the signature represents a ISO9796-2 signature + * for the passed in message. + */ + public boolean verifySignature( + byte[] signature) + { + byte[] block = null; + + if (preSig == null) + { + try + { + block = cipher.processBlock(signature, 0, signature.length); + } + catch (Exception e) + { + return false; + } + } + else + { + if (!Arrays.areEqual(preSig, signature)) + { + throw new IllegalStateException("updateWithRecoveredMessage called on different signature"); + } + + block = preBlock; + + preSig = null; + preBlock = null; + } + + if (((block[0] & 0xC0) ^ 0x40) != 0) + { + return returnFalse(block); + } + + if (((block[block.length - 1] & 0xF) ^ 0xC) != 0) + { + return returnFalse(block); + } + + int delta = 0; + + if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0) + { + delta = 1; + } + else + { + int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF); + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + if (sigTrail != trailerObj.intValue()) + { + throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail); + } + } + else + { + throw new IllegalArgumentException("unrecognised hash in signature"); + } + + delta = 2; + } + + // + // find out how much padding we've got + // + int mStart = 0; + + for (mStart = 0; mStart != block.length; mStart++) + { + if (((block[mStart] & 0x0f) ^ 0x0a) == 0) + { + break; + } + } + + mStart++; + + // + // check the hashes + // + byte[] hash = new byte[digest.getDigestSize()]; + + int off = block.length - delta - hash.length; + + // + // there must be at least one byte of message string + // + if ((off - mStart) <= 0) + { + return returnFalse(block); + } + + // + // if we contain the whole message as well, check the hash of that. + // + if ((block[0] & 0x20) == 0) + { + fullMessage = true; + + // check right number of bytes passed in. + if (messageLength > off - mStart) + { + return returnFalse(block); + } + + digest.reset(); + digest.update(block, mStart, off - mStart); + digest.doFinal(hash, 0); + + boolean isOkay = true; + + for (int i = 0; i != hash.length; i++) + { + block[off + i] ^= hash[i]; + if (block[off + i] != 0) + { + isOkay = false; + } + } + + if (!isOkay) + { + return returnFalse(block); + } + + recoveredMessage = new byte[off - mStart]; + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + } + else + { + fullMessage = false; + + digest.doFinal(hash, 0); + + boolean isOkay = true; + + for (int i = 0; i != hash.length; i++) + { + block[off + i] ^= hash[i]; + if (block[off + i] != 0) + { + isOkay = false; + } + } + + if (!isOkay) + { + return returnFalse(block); + } + + recoveredMessage = new byte[off - mStart]; + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + } + + // + // if they've input a message check what we've recovered against + // what was input. + // + if (messageLength != 0) + { + if (!isSameAs(mBuf, recoveredMessage)) + { + return returnFalse(block); + } + } + + clearBlock(mBuf); + clearBlock(block); + + return true; + } + + private boolean returnFalse(byte[] block) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + + /** + * Return true if the full message was recoveredMessage. + * + * @return true on full message recovery, false otherwise. + * @see org.bouncycastle.crypto.SignerWithRecovery#hasFullMessage() + */ + public boolean hasFullMessage() + { + return fullMessage; + } + + /** + * Return a reference to the recoveredMessage message. + * + * @return the full/partial recoveredMessage message. + * @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage() + */ + public byte[] getRecoveredMessage() + { + return recoveredMessage; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/PSSSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/PSSSigner.java new file mode 100644 index 00000000..8c9eb940 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/PSSSigner.java @@ -0,0 +1,348 @@ +package org.bouncycastle.crypto.signers; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSABlindingParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * RSA-PSS as described in PKCS# 1 v 2.1. + * <p> + * Note: the usual value for the salt length is the number of + * bytes in the hash function. + */ +public class PSSSigner + implements Signer +{ + static final public byte TRAILER_IMPLICIT = (byte)0xBC; + + private Digest contentDigest; + private Digest mgfDigest; + private AsymmetricBlockCipher cipher; + private SecureRandom random; + + private int hLen; + private int mgfhLen; + private int sLen; + private int emBits; + private byte[] salt; + private byte[] mDash; + private byte[] block; + private byte trailer; + + /** + * basic constructor + * + * @param cipher the asymmetric cipher to use. + * @param digest the digest to use. + * @param sLen the length of the salt to use (in bytes). + */ + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int sLen) + { + this(cipher, digest, sLen, TRAILER_IMPLICIT); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest contentDigest, + Digest mgfDigest, + int sLen) + { + this(cipher, contentDigest, mgfDigest, sLen, TRAILER_IMPLICIT); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int sLen, + byte trailer) + { + this(cipher, digest, digest, sLen, trailer); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest contentDigest, + Digest mgfDigest, + int sLen, + byte trailer) + { + this.cipher = cipher; + this.contentDigest = contentDigest; + this.mgfDigest = mgfDigest; + this.hLen = contentDigest.getDigestSize(); + this.mgfhLen = mgfDigest.getDigestSize(); + this.sLen = sLen; + this.salt = new byte[sLen]; + this.mDash = new byte[8 + sLen + hLen]; + this.trailer = trailer; + } + + public void init( + boolean forSigning, + CipherParameters param) + { + CipherParameters params; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + params = p.getParameters(); + random = p.getRandom(); + } + else + { + params = param; + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.init(forSigning, params); + + RSAKeyParameters kParam; + + if (params instanceof RSABlindingParameters) + { + kParam = ((RSABlindingParameters)params).getPublicKey(); + } + else + { + kParam = (RSAKeyParameters)params; + } + + emBits = kParam.getModulus().bitLength() - 1; + + if (emBits < (8 * hLen + 8 * sLen + 9)) + { + throw new IllegalArgumentException("key too small for specified hash and salt lengths"); + } + + block = new byte[(emBits + 7) / 8]; + + reset(); + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + contentDigest.update(b); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + contentDigest.update(in, off, len); + } + + /** + * reset the internal state + */ + public void reset() + { + contentDigest.reset(); + } + + /** + * generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + contentDigest.doFinal(mDash, mDash.length - hLen - sLen); + + if (sLen != 0) + { + random.nextBytes(salt); + + System.arraycopy(salt, 0, mDash, mDash.length - sLen, sLen); + } + + byte[] h = new byte[hLen]; + + contentDigest.update(mDash, 0, mDash.length); + + contentDigest.doFinal(h, 0); + + block[block.length - sLen - 1 - hLen - 1] = 0x01; + System.arraycopy(salt, 0, block, block.length - sLen - hLen - 1, sLen); + + byte[] dbMask = maskGeneratorFunction1(h, 0, h.length, block.length - hLen - 1); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= (0xff >> ((block.length * 8) - emBits)); + + System.arraycopy(h, 0, block, block.length - hLen - 1, hLen); + + block[block.length - 1] = trailer; + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(block); + + return b; + } + + /** + * return true if the internal state represents the signature described + * in the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + contentDigest.doFinal(mDash, mDash.length - hLen - sLen); + + try + { + byte[] b = cipher.processBlock(signature, 0, signature.length); + System.arraycopy(b, 0, block, block.length - b.length, b.length); + } + catch (Exception e) + { + return false; + } + + if (block[block.length - 1] != trailer) + { + clearBlock(block); + return false; + } + + byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - 1, hLen, block.length - hLen - 1); + + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= (0xff >> ((block.length * 8) - emBits)); + + for (int i = 0; i != block.length - hLen - sLen - 2; i++) + { + if (block[i] != 0) + { + clearBlock(block); + return false; + } + } + + if (block[block.length - hLen - sLen - 2] != 0x01) + { + clearBlock(block); + return false; + } + + System.arraycopy(block, block.length - sLen - hLen - 1, mDash, mDash.length - sLen, sLen); + + contentDigest.update(mDash, 0, mDash.length); + contentDigest.doFinal(mDash, mDash.length - hLen); + + for (int i = block.length - hLen - 1, j = mDash.length - hLen; + j != mDash.length; i++, j++) + { + if ((block[i] ^ mDash[j]) != 0) + { + clearBlock(mDash); + clearBlock(block); + return false; + } + } + + clearBlock(mDash); + clearBlock(block); + + return true; + } + + /** + * 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); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[mgfhLen]; + byte[] C = new byte[4]; + int counter = 0; + + mgfDigest.reset(); + + while (counter < (length / mgfhLen)) + { + ItoOSP(counter, C); + + mgfDigest.update(Z, zOff, zLen); + mgfDigest.update(C, 0, C.length); + mgfDigest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * mgfhLen, mgfhLen); + + counter++; + } + + if ((counter * mgfhLen) < length) + { + ItoOSP(counter, C); + + mgfDigest.update(Z, zOff, zLen); + mgfDigest.update(C, 0, C.length); + mgfDigest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * mgfhLen, mask.length - (counter * mgfhLen)); + } + + return mask; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java new file mode 100644 index 00000000..f33ed317 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java @@ -0,0 +1,232 @@ +package org.bouncycastle.crypto.signers; + +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DigestInfo; +import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.engines.RSABlindedEngine; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +public class RSADigestSigner + implements Signer +{ + private final AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine()); + private final AlgorithmIdentifier algId; + private final Digest digest; + private boolean forSigning; + + private static final Hashtable oidMap = new Hashtable(); + + /* + * Load OID table. + */ + static + { + oidMap.put("RIPEMD128", TeleTrusTObjectIdentifiers.ripemd128); + oidMap.put("RIPEMD160", TeleTrusTObjectIdentifiers.ripemd160); + oidMap.put("RIPEMD256", TeleTrusTObjectIdentifiers.ripemd256); + + oidMap.put("SHA-1", X509ObjectIdentifiers.id_SHA1); + oidMap.put("SHA-224", NISTObjectIdentifiers.id_sha224); + oidMap.put("SHA-256", NISTObjectIdentifiers.id_sha256); + oidMap.put("SHA-384", NISTObjectIdentifiers.id_sha384); + oidMap.put("SHA-512", NISTObjectIdentifiers.id_sha512); + + oidMap.put("MD2", PKCSObjectIdentifiers.md2); + oidMap.put("MD4", PKCSObjectIdentifiers.md4); + oidMap.put("MD5", PKCSObjectIdentifiers.md5); + } + + public RSADigestSigner( + Digest digest) + { + this.digest = digest; + + algId = new AlgorithmIdentifier((ASN1ObjectIdentifier)oidMap.get(digest.getAlgorithmName()), DERNull.INSTANCE); + } + + /** + * @deprecated + */ + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "withRSA"; + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("signing requires private key"); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("verification requires public key"); + } + + reset(); + + rsaEngine.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + if (!forSigning) + { + throw new IllegalStateException("RSADigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + byte[] data = derEncode(hash); + return rsaEngine.processBlock(data, 0, data.length); + } + catch (IOException e) + { + throw new CryptoException("unable to encode signature: " + e.getMessage(), e); + } + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("RSADigestSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + + digest.doFinal(hash, 0); + + byte[] sig; + byte[] expected; + + try + { + sig = rsaEngine.processBlock(signature, 0, signature.length); + expected = derEncode(hash); + } + catch (Exception e) + { + return false; + } + + if (sig.length == expected.length) + { + return Arrays.constantTimeAreEqual(sig, expected); + } + else if (sig.length == expected.length - 2) // NULL left out + { + int sigOffset = sig.length - hash.length - 2; + int expectedOffset = expected.length - hash.length - 2; + + expected[1] -= 2; // adjust lengths + expected[3] -= 2; + + int nonEqual = 0; + + for (int i = 0; i < hash.length; i++) + { + nonEqual |= (sig[sigOffset + i] ^ expected[expectedOffset + i]); + } + + for (int i = 0; i < sigOffset; i++) + { + nonEqual |= (sig[i] ^ expected[i]); // check header less NULL + } + + return nonEqual == 0; + } + else + { + return false; + } + } + + public void reset() + { + digest.reset(); + } + + private byte[] derEncode( + byte[] hash) + throws IOException + { + DigestInfo dInfo = new DigestInfo(algId, hash); + + return dInfo.getEncoded(ASN1Encoding.DER); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java new file mode 100644 index 00000000..9c2a526e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public class AbstractTlsCipherFactory + implements TlsCipherFactory +{ + + public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) + throws IOException + { + + throw new TlsFatalAlert(AlertDescription.internal_error); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java new file mode 100644 index 00000000..9e113f98 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java @@ -0,0 +1,218 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; + +public abstract class AbstractTlsClient + extends AbstractTlsPeer + implements TlsClient +{ + + protected TlsCipherFactory cipherFactory; + + protected TlsClientContext context; + + protected Vector supportedSignatureAlgorithms; + + protected int selectedCipherSuite; + protected short selectedCompressionMethod; + + public AbstractTlsClient() + { + this(new DefaultTlsCipherFactory()); + } + + public AbstractTlsClient(TlsCipherFactory cipherFactory) + { + this.cipherFactory = cipherFactory; + } + + public void init(TlsClientContext context) + { + this.context = context; + } + + /** + * RFC 5246 E.1. "TLS clients that wish to negotiate with older servers MAY send any value + * {03,XX} as the record layer version number. Typical values would be {03,00}, the lowest + * version number supported by the client, and the value of ClientHello.client_version. No + * single value will guarantee interoperability with all old servers, but this is a complex + * topic beyond the scope of this document." + */ + public ProtocolVersion getClientHelloRecordLayerVersion() + { + // "{03,00}" + // return ProtocolVersion.SSLv3; + + // "the lowest version number supported by the client" + // return getMinimumServerVersion(); + + // "the value of ClientHello.client_version" + return getClientVersion(); + } + + public ProtocolVersion getClientVersion() + { + return ProtocolVersion.TLSv11; + } + + public Hashtable getClientExtensions() + throws IOException + { + + Hashtable clientExtensions = null; + + ProtocolVersion clientVersion = context.getClientVersion(); + + /* + * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior to 1.2. + * Clients MUST NOT offer it if they are offering prior versions. + */ + if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion)) + { + + // TODO Provide a way for the user to specify the acceptable hash/signature algorithms. + + short[] hashAlgorithms = new short[]{HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256, + HashAlgorithm.sha224, HashAlgorithm.sha1}; + + // TODO Sort out ECDSA signatures and add them as the preferred option here + short[] signatureAlgorithms = new short[]{SignatureAlgorithm.rsa}; + + this.supportedSignatureAlgorithms = new Vector(); + for (int i = 0; i < hashAlgorithms.length; ++i) + { + for (int j = 0; j < signatureAlgorithms.length; ++j) + { + this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(hashAlgorithms[i], + signatureAlgorithms[j])); + } + } + + /* + * RFC 5264 7.4.3. Currently, DSA [DSS] may only be used with SHA-1. + */ + this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, + SignatureAlgorithm.dsa)); + + if (clientExtensions == null) + { + clientExtensions = new Hashtable(); + } + + TlsUtils.addSignatureAlgorithmsExtension(clientExtensions, supportedSignatureAlgorithms); + } + + return clientExtensions; + } + + public ProtocolVersion getMinimumVersion() + { + return ProtocolVersion.TLSv10; + } + + public void notifyServerVersion(ProtocolVersion serverVersion) + throws IOException + { + if (!getMinimumVersion().isEqualOrEarlierVersionOf(serverVersion)) + { + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + } + + public short[] getCompressionMethods() + { + return new short[]{CompressionMethod._null}; + } + + public void notifySessionID(byte[] sessionID) + { + // Currently ignored + } + + public void notifySelectedCipherSuite(int selectedCipherSuite) + { + this.selectedCipherSuite = selectedCipherSuite; + } + + public void notifySelectedCompressionMethod(short selectedCompressionMethod) + { + this.selectedCompressionMethod = selectedCompressionMethod; + } + + public void notifySecureRenegotiation(boolean secureRenegotiation) + throws IOException + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.4. In this case, some clients may want to terminate the handshake instead + * of continuing; see Section 4.1 for discussion. + */ + // throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + public void processServerExtensions(Hashtable serverExtensions) + throws IOException + { + /* + * TlsProtocol implementation validates that any server extensions received correspond to + * client extensions sent. By default, we don't send any, and this method is not called. + */ + if (serverExtensions != null) + { + /* + * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension. + */ + if (serverExtensions.containsKey(TlsUtils.EXT_signature_algorithms)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public void processServerSupplementalData(Vector serverSupplementalData) + throws IOException + { + if (serverSupplementalData != null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public Vector getClientSupplementalData() + throws IOException + { + return null; + } + + public TlsCompression getCompression() + throws IOException + { + switch (selectedCompressionMethod) + { + case CompressionMethod._null: + return new TlsNullCompression(); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected compression method was in the list of client-offered compression + * methods, so if we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public void notifyNewSessionTicket(NewSessionTicket newSessionTicket) + throws IOException + { + } + + public void notifyHandshakeComplete() + throws IOException + { + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java new file mode 100644 index 00000000..1ff67e33 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java @@ -0,0 +1,96 @@ +package org.bouncycastle.crypto.tls; + +import java.security.SecureRandom; + +abstract class AbstractTlsContext + implements TlsContext +{ + + private SecureRandom secureRandom; + private SecurityParameters securityParameters; + + private ProtocolVersion clientVersion = null; + private ProtocolVersion serverVersion = null; + private Object userObject = null; + + AbstractTlsContext(SecureRandom secureRandom, SecurityParameters securityParameters) + { + this.secureRandom = secureRandom; + this.securityParameters = securityParameters; + } + + public SecureRandom getSecureRandom() + { + return secureRandom; + } + + public SecurityParameters getSecurityParameters() + { + return securityParameters; + } + + public ProtocolVersion getClientVersion() + { + return clientVersion; + } + + public void setClientVersion(ProtocolVersion clientVersion) + { + this.clientVersion = clientVersion; + } + + public ProtocolVersion getServerVersion() + { + return serverVersion; + } + + public void setServerVersion(ProtocolVersion serverVersion) + { + this.serverVersion = serverVersion; + } + + public Object getUserObject() + { + return userObject; + } + + public void setUserObject(Object userObject) + { + this.userObject = userObject; + } + + public byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length) + { + + SecurityParameters sp = getSecurityParameters(); + byte[] cr = sp.getClientRandom(), sr = sp.getServerRandom(); + + int seedLength = cr.length + sr.length; + if (context_value != null) + { + seedLength += (2 + context_value.length); + } + + byte[] seed = new byte[seedLength]; + int seedPos = 0; + + System.arraycopy(cr, 0, seed, seedPos, cr.length); + seedPos += cr.length; + System.arraycopy(sr, 0, seed, seedPos, sr.length); + seedPos += sr.length; + if (context_value != null) + { + TlsUtils.writeUint16(context_value.length, seed, seedPos); + seedPos += 2; + System.arraycopy(context_value, 0, seed, seedPos, context_value.length); + seedPos += context_value.length; + } + + if (seedPos != seedLength) + { + throw new IllegalStateException("error in calculation of seed for export"); + } + + return TlsUtils.PRF(this, sp.getMasterSecret(), asciiLabel, seed, length); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java new file mode 100644 index 00000000..85057c13 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java @@ -0,0 +1,164 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +public abstract class AbstractTlsKeyExchange + implements TlsKeyExchange +{ + + protected int keyExchange; + protected Vector supportedSignatureAlgorithms; + + protected TlsContext context; + + protected AbstractTlsKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms) + { + this.keyExchange = keyExchange; + this.supportedSignatureAlgorithms = supportedSignatureAlgorithms; + } + + public void init(TlsContext context) + { + this.context = context; + + ProtocolVersion clientVersion = context.getClientVersion(); + + if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion)) + { + + /* + * RFC 5264 7.4.1.4.1. If the client does not send the signature_algorithms extension, + * the server MUST do the following: + * + * - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, DH_RSA, RSA_PSK, + * ECDH_RSA, ECDHE_RSA), behave as if client had sent the value {sha1,rsa}. + * + * - If the negotiated key exchange algorithm is one of (DHE_DSS, DH_DSS), behave as if + * the client had sent the value {sha1,dsa}. + * + * - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, ECDHE_ECDSA), + * behave as if the client had sent value {sha1,ecdsa}. + */ + if (this.supportedSignatureAlgorithms == null) + { + switch (keyExchange) + { + + case KeyExchangeAlgorithm.DH_DSS: + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.SRP_DSS: + { + this.supportedSignatureAlgorithms = TlsUtils.getDefaultDSSSignatureAlgorithms(); + break; + } + + case KeyExchangeAlgorithm.ECDH_ECDSA: + case KeyExchangeAlgorithm.ECDHE_ECDSA: + { + this.supportedSignatureAlgorithms = TlsUtils.getDefaultECDSASignatureAlgorithms(); + break; + } + + case KeyExchangeAlgorithm.DH_RSA: + case KeyExchangeAlgorithm.DHE_RSA: + case KeyExchangeAlgorithm.ECDH_RSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + case KeyExchangeAlgorithm.RSA: + case KeyExchangeAlgorithm.RSA_PSK: + case KeyExchangeAlgorithm.SRP_RSA: + { + this.supportedSignatureAlgorithms = TlsUtils.getDefaultRSASignatureAlgorithms(); + break; + } + + default: + throw new IllegalStateException("unsupported key exchange algorithm"); + } + } + + } + else if (this.supportedSignatureAlgorithms != null) + { + throw new IllegalStateException("supported_signature_algorithms not allowed for " + clientVersion); + } + } + + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { + + if (supportedSignatureAlgorithms == null) + { + /* + * TODO RFC 2264 7.4.2. Unless otherwise specified, the signing algorithm for the + * certificate must be the same as the algorithm for the certificate key. + */ + } + else + { + /* + * TODO RFC 5264 7.4.2. If the client provided a "signature_algorithms" extension, then + * all certificates provided by the server MUST be signed by a hash/signature algorithm + * pair that appears in that extension. + */ + } + } + + public void processServerCredentials(TlsCredentials serverCredentials) + throws IOException + { + processServerCertificate(serverCredentials.getCertificate()); + } + + public boolean requiresServerKeyExchange() + { + return false; + } + + public byte[] generateServerKeyExchange() + throws IOException + { + if (requiresServerKeyExchange()) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + return null; + } + + public void skipServerKeyExchange() + throws IOException + { + if (requiresServerKeyExchange()) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public void processServerKeyExchange(InputStream input) + throws IOException + { + if (!requiresServerKeyExchange()) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public void skipClientCredentials() + throws IOException + { + } + + public void processClientCertificate(Certificate clientCertificate) + throws IOException + { + } + + public void processClientKeyExchange(InputStream input) + throws IOException + { + // Key exchange implementation MUST support client key exchange + throw new TlsFatalAlert(AlertDescription.internal_error); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java new file mode 100644 index 00000000..bdfd0d5e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java @@ -0,0 +1,14 @@ +package org.bouncycastle.crypto.tls; + +public abstract class AbstractTlsPeer + implements TlsPeer +{ + + public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause) + { + } + + public void notifyAlertReceived(short alertLevel, short alertDescription) + { + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java new file mode 100644 index 00000000..8235fd1b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java @@ -0,0 +1,304 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; + +public abstract class AbstractTlsServer + extends AbstractTlsPeer + implements TlsServer +{ + + protected TlsCipherFactory cipherFactory; + + protected TlsServerContext context; + + protected ProtocolVersion clientVersion; + protected int[] offeredCipherSuites; + protected short[] offeredCompressionMethods; + protected Hashtable clientExtensions; + + protected Vector supportedSignatureAlgorithms; + protected boolean eccCipherSuitesOffered; + protected int[] namedCurves; + protected short[] clientECPointFormats, serverECPointFormats; + + protected ProtocolVersion serverVersion; + protected int selectedCipherSuite; + protected short selectedCompressionMethod; + protected Hashtable serverExtensions; + + public AbstractTlsServer() + { + this(new DefaultTlsCipherFactory()); + } + + public AbstractTlsServer(TlsCipherFactory cipherFactory) + { + this.cipherFactory = cipherFactory; + } + + protected abstract int[] getCipherSuites(); + + protected short[] getCompressionMethods() + { + return new short[]{CompressionMethod._null}; + } + + protected ProtocolVersion getMaximumVersion() + { + return ProtocolVersion.TLSv11; + } + + protected ProtocolVersion getMinimumVersion() + { + return ProtocolVersion.TLSv10; + } + + protected boolean supportsClientECCCapabilities(int[] namedCurves, short[] ecPointFormats) + { + + // NOTE: BC supports all the current set of point formats so we don't check them here + + if (namedCurves == null) + { + /* + * RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these + * extensions. In this case, the server is free to choose any one of the elliptic curves + * or point formats [...]. + */ + return TlsECCUtils.hasAnySupportedNamedCurves(); + } + + for (int i = 0; i < namedCurves.length; ++i) + { + int namedCurve = namedCurves[i]; + if (!NamedCurve.refersToASpecificNamedCurve(namedCurve) || TlsECCUtils.isSupportedNamedCurve(namedCurve)) + { + return true; + } + } + + return false; + } + + public void init(TlsServerContext context) + { + this.context = context; + } + + public void notifyClientVersion(ProtocolVersion clientVersion) + throws IOException + { + this.clientVersion = clientVersion; + } + + public void notifyOfferedCipherSuites(int[] offeredCipherSuites) + throws IOException + { + this.offeredCipherSuites = offeredCipherSuites; + this.eccCipherSuitesOffered = TlsECCUtils.containsECCCipherSuites(this.offeredCipherSuites); + } + + public void notifyOfferedCompressionMethods(short[] offeredCompressionMethods) + throws IOException + { + this.offeredCompressionMethods = offeredCompressionMethods; + } + + public void notifySecureRenegotiation(boolean secureRenegotiation) + throws IOException + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.6. In this case, some servers may want to terminate the handshake instead + * of continuing; see Section 4.3 for discussion. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + public void processClientExtensions(Hashtable clientExtensions) + throws IOException + { + + this.clientExtensions = clientExtensions; + + if (clientExtensions != null) + { + + this.supportedSignatureAlgorithms = TlsUtils.getSignatureAlgorithmsExtension(clientExtensions); + if (this.supportedSignatureAlgorithms != null) + { + /* + * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior + * to 1.2. Clients MUST NOT offer it if they are offering prior versions. + */ + if (!TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + this.namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(clientExtensions); + this.clientECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(clientExtensions); + } + + /* + * RFC 4429 4. The client MUST NOT include these extensions in the ClientHello message if it + * does not propose any ECC cipher suites. + */ + if (!this.eccCipherSuitesOffered && (this.namedCurves != null || this.clientECPointFormats != null)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + public ProtocolVersion getServerVersion() + throws IOException + { + if (getMinimumVersion().isEqualOrEarlierVersionOf(clientVersion)) + { + ProtocolVersion maximumVersion = getMaximumVersion(); + if (clientVersion.isEqualOrEarlierVersionOf(maximumVersion)) + { + return serverVersion = clientVersion; + } + if (clientVersion.isLaterVersionOf(maximumVersion)) + { + return serverVersion = maximumVersion; + } + } + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + + public int getSelectedCipherSuite() + throws IOException + { + + /* + * TODO RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate + * cipher suites against the "signature_algorithms" extension before selecting them. This is + * somewhat inelegant but is a compromise designed to minimize changes to the original + * cipher suite design. + */ + + /* + * RFC 4429 5.1. A server that receives a ClientHello containing one or both of these + * extensions MUST use the client's enumerated capabilities to guide its selection of an + * appropriate cipher suite. One of the proposed ECC cipher suites must be negotiated only + * if the server can successfully complete the handshake while using the curves and point + * formats supported by the client [...]. + */ + boolean eccCipherSuitesEnabled = supportsClientECCCapabilities(this.namedCurves, this.clientECPointFormats); + + int[] cipherSuites = getCipherSuites(); + for (int i = 0; i < cipherSuites.length; ++i) + { + int cipherSuite = cipherSuites[i]; + if (TlsProtocol.arrayContains(this.offeredCipherSuites, cipherSuite) + && (eccCipherSuitesEnabled || !TlsECCUtils.isECCCipherSuite(cipherSuite))) + { + return this.selectedCipherSuite = cipherSuite; + } + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + public short getSelectedCompressionMethod() + throws IOException + { + short[] compressionMethods = getCompressionMethods(); + for (int i = 0; i < compressionMethods.length; ++i) + { + if (TlsProtocol.arrayContains(offeredCompressionMethods, compressionMethods[i])) + { + return this.selectedCompressionMethod = compressionMethods[i]; + } + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + // Hashtable is (Integer -> byte[]) + public Hashtable getServerExtensions() + throws IOException + { + + if (this.clientECPointFormats != null && TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite)) + { + /* + * RFC 4492 5.2. A server that selects an ECC cipher suite in response to a ClientHello + * message including a Supported Point Formats Extension appends this extension (along + * with others) to its ServerHello message, enumerating the point formats it can parse. + */ + this.serverECPointFormats = new short[]{ECPointFormat.ansiX962_compressed_char2, + ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed}; + + this.serverExtensions = new Hashtable(); + TlsECCUtils.addSupportedPointFormatsExtension(serverExtensions, serverECPointFormats); + return serverExtensions; + } + + return null; + } + + public Vector getServerSupplementalData() + throws IOException + { + return null; + } + + public CertificateRequest getCertificateRequest() + { + return null; + } + + public void processClientSupplementalData(Vector clientSupplementalData) + throws IOException + { + if (clientSupplementalData != null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public void notifyClientCertificate(Certificate clientCertificate) + throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public TlsCompression getCompression() + throws IOException + { + switch (selectedCompressionMethod) + { + case CompressionMethod._null: + return new TlsNullCompression(); + + default: + /* + * Note: internal error here; we selected the compression method, so if we now can't + * produce an implementation, we shouldn't have chosen it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public NewSessionTicket getNewSessionTicket() + throws IOException + { + /* + * RFC 5077 3.3. If the server determines that it does not want to include a ticket after it + * has included the SessionTicket extension in the ServerHello, then it sends a zero-length + * ticket in the NewSessionTicket handshake message. + */ + return new NewSessionTicket(0L, TlsUtils.EMPTY_BYTES); + } + + public void notifyHandshakeComplete() + throws IOException + { + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java new file mode 100644 index 00000000..a0c24c73 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto.tls; + +public abstract class AbstractTlsSigner + implements TlsSigner +{ + + protected TlsContext context; + + public void init(TlsContext context) + { + this.context = context; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java b/core/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java new file mode 100644 index 00000000..5e3269bd --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java @@ -0,0 +1,215 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 5246 7.2. + */ +public class AlertDescription +{ + + /** + * This message notifies the recipient that the sender will not send any more messages on this + * connection. The session becomes unresumable if any connection is terminated without proper + * close_notify messages with level equal to warning. + */ + public static final short close_notify = 0; + + /** + * An inappropriate message was received. This alert is always fatal and should never be + * observed in communication between proper implementations. + */ + public static final short unexpected_message = 10; + + /** + * This alert is returned if a record is received with an incorrect MAC. This alert also MUST be + * returned if an alert is sent because a TLSCiphertext decrypted in an invalid way: either it + * wasn't an even multiple of the block length, or its padding values, when checked, weren't + * correct. This message is always fatal and should never be observed in communication between + * proper implementations (except when messages were corrupted in the network). + */ + public static final short bad_record_mac = 20; + + /** + * This alert was used in some earlier versions of TLS, and may have permitted certain attacks + * against the CBC mode [CBCATT]. It MUST NOT be sent by compliant implementations. + */ + public static final short decryption_failed = 21; + + /** + * A TLSCiphertext record was received that had a length more than 2^14+2048 bytes, or a record + * decrypted to a TLSCompressed record with more than 2^14+1024 bytes. This message is always + * fatal and should never be observed in communication between proper implementations (except + * when messages were corrupted in the network). + */ + public static final short record_overflow = 22; + + /** + * The decompression function received improper input (e.g., data that would expand to excessive + * length). This message is always fatal and should never be observed in communication between + * proper implementations. + */ + public static final short decompression_failure = 30; + + /** + * Reception of a handshake_failure alert message indicates that the sender was unable to + * negotiate an acceptable set of security parameters given the options available. This is a + * fatal error. + */ + public static final short handshake_failure = 40; + + /** + * This alert was used in SSLv3 but not any version of TLS. It MUST NOT be sent by compliant + * implementations. + */ + public static final short no_certificate = 41; + + /** + * A certificate was corrupt, contained signatures that did not verify correctly, etc. + */ + public static final short bad_certificate = 42; + + /** + * A certificate was of an unsupported type. + */ + public static final short unsupported_certificate = 43; + + /** + * A certificate was revoked by its signer. + */ + public static final short certificate_revoked = 44; + + /** + * A certificate has expired or is not currently valid. + */ + public static final short certificate_expired = 45; + + /** + * Some other (unspecified) issue arose in processing the certificate, rendering it + * unacceptable. + */ + public static final short certificate_unknown = 46; + + /** + * A field in the handshake was out of range or inconsistent with other fields. This message is + * always fatal. + */ + public static final short illegal_parameter = 47; + + /** + * A valid certificate chain or partial chain was received, but the certificate was not accepted + * because the CA certificate could not be located or couldn't be matched with a known, trusted + * CA. This message is always fatal. + */ + public static final short unknown_ca = 48; + + /** + * A valid certificate was received, but when access control was applied, the sender decided not + * to proceed with negotiation. This message is always fatal. + */ + public static final short access_denied = 49; + + /** + * A message could not be decoded because some field was out of the specified range or the + * length of the message was incorrect. This message is always fatal and should never be + * observed in communication between proper implementations (except when messages were corrupted + * in the network). + */ + public static final short decode_error = 50; + + /** + * A handshake cryptographic operation failed, including being unable to correctly verify a + * signature or validate a Finished message. This message is always fatal. + */ + public static final short decrypt_error = 51; + + /** + * This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant + * implementations. + */ + public static final short export_restriction = 60; + + /** + * The protocol version the client has attempted to negotiate is recognized but not supported. + * (For example, old protocol versions might be avoided for security reasons.) This message is + * always fatal. + */ + public static final short protocol_version = 70; + + /** + * Returned instead of handshake_failure when a negotiation has failed specifically because the + * server requires ciphers more secure than those supported by the client. This message is + * always fatal. + */ + public static final short insufficient_security = 71; + + /** + * An internal error unrelated to the peer or the correctness of the protocol (such as a memory + * allocation failure) makes it impossible to continue. This message is always fatal. + */ + public static final short internal_error = 80; + + /** + * This handshake is being canceled for some reason unrelated to a protocol failure. If the user + * cancels an operation after the handshake is complete, just closing the connection by sending + * a close_notify is more appropriate. This alert should be followed by a close_notify. This + * message is generally a warning. + */ + public static final short user_canceled = 90; + + /** + * Sent by the client in response to a hello request or by the server in response to a client + * hello after initial handshaking. Either of these would normally lead to renegotiation; when + * that is not appropriate, the recipient should respond with this alert. At that point, the + * original requester can decide whether to proceed with the connection. One case where this + * would be appropriate is where a server has spawned a process to satisfy a request; the + * process might receive security parameters (key length, authentication, etc.) at startup, and + * it might be difficult to communicate changes to these parameters after that point. This + * message is always a warning. + */ + public static final short no_renegotiation = 100; + + /** + * Sent by clients that receive an extended server hello containing an extension that they did + * not put in the corresponding client hello. This message is always fatal. + */ + public static final short unsupported_extension = 110; + + /* + * RFC 3546 + */ + + /** + * This alert is sent by servers who are unable to retrieve a certificate chain from the URL + * supplied by the client (see Section 3.3). This message MAY be fatal - for example if client + * authentication is required by the server for the handshake to continue and the server is + * unable to retrieve the certificate chain, it may send a fatal alert. + */ + public static final short certificate_unobtainable = 111; + + /** + * This alert is sent by servers that receive a server_name extension request, but do not + * recognize the server name. This message MAY be fatal. + */ + public static final short unrecognized_name = 112; + + /** + * This alert is sent by clients that receive an invalid certificate status response (see + * Section 3.6). This message is always fatal. + */ + public static final short bad_certificate_status_response = 113; + + /** + * This alert is sent by servers when a certificate hash does not match a client provided + * certificate_hash. This message is always fatal. + */ + public static final short bad_certificate_hash_value = 114; + + /* + * RFC 4279 + */ + + /** + * If the server does not recognize the PSK identity, it MAY respond with an + * "unknown_psk_identity" alert message. + */ + public static final short unknown_psk_identity = 115; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AlertLevel.java b/core/src/main/java/org/bouncycastle/crypto/tls/AlertLevel.java new file mode 100644 index 00000000..b0b131d6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AlertLevel.java @@ -0,0 +1,10 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 7.2 + */ +public class AlertLevel +{ + public static final short warning = 1; + public static final short fatal = 2; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java b/core/src/main/java/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java new file mode 100644 index 00000000..bf4cd13b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto.tls; + +/** + * A certificate verifyer, that will always return true. + * <p/> + * <pre> + * DO NOT USE THIS FILE UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING. + * </pre> + * + * @deprecated Perform certificate verification in TlsAuthentication implementation + */ +public class AlwaysValidVerifyer + implements CertificateVerifyer +{ + /** + * Return true. + * + * @see org.bouncycastle.crypto.tls.CertificateVerifyer#isValid(org.bouncycastle.asn1.x509.Certificate[]) + */ + public boolean isValid(org.bouncycastle.asn1.x509.Certificate[] certs) + { + return true; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/BulkCipherAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/BulkCipherAlgorithm.java new file mode 100644 index 00000000..595bdad2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/BulkCipherAlgorithm.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class BulkCipherAlgorithm +{ + + public static final int _null = 0; + public static final int rc4 = 1; + public static final int rc2 = 2; + public static final int des = 3; + public static final int _3des = 4; + public static final int des40 = 5; + + /* + * RFC 4346 + */ + public static final int aes = 6; + public static final int idea = 7; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java b/core/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java new file mode 100644 index 00000000..8b9d4ab1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java @@ -0,0 +1,123 @@ +package org.bouncycastle.crypto.tls; + +/** + * A queue for bytes. This file could be more optimized. + */ +public class ByteQueue +{ + /** + * @return The smallest number which can be written as 2^x which is bigger than i. + */ + public static final int nextTwoPow(int i) + { + /* + * This code is based of a lot of code I found on the Internet which mostly + * referenced a book called "Hacking delight". + */ + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return i + 1; + } + + /** + * The initial size for our buffer. + */ + private static final int INITBUFSIZE = 1024; + + /** + * The buffer where we store our data. + */ + private byte[] databuf = new byte[ByteQueue.INITBUFSIZE]; + + /** + * How many bytes at the beginning of the buffer are skipped. + */ + private int skipped = 0; + + /** + * How many bytes in the buffer are valid data. + */ + private int available = 0; + + /** + * Read data from the buffer. + * + * @param buf The buffer where the read data will be copied to. + * @param offset How many bytes to skip at the beginning of buf. + * @param len How many bytes to read at all. + * @param skip How many bytes from our data to skip. + */ + public void read(byte[] buf, int offset, int len, int skip) + { + if ((available - skip) < len) + { + throw new TlsRuntimeException("Not enough data to read"); + } + if ((buf.length - offset) < len) + { + throw new TlsRuntimeException("Buffer size of " + buf.length + + " is too small for a read of " + len + " bytes"); + } + System.arraycopy(databuf, skipped + skip, buf, offset, len); + return; + } + + /** + * Add some data to our buffer. + * + * @param data A byte-array to read data from. + * @param offset How many bytes to skip at the beginning of the array. + * @param len How many bytes to read from the array. + */ + public void addData(byte[] data, int offset, int len) + { + if ((skipped + available + len) > databuf.length) + { + byte[] tmp = new byte[ByteQueue.nextTwoPow(data.length)]; + System.arraycopy(databuf, skipped, tmp, 0, available); + skipped = 0; + databuf = tmp; + } + System.arraycopy(data, offset, databuf, skipped + available, len); + available += len; + } + + /** + * Remove some bytes from our data from the beginning. + * + * @param i How many bytes to remove. + */ + public void removeData(int i) + { + if (i > available) + { + throw new TlsRuntimeException("Cannot remove " + i + " bytes, only got " + available); + } + + /* + * Skip the data. + */ + available -= i; + skipped += i; + + /* + * If more than half of our data is skipped, we will move the data in the buffer. + */ + if (skipped > (databuf.length / 2)) + { + System.arraycopy(databuf, skipped, databuf, 0, available); + skipped = 0; + } + } + + /** + * @return The number of bytes which are available in this buffer. + */ + public int size() + { + return available; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/Certificate.java b/core/src/main/java/org/bouncycastle/crypto/tls/Certificate.java new file mode 100644 index 00000000..fab79f4c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/Certificate.java @@ -0,0 +1,153 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Primitive; + +/** + * Parsing and encoding of a <i>Certificate</i> struct from RFC 4346. + * <p/> + * <pre> + * opaque ASN.1Cert<2^24-1>; + * + * struct { + * ASN.1Cert certificate_list<0..2^24-1>; + * } Certificate; + * </pre> + * + * @see org.bouncycastle.asn1.x509.Certificate + */ +public class Certificate +{ + + public static final Certificate EMPTY_CHAIN = new Certificate( + new org.bouncycastle.asn1.x509.Certificate[0]); + + protected org.bouncycastle.asn1.x509.Certificate[] certificateList; + + public Certificate(org.bouncycastle.asn1.x509.Certificate[] certificateList) + { + if (certificateList == null) + { + throw new IllegalArgumentException("'certificateList' cannot be null"); + } + + this.certificateList = certificateList; + } + + /** + * @deprecated use {@link #getCertificateList()} instead + */ + public org.bouncycastle.asn1.x509.Certificate[] getCerts() + { + return clone(certificateList); + } + + /** + * @return an array of {@link org.bouncycastle.asn1.x509.Certificate} representing a certificate + * chain. + */ + public org.bouncycastle.asn1.x509.Certificate[] getCertificateList() + { + return clone(certificateList); + } + + public org.bouncycastle.asn1.x509.Certificate getCertificateAt(int index) + { + return certificateList[index]; + } + + public int getLength() + { + return certificateList.length; + } + + /** + * @return <code>true</code> if this certificate chain contains no certificates, or + * <code>false</code> otherwise. + */ + public boolean isEmpty() + { + return certificateList.length == 0; + } + + /** + * Encode this {@link Certificate} to an {@link OutputStream}. + * + * @param output the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) + throws IOException + { + Vector encCerts = new Vector(this.certificateList.length); + int totalLength = 0; + for (int i = 0; i < this.certificateList.length; ++i) + { + byte[] encCert = certificateList[i].getEncoded(ASN1Encoding.DER); + encCerts.addElement(encCert); + totalLength += encCert.length + 3; + } + + TlsUtils.writeUint24(totalLength, output); + + for (int i = 0; i < encCerts.size(); ++i) + { + byte[] encCert = (byte[])encCerts.elementAt(i); + TlsUtils.writeOpaque24(encCert, output); + } + } + + /** + * Parse a {@link Certificate} from an {@link InputStream}. + * + * @param input the {@link InputStream} to parse from. + * @return a {@link Certificate} object. + * @throws IOException + */ + public static Certificate parse(InputStream input) + throws IOException + { + org.bouncycastle.asn1.x509.Certificate[] certs; + int left = TlsUtils.readUint24(input); + if (left == 0) + { + return EMPTY_CHAIN; + } + Vector tmp = new Vector(); + while (left > 0) + { + int size = TlsUtils.readUint24(input); + left -= 3 + size; + + byte[] buf = TlsUtils.readFully(size, input); + + ByteArrayInputStream bis = new ByteArrayInputStream(buf); + ASN1Primitive asn1 = new ASN1InputStream(bis).readObject(); + TlsProtocol.assertEmpty(bis); + + tmp.addElement(org.bouncycastle.asn1.x509.Certificate.getInstance(asn1)); + } + certs = new org.bouncycastle.asn1.x509.Certificate[tmp.size()]; + for (int i = 0; i < tmp.size(); i++) + { + certs[i] = (org.bouncycastle.asn1.x509.Certificate)tmp.elementAt(i); + } + return new Certificate(certs); + } + + private org.bouncycastle.asn1.x509.Certificate[] clone(org.bouncycastle.asn1.x509.Certificate[] list) + { + org.bouncycastle.asn1.x509.Certificate[] rv = new org.bouncycastle.asn1.x509.Certificate[list.length]; + + System.arraycopy(list, 0, rv, 0, rv.length); + + return rv; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java b/core/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java new file mode 100644 index 00000000..00bf9508 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java @@ -0,0 +1,140 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.x500.X500Name; + +/** + * Parsing and encoding of a <i>CertificateRequest</i> struct from RFC 4346. + * <p/> + * <pre> + * struct { + * ClientCertificateType certificate_types<1..2^8-1>; + * DistinguishedName certificate_authorities<3..2^16-1>; + * } CertificateRequest; + * </pre> + * + * @see ClientCertificateType + * @see X500Name + */ +public class CertificateRequest +{ + private short[] certificateTypes; + private Vector certificateAuthorities; + + /* + * TODO RFC 5264 7.4.4 A list of the hash/signature algorithm pairs that the server is able to + * verify, listed in descending order of preference. + */ + + /** + * @param certificateTypes see {@link ClientCertificateType} for valid constants. + * @param certificateAuthorities a {@link Vector} of {@link X500Name}. + */ + public CertificateRequest(short[] certificateTypes, Vector certificateAuthorities) + { + this.certificateTypes = certificateTypes; + this.certificateAuthorities = certificateAuthorities; + } + + /** + * @return an array of certificate types + * @see {@link ClientCertificateType} + */ + public short[] getCertificateTypes() + { + return certificateTypes; + } + + /** + * @return a {@link Vector} of {@link X500Name} + */ + public Vector getCertificateAuthorities() + { + return certificateAuthorities; + } + + /** + * Encode this {@link CertificateRequest} to an {@link OutputStream}. + * + * @param output the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) + throws IOException + { + + if (certificateTypes == null || certificateTypes.length == 0) + { + TlsUtils.writeUint8((short)0, output); + } + else + { + TlsUtils.writeUint8((short)certificateTypes.length, output); + TlsUtils.writeUint8Array(certificateTypes, output); + } + + if (certificateAuthorities == null || certificateAuthorities.isEmpty()) + { + TlsUtils.writeUint16(0, output); + } + else + { + + Vector encDNs = new Vector(certificateAuthorities.size()); + int totalLength = 0; + for (int i = 0; i < certificateAuthorities.size(); ++i) + { + X500Name authorityDN = (X500Name)certificateAuthorities.elementAt(i); + byte[] encDN = authorityDN.getEncoded(ASN1Encoding.DER); + encDNs.addElement(encDN); + totalLength += encDN.length; + } + + TlsUtils.writeUint16(totalLength, output); + + for (int i = 0; i < encDNs.size(); ++i) + { + byte[] encDN = (byte[])encDNs.elementAt(i); + output.write(encDN); + } + } + } + + /** + * Parse a {@link CertificateRequest} from an {@link InputStream}. + * + * @param input the {@link InputStream} to parse from. + * @return a {@link CertificateRequest} object. + * @throws IOException + */ + public static CertificateRequest parse(InputStream input) + throws IOException + { + int numTypes = TlsUtils.readUint8(input); + short[] certificateTypes = new short[numTypes]; + for (int i = 0; i < numTypes; ++i) + { + certificateTypes[i] = TlsUtils.readUint8(input); + } + + byte[] authorities = TlsUtils.readOpaque16(input); + + Vector authorityDNs = new Vector(); + + ByteArrayInputStream bis = new ByteArrayInputStream(authorities); + while (bis.available() > 0) + { + byte[] dnBytes = TlsUtils.readOpaque16(bis); + authorityDNs.addElement(X500Name.getInstance(ASN1Primitive.fromByteArray(dnBytes))); + } + + return new CertificateRequest(certificateTypes, authorityDNs); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/CertificateVerifyer.java b/core/src/main/java/org/bouncycastle/crypto/tls/CertificateVerifyer.java new file mode 100644 index 00000000..2e3715c1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/CertificateVerifyer.java @@ -0,0 +1,16 @@ +package org.bouncycastle.crypto.tls; + +/** + * This should be implemented by any class which can find out, if a given certificate + * chain is being accepted by an client. + * + * @deprecated Perform certificate verification in TlsAuthentication implementation + */ +public interface CertificateVerifyer +{ + /** + * @param certs The certs, which are part of the chain. + * @return True, if the chain is accepted, false otherwise. + */ + public boolean isValid(org.bouncycastle.asn1.x509.Certificate[] certs); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java b/core/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java new file mode 100644 index 00000000..2979cdef --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java @@ -0,0 +1,207 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 A.5 + */ +public class CipherSuite +{ + + public static final int TLS_NULL_WITH_NULL_NULL = 0x0000; + public static final int TLS_RSA_WITH_NULL_MD5 = 0x0001; + public static final int TLS_RSA_WITH_NULL_SHA = 0x0002; + public static final int TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003; + public static final int TLS_RSA_WITH_RC4_128_MD5 = 0x0004; + public static final int TLS_RSA_WITH_RC4_128_SHA = 0x0005; + public static final int TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006; + public static final int TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007; + public static final int TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008; + public static final int TLS_RSA_WITH_DES_CBC_SHA = 0x0009; + public static final int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A; + public static final int TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B; + public static final int TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C; + public static final int TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D; + public static final int TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E; + public static final int TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F; + public static final int TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010; + public static final int TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011; + public static final int TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012; + public static final int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013; + public static final int TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014; + public static final int TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015; + public static final int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016; + public static final int TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017; + public static final int TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018; + public static final int TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019; + public static final int TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A; + public static final int TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B; + + /* + * Note: The cipher suite values { 0x00, 0x1C } and { 0x00, 0x1D } are reserved to avoid + * collision with Fortezza-based cipher suites in SSL 3. + */ + + /* + * RFC 3268 + */ + public static final int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F; + public static final int TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030; + public static final int TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031; + public static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032; + public static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033; + public static final int TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034; + public static final int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035; + public static final int TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036; + public static final int TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037; + public static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038; + public static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039; + public static final int TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A; + + /* + * RFC 4132 + */ + public static final int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041; + public static final int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0042; + public static final int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043; + public static final int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0044; + public static final int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045; + public static final int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = 0x0046; + public static final int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084; + public static final int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0085; + public static final int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086; + public static final int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087; + public static final int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088; + public static final int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = 0x0089; + + /* + * RFC 4162 + */ + public static final int TLS_RSA_WITH_SEED_CBC_SHA = 0x0096; + public static final int TLS_DH_DSS_WITH_SEED_CBC_SHA = 0x0097; + public static final int TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098; + public static final int TLS_DHE_DSS_WITH_SEED_CBC_SHA = 0x0099; + public static final int TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009A; + public static final int TLS_DH_anon_WITH_SEED_CBC_SHA = 0x009B; + + /* + * RFC 4279 + */ + public static final int TLS_PSK_WITH_RC4_128_SHA = 0x008A; + public static final int TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B; + public static final int TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C; + public static final int TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D; + public static final int TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E; + public static final int TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F; + public static final int TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090; + public static final int TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091; + public static final int TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092; + public static final int TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093; + public static final int TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094; + public static final int TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095; + + /* + * RFC 4492 + */ + public static final int TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001; + public static final int TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002; + public static final int TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003; + public static final int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004; + public static final int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005; + public static final int TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006; + public static final int TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007; + public static final int TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008; + public static final int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009; + public static final int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A; + public static final int TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B; + public static final int TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C; + public static final int TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D; + public static final int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E; + public static final int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F; + public static final int TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010; + public static final int TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011; + public static final int TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012; + public static final int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013; + public static final int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014; + public static final int TLS_ECDH_anon_WITH_NULL_SHA = 0xC015; + public static final int TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016; + public static final int TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017; + public static final int TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018; + public static final int TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019; + + /* + * RFC 4785 + */ + public static final int TLS_PSK_WITH_NULL_SHA = 0x002C; + public static final int TLS_DHE_PSK_WITH_NULL_SHA = 0x002D; + public static final int TLS_RSA_PSK_WITH_NULL_SHA = 0x002E; + + /* + * RFC 5054 + */ + public static final int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A; + public static final int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B; + public static final int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C; + public static final int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D; + public static final int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E; + public static final int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F; + public static final int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020; + public static final int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021; + public static final int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022; + + /* + * RFC 5246 + */ + public static final int TLS_RSA_WITH_NULL_SHA256 = 0x003B; + public static final int TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C; + public static final int TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D; + public static final int TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E; + public static final int TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F; + public static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040; + public static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067; + public static final int TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068; + public static final int TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069; + public static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A; + public static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B; + public static final int TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C; + public static final int TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D; + + /* + * RFC 5288 + */ + public static final int TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C; + public static final int TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D; + public static final int TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E; + public static final int TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F; + public static final int TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0; + public static final int TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1; + public static final int TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2; + public static final int TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3; + public static final int TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4; + public static final int TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5; + public static final int TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6; + public static final int TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7; + + /* + * RFC 5289 + */ + public static final int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023; + public static final int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024; + public static final int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025; + public static final int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026; + public static final int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027; + public static final int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028; + public static final int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029; + public static final int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A; + public static final int TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B; + public static final int TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C; + public static final int TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D; + public static final int TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E; + public static final int TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F; + public static final int TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030; + public static final int TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031; + public static final int TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032; + + /* + * RFC 5746 + */ + public static final int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/CipherType.java b/core/src/main/java/org/bouncycastle/crypto/tls/CipherType.java new file mode 100644 index 00000000..cac7dbe4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/CipherType.java @@ -0,0 +1,19 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class CipherType +{ + + public static final int stream = 0; + public static final int block = 1; + + /* + * RFC 5246 + */ + public static final int aead = 2; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ClientAuthenticationType.java b/core/src/main/java/org/bouncycastle/crypto/tls/ClientAuthenticationType.java new file mode 100644 index 00000000..a77a8265 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ClientAuthenticationType.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +public class ClientAuthenticationType +{ + + /* + * RFC 5077 4 + */ + public static final short anonymous = 0; + public static final short certificate_based = 1; + public static final short psk = 2; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ClientCertificateType.java b/core/src/main/java/org/bouncycastle/crypto/tls/ClientCertificateType.java new file mode 100644 index 00000000..0a12acae --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ClientCertificateType.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.tls; + +public class ClientCertificateType +{ + + /* + * RFC 4346 7.4.4 + */ + public static final short rsa_sign = 1; + public static final short dss_sign = 2; + public static final short rsa_fixed_dh = 3; + public static final short dss_fixed_dh = 4; + public static final short rsa_ephemeral_dh_RESERVED = 5; + public static final short dss_ephemeral_dh_RESERVED = 6; + public static final short fortezza_dms_RESERVED = 20; + + /* + * RFC 4492 5.5 + */ + public static final short ecdsa_sign = 64; + public static final short rsa_fixed_ecdh = 65; + public static final short ecdsa_fixed_ecdh = 66; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java b/core/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java new file mode 100644 index 00000000..1a484911 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java @@ -0,0 +1,117 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.Digest; + +/** + * A combined hash, which implements md5(m) || sha1(m). + */ +class CombinedHash + implements TlsHandshakeHash +{ + + protected TlsContext context; + protected Digest md5; + protected Digest sha1; + + CombinedHash() + { + this.md5 = TlsUtils.createHash(HashAlgorithm.md5); + this.sha1 = TlsUtils.createHash(HashAlgorithm.sha1); + } + + CombinedHash(CombinedHash t) + { + this.context = t.context; + this.md5 = TlsUtils.cloneHash(HashAlgorithm.md5, t.md5); + this.sha1 = TlsUtils.cloneHash(HashAlgorithm.sha1, t.sha1); + } + + public void init(TlsContext context) + { + this.context = context; + } + + public TlsHandshakeHash commit() + { + return this; + } + + public TlsHandshakeHash fork() + { + return new CombinedHash(this); + } + + /** + * @see org.bouncycastle.crypto.Digest#getAlgorithmName() + */ + public String getAlgorithmName() + { + return md5.getAlgorithmName() + " and " + sha1.getAlgorithmName(); + } + + /** + * @see org.bouncycastle.crypto.Digest#getDigestSize() + */ + public int getDigestSize() + { + return md5.getDigestSize() + sha1.getDigestSize(); + } + + /** + * @see org.bouncycastle.crypto.Digest#update(byte) + */ + public void update(byte in) + { + md5.update(in); + sha1.update(in); + } + + /** + * @see org.bouncycastle.crypto.Digest#update(byte[], int, int) + */ + public void update(byte[] in, int inOff, int len) + { + md5.update(in, inOff, len); + sha1.update(in, inOff, len); + } + + /** + * @see org.bouncycastle.crypto.Digest#doFinal(byte[], int) + */ + public int doFinal(byte[] out, int outOff) + { + if (context != null && context.getServerVersion().isSSL()) + { + ssl3Complete(md5, SSL3Mac.IPAD, SSL3Mac.OPAD, 48); + ssl3Complete(sha1, SSL3Mac.IPAD, SSL3Mac.OPAD, 40); + } + + int i1 = md5.doFinal(out, outOff); + int i2 = sha1.doFinal(out, outOff + i1); + return i1 + i2; + } + + /** + * @see org.bouncycastle.crypto.Digest#reset() + */ + public void reset() + { + md5.reset(); + sha1.reset(); + } + + protected void ssl3Complete(Digest d, byte[] ipad, byte[] opad, int padLength) + { + byte[] secret = context.getSecurityParameters().masterSecret; + + d.update(secret, 0, secret.length); + d.update(ipad, 0, padLength); + + byte[] tmp = new byte[d.getDigestSize()]; + d.doFinal(tmp, 0); + + d.update(secret, 0, secret.length); + d.update(opad, 0, padLength); + d.update(tmp, 0, tmp.length); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/CompressionMethod.java b/core/src/main/java/org/bouncycastle/crypto/tls/CompressionMethod.java new file mode 100644 index 00000000..935d378f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/CompressionMethod.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 6.1 + */ +public class CompressionMethod +{ + public static final short _null = 0; + + /** + * @deprecated use '_null' instead + */ + public static final short NULL = _null; + + /* + * RFC 3749 2 + */ + public static final short DEFLATE = 1; + + /* + * Values from 224 decimal (0xE0) through 255 decimal (0xFF) + * inclusive are reserved for private use. + */ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ConnectionEnd.java b/core/src/main/java/org/bouncycastle/crypto/tls/ConnectionEnd.java new file mode 100644 index 00000000..f13def6e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ConnectionEnd.java @@ -0,0 +1,14 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class ConnectionEnd +{ + + public static final int server = 0; + public static final int client = 1; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ContentType.java b/core/src/main/java/org/bouncycastle/crypto/tls/ContentType.java new file mode 100644 index 00000000..d814eac0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ContentType.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 6.2.1 + */ +public class ContentType +{ + public static final short change_cipher_spec = 20; + public static final short alert = 21; + public static final short handshake = 22; + public static final short application_data = 23; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java new file mode 100644 index 00000000..8ccacfba --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java @@ -0,0 +1,634 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.util.Arrays; + +public class DTLSClientProtocol + extends DTLSProtocol +{ + + public DTLSClientProtocol(SecureRandom secureRandom) + { + super(secureRandom); + } + + public DTLSTransport connect(TlsClient client, DatagramTransport transport) + throws IOException + { + + if (client == null) + { + throw new IllegalArgumentException("'client' cannot be null"); + } + if (transport == null) + { + throw new IllegalArgumentException("'transport' cannot be null"); + } + + SecurityParameters securityParameters = new SecurityParameters(); + securityParameters.entity = ConnectionEnd.client; + securityParameters.clientRandom = TlsProtocol.createRandomBlock(secureRandom); + + ClientHandshakeState state = new ClientHandshakeState(); + state.client = client; + state.clientContext = new TlsClientContextImpl(secureRandom, securityParameters); + client.init(state.clientContext); + + DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.clientContext, client, ContentType.handshake); + + try + { + return clientHandshake(state, recordLayer); + } + catch (TlsFatalAlert fatalAlert) + { + recordLayer.fail(fatalAlert.getAlertDescription()); + throw fatalAlert; + } + catch (IOException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw e; + } + catch (RuntimeException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLayer recordLayer) + throws IOException + { + + SecurityParameters securityParameters = state.clientContext.getSecurityParameters(); + DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer); + + byte[] clientHelloBody = generateClientHello(state, state.client); + handshake.sendMessage(HandshakeType.client_hello, clientHelloBody); + + DTLSReliableHandshake.Message serverMessage = handshake.receiveMessage(); + + { + // NOTE: After receiving a record from the server, we discover the record layer version + ProtocolVersion server_version = recordLayer.getDiscoveredPeerVersion(); + ProtocolVersion client_version = state.clientContext.getClientVersion(); + + if (!server_version.isEqualOrEarlierVersionOf(client_version)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + state.clientContext.setServerVersion(server_version); + state.client.notifyServerVersion(server_version); + } + + while (serverMessage.getType() == HandshakeType.hello_verify_request) + { + byte[] cookie = parseHelloVerifyRequest(state.clientContext, serverMessage.getBody()); + byte[] patched = patchClientHelloWithCookie(clientHelloBody, cookie); + + handshake.resetHandshakeMessagesDigest(); + handshake.sendMessage(HandshakeType.client_hello, patched); + + serverMessage = handshake.receiveMessage(); + } + + if (serverMessage.getType() == HandshakeType.server_hello) + { + processServerHello(state, serverMessage.getBody()); + serverMessage = handshake.receiveMessage(); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.selectedCipherSuite); + securityParameters.compressionAlgorithm = state.selectedCompressionMethod; + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has + * a verify_data_length equal to 12. This includes all existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + handshake.notifyHelloComplete(); + + if (serverMessage.getType() == HandshakeType.supplemental_data) + { + processServerSupplementalData(state, serverMessage.getBody()); + serverMessage = handshake.receiveMessage(); + } + else + { + state.client.processServerSupplementalData(null); + } + + state.keyExchange = state.client.getKeyExchange(); + state.keyExchange.init(state.clientContext); + + if (serverMessage.getType() == HandshakeType.certificate) + { + processServerCertificate(state, serverMessage.getBody()); + serverMessage = handshake.receiveMessage(); + } + else + { + // Okay, Certificate is optional + state.keyExchange.skipServerCredentials(); + } + + if (serverMessage.getType() == HandshakeType.server_key_exchange) + { + processServerKeyExchange(state, serverMessage.getBody()); + serverMessage = handshake.receiveMessage(); + } + else + { + // Okay, ServerKeyExchange is optional + state.keyExchange.skipServerKeyExchange(); + } + + if (serverMessage.getType() == HandshakeType.certificate_request) + { + processCertificateRequest(state, serverMessage.getBody()); + serverMessage = handshake.receiveMessage(); + } + else + { + // Okay, CertificateRequest is optional + } + + if (serverMessage.getType() == HandshakeType.server_hello_done) + { + if (serverMessage.getBody().length != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + Vector clientSupplementalData = state.client.getClientSupplementalData(); + if (clientSupplementalData != null) + { + byte[] supplementalDataBody = generateSupplementalData(clientSupplementalData); + handshake.sendMessage(HandshakeType.supplemental_data, supplementalDataBody); + } + + if (state.certificateRequest != null) + { + state.clientCredentials = state.authentication.getClientCredentials(state.certificateRequest); + + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a certificate + * message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + Certificate clientCertificate = null; + if (state.clientCredentials != null) + { + clientCertificate = state.clientCredentials.getCertificate(); + } + if (clientCertificate == null) + { + clientCertificate = Certificate.EMPTY_CHAIN; + } + + byte[] certificateBody = generateCertificate(clientCertificate); + handshake.sendMessage(HandshakeType.certificate, certificateBody); + } + + if (state.clientCredentials != null) + { + state.keyExchange.processClientCredentials(state.clientCredentials); + } + else + { + state.keyExchange.skipClientCredentials(); + } + + byte[] clientKeyExchangeBody = generateClientKeyExchange(state); + handshake.sendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody); + + TlsProtocol.establishMasterSecret(state.clientContext, state.keyExchange); + + if (state.clientCredentials instanceof TlsSignerCredentials) + { + /* + * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended + * from TLS 1.2 + */ + TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials; + byte[] md5andsha1 = handshake.getCurrentHash(); + byte[] signature = signerCredentials.generateCertificateSignature(md5andsha1); + byte[] certificateVerifyBody = generateCertificateVerify(state, signature); + handshake.sendMessage(HandshakeType.certificate_verify, certificateVerifyBody); + } + + recordLayer.initPendingEpoch(state.client.getCipher()); + + // NOTE: Calculated exclusive of the Finished message itself + byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, "client finished", + handshake.getCurrentHash()); + handshake.sendMessage(HandshakeType.finished, clientVerifyData); + + if (state.expectSessionTicket) + { + serverMessage = handshake.receiveMessage(); + if (serverMessage.getType() == HandshakeType.session_ticket) + { + processNewSessionTicket(state, serverMessage.getBody()); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + // NOTE: Calculated exclusive of the actual Finished message from the server + byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, "server finished", + handshake.getCurrentHash()); + serverMessage = handshake.receiveMessage(); + + if (serverMessage.getType() == HandshakeType.finished) + { + processFinished(serverMessage.getBody(), expectedServerVerifyData); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + handshake.finish(); + + state.client.notifyHandshakeComplete(); + + return new DTLSTransport(recordLayer); + } + + protected byte[] generateCertificateVerify(ClientHandshakeState state, byte[] signature) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeOpaque16(signature, buf); + return buf.toByteArray(); + } + + protected byte[] generateClientHello(ClientHandshakeState state, TlsClient client) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + ProtocolVersion client_version = client.getClientVersion(); + if (!client_version.isDTLS()) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + state.clientContext.setClientVersion(client_version); + TlsUtils.writeVersion(client_version, buf); + + buf.write(state.clientContext.getSecurityParameters().getClientRandom()); + + // Session id + TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + + // Cookie + TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + + /* + * Cipher suites + */ + state.offeredCipherSuites = client.getCipherSuites(); + + // Integer -> byte[] + state.clientExtensions = client.getClientExtensions(); + + // Cipher Suites (and SCSV) + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + boolean noRenegExt = state.clientExtensions == null + || state.clientExtensions.get(TlsProtocol.EXT_RenegotiationInfo) == null; + + int count = state.offeredCipherSuites.length; + if (noRenegExt) + { + // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV + ++count; + } + + TlsUtils.writeUint16(2 * count, buf); + TlsUtils.writeUint16Array(state.offeredCipherSuites, buf); + + if (noRenegExt) + { + TlsUtils.writeUint16(CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, buf); + } + } + + // TODO Add support for compression + // Compression methods + // state.offeredCompressionMethods = client.getCompressionMethods(); + state.offeredCompressionMethods = new short[]{CompressionMethod._null}; + + TlsUtils.writeUint8((short)state.offeredCompressionMethods.length, buf); + TlsUtils.writeUint8Array(state.offeredCompressionMethods, buf); + + // Extensions + if (state.clientExtensions != null) + { + TlsProtocol.writeExtensions(buf, state.clientExtensions); + } + + return buf.toByteArray(); + } + + protected byte[] generateClientKeyExchange(ClientHandshakeState state) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + state.keyExchange.generateClientKeyExchange(buf); + return buf.toByteArray(); + } + + protected void processCertificateRequest(ClientHandshakeState state, byte[] body) + throws IOException + { + + if (state.authentication == null) + { + /* + * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to + * request client identification. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + state.certificateRequest = CertificateRequest.parse(buf); + + TlsProtocol.assertEmpty(buf); + + state.keyExchange.validateCertificateRequest(state.certificateRequest); + } + + protected void processNewSessionTicket(ClientHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf); + + TlsProtocol.assertEmpty(buf); + + state.client.notifyNewSessionTicket(newSessionTicket); + } + + protected void processServerCertificate(ClientHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + Certificate serverCertificate = Certificate.parse(buf); + + TlsProtocol.assertEmpty(buf); + + state.keyExchange.processServerCertificate(serverCertificate); + state.authentication = state.client.getAuthentication(); + state.authentication.notifyServerCertificate(serverCertificate); + } + + protected void processServerHello(ClientHandshakeState state, byte[] body) + throws IOException + { + + SecurityParameters securityParameters = state.clientContext.getSecurityParameters(); + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + // TODO Read RFCs for guidance on the expected record layer version number + ProtocolVersion server_version = TlsUtils.readVersion(buf); + if (!server_version.equals(state.clientContext.getServerVersion())) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + securityParameters.serverRandom = TlsUtils.readFully(32, buf); + + byte[] sessionID = TlsUtils.readOpaque8(buf); + if (sessionID.length > 32) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + state.client.notifySessionID(sessionID); + + state.selectedCipherSuite = TlsUtils.readUint16(buf); + if (!TlsProtocol.arrayContains(state.offeredCipherSuites, state.selectedCipherSuite) + || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.illegal_parameter); + + state.client.notifySelectedCipherSuite(state.selectedCipherSuite); + + state.selectedCompressionMethod = TlsUtils.readUint8(buf); + if (!TlsProtocol.arrayContains(state.offeredCompressionMethods, state.selectedCompressionMethod)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + state.client.notifySelectedCompressionMethod(state.selectedCompressionMethod); + + /* + * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server + * hello message when the client has requested extended functionality via the extended + * client hello message specified in Section 2.1. ... Note that the extended server hello + * message is only sent in response to an extended client hello message. This prevents the + * possibility that the extended server hello message could "break" existing TLS 1.0 + * clients. + */ + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + + // Integer -> byte[] + Hashtable serverExtensions = TlsProtocol.readExtensions(buf); + + /* + * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an + * extended client hello message. However, see RFC 5746 exception below. We always include + * the SCSV, so an Extended Server Hello is always allowed. + */ + if (serverExtensions != null) + { + Enumeration e = serverExtensions.keys(); + while (e.hasMoreElements()) + { + Integer extType = (Integer)e.nextElement(); + + /* + * RFC 5746 Note that sending a "renegotiation_info" extension in response to a + * ClientHello containing only the SCSV is an explicit exception to the prohibition + * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is + * only allowed because the client is signaling its willingness to receive the + * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. TLS implementations + * MUST continue to comply with Section 7.4.1.4 for all other extensions. + */ + if (!extType.equals(TlsProtocol.EXT_RenegotiationInfo) + && (state.clientExtensions == null || state.clientExtensions.get(extType) == null)) + { + /* + * RFC 3546 2.3 Note that for all extension types (including those defined in + * future), the extension type MUST NOT appear in the extended server hello + * unless the same extension type appeared in the corresponding client hello. + * Thus clients MUST abort the handshake if they receive an extension type in + * the extended server hello that they did not request in the associated + * (extended) client hello. + */ + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + } + } + + /* + * RFC 5746 3.4. Client Behavior: Initial Handshake + */ + { + /* + * When a ServerHello is received, the client MUST check if it includes the + * "renegotiation_info" extension: + */ + byte[] renegExtValue = (byte[])serverExtensions.get(TlsProtocol.EXT_RenegotiationInfo); + if (renegExtValue != null) + { + /* + * If the extension is present, set the secure_renegotiation flag to TRUE. The + * client MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake (by sending a fatal + * handshake_failure alert). + */ + state.secure_renegotiation = true; + + if (!Arrays.constantTimeAreEqual(renegExtValue, + TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + } + + state.expectSessionTicket = serverExtensions.containsKey(TlsProtocol.EXT_SessionTicket); + } + + state.client.notifySecureRenegotiation(state.secure_renegotiation); + + if (state.clientExtensions != null) + { + state.client.processServerExtensions(serverExtensions); + } + } + + protected void processServerKeyExchange(ClientHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + state.keyExchange.processServerKeyExchange(buf); + + TlsProtocol.assertEmpty(buf); + } + + protected void processServerSupplementalData(ClientHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + Vector serverSupplementalData = TlsProtocol.readSupplementalDataMessage(buf); + state.client.processServerSupplementalData(serverSupplementalData); + } + + protected static byte[] parseHelloVerifyRequest(TlsContext context, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + ProtocolVersion server_version = TlsUtils.readVersion(buf); + if (!server_version.equals(context.getServerVersion())) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + byte[] cookie = TlsUtils.readOpaque8(buf); + + // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347 + + TlsProtocol.assertEmpty(buf); + + return cookie; + } + + protected static byte[] patchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie) + throws IOException + { + + int sessionIDPos = 34; + int sessionIDLength = TlsUtils.readUint8(clientHelloBody, sessionIDPos); + + int cookieLengthPos = sessionIDPos + 1 + sessionIDLength; + int cookiePos = cookieLengthPos + 1; + + byte[] patched = new byte[clientHelloBody.length + cookie.length]; + System.arraycopy(clientHelloBody, 0, patched, 0, cookieLengthPos); + TlsUtils.writeUint8((short)cookie.length, patched, cookieLengthPos); + System.arraycopy(cookie, 0, patched, cookiePos, cookie.length); + System.arraycopy(clientHelloBody, cookiePos, patched, cookiePos + cookie.length, clientHelloBody.length + - cookiePos); + + return patched; + } + + protected static class ClientHandshakeState + { + TlsClient client = null; + TlsClientContextImpl clientContext = null; + int[] offeredCipherSuites = null; + short[] offeredCompressionMethods = null; + Hashtable clientExtensions = null; + int selectedCipherSuite = -1; + short selectedCompressionMethod = -1; + boolean secure_renegotiation = false; + boolean expectSessionTicket = false; + TlsKeyExchange keyExchange = null; + TlsAuthentication authentication = null; + CertificateRequest certificateRequest = null; + TlsCredentials clientCredentials = null; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSEpoch.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSEpoch.java new file mode 100644 index 00000000..59fbc53e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSEpoch.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto.tls; + +class DTLSEpoch +{ + + private final DTLSReplayWindow replayWindow = new DTLSReplayWindow(); + + private final int epoch; + private final TlsCipher cipher; + + private long sequence_number = 0; + + DTLSEpoch(int epoch, TlsCipher cipher) + { + if (epoch < 0) + { + throw new IllegalArgumentException("'epoch' must be >= 0"); + } + if (cipher == null) + { + throw new IllegalArgumentException("'cipher' cannot be null"); + } + + this.epoch = epoch; + this.cipher = cipher; + } + + long allocateSequenceNumber() + { + // TODO Check for overflow + return sequence_number++; + } + + TlsCipher getCipher() + { + return cipher; + } + + int getEpoch() + { + return epoch; + } + + DTLSReplayWindow getReplayWindow() + { + return replayWindow; + } + + long getSequence_number() + { + return sequence_number; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSHandshakeRetransmit.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSHandshakeRetransmit.java new file mode 100644 index 00000000..251d3a28 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSHandshakeRetransmit.java @@ -0,0 +1,9 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +interface DTLSHandshakeRetransmit +{ + void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java new file mode 100644 index 00000000..2789b22d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java @@ -0,0 +1,84 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Vector; + +import org.bouncycastle.util.Arrays; + +public abstract class DTLSProtocol +{ + + protected final SecureRandom secureRandom; + + protected DTLSProtocol(SecureRandom secureRandom) + { + + if (secureRandom == null) + { + throw new IllegalArgumentException("'secureRandom' cannot be null"); + } + + this.secureRandom = secureRandom; + } + + protected void processFinished(byte[] body, byte[] expected_verify_data) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf); + + TlsProtocol.assertEmpty(buf); + + if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data)) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + protected static byte[] generateCertificate(Certificate certificate) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + certificate.encode(buf); + return buf.toByteArray(); + } + + protected static byte[] generateSupplementalData(Vector supplementalData) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsProtocol.writeSupplementalData(buf, supplementalData); + return buf.toByteArray(); + } + + protected static void validateSelectedCipherSuite(int selectedCipherSuite, short alertDescription) + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_DH_anon_EXPORT_WITH_RC4_40_MD5: + case CipherSuite.TLS_DH_anon_WITH_RC4_128_MD5: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA: + // TODO Alert + throw new IllegalStateException("RC4 MUST NOT be used with DTLS"); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReassembler.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReassembler.java new file mode 100644 index 00000000..d82bcc97 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReassembler.java @@ -0,0 +1,136 @@ +package org.bouncycastle.crypto.tls; + +import java.util.Vector; + +class DTLSReassembler +{ + + private final short msg_type; + private final byte[] body; + + private Vector missing = new Vector(); + + DTLSReassembler(short msg_type, int length) + { + this.msg_type = msg_type; + this.body = new byte[length]; + this.missing.addElement(new Range(0, length)); + } + + short getType() + { + return msg_type; + } + + byte[] getBodyIfComplete() + { + return missing.isEmpty() ? body : null; + } + + void contributeFragment(short msg_type, int length, byte[] buf, int off, int fragment_offset, + int fragment_length) + { + + int fragment_end = fragment_offset + fragment_length; + + if (this.msg_type != msg_type || this.body.length != length || fragment_end > length) + { + return; + } + + if (fragment_length == 0) + { + // NOTE: Empty messages still require an empty fragment to complete it + if (fragment_offset == 0 && !missing.isEmpty()) + { + Range firstRange = (Range)missing.firstElement(); + if (firstRange.getEnd() == 0) + { + missing.removeElementAt(0); + } + } + return; + } + + for (int i = 0; i < missing.size(); ++i) + { + Range range = (Range)missing.elementAt(i); + if (range.getStart() >= fragment_end) + { + break; + } + if (range.getEnd() > fragment_offset) + { + + int copyStart = Math.max(range.getStart(), fragment_offset); + int copyEnd = Math.min(range.getEnd(), fragment_end); + int copyLength = copyEnd - copyStart; + + System.arraycopy(buf, off + copyStart - fragment_offset, body, copyStart, + copyLength); + + if (copyStart == range.getStart()) + { + if (copyEnd == range.getEnd()) + { + missing.removeElementAt(i--); + } + else + { + range.setStart(copyEnd); + } + } + else + { + if (copyEnd == range.getEnd()) + { + range.setEnd(copyStart); + } + else + { + missing.insertElementAt(new Range(copyEnd, range.getEnd()), ++i); + range.setEnd(copyStart); + } + } + } + } + } + + void reset() + { + this.missing.removeAllElements(); + this.missing.addElement(new Range(0, body.length)); + } + + private static class Range + { + + private int start, end; + + Range(int start, int end) + { + this.start = start; + this.end = end; + } + + public int getStart() + { + return start; + } + + public void setStart(int start) + { + this.start = start; + } + + public int getEnd() + { + return end; + } + + public void setEnd(int end) + { + this.end = end; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java new file mode 100644 index 00000000..3fde01aa --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java @@ -0,0 +1,497 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +class DTLSRecordLayer + implements DatagramTransport +{ + + private static final int RECORD_HEADER_LENGTH = 13; + private static final int MAX_FRAGMENT_LENGTH = 1 << 14; + private static final long TCP_MSL = 1000L * 60 * 2; + private static final long RETRANSMIT_TIMEOUT = TCP_MSL * 2; + + private final DatagramTransport transport; + private final TlsContext context; + private final TlsPeer peer; + + private final ByteQueue recordQueue = new ByteQueue(); + + private volatile boolean closed = false; + private volatile boolean failed = false; + private volatile ProtocolVersion discoveredPeerVersion = null; + private volatile boolean inHandshake; + private DTLSEpoch currentEpoch, pendingEpoch; + private DTLSEpoch readEpoch, writeEpoch; + + private DTLSHandshakeRetransmit retransmit = null; + private DTLSEpoch retransmitEpoch = null; + private long retransmitExpiry = 0; + + DTLSRecordLayer(DatagramTransport transport, TlsContext context, TlsPeer peer, short contentType) + { + this.transport = transport; + this.context = context; + this.peer = peer; + + this.inHandshake = true; + + this.currentEpoch = new DTLSEpoch(0, new TlsNullCipher(context)); + this.pendingEpoch = null; + this.readEpoch = currentEpoch; + this.writeEpoch = currentEpoch; + } + + ProtocolVersion getDiscoveredPeerVersion() + { + return discoveredPeerVersion; + } + + void initPendingEpoch(TlsCipher pendingCipher) + { + if (pendingEpoch != null) + { + throw new IllegalStateException(); + } + + /* + * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations + * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment + * lifetime." + */ + + // TODO Check for overflow + this.pendingEpoch = new DTLSEpoch(writeEpoch.getEpoch() + 1, pendingCipher); + } + + void handshakeSuccessful(DTLSHandshakeRetransmit retransmit) + { + if (readEpoch == currentEpoch || writeEpoch == currentEpoch) + { + // TODO + throw new IllegalStateException(); + } + + if (retransmit != null) + { + this.retransmit = retransmit; + this.retransmitEpoch = currentEpoch; + this.retransmitExpiry = System.currentTimeMillis() + RETRANSMIT_TIMEOUT; + } + + this.inHandshake = false; + this.currentEpoch = pendingEpoch; + this.pendingEpoch = null; + } + + void resetWriteEpoch() + { + if (retransmitEpoch != null) + { + this.writeEpoch = retransmitEpoch; + } + else + { + this.writeEpoch = currentEpoch; + } + } + + public int getReceiveLimit() + throws IOException + { + return Math.min(MAX_FRAGMENT_LENGTH, + readEpoch.getCipher().getPlaintextLimit(transport.getReceiveLimit() - RECORD_HEADER_LENGTH)); + } + + public int getSendLimit() + throws IOException + { + return Math.min(MAX_FRAGMENT_LENGTH, + writeEpoch.getCipher().getPlaintextLimit(transport.getSendLimit() - RECORD_HEADER_LENGTH)); + } + + public int receive(byte[] buf, int off, int len, int waitMillis) + throws IOException + { + + byte[] record = null; + + for (; ; ) + { + + int receiveLimit = Math.min(len, getReceiveLimit()) + RECORD_HEADER_LENGTH; + if (record == null || record.length < receiveLimit) + { + record = new byte[receiveLimit]; + } + + try + { + if (retransmit != null && System.currentTimeMillis() > retransmitExpiry) + { + retransmit = null; + retransmitEpoch = null; + } + + int received = receiveRecord(record, 0, receiveLimit, waitMillis); + if (received < 0) + { + return received; + } + if (received < RECORD_HEADER_LENGTH) + { + continue; + } + int length = TlsUtils.readUint16(record, 11); + if (received != (length + RECORD_HEADER_LENGTH)) + { + continue; + } + + short type = TlsUtils.readUint8(record, 0); + + // TODO Support user-specified custom protocols? + switch (type) + { + case ContentType.alert: + case ContentType.application_data: + case ContentType.change_cipher_spec: + case ContentType.handshake: + break; + default: + // TODO Exception? + continue; + } + + int epoch = TlsUtils.readUint16(record, 3); + + DTLSEpoch recordEpoch = null; + if (epoch == readEpoch.getEpoch()) + { + recordEpoch = readEpoch; + } + else if (type == ContentType.handshake && retransmitEpoch != null + && epoch == retransmitEpoch.getEpoch()) + { + recordEpoch = retransmitEpoch; + } + + if (recordEpoch == null) + { + continue; + } + + long seq = TlsUtils.readUint48(record, 5); + if (recordEpoch.getReplayWindow().shouldDiscard(seq)) + { + continue; + } + + ProtocolVersion version = TlsUtils.readVersion(record, 1); + if (discoveredPeerVersion != null && !discoveredPeerVersion.equals(version)) + { + continue; + } + + byte[] plaintext = recordEpoch.getCipher().decodeCiphertext( + getMacSequenceNumber(recordEpoch.getEpoch(), seq), type, record, RECORD_HEADER_LENGTH, + received - RECORD_HEADER_LENGTH); + + recordEpoch.getReplayWindow().reportAuthenticated(seq); + + if (discoveredPeerVersion == null) + { + discoveredPeerVersion = version; + } + + switch (type) + { + case ContentType.alert: + { + + if (plaintext.length == 2) + { + short alertLevel = plaintext[0]; + short alertDescription = plaintext[1]; + + peer.notifyAlertReceived(alertLevel, alertDescription); + + if (alertLevel == AlertLevel.fatal) + { + fail(alertDescription); + throw new TlsFatalAlert(alertDescription); + } + + // TODO Can close_notify be a fatal alert? + if (alertDescription == AlertDescription.close_notify) + { + closeTransport(); + } + } + else + { + // TODO What exception? + } + + continue; + } + case ContentType.application_data: + { + if (inHandshake) + { + // TODO Consider buffering application data for new epoch that arrives + // out-of-order with the Finished message + continue; + } + break; + } + case ContentType.change_cipher_spec: + { + // Implicitly receive change_cipher_spec and change to pending cipher state + + if (plaintext.length != 1 || plaintext[0] != 1) + { + continue; + } + + if (pendingEpoch != null) + { + readEpoch = pendingEpoch; + } + + continue; + } + case ContentType.handshake: + { + if (!inHandshake) + { + if (retransmit != null) + { + retransmit.receivedHandshakeRecord(epoch, plaintext, 0, plaintext.length); + } + + // TODO Consider support for HelloRequest + continue; + } + } + } + + /* + * NOTE: If we receive any non-handshake data in the new epoch implies the peer has + * received our final flight. + */ + if (!inHandshake && retransmit != null) + { + this.retransmit = null; + this.retransmitEpoch = null; + } + + System.arraycopy(plaintext, 0, buf, off, plaintext.length); + return plaintext.length; + } + catch (IOException e) + { + // NOTE: Assume this is a timeout for the moment + throw e; + } + } + } + + public void send(byte[] buf, int off, int len) + throws IOException + { + + short contentType = ContentType.application_data; + + if (this.inHandshake || this.writeEpoch == this.retransmitEpoch) + { + + contentType = ContentType.handshake; + + short handshakeType = TlsUtils.readUint8(buf, off); + if (handshakeType == HandshakeType.finished) + { + + DTLSEpoch nextEpoch = null; + if (this.inHandshake) + { + nextEpoch = pendingEpoch; + } + else if (this.writeEpoch == this.retransmitEpoch) + { + nextEpoch = currentEpoch; + } + + if (nextEpoch == null) + { + // TODO + throw new IllegalStateException(); + } + + // Implicitly send change_cipher_spec and change to pending cipher state + + // TODO Send change_cipher_spec and finished records in single datagram? + byte[] data = new byte[]{1}; + sendRecord(ContentType.change_cipher_spec, data, 0, data.length); + + writeEpoch = nextEpoch; + } + } + + sendRecord(contentType, buf, off, len); + } + + public void close() + throws IOException + { + if (!closed) + { + if (inHandshake) + { + warn(AlertDescription.user_canceled, "User canceled handshake"); + } + closeTransport(); + } + } + + void fail(short alertDescription) + { + if (!closed) + { + try + { + raiseAlert(AlertLevel.fatal, alertDescription, null, null); + } + catch (Exception e) + { + // Ignore + } + + failed = true; + + closeTransport(); + } + } + + void warn(short alertDescription, String message) + throws IOException + { + raiseAlert(AlertLevel.warning, alertDescription, message, null); + } + + private void closeTransport() + { + if (!closed) + { + /* + * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is + * required to send a close_notify alert before closing the write side of the + * connection. The other party MUST respond with a close_notify alert of its own and + * close down the connection immediately, discarding any pending writes. + */ + + try + { + if (!failed) + { + warn(AlertDescription.close_notify, null); + } + transport.close(); + } + catch (Exception e) + { + // Ignore + } + + closed = true; + } + } + + private void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause) + throws IOException + { + + peer.notifyAlertRaised(alertLevel, alertDescription, message, cause); + + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + + sendRecord(ContentType.alert, error, 0, 2); + } + + private int receiveRecord(byte[] buf, int off, int len, int waitMillis) + throws IOException + { + if (recordQueue.size() > 0) + { + int length = 0; + if (recordQueue.size() >= RECORD_HEADER_LENGTH) + { + byte[] lengthBytes = new byte[2]; + recordQueue.read(lengthBytes, 0, 2, 11); + length = TlsUtils.readUint16(lengthBytes, 0); + } + + int received = Math.min(recordQueue.size(), RECORD_HEADER_LENGTH + length); + recordQueue.read(buf, off, received, 0); + recordQueue.removeData(received); + return received; + } + + int received = transport.receive(buf, off, len, waitMillis); + if (received >= RECORD_HEADER_LENGTH) + { + int fragmentLength = TlsUtils.readUint16(buf, off + 11); + int recordLength = RECORD_HEADER_LENGTH + fragmentLength; + if (received > recordLength) + { + recordQueue.addData(buf, off + recordLength, received - recordLength); + received = recordLength; + } + } + + return received; + } + + private void sendRecord(short contentType, byte[] buf, int off, int len) + throws IOException + { + + /* + * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (len < 1 && contentType != ContentType.application_data) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + int recordEpoch = writeEpoch.getEpoch(); + long recordSequenceNumber = writeEpoch.allocateSequenceNumber(); + + byte[] ciphertext = writeEpoch.getCipher().encodePlaintext( + getMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len); + + if (ciphertext.length > MAX_FRAGMENT_LENGTH) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + byte[] record = new byte[ciphertext.length + RECORD_HEADER_LENGTH]; + TlsUtils.writeUint8(contentType, record, 0); + ProtocolVersion version = discoveredPeerVersion != null ? discoveredPeerVersion : context.getClientVersion(); + TlsUtils.writeVersion(version, record, 1); + TlsUtils.writeUint16(recordEpoch, record, 3); + TlsUtils.writeUint48(recordSequenceNumber, record, 5); + TlsUtils.writeUint16(ciphertext.length, record, 11); + System.arraycopy(ciphertext, 0, record, RECORD_HEADER_LENGTH, ciphertext.length); + + transport.send(record, 0, record.length); + } + + private static long getMacSequenceNumber(int epoch, long sequence_number) + { + return ((long)epoch << 48) | sequence_number; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java new file mode 100644 index 00000000..38192513 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java @@ -0,0 +1,432 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.util.Integers; + +class DTLSReliableHandshake +{ + + private final static int MAX_RECEIVE_AHEAD = 10; + + private final DTLSRecordLayer recordLayer; + + private TlsHandshakeHash hash = new DeferredHash(); + + private Hashtable currentInboundFlight = new Hashtable(); + private Hashtable previousInboundFlight = null; + private Vector outboundFlight = new Vector(); + private boolean sending = true; + + private int message_seq = 0, next_receive_seq = 0; + + DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport) + { + this.recordLayer = transport; + this.hash.init(context); + } + + void notifyHelloComplete() + { + this.hash = this.hash.commit(); + } + + byte[] getCurrentHash() + { + TlsHandshakeHash copyOfHash = hash.fork(); + byte[] result = new byte[copyOfHash.getDigestSize()]; + copyOfHash.doFinal(result, 0); + return result; + } + + void sendMessage(short msg_type, byte[] body) + throws IOException + { + + if (!sending) + { + checkInboundFlight(); + sending = true; + outboundFlight.removeAllElements(); + } + + Message message = new Message(message_seq++, msg_type, body); + + outboundFlight.addElement(message); + + writeMessage(message); + updateHandshakeMessagesDigest(message); + } + + Message receiveMessage() + throws IOException + { + + if (sending) + { + sending = false; + prepareInboundFlight(); + } + + // Check if we already have the next message waiting + { + DTLSReassembler next = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(next_receive_seq)); + if (next != null) + { + byte[] body = next.getBodyIfComplete(); + if (body != null) + { + previousInboundFlight = null; + return updateHandshakeMessagesDigest(new Message(next_receive_seq++, next.getType(), body)); + } + } + } + + byte[] buf = null; + + // TODO Check the conditions under which we should reset this + int readTimeoutMillis = 1000; + + for (; ; ) + { + + int receiveLimit = recordLayer.getReceiveLimit(); + if (buf == null || buf.length < receiveLimit) + { + buf = new byte[receiveLimit]; + } + + // TODO Handle records containing multiple handshake messages + + try + { + for (; ; ) + { + int received = recordLayer.receive(buf, 0, receiveLimit, readTimeoutMillis); + if (received < 0) + { + break; + } + if (received < 12) + { + continue; + } + int fragment_length = TlsUtils.readUint24(buf, 9); + if (received != (fragment_length + 12)) + { + continue; + } + int seq = TlsUtils.readUint16(buf, 4); + if (seq > (next_receive_seq + MAX_RECEIVE_AHEAD)) + { + continue; + } + short msg_type = TlsUtils.readUint8(buf, 0); + int length = TlsUtils.readUint24(buf, 1); + int fragment_offset = TlsUtils.readUint24(buf, 6); + if (fragment_offset + fragment_length > length) + { + continue; + } + + if (seq < next_receive_seq) + { + /* + * NOTE: If we receive the previous flight of incoming messages in full + * again, retransmit our last flight + */ + if (previousInboundFlight != null) + { + DTLSReassembler reassembler = (DTLSReassembler)previousInboundFlight.get(Integers + .valueOf(seq)); + if (reassembler != null) + { + + reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset, + fragment_length); + + if (checkAll(previousInboundFlight)) + { + + resendOutboundFlight(); + + /* + * TODO[DTLS] implementations SHOULD back off handshake packet + * size during the retransmit backoff. + */ + readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000); + + resetAll(previousInboundFlight); + } + } + } + } + else + { + + DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq)); + if (reassembler == null) + { + reassembler = new DTLSReassembler(msg_type, length); + currentInboundFlight.put(Integers.valueOf(seq), reassembler); + } + + reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset, fragment_length); + + if (seq == next_receive_seq) + { + byte[] body = reassembler.getBodyIfComplete(); + if (body != null) + { + previousInboundFlight = null; + return updateHandshakeMessagesDigest(new Message(next_receive_seq++, + reassembler.getType(), body)); + } + } + } + } + } + catch (IOException e) + { + // NOTE: Assume this is a timeout for the moment + } + + resendOutboundFlight(); + + /* + * TODO[DTLS] implementations SHOULD back off handshake packet size during the + * retransmit backoff. + */ + readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000); + } + } + + void finish() + { + DTLSHandshakeRetransmit retransmit = null; + if (!sending) + { + checkInboundFlight(); + } + else if (currentInboundFlight != null) + { + /* + * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP], + * when in the FINISHED state, the node that transmits the last flight (the server in an + * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit + * of the peer's last flight with a retransmit of the last flight. + */ + retransmit = new DTLSHandshakeRetransmit() + { + public void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len) + throws IOException + { + /* + * TODO Need to handle the case where the previous inbound flight contains + * messages from two epochs. + */ + if (len < 12) + { + return; + } + int fragment_length = TlsUtils.readUint24(buf, off + 9); + if (len != (fragment_length + 12)) + { + return; + } + int seq = TlsUtils.readUint16(buf, off + 4); + if (seq >= next_receive_seq) + { + return; + } + + short msg_type = TlsUtils.readUint8(buf, off); + + // TODO This is a hack that only works until we try to support renegotiation + int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0; + if (epoch != expectedEpoch) + { + return; + } + + int length = TlsUtils.readUint24(buf, off + 1); + int fragment_offset = TlsUtils.readUint24(buf, off + 6); + if (fragment_offset + fragment_length > length) + { + return; + } + + DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq)); + if (reassembler != null) + { + reassembler.contributeFragment(msg_type, length, buf, off + 12, fragment_offset, + fragment_length); + if (checkAll(currentInboundFlight)) + { + resendOutboundFlight(); + resetAll(currentInboundFlight); + } + } + } + }; + } + + recordLayer.handshakeSuccessful(retransmit); + } + + void resetHandshakeMessagesDigest() + { + hash.reset(); + } + + /** + * Check that there are no "extra" messages left in the current inbound flight + */ + private void checkInboundFlight() + { + Enumeration e = currentInboundFlight.keys(); + while (e.hasMoreElements()) + { + Integer key = (Integer)e.nextElement(); + if (key.intValue() >= next_receive_seq) + { + // TODO Should this be considered an error? + } + } + } + + private void prepareInboundFlight() + { + resetAll(currentInboundFlight); + previousInboundFlight = currentInboundFlight; + currentInboundFlight = new Hashtable(); + } + + private void resendOutboundFlight() + throws IOException + { + recordLayer.resetWriteEpoch(); + for (int i = 0; i < outboundFlight.size(); ++i) + { + writeMessage((Message)outboundFlight.elementAt(i)); + } + } + + private Message updateHandshakeMessagesDigest(Message message) + throws IOException + { + if (message.getType() != HandshakeType.hello_request) + { + byte[] body = message.getBody(); + byte[] buf = new byte[12]; + TlsUtils.writeUint8(message.getType(), buf, 0); + TlsUtils.writeUint24(body.length, buf, 1); + TlsUtils.writeUint16(message.getSeq(), buf, 4); + TlsUtils.writeUint24(0, buf, 6); + TlsUtils.writeUint24(body.length, buf, 9); + hash.update(buf, 0, buf.length); + hash.update(body, 0, body.length); + } + return message; + } + + private void writeMessage(Message message) + throws IOException + { + + int sendLimit = recordLayer.getSendLimit(); + int fragmentLimit = sendLimit - 12; + + // TODO Support a higher minimum fragment size? + if (fragmentLimit < 1) + { + // TODO Should we be throwing an exception here? + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + int length = message.getBody().length; + + // NOTE: Must still send a fragment if body is empty + int fragment_offset = 0; + do + { + int fragment_length = Math.min(length - fragment_offset, fragmentLimit); + writeHandshakeFragment(message, fragment_offset, fragment_length); + fragment_offset += fragment_length; + } + while (fragment_offset < length); + } + + private void writeHandshakeFragment(Message message, int fragment_offset, int fragment_length) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8(message.getType(), buf); + TlsUtils.writeUint24(message.getBody().length, buf); + TlsUtils.writeUint16(message.getSeq(), buf); + TlsUtils.writeUint24(fragment_offset, buf); + TlsUtils.writeUint24(fragment_length, buf); + buf.write(message.getBody(), fragment_offset, fragment_length); + + byte[] fragment = buf.toByteArray(); + + recordLayer.send(fragment, 0, fragment.length); + } + + private static boolean checkAll(Hashtable inboundFlight) + { + Enumeration e = inboundFlight.elements(); + while (e.hasMoreElements()) + { + if (((DTLSReassembler)e.nextElement()).getBodyIfComplete() == null) + { + return false; + } + } + return true; + } + + private static void resetAll(Hashtable inboundFlight) + { + Enumeration e = inboundFlight.elements(); + while (e.hasMoreElements()) + { + ((DTLSReassembler)e.nextElement()).reset(); + } + } + + static class Message + { + + private final int message_seq; + private final short msg_type; + private final byte[] body; + + private Message(int message_seq, short msg_type, byte[] body) + { + this.message_seq = message_seq; + this.msg_type = msg_type; + this.body = body; + } + + public int getSeq() + { + return message_seq; + } + + public short getType() + { + return msg_type; + } + + public byte[] getBody() + { + return body; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReplayWindow.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReplayWindow.java new file mode 100644 index 00000000..0a5325be --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSReplayWindow.java @@ -0,0 +1,91 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4347 4.1.2.5 Anti-replay + * <p/> + * Support fast rejection of duplicate records by maintaining a sliding receive window + */ +class DTLSReplayWindow +{ + + private static final long VALID_SEQ_MASK = 0x0000FFFFFFFFFFFFL; + + private static final long WINDOW_SIZE = 64L; + + private long latestConfirmedSeq = -1; + private long bitmap = 0; + + /** + * Check whether a received record with the given sequence number should be rejected as a duplicate. + * + * @param seq the 48-bit DTLSPlainText.sequence_number field of a received record. + * @return true if the record should be discarded without further processing. + */ + boolean shouldDiscard(long seq) + { + if ((seq & VALID_SEQ_MASK) != seq) + { + return true; + } + + if (seq <= latestConfirmedSeq) + { + long diff = latestConfirmedSeq - seq; + if (diff >= WINDOW_SIZE) + { + return true; + } + if ((bitmap & (1L << diff)) != 0) + { + return true; + } + } + + return false; + } + + /** + * Report that a received record with the given sequence number passed authentication checks. + * + * @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record. + */ + void reportAuthenticated(long seq) + { + if ((seq & VALID_SEQ_MASK) != seq) + { + throw new IllegalArgumentException("'seq' out of range"); + } + + if (seq <= latestConfirmedSeq) + { + long diff = latestConfirmedSeq - seq; + if (diff < WINDOW_SIZE) + { + bitmap |= (1L << diff); + } + } + else + { + long diff = seq - latestConfirmedSeq; + if (diff >= WINDOW_SIZE) + { + bitmap = 1; + } + else + { + bitmap <<= (int)diff; // for earlier JDKs + bitmap |= 1; + } + latestConfirmedSeq = seq; + } + } + + /** + * When a new epoch begins, sequence numbers begin again at 0 + */ + void reset() + { + latestConfirmedSeq = -1; + bitmap = 0; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java new file mode 100644 index 00000000..3a100d1c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java @@ -0,0 +1,631 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.bouncycastle.util.Arrays; + +public class DTLSServerProtocol + extends DTLSProtocol +{ + + protected boolean verifyRequests = true; + + public DTLSServerProtocol(SecureRandom secureRandom) + { + super(secureRandom); + } + + public boolean getVerifyRequests() + { + return verifyRequests; + } + + public void setVerifyRequests(boolean verifyRequests) + { + this.verifyRequests = verifyRequests; + } + + public DTLSTransport accept(TlsServer server, DatagramTransport transport) + throws IOException + { + + if (server == null) + { + throw new IllegalArgumentException("'server' cannot be null"); + } + if (transport == null) + { + throw new IllegalArgumentException("'transport' cannot be null"); + } + + SecurityParameters securityParameters = new SecurityParameters(); + securityParameters.entity = ConnectionEnd.server; + securityParameters.serverRandom = TlsProtocol.createRandomBlock(secureRandom); + + ServerHandshakeState state = new ServerHandshakeState(); + state.server = server; + state.serverContext = new TlsServerContextImpl(secureRandom, securityParameters); + server.init(state.serverContext); + + DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.serverContext, server, ContentType.handshake); + + // TODO Need to handle sending of HelloVerifyRequest without entering a full connection + + try + { + return serverHandshake(state, recordLayer); + } + catch (TlsFatalAlert fatalAlert) + { + recordLayer.fail(fatalAlert.getAlertDescription()); + throw fatalAlert; + } + catch (IOException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw e; + } + catch (RuntimeException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public DTLSTransport serverHandshake(ServerHandshakeState state, DTLSRecordLayer recordLayer) + throws IOException + { + + SecurityParameters securityParameters = state.serverContext.getSecurityParameters(); + DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.serverContext, recordLayer); + + DTLSReliableHandshake.Message clientMessage = handshake.receiveMessage(); + + { + // NOTE: After receiving a record from the client, we discover the record layer version + ProtocolVersion client_version = recordLayer.getDiscoveredPeerVersion(); + // TODO Read RFCs for guidance on the expected record layer version number + state.serverContext.setClientVersion(client_version); + } + + if (clientMessage.getType() == HandshakeType.client_hello) + { + processClientHello(state, clientMessage.getBody()); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + byte[] serverHelloBody = generateServerHello(state); + handshake.sendMessage(HandshakeType.server_hello, serverHelloBody); + + // TODO This block could really be done before actually sending the hello + { + securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.selectedCipherSuite); + securityParameters.compressionAlgorithm = state.selectedCompressionMethod; + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length + * has a verify_data_length equal to 12. This includes all existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + handshake.notifyHelloComplete(); + } + + Vector serverSupplementalData = state.server.getServerSupplementalData(); + if (serverSupplementalData != null) + { + byte[] supplementalDataBody = generateSupplementalData(serverSupplementalData); + handshake.sendMessage(HandshakeType.supplemental_data, supplementalDataBody); + } + + state.keyExchange = state.server.getKeyExchange(); + state.keyExchange.init(state.serverContext); + + state.serverCredentials = state.server.getCredentials(); + if (state.serverCredentials == null) + { + state.keyExchange.skipServerCredentials(); + } + else + { + state.keyExchange.processServerCredentials(state.serverCredentials); + + byte[] certificateBody = generateCertificate(state.serverCredentials.getCertificate()); + handshake.sendMessage(HandshakeType.certificate, certificateBody); + } + + byte[] serverKeyExchange = state.keyExchange.generateServerKeyExchange(); + if (serverKeyExchange != null) + { + handshake.sendMessage(HandshakeType.server_key_exchange, serverKeyExchange); + } + + if (state.serverCredentials != null) + { + state.certificateRequest = state.server.getCertificateRequest(); + if (state.certificateRequest != null) + { + state.keyExchange.validateCertificateRequest(state.certificateRequest); + + byte[] certificateRequestBody = generateCertificateRequest(state, state.certificateRequest); + handshake.sendMessage(HandshakeType.certificate_request, certificateRequestBody); + } + } + + handshake.sendMessage(HandshakeType.server_hello_done, TlsUtils.EMPTY_BYTES); + + clientMessage = handshake.receiveMessage(); + + if (clientMessage.getType() == HandshakeType.supplemental_data) + { + processClientSupplementalData(state, clientMessage.getBody()); + clientMessage = handshake.receiveMessage(); + } + else + { + state.server.processClientSupplementalData(null); + } + + if (state.certificateRequest == null) + { + state.keyExchange.skipClientCredentials(); + } + else + { + if (clientMessage.getType() == HandshakeType.certificate) + { + processClientCertificate(state, clientMessage.getBody()); + clientMessage = handshake.receiveMessage(); + } + else + { + ProtocolVersion equivalentTLSVersion = state.serverContext.getServerVersion().getEquivalentTLSVersion(); + + if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(equivalentTLSVersion)) + { + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + notifyClientCertificate(state, Certificate.EMPTY_CHAIN); + } + } + + if (clientMessage.getType() == HandshakeType.client_key_exchange) + { + processClientKeyExchange(state, clientMessage.getBody()); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + recordLayer.initPendingEpoch(state.server.getCipher()); + + /* + * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing + * capability (i.e., all certificates except those containing fixed Diffie-Hellman + * parameters). + */ + if (expectCertificateVerifyMessage(state)) + { + byte[] certificateVerifyHash = handshake.getCurrentHash(); + clientMessage = handshake.receiveMessage(); + + if (clientMessage.getType() == HandshakeType.certificate_verify) + { + processCertificateVerify(state, clientMessage.getBody(), certificateVerifyHash); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + // NOTE: Calculated exclusive of the actual Finished message from the client + byte[] clientFinishedHash = handshake.getCurrentHash(); + clientMessage = handshake.receiveMessage(); + + if (clientMessage.getType() == HandshakeType.finished) + { + byte[] expectedClientVerifyData = TlsUtils.calculateVerifyData(state.serverContext, "client finished", + clientFinishedHash); + processFinished(clientMessage.getBody(), expectedClientVerifyData); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + if (state.expectSessionTicket) + { + NewSessionTicket newSessionTicket = state.server.getNewSessionTicket(); + byte[] newSessionTicketBody = generateNewSessionTicket(state, newSessionTicket); + handshake.sendMessage(HandshakeType.session_ticket, newSessionTicketBody); + } + + // NOTE: Calculated exclusive of the Finished message itself + byte[] serverVerifyData = TlsUtils.calculateVerifyData(state.serverContext, "server finished", + handshake.getCurrentHash()); + handshake.sendMessage(HandshakeType.finished, serverVerifyData); + + handshake.finish(); + + state.server.notifyHandshakeComplete(); + + return new DTLSTransport(recordLayer); + } + + protected byte[] generateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + certificateRequest.encode(buf); + return buf.toByteArray(); + } + + protected byte[] generateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + newSessionTicket.encode(buf); + return buf.toByteArray(); + } + + protected byte[] generateServerHello(ServerHandshakeState state) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + ProtocolVersion server_version = state.server.getServerVersion(); + if (!server_version.isEqualOrEarlierVersionOf(state.serverContext.getClientVersion())) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + // TODO Read RFCs for guidance on the expected record layer version number + // recordStream.setReadVersion(server_version); + // recordStream.setWriteVersion(server_version); + // recordStream.setRestrictReadVersion(true); + state.serverContext.setServerVersion(server_version); + + TlsUtils.writeVersion(state.serverContext.getServerVersion(), buf); + + buf.write(state.serverContext.getSecurityParameters().serverRandom); + + /* + * The server may return an empty session_id to indicate that the session will not be cached + * and therefore cannot be resumed. + */ + TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + + state.selectedCipherSuite = state.server.getSelectedCipherSuite(); + if (!TlsProtocol.arrayContains(state.offeredCipherSuites, state.selectedCipherSuite) + || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.internal_error); + + state.selectedCompressionMethod = state.server.getSelectedCompressionMethod(); + if (!TlsProtocol.arrayContains(state.offeredCompressionMethods, state.selectedCompressionMethod)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + TlsUtils.writeUint16(state.selectedCipherSuite, buf); + TlsUtils.writeUint8(state.selectedCompressionMethod, buf); + + state.serverExtensions = state.server.getServerExtensions(); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + if (state.secure_renegotiation) + { + + boolean noRenegExt = state.serverExtensions == null + || !state.serverExtensions.containsKey(TlsProtocol.EXT_RenegotiationInfo); + + if (noRenegExt) + { + /* + * Note that sending a "renegotiation_info" extension in response to a ClientHello + * containing only the SCSV is an explicit exception to the prohibition in RFC 5246, + * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed + * because the client is signaling its willingness to receive the extension via the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + if (state.serverExtensions == null) + { + state.serverExtensions = new Hashtable(); + } + + /* + * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty + * "renegotiation_info" extension in the ServerHello message. + */ + state.serverExtensions.put(TlsProtocol.EXT_RenegotiationInfo, + TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES)); + } + } + + if (state.serverExtensions != null) + { + state.expectSessionTicket = state.serverExtensions.containsKey(TlsProtocol.EXT_SessionTicket); + TlsProtocol.writeExtensions(buf, state.serverExtensions); + } + + return buf.toByteArray(); + } + + protected void notifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate) + throws IOException + { + + if (state.certificateRequest == null) + { + throw new IllegalStateException(); + } + + if (state.clientCertificate != null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + state.clientCertificate = clientCertificate; + + if (clientCertificate.isEmpty()) + { + state.keyExchange.skipClientCredentials(); + } + else + { + + /* + * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request + * message was non-empty, one of the certificates in the certificate chain SHOULD be + * issued by one of the listed CAs. + */ + + state.clientCertificateType = TlsUtils.getClientCertificateType(clientCertificate, + state.serverCredentials.getCertificate()); + + state.keyExchange.processClientCertificate(clientCertificate); + } + + /* + * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its + * discretion either continue the handshake without client authentication, or respond with a + * fatal handshake_failure alert. Also, if some aspect of the certificate chain was + * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its + * discretion either continue the handshake (considering the client unauthenticated) or send + * a fatal alert. + */ + state.server.notifyClientCertificate(clientCertificate); + } + + protected void processClientCertificate(ServerHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + Certificate clientCertificate = Certificate.parse(buf); + + TlsProtocol.assertEmpty(buf); + + notifyClientCertificate(state, clientCertificate); + } + + protected void processCertificateVerify(ServerHandshakeState state, byte[] body, byte[] certificateVerifyHash) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + byte[] clientCertificateSignature = TlsUtils.readOpaque16(buf); + + TlsProtocol.assertEmpty(buf); + + // Verify the CertificateVerify message contains a correct signature. + try + { + TlsSigner tlsSigner = TlsUtils.createTlsSigner(state.clientCertificateType); + tlsSigner.init(state.serverContext); + + org.bouncycastle.asn1.x509.Certificate x509Cert = state.clientCertificate.getCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo); + + tlsSigner.verifyRawSignature(clientCertificateSignature, publicKey, certificateVerifyHash); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + } + + protected void processClientHello(ServerHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + // TODO Read RFCs for guidance on the expected record layer version number + ProtocolVersion client_version = TlsUtils.readVersion(buf); + if (!client_version.isDTLS()) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + /* + * Read the client random + */ + byte[] client_random = TlsUtils.readFully(32, buf); + + byte[] sessionID = TlsUtils.readOpaque8(buf); + if (sessionID.length > 32) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347 + byte[] cookie = TlsUtils.readOpaque8(buf); + + int cipher_suites_length = TlsUtils.readUint16(buf); + if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + /* + * NOTE: "If the session_id field is not empty (implying a session resumption request) this + * vector must include at least the cipher_suite from that session." + */ + state.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf); + + int compression_methods_length = TlsUtils.readUint8(buf); + if (compression_methods_length < 1) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + state.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf); + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + state.clientExtensions = TlsProtocol.readExtensions(buf); + + state.serverContext.setClientVersion(client_version); + + state.server.notifyClientVersion(client_version); + + state.serverContext.getSecurityParameters().clientRandom = client_random; + + state.server.notifyOfferedCipherSuites(state.offeredCipherSuites); + state.server.notifyOfferedCompressionMethods(state.offeredCompressionMethods); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + + /* + * When a ClientHello is received, the server MUST check if it includes the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag + * to TRUE. + */ + if (TlsProtocol.arrayContains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + { + state.secure_renegotiation = true; + } + + /* + * The server MUST check if the "renegotiation_info" extension is included in the + * ClientHello. + */ + if (state.clientExtensions != null) + { + byte[] renegExtValue = (byte[])state.clientExtensions.get(TlsProtocol.EXT_RenegotiationInfo); + if (renegExtValue != null) + { + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + state.secure_renegotiation = true; + + if (!Arrays.constantTimeAreEqual(renegExtValue, + TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + } + } + + state.server.notifySecureRenegotiation(state.secure_renegotiation); + + if (state.clientExtensions != null) + { + state.server.processClientExtensions(state.clientExtensions); + } + } + + protected void processClientKeyExchange(ServerHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + state.keyExchange.processClientKeyExchange(buf); + + TlsProtocol.assertEmpty(buf); + + TlsProtocol.establishMasterSecret(state.serverContext, state.keyExchange); + } + + protected void processClientSupplementalData(ServerHandshakeState state, byte[] body) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(body); + Vector clientSupplementalData = TlsProtocol.readSupplementalDataMessage(buf); + state.server.processClientSupplementalData(clientSupplementalData); + } + + protected boolean expectCertificateVerifyMessage(ServerHandshakeState state) + { + return state.clientCertificateType >= 0 && TlsUtils.hasSigningCapability(state.clientCertificateType); + } + + protected static class ServerHandshakeState + { + TlsServer server = null; + TlsServerContextImpl serverContext = null; + int[] offeredCipherSuites; + short[] offeredCompressionMethods; + Hashtable clientExtensions; + int selectedCipherSuite = -1; + short selectedCompressionMethod = -1; + boolean secure_renegotiation = false; + boolean expectSessionTicket = false; + Hashtable serverExtensions = null; + TlsKeyExchange keyExchange = null; + TlsCredentials serverCredentials = null; + CertificateRequest certificateRequest = null; + short clientCertificateType = -1; + Certificate clientCertificate = null; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DTLSTransport.java b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSTransport.java new file mode 100644 index 00000000..a67d7bd6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DTLSTransport.java @@ -0,0 +1,81 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public class DTLSTransport + implements DatagramTransport +{ + + private final DTLSRecordLayer recordLayer; + + DTLSTransport(DTLSRecordLayer recordLayer) + { + this.recordLayer = recordLayer; + } + + public int getReceiveLimit() + throws IOException + { + return recordLayer.getReceiveLimit(); + } + + public int getSendLimit() + throws IOException + { + return recordLayer.getSendLimit(); + } + + public int receive(byte[] buf, int off, int len, int waitMillis) + throws IOException + { + try + { + return recordLayer.receive(buf, off, len, waitMillis); + } + catch (TlsFatalAlert fatalAlert) + { + recordLayer.fail(fatalAlert.getAlertDescription()); + throw fatalAlert; + } + catch (IOException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw e; + } + catch (RuntimeException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public void send(byte[] buf, int off, int len) + throws IOException + { + try + { + recordLayer.send(buf, off, len); + } + catch (TlsFatalAlert fatalAlert) + { + recordLayer.fail(fatalAlert.getAlertDescription()); + throw fatalAlert; + } + catch (IOException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw e; + } + catch (RuntimeException e) + { + recordLayer.fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public void close() + throws IOException + { + recordLayer.close(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DatagramTransport.java b/core/src/main/java/org/bouncycastle/crypto/tls/DatagramTransport.java new file mode 100644 index 00000000..df63b185 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DatagramTransport.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public interface DatagramTransport +{ + + int getReceiveLimit() + throws IOException; + + int getSendLimit() + throws IOException; + + int receive(byte[] buf, int off, int len, int waitMillis) + throws IOException; + + void send(byte[] buf, int off, int len) + throws IOException; + + void close() + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java new file mode 100644 index 00000000..98efc4f0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java @@ -0,0 +1,79 @@ +package org.bouncycastle.crypto.tls; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.BasicAgreement; +import org.bouncycastle.crypto.agreement.DHBasicAgreement; +import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.util.BigIntegers; + +public class DefaultTlsAgreementCredentials + implements TlsAgreementCredentials +{ + + protected Certificate certificate; + protected AsymmetricKeyParameter privateKey; + + protected BasicAgreement basicAgreement; + protected boolean truncateAgreement; + + public DefaultTlsAgreementCredentials(Certificate certificate, AsymmetricKeyParameter privateKey) + { + if (certificate == null) + { + throw new IllegalArgumentException("'certificate' cannot be null"); + } + if (certificate.isEmpty()) + { + throw new IllegalArgumentException("'certificate' cannot be empty"); + } + if (privateKey == null) + { + throw new IllegalArgumentException("'privateKey' cannot be null"); + } + if (!privateKey.isPrivate()) + { + throw new IllegalArgumentException("'privateKey' must be private"); + } + + if (privateKey instanceof DHPrivateKeyParameters) + { + basicAgreement = new DHBasicAgreement(); + truncateAgreement = true; + } + else if (privateKey instanceof ECPrivateKeyParameters) + { + basicAgreement = new ECDHBasicAgreement(); + truncateAgreement = false; + } + else + { + throw new IllegalArgumentException("'privateKey' type not supported: " + + privateKey.getClass().getName()); + } + + this.certificate = certificate; + this.privateKey = privateKey; + } + + public Certificate getCertificate() + { + return certificate; + } + + public byte[] generateAgreement(AsymmetricKeyParameter peerPublicKey) + { + basicAgreement.init(privateKey); + BigInteger agreementValue = basicAgreement.calculateAgreement(peerPublicKey); + + if (truncateAgreement) + { + return BigIntegers.asUnsignedByteArray(agreementValue); + } + + return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java new file mode 100644 index 00000000..82b37d90 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java @@ -0,0 +1,163 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.digests.MD5Digest; +import org.bouncycastle.crypto.digests.SHA1Digest; +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.CamelliaEngine; +import org.bouncycastle.crypto.engines.DESedeEngine; +import org.bouncycastle.crypto.engines.RC4Engine; +import org.bouncycastle.crypto.engines.SEEDEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; + +public class DefaultTlsCipherFactory + extends AbstractTlsCipherFactory +{ + + public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) + throws IOException + { + + switch (encryptionAlgorithm) + { + case EncryptionAlgorithm._3DES_EDE_CBC: + return createDESedeCipher(context, macAlgorithm); + case EncryptionAlgorithm.AES_128_CBC: + return createAESCipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.AES_128_GCM: + // NOTE: Ignores macAlgorithm + return createCipher_AES_GCM(context, 16, 16); + case EncryptionAlgorithm.AES_256_CBC: + return createAESCipher(context, 32, macAlgorithm); + case EncryptionAlgorithm.AES_256_GCM: + // NOTE: Ignores macAlgorithm + return createCipher_AES_GCM(context, 32, 16); + case EncryptionAlgorithm.CAMELLIA_128_CBC: + return createCamelliaCipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.CAMELLIA_256_CBC: + return createCamelliaCipher(context, 32, macAlgorithm); + case EncryptionAlgorithm.NULL: + return createNullCipher(context, macAlgorithm); + case EncryptionAlgorithm.RC4_128: + return createRC4Cipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.SEED_CBC: + return createSEEDCipher(context, macAlgorithm); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected TlsBlockCipher createAESCipher(TlsContext context, int cipherKeySize, int macAlgorithm) + throws IOException + { + return new TlsBlockCipher(context, createAESBlockCipher(), createAESBlockCipher(), + createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize); + } + + protected TlsAEADCipher createCipher_AES_GCM(TlsContext context, int cipherKeySize, int macSize) + throws IOException + { + return new TlsAEADCipher(context, createAEADBlockCipher_AES_GCM(), + createAEADBlockCipher_AES_GCM(), cipherKeySize, macSize); + } + + protected TlsBlockCipher createCamelliaCipher(TlsContext context, int cipherKeySize, + int macAlgorithm) + throws IOException + { + return new TlsBlockCipher(context, createCamelliaBlockCipher(), + createCamelliaBlockCipher(), createHMACDigest(macAlgorithm), + createHMACDigest(macAlgorithm), cipherKeySize); + } + + protected TlsNullCipher createNullCipher(TlsContext context, int macAlgorithm) + throws IOException + { + return new TlsNullCipher(context, createHMACDigest(macAlgorithm), + createHMACDigest(macAlgorithm)); + } + + protected TlsStreamCipher createRC4Cipher(TlsContext context, int cipherKeySize, + int macAlgorithm) + throws IOException + { + return new TlsStreamCipher(context, createRC4StreamCipher(), createRC4StreamCipher(), + createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize); + } + + protected TlsBlockCipher createDESedeCipher(TlsContext context, int macAlgorithm) + throws IOException + { + return new TlsBlockCipher(context, createDESedeBlockCipher(), createDESedeBlockCipher(), + createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 24); + } + + protected TlsBlockCipher createSEEDCipher(TlsContext context, int macAlgorithm) + throws IOException + { + return new TlsBlockCipher(context, createSEEDBlockCipher(), createSEEDBlockCipher(), + createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 16); + } + + protected StreamCipher createRC4StreamCipher() + { + return new RC4Engine(); + } + + protected BlockCipher createAESBlockCipher() + { + return new CBCBlockCipher(new AESFastEngine()); + } + + protected AEADBlockCipher createAEADBlockCipher_AES_GCM() + { + // TODO Consider allowing custom configuration of multiplier + return new GCMBlockCipher(new AESFastEngine()); + } + + protected BlockCipher createCamelliaBlockCipher() + { + return new CBCBlockCipher(new CamelliaEngine()); + } + + protected BlockCipher createDESedeBlockCipher() + { + return new CBCBlockCipher(new DESedeEngine()); + } + + protected BlockCipher createSEEDBlockCipher() + { + return new CBCBlockCipher(new SEEDEngine()); + } + + protected Digest createHMACDigest(int macAlgorithm) + throws IOException + { + switch (macAlgorithm) + { + case MACAlgorithm._null: + return null; + case MACAlgorithm.hmac_md5: + return new MD5Digest(); + case MACAlgorithm.hmac_sha1: + return new SHA1Digest(); + case MACAlgorithm.hmac_sha256: + return new SHA256Digest(); + case MACAlgorithm.hmac_sha384: + return new SHA384Digest(); + case MACAlgorithm.hmac_sha512: + return new SHA512Digest(); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java new file mode 100644 index 00000000..4f9fe27f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java @@ -0,0 +1,380 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.util.Hashtable; + +public abstract class DefaultTlsClient + extends AbstractTlsClient +{ + + protected int[] namedCurves; + protected short[] clientECPointFormats, serverECPointFormats; + + public DefaultTlsClient() + { + super(); + } + + public DefaultTlsClient(TlsCipherFactory cipherFactory) + { + super(cipherFactory); + } + + public int[] getCipherSuites() + { + return new int[]{CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,}; + } + + public Hashtable getClientExtensions() + throws IOException + { + + Hashtable clientExtensions = super.getClientExtensions(); + + if (TlsECCUtils.containsECCCipherSuites(getCipherSuites())) + { + /* + * RFC 4492 5.1. A client that proposes ECC cipher suites in its ClientHello message + * appends these extensions (along with any others), enumerating the curves it supports + * and the point formats it can parse. Clients SHOULD send both the Supported Elliptic + * Curves Extension and the Supported Point Formats Extension. + */ + /* + * TODO Could just add all the curves since we support them all, but users may not want + * to use unnecessarily large fields. Need configuration options. + */ + this.namedCurves = new int[]{NamedCurve.secp256r1, NamedCurve.sect233r1, NamedCurve.secp224r1, + NamedCurve.sect193r1, NamedCurve.secp192r1, NamedCurve.arbitrary_explicit_char2_curves, + NamedCurve.arbitrary_explicit_prime_curves}; + this.clientECPointFormats = new short[]{ECPointFormat.ansiX962_compressed_char2, + ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed}; + + if (clientExtensions == null) + { + clientExtensions = new Hashtable(); + } + + TlsECCUtils.addSupportedEllipticCurvesExtension(clientExtensions, namedCurves); + TlsECCUtils.addSupportedPointFormatsExtension(clientExtensions, clientECPointFormats); + } + + return clientExtensions; + } + + public void processServerExtensions(Hashtable serverExtensions) + throws IOException + { + + super.processServerExtensions(serverExtensions); + + if (serverExtensions != null) + { + int[] namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(serverExtensions); + if (namedCurves != null) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + this.serverECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(serverExtensions); + if (this.serverECPointFormats != null && !TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public TlsKeyExchange getKeyExchange() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + return createDHKeyExchange(KeyExchangeAlgorithm.DH_DSS); + + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + return createDHKeyExchange(KeyExchangeAlgorithm.DH_RSA); + + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_DSS); + + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_ECDSA); + + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_RSA); + + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA); + + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA); + + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return createRSAKeyExchange(); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public TlsCipher getCipher() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null); + + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null); + + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_md5); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SEED_CBC, MACAlgorithm.hmac_sha1); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected TlsKeyExchange createDHKeyExchange(int keyExchange) + { + return new TlsDHKeyExchange(keyExchange, supportedSignatureAlgorithms, null); + } + + protected TlsKeyExchange createDHEKeyExchange(int keyExchange) + { + return new TlsDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, null); + } + + protected TlsKeyExchange createECDHKeyExchange(int keyExchange) + { + return new TlsECDHKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, + serverECPointFormats); + } + + protected TlsKeyExchange createECDHEKeyExchange(int keyExchange) + { + return new TlsECDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, + serverECPointFormats); + } + + protected TlsKeyExchange createRSAKeyExchange() + { + return new TlsRSAKeyExchange(supportedSignatureAlgorithms); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java new file mode 100644 index 00000000..a338c388 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java @@ -0,0 +1,75 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.engines.RSABlindedEngine; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +public class DefaultTlsEncryptionCredentials + implements TlsEncryptionCredentials +{ + protected TlsContext context; + protected Certificate certificate; + protected AsymmetricKeyParameter privateKey; + + public DefaultTlsEncryptionCredentials(TlsContext context, Certificate certificate, + AsymmetricKeyParameter privateKey) + { + if (certificate == null) + { + throw new IllegalArgumentException("'certificate' cannot be null"); + } + if (certificate.isEmpty()) + { + throw new IllegalArgumentException("'certificate' cannot be empty"); + } + if (privateKey == null) + { + throw new IllegalArgumentException("'privateKey' cannot be null"); + } + if (!privateKey.isPrivate()) + { + throw new IllegalArgumentException("'privateKey' must be private"); + } + + if (privateKey instanceof RSAKeyParameters) + { + } + else + { + throw new IllegalArgumentException("'privateKey' type not supported: " + + privateKey.getClass().getName()); + } + + this.context = context; + this.certificate = certificate; + this.privateKey = privateKey; + } + + public Certificate getCertificate() + { + return certificate; + } + + public byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret) + throws IOException + { + + PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine()); + encoding.init(false, new ParametersWithRandom(this.privateKey, context.getSecureRandom())); + + try + { + return encoding.processBlock(encryptedPreMasterSecret, 0, + encryptedPreMasterSecret.length); + } + catch (InvalidCipherTextException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java new file mode 100644 index 00000000..246b87ec --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java @@ -0,0 +1,384 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.agreement.DHStandardGroups; +import org.bouncycastle.crypto.params.DHParameters; + +public abstract class DefaultTlsServer + extends AbstractTlsServer +{ + + public DefaultTlsServer() + { + super(); + } + + public DefaultTlsServer(TlsCipherFactory cipherFactory) + { + super(cipherFactory); + } + + protected TlsEncryptionCredentials getRSAEncryptionCredentials() + throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected TlsSignerCredentials getRSASignerCredentials() + throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected DHParameters getDHParameters() + { + return DHStandardGroups.rfc5114_1024_160; + } + + protected int[] getCipherSuites() + { + return new int[]{CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,}; + } + + public TlsCredentials getCredentials() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return getRSAEncryptionCredentials(); + + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return getRSASignerCredentials(); + + default: + /* + * Note: internal error here; selected a key exchange we don't implement! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public TlsKeyExchange getKeyExchange() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + return createDHKeyExchange(KeyExchangeAlgorithm.DH_DSS); + + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + return createDHKeyExchange(KeyExchangeAlgorithm.DH_RSA); + + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_DSS); + + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_ECDSA); + + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_RSA); + + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA); + + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA); + + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return createRSAKeyExchange(); + + default: + /* + * Note: internal error here; selected a key exchange we don't implement! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public TlsCipher getCipher() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null); + + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null); + + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_md5); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SEED_CBC, MACAlgorithm.hmac_sha1); + + default: + /* + * Note: internal error here; selected a cipher suite we don't implement! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected TlsKeyExchange createDHKeyExchange(int keyExchange) + { + return new TlsDHKeyExchange(keyExchange, supportedSignatureAlgorithms, getDHParameters()); + } + + protected TlsKeyExchange createDHEKeyExchange(int keyExchange) + { + return new TlsDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, getDHParameters()); + } + + protected TlsKeyExchange createECDHKeyExchange(int keyExchange) + { + return new TlsECDHKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, + serverECPointFormats); + } + + protected TlsKeyExchange createECDHEKeyExchange(int keyExchange) + { + return new TlsECDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, + serverECPointFormats); + } + + protected TlsKeyExchange createRSAKeyExchange() + { + return new TlsRSAKeyExchange(supportedSignatureAlgorithms); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java new file mode 100755 index 00000000..b7752501 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java @@ -0,0 +1,81 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +public class DefaultTlsSignerCredentials + implements TlsSignerCredentials +{ + protected TlsContext context; + protected Certificate certificate; + protected AsymmetricKeyParameter privateKey; + + protected TlsSigner signer; + + public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey) + { + + if (certificate == null) + { + throw new IllegalArgumentException("'certificate' cannot be null"); + } + if (certificate.isEmpty()) + { + throw new IllegalArgumentException("'certificate' cannot be empty"); + } + if (privateKey == null) + { + throw new IllegalArgumentException("'privateKey' cannot be null"); + } + if (!privateKey.isPrivate()) + { + throw new IllegalArgumentException("'privateKey' must be private"); + } + + if (privateKey instanceof RSAKeyParameters) + { + this.signer = new TlsRSASigner(); + } + else if (privateKey instanceof DSAPrivateKeyParameters) + { + this.signer = new TlsDSSSigner(); + } + else if (privateKey instanceof ECPrivateKeyParameters) + { + this.signer = new TlsECDSASigner(); + } + else + { + throw new IllegalArgumentException("'privateKey' type not supported: " + privateKey.getClass().getName()); + } + + this.signer.init(context); + + this.context = context; + this.certificate = certificate; + this.privateKey = privateKey; + } + + public Certificate getCertificate() + { + return certificate; + } + + public byte[] generateCertificateSignature(byte[] md5andsha1) + throws IOException + { + try + { + return signer.generateRawSignature(privateKey, md5andsha1); + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java b/core/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java new file mode 100644 index 00000000..e8c76e65 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java @@ -0,0 +1,128 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; + +import org.bouncycastle.crypto.Digest; + +/** + * Buffers input until the hash algorithm is determined. + */ +class DeferredHash + implements TlsHandshakeHash +{ + + protected TlsContext context; + + private ByteArrayOutputStream buf = new ByteArrayOutputStream(); + private int prfAlgorithm = -1; + private Digest hash = null; + + DeferredHash() + { + this.buf = new ByteArrayOutputStream(); + this.hash = null; + } + + private DeferredHash(Digest hash) + { + this.buf = null; + this.hash = hash; + } + + public void init(TlsContext context) + { + this.context = context; + } + + public TlsHandshakeHash commit() + { + + int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm(); + + Digest prfHash = TlsUtils.createPRFHash(prfAlgorithm); + + byte[] data = buf.toByteArray(); + prfHash.update(data, 0, data.length); + + if (prfHash instanceof TlsHandshakeHash) + { + TlsHandshakeHash tlsPRFHash = (TlsHandshakeHash)prfHash; + tlsPRFHash.init(context); + return tlsPRFHash.commit(); + } + + this.prfAlgorithm = prfAlgorithm; + this.hash = prfHash; + this.buf = null; + + return this; + } + + public TlsHandshakeHash fork() + { + checkHash(); + return new DeferredHash(TlsUtils.clonePRFHash(prfAlgorithm, hash)); + } + + public String getAlgorithmName() + { + checkHash(); + return hash.getAlgorithmName(); + } + + public int getDigestSize() + { + checkHash(); + return hash.getDigestSize(); + } + + public void update(byte input) + { + if (hash == null) + { + buf.write(input); + } + else + { + hash.update(input); + } + } + + public void update(byte[] input, int inOff, int len) + { + if (hash == null) + { + buf.write(input, inOff, len); + } + else + { + hash.update(input, inOff, len); + } + } + + public int doFinal(byte[] output, int outOff) + { + checkHash(); + return hash.doFinal(output, outOff); + } + + public void reset() + { + if (hash == null) + { + buf.reset(); + } + else + { + hash.reset(); + } + } + + protected void checkHash() + { + if (hash == null) + { + throw new IllegalStateException("No hash algorithm has been set"); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/DigestAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/DigestAlgorithm.java new file mode 100644 index 00000000..41d54009 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/DigestAlgorithm.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + * + * @deprecated use MACAlgorithm constants instead + */ +public class DigestAlgorithm +{ + public static final int NULL = 0; + public static final int MD5 = 1; + public static final int SHA = 2; + + /* + * RFC 5246 + */ + public static final int SHA256 = 3; + public static final int SHA384 = 4; + public static final int SHA512 = 5; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ECBasisType.java b/core/src/main/java/org/bouncycastle/crypto/tls/ECBasisType.java new file mode 100644 index 00000000..57f0ad01 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ECBasisType.java @@ -0,0 +1,11 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4492 5.4. (Errata ID: 2389) + */ +public class ECBasisType +{ + + public static final short ec_basis_trinomial = 1; + public static final short ec_basis_pentanomial = 2; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ECCurveType.java b/core/src/main/java/org/bouncycastle/crypto/tls/ECCurveType.java new file mode 100644 index 00000000..0b6542fc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ECCurveType.java @@ -0,0 +1,28 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4492 5.4 + */ +public class ECCurveType +{ + /** + * Indicates the elliptic curve domain parameters are conveyed verbosely, and the + * underlying finite field is a prime field. + */ + public static final short explicit_prime = 1; + + /** + * Indicates the elliptic curve domain parameters are conveyed verbosely, and the + * underlying finite field is a characteristic-2 field. + */ + public static final short explicit_char2 = 2; + + /** + * Indicates that a named curve is used. This option SHOULD be used when applicable. + */ + public static final short named_curve = 3; + + /* + * Values 248 through 255 are reserved for private use. + */ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ECPointFormat.java b/core/src/main/java/org/bouncycastle/crypto/tls/ECPointFormat.java new file mode 100644 index 00000000..969d42ee --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ECPointFormat.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4492 5.1.2 + */ +public class ECPointFormat +{ + public static final short uncompressed = 0; + public static final short ansiX962_compressed_prime = 1; + public static final short ansiX962_compressed_char2 = 2; + + /* + * reserved (248..255) + */ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java new file mode 100644 index 00000000..f991e4ab --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java @@ -0,0 +1,43 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class EncryptionAlgorithm +{ + + public static final int NULL = 0; + public static final int RC4_40 = 1; + public static final int RC4_128 = 2; + public static final int RC2_CBC_40 = 3; + public static final int IDEA_CBC = 4; + public static final int DES40_CBC = 5; + public static final int DES_CBC = 6; + public static final int _3DES_EDE_CBC = 7; + + /* + * RFC 3268 + */ + public static final int AES_128_CBC = 8; + public static final int AES_256_CBC = 9; + + /* + * RFC 4132 + */ + public static final int CAMELLIA_128_CBC = 12; + public static final int CAMELLIA_256_CBC = 13; + + /* + * RFC 4162 + */ + public static final int SEED_CBC = 14; + + /* + * RFC 5289 + */ + public static final int AES_128_GCM = 10; + public static final int AES_256_GCM = 11; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ExporterLabel.java b/core/src/main/java/org/bouncycastle/crypto/tls/ExporterLabel.java new file mode 100644 index 00000000..902720ac --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ExporterLabel.java @@ -0,0 +1,31 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 5705 + */ +public class ExporterLabel +{ + /* + * RFC 5246 + */ + public static final String client_finished = "client finished"; + public static final String server_finished = "server finished"; + public static final String master_secret = "master secret"; + public static final String key_expansion = "key expansion"; + + /* + * RFC 5216 + */ + public static final String client_EAP_encryption = "client EAP encryption"; + + /* + * RFC 5281 + */ + public static final String ttls_keying_material = "ttls keying material"; + public static final String ttls_challenge = "ttls challenge"; + + /* + * RFC 5764 + */ + public static final String dtls_srtp = "EXTRACTOR-dtls_srtp"; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java b/core/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java new file mode 100644 index 00000000..0be64654 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java @@ -0,0 +1,50 @@ +package org.bouncycastle.crypto.tls; + +public class ExtensionType +{ + /* + * RFC 6066 1.1. + */ + public static final int server_name = 0; + public static final int max_fragment_length = 1; + public static final int client_certificate_url = 2; + public static final int trusted_ca_keys = 3; + public static final int truncated_hmac = 4; + public static final int status_request = 5; + + /* + * RFC 4681 + */ + public static final int user_mapping = 6; + + /* + * RFC 4492 5.1. + */ + public static final int elliptic_curves = 10; + public static final int ec_point_formats = 11; + + /* + * RFC 5054 2.8.1. + */ + public static final int srp = 12; + + /* + * RFC 5077 7. + */ + public static final int session_ticket = 35; + + /* + * RFC 5246 7.4.1.4. + */ + public static final int signature_algorithms = 13; + + /* + * RFC 5764 9. + */ + public static final int use_srtp = 14; + + /* + * RFC 5746 3.2. + */ + public static final int renegotiation_info = 0xff01; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java b/core/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java new file mode 100644 index 00000000..53b4520c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java @@ -0,0 +1,33 @@ +package org.bouncycastle.crypto.tls; + +public class HandshakeType +{ + /* + * RFC 2246 7.4 + */ + public static final short hello_request = 0; + public static final short client_hello = 1; + public static final short server_hello = 2; + public static final short certificate = 11; + public static final short server_key_exchange = 12; + public static final short certificate_request = 13; + public static final short server_hello_done = 14; + public static final short certificate_verify = 15; + public static final short client_key_exchange = 16; + public static final short finished = 20; + + /* + * (DTLS) RFC 4347 4.3.2 + */ + public static final short hello_verify_request = 3; + + /* + * RFC 4680 + */ + public static final short supplemental_data = 23; + + /* + * RFC 5077 + */ + public static final short session_ticket = 4; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java new file mode 100644 index 00000000..ac0a4c61 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java @@ -0,0 +1,16 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 5246 7.4.1.4.1 + */ +public class HashAlgorithm +{ + + public static final short none = 0; + public static final short md5 = 1; + public static final short sha1 = 2; + public static final short sha224 = 3; + public static final short sha256 = 4; + public static final short sha384 = 5; + public static final short sha512 = 6; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java new file mode 100644 index 00000000..c049bb70 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java @@ -0,0 +1,47 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class KeyExchangeAlgorithm +{ + public static final int NULL = 0; + public static final int RSA = 1; + public static final int RSA_EXPORT = 2; + public static final int DHE_DSS = 3; + public static final int DHE_DSS_EXPORT = 4; + public static final int DHE_RSA = 5; + public static final int DHE_RSA_EXPORT = 6; + public static final int DH_DSS = 7; + public static final int DH_DSS_EXPORT = 8; + public static final int DH_RSA = 9; + public static final int DH_RSA_EXPORT = 10; + public static final int DH_anon = 11; + public static final int DH_anon_EXPORT = 12; + + /* + * RFC 4279 + */ + public static final int PSK = 13; + public static final int DHE_PSK = 14; + public static final int RSA_PSK = 15; + + /* + * RFC 4429 + */ + public static final int ECDH_ECDSA = 16; + public static final int ECDHE_ECDSA = 17; + public static final int ECDH_RSA = 18; + public static final int ECDHE_RSA = 19; + public static final int ECDH_anon = 20; + + /* + * RFC 5054 + */ + public static final int SRP = 21; + public static final int SRP_DSS = 22; + public static final int SRP_RSA = 23; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/LegacyTlsAuthentication.java b/core/src/main/java/org/bouncycastle/crypto/tls/LegacyTlsAuthentication.java new file mode 100644 index 00000000..f638714e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/LegacyTlsAuthentication.java @@ -0,0 +1,28 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +/** + * A temporary class to wrap old CertificateVerifyer stuff for new TlsAuthentication + * + * @deprecated + */ +public class LegacyTlsAuthentication + extends ServerOnlyTlsAuthentication +{ + protected CertificateVerifyer verifyer; + + public LegacyTlsAuthentication(CertificateVerifyer verifyer) + { + this.verifyer = verifyer; + } + + public void notifyServerCertificate(Certificate serverCertificate) + throws IOException + { + if (!this.verifyer.isValid(serverCertificate.getCertificateList())) + { + throw new TlsFatalAlert(AlertDescription.user_canceled); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/LegacyTlsClient.java b/core/src/main/java/org/bouncycastle/crypto/tls/LegacyTlsClient.java new file mode 100644 index 00000000..33217ac4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/LegacyTlsClient.java @@ -0,0 +1,33 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +/** + * A temporary class to use LegacyTlsAuthentication + * + * @deprecated + */ +public class LegacyTlsClient + extends DefaultTlsClient +{ + /** + * @deprecated + */ + protected CertificateVerifyer verifyer; + + /** + * @deprecated + */ + public LegacyTlsClient(CertificateVerifyer verifyer) + { + super(); + + this.verifyer = verifyer; + } + + public TlsAuthentication getAuthentication() + throws IOException + { + return new LegacyTlsAuthentication(verifyer); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java new file mode 100644 index 00000000..40ef15c4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 2246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class MACAlgorithm +{ + + public static final int _null = 0; + public static final int md5 = 1; + public static final int sha = 2; + + /* + * RFC 5246 + */ + public static final int hmac_md5 = md5; + public static final int hmac_sha1 = sha; + public static final int hmac_sha256 = 3; + public static final int hmac_sha384 = 4; + public static final int hmac_sha512 = 5; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java b/core/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java new file mode 100644 index 00000000..690115cc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java @@ -0,0 +1,59 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4492 5.1.1 + * <p/> + * The named curves defined here are those specified in SEC 2 [13]. Note that many of these curves + * are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 through 0xFEFF are + * reserved for private use. Values 0xFF01 and 0xFF02 indicate that the client supports arbitrary + * prime and characteristic-2 curves, respectively (the curve parameters must be encoded explicitly + * in ECParameters). + */ +public class NamedCurve +{ + public static final int sect163k1 = 1; + public static final int sect163r1 = 2; + public static final int sect163r2 = 3; + public static final int sect193r1 = 4; + public static final int sect193r2 = 5; + public static final int sect233k1 = 6; + public static final int sect233r1 = 7; + public static final int sect239k1 = 8; + public static final int sect283k1 = 9; + public static final int sect283r1 = 10; + public static final int sect409k1 = 11; + public static final int sect409r1 = 12; + public static final int sect571k1 = 13; + public static final int sect571r1 = 14; + public static final int secp160k1 = 15; + public static final int secp160r1 = 16; + public static final int secp160r2 = 17; + public static final int secp192k1 = 18; + public static final int secp192r1 = 19; + public static final int secp224k1 = 20; + public static final int secp224r1 = 21; + public static final int secp256k1 = 22; + public static final int secp256r1 = 23; + public static final int secp384r1 = 24; + public static final int secp521r1 = 25; + + /* + * reserved (0xFE00..0xFEFF) + */ + + public static final int arbitrary_explicit_prime_curves = 0xFF01; + public static final int arbitrary_explicit_char2_curves = 0xFF02; + + public static boolean refersToASpecificNamedCurve(int namedCurve) + { + switch (namedCurve) + { + case arbitrary_explicit_prime_curves: + case arbitrary_explicit_char2_curves: + return false; + default: + return true; + } + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java b/core/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java new file mode 100644 index 00000000..f3d10222 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java @@ -0,0 +1,43 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class NewSessionTicket +{ + + protected long ticketLifetimeHint; + protected byte[] ticket; + + public NewSessionTicket(long ticketLifetimeHint, byte[] ticket) + { + this.ticketLifetimeHint = ticketLifetimeHint; + this.ticket = ticket; + } + + public long getTicketLifetimeHint() + { + return ticketLifetimeHint; + } + + public byte[] getTicket() + { + return ticket; + } + + public void encode(OutputStream output) + throws IOException + { + TlsUtils.writeUint32(ticketLifetimeHint, output); + TlsUtils.writeOpaque16(ticket, output); + } + + public static NewSessionTicket parse(InputStream input) + throws IOException + { + long ticketLifetimeHint = TlsUtils.readUint32(input); + byte[] ticket = TlsUtils.readOpaque16(input); + return new NewSessionTicket(ticketLifetimeHint, ticket); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/PRFAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/PRFAlgorithm.java new file mode 100644 index 00000000..81a3bffb --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/PRFAlgorithm.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 5246 + * <p/> + * Note that the values here are implementation-specific and arbitrary. It is recommended not to + * depend on the particular values (e.g. serialization). + */ +public class PRFAlgorithm +{ + + /* + * Placeholder to refer to the legacy TLS algorithm + */ + public static final int tls_prf_legacy = 0; + + public static final int tls_prf_sha256 = 1; + + /* + * Implied by RFC 5288 + */ + public static final int tls_prf_sha384 = 2; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java b/core/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java new file mode 100644 index 00000000..29750cb7 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java @@ -0,0 +1,114 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public abstract class PSKTlsClient + extends AbstractTlsClient +{ + protected TlsPSKIdentity pskIdentity; + + public PSKTlsClient(TlsPSKIdentity pskIdentity) + { + super(); + this.pskIdentity = pskIdentity; + } + + public PSKTlsClient(TlsCipherFactory cipherFactory, TlsPSKIdentity pskIdentity) + { + super(cipherFactory); + this.pskIdentity = pskIdentity; + } + + public int[] getCipherSuites() + { + return new int[]{CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA, + CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA, + CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_PSK_WITH_RC4_128_SHA,}; + } + + public TlsKeyExchange getKeyExchange() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + return createPSKKeyExchange(KeyExchangeAlgorithm.PSK); + + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + return createPSKKeyExchange(KeyExchangeAlgorithm.RSA_PSK); + + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + return createPSKKeyExchange(KeyExchangeAlgorithm.DHE_PSK); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public TlsCipher getCipher() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected TlsKeyExchange createPSKKeyExchange(int keyExchange) + { + return new TlsPSKKeyExchange(keyExchange, supportedSignatureAlgorithms, pskIdentity); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java b/core/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java new file mode 100644 index 00000000..c001e584 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java @@ -0,0 +1,126 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public final class ProtocolVersion +{ + + public static final ProtocolVersion SSLv3 = new ProtocolVersion(0x0300, "SSL 3.0"); + public static final ProtocolVersion TLSv10 = new ProtocolVersion(0x0301, "TLS 1.0"); + public static final ProtocolVersion TLSv11 = new ProtocolVersion(0x0302, "TLS 1.1"); + public static final ProtocolVersion TLSv12 = new ProtocolVersion(0x0303, "TLS 1.2"); + public static final ProtocolVersion DTLSv10 = new ProtocolVersion(0xFEFF, "DTLS 1.0"); + public static final ProtocolVersion DTLSv12 = new ProtocolVersion(0xFEFD, "DTLS 1.2"); + + private int version; + private String name; + + private ProtocolVersion(int v, String name) + { + this.version = v & 0xffff; + this.name = name; + } + + public int getFullVersion() + { + return version; + } + + public int getMajorVersion() + { + return version >> 8; + } + + public int getMinorVersion() + { + return version & 0xff; + } + + public boolean isDTLS() + { + return getMajorVersion() == 0xFE; + } + + public boolean isSSL() + { + return this == SSLv3; + } + + public ProtocolVersion getEquivalentTLSVersion() + { + if (!isDTLS()) + { + return this; + } + if (this == DTLSv10) + { + return TLSv11; + } + return TLSv12; + } + + public boolean isEqualOrEarlierVersionOf(ProtocolVersion version) + { + if (getMajorVersion() != version.getMajorVersion()) + { + return false; + } + int diffMinorVersion = version.getMinorVersion() - getMinorVersion(); + return isDTLS() ? diffMinorVersion <= 0 : diffMinorVersion >= 0; + } + + public boolean isLaterVersionOf(ProtocolVersion version) + { + if (getMajorVersion() != version.getMajorVersion()) + { + return false; + } + int diffMinorVersion = version.getMinorVersion() - getMinorVersion(); + return isDTLS() ? diffMinorVersion > 0 : diffMinorVersion < 0; + } + + public boolean equals(Object obj) + { + return this == obj; + } + + public int hashCode() + { + return version; + } + + public static ProtocolVersion get(int major, int minor) + throws IOException + { + switch (major) + { + case 0x03: + switch (minor) + { + case 0x00: + return SSLv3; + case 0x01: + return TLSv10; + case 0x02: + return TLSv11; + case 0x03: + return TLSv12; + } + case 0xFE: + switch (minor) + { + case 0xFF: + return DTLSv10; + case 0xFD: + return DTLSv12; + } + } + + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + public String toString() + { + return name; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java b/core/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java new file mode 100644 index 00000000..3a31c201 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java @@ -0,0 +1,356 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bouncycastle.crypto.Digest; + +/** + * An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3. + */ +class RecordStream +{ + + private static int PLAINTEXT_LIMIT = (1 << 14); + private static int COMPRESSED_LIMIT = PLAINTEXT_LIMIT + 1024; + private static int CIPHERTEXT_LIMIT = COMPRESSED_LIMIT + 1024; + + private TlsProtocol handler; + private InputStream input; + private OutputStream output; + private TlsCompression pendingCompression = null, readCompression = null, writeCompression = null; + private TlsCipher pendingCipher = null, readCipher = null, writeCipher = null; + private long readSeqNo = 0, writeSeqNo = 0; + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + private TlsContext context = null; + private TlsHandshakeHash hash = null; + + private ProtocolVersion readVersion = null, writeVersion = null; + private boolean restrictReadVersion = true; + + RecordStream(TlsProtocol handler, InputStream input, OutputStream output) + { + this.handler = handler; + this.input = input; + this.output = output; + this.readCompression = new TlsNullCompression(); + this.writeCompression = this.readCompression; + this.readCipher = new TlsNullCipher(context); + this.writeCipher = this.readCipher; + } + + void init(TlsContext context) + { + this.context = context; + this.hash = new DeferredHash(); + this.hash.init(context); + } + + ProtocolVersion getReadVersion() + { + return readVersion; + } + + void setReadVersion(ProtocolVersion readVersion) + { + this.readVersion = readVersion; + } + + void setWriteVersion(ProtocolVersion writeVersion) + { + this.writeVersion = writeVersion; + } + + /** + * RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the + * record layer version number (TLSPlaintext.version) should contain when sending ClientHello + * (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers + * compliant with this specification MUST accept any value {03,XX} as the record layer version + * number for ClientHello." + */ + void setRestrictReadVersion(boolean enabled) + { + this.restrictReadVersion = enabled; + } + + void notifyHelloComplete() + { + this.hash = this.hash.commit(); + } + + void setPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher) + { + this.pendingCompression = tlsCompression; + this.pendingCipher = tlsCipher; + } + + void sentWriteCipherSpec() + throws IOException + { + if (pendingCompression == null || pendingCipher == null) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + this.writeCompression = this.pendingCompression; + this.writeCipher = this.pendingCipher; + this.writeSeqNo = 0; + } + + void receivedReadCipherSpec() + throws IOException + { + if (pendingCompression == null || pendingCipher == null) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + this.readCompression = this.pendingCompression; + this.readCipher = this.pendingCipher; + this.readSeqNo = 0; + } + + void finaliseHandshake() + throws IOException + { + if (readCompression != pendingCompression || writeCompression != pendingCompression + || readCipher != pendingCipher || writeCipher != pendingCipher) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + pendingCompression = null; + pendingCipher = null; + } + + public void readRecord() + throws IOException + { + + short type = TlsUtils.readUint8(input); + + // TODO In earlier RFCs, it was "SHOULD ignore"; should this be version-dependent? + /* + * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an + * unexpected_message alert. + */ + checkType(type, AlertDescription.unexpected_message); + + if (!restrictReadVersion) + { + int version = TlsUtils.readVersionRaw(input); + if ((version & 0xffffff00) != 0x0300) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + else + { + ProtocolVersion version = TlsUtils.readVersion(input); + if (readVersion == null) + { + readVersion = version; + } + else if (!version.equals(readVersion)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + int length = TlsUtils.readUint16(input); + byte[] plaintext = decodeAndVerify(type, input, length); + handler.processRecord(type, plaintext, 0, plaintext.length); + } + + protected byte[] decodeAndVerify(short type, InputStream input, int len) + throws IOException + { + + checkLength(len, CIPHERTEXT_LIMIT, AlertDescription.record_overflow); + + byte[] buf = TlsUtils.readFully(len, input); + byte[] decoded = readCipher.decodeCiphertext(readSeqNo++, type, buf, 0, buf.length); + + checkLength(decoded.length, COMPRESSED_LIMIT, AlertDescription.record_overflow); + + /* + * TODO RFC5264 6.2.2. Implementation note: Decompression functions are responsible for + * ensuring that messages cannot cause internal buffer overflows. + */ + OutputStream cOut = readCompression.decompress(buffer); + if (cOut != buffer) + { + cOut.write(decoded, 0, decoded.length); + cOut.flush(); + decoded = getBufferContents(); + } + + /* + * RFC 5264 6.2.2. If the decompression function encounters a TLSCompressed.fragment that + * would decompress to a length in excess of 2^14 bytes, it should report a fatal + * decompression failure error. + */ + checkLength(decoded.length, PLAINTEXT_LIMIT, AlertDescription.decompression_failure); + + return decoded; + } + + protected void writeRecord(short type, byte[] plaintext, int plaintextOffset, int plaintextLength) + throws IOException + { + + /* + * RFC 5264 6. Implementations MUST NOT send record types not defined in this document + * unless negotiated by some extension. + */ + checkType(type, AlertDescription.internal_error); + + /* + * RFC 5264 6.2.1 The length should not exceed 2^14. + */ + checkLength(plaintextLength, PLAINTEXT_LIMIT, AlertDescription.internal_error); + + /* + * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (plaintextLength < 1 && type != ContentType.application_data) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (type == ContentType.handshake) + { + updateHandshakeData(plaintext, plaintextOffset, plaintextLength); + } + + OutputStream cOut = writeCompression.compress(buffer); + + byte[] ciphertext; + if (cOut == buffer) + { + ciphertext = writeCipher.encodePlaintext(writeSeqNo++, type, plaintext, plaintextOffset, plaintextLength); + } + else + { + cOut.write(plaintext, plaintextOffset, plaintextLength); + cOut.flush(); + byte[] compressed = getBufferContents(); + + /* + * RFC5264 6.2.2. Compression must be lossless and may not increase the content length + * by more than 1024 bytes. + */ + checkLength(compressed.length, plaintextLength + 1024, AlertDescription.internal_error); + + ciphertext = writeCipher.encodePlaintext(writeSeqNo++, type, compressed, 0, compressed.length); + } + + /* + * RFC 5264 6.2.3. The length may not exceed 2^14 + 2048. + */ + checkLength(ciphertext.length, CIPHERTEXT_LIMIT, AlertDescription.internal_error); + + byte[] record = new byte[ciphertext.length + 5]; + TlsUtils.writeUint8(type, record, 0); + TlsUtils.writeVersion(writeVersion, record, 1); + TlsUtils.writeUint16(ciphertext.length, record, 3); + System.arraycopy(ciphertext, 0, record, 5, ciphertext.length); + output.write(record); + output.flush(); + } + + void updateHandshakeData(byte[] message, int offset, int len) + { + hash.update(message, offset, len); + } + + /** + * 'sender' only relevant to SSLv3 + */ + byte[] getCurrentHash(byte[] sender) + { + TlsHandshakeHash d = hash.fork(); + + if (context.getServerVersion().isSSL()) + { + if (sender != null) + { + d.update(sender, 0, sender.length); + } + } + + return doFinal(d); + } + + protected void close() + throws IOException + { + IOException e = null; + try + { + input.close(); + } + catch (IOException ex) + { + e = ex; + } + try + { + output.close(); + } + catch (IOException ex) + { + e = ex; + } + if (e != null) + { + throw e; + } + } + + protected void flush() + throws IOException + { + output.flush(); + } + + private byte[] getBufferContents() + { + byte[] contents = buffer.toByteArray(); + buffer.reset(); + return contents; + } + + private static byte[] doFinal(Digest d) + { + byte[] bs = new byte[d.getDigestSize()]; + d.doFinal(bs, 0); + return bs; + } + + private static void checkType(short type, short alertDescription) + throws IOException + { + + switch (type) + { + case ContentType.change_cipher_spec: + case ContentType.alert: + case ContentType.handshake: + case ContentType.application_data: + break; + default: + throw new TlsFatalAlert(alertDescription); + } + } + + private static void checkLength(int length, int limit, short alertDescription) + throws IOException + { + if (length > limit) + { + throw new TlsFatalAlert(alertDescription); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java b/core/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java new file mode 100644 index 00000000..a5d48404 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java @@ -0,0 +1,137 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; + +public abstract class SRPTlsClient + extends AbstractTlsClient +{ + public static final Integer EXT_SRP = Integers.valueOf(ExtensionType.srp); + + protected byte[] identity; + protected byte[] password; + + public SRPTlsClient(byte[] identity, byte[] password) + { + super(); + this.identity = Arrays.clone(identity); + this.password = Arrays.clone(password); + } + + public SRPTlsClient(TlsCipherFactory cipherFactory, byte[] identity, byte[] password) + { + super(cipherFactory); + this.identity = Arrays.clone(identity); + this.password = Arrays.clone(password); + } + + public int[] getCipherSuites() + { + return new int[]{CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,}; + } + + public Hashtable getClientExtensions() + throws IOException + { + + Hashtable clientExtensions = super.getClientExtensions(); + if (clientExtensions == null) + { + clientExtensions = new Hashtable(); + } + + ByteArrayOutputStream srpData = new ByteArrayOutputStream(); + TlsUtils.writeOpaque8(this.identity, srpData); + clientExtensions.put(EXT_SRP, srpData.toByteArray()); + + return clientExtensions; + } + + public void processServerExtensions(Hashtable serverExtensions) + throws IOException + { + // No explicit guidance in RFC 5054 here; we allow an optional empty extension from server + if (serverExtensions != null) + { + byte[] extValue = (byte[])serverExtensions.get(EXT_SRP); + if (extValue != null && extValue.length > 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public TlsKeyExchange getKeyExchange() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + return createSRPKeyExchange(KeyExchangeAlgorithm.SRP); + + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + return createSRPKeyExchange(KeyExchangeAlgorithm.SRP_RSA); + + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + return createSRPKeyExchange(KeyExchangeAlgorithm.SRP_DSS); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public TlsCipher getCipher() + throws IOException + { + + switch (selectedCipherSuite) + { + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected TlsKeyExchange createSRPKeyExchange(int keyExchange) + { + return new TlsSRPKeyExchange(keyExchange, supportedSignatureAlgorithms, identity, password); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SRTPProtectionProfile.java b/core/src/main/java/org/bouncycastle/crypto/tls/SRTPProtectionProfile.java new file mode 100644 index 00000000..1faac960 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SRTPProtectionProfile.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +public class SRTPProtectionProfile +{ + /* + * RFC 5764 4.1.2. + */ + public static final int SRTP_AES128_CM_HMAC_SHA1_80 = 0x0001; + public static final int SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002; + public static final int SRTP_NULL_HMAC_SHA1_80 = 0x0005; + public static final int SRTP_NULL_HMAC_SHA1_32 = 0x0006; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SSL3Mac.java b/core/src/main/java/org/bouncycastle/crypto/tls/SSL3Mac.java new file mode 100644 index 00000000..0d2e2f19 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SSL3Mac.java @@ -0,0 +1,114 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; + +/** + * HMAC implementation based on original internet draft for HMAC (RFC 2104) + * <p/> + * The difference is that padding is concatenated versus XORed with the key + * <p/> + * H(K + opad, H(K + ipad, text)) + */ +public class SSL3Mac + implements Mac +{ + private final static byte IPAD_BYTE = (byte)0x36; + private final static byte OPAD_BYTE = (byte)0x5C; + + static final byte[] IPAD = genPad(IPAD_BYTE, 48); + static final byte[] OPAD = genPad(OPAD_BYTE, 48); + + private Digest digest; + + private byte[] secret; + private int padLength; + + /** + * Base constructor for one of the standard digest algorithms that the byteLength of + * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1. + * + * @param digest the digest. + */ + public SSL3Mac(Digest digest) + { + this.digest = digest; + + if (digest.getDigestSize() == 20) + { + this.padLength = 40; + } + else + { + this.padLength = 48; + } + } + + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "/SSL3MAC"; + } + + public Digest getUnderlyingDigest() + { + return digest; + } + + public void init(CipherParameters params) + { + secret = Arrays.clone(((KeyParameter)params).getKey()); + + reset(); + } + + public int getMacSize() + { + return digest.getDigestSize(); + } + + public void update(byte in) + { + digest.update(in); + } + + public void update(byte[] in, int inOff, int len) + { + digest.update(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + byte[] tmp = new byte[digest.getDigestSize()]; + digest.doFinal(tmp, 0); + + digest.update(secret, 0, secret.length); + digest.update(OPAD, 0, padLength); + digest.update(tmp, 0, tmp.length); + + int len = digest.doFinal(out, outOff); + + reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + digest.reset(); + digest.update(secret, 0, secret.length); + digest.update(IPAD, 0, padLength); + } + + private static byte[] genPad(byte b, int count) + { + byte[] padding = new byte[count]; + Arrays.fill(padding, b); + return padding; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java b/core/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java new file mode 100644 index 00000000..a7701fe4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java @@ -0,0 +1,57 @@ +package org.bouncycastle.crypto.tls; + +public class SecurityParameters +{ + + int entity = -1; + int prfAlgorithm = -1; + short compressionAlgorithm = -1; + int verifyDataLength = -1; + byte[] masterSecret = null; + byte[] clientRandom = null; + byte[] serverRandom = null; + + /** + * @return {@link ConnectionEnd} + */ + public int getEntity() + { + return entity; + } + + /** + * @return {@link PRFAlgorithm} + */ + public int getPrfAlgorithm() + { + return prfAlgorithm; + } + + /** + * @return {@link CompressionMethod} + */ + public short getCompressionAlgorithm() + { + return compressionAlgorithm; + } + + public int getVerifyDataLength() + { + return verifyDataLength; + } + + public byte[] getMasterSecret() + { + return masterSecret; + } + + public byte[] getClientRandom() + { + return clientRandom; + } + + public byte[] getServerRandom() + { + return serverRandom; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/ServerOnlyTlsAuthentication.java b/core/src/main/java/org/bouncycastle/crypto/tls/ServerOnlyTlsAuthentication.java new file mode 100644 index 00000000..eccbb3f6 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/ServerOnlyTlsAuthentication.java @@ -0,0 +1,10 @@ +package org.bouncycastle.crypto.tls; + +public abstract class ServerOnlyTlsAuthentication + implements TlsAuthentication +{ + public final TlsCredentials getClientCredentials(CertificateRequest certificateRequest) + { + return null; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SignatureAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/SignatureAlgorithm.java new file mode 100644 index 00000000..e63c7936 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SignatureAlgorithm.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 5246 7.4.1.4.1 (in RFC 2246, there were no specific values assigned) + */ +public class SignatureAlgorithm +{ + + public static final short anonymous = 0; + public static final short rsa = 1; + public static final short dsa = 2; + public static final short ecdsa = 3; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java b/core/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java new file mode 100644 index 00000000..7ad46442 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java @@ -0,0 +1,98 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * RFC 5246 7.4.1.4.1 + */ +public class SignatureAndHashAlgorithm +{ + + private short hash; + private short signature; + + /** + * @param hash {@link HashAlgorithm} + * @param signature {@link SignatureAlgorithm} + */ + public SignatureAndHashAlgorithm(short hash, short signature) + { + + if (!TlsUtils.isValidUint8(hash)) + { + throw new IllegalArgumentException("'hash' should be a uint8"); + } + if (!TlsUtils.isValidUint8(signature)) + { + throw new IllegalArgumentException("'signature' should be a uint8"); + } + if (signature == SignatureAlgorithm.anonymous) + { + throw new IllegalArgumentException("'signature' MUST NOT be \"anonymous\""); + } + + this.hash = hash; + this.signature = signature; + } + + /** + * @return {@link HashAlgorithm} + */ + public short getHash() + { + return hash; + } + + /** + * @return {@link SignatureAlgorithm} + */ + public short getSignature() + { + return signature; + } + + public boolean equals(Object obj) + { + if (!(obj instanceof SignatureAndHashAlgorithm)) + { + return false; + } + SignatureAndHashAlgorithm other = (SignatureAndHashAlgorithm)obj; + return other.getHash() == getHash() && other.getSignature() == getSignature(); + } + + public int hashCode() + { + return (getHash() << 8) | getSignature(); + } + + /** + * Encode this {@link SignatureAndHashAlgorithm} to an {@link OutputStream}. + * + * @param output the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) + throws IOException + { + TlsUtils.writeUint8(hash, output); + TlsUtils.writeUint8(signature, output); + } + + /** + * Parse a {@link SignatureAndHashAlgorithm} from an {@link InputStream}. + * + * @param input the {@link InputStream} to parse from. + * @return a {@link SignatureAndHashAlgorithm} object. + * @throws IOException + */ + public static SignatureAndHashAlgorithm parse(InputStream input) + throws IOException + { + short hash = TlsUtils.readUint8(input); + short signature = TlsUtils.readUint8(input); + return new SignatureAndHashAlgorithm(hash, signature); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java b/core/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java new file mode 100644 index 00000000..5a71f9b0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto.tls; + +public class SupplementalDataEntry +{ + + private int supp_data_type; + private byte[] data; + + public SupplementalDataEntry(int supp_data_type, byte[] data) + { + this.supp_data_type = supp_data_type; + this.data = data; + } + + public int getDataType() + { + return supp_data_type; + } + + public byte[] getData() + { + return data; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataType.java b/core/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataType.java new file mode 100644 index 00000000..218f36b1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataType.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4680 + */ +public class SupplementalDataType +{ + /* + * RFC 4681 + */ + public static final int user_mapping_data = 0; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java new file mode 100644 index 00000000..dbf9d79a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java @@ -0,0 +1,197 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; + +public class TlsAEADCipher + implements TlsCipher +{ + + protected TlsContext context; + protected int macSize; + protected int nonce_explicit_length; + + protected AEADBlockCipher encryptCipher; + protected AEADBlockCipher decryptCipher; + + protected byte[] encryptImplicitNonce, decryptImplicitNonce; + + public TlsAEADCipher(TlsContext context, AEADBlockCipher clientWriteCipher, AEADBlockCipher serverWriteCipher, + int cipherKeySize, int macSize) + throws IOException + { + + if (!ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion())) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.context = context; + this.macSize = macSize; + + // NOTE: Valid for RFC 5288 ciphers but may need review for other AEAD ciphers + this.nonce_explicit_length = 8; + + // TODO SecurityParameters.fixed_iv_length + int fixed_iv_length = 4; + + int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length); + + byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size); + + int offset = 0; + + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + byte[] client_write_IV = Arrays.copyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + byte[] server_write_IV = Arrays.copyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + KeyParameter encryptKey, decryptKey; + if (context.isServer()) + { + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + this.encryptImplicitNonce = server_write_IV; + this.decryptImplicitNonce = client_write_IV; + encryptKey = server_write_key; + decryptKey = client_write_key; + } + else + { + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + this.encryptImplicitNonce = client_write_IV; + this.decryptImplicitNonce = server_write_IV; + encryptKey = client_write_key; + decryptKey = server_write_key; + } + + byte[] dummyNonce = new byte[fixed_iv_length + nonce_explicit_length]; + + this.encryptCipher.init(true, new AEADParameters(encryptKey, 8 * macSize, dummyNonce)); + this.decryptCipher.init(false, new AEADParameters(decryptKey, 8 * macSize, dummyNonce)); + } + + public int getPlaintextLimit(int ciphertextLimit) + { + // TODO We ought to be able to ask the decryptCipher (independently of it's current state!) + return ciphertextLimit - macSize - nonce_explicit_length; + } + + public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) + throws IOException + { + + byte[] nonce = new byte[this.encryptImplicitNonce.length + nonce_explicit_length]; + System.arraycopy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.length); + + /* + * RFC 5288 The nonce_explicit MAY be the 64-bit sequence number. + * + * (May need review for other AEAD ciphers). + */ + TlsUtils.writeUint64(seqNo, nonce, encryptImplicitNonce.length); + + int plaintextOffset = offset; + int plaintextLength = len; + int ciphertextLength = encryptCipher.getOutputSize(plaintextLength); + + byte[] output = new byte[nonce_explicit_length + ciphertextLength]; + System.arraycopy(nonce, encryptImplicitNonce.length, output, 0, nonce_explicit_length); + int outputPos = nonce_explicit_length; + + encryptCipher.init(true, + new AEADParameters(null, 8 * macSize, nonce, getAdditionalData(seqNo, type, plaintextLength))); + + outputPos += encryptCipher.processBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos); + try + { + outputPos += encryptCipher.doFinal(output, outputPos); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (outputPos != output.length) + { + // NOTE: Existing AEAD cipher implementations all give exact output lengths + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return output; + } + + public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) + throws IOException + { + + if (getPlaintextLimit(len) < 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + byte[] nonce = new byte[this.decryptImplicitNonce.length + nonce_explicit_length]; + System.arraycopy(decryptImplicitNonce, 0, nonce, 0, decryptImplicitNonce.length); + System.arraycopy(ciphertext, offset, nonce, decryptImplicitNonce.length, nonce_explicit_length); + + int ciphertextOffset = offset + nonce_explicit_length; + int ciphertextLength = len - nonce_explicit_length; + int plaintextLength = decryptCipher.getOutputSize(ciphertextLength); + + byte[] output = new byte[plaintextLength]; + int outputPos = 0; + + decryptCipher.init(false, + new AEADParameters(null, 8 * macSize, nonce, getAdditionalData(seqNo, type, plaintextLength))); + + outputPos += decryptCipher.processBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos); + + try + { + outputPos += decryptCipher.doFinal(output, outputPos); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + if (outputPos != output.length) + { + // NOTE: Existing AEAD cipher implementations all give exact output lengths + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return output; + } + + protected byte[] getAdditionalData(long seqNo, short type, int len) + throws IOException + { + /* + * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + + * TLSCompressed.length + */ + + byte[] additional_data = new byte[13]; + TlsUtils.writeUint64(seqNo, additional_data, 0); + TlsUtils.writeUint8(type, additional_data, 8); + TlsUtils.writeVersion(context.getServerVersion(), additional_data, 9); + TlsUtils.writeUint16(len, additional_data, 11); + + return additional_data; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsAgreementCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsAgreementCredentials.java new file mode 100644 index 00000000..d8fe239c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsAgreementCredentials.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public interface TlsAgreementCredentials + extends TlsCredentials +{ + + byte[] generateAgreement(AsymmetricKeyParameter peerPublicKey) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsAuthentication.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsAuthentication.java new file mode 100755 index 00000000..62c2616e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsAuthentication.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public interface TlsAuthentication +{ + /** + * Called by the protocol handler to report the server certificate + * Note: this method is responsible for certificate verification and validation + * + * @param serverCertificate the server certificate received + * @throws IOException + */ + void notifyServerCertificate(Certificate serverCertificate) + throws IOException; + + /** + * Return client credentials in response to server's certificate request + * + * @param certificateRequest details of the certificate request + * @return a TlsCredentials object or null for no client authentication + * @throws IOException + */ + TlsCredentials getClientCredentials(CertificateRequest certificateRequest) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java new file mode 100644 index 00000000..0b218c18 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java @@ -0,0 +1,313 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.util.Arrays; + +/** + * A generic TLS 1.0-1.1 / SSLv3 block cipher. This can be used for AES or 3DES for example. + */ +public class TlsBlockCipher + implements TlsCipher +{ + + protected TlsContext context; + protected byte[] randomData; + protected boolean useExplicitIV; + + protected BlockCipher encryptCipher; + protected BlockCipher decryptCipher; + + protected TlsMac writeMac; + protected TlsMac readMac; + + public TlsMac getWriteMac() + { + return writeMac; + } + + public TlsMac getReadMac() + { + return readMac; + } + + public TlsBlockCipher(TlsContext context, BlockCipher clientWriteCipher, BlockCipher serverWriteCipher, + Digest clientWriteDigest, Digest serverWriteDigest, int cipherKeySize) + throws IOException + { + + this.context = context; + + this.randomData = new byte[256]; + context.getSecureRandom().nextBytes(randomData); + + this.useExplicitIV = ProtocolVersion.TLSv11.isEqualOrEarlierVersionOf(context.getServerVersion() + .getEquivalentTLSVersion()); + + int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize() + + serverWriteDigest.getDigestSize(); + + // From TLS 1.1 onwards, block ciphers don't need client_write_IV + if (!useExplicitIV) + { + key_block_size += clientWriteCipher.getBlockSize() + serverWriteCipher.getBlockSize(); + } + + byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size); + + int offset = 0; + + TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.getDigestSize()); + offset += clientWriteDigest.getDigestSize(); + TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.getDigestSize()); + offset += serverWriteDigest.getDigestSize(); + + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + + byte[] client_write_IV, server_write_IV; + if (useExplicitIV) + { + client_write_IV = new byte[clientWriteCipher.getBlockSize()]; + server_write_IV = new byte[serverWriteCipher.getBlockSize()]; + } + else + { + client_write_IV = Arrays.copyOfRange(key_block, offset, offset + clientWriteCipher.getBlockSize()); + offset += clientWriteCipher.getBlockSize(); + server_write_IV = Arrays.copyOfRange(key_block, offset, offset + serverWriteCipher.getBlockSize()); + offset += serverWriteCipher.getBlockSize(); + } + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + CipherParameters encryptParams, decryptParams; + if (context.isServer()) + { + this.writeMac = serverWriteMac; + this.readMac = clientWriteMac; + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + encryptParams = new ParametersWithIV(server_write_key, server_write_IV); + decryptParams = new ParametersWithIV(client_write_key, client_write_IV); + } + else + { + this.writeMac = clientWriteMac; + this.readMac = serverWriteMac; + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + encryptParams = new ParametersWithIV(client_write_key, client_write_IV); + decryptParams = new ParametersWithIV(server_write_key, server_write_IV); + } + + this.encryptCipher.init(true, encryptParams); + this.decryptCipher.init(false, decryptParams); + } + + public int getPlaintextLimit(int ciphertextLimit) + { + int blockSize = encryptCipher.getBlockSize(); + int macSize = writeMac.getSize(); + + int result = ciphertextLimit - (ciphertextLimit % blockSize) - macSize - 1; + if (useExplicitIV) + { + result -= blockSize; + } + + return result; + } + + public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) + { + int blockSize = encryptCipher.getBlockSize(); + int macSize = writeMac.getSize(); + + ProtocolVersion version = context.getServerVersion(); + + int padding_length = blockSize - 1 - ((len + macSize) % blockSize); + + // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though) + if (!version.isDTLS() && !version.isSSL()) + { + // Add a random number of extra blocks worth of padding + int maxExtraPadBlocks = (255 - padding_length) / blockSize; + int actualExtraPadBlocks = chooseExtraPadBlocks(context.getSecureRandom(), maxExtraPadBlocks); + padding_length += actualExtraPadBlocks * blockSize; + } + + int totalSize = len + macSize + padding_length + 1; + if (useExplicitIV) + { + totalSize += blockSize; + } + + byte[] outbuf = new byte[totalSize]; + int outOff = 0; + + if (useExplicitIV) + { + byte[] explicitIV = new byte[blockSize]; + context.getSecureRandom().nextBytes(explicitIV); + + encryptCipher.init(true, new ParametersWithIV(null, explicitIV)); + + System.arraycopy(explicitIV, 0, outbuf, outOff, blockSize); + outOff += blockSize; + } + + byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + + System.arraycopy(plaintext, offset, outbuf, outOff, len); + System.arraycopy(mac, 0, outbuf, outOff + len, mac.length); + + int padOffset = outOff + len + mac.length; + for (int i = 0; i <= padding_length; i++) + { + outbuf[i + padOffset] = (byte)padding_length; + } + for (int i = outOff; i < totalSize; i += blockSize) + { + encryptCipher.processBlock(outbuf, i, outbuf, i); + } + return outbuf; + } + + public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) + throws IOException + { + int blockSize = decryptCipher.getBlockSize(); + int macSize = readMac.getSize(); + + int minLen = Math.max(blockSize, macSize + 1); + if (useExplicitIV) + { + minLen += blockSize; + } + + if (len < minLen) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + if (len % blockSize != 0) + { + throw new TlsFatalAlert(AlertDescription.decryption_failed); + } + + if (useExplicitIV) + { + decryptCipher.init(false, new ParametersWithIV(null, ciphertext, offset, blockSize)); + + offset += blockSize; + len -= blockSize; + } + + for (int i = 0; i < len; i += blockSize) + { + decryptCipher.processBlock(ciphertext, offset + i, ciphertext, offset + i); + } + + // If there's anything wrong with the padding, this will return zero + int totalPad = checkPaddingConstantTime(ciphertext, offset, len, blockSize, macSize); + + int macInputLen = len - totalPad - macSize; + + byte[] decryptedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + macInputLen + macSize); + byte[] calculatedMac = readMac.calculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, len + - macSize, randomData); + + boolean badMac = !Arrays.constantTimeAreEqual(calculatedMac, decryptedMac); + + if (badMac || totalPad == 0) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + return Arrays.copyOfRange(ciphertext, offset, offset + macInputLen); + } + + protected int checkPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize) + { + int end = off + len; + byte lastByte = buf[end - 1]; + int padlen = lastByte & 0xff; + int totalPad = padlen + 1; + + int dummyIndex = 0; + byte padDiff = 0; + + if ((context.getServerVersion().isSSL() && totalPad > blockSize) || (macSize + totalPad > len)) + { + totalPad = 0; + } + else + { + int padPos = end - totalPad; + do + { + padDiff |= (buf[padPos++] ^ lastByte); + } + while (padPos < end); + + dummyIndex = totalPad; + + if (padDiff != 0) + { + totalPad = 0; + } + } + + // Run some extra dummy checks so the number of checks is always constant + { + byte[] dummyPad = randomData; + while (dummyIndex < 256) + { + padDiff |= (dummyPad[dummyIndex++] ^ lastByte); + } + // Ensure the above loop is not eliminated + dummyPad[0] ^= padDiff; + } + + return totalPad; + } + + protected int chooseExtraPadBlocks(SecureRandom r, int max) + { + // return r.nextInt(max + 1); + + int x = r.nextInt(); + int n = lowestBitSet(x); + return Math.min(n, max); + } + + protected int lowestBitSet(int x) + { + if (x == 0) + { + return 32; + } + + int n = 0; + while ((x & 1) == 0) + { + ++n; + x >>= 1; + } + return n; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsCipher.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCipher.java new file mode 100644 index 00000000..2f0af080 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCipher.java @@ -0,0 +1,14 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public interface TlsCipher +{ + int getPlaintextLimit(int ciphertextLimit); + + byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) + throws IOException; + + byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsCipherFactory.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCipherFactory.java new file mode 100644 index 00000000..29d961ff --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCipherFactory.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public interface TlsCipherFactory +{ + + /** + * See enumeration classes EncryptionAlgorithm, MACAlgorithm for appropriate argument values + */ + TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java new file mode 100644 index 00000000..62444fa8 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java @@ -0,0 +1,76 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; + +public interface TlsClient + extends TlsPeer +{ + + void init(TlsClientContext context); + + ProtocolVersion getClientHelloRecordLayerVersion(); + + ProtocolVersion getClientVersion(); + + int[] getCipherSuites(); + + short[] getCompressionMethods(); + + // Hashtable is (Integer -> byte[]) + Hashtable getClientExtensions() + throws IOException; + + void notifyServerVersion(ProtocolVersion selectedVersion) + throws IOException; + + void notifySessionID(byte[] sessionID); + + void notifySelectedCipherSuite(int selectedCipherSuite); + + void notifySelectedCompressionMethod(short selectedCompressionMethod); + + void notifySecureRenegotiation(boolean secureNegotiation) + throws IOException; + + // Hashtable is (Integer -> byte[]) + void processServerExtensions(Hashtable serverExtensions) + throws IOException; + + // Vector is (SupplementalDataEntry) + void processServerSupplementalData(Vector serverSupplementalData) + throws IOException; + + TlsKeyExchange getKeyExchange() + throws IOException; + + TlsAuthentication getAuthentication() + throws IOException; + + // Vector is (SupplementalDataEntry) + Vector getClientSupplementalData() + throws IOException; + + TlsCompression getCompression() + throws IOException; + + TlsCipher getCipher() + throws IOException; + + /** + * RFC 5077 3.3. NewSessionTicket Handshake Message + * <p/> + * This method will be called (only) when a NewSessionTicket handshake message is received. The + * ticket is opaque to the client and clients MUST NOT examine the ticket under the assumption + * that it complies with e.g. <i>RFC 5077 4. Recommended Ticket Construction</i>. + * + * @param newSessionTicket The ticket. + * @throws IOException + */ + void notifyNewSessionTicket(NewSessionTicket newSessionTicket) + throws IOException; + + void notifyHandshakeComplete() + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientContext.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientContext.java new file mode 100644 index 00000000..db9f15b7 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientContext.java @@ -0,0 +1,6 @@ +package org.bouncycastle.crypto.tls; + +public interface TlsClientContext + extends TlsContext +{ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java new file mode 100644 index 00000000..d91f7f8e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java @@ -0,0 +1,19 @@ +package org.bouncycastle.crypto.tls; + +import java.security.SecureRandom; + +class TlsClientContextImpl + extends AbstractTlsContext + implements TlsClientContext +{ + + TlsClientContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) + { + super(secureRandom, securityParameters); + } + + public boolean isServer() + { + return false; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java new file mode 100644 index 00000000..33cd9141 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java @@ -0,0 +1,732 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.crypto.prng.ThreadedSeedGenerator; +import org.bouncycastle.util.Arrays; + +public class TlsClientProtocol + extends TlsProtocol +{ + + protected TlsClient tlsClient = null; + protected TlsClientContextImpl tlsClientContext = null; + + protected int[] offeredCipherSuites = null; + protected short[] offeredCompressionMethods = null; + protected Hashtable clientExtensions = null; + + protected int selectedCipherSuite; + protected short selectedCompressionMethod; + + protected TlsKeyExchange keyExchange = null; + protected TlsAuthentication authentication = null; + protected CertificateRequest certificateRequest = null; + + private static SecureRandom createSecureRandom() + { + /* + * We use our threaded seed generator to generate a good random seed. If the user has a + * better random seed, he should use the constructor with a SecureRandom. + */ + ThreadedSeedGenerator tsg = new ThreadedSeedGenerator(); + SecureRandom random = new SecureRandom(); + + /* + * Hopefully, 20 bytes in fast mode are good enough. + */ + random.setSeed(tsg.generateSeed(20, true)); + + return random; + } + + public TlsClientProtocol(InputStream input, OutputStream output) + { + this(input, output, createSecureRandom()); + } + + public TlsClientProtocol(InputStream input, OutputStream output, SecureRandom secureRandom) + { + super(input, output, secureRandom); + } + + /** + * Initiates a TLS handshake in the role of client + * + * @param tlsClient + * @throws IOException If handshake was not successful. + */ + public void connect(TlsClient tlsClient) + throws IOException + { + if (tlsClient == null) + { + throw new IllegalArgumentException("'tlsClient' cannot be null"); + } + if (this.tlsClient != null) + { + throw new IllegalStateException("connect can only be called once"); + } + + this.tlsClient = tlsClient; + + this.securityParameters = new SecurityParameters(); + this.securityParameters.entity = ConnectionEnd.client; + this.securityParameters.clientRandom = createRandomBlock(secureRandom); + + this.tlsClientContext = new TlsClientContextImpl(secureRandom, securityParameters); + this.tlsClient.init(tlsClientContext); + this.recordStream.init(tlsClientContext); + + sendClientHelloMessage(); + this.connection_state = CS_CLIENT_HELLO; + + completeHandshake(); + + this.tlsClient.notifyHandshakeComplete(); + } + + protected AbstractTlsContext getContext() + { + return tlsClientContext; + } + + protected TlsPeer getPeer() + { + return tlsClient; + } + + protected void handleChangeCipherSpecMessage() + throws IOException + { + + switch (this.connection_state) + { + case CS_CLIENT_FINISHED: + { + if (this.expectSessionTicket) + { + /* + * RFC 5077 3.3. This message MUST be sent if the server included a SessionTicket + * extension in the ServerHello. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + // NB: Fall through to next case label + } + case CS_SERVER_SESSION_TICKET: + this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC; + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + } + + protected void handleHandshakeMessage(short type, byte[] data) + throws IOException + { + ByteArrayInputStream buf = new ByteArrayInputStream(data); + + switch (type) + { + case HandshakeType.certificate: + { + switch (this.connection_state) + { + case CS_SERVER_HELLO: + { + handleSupplementalData(null); + // NB: Fall through to next case label + } + case CS_SERVER_SUPPLEMENTAL_DATA: + { + // Parse the Certificate message and send to cipher suite + + Certificate serverCertificate = Certificate.parse(buf); + + assertEmpty(buf); + + this.keyExchange.processServerCertificate(serverCertificate); + + this.authentication = tlsClient.getAuthentication(); + this.authentication.notifyServerCertificate(serverCertificate); + + break; + } + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + + this.connection_state = CS_SERVER_CERTIFICATE; + break; + } + case HandshakeType.finished: + switch (this.connection_state) + { + case CS_SERVER_CHANGE_CIPHER_SPEC: + processFinishedMessage(buf); + this.connection_state = CS_SERVER_FINISHED; + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + break; + case HandshakeType.server_hello: + switch (this.connection_state) + { + case CS_CLIENT_HELLO: + receiveServerHelloMessage(buf); + this.connection_state = CS_SERVER_HELLO; + + securityParameters.prfAlgorithm = getPRFAlgorithm(selectedCipherSuite); + securityParameters.compressionAlgorithm = this.selectedCompressionMethod; + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify + * verify_data_length has a verify_data_length equal to 12. This includes all + * existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + recordStream.notifyHelloComplete(); + + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + break; + case HandshakeType.supplemental_data: + { + switch (this.connection_state) + { + case CS_SERVER_HELLO: + handleSupplementalData(readSupplementalDataMessage(buf)); + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.server_hello_done: + switch (this.connection_state) + { + case CS_SERVER_HELLO: + { + handleSupplementalData(null); + // NB: Fall through to next case label + } + case CS_SERVER_SUPPLEMENTAL_DATA: + { + + // There was no server certificate message; check it's OK + this.keyExchange.skipServerCredentials(); + this.authentication = null; + + // NB: Fall through to next case label + } + case CS_SERVER_CERTIFICATE: + + // There was no server key exchange message; check it's OK + this.keyExchange.skipServerKeyExchange(); + + // NB: Fall through to next case label + + case CS_SERVER_KEY_EXCHANGE: + case CS_CERTIFICATE_REQUEST: + + assertEmpty(buf); + + this.connection_state = CS_SERVER_HELLO_DONE; + + Vector clientSupplementalData = tlsClient.getClientSupplementalData(); + if (clientSupplementalData != null) + { + sendSupplementalDataMessage(clientSupplementalData); + } + this.connection_state = CS_CLIENT_SUPPLEMENTAL_DATA; + + TlsCredentials clientCreds = null; + if (certificateRequest == null) + { + this.keyExchange.skipClientCredentials(); + } + else + { + clientCreds = this.authentication.getClientCredentials(certificateRequest); + + if (clientCreds == null) + { + this.keyExchange.skipClientCredentials(); + + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + sendCertificateMessage(Certificate.EMPTY_CHAIN); + } + else + { + this.keyExchange.processClientCredentials(clientCreds); + + sendCertificateMessage(clientCreds.getCertificate()); + } + } + + this.connection_state = CS_CLIENT_CERTIFICATE; + + /* + * Send the client key exchange message, depending on the key exchange we are using + * in our CipherSuite. + */ + sendClientKeyExchangeMessage(); + + establishMasterSecret(getContext(), keyExchange); + + /* + * Initialize our cipher suite + */ + recordStream.setPendingConnectionState(tlsClient.getCompression(), tlsClient.getCipher()); + + this.connection_state = CS_CLIENT_KEY_EXCHANGE; + + if (clientCreds != null && clientCreds instanceof TlsSignerCredentials) + { + /* + * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm + * prepended from TLS 1.2 + */ + TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds; + byte[] md5andsha1 = recordStream.getCurrentHash(null); + byte[] clientCertificateSignature = signerCreds.generateCertificateSignature(md5andsha1); + sendCertificateVerifyMessage(clientCertificateSignature); + + this.connection_state = CS_CERTIFICATE_VERIFY; + } + + sendChangeCipherSpecMessage(); + this.connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC; + + sendFinishedMessage(); + this.connection_state = CS_CLIENT_FINISHED; + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + break; + case HandshakeType.server_key_exchange: + { + switch (this.connection_state) + { + case CS_SERVER_HELLO: + { + handleSupplementalData(null); + // NB: Fall through to next case label + } + case CS_SERVER_SUPPLEMENTAL_DATA: + { + + // There was no server certificate message; check it's OK + this.keyExchange.skipServerCredentials(); + this.authentication = null; + + // NB: Fall through to next case label + } + case CS_SERVER_CERTIFICATE: + + this.keyExchange.processServerKeyExchange(buf); + + assertEmpty(buf); + break; + + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + + this.connection_state = CS_SERVER_KEY_EXCHANGE; + break; + } + case HandshakeType.certificate_request: + { + switch (this.connection_state) + { + case CS_SERVER_CERTIFICATE: + + // There was no server key exchange message; check it's OK + this.keyExchange.skipServerKeyExchange(); + + // NB: Fall through to next case label + + case CS_SERVER_KEY_EXCHANGE: + { + if (this.authentication == null) + { + /* + * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server + * to request client identification. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + + this.certificateRequest = CertificateRequest.parse(buf); + + assertEmpty(buf); + + this.keyExchange.validateCertificateRequest(this.certificateRequest); + + break; + } + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + + this.connection_state = CS_CERTIFICATE_REQUEST; + break; + } + case HandshakeType.session_ticket: + { + switch (this.connection_state) + { + case CS_CLIENT_FINISHED: + if (!this.expectSessionTicket) + { + /* + * RFC 5077 3.3. This message MUST NOT be sent if the server did not include a + * SessionTicket extension in the ServerHello. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + receiveNewSessionTicketMessage(buf); + this.connection_state = CS_SERVER_SESSION_TICKET; + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + case HandshakeType.hello_request: + + assertEmpty(buf); + + /* + * RFC 2246 7.4.1.1 Hello request This message will be ignored by the client if the + * client is currently negotiating a session. This message may be ignored by the client + * if it does not wish to renegotiate a session, or the client may, if it wishes, + * respond with a no_renegotiation alert. + */ + if (this.connection_state == CS_SERVER_FINISHED) + { + String message = "Renegotiation not supported"; + raiseWarning(AlertDescription.no_renegotiation, message); + } + break; + case HandshakeType.client_key_exchange: + case HandshakeType.certificate_verify: + case HandshakeType.client_hello: + case HandshakeType.hello_verify_request: + default: + // We do not support this! + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + } + + protected void handleSupplementalData(Vector serverSupplementalData) + throws IOException + { + + this.tlsClient.processServerSupplementalData(serverSupplementalData); + this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA; + + this.keyExchange = tlsClient.getKeyExchange(); + this.keyExchange.init(getContext()); + } + + protected void receiveNewSessionTicketMessage(ByteArrayInputStream buf) + throws IOException + { + + NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf); + + TlsProtocol.assertEmpty(buf); + + tlsClient.notifyNewSessionTicket(newSessionTicket); + } + + protected void receiveServerHelloMessage(ByteArrayInputStream buf) + throws IOException + { + + ProtocolVersion server_version = TlsUtils.readVersion(buf); + if (server_version.isDTLS()) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + // Check that this matches what the server is sending in the record layer + if (!server_version.equals(recordStream.getReadVersion())) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + ProtocolVersion client_version = getContext().getClientVersion(); + if (!server_version.isEqualOrEarlierVersionOf(client_version)) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.recordStream.setWriteVersion(server_version); + getContext().setServerVersion(server_version); + this.tlsClient.notifyServerVersion(server_version); + + /* + * Read the server random + */ + securityParameters.serverRandom = TlsUtils.readFully(32, buf); + + byte[] sessionID = TlsUtils.readOpaque8(buf); + if (sessionID.length > 32) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.tlsClient.notifySessionID(sessionID); + + /* + * Find out which CipherSuite the server has chosen and check that it was one of the offered + * ones. + */ + this.selectedCipherSuite = TlsUtils.readUint16(buf); + if (!arrayContains(offeredCipherSuites, this.selectedCipherSuite) + || this.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || this.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.tlsClient.notifySelectedCipherSuite(this.selectedCipherSuite); + + /* + * Find out which CompressionMethod the server has chosen and check that it was one of the + * offered ones. + */ + short selectedCompressionMethod = TlsUtils.readUint8(buf); + if (!arrayContains(offeredCompressionMethods, selectedCompressionMethod)) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.tlsClient.notifySelectedCompressionMethod(selectedCompressionMethod); + + /* + * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server + * hello message when the client has requested extended functionality via the extended + * client hello message specified in Section 2.1. ... Note that the extended server hello + * message is only sent in response to an extended client hello message. This prevents the + * possibility that the extended server hello message could "break" existing TLS 1.0 + * clients. + */ + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + + // Integer -> byte[] + Hashtable serverExtensions = readExtensions(buf); + + /* + * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an + * extended client hello message. + * + * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server + * Hello is always allowed. + */ + if (serverExtensions != null) + { + Enumeration e = serverExtensions.keys(); + while (e.hasMoreElements()) + { + Integer extType = (Integer)e.nextElement(); + + /* + * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a + * ClientHello containing only the SCSV is an explicit exception to the prohibition + * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is + * only allowed because the client is signaling its willingness to receive the + * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + if (!extType.equals(EXT_RenegotiationInfo) + && (clientExtensions == null || clientExtensions.get(extType) == null)) + { + /* + * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless + * the same extension type appeared in the corresponding ClientHello. If a + * client receives an extension type in ServerHello that it did not request in + * the associated ClientHello, it MUST abort the handshake with an + * unsupported_extension fatal alert. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.unsupported_extension); + } + } + + /* + * RFC 5746 3.4. Client Behavior: Initial Handshake + */ + { + /* + * When a ServerHello is received, the client MUST check if it includes the + * "renegotiation_info" extension: + */ + byte[] renegExtValue = (byte[])serverExtensions.get(EXT_RenegotiationInfo); + if (renegExtValue != null) + { + /* + * If the extension is present, set the secure_renegotiation flag to TRUE. The + * client MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake (by sending a fatal + * handshake_failure alert). + */ + this.secure_renegotiation = true; + + if (!Arrays.constantTimeAreEqual(renegExtValue, createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) + { + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + } + } + + this.expectSessionTicket = serverExtensions.containsKey(EXT_SessionTicket); + } + + tlsClient.notifySecureRenegotiation(this.secure_renegotiation); + + if (clientExtensions != null) + { + tlsClient.processServerExtensions(serverExtensions); + } + } + + protected void sendCertificateVerifyMessage(byte[] data) + throws IOException + { + /* + * Send signature of handshake messages so far to prove we are the owner of the cert See RFC + * 2246 sections 4.7, 7.4.3 and 7.4.8 + */ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.certificate_verify, bos); + TlsUtils.writeUint24(data.length + 2, bos); + TlsUtils.writeOpaque16(data, bos); + byte[] message = bos.toByteArray(); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendClientHelloMessage() + throws IOException + { + + recordStream.setWriteVersion(this.tlsClient.getClientHelloRecordLayerVersion()); + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.client_hello, buf); + + // Reserve space for length + TlsUtils.writeUint24(0, buf); + + ProtocolVersion client_version = this.tlsClient.getClientVersion(); + if (client_version.isDTLS()) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + + getContext().setClientVersion(client_version); + TlsUtils.writeVersion(client_version, buf); + + buf.write(securityParameters.clientRandom); + + // Session id + TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + + /* + * Cipher suites + */ + this.offeredCipherSuites = this.tlsClient.getCipherSuites(); + + // Integer -> byte[] + this.clientExtensions = this.tlsClient.getClientExtensions(); + + // Cipher Suites (and SCSV) + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + boolean noRenegExt = clientExtensions == null || clientExtensions.get(EXT_RenegotiationInfo) == null; + + int count = offeredCipherSuites.length; + if (noRenegExt) + { + // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV + ++count; + } + + TlsUtils.writeUint16(2 * count, buf); + TlsUtils.writeUint16Array(offeredCipherSuites, buf); + + if (noRenegExt) + { + TlsUtils.writeUint16(CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, buf); + } + } + + // Compression methods + this.offeredCompressionMethods = this.tlsClient.getCompressionMethods(); + + TlsUtils.writeUint8((short)offeredCompressionMethods.length, buf); + TlsUtils.writeUint8Array(offeredCompressionMethods, buf); + + // Extensions + if (clientExtensions != null) + { + writeExtensions(buf, clientExtensions); + } + + byte[] message = buf.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendClientKeyExchangeMessage() + throws IOException + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + TlsUtils.writeUint8(HandshakeType.client_key_exchange, bos); + + // Reserve space for length + TlsUtils.writeUint24(0, bos); + + this.keyExchange.generateClientKeyExchange(bos); + byte[] message = bos.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsCompression.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCompression.java new file mode 100644 index 00000000..cdeb7e33 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCompression.java @@ -0,0 +1,10 @@ +package org.bouncycastle.crypto.tls; + +import java.io.OutputStream; + +public interface TlsCompression +{ + OutputStream compress(OutputStream output); + + OutputStream decompress(OutputStream output); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java new file mode 100644 index 00000000..dfb10524 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java @@ -0,0 +1,32 @@ +package org.bouncycastle.crypto.tls; + +import java.security.SecureRandom; + +public interface TlsContext +{ + + SecureRandom getSecureRandom(); + + SecurityParameters getSecurityParameters(); + + boolean isServer(); + + ProtocolVersion getClientVersion(); + + ProtocolVersion getServerVersion(); + + Object getUserObject(); + + void setUserObject(Object userObject); + + /** + * Export keying material according to RFC 5705: "Keying Material Exporters for TLS". + * + * @param asciiLabel indicates which application will use the exported keys. + * @param context_value allows the application using the exporter to mix its own data with the TLS PRF for + * the exporter output. + * @param length the number of bytes to generate + * @return a pseudorandom bit string of 'length' bytes generated from the master_secret. + */ + byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCredentials.java new file mode 100644 index 00000000..b8a8747e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsCredentials.java @@ -0,0 +1,6 @@ +package org.bouncycastle.crypto.tls; + +public interface TlsCredentials +{ + Certificate getCertificate(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java new file mode 100644 index 00000000..57376592 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java @@ -0,0 +1,113 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.Vector; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.generators.DHKeyPairGenerator; +import org.bouncycastle.crypto.io.SignerInputStream; +import org.bouncycastle.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; + +public class TlsDHEKeyExchange + extends TlsDHKeyExchange +{ + + protected TlsSignerCredentials serverCredentials = null; + + public TlsDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters) + { + super(keyExchange, supportedSignatureAlgorithms, dhParameters); + } + + public void processServerCredentials(TlsCredentials serverCredentials) + throws IOException + { + + if (!(serverCredentials instanceof TlsSignerCredentials)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + processServerCertificate(serverCredentials.getCertificate()); + + this.serverCredentials = (TlsSignerCredentials)serverCredentials; + } + + public byte[] generateServerKeyExchange() + throws IOException + { + + if (this.dhParameters == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + DHKeyPairGenerator kpg = new DHKeyPairGenerator(); + kpg.init(new DHKeyGenerationParameters(context.getSecureRandom(), this.dhParameters)); + AsymmetricCipherKeyPair kp = kpg.generateKeyPair(); + + BigInteger Ys = ((DHPublicKeyParameters)kp.getPublic()).getY(); + + TlsDHUtils.writeDHParameter(dhParameters.getP(), buf); + TlsDHUtils.writeDHParameter(dhParameters.getG(), buf); + TlsDHUtils.writeDHParameter(Ys, buf); + + byte[] digestInput = buf.toByteArray(); + + Digest d = new CombinedHash(); + SecurityParameters securityParameters = context.getSecurityParameters(); + d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); + d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); + d.update(digestInput, 0, digestInput.length); + + byte[] hash = new byte[d.getDigestSize()]; + d.doFinal(hash, 0); + + byte[] sigBytes = serverCredentials.generateCertificateSignature(hash); + /* + * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended from TLS 1.2 + */ + TlsUtils.writeOpaque16(sigBytes, buf); + + return buf.toByteArray(); + } + + public void processServerKeyExchange(InputStream input) + throws IOException + { + + SecurityParameters securityParameters = context.getSecurityParameters(); + + Signer signer = initVerifyer(tlsSigner, securityParameters); + InputStream sigIn = new SignerInputStream(input, signer); + + BigInteger p = TlsDHUtils.readDHParameter(sigIn); + BigInteger g = TlsDHUtils.readDHParameter(sigIn); + BigInteger Ys = TlsDHUtils.readDHParameter(sigIn); + + byte[] sigBytes = TlsUtils.readOpaque16(input); + if (!signer.verifySignature(sigBytes)) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + + this.dhAgreeServerPublicKey = validateDHPublicKey(new DHPublicKeyParameters(Ys, new DHParameters(p, g))); + } + + protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters) + { + Signer signer = tlsSigner.createVerifyer(this.serverPublicKey); + signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); + signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); + return signer; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java new file mode 100644 index 00000000..60e5105c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java @@ -0,0 +1,222 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.crypto.util.PublicKeyFactory; + +/** + * TLS 1.0/1.1 DH key exchange. + */ +public class TlsDHKeyExchange + extends AbstractTlsKeyExchange +{ + + protected static final BigInteger ONE = BigInteger.valueOf(1); + protected static final BigInteger TWO = BigInteger.valueOf(2); + + protected TlsSigner tlsSigner; + protected DHParameters dhParameters; + + protected AsymmetricKeyParameter serverPublicKey; + protected DHPublicKeyParameters dhAgreeServerPublicKey; + protected TlsAgreementCredentials agreementCredentials; + protected DHPrivateKeyParameters dhAgreeClientPrivateKey; + + protected DHPublicKeyParameters dhAgreeClientPublicKey; + + public TlsDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters) + { + + super(keyExchange, supportedSignatureAlgorithms); + + switch (keyExchange) + { + case KeyExchangeAlgorithm.DH_RSA: + case KeyExchangeAlgorithm.DH_DSS: + this.tlsSigner = null; + break; + case KeyExchangeAlgorithm.DHE_RSA: + this.tlsSigner = new TlsRSASigner(); + break; + case KeyExchangeAlgorithm.DHE_DSS: + this.tlsSigner = new TlsDSSSigner(); + break; + default: + throw new IllegalArgumentException("unsupported key exchange algorithm"); + } + + this.dhParameters = dhParameters; + } + + public void init(TlsContext context) + { + super.init(context); + + if (this.tlsSigner != null) + { + this.tlsSigner.init(context); + } + } + + public void skipServerCredentials() + throws IOException + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { + + if (serverCertificate.isEmpty()) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + try + { + this.serverPublicKey = PublicKeyFactory.createKey(keyInfo); + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + if (tlsSigner == null) + { + try + { + this.dhAgreeServerPublicKey = validateDHPublicKey((DHPublicKeyParameters)this.serverPublicKey); + } + catch (ClassCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement); + } + else + { + if (!tlsSigner.isValidPublicKey(this.serverPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + } + + super.processServerCertificate(serverCertificate); + } + + public boolean requiresServerKeyExchange() + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.DHE_RSA: + case KeyExchangeAlgorithm.DH_anon: + return true; + default: + return false; + } + } + + public void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException + { + short[] types = certificateRequest.getCertificateTypes(); + for (int i = 0; i < types.length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.rsa_fixed_dh: + case ClientCertificateType.dss_fixed_dh: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public void processClientCredentials(TlsCredentials clientCredentials) + throws IOException + { + if (clientCredentials instanceof TlsAgreementCredentials) + { + // TODO Validate client cert has matching parameters (see 'areCompatibleParameters')? + + this.agreementCredentials = (TlsAgreementCredentials)clientCredentials; + } + else if (clientCredentials instanceof TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public void generateClientKeyExchange(OutputStream output) + throws IOException + { + /* + * RFC 2246 7.4.7.2 If the client certificate already contains a suitable Diffie-Hellman + * key, then Yc is implicit and does not need to be sent again. In this case, the Client Key + * Exchange message will be sent, but will be empty. + */ + if (agreementCredentials == null) + { + this.dhAgreeClientPrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(), + dhAgreeServerPublicKey.getParameters(), output); + } + } + + public byte[] generatePremasterSecret() + throws IOException + { + if (agreementCredentials != null) + { + return agreementCredentials.generateAgreement(dhAgreeServerPublicKey); + } + + return calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); + } + + protected boolean areCompatibleParameters(DHParameters a, DHParameters b) + { + return a.getP().equals(b.getP()) && a.getG().equals(b.getG()); + } + + protected byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, DHPrivateKeyParameters privateKey) + { + return TlsDHUtils.calculateDHBasicAgreement(publicKey, privateKey); + } + + protected AsymmetricCipherKeyPair generateDHKeyPair(DHParameters dhParams) + { + return TlsDHUtils.generateDHKeyPair(context.getSecureRandom(), dhParams); + } + + protected DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key) + throws IOException + { + return TlsDHUtils.validateDHPublicKey(key); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java new file mode 100644 index 00000000..014e40f0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java @@ -0,0 +1,100 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.agreement.DHBasicAgreement; +import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator; +import org.bouncycastle.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.util.BigIntegers; + +public class TlsDHUtils +{ + + static final BigInteger ONE = BigInteger.valueOf(1); + static final BigInteger TWO = BigInteger.valueOf(2); + + public static byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, + DHPrivateKeyParameters privateKey) + { + + DHBasicAgreement basicAgreement = new DHBasicAgreement(); + basicAgreement.init(privateKey); + BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey); + + /* + * RFC 5246 8.1.2. Leading bytes of Z that contain all zero bits are stripped before it is + * used as the pre_master_secret. + */ + return BigIntegers.asUnsignedByteArray(agreementValue); + } + + public static AsymmetricCipherKeyPair generateDHKeyPair(SecureRandom random, + DHParameters dhParams) + { + DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator(); + dhGen.init(new DHKeyGenerationParameters(random, dhParams)); + return dhGen.generateKeyPair(); + } + + public static DHPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random, + DHParameters dhParams, OutputStream output) + throws IOException + { + + AsymmetricCipherKeyPair dhAgreeClientKeyPair = generateDHKeyPair(random, dhParams); + DHPrivateKeyParameters dhAgreeClientPrivateKey = (DHPrivateKeyParameters)dhAgreeClientKeyPair + .getPrivate(); + + BigInteger Yc = ((DHPublicKeyParameters)dhAgreeClientKeyPair.getPublic()).getY(); + byte[] keData = BigIntegers.asUnsignedByteArray(Yc); + TlsUtils.writeOpaque16(keData, output); + + return dhAgreeClientPrivateKey; + } + + public static DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key) + throws IOException + { + BigInteger Y = key.getY(); + DHParameters params = key.getParameters(); + BigInteger p = params.getP(); + BigInteger g = params.getG(); + + if (!p.isProbablePrime(2)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + if (g.compareTo(TWO) < 0 || g.compareTo(p.subtract(TWO)) > 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + if (Y.compareTo(TWO) < 0 || Y.compareTo(p.subtract(ONE)) > 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + // TODO See RFC 2631 for more discussion of Diffie-Hellman validation + + return key; + } + + public static BigInteger readDHParameter(InputStream input) + throws IOException + { + return new BigInteger(1, TlsUtils.readOpaque16(input)); + } + + public static void writeDHParameter(BigInteger x, OutputStream output) + throws IOException + { + TlsUtils.writeOpaque16(BigIntegers.asUnsignedByteArray(x), output); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java new file mode 100644 index 00000000..b0e89572 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java @@ -0,0 +1,57 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.digests.NullDigest; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.signers.DSADigestSigner; + +public abstract class TlsDSASigner + extends AbstractTlsSigner +{ + + public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + throws CryptoException + { + + // Note: Only use the SHA1 part of the hash + Signer signer = makeSigner(new NullDigest(), true, + new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + signer.update(md5AndSha1, 16, 20); + return signer.generateSignature(); + } + + public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + throws CryptoException + { + + // Note: Only use the SHA1 part of the hash + Signer signer = makeSigner(new NullDigest(), false, publicKey); + signer.update(md5AndSha1, 16, 20); + return signer.verifySignature(sigBytes); + } + + public Signer createSigner(AsymmetricKeyParameter privateKey) + { + return makeSigner(new SHA1Digest(), true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + } + + public Signer createVerifyer(AsymmetricKeyParameter publicKey) + { + return makeSigner(new SHA1Digest(), false, publicKey); + } + + protected Signer makeSigner(Digest d, boolean forSigning, CipherParameters cp) + { + Signer s = new DSADigestSigner(createDSAImpl(), d); + s.init(forSigning, cp); + return s; + } + + protected abstract DSA createDSAImpl(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java new file mode 100644 index 00000000..e0eeca98 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.crypto.signers.DSASigner; + +public class TlsDSSSigner + extends TlsDSASigner +{ + + public boolean isValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey instanceof DSAPublicKeyParameters; + } + + protected DSA createDSAImpl() + { + return new DSASigner(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java new file mode 100644 index 00000000..a49f83ff --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java @@ -0,0 +1,619 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Hashtable; + +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.Integers; + +public class TlsECCUtils +{ + + public static final Integer EXT_elliptic_curves = Integers.valueOf(ExtensionType.elliptic_curves); + public static final Integer EXT_ec_point_formats = Integers.valueOf(ExtensionType.ec_point_formats); + + private static final String[] curveNames = new String[]{"sect163k1", "sect163r1", "sect163r2", "sect193r1", + "sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1", + "sect571k1", "sect571r1", "secp160k1", "secp160r1", "secp160r2", "secp192k1", "secp192r1", "secp224k1", + "secp224r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1",}; + + public static void addSupportedEllipticCurvesExtension(Hashtable extensions, int[] namedCurves) + throws IOException + { + + extensions.put(EXT_elliptic_curves, createSupportedEllipticCurvesExtension(namedCurves)); + } + + public static void addSupportedPointFormatsExtension(Hashtable extensions, short[] ecPointFormats) + throws IOException + { + + extensions.put(EXT_ec_point_formats, createSupportedPointFormatsExtension(ecPointFormats)); + } + + public static int[] getSupportedEllipticCurvesExtension(Hashtable extensions) + throws IOException + { + + if (extensions == null) + { + return null; + } + byte[] extensionValue = (byte[])extensions.get(EXT_elliptic_curves); + if (extensionValue == null) + { + return null; + } + return readSupportedEllipticCurvesExtension(extensionValue); + } + + public static short[] getSupportedPointFormatsExtension(Hashtable extensions) + throws IOException + { + + if (extensions == null) + { + return null; + } + byte[] extensionValue = (byte[])extensions.get(EXT_ec_point_formats); + if (extensionValue == null) + { + return null; + } + return readSupportedPointFormatsExtension(extensionValue); + } + + public static byte[] createSupportedEllipticCurvesExtension(int[] namedCurves) + throws IOException + { + + if (namedCurves == null || namedCurves.length < 1) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint16(2 * namedCurves.length, buf); + TlsUtils.writeUint16Array(namedCurves, buf); + return buf.toByteArray(); + } + + public static byte[] createSupportedPointFormatsExtension(short[] ecPointFormats) + throws IOException + { + + if (ecPointFormats == null) + { + ecPointFormats = new short[]{ECPointFormat.uncompressed}; + } + else if (!TlsProtocol.arrayContains(ecPointFormats, ECPointFormat.uncompressed)) + { + /* + * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST + * contain the value 0 (uncompressed) as one of the items in the list of point formats. + */ + + // NOTE: We add it at the end (lowest preference) + short[] tmp = new short[ecPointFormats.length + 1]; + System.arraycopy(ecPointFormats, 0, tmp, 0, ecPointFormats.length); + tmp[ecPointFormats.length] = ECPointFormat.uncompressed; + + ecPointFormats = tmp; + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8((short)ecPointFormats.length, buf); + TlsUtils.writeUint8Array(ecPointFormats, buf); + return buf.toByteArray(); + } + + public static int[] readSupportedEllipticCurvesExtension(byte[] extensionValue) + throws IOException + { + + if (extensionValue == null) + { + throw new IllegalArgumentException("'extensionValue' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + + int length = TlsUtils.readUint16(buf); + if (length < 2 || (length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + int[] namedCurves = TlsUtils.readUint16Array(length / 2, buf); + + TlsProtocol.assertEmpty(buf); + + return namedCurves; + } + + public static short[] readSupportedPointFormatsExtension(byte[] extensionValue) + throws IOException + { + + if (extensionValue == null) + { + throw new IllegalArgumentException("'extensionValue' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + + short length = TlsUtils.readUint8(buf); + if (length < 1) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + short[] ecPointFormats = TlsUtils.readUint8Array(length, buf); + + TlsProtocol.assertEmpty(buf); + + if (!TlsProtocol.arrayContains(ecPointFormats, ECPointFormat.uncompressed)) + { + /* + * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST + * contain the value 0 (uncompressed) as one of the items in the list of point formats. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return ecPointFormats; + } + + public static String getNameOfNamedCurve(int namedCurve) + { + return isSupportedNamedCurve(namedCurve) ? curveNames[namedCurve - 1] : null; + } + + public static ECDomainParameters getParametersForNamedCurve(int namedCurve) + { + String curveName = getNameOfNamedCurve(namedCurve); + if (curveName == null) + { + return null; + } + + // Lazily created the first time a particular curve is accessed + X9ECParameters ecP = SECNamedCurves.getByName(curveName); + + if (ecP == null) + { + return null; + } + + // It's a bit inefficient to do this conversion every time + return new ECDomainParameters(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed()); + } + + public static boolean hasAnySupportedNamedCurves() + { + return curveNames.length > 0; + } + + public static boolean containsECCCipherSuites(int[] cipherSuites) + { + for (int i = 0; i < cipherSuites.length; ++i) + { + if (isECCCipherSuite(cipherSuites[i])) + { + return true; + } + } + return false; + } + + public static boolean isECCCipherSuite(int cipherSuite) + { + switch (cipherSuite) + { + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + return true; + default: + return false; + } + } + + public static boolean areOnSameCurve(ECDomainParameters a, ECDomainParameters b) + { + // TODO Move to ECDomainParameters.equals() or other utility method? + return a.getCurve().equals(b.getCurve()) && a.getG().equals(b.getG()) && a.getN().equals(b.getN()) + && a.getH().equals(b.getH()); + } + + public static boolean isSupportedNamedCurve(int namedCurve) + { + return (namedCurve > 0 && namedCurve <= curveNames.length); + } + + public static boolean isCompressionPreferred(short[] ecPointFormats, short compressionFormat) + { + if (ecPointFormats == null) + { + return false; + } + for (int i = 0; i < ecPointFormats.length; ++i) + { + short ecPointFormat = ecPointFormats[i]; + if (ecPointFormat == ECPointFormat.uncompressed) + { + return false; + } + if (ecPointFormat == compressionFormat) + { + return true; + } + } + return false; + } + + public static byte[] serializeECFieldElement(int fieldSize, BigInteger x) + throws IOException + { + int requiredLength = (fieldSize + 7) / 8; + return BigIntegers.asUnsignedByteArray(requiredLength, x); + } + + public static byte[] serializeECPoint(short[] ecPointFormats, ECPoint point) + throws IOException + { + + ECCurve curve = point.getCurve(); + + /* + * RFC 4492 5.7. ...an elliptic curve point in uncompressed or compressed format. Here, the + * format MUST conform to what the server has requested through a Supported Point Formats + * Extension if this extension was used, and MUST be uncompressed if this extension was not + * used. + */ + boolean compressed = false; + if (curve instanceof ECCurve.F2m) + { + compressed = isCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_char2); + } + else if (curve instanceof ECCurve.Fp) + { + compressed = isCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_prime); + } + return point.getEncoded(compressed); + } + + public static byte[] serializeECPublicKey(short[] ecPointFormats, ECPublicKeyParameters keyParameters) + throws IOException + { + + return serializeECPoint(ecPointFormats, keyParameters.getQ()); + } + + public static BigInteger deserializeECFieldElement(int fieldSize, byte[] encoding) + throws IOException + { + int requiredLength = (fieldSize + 7) / 8; + if (encoding.length != requiredLength) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + return new BigInteger(1, encoding); + } + + public static ECPoint deserializeECPoint(short[] ecPointFormats, ECCurve curve, byte[] encoding) + throws IOException + { + /* + * NOTE: Here we implicitly decode compressed or uncompressed encodings. DefaultTlsClient by + * default is set up to advertise that we can parse any encoding so this works fine, but + * extra checks might be needed here if that were changed. + */ + return curve.decodePoint(encoding); + } + + public static ECPublicKeyParameters deserializeECPublicKey(short[] ecPointFormats, ECDomainParameters curve_params, + byte[] encoding) + throws IOException + { + + try + { + ECPoint Y = deserializeECPoint(ecPointFormats, curve_params.getCurve(), encoding); + return new ECPublicKeyParameters(Y, curve_params); + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + public static byte[] calculateECDHBasicAgreement(ECPublicKeyParameters publicKey, ECPrivateKeyParameters privateKey) + { + + ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement(); + basicAgreement.init(privateKey); + BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey); + + /* + * RFC 4492 5.10. Note that this octet string (Z in IEEE 1363 terminology) as output by + * FE2OSP, the Field Element to Octet String Conversion Primitive, has constant length for + * any given field; leading zeros found in this octet string MUST NOT be truncated. + */ + return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue); + } + + public static AsymmetricCipherKeyPair generateECKeyPair(SecureRandom random, ECDomainParameters ecParams) + { + + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(ecParams, random); + keyPairGenerator.init(keyGenerationParameters); + return keyPairGenerator.generateKeyPair(); + } + + public static ECPublicKeyParameters validateECPublicKey(ECPublicKeyParameters key) + throws IOException + { + // TODO Check RFC 4492 for validation + return key; + } + + public static int readECExponent(int fieldSize, InputStream input) + throws IOException + { + BigInteger K = readECParameter(input); + if (K.bitLength() < 32) + { + int k = K.intValue(); + if (k > 0 && k < fieldSize) + { + return k; + } + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + public static BigInteger readECFieldElement(int fieldSize, InputStream input) + throws IOException + { + return deserializeECFieldElement(fieldSize, TlsUtils.readOpaque8(input)); + } + + public static BigInteger readECParameter(InputStream input) + throws IOException + { + // TODO Are leading zeroes okay here? + return new BigInteger(1, TlsUtils.readOpaque8(input)); + } + + public static ECDomainParameters readECParameters(int[] namedCurves, short[] ecPointFormats, InputStream input) + throws IOException + { + + try + { + short curveType = TlsUtils.readUint8(input); + + switch (curveType) + { + case ECCurveType.explicit_prime: + { + BigInteger prime_p = readECParameter(input); + BigInteger a = readECFieldElement(prime_p.bitLength(), input); + BigInteger b = readECFieldElement(prime_p.bitLength(), input); + ECCurve curve = new ECCurve.Fp(prime_p, a, b); + ECPoint base = deserializeECPoint(ecPointFormats, curve, TlsUtils.readOpaque8(input)); + BigInteger order = readECParameter(input); + BigInteger cofactor = readECParameter(input); + return new ECDomainParameters(curve, base, order, cofactor); + } + case ECCurveType.explicit_char2: + { + int m = TlsUtils.readUint16(input); + short basis = TlsUtils.readUint8(input); + ECCurve curve; + switch (basis) + { + case ECBasisType.ec_basis_trinomial: + { + int k = readECExponent(m, input); + BigInteger a = readECFieldElement(m, input); + BigInteger b = readECFieldElement(m, input); + curve = new ECCurve.F2m(m, k, a, b); + break; + } + case ECBasisType.ec_basis_pentanomial: + { + int k1 = readECExponent(m, input); + int k2 = readECExponent(m, input); + int k3 = readECExponent(m, input); + BigInteger a = readECFieldElement(m, input); + BigInteger b = readECFieldElement(m, input); + curve = new ECCurve.F2m(m, k1, k2, k3, a, b); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + ECPoint base = deserializeECPoint(ecPointFormats, curve, TlsUtils.readOpaque8(input)); + BigInteger order = readECParameter(input); + BigInteger cofactor = readECParameter(input); + return new ECDomainParameters(curve, base, order, cofactor); + } + case ECCurveType.named_curve: + { + int namedCurve = TlsUtils.readUint16(input); + if (!NamedCurve.refersToASpecificNamedCurve(namedCurve)) + { + /* + * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a + * specific curve. Values of NamedCurve that indicate support for a class of + * explicitly defined curves are not allowed here [...]. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + if (!TlsProtocol.arrayContains(namedCurves, namedCurve)) + { + /* + * RFC 4492 4. [...] servers MUST NOT negotiate the use of an ECC cipher suite + * unless they can complete the handshake while respecting the choice of curves + * and compression techniques specified by the client. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return TlsECCUtils.getParametersForNamedCurve(namedCurve); + } + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + public static void writeECExponent(int k, OutputStream output) + throws IOException + { + BigInteger K = BigInteger.valueOf(k); + writeECParameter(K, output); + } + + public static void writeECFieldElement(int fieldSize, BigInteger x, OutputStream output) + throws IOException + { + TlsUtils.writeOpaque8(serializeECFieldElement(fieldSize, x), output); + } + + public static void writeECParameter(BigInteger x, OutputStream output) + throws IOException + { + TlsUtils.writeOpaque8(BigIntegers.asUnsignedByteArray(x), output); + } + + public static void writeExplicitECParameters(short[] ecPointFormats, ECDomainParameters ecParameters, + OutputStream output) + throws IOException + { + + ECCurve curve = ecParameters.getCurve(); + if (curve instanceof ECCurve.Fp) + { + + TlsUtils.writeUint8(ECCurveType.explicit_prime, output); + + ECCurve.Fp fp = (ECCurve.Fp)curve; + writeECParameter(fp.getQ(), output); + + } + else if (curve instanceof ECCurve.F2m) + { + + TlsUtils.writeUint8(ECCurveType.explicit_char2, output); + + ECCurve.F2m f2m = (ECCurve.F2m)curve; + TlsUtils.writeUint16(f2m.getM(), output); + + if (f2m.isTrinomial()) + { + TlsUtils.writeUint8(ECBasisType.ec_basis_trinomial, output); + writeECExponent(f2m.getK1(), output); + } + else + { + TlsUtils.writeUint8(ECBasisType.ec_basis_pentanomial, output); + writeECExponent(f2m.getK1(), output); + writeECExponent(f2m.getK2(), output); + writeECExponent(f2m.getK3(), output); + } + + } + else + { + throw new IllegalArgumentException("'ecParameters' not a known curve type"); + } + + writeECFieldElement(curve.getFieldSize(), curve.getA().toBigInteger(), output); + writeECFieldElement(curve.getFieldSize(), curve.getB().toBigInteger(), output); + TlsUtils.writeOpaque8(serializeECPoint(ecPointFormats, ecParameters.getG()), output); + writeECParameter(ecParameters.getN(), output); + writeECParameter(ecParameters.getH(), output); + } + + public static void writeNamedECParameters(int namedCurve, OutputStream output) + throws IOException + { + + if (!NamedCurve.refersToASpecificNamedCurve(namedCurve)) + { + /* + * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a specific + * curve. Values of NamedCurve that indicate support for a class of explicitly defined + * curves are not allowed here [...]. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + TlsUtils.writeUint8(ECCurveType.named_curve, output); + TlsUtils.writeUint16(namedCurve, output); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java new file mode 100644 index 00000000..1124560b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java @@ -0,0 +1,206 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.io.SignerInputStream; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; + +/** + * ECDHE key exchange (see RFC 4492) + */ +public class TlsECDHEKeyExchange + extends TlsECDHKeyExchange +{ + + protected TlsSignerCredentials serverCredentials = null; + + public TlsECDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves, + short[] clientECPointFormats, short[] serverECPointFormats) + { + super(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, serverECPointFormats); + } + + public void processServerCredentials(TlsCredentials serverCredentials) + throws IOException + { + + if (!(serverCredentials instanceof TlsSignerCredentials)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + processServerCertificate(serverCredentials.getCertificate()); + + this.serverCredentials = (TlsSignerCredentials)serverCredentials; + } + + public byte[] generateServerKeyExchange() + throws IOException + { + + /* + * First we try to find a supported named curve from the client's list. + */ + int namedCurve = -1; + if (namedCurves == null) + { + namedCurve = NamedCurve.secp256r1; + } + else + { + for (int i = 0; i < namedCurves.length; ++i) + { + int entry = namedCurves[i]; + if (TlsECCUtils.isSupportedNamedCurve(entry)) + { + namedCurve = entry; + break; + } + } + } + + ECDomainParameters curve_params = null; + if (namedCurve >= 0) + { + curve_params = TlsECCUtils.getParametersForNamedCurve(namedCurve); + } + else + { + /* + * If no named curves are suitable, check if the client supports explicit curves. + */ + if (TlsProtocol.arrayContains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves)) + { + curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.secp256r1); + } + else if (TlsProtocol.arrayContains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves)) + { + curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.sect233r1); + } + } + + if (curve_params == null) + { + /* + * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find + * a suitable curve. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + AsymmetricCipherKeyPair kp = TlsECCUtils.generateECKeyPair(context.getSecureRandom(), curve_params); + this.ecAgreeServerPrivateKey = (ECPrivateKeyParameters)kp.getPrivate(); + + byte[] publicBytes = TlsECCUtils.serializeECPublicKey(clientECPointFormats, + (ECPublicKeyParameters)kp.getPublic()); + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + if (namedCurve < 0) + { + TlsECCUtils.writeExplicitECParameters(clientECPointFormats, curve_params, buf); + } + else + { + TlsECCUtils.writeNamedECParameters(namedCurve, buf); + } + + TlsUtils.writeOpaque8(publicBytes, buf); + + byte[] digestInput = buf.toByteArray(); + + Digest d = new CombinedHash(); + SecurityParameters securityParameters = context.getSecurityParameters(); + d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); + d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); + d.update(digestInput, 0, digestInput.length); + + byte[] hash = new byte[d.getDigestSize()]; + d.doFinal(hash, 0); + + byte[] sigBytes = serverCredentials.generateCertificateSignature(hash); + /* + * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended + * from TLS 1.2 + */ + TlsUtils.writeOpaque16(sigBytes, buf); + + return buf.toByteArray(); + } + + public void processServerKeyExchange(InputStream input) + throws IOException + { + + SecurityParameters securityParameters = context.getSecurityParameters(); + + Signer signer = initVerifyer(tlsSigner, securityParameters); + InputStream sigIn = new SignerInputStream(input, signer); + + ECDomainParameters curve_params = TlsECCUtils.readECParameters(namedCurves, clientECPointFormats, sigIn); + + byte[] point = TlsUtils.readOpaque8(sigIn); + + byte[] sigByte = TlsUtils.readOpaque16(input); + if (!signer.verifySignature(sigByte)) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + + this.ecAgreeServerPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey( + clientECPointFormats, curve_params, point)); + } + + public void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException + { + /* + * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with + * ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because + * the use of a long-term ECDH client key would jeopardize the forward secrecy property of + * these algorithms. + */ + short[] types = certificateRequest.getCertificateTypes(); + for (int i = 0; i < types.length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public void processClientCredentials(TlsCredentials clientCredentials) + throws IOException + { + if (clientCredentials instanceof TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters) + { + Signer signer = tlsSigner.createVerifyer(this.serverPublicKey); + signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); + signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); + return signer; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java new file mode 100644 index 00000000..26c09759 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java @@ -0,0 +1,250 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.util.PublicKeyFactory; + +/** + * ECDH key exchange (see RFC 4492) + */ +public class TlsECDHKeyExchange + extends AbstractTlsKeyExchange +{ + + protected TlsSigner tlsSigner; + protected int[] namedCurves; + protected short[] clientECPointFormats, serverECPointFormats; + + protected AsymmetricKeyParameter serverPublicKey; + protected ECPublicKeyParameters ecAgreeServerPublicKey; + protected TlsAgreementCredentials agreementCredentials; + protected ECPrivateKeyParameters ecAgreeClientPrivateKey; + + protected ECPrivateKeyParameters ecAgreeServerPrivateKey; + protected ECPublicKeyParameters ecAgreeClientPublicKey; + + public TlsECDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves, + short[] clientECPointFormats, short[] serverECPointFormats) + { + + super(keyExchange, supportedSignatureAlgorithms); + + switch (keyExchange) + { + case KeyExchangeAlgorithm.ECDHE_RSA: + this.tlsSigner = new TlsRSASigner(); + break; + case KeyExchangeAlgorithm.ECDHE_ECDSA: + this.tlsSigner = new TlsECDSASigner(); + break; + case KeyExchangeAlgorithm.ECDH_RSA: + case KeyExchangeAlgorithm.ECDH_ECDSA: + this.tlsSigner = null; + break; + default: + throw new IllegalArgumentException("unsupported key exchange algorithm"); + } + + this.keyExchange = keyExchange; + this.namedCurves = namedCurves; + this.clientECPointFormats = clientECPointFormats; + this.serverECPointFormats = serverECPointFormats; + } + + public void init(TlsContext context) + { + super.init(context); + + if (this.tlsSigner != null) + { + this.tlsSigner.init(context); + } + } + + public void skipServerCredentials() + throws IOException + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { + + if (serverCertificate.isEmpty()) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + try + { + this.serverPublicKey = PublicKeyFactory.createKey(keyInfo); + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + if (tlsSigner == null) + { + try + { + this.ecAgreeServerPublicKey = TlsECCUtils + .validateECPublicKey((ECPublicKeyParameters)this.serverPublicKey); + } + catch (ClassCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement); + } + else + { + if (!tlsSigner.isValidPublicKey(this.serverPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + } + + super.processServerCertificate(serverCertificate); + } + + public boolean requiresServerKeyExchange() + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.ECDHE_ECDSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + case KeyExchangeAlgorithm.ECDH_anon: + return true; + default: + return false; + } + } + + public void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException + { + /* + * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with + * ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because + * the use of a long-term ECDH client key would jeopardize the forward secrecy property of + * these algorithms. + */ + short[] types = certificateRequest.getCertificateTypes(); + for (int i = 0; i < types.length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + case ClientCertificateType.rsa_fixed_ecdh: + case ClientCertificateType.ecdsa_fixed_ecdh: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public void processClientCredentials(TlsCredentials clientCredentials) + throws IOException + { + if (clientCredentials instanceof TlsAgreementCredentials) + { + // TODO Validate client cert has matching parameters (see 'TlsECCUtils.areOnSameCurve')? + + this.agreementCredentials = (TlsAgreementCredentials)clientCredentials; + } + else if (clientCredentials instanceof TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public void generateClientKeyExchange(OutputStream output) + throws IOException + { + if (agreementCredentials != null) + { + return; + } + + AsymmetricCipherKeyPair ecAgreeClientKeyPair = TlsECCUtils.generateECKeyPair(context.getSecureRandom(), + ecAgreeServerPublicKey.getParameters()); + this.ecAgreeClientPrivateKey = (ECPrivateKeyParameters)ecAgreeClientKeyPair.getPrivate(); + + byte[] point = TlsECCUtils.serializeECPublicKey(serverECPointFormats, + (ECPublicKeyParameters)ecAgreeClientKeyPair.getPublic()); + + TlsUtils.writeOpaque8(point, output); + } + + public void processClientCertificate(Certificate clientCertificate) + throws IOException + { + + // TODO Extract the public key + // TODO If the certificate is 'fixed', take the public key as ecAgreeClientPublicKey + } + + public void processClientKeyExchange(InputStream input) + throws IOException + { + + if (ecAgreeClientPublicKey != null) + { + // For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate + return; + } + + byte[] point = TlsUtils.readOpaque8(input); + + ECDomainParameters curve_params = this.ecAgreeServerPrivateKey.getParameters(); + + this.ecAgreeClientPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey( + serverECPointFormats, curve_params, point)); + } + + public byte[] generatePremasterSecret() + throws IOException + { + if (agreementCredentials != null) + { + return agreementCredentials.generateAgreement(ecAgreeServerPublicKey); + } + + if (ecAgreeServerPrivateKey != null) + { + return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeClientPublicKey, ecAgreeServerPrivateKey); + } + + if (ecAgreeClientPrivateKey != null) + { + return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeServerPublicKey, ecAgreeClientPrivateKey); + } + + throw new TlsFatalAlert(AlertDescription.internal_error); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java new file mode 100644 index 00000000..6809815c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; + +public class TlsECDSASigner + extends TlsDSASigner +{ + + public boolean isValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey instanceof ECPublicKeyParameters; + } + + protected DSA createDSAImpl() + { + return new ECDSASigner(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java new file mode 100644 index 00000000..2680136d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java @@ -0,0 +1,11 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public interface TlsEncryptionCredentials + extends TlsCredentials +{ + + byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsFatalAlert.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsFatalAlert.java new file mode 100644 index 00000000..61cec318 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsFatalAlert.java @@ -0,0 +1,21 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public class TlsFatalAlert + extends IOException +{ + private static final long serialVersionUID = 3584313123679111168L; + + private short alertDescription; + + public TlsFatalAlert(short alertDescription) + { + this.alertDescription = alertDescription; + } + + public short getAlertDescription() + { + return alertDescription; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java new file mode 100644 index 00000000..b17b8d7c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java @@ -0,0 +1,14 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.Digest; + +interface TlsHandshakeHash + extends Digest +{ + + void init(TlsContext context); + + TlsHandshakeHash commit(); + + TlsHandshakeHash fork(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsInputStream.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsInputStream.java new file mode 100644 index 00000000..9509dc4f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsInputStream.java @@ -0,0 +1,41 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An InputStream for an TLS 1.0 connection. + */ +class TlsInputStream + extends InputStream +{ + private byte[] buf = new byte[1]; + private TlsProtocol handler = null; + + TlsInputStream(TlsProtocol handler) + { + this.handler = handler; + } + + public int read(byte[] buf, int offset, int len) + throws IOException + { + return this.handler.readApplicationData(buf, offset, len); + } + + public int read() + throws IOException + { + if (this.read(buf) < 0) + { + return -1; + } + return buf[0] & 0xff; + } + + public void close() + throws IOException + { + handler.close(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsKeyExchange.java new file mode 100644 index 00000000..91590cec --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsKeyExchange.java @@ -0,0 +1,55 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A generic interface for key exchange implementations in TLS 1.0/1.1. + */ +public interface TlsKeyExchange +{ + + void init(TlsContext context); + + void skipServerCredentials() + throws IOException; + + void processServerCredentials(TlsCredentials serverCredentials) + throws IOException; + + void processServerCertificate(Certificate serverCertificate) + throws IOException; + + boolean requiresServerKeyExchange(); + + byte[] generateServerKeyExchange() + throws IOException; + + void skipServerKeyExchange() + throws IOException; + + void processServerKeyExchange(InputStream input) + throws IOException; + + void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException; + + void skipClientCredentials() + throws IOException; + + void processClientCredentials(TlsCredentials clientCredentials) + throws IOException; + + void processClientCertificate(Certificate clientCertificate) + throws IOException; + + void generateClientKeyExchange(OutputStream output) + throws IOException; + + void processClientKeyExchange(InputStream input) + throws IOException; + + byte[] generatePremasterSecret() + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java new file mode 100644 index 00000000..ec111302 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java @@ -0,0 +1,172 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.digests.LongDigest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; + +/** + * A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest. + */ +public class TlsMac +{ + + protected TlsContext context; + protected byte[] secret; + protected Mac mac; + protected int digestBlockSize; + protected int digestOverhead; + + /** + * Generate a new instance of an TlsMac. + * + * @param context the TLS client context + * @param digest The digest to use. + * @param key A byte-array where the key for this mac is located. + * @param keyOff The number of bytes to skip, before the key starts in the buffer. + * @param len The length of the key. + */ + public TlsMac(TlsContext context, Digest digest, byte[] key, int keyOff, int keyLen) + { + this.context = context; + + KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen); + + this.secret = Arrays.clone(keyParameter.getKey()); + + // TODO This should check the actual algorithm, not rely on the engine type + if (digest instanceof LongDigest) + { + this.digestBlockSize = 128; + this.digestOverhead = 16; + } + else + { + this.digestBlockSize = 64; + this.digestOverhead = 8; + } + + if (context.getServerVersion().isSSL()) + { + this.mac = new SSL3Mac(digest); + + // TODO This should check the actual algorithm, not assume based on the digest size + if (digest.getDigestSize() == 20) + { + /* + * NOTE: When SHA-1 is used with the SSL 3.0 MAC, the secret + input pad is not + * digest block-aligned. + */ + this.digestOverhead = 4; + } + } + else + { + this.mac = new HMac(digest); + + // NOTE: The input pad for HMAC is always a full digest block + } + + this.mac.init(keyParameter); + } + + /** + * @return the MAC write secret + */ + public byte[] getMACSecret() + { + return this.secret; + } + + /** + * @return The Keysize of the mac. + */ + public int getSize() + { + return mac.getMacSize(); + } + + /** + * Calculate the MAC for some given data. + * + * @param type The message type of the message. + * @param message A byte-buffer containing the message. + * @param offset The number of bytes to skip, before the message starts. + * @param length The length of the message. + * @return A new byte-buffer containing the MAC value. + */ + public byte[] calculateMac(long seqNo, short type, byte[] message, int offset, int length) + { + + ProtocolVersion serverVersion = context.getServerVersion(); + boolean isSSL = serverVersion.isSSL(); + + ByteArrayOutputStream bosMac = new ByteArrayOutputStream(isSSL ? 11 : 13); + try + { + TlsUtils.writeUint64(seqNo, bosMac); + TlsUtils.writeUint8(type, bosMac); + + if (!isSSL) + { + TlsUtils.writeVersion(serverVersion, bosMac); + } + + TlsUtils.writeUint16(length, bosMac); + } + catch (IOException e) + { + // This should never happen + throw new IllegalStateException("Internal error during mac calculation"); + } + + byte[] macHeader = bosMac.toByteArray(); + mac.update(macHeader, 0, macHeader.length); + mac.update(message, offset, length); + + byte[] result = new byte[mac.getMacSize()]; + mac.doFinal(result, 0); + return result; + } + + public byte[] calculateMacConstantTime(long seqNo, short type, byte[] message, int offset, int length, + int fullLength, byte[] dummyData) + { + + /* + * Actual MAC only calculated on 'length' bytes... + */ + byte[] result = calculateMac(seqNo, type, message, offset, length); + + /* + * ...but ensure a constant number of complete digest blocks are processed (as many as would + * be needed for 'fullLength' bytes of input). + */ + int headerLength = context.getServerVersion().isSSL() ? 11 : 13; + + // How many extra full blocks do we need to calculate? + int extra = getDigestBlockCount(headerLength + fullLength) - getDigestBlockCount(headerLength + length); + + while (--extra >= 0) + { + mac.update(dummyData, 0, digestBlockSize); + } + + // One more byte in case the implementation is "lazy" about processing blocks + mac.update(dummyData[0]); + mac.reset(); + + return result; + } + + private int getDigestBlockCount(int inputLength) + { + // NOTE: This calculation assumes a minimum of 1 pad byte + return (inputLength + digestOverhead) / digestBlockSize; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java new file mode 100644 index 00000000..d5b2b98e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java @@ -0,0 +1,127 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.util.Arrays; + +/** + * A NULL CipherSuite with optional MAC + */ +public class TlsNullCipher + implements TlsCipher +{ + protected TlsContext context; + + protected TlsMac writeMac; + protected TlsMac readMac; + + public TlsNullCipher(TlsContext context) + { + this.context = context; + this.writeMac = null; + this.readMac = null; + } + + public TlsNullCipher(TlsContext context, Digest clientWriteDigest, Digest serverWriteDigest) + throws IOException + { + + if ((clientWriteDigest == null) != (serverWriteDigest == null)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.context = context; + + TlsMac clientWriteMac = null, serverWriteMac = null; + + if (clientWriteDigest != null) + { + + int key_block_size = clientWriteDigest.getDigestSize() + + serverWriteDigest.getDigestSize(); + byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size); + + int offset = 0; + + clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.getDigestSize()); + offset += clientWriteDigest.getDigestSize(); + + serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.getDigestSize()); + offset += serverWriteDigest.getDigestSize(); + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + if (context.isServer()) + { + writeMac = serverWriteMac; + readMac = clientWriteMac; + } + else + { + writeMac = clientWriteMac; + readMac = serverWriteMac; + } + } + + public int getPlaintextLimit(int ciphertextLimit) + { + int result = ciphertextLimit; + if (writeMac != null) + { + result -= writeMac.getSize(); + } + return result; + } + + public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) + throws IOException + { + + if (writeMac == null) + { + return Arrays.copyOfRange(plaintext, offset, offset + len); + } + + byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + byte[] ciphertext = new byte[len + mac.length]; + System.arraycopy(plaintext, offset, ciphertext, 0, len); + System.arraycopy(mac, 0, ciphertext, len, mac.length); + return ciphertext; + } + + public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) + throws IOException + { + + if (readMac == null) + { + return Arrays.copyOfRange(ciphertext, offset, offset + len); + } + + int macSize = readMac.getSize(); + if (len < macSize) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + int macInputLen = len - macSize; + + byte[] receivedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + len); + byte[] computedMac = readMac.calculateMac(seqNo, type, ciphertext, offset, macInputLen); + + if (!Arrays.constantTimeAreEqual(receivedMac, computedMac)) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + return Arrays.copyOfRange(ciphertext, offset, offset + macInputLen); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsNullCompression.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsNullCompression.java new file mode 100644 index 00000000..13a85abf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsNullCompression.java @@ -0,0 +1,17 @@ +package org.bouncycastle.crypto.tls; + +import java.io.OutputStream; + +public class TlsNullCompression + implements TlsCompression +{ + public OutputStream compress(OutputStream output) + { + return output; + } + + public OutputStream decompress(OutputStream output) + { + return output; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsOutputStream.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsOutputStream.java new file mode 100644 index 00000000..d9532414 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsOutputStream.java @@ -0,0 +1,44 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An OutputStream for an TLS connection. + */ +class TlsOutputStream + extends OutputStream +{ + private byte[] buf = new byte[1]; + private TlsProtocol handler; + + TlsOutputStream(TlsProtocol handler) + { + this.handler = handler; + } + + public void write(byte buf[], int offset, int len) + throws IOException + { + this.handler.writeData(buf, offset, len); + } + + public void write(int arg0) + throws IOException + { + buf[0] = (byte)arg0; + this.write(buf, 0, 1); + } + + public void close() + throws IOException + { + handler.close(); + } + + public void flush() + throws IOException + { + handler.flush(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsPSKIdentity.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsPSKIdentity.java new file mode 100644 index 00000000..2f6eea26 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsPSKIdentity.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +public interface TlsPSKIdentity +{ + void skipIdentityHint(); + + void notifyIdentityHint(byte[] psk_identity_hint); + + byte[] getPSKIdentity(); + + byte[] getPSK(); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java new file mode 100644 index 00000000..cfabb76c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java @@ -0,0 +1,210 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.util.PublicKeyFactory; + +/** + * TLS 1.0 PSK key exchange (RFC 4279). + */ +public class TlsPSKKeyExchange + extends AbstractTlsKeyExchange +{ + + protected TlsPSKIdentity pskIdentity; + + protected byte[] psk_identity_hint = null; + + protected DHPublicKeyParameters dhAgreeServerPublicKey = null; + protected DHPrivateKeyParameters dhAgreeClientPrivateKey = null; + + protected AsymmetricKeyParameter serverPublicKey = null; + protected RSAKeyParameters rsaServerPublicKey = null; + protected byte[] premasterSecret; + + public TlsPSKKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, TlsPSKIdentity pskIdentity) + { + super(keyExchange, supportedSignatureAlgorithms); + + switch (keyExchange) + { + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.RSA_PSK: + case KeyExchangeAlgorithm.DHE_PSK: + break; + default: + throw new IllegalArgumentException("unsupported key exchange algorithm"); + } + + this.pskIdentity = pskIdentity; + } + + public void skipServerCredentials() + throws IOException + { + if (keyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { + + if (keyExchange != KeyExchangeAlgorithm.RSA_PSK) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + if (serverCertificate.isEmpty()) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + try + { + this.serverPublicKey = PublicKeyFactory.createKey(keyInfo); + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + // Sanity check the PublicKeyFactory + if (this.serverPublicKey.isPrivate()) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.rsaServerPublicKey = validateRSAPublicKey((RSAKeyParameters)this.serverPublicKey); + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyEncipherment); + + super.processServerCertificate(serverCertificate); + } + + public boolean requiresServerKeyExchange() + { + return keyExchange == KeyExchangeAlgorithm.DHE_PSK; + } + + public void processServerKeyExchange(InputStream input) + throws IOException + { + + this.psk_identity_hint = TlsUtils.readOpaque16(input); + + if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + byte[] pBytes = TlsUtils.readOpaque16(input); + byte[] gBytes = TlsUtils.readOpaque16(input); + byte[] YsBytes = TlsUtils.readOpaque16(input); + + BigInteger p = new BigInteger(1, pBytes); + BigInteger g = new BigInteger(1, gBytes); + BigInteger Ys = new BigInteger(1, YsBytes); + + this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey(new DHPublicKeyParameters(Ys, + new DHParameters(p, g))); + } + } + + public void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public void processClientCredentials(TlsCredentials clientCredentials) + throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public void generateClientKeyExchange(OutputStream output) + throws IOException + { + + if (psk_identity_hint == null) + { + pskIdentity.skipIdentityHint(); + } + else + { + pskIdentity.notifyIdentityHint(psk_identity_hint); + } + + byte[] psk_identity = pskIdentity.getPSKIdentity(); + + TlsUtils.writeOpaque16(psk_identity, output); + + if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey, + output); + } + else if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + this.dhAgreeClientPrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(), + dhAgreeServerPublicKey.getParameters(), output); + } + } + + public byte[] generatePremasterSecret() + throws IOException + { + + byte[] psk = pskIdentity.getPSK(); + byte[] other_secret = generateOtherSecret(psk.length); + + ByteArrayOutputStream buf = new ByteArrayOutputStream(4 + other_secret.length + psk.length); + TlsUtils.writeOpaque16(other_secret, buf); + TlsUtils.writeOpaque16(psk, buf); + return buf.toByteArray(); + } + + protected byte[] generateOtherSecret(int pskLength) + { + + if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + return TlsDHUtils.calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); + } + + if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + return this.premasterSecret; + } + + return new byte[pskLength]; + } + + protected RSAKeyParameters validateRSAPublicKey(RSAKeyParameters key) + throws IOException + { + // TODO What is the minimum bit length required? + // key.getModulus().bitLength(); + + if (!key.getExponent().isProbablePrime(2)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return key; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java new file mode 100644 index 00000000..e4080022 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.tls; + +public interface TlsPeer +{ + + /** + * This method will be called when an alert is raised by the protocol. + * + * @param alertLevel {@link AlertLevel} + * @param alertDescription {@link AlertDescription} + * @param message A human-readable message explaining what caused this alert. May be null. + * @param cause The exception that caused this alert to be raised. May be null. + */ + void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause); + + /** + * This method will be called when an alert is received from the remote peer. + * + * @param alertLevel {@link AlertLevel} + * @param alertDescription {@link AlertDescription} + */ + void notifyAlertReceived(short alertLevel, short alertDescription); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java new file mode 100644 index 00000000..6d8e3d3c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java @@ -0,0 +1,943 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; + +/** + * An implementation of all high level protocols in TLS 1.0/1.1. + */ +public abstract class TlsProtocol +{ + + protected static final Integer EXT_RenegotiationInfo = Integers.valueOf(ExtensionType.renegotiation_info); + protected static final Integer EXT_SessionTicket = Integers.valueOf(ExtensionType.session_ticket); + + private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack"; + + /* + * Our Connection states + */ + protected static final short CS_START = 0; + protected static final short CS_CLIENT_HELLO = 1; + protected static final short CS_SERVER_HELLO = 2; + protected static final short CS_SERVER_SUPPLEMENTAL_DATA = 3; + protected static final short CS_SERVER_CERTIFICATE = 4; + protected static final short CS_SERVER_KEY_EXCHANGE = 5; + protected static final short CS_CERTIFICATE_REQUEST = 6; + protected static final short CS_SERVER_HELLO_DONE = 7; + protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 8; + protected static final short CS_CLIENT_CERTIFICATE = 9; + protected static final short CS_CLIENT_KEY_EXCHANGE = 10; + protected static final short CS_CERTIFICATE_VERIFY = 11; + protected static final short CS_CLIENT_CHANGE_CIPHER_SPEC = 12; + protected static final short CS_CLIENT_FINISHED = 13; + protected static final short CS_SERVER_SESSION_TICKET = 14; + protected static final short CS_SERVER_CHANGE_CIPHER_SPEC = 15; + protected static final short CS_SERVER_FINISHED = 16; + + /* + * Queues for data from some protocols. + */ + private ByteQueue applicationDataQueue = new ByteQueue(); + private ByteQueue changeCipherSpecQueue = new ByteQueue(); + private ByteQueue alertQueue = new ByteQueue(); + private ByteQueue handshakeQueue = new ByteQueue(); + + /* + * The Record Stream we use + */ + protected RecordStream recordStream; + protected SecureRandom secureRandom; + + private TlsInputStream tlsInputStream = null; + private TlsOutputStream tlsOutputStream = null; + + private volatile boolean closed = false; + private volatile boolean failedWithError = false; + private volatile boolean appDataReady = false; + private volatile boolean writeExtraEmptyRecords = true; + private byte[] expected_verify_data = null; + + protected SecurityParameters securityParameters = null; + + protected short connection_state = CS_START; + protected boolean secure_renegotiation = false; + protected boolean expectSessionTicket = false; + + public TlsProtocol(InputStream input, OutputStream output, SecureRandom secureRandom) + { + this.recordStream = new RecordStream(this, input, output); + this.secureRandom = secureRandom; + } + + protected abstract AbstractTlsContext getContext(); + + protected abstract TlsPeer getPeer(); + + protected abstract void handleChangeCipherSpecMessage() + throws IOException; + + protected abstract void handleHandshakeMessage(short type, byte[] buf) + throws IOException; + + protected void handleWarningMessage(short description) + throws IOException + { + + } + + protected void completeHandshake() + throws IOException + { + + this.expected_verify_data = null; + + /* + * We will now read data, until we have completed the handshake. + */ + while (this.connection_state != CS_SERVER_FINISHED) + { + safeReadRecord(); + } + + this.recordStream.finaliseHandshake(); + + ProtocolVersion version = getContext().getServerVersion(); + this.writeExtraEmptyRecords = version.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10); + + /* + * If this was an initial handshake, we are now ready to send and receive application data. + */ + if (!appDataReady) + { + this.appDataReady = true; + + this.tlsInputStream = new TlsInputStream(this); + this.tlsOutputStream = new TlsOutputStream(this); + } + } + + protected void processRecord(short protocol, byte[] buf, int offset, int len) + throws IOException + { + /* + * Have a look at the protocol type, and add it to the correct queue. + */ + switch (protocol) + { + case ContentType.change_cipher_spec: + changeCipherSpecQueue.addData(buf, offset, len); + processChangeCipherSpec(); + break; + case ContentType.alert: + alertQueue.addData(buf, offset, len); + processAlert(); + break; + case ContentType.handshake: + handshakeQueue.addData(buf, offset, len); + processHandshake(); + break; + case ContentType.application_data: + if (!appDataReady) + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + applicationDataQueue.addData(buf, offset, len); + processApplicationData(); + break; + default: + /* + * Uh, we don't know this protocol. + * + * RFC2246 defines on page 13, that we should ignore this. + */ + } + } + + private void processHandshake() + throws IOException + { + boolean read; + do + { + read = false; + /* + * We need the first 4 bytes, they contain type and length of the message. + */ + if (handshakeQueue.size() >= 4) + { + byte[] beginning = new byte[4]; + handshakeQueue.read(beginning, 0, 4, 0); + ByteArrayInputStream bis = new ByteArrayInputStream(beginning); + short type = TlsUtils.readUint8(bis); + int len = TlsUtils.readUint24(bis); + + /* + * Check if we have enough bytes in the buffer to read the full message. + */ + if (handshakeQueue.size() >= (len + 4)) + { + /* + * Read the message. + */ + byte[] buf = new byte[len]; + handshakeQueue.read(buf, 0, len, 4); + handshakeQueue.removeData(len + 4); + + /* + * RFC 2246 7.4.9. The value handshake_messages includes all handshake messages + * starting at client hello up to, but not including, this finished message. + * [..] Note: [Also,] Hello Request messages are omitted from handshake hashes. + */ + switch (type) + { + case HandshakeType.hello_request: + break; + case HandshakeType.finished: + { + + if (this.expected_verify_data == null) + { + this.expected_verify_data = createVerifyData(!getContext().isServer()); + } + + // NB: Fall through to next case label + } + default: + recordStream.updateHandshakeData(beginning, 0, 4); + recordStream.updateHandshakeData(buf, 0, len); + break; + } + + /* + * Now, parse the message. + */ + handleHandshakeMessage(type, buf); + read = true; + } + } + } + while (read); + } + + private void processApplicationData() + { + /* + * There is nothing we need to do here. + * + * This function could be used for callbacks when application data arrives in the future. + */ + } + + private void processAlert() + throws IOException + { + while (alertQueue.size() >= 2) + { + /* + * An alert is always 2 bytes. Read the alert. + */ + byte[] tmp = new byte[2]; + alertQueue.read(tmp, 0, 2, 0); + alertQueue.removeData(2); + short level = tmp[0]; + short description = tmp[1]; + + getPeer().notifyAlertReceived(level, description); + + if (level == AlertLevel.fatal) + { + + this.failedWithError = true; + this.closed = true; + /* + * Now try to close the stream, ignore errors. + */ + try + { + recordStream.close(); + } + catch (Exception e) + { + + } + throw new IOException(TLS_ERROR_MESSAGE); + } + else + { + + /* + * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own + * and close down the connection immediately, discarding any pending writes. + */ + // TODO Can close_notify be a fatal alert? + if (description == AlertDescription.close_notify) + { + handleClose(false); + } + + /* + * If it is just a warning, we continue. + */ + handleWarningMessage(description); + } + } + } + + /** + * This method is called, when a change cipher spec message is received. + * + * @throws IOException If the message has an invalid content or the handshake is not in the correct + * state. + */ + private void processChangeCipherSpec() + throws IOException + { + while (changeCipherSpecQueue.size() > 0) + { + /* + * A change cipher spec message is only one byte with the value 1. + */ + byte[] b = new byte[1]; + changeCipherSpecQueue.read(b, 0, 1, 0); + changeCipherSpecQueue.removeData(1); + if (b[0] != 1) + { + /* + * This should never happen. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + + recordStream.receivedReadCipherSpec(); + + handleChangeCipherSpecMessage(); + } + } + + /** + * Read data from the network. The method will return immediately, if there is still some data + * left in the buffer, or block until some application data has been read from the network. + * + * @param buf The buffer where the data will be copied to. + * @param offset The position where the data will be placed in the buffer. + * @param len The maximum number of bytes to read. + * @return The number of bytes read. + * @throws IOException If something goes wrong during reading data. + */ + protected int readApplicationData(byte[] buf, int offset, int len) + throws IOException + { + + if (len < 1) + { + return 0; + } + + while (applicationDataQueue.size() == 0) + { + /* + * We need to read some data. + */ + if (this.closed) + { + if (this.failedWithError) + { + /* + * Something went terribly wrong, we should throw an IOException + */ + throw new IOException(TLS_ERROR_MESSAGE); + } + + /* + * Connection has been closed, there is no more data to read. + */ + return -1; + } + + safeReadRecord(); + } + len = Math.min(len, applicationDataQueue.size()); + applicationDataQueue.read(buf, offset, len, 0); + applicationDataQueue.removeData(len); + return len; + } + + protected void safeReadRecord() + throws IOException + { + try + { + recordStream.readRecord(); + } + catch (TlsFatalAlert e) + { + if (!this.closed) + { + this.failWithError(AlertLevel.fatal, e.getAlertDescription()); + } + throw e; + } + catch (IOException e) + { + if (!this.closed) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + throw e; + } + catch (RuntimeException e) + { + if (!this.closed) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + throw e; + } + } + + protected void safeWriteRecord(short type, byte[] buf, int offset, int len) + throws IOException + { + try + { + recordStream.writeRecord(type, buf, offset, len); + } + catch (TlsFatalAlert e) + { + if (!this.closed) + { + this.failWithError(AlertLevel.fatal, e.getAlertDescription()); + } + throw e; + } + catch (IOException e) + { + if (!closed) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + throw e; + } + catch (RuntimeException e) + { + if (!closed) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + throw e; + } + } + + /** + * Send some application data to the remote system. + * <p/> + * The method will handle fragmentation internally. + * + * @param buf The buffer with the data. + * @param offset The position in the buffer where the data is placed. + * @param len The length of the data. + * @throws IOException If something goes wrong during sending. + */ + protected void writeData(byte[] buf, int offset, int len) + throws IOException + { + if (this.closed) + { + if (this.failedWithError) + { + throw new IOException(TLS_ERROR_MESSAGE); + } + + throw new IOException("Sorry, connection has been closed, you cannot write more data"); + } + + while (len > 0) + { + /* + * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are + * potentially useful as a traffic analysis countermeasure. + */ + if (this.writeExtraEmptyRecords) + { + /* + * Protect against known IV attack! + * + * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE. + */ + safeWriteRecord(ContentType.application_data, TlsUtils.EMPTY_BYTES, 0, 0); + } + + /* + * We are only allowed to write fragments up to 2^14 bytes. + */ + int toWrite = Math.min(len, 1 << 14); + + safeWriteRecord(ContentType.application_data, buf, offset, toWrite); + + offset += toWrite; + len -= toWrite; + } + } + + /** + * @return An OutputStream which can be used to send data. + */ + public OutputStream getOutputStream() + { + return this.tlsOutputStream; + } + + /** + * @return An InputStream which can be used to read data. + */ + public InputStream getInputStream() + { + return this.tlsInputStream; + } + + /** + * Terminate this connection with an alert. + * <p/> + * Can be used for normal closure too. + * + * @param alertLevel The level of the alert, an be AlertLevel.fatal or AL_warning. + * @param alertDescription The exact alert message. + * @throws IOException If alert was fatal. + */ + protected void failWithError(short alertLevel, short alertDescription) + throws IOException + { + /* + * Check if the connection is still open. + */ + if (!closed) + { + /* + * Prepare the message + */ + this.closed = true; + + if (alertLevel == AlertLevel.fatal) + { + /* + * This is a fatal message. + */ + this.failedWithError = true; + } + raiseAlert(alertLevel, alertDescription, null, null); + recordStream.close(); + if (alertLevel == AlertLevel.fatal) + { + throw new IOException(TLS_ERROR_MESSAGE); + } + } + else + { + throw new IOException(TLS_ERROR_MESSAGE); + } + } + + protected void processFinishedMessage(ByteArrayInputStream buf) + throws IOException + { + + byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf); + + assertEmpty(buf); + + /* + * Compare both checksums. + */ + if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data)) + { + /* + * Wrong checksum in the finished message. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.decrypt_error); + } + } + + protected void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause) + throws IOException + { + + getPeer().notifyAlertRaised(alertLevel, alertDescription, message, cause); + + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + + safeWriteRecord(ContentType.alert, error, 0, 2); + } + + protected void raiseWarning(short alertDescription, String message) + throws IOException + { + raiseAlert(AlertLevel.warning, alertDescription, message, null); + } + + protected void sendCertificateMessage(Certificate certificate) + throws IOException + { + + if (certificate == null) + { + certificate = Certificate.EMPTY_CHAIN; + } + + if (certificate.getLength() == 0) + { + TlsContext context = getContext(); + if (!context.isServer()) + { + ProtocolVersion serverVersion = getContext().getServerVersion(); + if (serverVersion.isSSL()) + { + String message = serverVersion.toString() + " client didn't provide credentials"; + raiseWarning(AlertDescription.no_certificate, message); + return; + } + } + } + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.certificate, bos); + + // Reserve space for length + TlsUtils.writeUint24(0, bos); + + certificate.encode(bos); + byte[] message = bos.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendChangeCipherSpecMessage() + throws IOException + { + byte[] message = new byte[]{1}; + safeWriteRecord(ContentType.change_cipher_spec, message, 0, message.length); + recordStream.sentWriteCipherSpec(); + } + + protected void sendFinishedMessage() + throws IOException + { + byte[] verify_data = createVerifyData(getContext().isServer()); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.finished, bos); + TlsUtils.writeUint24(verify_data.length, bos); + bos.write(verify_data); + byte[] message = bos.toByteArray(); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendSupplementalDataMessage(Vector supplementalData) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.supplemental_data, buf); + + // Reserve space for length + TlsUtils.writeUint24(0, buf); + + writeSupplementalData(buf, supplementalData); + + byte[] message = buf.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected byte[] createVerifyData(boolean isServer) + { + TlsContext context = getContext(); + + if (isServer) + { + return TlsUtils.calculateVerifyData(context, "server finished", + recordStream.getCurrentHash(TlsUtils.SSL_SERVER)); + } + + return TlsUtils.calculateVerifyData(context, "client finished", + recordStream.getCurrentHash(TlsUtils.SSL_CLIENT)); + } + + /** + * Closes this connection. + * + * @throws IOException If something goes wrong during closing. + */ + public void close() + throws IOException + { + handleClose(true); + } + + protected void handleClose(boolean user_canceled) + throws IOException + { + if (!closed) + { + if (user_canceled && !appDataReady) + { + raiseWarning(AlertDescription.user_canceled, "User canceled handshake"); + } + this.failWithError(AlertLevel.warning, AlertDescription.close_notify); + } + } + + protected void flush() + throws IOException + { + recordStream.flush(); + } + + protected static boolean arrayContains(short[] a, short n) + { + for (int i = 0; i < a.length; ++i) + { + if (a[i] == n) + { + return true; + } + } + return false; + } + + protected static boolean arrayContains(int[] a, int n) + { + for (int i = 0; i < a.length; ++i) + { + if (a[i] == n) + { + return true; + } + } + return false; + } + + /** + * Make sure the InputStream 'buf' now empty. Fail otherwise. + * + * @param buf The InputStream to check. + * @throws IOException If 'buf' is not empty. + */ + protected static void assertEmpty(ByteArrayInputStream buf) + throws IOException + { + if (buf.available() > 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + } + + protected static byte[] createRandomBlock(SecureRandom random) + { + byte[] result = new byte[32]; + random.nextBytes(result); + TlsUtils.writeGMTUnixTime(result, 0); + return result; + } + + protected static byte[] createRenegotiationInfo(byte[] renegotiated_connection) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeOpaque8(renegotiated_connection, buf); + return buf.toByteArray(); + } + + protected static void establishMasterSecret(TlsContext context, TlsKeyExchange keyExchange) + throws IOException + { + + byte[] pre_master_secret = keyExchange.generatePremasterSecret(); + + try + { + context.getSecurityParameters().masterSecret = TlsUtils.calculateMasterSecret(context, pre_master_secret); + } + finally + { + // TODO Is there a way to ensure the data is really overwritten? + /* + * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the + * master_secret has been computed. + */ + if (pre_master_secret != null) + { + Arrays.fill(pre_master_secret, (byte)0); + } + } + } + + protected static Hashtable readExtensions(ByteArrayInputStream input) + throws IOException + { + + if (input.available() < 1) + { + return null; + } + + byte[] extBytes = TlsUtils.readOpaque16(input); + + assertEmpty(input); + + ByteArrayInputStream buf = new ByteArrayInputStream(extBytes); + + // Integer -> byte[] + Hashtable extensions = new Hashtable(); + + while (buf.available() > 0) + { + Integer extType = Integers.valueOf(TlsUtils.readUint16(buf)); + byte[] extValue = TlsUtils.readOpaque16(buf); + + /* + * RFC 3546 2.3 There MUST NOT be more than one extension of the same type. + */ + if (null != extensions.put(extType, extValue)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + return extensions; + } + + protected static Vector readSupplementalDataMessage(ByteArrayInputStream input) + throws IOException + { + + byte[] supp_data = TlsUtils.readOpaque24(input); + + assertEmpty(input); + + ByteArrayInputStream buf = new ByteArrayInputStream(supp_data); + + Vector supplementalData = new Vector(); + + while (buf.available() > 0) + { + int supp_data_type = TlsUtils.readUint16(buf); + byte[] data = TlsUtils.readOpaque16(buf); + + supplementalData.addElement(new SupplementalDataEntry(supp_data_type, data)); + } + + return supplementalData; + } + + protected static void writeExtensions(OutputStream output, Hashtable extensions) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + Enumeration keys = extensions.keys(); + while (keys.hasMoreElements()) + { + Integer extType = (Integer)keys.nextElement(); + byte[] extValue = (byte[])extensions.get(extType); + + TlsUtils.writeUint16(extType.intValue(), buf); + TlsUtils.writeOpaque16(extValue, buf); + } + + byte[] extBytes = buf.toByteArray(); + + TlsUtils.writeOpaque16(extBytes, output); + } + + protected static void writeSupplementalData(OutputStream output, Vector supplementalData) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + for (int i = 0; i < supplementalData.size(); ++i) + { + SupplementalDataEntry entry = (SupplementalDataEntry)supplementalData.elementAt(i); + + TlsUtils.writeUint16(entry.getDataType(), buf); + TlsUtils.writeOpaque16(entry.getData(), buf); + } + + byte[] supp_data = buf.toByteArray(); + + TlsUtils.writeOpaque24(supp_data, output); + } + + protected static int getPRFAlgorithm(int ciphersuite) + { + + switch (ciphersuite) + { + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + return PRFAlgorithm.tls_prf_sha256; + + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + return PRFAlgorithm.tls_prf_sha384; + + default: + return PRFAlgorithm.tls_prf_legacy; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsProtocolHandler.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsProtocolHandler.java new file mode 100644 index 00000000..e4fcf285 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsProtocolHandler.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.tls; + +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +/** + * @deprecated use TlsClientProtocol instead + */ +public class TlsProtocolHandler + extends TlsClientProtocol +{ + + public TlsProtocolHandler(InputStream is, OutputStream os) + { + super(is, os); + } + + public TlsProtocolHandler(InputStream is, OutputStream os, SecureRandom sr) + { + super(is, os, sr); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java new file mode 100644 index 00000000..24eec539 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java @@ -0,0 +1,255 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.bouncycastle.util.io.Streams; + +/** + * TLS 1.0/1.1 and SSLv3 RSA key exchange. + */ +public class TlsRSAKeyExchange + extends AbstractTlsKeyExchange +{ + protected AsymmetricKeyParameter serverPublicKey = null; + + protected RSAKeyParameters rsaServerPublicKey = null; + + protected TlsEncryptionCredentials serverCredentials = null; + + protected byte[] premasterSecret; + + public TlsRSAKeyExchange(Vector supportedSignatureAlgorithms) + { + super(KeyExchangeAlgorithm.RSA, supportedSignatureAlgorithms); + } + + public void skipServerCredentials() + throws IOException + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public void processServerCredentials(TlsCredentials serverCredentials) + throws IOException + { + + if (!(serverCredentials instanceof TlsEncryptionCredentials)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + processServerCertificate(serverCredentials.getCertificate()); + + this.serverCredentials = (TlsEncryptionCredentials)serverCredentials; + } + + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { + + if (serverCertificate.isEmpty()) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + try + { + this.serverPublicKey = PublicKeyFactory.createKey(keyInfo); + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + // Sanity check the PublicKeyFactory + if (this.serverPublicKey.isPrivate()) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.rsaServerPublicKey = validateRSAPublicKey((RSAKeyParameters)this.serverPublicKey); + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyEncipherment); + + super.processServerCertificate(serverCertificate); + } + + public void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException + { + short[] types = certificateRequest.getCertificateTypes(); + for (int i = 0; i < types.length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public void processClientCredentials(TlsCredentials clientCredentials) + throws IOException + { + if (!(clientCredentials instanceof TlsSignerCredentials)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public void generateClientKeyExchange(OutputStream output) + throws IOException + { + this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey, output); + } + + public void processClientKeyExchange(InputStream input) + throws IOException + { + + byte[] encryptedPreMasterSecret; + if (context.getServerVersion().isSSL()) + { + // TODO Do any SSLv3 clients actually include the length? + encryptedPreMasterSecret = Streams.readAll(input); + } + else + { + encryptedPreMasterSecret = TlsUtils.readOpaque16(input); + } + + ProtocolVersion clientVersion = context.getClientVersion(); + + /* + * RFC 5246 7.4.7.1. + */ + { + // TODO Provide as configuration option? + boolean versionNumberCheckDisabled = false; + + /* + * See notes regarding Bleichenbacher/Klima attack. The code here implements the first + * construction proposed there, which is RECOMMENDED. + */ + byte[] R = new byte[48]; + this.context.getSecureRandom().nextBytes(R); + + byte[] M = TlsUtils.EMPTY_BYTES; + try + { + M = serverCredentials.decryptPreMasterSecret(encryptedPreMasterSecret); + } + catch (Exception e) + { + /* + * In any case, a TLS server MUST NOT generate an alert if processing an + * RSA-encrypted premaster secret message fails, or the version number is not as + * expected. Instead, it MUST continue the handshake with a randomly generated + * premaster secret. + */ + } + + if (M.length != 48) + { + TlsUtils.writeVersion(clientVersion, R, 0); + this.premasterSecret = R; + } + else + { + /* + * If ClientHello.client_version is TLS 1.1 or higher, server implementations MUST + * check the version number [..]. + */ + if (versionNumberCheckDisabled && clientVersion.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10)) + { + /* + * If the version number is TLS 1.0 or earlier, server implementations SHOULD + * check the version number, but MAY have a configuration option to disable the + * check. + */ + } + else + { + /* + * Note that explicitly constructing the pre_master_secret with the + * ClientHello.client_version produces an invalid master_secret if the client + * has sent the wrong version in the original pre_master_secret. + */ + TlsUtils.writeVersion(clientVersion, M, 0); + } + this.premasterSecret = M; + } + } + } + + public byte[] generatePremasterSecret() + throws IOException + { + if (this.premasterSecret == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + byte[] tmp = this.premasterSecret; + this.premasterSecret = null; + return tmp; + } + + // Would be needed to process RSA_EXPORT server key exchange + // protected void processRSAServerKeyExchange(InputStream is, Signer signer) throws IOException + // { + // InputStream sigIn = is; + // if (signer != null) + // { + // sigIn = new SignerInputStream(is, signer); + // } + // + // byte[] modulusBytes = TlsUtils.readOpaque16(sigIn); + // byte[] exponentBytes = TlsUtils.readOpaque16(sigIn); + // + // if (signer != null) + // { + // byte[] sigByte = TlsUtils.readOpaque16(is); + // + // if (!signer.verifySignature(sigByte)) + // { + // handler.failWithError(AlertLevel.fatal, AlertDescription.bad_certificate); + // } + // } + // + // BigInteger modulus = new BigInteger(1, modulusBytes); + // BigInteger exponent = new BigInteger(1, exponentBytes); + // + // this.rsaServerPublicKey = validateRSAPublicKey(new RSAKeyParameters(false, modulus, + // exponent)); + // } + + protected RSAKeyParameters validateRSAPublicKey(RSAKeyParameters key) + throws IOException + { + // TODO What is the minimum bit length required? + // key.getModulus().bitLength(); + + if (!key.getExponent().isProbablePrime(2)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return key; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java new file mode 100644 index 00000000..d9f7975a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java @@ -0,0 +1,89 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.engines.RSABlindedEngine; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.signers.GenericSigner; +import org.bouncycastle.crypto.signers.RSADigestSigner; +import org.bouncycastle.util.Arrays; + +public class TlsRSASigner + extends AbstractTlsSigner +{ + + public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + throws CryptoException + { + + AsymmetricBlockCipher engine = createRSAImpl(); + engine.init(true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + return engine.processBlock(md5AndSha1, 0, md5AndSha1.length); + } + + public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + throws CryptoException + { + + AsymmetricBlockCipher engine = createRSAImpl(); + engine.init(false, publicKey); + byte[] signed = engine.processBlock(sigBytes, 0, sigBytes.length); + return Arrays.constantTimeAreEqual(signed, md5AndSha1); + } + + public Signer createSigner(AsymmetricKeyParameter privateKey) + { + return makeSigner(new CombinedHash(), true, + new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + } + + public Signer createVerifyer(AsymmetricKeyParameter publicKey) + { + return makeSigner(new CombinedHash(), false, publicKey); + } + + public boolean isValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey instanceof RSAKeyParameters && !publicKey.isPrivate(); + } + + protected Signer makeSigner(Digest d, boolean forSigning, CipherParameters cp) + { + Signer s; + if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion())) + { + /* + * RFC 5246 4.7. In RSA signing, the opaque vector contains the signature generated + * using the RSASSA-PKCS1-v1_5 signature scheme defined in [PKCS1]. + */ + s = new RSADigestSigner(d); + } + else + { + /* + * RFC 5246 4.7. Note that earlier versions of TLS used a different RSA signature scheme + * that did not include a DigestInfo encoding. + */ + s = new GenericSigner(createRSAImpl(), d); + } + s.init(forSigning, cp); + return s; + } + + protected AsymmetricBlockCipher createRSAImpl() + { + /* + * RFC 5264 7.4.7.1. Implementation note: It is now known that remote timing-based attacks + * on TLS are possible, at least when the client and server are on the same LAN. + * Accordingly, implementations that use static RSA keys MUST use RSA blinding or some other + * anti-timing technique, as described in [TIMING]. + */ + return new PKCS1Encoding(new RSABlindedEngine()); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java new file mode 100644 index 00000000..f67e572f --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.OutputStream; + +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.engines.RSABlindedEngine; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +public class TlsRSAUtils +{ + public static byte[] generateEncryptedPreMasterSecret(TlsContext context, RSAKeyParameters rsaServerPublicKey, + OutputStream output) + throws IOException + { + /* + * Choose a PremasterSecret and send it encrypted to the server + */ + byte[] premasterSecret = new byte[48]; + context.getSecureRandom().nextBytes(premasterSecret); + TlsUtils.writeVersion(context.getClientVersion(), premasterSecret, 0); + + PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine()); + encoding.init(true, new ParametersWithRandom(rsaServerPublicKey, context.getSecureRandom())); + + try + { + byte[] encryptedPreMasterSecret = encoding.processBlock(premasterSecret, 0, premasterSecret.length); + + if (context.getServerVersion().isSSL()) + { + // TODO Do any SSLv3 servers actually expect the length? + output.write(encryptedPreMasterSecret); + } + else + { + TlsUtils.writeOpaque16(encryptedPreMasterSecret, output); + } + } + catch (InvalidCipherTextException e) + { + /* + * This should never happen, only during decryption. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return premasterSecret; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsRuntimeException.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRuntimeException.java new file mode 100644 index 00000000..3340e490 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsRuntimeException.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto.tls; + +public class TlsRuntimeException + extends RuntimeException +{ + private static final long serialVersionUID = 1928023487348344086L; + + Throwable e; + + public TlsRuntimeException(String message, Throwable e) + { + super(message); + + this.e = e; + } + + public TlsRuntimeException(String message) + { + super(message); + } + + public Throwable getCause() + { + return e; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java new file mode 100644 index 00000000..b928b91a --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java @@ -0,0 +1,217 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.agreement.srp.SRP6Client; +import org.bouncycastle.crypto.agreement.srp.SRP6Util; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.io.SignerInputStream; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.bouncycastle.util.BigIntegers; + +/** + * TLS 1.1 SRP key exchange (RFC 5054). + */ +public class TlsSRPKeyExchange + extends AbstractTlsKeyExchange +{ + + protected TlsSigner tlsSigner; + protected byte[] identity; + protected byte[] password; + + protected AsymmetricKeyParameter serverPublicKey = null; + + protected byte[] s = null; + protected BigInteger B = null; + protected SRP6Client srpClient = new SRP6Client(); + + public TlsSRPKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, byte[] identity, byte[] password) + { + + super(keyExchange, supportedSignatureAlgorithms); + + switch (keyExchange) + { + case KeyExchangeAlgorithm.SRP: + this.tlsSigner = null; + break; + case KeyExchangeAlgorithm.SRP_RSA: + this.tlsSigner = new TlsRSASigner(); + break; + case KeyExchangeAlgorithm.SRP_DSS: + this.tlsSigner = new TlsDSSSigner(); + break; + default: + throw new IllegalArgumentException("unsupported key exchange algorithm"); + } + + this.keyExchange = keyExchange; + this.identity = identity; + this.password = password; + } + + public void init(TlsContext context) + { + super.init(context); + + if (this.tlsSigner != null) + { + this.tlsSigner.init(context); + } + } + + public void skipServerCredentials() + throws IOException + { + if (tlsSigner != null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { + + if (tlsSigner == null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + if (serverCertificate.isEmpty()) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + try + { + this.serverPublicKey = PublicKeyFactory.createKey(keyInfo); + } + catch (RuntimeException e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + if (!tlsSigner.isValidPublicKey(this.serverPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + + super.processServerCertificate(serverCertificate); + } + + public boolean requiresServerKeyExchange() + { + return true; + } + + public void processServerKeyExchange(InputStream input) + throws IOException + { + + SecurityParameters securityParameters = context.getSecurityParameters(); + + InputStream sigIn = input; + Signer signer = null; + + if (tlsSigner != null) + { + signer = initVerifyer(tlsSigner, securityParameters); + sigIn = new SignerInputStream(input, signer); + } + + byte[] NBytes = TlsUtils.readOpaque16(sigIn); + byte[] gBytes = TlsUtils.readOpaque16(sigIn); + byte[] sBytes = TlsUtils.readOpaque8(sigIn); + byte[] BBytes = TlsUtils.readOpaque16(sigIn); + + if (signer != null) + { + byte[] sigByte = TlsUtils.readOpaque16(input); + + if (!signer.verifySignature(sigByte)) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + } + + BigInteger N = new BigInteger(1, NBytes); + BigInteger g = new BigInteger(1, gBytes); + + // TODO Validate group parameters (see RFC 5054) + // handler.failWithError(AlertLevel.fatal, AlertDescription.insufficient_security); + + this.s = sBytes; + + /* + * RFC 5054 2.5.3: The client MUST abort the handshake with an "illegal_parameter" alert if + * B % N = 0. + */ + try + { + this.B = SRP6Util.validatePublicValue(N, new BigInteger(1, BBytes)); + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + this.srpClient.init(N, g, new SHA1Digest(), context.getSecureRandom()); + } + + public void validateCertificateRequest(CertificateRequest certificateRequest) + throws IOException + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public void processClientCredentials(TlsCredentials clientCredentials) + throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public void generateClientKeyExchange(OutputStream output) + throws IOException + { + byte[] keData = BigIntegers.asUnsignedByteArray(srpClient.generateClientCredentials(s, this.identity, + this.password)); + TlsUtils.writeOpaque16(keData, output); + } + + public byte[] generatePremasterSecret() + throws IOException + { + try + { + // TODO Check if this needs to be a fixed size + return BigIntegers.asUnsignedByteArray(srpClient.calculateSecret(B)); + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters) + { + Signer signer = tlsSigner.createVerifyer(this.serverPublicKey); + signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); + signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); + return signer; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java new file mode 100644 index 00000000..f82f94df --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java @@ -0,0 +1,89 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.util.Integers; + +/** + * RFC 5764 DTLS Extension to Establish Keys for SRTP. + */ +public class TlsSRTPUtils +{ + + public static final Integer EXT_use_srtp = Integers.valueOf(ExtensionType.use_srtp); + + public static void addUseSRTPExtension(Hashtable extensions, UseSRTPData useSRTPData) + throws IOException + { + + extensions.put(EXT_use_srtp, createUseSRTPExtension(useSRTPData)); + } + + public static UseSRTPData getUseSRTPExtension(Hashtable extensions) + throws IOException + { + + if (extensions == null) + { + return null; + } + byte[] extensionValue = (byte[])extensions.get(EXT_use_srtp); + if (extensionValue == null) + { + return null; + } + return readUseSRTPExtension(extensionValue); + } + + public static byte[] createUseSRTPExtension(UseSRTPData useSRTPData) + throws IOException + { + + if (useSRTPData == null) + { + throw new IllegalArgumentException("'useSRTPData' cannot be null"); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + // SRTPProtectionProfiles + int[] protectionProfiles = useSRTPData.getProtectionProfiles(); + TlsUtils.writeUint16(2 * protectionProfiles.length, buf); + TlsUtils.writeUint16Array(protectionProfiles, buf); + + // srtp_mki + TlsUtils.writeOpaque8(useSRTPData.getMki(), buf); + + return buf.toByteArray(); + } + + public static UseSRTPData readUseSRTPExtension(byte[] extensionValue) + throws IOException + { + + if (extensionValue == null) + { + throw new IllegalArgumentException("'extensionValue' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + + // SRTPProtectionProfiles + int length = TlsUtils.readUint16(buf); + if (length < 2 || (length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + int[] protectionProfiles = TlsUtils.readUint16Array(length / 2, buf); + + // srtp_mki + byte[] mki = TlsUtils.readOpaque8(buf); + + TlsProtocol.assertEmpty(buf); + + return new UseSRTPData(protectionProfiles, mki); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java new file mode 100644 index 00000000..0b463912 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java @@ -0,0 +1,89 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; + +public interface TlsServer + extends TlsPeer +{ + + void init(TlsServerContext context); + + void notifyClientVersion(ProtocolVersion clientVersion) + throws IOException; + + void notifyOfferedCipherSuites(int[] offeredCipherSuites) + throws IOException; + + void notifyOfferedCompressionMethods(short[] offeredCompressionMethods) + throws IOException; + + void notifySecureRenegotiation(boolean secureNegotiation) + throws IOException; + + // Hashtable is (Integer -> byte[]) + void processClientExtensions(Hashtable clientExtensions) + throws IOException; + + ProtocolVersion getServerVersion() + throws IOException; + + int getSelectedCipherSuite() + throws IOException; + + short getSelectedCompressionMethod() + throws IOException; + + // Hashtable is (Integer -> byte[]) + Hashtable getServerExtensions() + throws IOException; + + // Vector is (SupplementalDataEntry) + Vector getServerSupplementalData() + throws IOException; + + TlsCredentials getCredentials() + throws IOException; + + TlsKeyExchange getKeyExchange() + throws IOException; + + CertificateRequest getCertificateRequest(); + + // Vector is (SupplementalDataEntry) + void processClientSupplementalData(Vector clientSupplementalData) + throws IOException; + + /** + * Called by the protocol handler to report the client certificate, only if a Certificate + * {@link #getCertificateRequest()} returned non-null. Note: this method is responsible for + * certificate verification and validation. + * + * @param clientCertificate the effective client certificate (may be an empty chain). + * @throws IOException + */ + void notifyClientCertificate(Certificate clientCertificate) + throws IOException; + + TlsCompression getCompression() + throws IOException; + + TlsCipher getCipher() + throws IOException; + + /** + * RFC 5077 3.3. NewSessionTicket Handshake Message. + * <p/> + * This method will be called (only) if a NewSessionTicket extension was sent by the server. See + * <i>RFC 5077 4. Recommended Ticket Construction</i> for recommended format and protection. + * + * @return The ticket. + * @throws IOException + */ + NewSessionTicket getNewSessionTicket() + throws IOException; + + void notifyHandshakeComplete() + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerContext.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerContext.java new file mode 100644 index 00000000..37a0c952 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerContext.java @@ -0,0 +1,6 @@ +package org.bouncycastle.crypto.tls; + +public interface TlsServerContext + extends TlsContext +{ +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java new file mode 100644 index 00000000..2fa40293 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java @@ -0,0 +1,19 @@ +package org.bouncycastle.crypto.tls; + +import java.security.SecureRandom; + +class TlsServerContextImpl + extends AbstractTlsContext + implements TlsServerContext +{ + + TlsServerContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) + { + super(secureRandom, securityParameters); + } + + public boolean isServer() + { + return true; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java new file mode 100644 index 00000000..961669f7 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java @@ -0,0 +1,772 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.bouncycastle.util.Arrays; + +public class TlsServerProtocol + extends TlsProtocol +{ + + protected TlsServer tlsServer = null; + protected TlsServerContextImpl tlsServerContext = null; + + protected int[] offeredCipherSuites; + protected short[] offeredCompressionMethods; + protected Hashtable clientExtensions; + + protected int selectedCipherSuite; + protected short selectedCompressionMethod; + protected Hashtable serverExtensions; + + protected TlsKeyExchange keyExchange = null; + protected TlsCredentials serverCredentials = null; + protected CertificateRequest certificateRequest = null; + + protected short clientCertificateType = -1; + protected Certificate clientCertificate = null; + protected byte[] certificateVerifyHash = null; + + public TlsServerProtocol(InputStream input, OutputStream output, SecureRandom secureRandom) + { + super(input, output, secureRandom); + } + + /** + * Receives a TLS handshake in the role of server + * + * @param tlsServer + * @throws IOException If handshake was not successful. + */ + public void accept(TlsServer tlsServer) + throws IOException + { + + if (tlsServer == null) + { + throw new IllegalArgumentException("'tlsServer' cannot be null"); + } + if (this.tlsServer != null) + { + throw new IllegalStateException("accept can only be called once"); + } + + this.tlsServer = tlsServer; + + this.securityParameters = new SecurityParameters(); + this.securityParameters.entity = ConnectionEnd.server; + this.securityParameters.serverRandom = createRandomBlock(secureRandom); + + this.tlsServerContext = new TlsServerContextImpl(secureRandom, securityParameters); + this.tlsServer.init(tlsServerContext); + this.recordStream.init(tlsServerContext); + + this.recordStream.setRestrictReadVersion(false); + + completeHandshake(); + + this.tlsServer.notifyHandshakeComplete(); + } + + protected AbstractTlsContext getContext() + { + return tlsServerContext; + } + + protected TlsPeer getPeer() + { + return tlsServer; + } + + protected void handleChangeCipherSpecMessage() + throws IOException + { + + switch (this.connection_state) + { + case CS_CLIENT_KEY_EXCHANGE: + { + if (this.certificateVerifyHash != null) + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + // NB: Fall through to next case label + } + case CS_CERTIFICATE_VERIFY: + { + this.connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC; + break; + } + default: + { + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + } + } + + protected void handleHandshakeMessage(short type, byte[] data) + throws IOException + { + + ByteArrayInputStream buf = new ByteArrayInputStream(data); + + switch (type) + { + case HandshakeType.client_hello: + { + switch (this.connection_state) + { + case CS_START: + { + receiveClientHelloMessage(buf); + this.connection_state = CS_CLIENT_HELLO; + + sendServerHelloMessage(); + this.connection_state = CS_SERVER_HELLO; + + // TODO This block could really be done before actually sending the hello + { + securityParameters.prfAlgorithm = getPRFAlgorithm(selectedCipherSuite); + securityParameters.compressionAlgorithm = this.selectedCompressionMethod; + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify + * verify_data_length has a verify_data_length equal to 12. This includes all + * existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + recordStream.notifyHelloComplete(); + } + + Vector serverSupplementalData = tlsServer.getServerSupplementalData(); + if (serverSupplementalData != null) + { + sendSupplementalDataMessage(serverSupplementalData); + } + this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA; + + this.keyExchange = tlsServer.getKeyExchange(); + this.keyExchange.init(getContext()); + + this.serverCredentials = tlsServer.getCredentials(); + if (this.serverCredentials == null) + { + this.keyExchange.skipServerCredentials(); + } + else + { + this.keyExchange.processServerCredentials(this.serverCredentials); + sendCertificateMessage(this.serverCredentials.getCertificate()); + } + this.connection_state = CS_SERVER_CERTIFICATE; + + byte[] serverKeyExchange = this.keyExchange.generateServerKeyExchange(); + if (serverKeyExchange != null) + { + sendServerKeyExchangeMessage(serverKeyExchange); + } + this.connection_state = CS_SERVER_KEY_EXCHANGE; + + if (this.serverCredentials != null) + { + this.certificateRequest = tlsServer.getCertificateRequest(); + if (this.certificateRequest != null) + { + this.keyExchange.validateCertificateRequest(certificateRequest); + sendCertificateRequestMessage(certificateRequest); + } + } + this.connection_state = CS_CERTIFICATE_REQUEST; + + sendServerHelloDoneMessage(); + this.connection_state = CS_SERVER_HELLO_DONE; + + break; + } + default: + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + break; + } + case HandshakeType.supplemental_data: + { + switch (this.connection_state) + { + case CS_SERVER_HELLO_DONE: + { + tlsServer.processClientSupplementalData(readSupplementalDataMessage(buf)); + this.connection_state = CS_CLIENT_SUPPLEMENTAL_DATA; + break; + } + default: + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + break; + } + case HandshakeType.certificate: + { + switch (this.connection_state) + { + case CS_SERVER_HELLO_DONE: + { + tlsServer.processClientSupplementalData(null); + // NB: Fall through to next case label + } + case CS_CLIENT_SUPPLEMENTAL_DATA: + { + if (this.certificateRequest == null) + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + receiveCertificateMessage(buf); + this.connection_state = CS_CLIENT_CERTIFICATE; + break; + } + default: + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + break; + } + case HandshakeType.client_key_exchange: + { + switch (this.connection_state) + { + case CS_SERVER_HELLO_DONE: + { + tlsServer.processClientSupplementalData(null); + // NB: Fall through to next case label + } + case CS_CLIENT_SUPPLEMENTAL_DATA: + { + if (this.certificateRequest == null) + { + this.keyExchange.skipClientCredentials(); + } + else + { + + ProtocolVersion equivalentTLSVersion = getContext().getServerVersion().getEquivalentTLSVersion(); + + if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(equivalentTLSVersion)) + { + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + else if (equivalentTLSVersion.isSSL()) + { + if (clientCertificate == null) + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + else + { + notifyClientCertificate(Certificate.EMPTY_CHAIN); + } + } + // NB: Fall through to next case label + } + case CS_CLIENT_CERTIFICATE: + { + receiveClientKeyExchangeMessage(buf); + this.connection_state = CS_CLIENT_KEY_EXCHANGE; + break; + } + default: + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + break; + } + case HandshakeType.certificate_verify: + { + switch (this.connection_state) + { + case CS_CLIENT_KEY_EXCHANGE: + { + /* + * RFC 5246 7.4.8 This message is only sent following a client certificate that has + * signing capability (i.e., all certificates except those containing fixed + * Diffie-Hellman parameters). + */ + if (this.certificateVerifyHash == null) + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + receiveCertificateVerifyMessage(buf); + this.connection_state = CS_CERTIFICATE_VERIFY; + break; + } + default: + { + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + } + break; + } + case HandshakeType.finished: + { + switch (this.connection_state) + { + case CS_CLIENT_CHANGE_CIPHER_SPEC: + processFinishedMessage(buf); + this.connection_state = CS_CLIENT_FINISHED; + + if (expectSessionTicket) + { + sendNewSessionTicketMessage(tlsServer.getNewSessionTicket()); + } + this.connection_state = CS_SERVER_SESSION_TICKET; + + sendChangeCipherSpecMessage(); + this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC; + + sendFinishedMessage(); + this.connection_state = CS_SERVER_FINISHED; + break; + default: + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.hello_request: + case HandshakeType.hello_verify_request: + case HandshakeType.server_hello: + case HandshakeType.server_key_exchange: + case HandshakeType.certificate_request: + case HandshakeType.server_hello_done: + case HandshakeType.session_ticket: + default: + // We do not support this! + this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + } + + protected void handleWarningMessage(short description) + throws IOException + { + switch (description) + { + case AlertDescription.no_certificate: + { + /* + * SSL 3.0 If the server has sent a certificate request Message, the client must send + * either the certificate message or a no_certificate alert. + */ + if (getContext().getServerVersion().isSSL() && certificateRequest != null) + { + notifyClientCertificate(Certificate.EMPTY_CHAIN); + } + break; + } + default: + { + super.handleWarningMessage(description); + } + } + } + + protected void notifyClientCertificate(Certificate clientCertificate) + throws IOException + { + + if (certificateRequest == null) + { + throw new IllegalStateException(); + } + + if (this.clientCertificate != null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.clientCertificate = clientCertificate; + + if (clientCertificate.isEmpty()) + { + this.keyExchange.skipClientCredentials(); + } + else + { + + /* + * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request + * message was non-empty, one of the certificates in the certificate chain SHOULD be + * issued by one of the listed CAs. + */ + + this.clientCertificateType = TlsUtils.getClientCertificateType(clientCertificate, + this.serverCredentials.getCertificate()); + + this.keyExchange.processClientCertificate(clientCertificate); + } + + /* + * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its + * discretion either continue the handshake without client authentication, or respond with a + * fatal handshake_failure alert. Also, if some aspect of the certificate chain was + * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its + * discretion either continue the handshake (considering the client unauthenticated) or send + * a fatal alert. + */ + this.tlsServer.notifyClientCertificate(clientCertificate); + } + + protected void receiveCertificateMessage(ByteArrayInputStream buf) + throws IOException + { + + Certificate clientCertificate = Certificate.parse(buf); + + assertEmpty(buf); + + notifyClientCertificate(clientCertificate); + } + + protected void receiveCertificateVerifyMessage(ByteArrayInputStream buf) + throws IOException + { + + byte[] clientCertificateSignature = TlsUtils.readOpaque16(buf); + + assertEmpty(buf); + + // Verify the CertificateVerify message contains a correct signature. + try + { + TlsSigner tlsSigner = TlsUtils.createTlsSigner(this.clientCertificateType); + tlsSigner.init(getContext()); + + org.bouncycastle.asn1.x509.Certificate x509Cert = this.clientCertificate.getCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo); + + tlsSigner.verifyRawSignature(clientCertificateSignature, publicKey, this.certificateVerifyHash); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + } + + protected void receiveClientHelloMessage(ByteArrayInputStream buf) + throws IOException + { + + ProtocolVersion client_version = TlsUtils.readVersion(buf); + if (client_version.isDTLS()) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + /* + * Read the client random + */ + byte[] client_random = TlsUtils.readFully(32, buf); + + byte[] sessionID = TlsUtils.readOpaque8(buf); + if (sessionID.length > 32) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + int cipher_suites_length = TlsUtils.readUint16(buf); + if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) + { + this.failWithError(AlertLevel.fatal, AlertDescription.decode_error); + } + + /* + * NOTE: "If the session_id field is not empty (implying a session resumption request) this + * vector must include at least the cipher_suite from that session." + */ + this.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf); + + int compression_methods_length = TlsUtils.readUint8(buf); + if (compression_methods_length < 1) + { + this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf); + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + this.clientExtensions = readExtensions(buf); + + getContext().setClientVersion(client_version); + + tlsServer.notifyClientVersion(client_version); + + securityParameters.clientRandom = client_random; + + tlsServer.notifyOfferedCipherSuites(offeredCipherSuites); + tlsServer.notifyOfferedCompressionMethods(offeredCompressionMethods); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + + /* + * When a ClientHello is received, the server MUST check if it includes the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag + * to TRUE. + */ + if (arrayContains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + { + this.secure_renegotiation = true; + } + + /* + * The server MUST check if the "renegotiation_info" extension is included in the + * ClientHello. + */ + if (clientExtensions != null) + { + byte[] renegExtValue = (byte[])clientExtensions.get(EXT_RenegotiationInfo); + if (renegExtValue != null) + { + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + this.secure_renegotiation = true; + + if (!Arrays.constantTimeAreEqual(renegExtValue, createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) + { + this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + } + } + } + + tlsServer.notifySecureRenegotiation(this.secure_renegotiation); + + if (clientExtensions != null) + { + tlsServer.processClientExtensions(clientExtensions); + } + } + + protected void receiveClientKeyExchangeMessage(ByteArrayInputStream buf) + throws IOException + { + + this.keyExchange.processClientKeyExchange(buf); + + assertEmpty(buf); + + establishMasterSecret(getContext(), keyExchange); + + /* + * Initialize our cipher suite + */ + recordStream.setPendingConnectionState(tlsServer.getCompression(), tlsServer.getCipher()); + + if (expectCertificateVerifyMessage()) + { + this.certificateVerifyHash = recordStream.getCurrentHash(null); + } + } + + protected void sendCertificateRequestMessage(CertificateRequest certificateRequest) + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.certificate_request, buf); + + // Reserve space for length + TlsUtils.writeUint24(0, buf); + + certificateRequest.encode(buf); + byte[] message = buf.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendNewSessionTicketMessage(NewSessionTicket newSessionTicket) + throws IOException + { + + if (newSessionTicket == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.session_ticket, buf); + + // Reserve space for length + TlsUtils.writeUint24(0, buf); + + newSessionTicket.encode(buf); + byte[] message = buf.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendServerHelloMessage() + throws IOException + { + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HandshakeType.server_hello, buf); + + // Reserve space for length + TlsUtils.writeUint24(0, buf); + + ProtocolVersion server_version = tlsServer.getServerVersion(); + if (!server_version.isEqualOrEarlierVersionOf(getContext().getClientVersion())) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + + recordStream.setReadVersion(server_version); + recordStream.setWriteVersion(server_version); + recordStream.setRestrictReadVersion(true); + getContext().setServerVersion(server_version); + + TlsUtils.writeVersion(server_version, buf); + + buf.write(this.securityParameters.serverRandom); + + /* + * The server may return an empty session_id to indicate that the session will not be cached + * and therefore cannot be resumed. + */ + TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + + this.selectedCipherSuite = tlsServer.getSelectedCipherSuite(); + if (!arrayContains(this.offeredCipherSuites, this.selectedCipherSuite) + || this.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || this.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + + this.selectedCompressionMethod = tlsServer.getSelectedCompressionMethod(); + if (!arrayContains(this.offeredCompressionMethods, this.selectedCompressionMethod)) + { + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + } + + TlsUtils.writeUint16(this.selectedCipherSuite, buf); + TlsUtils.writeUint8(this.selectedCompressionMethod, buf); + + this.serverExtensions = tlsServer.getServerExtensions(); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + if (this.secure_renegotiation) + { + + boolean noRenegExt = this.serverExtensions == null + || !this.serverExtensions.containsKey(EXT_RenegotiationInfo); + + if (noRenegExt) + { + /* + * Note that sending a "renegotiation_info" extension in response to a ClientHello + * containing only the SCSV is an explicit exception to the prohibition in RFC 5246, + * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed + * because the client is signaling its willingness to receive the extension via the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + if (this.serverExtensions == null) + { + this.serverExtensions = new Hashtable(); + } + + /* + * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty + * "renegotiation_info" extension in the ServerHello message. + */ + this.serverExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)); + } + } + + if (this.serverExtensions != null) + { + this.expectSessionTicket = serverExtensions.containsKey(EXT_SessionTicket); + writeExtensions(buf, this.serverExtensions); + } + + byte[] message = buf.toByteArray(); + + // Patch actual length back in + TlsUtils.writeUint24(message.length - 4, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendServerHelloDoneMessage() + throws IOException + { + + byte[] message = new byte[4]; + TlsUtils.writeUint8(HandshakeType.server_hello_done, message, 0); + TlsUtils.writeUint24(0, message, 1); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected void sendServerKeyExchangeMessage(byte[] serverKeyExchange) + throws IOException + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + TlsUtils.writeUint8(HandshakeType.server_key_exchange, bos); + TlsUtils.writeUint24(serverKeyExchange.length, bos); + bos.write(serverKeyExchange); + byte[] message = bos.toByteArray(); + + safeWriteRecord(ContentType.handshake, message, 0, message.length); + } + + protected boolean expectCertificateVerifyMessage() + { + return this.clientCertificateType >= 0 && TlsUtils.hasSigningCapability(this.clientCertificateType); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java new file mode 100644 index 00000000..2b615076 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java @@ -0,0 +1,23 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public interface TlsSigner +{ + + void init(TlsContext context); + + byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + throws CryptoException; + + boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + throws CryptoException; + + Signer createSigner(AsymmetricKeyParameter privateKey); + + Signer createVerifyer(AsymmetricKeyParameter publicKey); + + boolean isValidPublicKey(AsymmetricKeyParameter publicKey); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java new file mode 100644 index 00000000..7067fa21 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java @@ -0,0 +1,10 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +public interface TlsSignerCredentials + extends TlsCredentials +{ + byte[] generateCertificateSignature(byte[] md5andsha1) + throws IOException; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java new file mode 100644 index 00000000..1755c2d4 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java @@ -0,0 +1,126 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; + +public class TlsStreamCipher + implements TlsCipher +{ + protected TlsContext context; + + protected StreamCipher encryptCipher; + protected StreamCipher decryptCipher; + + protected TlsMac writeMac; + protected TlsMac readMac; + + public TlsStreamCipher(TlsContext context, StreamCipher clientWriteCipher, + StreamCipher serverWriteCipher, Digest clientWriteDigest, Digest serverWriteDigest, + int cipherKeySize) + throws IOException + { + + boolean isServer = context.isServer(); + + this.context = context; + + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + + int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize() + + serverWriteDigest.getDigestSize(); + + byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size); + + int offset = 0; + + // Init MACs + TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.getDigestSize()); + offset += clientWriteDigest.getDigestSize(); + TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.getDigestSize()); + offset += serverWriteDigest.getDigestSize(); + + // Build keys + KeyParameter clientWriteKey = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter serverWriteKey = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + CipherParameters encryptParams, decryptParams; + if (isServer) + { + this.writeMac = serverWriteMac; + this.readMac = clientWriteMac; + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + encryptParams = serverWriteKey; + decryptParams = clientWriteKey; + } + else + { + this.writeMac = clientWriteMac; + this.readMac = serverWriteMac; + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + encryptParams = clientWriteKey; + decryptParams = serverWriteKey; + } + + this.encryptCipher.init(true, encryptParams); + this.decryptCipher.init(false, decryptParams); + } + + public int getPlaintextLimit(int ciphertextLimit) + { + return ciphertextLimit - writeMac.getSize(); + } + + public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) + { + byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + + byte[] outbuf = new byte[len + mac.length]; + + encryptCipher.processBytes(plaintext, offset, len, outbuf, 0); + encryptCipher.processBytes(mac, 0, mac.length, outbuf, len); + + return outbuf; + } + + public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) + throws IOException + { + int macSize = readMac.getSize(); + if (len < macSize) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + byte[] deciphered = new byte[len]; + decryptCipher.processBytes(ciphertext, offset, len, deciphered, 0); + + int macInputLen = len - macSize; + + byte[] receivedMac = Arrays.copyOfRange(deciphered, macInputLen, len); + byte[] computedMac = readMac.calculateMac(seqNo, type, deciphered, 0, macInputLen); + + if (!Arrays.constantTimeAreEqual(receivedMac, computedMac)) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + return Arrays.copyOfRange(deciphered, 0, macInputLen); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java b/core/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java new file mode 100644 index 00000000..8b162104 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java @@ -0,0 +1,968 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.MD5Digest; +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.macs.HMac; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; +import org.bouncycastle.util.Strings; +import org.bouncycastle.util.io.Streams; + +/** + * Some helper functions for MicroTLS. + */ +public class TlsUtils +{ + public static byte[] EMPTY_BYTES = new byte[0]; + + public static final Integer EXT_signature_algorithms = Integers.valueOf(ExtensionType.signature_algorithms); + + public static boolean isValidUint8(short i) + { + return (i & 0xFF) == i; + } + + public static boolean isValidUint16(int i) + { + return (i & 0xFFFF) == i; + } + + public static boolean isValidUint24(int i) + { + return (i & 0xFFFFFF) == i; + } + + public static boolean isValidUint32(long i) + { + return (i & 0xFFFFFFFFL) == i; + } + + public static boolean isValidUint48(long i) + { + return (i & 0xFFFFFFFFFFFFL) == i; + } + + public static boolean isValidUint64(long i) + { + return true; + } + + public static void writeUint8(short i, OutputStream output) + throws IOException + { + output.write(i); + } + + public static void writeUint8(short i, byte[] buf, int offset) + { + buf[offset] = (byte)i; + } + + public static void writeUint16(int i, OutputStream output) + throws IOException + { + output.write(i >> 8); + output.write(i); + } + + public static void writeUint16(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 8); + buf[offset + 1] = (byte)i; + } + + public static void writeUint24(int i, OutputStream output) + throws IOException + { + output.write(i >> 16); + output.write(i >> 8); + output.write(i); + } + + public static void writeUint24(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 16); + buf[offset + 1] = (byte)(i >> 8); + buf[offset + 2] = (byte)(i); + } + + public static void writeUint32(long i, OutputStream output) + throws IOException + { + output.write((int)(i >> 24)); + output.write((int)(i >> 16)); + output.write((int)(i >> 8)); + output.write((int)(i)); + } + + public static void writeUint32(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 24); + buf[offset + 1] = (byte)(i >> 16); + buf[offset + 2] = (byte)(i >> 8); + buf[offset + 3] = (byte)(i); + } + + public static void writeUint48(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 40); + buf[offset + 1] = (byte)(i >> 32); + buf[offset + 2] = (byte)(i >> 24); + buf[offset + 3] = (byte)(i >> 16); + buf[offset + 4] = (byte)(i >> 8); + buf[offset + 5] = (byte)(i); + } + + public static void writeUint64(long i, OutputStream output) + throws IOException + { + output.write((int)(i >> 56)); + output.write((int)(i >> 48)); + output.write((int)(i >> 40)); + output.write((int)(i >> 32)); + output.write((int)(i >> 24)); + output.write((int)(i >> 16)); + output.write((int)(i >> 8)); + output.write((int)(i)); + } + + public static void writeUint64(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 56); + buf[offset + 1] = (byte)(i >> 48); + buf[offset + 2] = (byte)(i >> 40); + buf[offset + 3] = (byte)(i >> 32); + buf[offset + 4] = (byte)(i >> 24); + buf[offset + 5] = (byte)(i >> 16); + buf[offset + 6] = (byte)(i >> 8); + buf[offset + 7] = (byte)(i); + } + + public static void writeOpaque8(byte[] buf, OutputStream output) + throws IOException + { + writeUint8((short)buf.length, output); + output.write(buf); + } + + public static void writeOpaque16(byte[] buf, OutputStream output) + throws IOException + { + writeUint16(buf.length, output); + output.write(buf); + } + + public static void writeOpaque24(byte[] buf, OutputStream output) + throws IOException + { + writeUint24(buf.length, output); + output.write(buf); + } + + public static void writeUint8Array(short[] uints, OutputStream output) + throws IOException + { + for (int i = 0; i < uints.length; ++i) + { + writeUint8(uints[i], output); + } + } + + public static void writeUint16Array(int[] uints, OutputStream output) + throws IOException + { + for (int i = 0; i < uints.length; ++i) + { + writeUint16(uints[i], output); + } + } + + public static short readUint8(InputStream input) + throws IOException + { + int i = input.read(); + if (i < 0) + { + throw new EOFException(); + } + return (short)i; + } + + public static short readUint8(byte[] buf, int offset) + { + return (short)buf[offset]; + } + + public static int readUint16(InputStream input) + throws IOException + { + int i1 = input.read(); + int i2 = input.read(); + if (i2 < 0) + { + throw new EOFException(); + } + return i1 << 8 | i2; + } + + public static int readUint16(byte[] buf, int offset) + { + int n = (buf[offset] & 0xff) << 8; + n |= (buf[++offset] & 0xff); + return n; + } + + public static int readUint24(InputStream input) + throws IOException + { + int i1 = input.read(); + int i2 = input.read(); + int i3 = input.read(); + if (i3 < 0) + { + throw new EOFException(); + } + return (i1 << 16) | (i2 << 8) | i3; + } + + public static int readUint24(byte[] buf, int offset) + { + int n = (buf[offset] & 0xff) << 16; + n |= (buf[++offset] & 0xff) << 8; + n |= (buf[++offset] & 0xff); + return n; + } + + public static long readUint32(InputStream input) + throws IOException + { + int i1 = input.read(); + int i2 = input.read(); + int i3 = input.read(); + int i4 = input.read(); + if (i4 < 0) + { + throw new EOFException(); + } + return (((long)i1) << 24) | (((long)i2) << 16) | (((long)i3) << 8) | ((long)i4); + } + + public static long readUint48(InputStream input) + throws IOException + { + int i1 = input.read(); + int i2 = input.read(); + int i3 = input.read(); + int i4 = input.read(); + int i5 = input.read(); + int i6 = input.read(); + if (i6 < 0) + { + throw new EOFException(); + } + return (((long)i1) << 40) | (((long)i2) << 32) | (((long)i3) << 24) | (((long)i4) << 16) | (((long)i5) << 8) | ((long)i6); + } + + public static long readUint48(byte[] buf, int offset) + { + int hi = readUint24(buf, offset); + int lo = readUint24(buf, offset + 3); + return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL); + } + + public static byte[] readFully(int length, InputStream input) + throws IOException + { + if (length < 1) + { + return EMPTY_BYTES; + } + byte[] buf = new byte[length]; + if (length != Streams.readFully(input, buf)) + { + throw new EOFException(); + } + return buf; + } + + public static void readFully(byte[] buf, InputStream input) + throws IOException + { + int length = buf.length; + if (length > 0 && length != Streams.readFully(input, buf)) + { + throw new EOFException(); + } + } + + public static byte[] readOpaque8(InputStream input) + throws IOException + { + short length = readUint8(input); + return readFully(length, input); + } + + public static byte[] readOpaque16(InputStream input) + throws IOException + { + int length = readUint16(input); + return readFully(length, input); + } + + public static byte[] readOpaque24(InputStream input) + throws IOException + { + int length = readUint24(input); + return readFully(length, input); + } + + public static short[] readUint8Array(int count, InputStream input) + throws IOException + { + short[] uints = new short[count]; + for (int i = 0; i < count; ++i) + { + uints[i] = readUint8(input); + } + return uints; + } + + public static int[] readUint16Array(int count, InputStream input) + throws IOException + { + int[] uints = new int[count]; + for (int i = 0; i < count; ++i) + { + uints[i] = readUint16(input); + } + return uints; + } + + public static ProtocolVersion readVersion(byte[] buf, int offset) + throws IOException + { + return ProtocolVersion.get(buf[offset] & 0xFF, buf[offset + 1] & 0xFF); + } + + public static ProtocolVersion readVersion(InputStream input) + throws IOException + { + int i1 = input.read(); + int i2 = input.read(); + if (i2 < 0) + { + throw new EOFException(); + } + return ProtocolVersion.get(i1, i2); + } + + public static int readVersionRaw(InputStream input) + throws IOException + { + int i1 = input.read(); + int i2 = input.read(); + if (i2 < 0) + { + throw new EOFException(); + } + return (i1 << 8) | i2; + } + + public static void writeGMTUnixTime(byte[] buf, int offset) + { + int t = (int)(System.currentTimeMillis() / 1000L); + buf[offset] = (byte)(t >> 24); + buf[offset + 1] = (byte)(t >> 16); + buf[offset + 2] = (byte)(t >> 8); + buf[offset + 3] = (byte)t; + } + + public static void writeVersion(ProtocolVersion version, OutputStream output) + throws IOException + { + output.write(version.getMajorVersion()); + output.write(version.getMinorVersion()); + } + + public static void writeVersion(ProtocolVersion version, byte[] buf, int offset) + throws IOException + { + buf[offset] = (byte)version.getMajorVersion(); + buf[offset + 1] = (byte)version.getMinorVersion(); + } + + public static Vector getDefaultDSSSignatureAlgorithms() + { + return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.dsa)); + } + + public static Vector getDefaultECDSASignatureAlgorithms() + { + return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa)); + } + + public static Vector getDefaultRSASignatureAlgorithms() + { + return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa)); + } + + public static boolean isSignatureAlgorithmsExtensionAllowed(ProtocolVersion clientVersion) + { + return ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(clientVersion.getEquivalentTLSVersion()); + } + + /** + * Add a 'signature_algorithms' extension to existing extensions. + * + * @param extensions A {@link Hashtable} to add the extension to. + * @param supportedSignatureAlgorithms {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + * @throws IOException + */ + public static void addSignatureAlgorithmsExtension(Hashtable extensions, Vector supportedSignatureAlgorithms) + throws IOException + { + extensions.put(EXT_signature_algorithms, createSignatureAlgorithmsExtension(supportedSignatureAlgorithms)); + } + + /** + * Get a 'signature_algorithms' extension from extensions. + * + * @param extensions A {@link Hashtable} to get the extension from, if it is present. + * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}, or null. + * @throws IOException + */ + public static Vector getSignatureAlgorithmsExtension(Hashtable extensions) + throws IOException + { + + if (extensions == null) + { + return null; + } + byte[] extensionValue = (byte[])extensions.get(EXT_signature_algorithms); + if (extensionValue == null) + { + return null; + } + return readSignatureAlgorithmsExtension(extensionValue); + } + + /** + * Create a 'signature_algorithms' extension value. + * + * @param supportedSignatureAlgorithms A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + * @return A byte array suitable for use as an extension value. + * @throws IOException + */ + public static byte[] createSignatureAlgorithmsExtension(Vector supportedSignatureAlgorithms) + throws IOException + { + + if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.size() < 1 || supportedSignatureAlgorithms.size() >= (1 << 15)) + { + throw new IllegalArgumentException( + "'supportedSignatureAlgorithms' must have length from 1 to (2^15 - 1)"); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + // supported_signature_algorithms + TlsUtils.writeUint16(2 * supportedSignatureAlgorithms.size(), buf); + for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i) + { + SignatureAndHashAlgorithm entry = (SignatureAndHashAlgorithm)supportedSignatureAlgorithms.elementAt(i); + entry.encode(buf); + } + + return buf.toByteArray(); + } + + /** + * Read a 'signature_algorithms' extension value. + * + * @param extensionValue The extension value. + * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + * @throws IOException + */ + public static Vector readSignatureAlgorithmsExtension(byte[] extensionValue) + throws IOException + { + + if (extensionValue == null) + { + throw new IllegalArgumentException("'extensionValue' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + + // supported_signature_algorithms + int length = TlsUtils.readUint16(buf); + if (length < 2 || (length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + int count = length / 2; + Vector result = new Vector(count); + for (int i = 0; i < count; ++i) + { + SignatureAndHashAlgorithm entry = SignatureAndHashAlgorithm.parse(buf); + result.addElement(entry); + } + + TlsProtocol.assertEmpty(buf); + + return result; + } + + public static byte[] PRF(TlsContext context, byte[] secret, String asciiLabel, byte[] seed, int size) + { + ProtocolVersion version = context.getServerVersion(); + + if (version.isSSL()) + { + throw new IllegalStateException("No PRF available for SSLv3 session"); + } + + byte[] label = Strings.toByteArray(asciiLabel); + byte[] labelSeed = concat(label, seed); + + int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm(); + + if (prfAlgorithm == PRFAlgorithm.tls_prf_legacy) + { + if (!ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(version.getEquivalentTLSVersion())) + { + return PRF_legacy(secret, label, labelSeed, size); + } + + prfAlgorithm = PRFAlgorithm.tls_prf_sha256; + } + + Digest prfDigest = createPRFHash(prfAlgorithm); + byte[] buf = new byte[size]; + hmac_hash(prfDigest, secret, labelSeed, buf); + return buf; + } + + static byte[] PRF_legacy(byte[] secret, byte[] label, byte[] labelSeed, int size) + { + int s_half = (secret.length + 1) / 2; + byte[] s1 = new byte[s_half]; + byte[] s2 = new byte[s_half]; + System.arraycopy(secret, 0, s1, 0, s_half); + System.arraycopy(secret, secret.length - s_half, s2, 0, s_half); + + byte[] b1 = new byte[size]; + byte[] b2 = new byte[size]; + hmac_hash(new MD5Digest(), s1, labelSeed, b1); + hmac_hash(new SHA1Digest(), s2, labelSeed, b2); + for (int i = 0; i < size; i++) + { + b1[i] ^= b2[i]; + } + return b1; + } + + static byte[] concat(byte[] a, byte[] b) + { + byte[] c = new byte[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static void hmac_hash(Digest digest, byte[] secret, byte[] seed, byte[] out) + { + HMac mac = new HMac(digest); + KeyParameter param = new KeyParameter(secret); + byte[] a = seed; + int size = digest.getDigestSize(); + int iterations = (out.length + size - 1) / size; + byte[] buf = new byte[mac.getMacSize()]; + byte[] buf2 = new byte[mac.getMacSize()]; + for (int i = 0; i < iterations; i++) + { + mac.init(param); + mac.update(a, 0, a.length); + mac.doFinal(buf, 0); + a = buf; + mac.init(param); + mac.update(a, 0, a.length); + mac.update(seed, 0, seed.length); + mac.doFinal(buf2, 0); + System.arraycopy(buf2, 0, out, (size * i), Math.min(size, out.length - (size * i))); + } + } + + static void validateKeyUsage(org.bouncycastle.asn1.x509.Certificate c, int keyUsageBits) + throws IOException + { + Extensions exts = c.getTBSCertificate().getExtensions(); + if (exts != null) + { + KeyUsage ku = KeyUsage.fromExtensions(exts); + if (ku != null) + { + int bits = ku.getBytes()[0] & 0xff; + if ((bits & keyUsageBits) != keyUsageBits) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + } + } + } + + static byte[] calculateKeyBlock(TlsContext context, int size) + { + SecurityParameters securityParameters = context.getSecurityParameters(); + byte[] master_secret = securityParameters.getMasterSecret(); + byte[] seed = concat(securityParameters.getServerRandom(), + securityParameters.getClientRandom()); + + if (context.getServerVersion().isSSL()) + { + return calculateKeyBlock_SSL(master_secret, seed, size); + } + + return PRF(context, master_secret, ExporterLabel.key_expansion, seed, size); + } + + static byte[] calculateKeyBlock_SSL(byte[] master_secret, byte[] random, int size) + { + Digest md5 = new MD5Digest(); + Digest sha1 = new SHA1Digest(); + int md5Size = md5.getDigestSize(); + byte[] shatmp = new byte[sha1.getDigestSize()]; + byte[] tmp = new byte[size + md5Size]; + + int i = 0, pos = 0; + while (pos < size) + { + byte[] ssl3Const = SSL3_CONST[i]; + + sha1.update(ssl3Const, 0, ssl3Const.length); + sha1.update(master_secret, 0, master_secret.length); + sha1.update(random, 0, random.length); + sha1.doFinal(shatmp, 0); + + md5.update(master_secret, 0, master_secret.length); + md5.update(shatmp, 0, shatmp.length); + md5.doFinal(tmp, pos); + + pos += md5Size; + ++i; + } + + byte rval[] = new byte[size]; + System.arraycopy(tmp, 0, rval, 0, size); + return rval; + } + + static byte[] calculateMasterSecret(TlsContext context, byte[] pre_master_secret) + { + SecurityParameters securityParameters = context.getSecurityParameters(); + byte[] seed = concat(securityParameters.getClientRandom(), securityParameters.getServerRandom()); + + if (context.getServerVersion().isSSL()) + { + return calculateMasterSecret_SSL(pre_master_secret, seed); + } + + return PRF(context, pre_master_secret, ExporterLabel.master_secret, seed, 48); + } + + static byte[] calculateMasterSecret_SSL(byte[] pre_master_secret, byte[] random) + { + Digest md5 = new MD5Digest(); + Digest sha1 = new SHA1Digest(); + int md5Size = md5.getDigestSize(); + byte[] shatmp = new byte[sha1.getDigestSize()]; + + byte[] rval = new byte[md5Size * 3]; + int pos = 0; + + for (int i = 0; i < 3; ++i) + { + byte[] ssl3Const = SSL3_CONST[i]; + + sha1.update(ssl3Const, 0, ssl3Const.length); + sha1.update(pre_master_secret, 0, pre_master_secret.length); + sha1.update(random, 0, random.length); + sha1.doFinal(shatmp, 0); + + md5.update(pre_master_secret, 0, pre_master_secret.length); + md5.update(shatmp, 0, shatmp.length); + md5.doFinal(rval, pos); + + pos += md5Size; + } + + return rval; + } + + static byte[] calculateVerifyData(TlsContext context, String asciiLabel, byte[] handshakeHash) + { + if (context.getServerVersion().isSSL()) + { + return handshakeHash; + } + + SecurityParameters securityParameters = context.getSecurityParameters(); + byte[] master_secret = securityParameters.getMasterSecret(); + int verify_data_length = securityParameters.getVerifyDataLength(); + + return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length); + } + + public static final Digest createHash(int hashAlgorithm) + { + switch (hashAlgorithm) + { + case HashAlgorithm.md5: + return new MD5Digest(); + case HashAlgorithm.sha1: + return new SHA1Digest(); + case HashAlgorithm.sha224: + return new SHA224Digest(); + case HashAlgorithm.sha256: + return new SHA256Digest(); + case HashAlgorithm.sha384: + return new SHA384Digest(); + case HashAlgorithm.sha512: + return new SHA512Digest(); + default: + throw new IllegalArgumentException("unknown HashAlgorithm"); + } + } + + public static final Digest cloneHash(int hashAlgorithm, Digest hash) + { + switch (hashAlgorithm) + { + case HashAlgorithm.md5: + return new MD5Digest((MD5Digest)hash); + case HashAlgorithm.sha1: + return new SHA1Digest((SHA1Digest)hash); + case HashAlgorithm.sha224: + return new SHA224Digest((SHA224Digest)hash); + case HashAlgorithm.sha256: + return new SHA256Digest((SHA256Digest)hash); + case HashAlgorithm.sha384: + return new SHA384Digest((SHA384Digest)hash); + case HashAlgorithm.sha512: + return new SHA512Digest((SHA512Digest)hash); + default: + throw new IllegalArgumentException("unknown HashAlgorithm"); + } + } + + public static final Digest createPRFHash(int prfAlgorithm) + { + switch (prfAlgorithm) + { + case PRFAlgorithm.tls_prf_legacy: + return new CombinedHash(); + default: + return createHash(getHashAlgorithmForPRFAlgorithm(prfAlgorithm)); + } + } + + public static final Digest clonePRFHash(int prfAlgorithm, Digest hash) + { + switch (prfAlgorithm) + { + case PRFAlgorithm.tls_prf_legacy: + return new CombinedHash((CombinedHash)hash); + default: + return cloneHash(getHashAlgorithmForPRFAlgorithm(prfAlgorithm), hash); + } + } + + public static final short getHashAlgorithmForPRFAlgorithm(int prfAlgorithm) + { + switch (prfAlgorithm) + { + case PRFAlgorithm.tls_prf_legacy: + throw new IllegalArgumentException("legacy PRF not a valid algorithm"); + case PRFAlgorithm.tls_prf_sha256: + return HashAlgorithm.sha256; + case PRFAlgorithm.tls_prf_sha384: + return HashAlgorithm.sha384; + default: + throw new IllegalArgumentException("unknown PRFAlgorithm"); + } + } + + public static ASN1ObjectIdentifier getOIDForHashAlgorithm(int hashAlgorithm) + { + switch (hashAlgorithm) + { + case HashAlgorithm.md5: + return PKCSObjectIdentifiers.md5; + case HashAlgorithm.sha1: + return X509ObjectIdentifiers.id_SHA1; + case HashAlgorithm.sha224: + return NISTObjectIdentifiers.id_sha224; + case HashAlgorithm.sha256: + return NISTObjectIdentifiers.id_sha256; + case HashAlgorithm.sha384: + return NISTObjectIdentifiers.id_sha384; + case HashAlgorithm.sha512: + return NISTObjectIdentifiers.id_sha512; + default: + throw new IllegalArgumentException("unknown HashAlgorithm"); + } + } + + static short getClientCertificateType(Certificate clientCertificate, Certificate serverCertificate) + throws IOException + { + if (clientCertificate.isEmpty()) + { + return -1; + } + + org.bouncycastle.asn1.x509.Certificate x509Cert = clientCertificate.getCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + try + { + AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo); + if (publicKey.isPrivate()) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + /* + * TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/ + * signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the + * constraints on certificate-signing algorithms found in prior versions of TLS. + */ + + /* + * RFC 5246 7.4.6. Client Certificate + */ + + /* + * RSA public key; the certificate MUST allow the key to be used for signing with the + * signature scheme and hash algorithm that will be employed in the certificate verify + * message. + */ + if (publicKey instanceof RSAKeyParameters) + { + validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + return ClientCertificateType.rsa_sign; + } + + /* + * DSA public key; the certificate MUST allow the key to be used for signing with the + * hash algorithm that will be employed in the certificate verify message. + */ + if (publicKey instanceof DSAPublicKeyParameters) + { + validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + return ClientCertificateType.dss_sign; + } + + /* + * ECDSA-capable public key; the certificate MUST allow the key to be used for signing + * with the hash algorithm that will be employed in the certificate verify message; the + * public key MUST use a curve and point format supported by the server. + */ + if (publicKey instanceof ECPublicKeyParameters) + { + validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + // TODO Check the curve and point format + return ClientCertificateType.ecdsa_sign; + } + + // TODO Add support for ClientCertificateType.*_fixed_* + + } + catch (Exception e) + { + } + + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + public static boolean hasSigningCapability(short clientCertificateType) + { + switch (clientCertificateType) + { + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + case ClientCertificateType.rsa_sign: + return true; + default: + return false; + } + } + + public static TlsSigner createTlsSigner(short clientCertificateType) + { + switch (clientCertificateType) + { + case ClientCertificateType.dss_sign: + return new TlsDSSSigner(); + case ClientCertificateType.ecdsa_sign: + return new TlsECDSASigner(); + case ClientCertificateType.rsa_sign: + return new TlsRSASigner(); + default: + throw new IllegalArgumentException("'clientCertificateType' is not a type with signing capability"); + } + } + + static final byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54}; + static final byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52}; + + // SSL3 magic mix constants ("A", "BB", "CCC", ...) + static final byte[][] SSL3_CONST = genConst(); + + private static byte[][] genConst() + { + int n = 10; + byte[][] arr = new byte[n][]; + for (int i = 0; i < n; i++) + { + byte[] b = new byte[i + 1]; + Arrays.fill(b, (byte)('A' + i)); + arr[i] = b; + } + return arr; + } + + private static Vector vectorOfOne(Object obj) + { + Vector v = new Vector(1); + v.addElement(obj); + return v; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java b/core/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java new file mode 100644 index 00000000..f3dd59ee --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java @@ -0,0 +1,77 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; + +public class UDPTransport + implements DatagramTransport +{ + + private final static int MIN_IP_OVERHEAD = 20; + private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64; + private final static int UDP_OVERHEAD = 8; + + private final DatagramSocket socket; + private final int receiveLimit, sendLimit; + + public UDPTransport(DatagramSocket socket, int mtu) + throws IOException + { + + if (!socket.isBound() || !socket.isConnected()) + { + throw new IllegalArgumentException("'socket' must be bound and connected"); + } + + this.socket = socket; + + // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU + + this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD; + this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD; + } + + public int getReceiveLimit() + { + return receiveLimit; + } + + public int getSendLimit() + { + // TODO[DTLS] Implement Path-MTU discovery? + return sendLimit; + } + + public int receive(byte[] buf, int off, int len, int waitMillis) + throws IOException + { + socket.setSoTimeout(waitMillis); + DatagramPacket packet = new DatagramPacket(buf, off, len); + socket.receive(packet); + return packet.getLength(); + } + + public void send(byte[] buf, int off, int len) + throws IOException + { + if (len > getSendLimit()) + { + /* + * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU, + * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet + * which will be fragmented." + */ + // TODO Exception + } + + DatagramPacket packet = new DatagramPacket(buf, off, len); + socket.send(packet); + } + + public void close() + throws IOException + { + socket.close(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/UseSRTPData.java b/core/src/main/java/org/bouncycastle/crypto/tls/UseSRTPData.java new file mode 100644 index 00000000..8ecfce00 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/UseSRTPData.java @@ -0,0 +1,54 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 5764 4.1.1 + */ +public class UseSRTPData +{ + + private int[] protectionProfiles; + private byte[] mki; + + /** + * @param protectionProfiles see {@link SRTPProtectionProfile} for valid constants. + * @param mki valid lengths from 0 to 255. + */ + public UseSRTPData(int[] protectionProfiles, byte[] mki) + { + + if (protectionProfiles == null || protectionProfiles.length < 1 + || protectionProfiles.length >= (1 << 15)) + { + throw new IllegalArgumentException( + "'protectionProfiles' must have length from 1 to (2^15 - 1)"); + } + + if (mki == null) + { + mki = TlsUtils.EMPTY_BYTES; + } + else if (mki.length > 255) + { + throw new IllegalArgumentException("'mki' cannot be longer than 255 bytes"); + } + + this.protectionProfiles = protectionProfiles; + this.mki = mki; + } + + /** + * @return see {@link SRTPProtectionProfile} for valid constants. + */ + public int[] getProtectionProfiles() + { + return protectionProfiles; + } + + /** + * @return valid lengths from 0 to 255. + */ + public byte[] getMki() + { + return mki; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/tls/UserMappingType.java b/core/src/main/java/org/bouncycastle/crypto/tls/UserMappingType.java new file mode 100644 index 00000000..8f6ae7ba --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/tls/UserMappingType.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +/** + * RFC 4681 + */ +public class UserMappingType +{ + /* + * RFC 4681 + */ + public static final short upn_domain_hint = 64; +} diff --git a/core/src/main/java/org/bouncycastle/crypto/util/Pack.java b/core/src/main/java/org/bouncycastle/crypto/util/Pack.java new file mode 100644 index 00000000..f0da0bf0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/util/Pack.java @@ -0,0 +1,192 @@ +package org.bouncycastle.crypto.util; + +public abstract class Pack +{ + public static int bigEndianToInt(byte[] bs, int off) + { + int n = bs[ off] << 24; + n |= (bs[++off] & 0xff) << 16; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff); + return n; + } + + public static void bigEndianToInt(byte[] bs, int off, int[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = bigEndianToInt(bs, off); + off += 4; + } + } + + public static byte[] intToBigEndian(int n) + { + byte[] bs = new byte[4]; + intToBigEndian(n, bs, 0); + return bs; + } + + public static void intToBigEndian(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n >>> 24); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n ); + } + + public static byte[] intToBigEndian(int[] ns) + { + byte[] bs = new byte[4 * ns.length]; + intToBigEndian(ns, bs, 0); + return bs; + } + + public static void intToBigEndian(int[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + intToBigEndian(ns[i], bs, off); + off += 4; + } + } + + public static long bigEndianToLong(byte[] bs, int off) + { + int hi = bigEndianToInt(bs, off); + int lo = bigEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void bigEndianToLong(byte[] bs, int off, long[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = bigEndianToLong(bs, off); + off += 8; + } + } + + public static byte[] longToBigEndian(long n) + { + byte[] bs = new byte[8]; + longToBigEndian(n, bs, 0); + return bs; + } + + public static void longToBigEndian(long n, byte[] bs, int off) + { + intToBigEndian((int)(n >>> 32), bs, off); + intToBigEndian((int)(n & 0xffffffffL), bs, off + 4); + } + + public static byte[] longToBigEndian(long[] ns) + { + byte[] bs = new byte[8 * ns.length]; + longToBigEndian(ns, bs, 0); + return bs; + } + + public static void longToBigEndian(long[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + longToBigEndian(ns[i], bs, off); + off += 8; + } + } + + public static int littleEndianToInt(byte[] bs, int off) + { + int n = bs[ off] & 0xff; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff) << 16; + n |= bs[++off] << 24; + return n; + } + + public static void littleEndianToInt(byte[] bs, int off, int[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToInt(bs, off); + off += 4; + } + } + + public static byte[] intToLittleEndian(int n) + { + byte[] bs = new byte[4]; + intToLittleEndian(n, bs, 0); + return bs; + } + + public static void intToLittleEndian(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n ); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 24); + } + + public static byte[] intToLittleEndian(int[] ns) + { + byte[] bs = new byte[4 * ns.length]; + intToLittleEndian(ns, bs, 0); + return bs; + } + + public static void intToLittleEndian(int[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + intToLittleEndian(ns[i], bs, off); + off += 4; + } + } + + public static long littleEndianToLong(byte[] bs, int off) + { + int lo = littleEndianToInt(bs, off); + int hi = littleEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void littleEndianToLong(byte[] bs, int off, long[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToLong(bs, off); + off += 8; + } + } + + public static byte[] longToLittleEndian(long n) + { + byte[] bs = new byte[8]; + longToLittleEndian(n, bs, 0); + return bs; + } + + public static void longToLittleEndian(long n, byte[] bs, int off) + { + intToLittleEndian((int)(n & 0xffffffffL), bs, off); + intToLittleEndian((int)(n >>> 32), bs, off + 4); + } + + public static byte[] longToLittleEndian(long[] ns) + { + byte[] bs = new byte[8 * ns.length]; + longToLittleEndian(ns, bs, 0); + return bs; + } + + public static void longToLittleEndian(long[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + longToLittleEndian(ns[i], bs, off); + off += 8; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java b/core/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java new file mode 100644 index 00000000..bfa304b2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java @@ -0,0 +1,170 @@ +package org.bouncycastle.crypto.util; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.oiw.ElGamalParameter; +import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.DHParameter; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.RSAPrivateKey; +import org.bouncycastle.asn1.sec.ECPrivateKey; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DSAParameter; +import org.bouncycastle.asn1.x9.X962NamedCurves; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ElGamalParameters; +import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +/** + * Factory for creating private key objects from PKCS8 PrivateKeyInfo objects. + */ +public class PrivateKeyFactory +{ + /** + * Create a private key parameter from a PKCS8 PrivateKeyInfo encoding. + * + * @param privateKeyInfoData the PrivateKeyInfo encoding + * @return a suitable private key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey(byte[] privateKeyInfoData) throws IOException + { + return createKey(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(privateKeyInfoData))); + } + + /** + * Create a private key parameter from a PKCS8 PrivateKeyInfo encoding read from a + * stream. + * + * @param inStr the stream to read the PrivateKeyInfo encoding from + * @return a suitable private key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey(InputStream inStr) throws IOException + { + return createKey(PrivateKeyInfo.getInstance(new ASN1InputStream(inStr).readObject())); + } + + /** + * Create a private key parameter from the passed in PKCS8 PrivateKeyInfo object. + * + * @param keyInfo the PrivateKeyInfo object containing the key material + * @return a suitable private key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey(PrivateKeyInfo keyInfo) throws IOException + { + AlgorithmIdentifier algId = keyInfo.getPrivateKeyAlgorithm(); + + if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.rsaEncryption)) + { + RSAPrivateKey keyStructure = RSAPrivateKey.getInstance(keyInfo.parsePrivateKey()); + + return new RSAPrivateCrtKeyParameters(keyStructure.getModulus(), + keyStructure.getPublicExponent(), keyStructure.getPrivateExponent(), + keyStructure.getPrime1(), keyStructure.getPrime2(), keyStructure.getExponent1(), + keyStructure.getExponent2(), keyStructure.getCoefficient()); + } + // TODO? +// else if (algId.getObjectId().equals(X9ObjectIdentifiers.dhpublicnumber)) + else if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.dhKeyAgreement)) + { + DHParameter params = DHParameter.getInstance(algId.getParameters()); + ASN1Integer derX = (ASN1Integer)keyInfo.parsePrivateKey(); + + BigInteger lVal = params.getL(); + int l = lVal == null ? 0 : lVal.intValue(); + DHParameters dhParams = new DHParameters(params.getP(), params.getG(), null, l); + + return new DHPrivateKeyParameters(derX.getValue(), dhParams); + } + else if (algId.getAlgorithm().equals(OIWObjectIdentifiers.elGamalAlgorithm)) + { + ElGamalParameter params = new ElGamalParameter((ASN1Sequence)algId.getParameters()); + ASN1Integer derX = (ASN1Integer)keyInfo.parsePrivateKey(); + + return new ElGamalPrivateKeyParameters(derX.getValue(), new ElGamalParameters( + params.getP(), params.getG())); + } + else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_dsa)) + { + ASN1Integer derX = (ASN1Integer)keyInfo.parsePrivateKey(); + ASN1Encodable de = algId.getParameters(); + + DSAParameters parameters = null; + if (de != null) + { + DSAParameter params = DSAParameter.getInstance(de.toASN1Primitive()); + parameters = new DSAParameters(params.getP(), params.getQ(), params.getG()); + } + + return new DSAPrivateKeyParameters(derX.getValue(), parameters); + } + else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey)) + { + X962Parameters params = new X962Parameters((ASN1Primitive)algId.getParameters()); + + X9ECParameters x9; + if (params.isNamedCurve()) + { + ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters()); + x9 = X962NamedCurves.getByOID(oid); + + if (x9 == null) + { + x9 = SECNamedCurves.getByOID(oid); + + if (x9 == null) + { + x9 = NISTNamedCurves.getByOID(oid); + + if (x9 == null) + { + x9 = TeleTrusTNamedCurves.getByOID(oid); + } + } + } + } + else + { + x9 = X9ECParameters.getInstance(params.getParameters()); + } + + ECPrivateKey ec = ECPrivateKey.getInstance(keyInfo.parsePrivateKey()); + BigInteger d = ec.getKey(); + + // TODO We lose any named parameters here + + ECDomainParameters dParams = new ECDomainParameters( + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + return new ECPrivateKeyParameters(d, dParams); + } + else + { + throw new RuntimeException("algorithm identifier in key not recognised"); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java b/core/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java new file mode 100644 index 00000000..ab52802c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java @@ -0,0 +1,51 @@ +package org.bouncycastle.crypto.util; + +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.RSAPrivateKey; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DSAParameter; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +/** + * Factory to create ASN.1 private key info objects from lightweight private keys. + */ +public class PrivateKeyInfoFactory +{ + /** + * Create a PrivateKeyInfo representation of a private key. + * + * @param privateKey the SubjectPublicKeyInfo encoding + * @return the appropriate key parameter + * @throws java.io.IOException on an error encoding the key + */ + public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter privateKey) throws IOException + { + if (privateKey instanceof RSAKeyParameters) + { + RSAPrivateCrtKeyParameters priv = (RSAPrivateCrtKeyParameters)privateKey; + + return new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPrivateKey(priv.getModulus(), priv.getPublicExponent(), priv.getExponent(), priv.getP(), priv.getQ(), priv.getDP(), priv.getDQ(), priv.getQInv())); + } + else if (privateKey instanceof DSAPrivateKeyParameters) + { + DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)privateKey; + DSAParameters params = priv.getParameters(); + + return new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(params.getP(), params.getQ(), params.getG())), new ASN1Integer(priv.getX())); + } + else + { + throw new IOException("key parameters not recognised."); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java b/core/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java new file mode 100644 index 00000000..343bbd3c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java @@ -0,0 +1,207 @@ +package org.bouncycastle.crypto.util; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.oiw.ElGamalParameter; +import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.DHParameter; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.RSAPublicKey; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DSAParameter; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; +import org.bouncycastle.asn1.x9.DHDomainParameters; +import org.bouncycastle.asn1.x9.DHPublicKey; +import org.bouncycastle.asn1.x9.DHValidationParms; +import org.bouncycastle.asn1.x9.X962NamedCurves; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9ECPoint; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.crypto.params.DHValidationParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ElGamalParameters; +import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * Factory to create asymmetric public key parameters for asymmetric ciphers from range of + * ASN.1 encoded SubjectPublicKeyInfo objects. + */ +public class PublicKeyFactory +{ + /** + * Create a public key from a SubjectPublicKeyInfo encoding + * + * @param keyInfoData the SubjectPublicKeyInfo encoding + * @return the appropriate key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey(byte[] keyInfoData) throws IOException + { + return createKey(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(keyInfoData))); + } + + /** + * Create a public key from a SubjectPublicKeyInfo encoding read from a stream + * + * @param inStr the stream to read the SubjectPublicKeyInfo encoding from + * @return the appropriate key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey(InputStream inStr) throws IOException + { + return createKey(SubjectPublicKeyInfo.getInstance(new ASN1InputStream(inStr).readObject())); + } + + /** + * Create a public key from the passed in SubjectPublicKeyInfo + * + * @param keyInfo the SubjectPublicKeyInfo containing the key data + * @return the appropriate key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey(SubjectPublicKeyInfo keyInfo) throws IOException + { + AlgorithmIdentifier algId = keyInfo.getAlgorithm(); + + if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.rsaEncryption) + || algId.getAlgorithm().equals(X509ObjectIdentifiers.id_ea_rsa)) + { + RSAPublicKey pubKey = RSAPublicKey.getInstance(keyInfo.parsePublicKey()); + + return new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent()); + } + else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.dhpublicnumber)) + { + DHPublicKey dhPublicKey = DHPublicKey.getInstance(keyInfo.parsePublicKey()); + + BigInteger y = dhPublicKey.getY().getValue(); + + DHDomainParameters dhParams = DHDomainParameters.getInstance(algId.getParameters()); + + BigInteger p = dhParams.getP().getValue(); + BigInteger g = dhParams.getG().getValue(); + BigInteger q = dhParams.getQ().getValue(); + + BigInteger j = null; + if (dhParams.getJ() != null) + { + j = dhParams.getJ().getValue(); + } + + DHValidationParameters validation = null; + DHValidationParms dhValidationParms = dhParams.getValidationParms(); + if (dhValidationParms != null) + { + byte[] seed = dhValidationParms.getSeed().getBytes(); + BigInteger pgenCounter = dhValidationParms.getPgenCounter().getValue(); + + // TODO Check pgenCounter size? + + validation = new DHValidationParameters(seed, pgenCounter.intValue()); + } + + return new DHPublicKeyParameters(y, new DHParameters(p, g, q, j, validation)); + } + else if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.dhKeyAgreement)) + { + DHParameter params = DHParameter.getInstance(algId.getParameters()); + ASN1Integer derY = (ASN1Integer)keyInfo.parsePublicKey(); + + BigInteger lVal = params.getL(); + int l = lVal == null ? 0 : lVal.intValue(); + DHParameters dhParams = new DHParameters(params.getP(), params.getG(), null, l); + + return new DHPublicKeyParameters(derY.getValue(), dhParams); + } + else if (algId.getAlgorithm().equals(OIWObjectIdentifiers.elGamalAlgorithm)) + { + ElGamalParameter params = new ElGamalParameter((ASN1Sequence)algId.getParameters()); + ASN1Integer derY = (ASN1Integer)keyInfo.parsePublicKey(); + + return new ElGamalPublicKeyParameters(derY.getValue(), new ElGamalParameters( + params.getP(), params.getG())); + } + else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_dsa) + || algId.getAlgorithm().equals(OIWObjectIdentifiers.dsaWithSHA1)) + { + ASN1Integer derY = (ASN1Integer)keyInfo.parsePublicKey(); + ASN1Encodable de = algId.getParameters(); + + DSAParameters parameters = null; + if (de != null) + { + DSAParameter params = DSAParameter.getInstance(de.toASN1Primitive()); + parameters = new DSAParameters(params.getP(), params.getQ(), params.getG()); + } + + return new DSAPublicKeyParameters(derY.getValue(), parameters); + } + else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey)) + { + X962Parameters params = new X962Parameters( + (ASN1Primitive)algId.getParameters()); + + X9ECParameters x9; + if (params.isNamedCurve()) + { + ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters(); + x9 = X962NamedCurves.getByOID(oid); + + if (x9 == null) + { + x9 = SECNamedCurves.getByOID(oid); + + if (x9 == null) + { + x9 = NISTNamedCurves.getByOID(oid); + + if (x9 == null) + { + x9 = TeleTrusTNamedCurves.getByOID(oid); + } + } + } + } + else + { + x9 = X9ECParameters.getInstance(params.getParameters()); + } + + ASN1OctetString key = new DEROctetString(keyInfo.getPublicKeyData().getBytes()); + X9ECPoint derQ = new X9ECPoint(x9.getCurve(), key); + + // TODO We lose any named parameters here + + ECDomainParameters dParams = new ECDomainParameters( + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + return new ECPublicKeyParameters(derQ.getPoint(), dParams); + } + else + { + throw new RuntimeException("algorithm identifier in key not recognised"); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/util/SubjectPublicKeyInfoFactory.java b/core/src/main/java/org/bouncycastle/crypto/util/SubjectPublicKeyInfoFactory.java new file mode 100644 index 00000000..bdc6cbd1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/util/SubjectPublicKeyInfoFactory.java @@ -0,0 +1,81 @@ +package org.bouncycastle.crypto.util; + +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.RSAPublicKey; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9ECPoint; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * Factory to create ASN.1 subject public key info objects from lightweight public keys. + */ +public class SubjectPublicKeyInfoFactory +{ + /** + * Create a SubjectPublicKeyInfo public key. + * + * @param publicKey the SubjectPublicKeyInfo encoding + * @return the appropriate key parameter + * @throws java.io.IOException on an error encoding the key + */ + public static SubjectPublicKeyInfo createSubjectPublicKeyInfo(AsymmetricKeyParameter publicKey) throws IOException + { + if (publicKey instanceof RSAKeyParameters) + { + RSAKeyParameters pub = (RSAKeyParameters)publicKey; + + return new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(pub.getModulus(), pub.getExponent())); + } + else if (publicKey instanceof DSAPublicKeyParameters) + { + DSAPublicKeyParameters pub = (DSAPublicKeyParameters)publicKey; + + return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), new ASN1Integer(pub.getY())); + } + else if (publicKey instanceof ECPublicKeyParameters) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters)publicKey; + ECDomainParameters domainParams = pub.getParameters(); + ASN1Encodable params; + + // TODO: need to handle named curves + if (domainParams == null) + { + params = new X962Parameters(DERNull.INSTANCE); // Implicitly CA + } + else + { + X9ECParameters ecP = new X9ECParameters( + domainParams.getCurve(), + domainParams.getG(), + domainParams.getN(), + domainParams.getH(), + domainParams.getSeed()); + + params = new X962Parameters(ecP); + } + + ASN1OctetString p = (ASN1OctetString)new X9ECPoint(pub.getQ()).toASN1Primitive(); + + return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets()); + } + else + { + throw new IOException("key parameters not recognised."); + } + } +} |