| /* RSACipherImpl.java -- |
| Copyright (C) 2005, 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.javax.crypto; |
| |
| import gnu.classpath.debug.Component; |
| import gnu.classpath.debug.SystemLogger; |
| import gnu.java.security.util.ByteArray; |
| |
| import java.math.BigInteger; |
| import java.security.AlgorithmParameters; |
| import java.security.InvalidKeyException; |
| import java.security.Key; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.SecureRandom; |
| import java.security.interfaces.RSAKey; |
| import java.security.interfaces.RSAPrivateCrtKey; |
| import java.security.interfaces.RSAPrivateKey; |
| import java.security.interfaces.RSAPublicKey; |
| import java.security.spec.AlgorithmParameterSpec; |
| import java.util.logging.Logger; |
| |
| import javax.crypto.BadPaddingException; |
| import javax.crypto.Cipher; |
| import javax.crypto.CipherSpi; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.NoSuchPaddingException; |
| import javax.crypto.ShortBufferException; |
| |
| public class RSACipherImpl |
| extends CipherSpi |
| { |
| private static final Logger logger = SystemLogger.SYSTEM; |
| private static final byte[] EMPTY = new byte[0]; |
| private int opmode = -1; |
| private RSAPrivateKey decipherKey = null; |
| private RSAPublicKey blindingKey = null; |
| private RSAPublicKey encipherKey = null; |
| private SecureRandom random = null; |
| private byte[] dataBuffer = null; |
| private int pos = 0; |
| |
| protected void engineSetMode(String mode) throws NoSuchAlgorithmException |
| { |
| throw new NoSuchAlgorithmException("only one mode available"); |
| } |
| |
| protected void engineSetPadding(String pad) throws NoSuchPaddingException |
| { |
| throw new NoSuchPaddingException("only one padding available"); |
| } |
| |
| protected int engineGetBlockSize() |
| { |
| return 1; |
| } |
| |
| protected int engineGetOutputSize(int inputLen) |
| { |
| int outputLen = 0; |
| if (decipherKey != null) |
| outputLen = (decipherKey.getModulus().bitLength() + 7) / 8; |
| else if (encipherKey != null) |
| outputLen = (encipherKey.getModulus().bitLength() + 7) / 8; |
| else |
| throw new IllegalStateException("not initialized"); |
| if (inputLen > outputLen) |
| throw new IllegalArgumentException("not configured to encode " + inputLen |
| + "bytes; at most " + outputLen); |
| return outputLen; |
| } |
| |
| protected int engineGetKeySize(final Key key) throws InvalidKeyException |
| { |
| if (! (key instanceof RSAKey)) |
| throw new InvalidKeyException("not an RSA key"); |
| return ((RSAKey) key).getModulus().bitLength(); |
| } |
| |
| protected byte[] engineGetIV() |
| { |
| return null; |
| } |
| |
| protected AlgorithmParameters engineGetParameters() |
| { |
| return null; |
| } |
| |
| protected void engineInit(int opmode, Key key, SecureRandom random) |
| throws InvalidKeyException |
| { |
| int outputLen = 0; |
| if (opmode == Cipher.ENCRYPT_MODE) |
| { |
| if (! (key instanceof RSAPublicKey)) |
| throw new InvalidKeyException("expecting a RSAPublicKey"); |
| encipherKey = (RSAPublicKey) key; |
| decipherKey = null; |
| blindingKey = null; |
| outputLen = (encipherKey.getModulus().bitLength() + 7) / 8; |
| } |
| else if (opmode == Cipher.DECRYPT_MODE) |
| { |
| if (key instanceof RSAPrivateKey) |
| { |
| decipherKey = (RSAPrivateKey) key; |
| encipherKey = null; |
| blindingKey = null; |
| outputLen = (decipherKey.getModulus().bitLength() + 7) / 8; |
| } |
| else if (key instanceof RSAPublicKey) |
| { |
| if (decipherKey == null) |
| throw new IllegalStateException("must configure decryption key first"); |
| if (! decipherKey.getModulus().equals(((RSAPublicKey) key).getModulus())) |
| throw new InvalidKeyException("blinding key is not compatible"); |
| blindingKey = (RSAPublicKey) key; |
| return; |
| } |
| else |
| throw new InvalidKeyException( |
| "expecting either an RSAPrivateKey or an RSAPublicKey (for blinding)"); |
| } |
| else |
| throw new IllegalArgumentException("only encryption and decryption supported"); |
| this.random = random; |
| this.opmode = opmode; |
| pos = 0; |
| dataBuffer = new byte[outputLen]; |
| } |
| |
| protected void engineInit(int opmode, Key key, AlgorithmParameterSpec spec, |
| SecureRandom random) throws InvalidKeyException |
| { |
| engineInit(opmode, key, random); |
| } |
| |
| protected void engineInit(int opmode, Key key, AlgorithmParameters params, |
| SecureRandom random) throws InvalidKeyException |
| { |
| engineInit(opmode, key, random); |
| } |
| |
| protected byte[] engineUpdate(byte[] in, int offset, int length) |
| { |
| if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) |
| throw new IllegalStateException("not initialized"); |
| System.arraycopy(in, offset, dataBuffer, pos, length); |
| pos += length; |
| return EMPTY; |
| } |
| |
| protected int engineUpdate(byte[] in, int offset, int length, byte[] out, |
| int outOffset) |
| { |
| engineUpdate(in, offset, length); |
| return 0; |
| } |
| |
| protected byte[] engineDoFinal(byte[] in, int offset, int length) |
| throws IllegalBlockSizeException, BadPaddingException |
| { |
| engineUpdate(in, offset, length); |
| if (opmode == Cipher.DECRYPT_MODE) |
| { |
| if (pos < dataBuffer.length) |
| throw new IllegalBlockSizeException("expecting exactly " |
| + dataBuffer.length + " bytes"); |
| BigInteger enc = new BigInteger(1, dataBuffer); |
| byte[] dec = rsaDecrypt(enc); |
| logger.log(Component.CRYPTO, "RSA: decryption produced\n{0}", |
| new ByteArray(dec)); |
| if (dec[0] != 0x02) |
| throw new BadPaddingException("expected padding type 2"); |
| int i; |
| for (i = 1; i < dec.length && dec[i] != 0x00; i++) |
| ; // keep incrementing i |
| int len = dec.length - i - 1; // skip the 0x00 byte |
| byte[] result = new byte[len]; |
| System.arraycopy(dec, i + 1, result, 0, len); |
| pos = 0; |
| return result; |
| } |
| else |
| { |
| offset = dataBuffer.length - pos; |
| if (offset < 3) |
| throw new IllegalBlockSizeException("input is too large to encrypt"); |
| byte[] dec = new byte[dataBuffer.length]; |
| dec[0] = 0x02; |
| if (random == null) |
| random = new SecureRandom(); |
| byte[] pad = new byte[offset - 2]; |
| random.nextBytes(pad); |
| for (int i = 0; i < pad.length; i++) |
| if (pad[i] == 0) |
| pad[i] = 1; |
| System.arraycopy(pad, 0, dec, 1, pad.length); |
| dec[dec.length - pos] = 0x00; |
| System.arraycopy(dataBuffer, 0, dec, offset, pos); |
| logger.log(Component.CRYPTO, "RSA: produced padded plaintext\n{0}", |
| new ByteArray(dec)); |
| BigInteger x = new BigInteger(1, dec); |
| BigInteger y = x.modPow(encipherKey.getPublicExponent(), |
| encipherKey.getModulus()); |
| byte[] enc = y.toByteArray(); |
| if (enc[0] == 0x00) |
| { |
| byte[] tmp = new byte[enc.length - 1]; |
| System.arraycopy(enc, 1, tmp, 0, tmp.length); |
| enc = tmp; |
| } |
| pos = 0; |
| return enc; |
| } |
| } |
| |
| protected int engineDoFinal(byte[] out, int offset) |
| throws ShortBufferException, IllegalBlockSizeException, |
| BadPaddingException |
| { |
| byte[] result = engineDoFinal(EMPTY, 0, 0); |
| if (out.length - offset < result.length) |
| throw new ShortBufferException("need " + result.length + ", have " |
| + (out.length - offset)); |
| System.arraycopy(result, 0, out, offset, result.length); |
| return result.length; |
| } |
| |
| protected int engineDoFinal(final byte[] input, final int offset, |
| final int length, final byte[] output, |
| final int outputOffset) |
| throws ShortBufferException, IllegalBlockSizeException, |
| BadPaddingException |
| { |
| byte[] result = engineDoFinal(input, offset, length); |
| if (output.length - outputOffset < result.length) |
| throw new ShortBufferException("need " + result.length + ", have " |
| + (output.length - outputOffset)); |
| System.arraycopy(result, 0, output, outputOffset, result.length); |
| return result.length; |
| } |
| |
| /** |
| * Decrypts the ciphertext, employing RSA blinding if possible. |
| */ |
| private byte[] rsaDecrypt(BigInteger enc) |
| { |
| if (random == null) |
| random = new SecureRandom(); |
| BigInteger n = decipherKey.getModulus(); |
| BigInteger r = null; |
| BigInteger pubExp = null; |
| if (blindingKey != null) |
| pubExp = blindingKey.getPublicExponent(); |
| if (pubExp != null && (decipherKey instanceof RSAPrivateCrtKey)) |
| pubExp = ((RSAPrivateCrtKey) decipherKey).getPublicExponent(); |
| if (pubExp != null) |
| { |
| r = new BigInteger(n.bitLength() - 1, random); |
| enc = r.modPow(pubExp, n).multiply(enc).mod(n); |
| } |
| BigInteger dec = enc.modPow(decipherKey.getPrivateExponent(), n); |
| if (pubExp != null) |
| dec = dec.multiply(r.modInverse(n)).mod(n); |
| return dec.toByteArray(); |
| } |
| } |