blob: 9cd3f797c022ec04c41601379ee3e1331375542f [file] [log] [blame]
package org.bouncycastle.crypto.engines;
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.params.CramerShoupKeyParameters;
import org.bouncycastle.crypto.params.CramerShoupPrivateKeyParameters;
import org.bouncycastle.crypto.params.CramerShoupPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Strings;
/**
* Essentially the Cramer-Shoup encryption / decryption algorithms according to
* "A practical public key cryptosystem provably secure against adaptive chosen ciphertext attack." (Crypto 1998)
*/
public class CramerShoupCoreEngine
{
private static final BigInteger ONE = BigInteger.valueOf(1);
private CramerShoupKeyParameters key;
private SecureRandom random;
private boolean forEncryption;
private byte[] label = null;
/**
* initialise the CramerShoup engine.
*
* @param forEncryption whether this engine should encrypt or decrypt
* @param param the necessary CramerShoup key parameters.
* @param label the label for labelled CS as {@link String}
*/
public void init(boolean forEncryption, CipherParameters param, String label)
{
init(forEncryption, param);
this.label = Strings.toUTF8ByteArray(label);
}
/**
* initialise the CramerShoup engine.
*
* @param forEncryption whether this engine should encrypt or decrypt
* @param param the necessary CramerShoup key parameters.
*/
public void init(boolean forEncryption, CipherParameters param)
{
SecureRandom providedRandom = null;
if (param instanceof ParametersWithRandom)
{
ParametersWithRandom rParam = (ParametersWithRandom)param;
key = (CramerShoupKeyParameters)rParam.getParameters();
providedRandom = rParam.getRandom();
}
else
{
key = (CramerShoupKeyParameters)param;
}
this.random = initSecureRandom(forEncryption, providedRandom);
this.forEncryption = forEncryption;
}
/**
* Return the maximum size for an input block to this engine. For Cramer
* Shoup this is always one byte less than the key size on encryption, and
* the same length as the key size on decryption.
* TODO: correct?
* @return maximum size for an input block.
*/
public int getInputBlockSize()
{
int bitSize = key.getParameters().getP().bitLength();
if (forEncryption)
{
return (bitSize + 7) / 8 - 1;
}
else
{
return (bitSize + 7) / 8;
}
}
/**
* Return the maximum size for an output block to this engine. For Cramer
* Shoup this is always one byte less than the key size on decryption, and
* the same length as the key size on encryption.
* TODO: correct?
* @return maximum size for an output block.
*/
public int getOutputBlockSize()
{
int bitSize = key.getParameters().getP().bitLength();
if (forEncryption)
{
return (bitSize + 7) / 8;
}
else
{
return (bitSize + 7) / 8 - 1;
}
}
public BigInteger convertInput(byte[] in, int inOff, int inLen)
{
if (inLen > (getInputBlockSize() + 1))
{
throw new DataLengthException("input too large for Cramer Shoup cipher.");
}
else if (inLen == (getInputBlockSize() + 1) && forEncryption)
{
throw new DataLengthException("input too large for Cramer Shoup cipher.");
}
byte[] block;
if (inOff != 0 || inLen != in.length)
{
block = new byte[inLen];
System.arraycopy(in, inOff, block, 0, inLen);
}
else
{
block = in;
}
BigInteger res = new BigInteger(1, block);
if (res.compareTo(key.getParameters().getP()) >= 0)
{
throw new DataLengthException("input too large for Cramer Shoup cipher.");
}
return res;
}
public byte[] convertOutput(BigInteger result)
{
byte[] output = result.toByteArray();
if (!forEncryption)
{
if (output[0] == 0 && output.length > getOutputBlockSize())
{ // 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;
}
if (output.length < getOutputBlockSize())
{// have ended up with less bytes than normal, lengthen
byte[] tmp = new byte[getOutputBlockSize()];
System.arraycopy(output, 0, tmp, tmp.length - output.length, output.length);
return tmp;
}
}
else
{
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;
}
public CramerShoupCiphertext encryptBlock(BigInteger input)
{
CramerShoupCiphertext result = null;
if (!key.isPrivate() && this.forEncryption && key instanceof CramerShoupPublicKeyParameters)
{
CramerShoupPublicKeyParameters pk = (CramerShoupPublicKeyParameters)key;
BigInteger p = pk.getParameters().getP();
BigInteger g1 = pk.getParameters().getG1();
BigInteger g2 = pk.getParameters().getG2();
BigInteger h = pk.getH();
if (!isValidMessage(input, p))
{
return result;
}
BigInteger r = generateRandomElement(p, random);
BigInteger u1, u2, v, e, a;
u1 = g1.modPow(r, p);
u2 = g2.modPow(r, p);
e = h.modPow(r, p).multiply(input).mod(p);
Digest digest = pk.getParameters().getH();
byte[] u1Bytes = u1.toByteArray();
digest.update(u1Bytes, 0, u1Bytes.length);
byte[] u2Bytes = u2.toByteArray();
digest.update(u2Bytes, 0, u2Bytes.length);
byte[] eBytes = e.toByteArray();
digest.update(eBytes, 0, eBytes.length);
if (this.label != null)
{
byte[] lBytes = this.label;
digest.update(lBytes, 0, lBytes.length);
}
byte[] out = new byte[digest.getDigestSize()];
digest.doFinal(out, 0);
a = new BigInteger(1, out);
v = pk.getC().modPow(r, p).multiply(pk.getD().modPow(r.multiply(a), p)).mod(p);
result = new CramerShoupCiphertext(u1, u2, e, v);
}
return result;
}
public BigInteger decryptBlock(CramerShoupCiphertext input)
throws CramerShoupCiphertextException
{
BigInteger result = null;
if (key.isPrivate() && !this.forEncryption && key instanceof CramerShoupPrivateKeyParameters)
{
CramerShoupPrivateKeyParameters sk = (CramerShoupPrivateKeyParameters)key;
BigInteger p = sk.getParameters().getP();
Digest digest = sk.getParameters().getH();
byte[] u1Bytes = input.getU1().toByteArray();
digest.update(u1Bytes, 0, u1Bytes.length);
byte[] u2Bytes = input.getU2().toByteArray();
digest.update(u2Bytes, 0, u2Bytes.length);
byte[] eBytes = input.getE().toByteArray();
digest.update(eBytes, 0, eBytes.length);
if (this.label != null)
{
byte[] lBytes = this.label;
digest.update(lBytes, 0, lBytes.length);
}
byte[] out = new byte[digest.getDigestSize()];
digest.doFinal(out, 0);
BigInteger a = new BigInteger(1, out);
BigInteger v = input.u1.modPow(sk.getX1().add(sk.getY1().multiply(a)), p).
multiply(input.u2.modPow(sk.getX2().add(sk.getY2().multiply(a)), p)).mod(p);
// check correctness of ciphertext
if (input.v.equals(v))
{
result = input.e.multiply(input.u1.modPow(sk.getZ(), p).modInverse(p)).mod(p);
}
else
{
throw new CramerShoupCiphertextException("Sorry, that ciphertext is not correct");
}
}
return result;
}
private BigInteger generateRandomElement(BigInteger p, SecureRandom random)
{
return BigIntegers.createRandomInRange(ONE, p.subtract(ONE), random);
}
/**
* just checking whether the message m is actually less than the group order p
*/
private boolean isValidMessage(BigInteger m, BigInteger p)
{
return m.compareTo(p) < 0;
}
protected SecureRandom initSecureRandom(boolean needed, SecureRandom provided)
{
return !needed ? null : (provided != null) ? provided : CryptoServicesRegistrar.getSecureRandom();
}
/**
* CS exception for wrong cipher-texts
*/
public static class CramerShoupCiphertextException
extends Exception
{
private static final long serialVersionUID = -6360977166495345076L;
public CramerShoupCiphertextException(String msg)
{
super(msg);
}
}
}