diff options
author | David Hook <dgh@cryptoworkshop.com> | 2013-05-31 11:07:45 +0400 |
---|---|---|
committer | David Hook <dgh@cryptoworkshop.com> | 2013-05-31 11:07:45 +0400 |
commit | 2b976f5364cfdbc37d3086019d93483c983eb80b (patch) | |
tree | cb846af3fd1d43f9c2562a1fb2d06b997ad8f229 /core/src/main/java/org/bouncycastle/crypto/encodings | |
parent | 5f714bd92fbd780d22406f4bc3681be005f6f04a (diff) |
initial reshuffle
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto/encodings')
3 files changed, 901 insertions, 0 deletions
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; + } +} |