| 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; |
| import org.bouncycastle.util.Arrays; |
| |
| /** |
| * 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 |
| { |
| /** |
| * @deprecated use NOT_STRICT_LENGTH_ENABLED_PROPERTY |
| */ |
| public static final String STRICT_LENGTH_ENABLED_PROPERTY = "org.bouncycastle.pkcs1.strict"; |
| |
| /** |
| * 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.not_strict to true. |
| * <p> |
| * The system property is checked during construction of the encoding object, it is set to |
| * false by default. |
| * </p> |
| */ |
| public static final String NOT_STRICT_LENGTH_ENABLED_PROPERTY = "org.bouncycastle.pkcs1.not_strict"; |
| |
| private static final int HEADER_LENGTH = 10; |
| |
| private SecureRandom random; |
| private AsymmetricBlockCipher engine; |
| private boolean forEncryption; |
| private boolean forPrivateKey; |
| private boolean useStrictLength; |
| private int pLen = -1; |
| private byte[] fallback = null; |
| private byte[] blockBuffer; |
| |
| /** |
| * Basic constructor. |
| * |
| * @param cipher |
| */ |
| public PKCS1Encoding( |
| AsymmetricBlockCipher cipher) |
| { |
| this.engine = cipher; |
| this.useStrictLength = useStrict(); |
| } |
| |
| /** |
| * Constructor for decryption with a fixed plaintext length. |
| * |
| * @param cipher The cipher to use for cryptographic operation. |
| * @param pLen Length of the expected plaintext. |
| */ |
| public PKCS1Encoding( |
| AsymmetricBlockCipher cipher, |
| int pLen) |
| { |
| this.engine = cipher; |
| this.useStrictLength = useStrict(); |
| this.pLen = pLen; |
| } |
| |
| /** |
| * Constructor for decryption with a fixed plaintext length and a fallback |
| * value that is returned, if the padding is incorrect. |
| * |
| * @param cipher The cipher to use for cryptographic operation. |
| * @param fallback The fallback value, we don't do an arraycopy here. |
| */ |
| public PKCS1Encoding( |
| AsymmetricBlockCipher cipher, |
| byte[] fallback) |
| { |
| this.engine = cipher; |
| this.useStrictLength = useStrict(); |
| this.fallback = fallback; |
| this.pLen = fallback.length; |
| } |
| |
| |
| // |
| // 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); |
| } |
| }); |
| String notStrict = (String)AccessController.doPrivileged(new PrivilegedAction() |
| { |
| public Object run() |
| { |
| return System.getProperty(NOT_STRICT_LENGTH_ENABLED_PROPERTY); |
| } |
| }); |
| |
| if (notStrict != null) |
| { |
| return !notStrict.equals("true"); |
| } |
| |
| 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 |
| { |
| kParam = (AsymmetricKeyParameter)param; |
| if (!kParam.isPrivate() && forEncryption) |
| { |
| this.random = new SecureRandom(); |
| } |
| } |
| |
| engine.init(forEncryption, param); |
| |
| this.forPrivateKey = kParam.isPrivate(); |
| this.forEncryption = forEncryption; |
| this.blockBuffer = new byte[engine.getOutputBlockSize()]; |
| |
| if (pLen > 0 && fallback == null && random == null) |
| { |
| throw new IllegalArgumentException("encoder requires random"); |
| } |
| } |
| |
| 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); |
| } |
| |
| /** |
| * Checks if the argument is a correctly PKCS#1.5 encoded Plaintext |
| * for encryption. |
| * |
| * @param encoded The Plaintext. |
| * @param pLen Expected length of the plaintext. |
| * @return Either 0, if the encoding is correct, or -1, if it is incorrect. |
| */ |
| private static int checkPkcs1Encoding(byte[] encoded, int pLen) |
| { |
| int correct = 0; |
| /* |
| * Check if the first two bytes are 0 2 |
| */ |
| correct |= (encoded[0] ^ 2); |
| |
| /* |
| * Now the padding check, check for no 0 byte in the padding |
| */ |
| int plen = encoded.length - ( |
| pLen /* Lenght of the PMS */ |
| + 1 /* Final 0-byte before PMS */ |
| ); |
| |
| for (int i = 1; i < plen; i++) |
| { |
| int tmp = encoded[i]; |
| tmp |= tmp >> 1; |
| tmp |= tmp >> 2; |
| tmp |= tmp >> 4; |
| correct |= (tmp & 1) - 1; |
| } |
| |
| /* |
| * Make sure the padding ends with a 0 byte. |
| */ |
| correct |= encoded[encoded.length - (pLen + 1)]; |
| |
| /* |
| * Return 0 or 1, depending on the result. |
| */ |
| correct |= correct >> 1; |
| correct |= correct >> 2; |
| correct |= correct >> 4; |
| return ~((correct & 1) - 1); |
| } |
| |
| |
| /** |
| * Decode PKCS#1.5 encoding, and return a random value if the padding is not correct. |
| * |
| * @param in The encrypted block. |
| * @param inOff Offset in the encrypted block. |
| * @param inLen Length of the encrypted block. |
| * //@param pLen Length of the desired output. |
| * @return The plaintext without padding, or a random value if the padding was incorrect. |
| * @throws InvalidCipherTextException |
| */ |
| private byte[] decodeBlockOrRandom(byte[] in, int inOff, int inLen) |
| throws InvalidCipherTextException |
| { |
| if (!forPrivateKey) |
| { |
| throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing"); |
| } |
| |
| byte[] block = engine.processBlock(in, inOff, inLen); |
| byte[] random; |
| if (this.fallback == null) |
| { |
| random = new byte[this.pLen]; |
| this.random.nextBytes(random); |
| } |
| else |
| { |
| random = fallback; |
| } |
| |
| byte[] data = (useStrictLength & (block.length != engine.getOutputBlockSize())) ? blockBuffer : block; |
| |
| /* |
| * Check the padding. |
| */ |
| int correct = PKCS1Encoding.checkPkcs1Encoding(data, this.pLen); |
| |
| /* |
| * Now, to a constant time constant memory copy of the decrypted value |
| * or the random value, depending on the validity of the padding. |
| */ |
| byte[] result = new byte[this.pLen]; |
| for (int i = 0; i < this.pLen; i++) |
| { |
| result[i] = (byte)((data[i + (data.length - pLen)] & (~correct)) | (random[i] & correct)); |
| } |
| |
| Arrays.fill(data, (byte)0); |
| |
| return result; |
| } |
| |
| /** |
| * @throws InvalidCipherTextException if the decrypted block is not in PKCS1 format. |
| */ |
| private byte[] decodeBlock( |
| byte[] in, |
| int inOff, |
| int inLen) |
| throws InvalidCipherTextException |
| { |
| /* |
| * If the length of the expected plaintext is known, we use a constant-time decryption. |
| * If the decryption fails, we return a random value. |
| */ |
| if (this.pLen != -1) |
| { |
| return this.decodeBlockOrRandom(in, inOff, inLen); |
| } |
| |
| byte[] block = engine.processBlock(in, inOff, inLen); |
| boolean incorrectLength = (useStrictLength & (block.length != engine.getOutputBlockSize())); |
| |
| byte[] data; |
| if (block.length < getOutputBlockSize()) |
| { |
| data = blockBuffer; |
| } |
| else |
| { |
| data = block; |
| } |
| |
| byte type = data[0]; |
| |
| boolean badType; |
| if (forPrivateKey) |
| { |
| badType = (type != 2); |
| } |
| else |
| { |
| badType = (type != 1); |
| } |
| |
| // |
| // find and extract the message block. |
| // |
| int start = findStart(type, data); |
| |
| start++; // data should start at the next byte |
| |
| if (badType | start < HEADER_LENGTH) |
| { |
| Arrays.fill(data, (byte)0); |
| throw new InvalidCipherTextException("block incorrect"); |
| } |
| |
| // if we get this far, it's likely to be a genuine encoding error |
| if (incorrectLength) |
| { |
| Arrays.fill(data, (byte)0); |
| throw new InvalidCipherTextException("block incorrect size"); |
| } |
| |
| byte[] result = new byte[data.length - start]; |
| |
| System.arraycopy(data, start, result, 0, result.length); |
| |
| return result; |
| } |
| |
| private int findStart(byte type, byte[] block) |
| throws InvalidCipherTextException |
| { |
| int start = -1; |
| boolean padErr = false; |
| |
| for (int i = 1; i != block.length; i++) |
| { |
| byte pad = block[i]; |
| |
| if (pad == 0 & start < 0) |
| { |
| start = i; |
| } |
| padErr |= (type == 1 & start < 0 & pad != (byte)0xff); |
| } |
| |
| if (padErr) |
| { |
| return -1; |
| } |
| |
| return start; |
| } |
| } |