/**
RawRSACipher.java
Cribbed from Cryptix by ekr
Blinding code Copyright (C) 2003, RTFM, Inc.
All Rights Reserved.
*/
// $Id: RawRSACipher.java,v 1.1.1.1 2003/05/09 20:36:14 gawor Exp $
//
// $Log: RawRSACipher.java,v $
// Revision 1.1.1.1 2003/05/09 20:36:14 gawor
// imported sources
//
// Revision 1.11 2000/08/18 02:32:23 edwin
// Oops, wrong check ;)
// (surprisingly enough the tests didn;t catch this)
//
// Revision 1.10 2000/08/17 11:41:00 edwin
// java.* -> xjava.*
//
// Revision 1.9 2000/08/16 17:51:43 edwin
// Throw a little more sensible exception when decryption fails and the decrypted
// data is one byte longer than it should.
//
// The code here is a little braindamaged, because it assumes that the first
// byte of the plaintext is always zero. If decryption fails due to a wrong key,
// this is often not the case, which results in a very weird exception.
//
// We still throw the original weird ArrayIndexOutOfBoundsException for
// compatibility, because that is what the old code throwed, however it does
// have a more descriptive message now.
//
// All of this will be documented in the to be written RSA faq, which details
// all peculiarities of the RSA code in cryptix3.
//
// Revision 1.8 1999/07/12 20:34:21 edwin
// renaming java.security.interfaces.RSAPrivateKey and RSAPublicKey to CryptixRSAPrivateKey and CryptixRSAPublicKey. This is one more step to JDK1.2 compatibility.
//
// Revision 1.7 1997/12/23 23:47:51 raif
// *** empty log message ***
//
// Revision 1.6.1 1997/12/24 raif
// + fixed the self-test. now runs OK.
// + rewrote the engineUpdate().
// + added enginePlaintextBlockSize(), engineCiphertextBlockSize()
// and crippled engineBlockSize(). The rationale is that with
// RSA, the encryption/decryption results usually do not match
// their inputs in term of bit/byte-length.
// This fixes the blockSize bug.
//
// Revision 1.6 1997/12/22 03:16:33 hopwood
// + RawRSACipher framing bug fixed.
// + BaseRSAKeyPairGenerator test #2 fixed.
//
// Revision 1.5 1997/11/23 03:09:18 hopwood
// + Mostly documentation changes.
//
// Revision 1.4 1997/11/20 19:46:57 hopwood
// + cryptix.util.* name changes.
//
// Revision 1.3.1 1997/11/18 David Hopwood
// + Use BI.dumpString to print parameters in test method.
// + Fixed bug in test method (wrong value for e was being displayed).
//
// Revision 1.3 1997/11/05 16:48:03 raif
// *** empty log message ***
//
// Revision 1.2 1997/11/04 19:33:31 raif
// *** empty log message ***
//
// Revision 1.1.1.1 1997/11/03 22:36:56 hopwood
// + Imported to CVS (tagged as 'start').
//
// Revision 0.1.1.1 1997/08/27 David Hopwood
// + Misc. fixes.
//
// Revision 0.1.1.0 1997/08/23 David Hopwood
// + Changed to use the RSA interfaces from java.security.*, rather
// than requiring a particular key class.
// + Plaintext and ciphertext block sizes are now equal; we throw a
// CryptixException if a BigInteger to be encrypted is out of range.
// + Use the RSA implementation from the RSAAlgorithm class.
// + Use new debugging conventions.
//
// Revision 0.1.0.0 1997/07/24 R. Naffah
// + Original version.
//
// $Endlog$
/*
* Copyright (c) 1997 Systemics Ltd
* on behalf of the Cryptix Development Team. All rights reserved.
*/
// package cryptix.provider.rsa;
package COM.claymoresystems.provider;
import cryptix.util.core.Debug;
import cryptix.CryptixException;
import cryptix.util.core.ArrayUtil;
import cryptix.util.core.Hex;
import cryptix.util.core.BI;
import java.io.PrintWriter;
import java.math.BigInteger;
import xjava.security.Cipher;
import xjava.security.AsymmetricCipher;
import COM.claymoresystems.crypto.Blindable;
import java.security.Key;
import java.security.KeyException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import xjava.security.interfaces.RSAKey;
import xjava.security.interfaces.CryptixRSAPublicKey;
import xjava.security.interfaces.CryptixRSAPrivateKey;
import xjava.security.interfaces.RSAFactors;
import java.security.SecureRandom;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
/**
* The raw RSA encryption algorithm, without any block framing.
* <p>
* If the number of bits in the modulus is <i>bitlength</i>, the plaintext and
* ciphertext block sizes will both be <code>(int) ((bitlength+7)/8)</code>.
* When in ENCRYPT mode, if the value of any plaintext block, considered as a
* BigInteger with Big-Endian byte order, is greater than or equal to the
* modulus, a CryptixException will be thrown.
* <p>
* This class is designed to allow any input to the RSA encryption algorithm,
* in order to facilitate implementation of standards which use a block framing
* format not otherwise supported by Cryptix. Note that using raw RSA directly on
* application data is potentially insecure; wherever possible a higher level
* algorithm such as "RSA/PKCS#1" should be used.
* <p>
* (Note: RSA/PKCS#1 is not implemented in this version of Cryptix.)
* <p>
* <b>References:</b>
* <ol>
* <li> <a href="mailto:schneier@counterpane.com">Bruce Schneier</a>,
* "Section 19.3 RSA,"
* <cite>Applied Cryptography, 2nd edition</cite>,
* John Wiley & Sons, 1996.
* </ol>
* <p>
* <b>Copyright</b> © 1997
* <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the
* <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>.
* <br>All rights reserved.
* <p>
* <b>$Revision: 1.1.1.1 $</b>
* @author Raif S. Naffah
* @author David Hopwood
* @since Cryptix 2.2.2
*/
public class RawRSACipher
extends Cipher
implements AsymmetricCipher, Cloneable, Blindable
{
// Debugging methods and vars.
//...........................................................................
private static final boolean DEBUG = Debug.GLOBAL_DEBUG;
private static final int debuglevel =
DEBUG ? Debug.getLevel("RSA", "RawRSACipher") : 0;
private static final PrintWriter err = DEBUG ? Debug.getOutput() : null;
private static void debug(String s) { err.println("RawRSACipher: " + s); }
// RSA constants and variables
//...........................................................................
/** true if temporary arrays should be wiped immediately. */
private static final boolean WIPE = true;
private static final int POSITIVE = 1;
private static final BigInteger ZERO = BigInteger.valueOf(0L);
private static final BigInteger ONE = BigInteger.valueOf(1L);
private BigInteger n;
private BigInteger exp; // e if state is ENCRYPT, d if DECRYPT.
private BigInteger publicExp = null; // e if state == DECRYPT
private BigInteger p; // null if the factors of n are unknown.
private BigInteger q;
private BigInteger u;
private int blockSize; // defaults to 0
private byte[] temp;
private SecureRandom blindingRNG = null;
// Constructor
//............................................................................
/**
* Constructs a RawRSA cipher object, in the UNINITIALIZED state.
* This calls the Cipher constructor with <i>implBuffering</i> false,
* <i>implPadding</i> false and the provider set to "Cryptix".
*/
public RawRSACipher() {
super(false, false, "Cryptix");
}
// JCE methods
//............................................................................
/**
* <b>SPI:</b> Initializes this cipher object for encryption, using the
* given public key.
*
* @param key the public key to be used for encryption.
* @exception InvalidKeyException if the key class does not implement
* java.security.interfaces.RSAPublicKey.
*/
protected void engineInitEncrypt(Key key)
throws InvalidKeyException {
if (!(key instanceof CryptixRSAPublicKey))
throw new InvalidKeyException(getAlgorithm() +
": Not an RSA public key");
CryptixRSAPublicKey rsa = (CryptixRSAPublicKey) key;
n = rsa.getModulus();
exp = rsa.getExponent();
if (key instanceof RSAFactors) {
RSAFactors factors = (RSAFactors) key;
p = factors.getP();
q = factors.getQ();
u = factors.getInverseOfQModP();
}
initInternal();
}
/**
* <b>SPI:</b> Initializes this cipher object for decryption, using the
* given private key.
*
* @param key the private key to be used for decryption.
* @exception InvalidKeyException if the key class does not implement
* java.security.interfaces.RSAPrivateKey.
*/
protected void engineInitDecrypt(Key key)
throws InvalidKeyException {
if (!(key instanceof CryptixRSAPrivateKey))
throw new InvalidKeyException(getAlgorithm() +
": Not an RSA private key");
CryptixRSAPrivateKey rsa = (CryptixRSAPrivateKey) key;
n = rsa.getModulus();
exp = rsa.getExponent();
if (key instanceof RSAFactors) {
RSAFactors factors = (RSAFactors) key;
p = factors.getP();
q = factors.getQ();
u = factors.getInverseOfQModP();
}
initInternal();
}
private void initInternal() {
// blockSize = (n.bitLength()+7)/8;
blockSize = BI.getMagnitude(n).length;
temp = new byte[blockSize];
}
/**
* <b>SPI</b>: Return the block size, in bytes. For RawRSA this is the number
* of bytes needed to represent the modulus, <i>n</i>.
*
* @return the block size in bytes.
* @exception CryptixException if the cipher object is uninitialized.
*/
// protected int engineBlockSize() {
// if (blockSize == 0) throw new CryptixException(getAlgorithm() +
// ": Block size is not valid until key is set");
// return blockSize;
// }
protected int enginePlaintextBlockSize () {
if (blockSize == 0) throw new CryptixException(getAlgorithm() +
": Block size is not valid until key is set");
return blockSize - 1;
}
protected int engineCiphertextBlockSize() {
if (blockSize == 0) throw new CryptixException(getAlgorithm() +
": Block size is not valid until key is set");
return blockSize;
}
protected int engineUpdate(byte[] in, int inOffset, int inLen,
byte[] out, int outOffset) {
/*
if (DEBUG && debuglevel >= 7) debug("in.length = " + in.length + ", out.length = " + out.length + ", blockSize = " + blockSize);
for (int i = 0; i <= inLen-blockSize; i += blockSize) {
if (DEBUG && debuglevel >= 8) debug("i = " + i);
System.arraycopy(in, inOffset+i, temp, 0, blockSize);
BigInteger I = new BigInteger(POSITIVE, temp);
if (I.compareTo(n) >= 0) throw new CryptixException(getAlgorithm() +
": Input block value is out of range (>= modulus)");
BigInteger O = RSAAlgorithm.rsa(I, n, exp, p, q, u);
byte[] buffer = O.toByteArray();
int displacement = blockSize-buffer.length;
if (displacement > 0) {
ArrayUtil.clear(out, outOffset+i, displacement);
System.arraycopy(buffer, 0, out, outOffset+i+displacement, buffer.length);
} else {
System.arraycopy(buffer, -displacement, out, outOffset+i, blockSize);
}
if (WIPE) ArrayUtil.clear(buffer);
}
if (WIPE) ArrayUtil.clear(temp);
return blockSize;
*/
if (inLen < 0) throw new IllegalArgumentException("inLen < 0");
int inBlockSize = getState() == ENCRYPT ?
enginePlaintextBlockSize() : engineCiphertextBlockSize();
int outBlockSize = getState() == ENCRYPT ?
engineCiphertextBlockSize() : enginePlaintextBlockSize();
int blockCount = inLen / inBlockSize;
for (int i = 0; i < blockCount; i++) {
ArrayUtil.clear(temp);
System.arraycopy(in, inOffset, temp, temp.length - inBlockSize, inBlockSize);
BigInteger I = new BigInteger(POSITIVE, temp);
if (I.compareTo(n) >= 0) throw new CryptixException(getAlgorithm() +
": Input block value is out of range (>= modulus)");
BigInteger O = RSAAlgorithmBlind.rsa(I, n, exp, publicExp, p, q, u, blindingRNG);
byte[] buffer = BI.getMagnitude(O);
if (buffer.length > outBlockSize) {
throw new ArrayIndexOutOfBoundsException("Decryption failed, "+
"wrong key?");
}
ArrayUtil.clear(temp);
System.arraycopy(buffer, 0, temp, outBlockSize - buffer.length, buffer.length);
System.arraycopy(temp, 0, out, outOffset, outBlockSize);
inOffset += inBlockSize;
outOffset += outBlockSize;
}
return blockCount * outBlockSize;
}
// Blindable implementation
/**
* set the RNG for blinding
*
* @param rng a secure RNG
*/
public void setBlindingInfo(SecureRandom rng,CryptixRSAPublicKey pubKey){
blindingRNG = rng;
publicExp=pubKey.getExponent();
}
// Test methods
//...........................................................................
//
// Don't expand this code please without thinking about it,
// much better to write a separate program.
//
/**
* Entry point for very basic <code>self_test</code>.
*/
public static final void main(String[] args) {
try {
self_test(new PrintWriter(System.out, true));
} catch (Exception e) { e.printStackTrace(); }
}
public static void self_test(PrintWriter out)
throws Exception {
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA", "Cryptix");
SecureRandom random = new SecureRandom();
long start = System.currentTimeMillis();
keygen.initialize(1024, random);
KeyPair keypair = keygen.generateKeyPair();
long duration = System.currentTimeMillis() - start;
out.println("Keygen: " + (float)duration/1000 + " seconds");
RawRSACipher raw = new RawRSACipher();
raw.test(out, keypair, random);
}
private void test(PrintWriter out, KeyPair keypair, SecureRandom random)
throws KeyException {
CryptixRSAPrivateKey privateKey = (CryptixRSAPrivateKey) (keypair.getPrivate());
CryptixRSAPublicKey publicKey = (CryptixRSAPublicKey) (keypair.getPublic());
// byte[] M = new byte[blockSize()];
// random.nextBytes(M);
long start = System.currentTimeMillis();
initEncrypt(publicKey);
BigInteger e = exp;
byte[] M = new byte[getPlaintextBlockSize()];
random.nextBytes(M);
byte[] C = crypt(M);
long midpoint = System.currentTimeMillis();
initDecrypt(privateKey);
byte[] Mdash = crypt(C);
long end = System.currentTimeMillis();
out.println(" n = " + BI.dumpString(n));
out.println(" e = " + BI.dumpString(e));
out.println(" d = " + BI.dumpString(exp));
out.println(" p = " + BI.dumpString(p));
out.println(" q = " + BI.dumpString(q));
out.println("q^-1 mod p = " + BI.dumpString(u));
out.println(" plaintext = " + Hex.toString(M) + "\n");
out.println("ciphertext = " + Hex.toString(C) + "\n");
if (!ArrayUtil.areEqual(M, Mdash)) {
out.println("DECRYPTION FAILED!\n");
out.println(" computed = " + Hex.toString(Mdash) + "\n");
}
out.println("Encrypt: " + ((float) (midpoint - start) / 1000) + " seconds");
out.println("Decrypt: " + ((float) (end - midpoint) / 1000) + " seconds");
}
}