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; } }