diff options
Diffstat (limited to 'libraries/spongycastle/core/src/main/java/org/spongycastle/crypto/encodings/OAEPEncoding.java')
-rw-r--r-- | libraries/spongycastle/core/src/main/java/org/spongycastle/crypto/encodings/OAEPEncoding.java | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/libraries/spongycastle/core/src/main/java/org/spongycastle/crypto/encodings/OAEPEncoding.java b/libraries/spongycastle/core/src/main/java/org/spongycastle/crypto/encodings/OAEPEncoding.java new file mode 100644 index 000000000..11796fcec --- /dev/null +++ b/libraries/spongycastle/core/src/main/java/org/spongycastle/crypto/encodings/OAEPEncoding.java @@ -0,0 +1,357 @@ +package org.spongycastle.crypto.encodings; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.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; + } +} |