package org.bouncycastle.crypto.kems;
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DerivationFunction;
import org.bouncycastle.crypto.KeyEncapsulation;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.KDFParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.BigIntegers;
/**
* The ECIES Key Encapsulation Mechanism (ECIES-KEM) from ISO 18033-2.
*/
public class ECIESKeyEncapsulation
implements KeyEncapsulation
{
private static final BigInteger ONE = BigInteger.valueOf(1);
private DerivationFunction kdf;
private SecureRandom rnd;
private ECKeyParameters key;
private boolean CofactorMode;
private boolean OldCofactorMode;
private boolean SingleHashMode;
/**
* Set up the ECIES-KEM.
*
* @param kdf the key derivation function to be used.
* @param rnd the random source for the session key.
*/
public ECIESKeyEncapsulation(
DerivationFunction kdf,
SecureRandom rnd)
{
this.kdf = kdf;
this.rnd = rnd;
this.CofactorMode = false;
this.OldCofactorMode = false;
this.SingleHashMode = false;
}
/**
* Set up the ECIES-KEM.
*
* @param kdf the key derivation function to be used.
* @param rnd the random source for the session key.
* @param cofactorMode true to use the new cofactor ECDH.
* @param oldCofactorMode true to use the old cofactor ECDH.
* @param singleHashMode true to use single hash mode.
*/
public ECIESKeyEncapsulation(
DerivationFunction kdf,
SecureRandom rnd,
boolean cofactorMode,
boolean oldCofactorMode,
boolean singleHashMode)
{
this.kdf = kdf;
this.rnd = rnd;
// If both cofactorMode and oldCofactorMode are set to true
// then the implementation will use the new cofactor ECDH
this.CofactorMode = cofactorMode;
this.OldCofactorMode = oldCofactorMode;
this.SingleHashMode = singleHashMode;
}
/**
* Initialise the ECIES-KEM.
*
* @param key the recipient's public (for encryption) or private (for decryption) key.
*/
public void init(CipherParameters key)
throws IllegalArgumentException
{
if (!(key instanceof ECKeyParameters))
{
throw new IllegalArgumentException("EC key required");
}
else
{
this.key = (ECKeyParameters)key;
}
}
/**
* Generate and encapsulate a random session key.
*
* @param out the output buffer for the encapsulated key.
* @param outOff the offset for the output buffer.
* @param keyLen the length of the session key.
* @return the random session key.
*/
public CipherParameters encrypt(byte[] out, int outOff, int keyLen)
throws IllegalArgumentException
{
if (!(key instanceof ECPublicKeyParameters))
{
throw new IllegalArgumentException("Public key required for encryption");
}
BigInteger n = key.getParameters().getN();
BigInteger h = key.getParameters().getH();
// Generate the ephemeral key pair
BigInteger r = BigIntegers.createRandomInRange(ONE, n, rnd);
ECPoint gTilde = key.getParameters().getG().multiply(r);
// Encode the ephemeral public key
byte[] C = gTilde.getEncoded();
System.arraycopy(C, 0, out, outOff, C.length);
// Compute the static-ephemeral key agreement
BigInteger rPrime;
if (CofactorMode)
{
rPrime = r.multiply(h).mod(n);
}
else
{
rPrime = r;
}
ECPoint hTilde = ((ECPublicKeyParameters)key).getQ().multiply(rPrime);
// Encode the shared secret value
int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8;
byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger());
// Initialise the KDF
byte[] kdfInput;
if (SingleHashMode)
{
kdfInput = new byte[C.length + PEH.length];
System.arraycopy(C, 0, kdfInput, 0, C.length);
System.arraycopy(PEH, 0, kdfInput, C.length, PEH.length);
}
else
{
kdfInput = PEH;
}
kdf.init(new KDFParameters(kdfInput, null));
// Generate the secret key
byte[] K = new byte[keyLen];
kdf.generateBytes(K, 0, K.length);
// Return the ciphertext
return new KeyParameter(K);
}
/**
* Generate and encapsulate a random session key.
*
* @param out the output buffer for the encapsulated key.
* @param keyLen the length of the session key.
* @return the random session key.
*/
public CipherParameters encrypt(byte[] out, int keyLen)
{
return encrypt(out, 0, keyLen);
}
/**
* Decrypt an encapsulated session key.
*
* @param in the input buffer for the encapsulated key.
* @param inOff the offset for the input buffer.
* @param inLen the length of the encapsulated key.
* @param keyLen the length of the session key.
* @return the session key.
*/
public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen)
throws IllegalArgumentException
{
if (!(key instanceof ECPrivateKeyParameters))
{
throw new IllegalArgumentException("Private key required for encryption");
}
BigInteger n = key.getParameters().getN();
BigInteger h = key.getParameters().getH();
// Decode the ephemeral public key
byte[] C = new byte[inLen];
System.arraycopy(in, inOff, C, 0, inLen);
ECPoint gTilde = key.getParameters().getCurve().decodePoint(C);
// Compute the static-ephemeral key agreement
ECPoint gHat;
if ((CofactorMode) || (OldCofactorMode))
{
gHat = gTilde.multiply(h);
}
else
{
gHat = gTilde;
}
BigInteger xHat;
if (CofactorMode)
{
xHat = ((ECPrivateKeyParameters)key).getD().multiply(h.modInverse(n)).mod(n);
}
else
{
xHat = ((ECPrivateKeyParameters)key).getD();
}
ECPoint hTilde = gHat.multiply(xHat);
// Encode the shared secret value
int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8;
byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger());
// Initialise the KDF
byte[] kdfInput;
if (SingleHashMode)
{
kdfInput = new byte[C.length + PEH.length];
System.arraycopy(C, 0, kdfInput, 0, C.length);
System.arraycopy(PEH, 0, kdfInput, C.length, PEH.length);
}
else
{
kdfInput = PEH;
}
kdf.init(new KDFParameters(kdfInput, null));
// Generate the secret key
byte[] K = new byte[keyLen];
kdf.generateBytes(K, 0, K.length);
return new KeyParameter(K);
}
/**
* Decrypt an encapsulated session key.
*
* @param in the input buffer for the encapsulated key.
* @param keyLen the length of the session key.
* @return the session key.
*/
public CipherParameters decrypt(byte[] in, int keyLen)
{
return decrypt(in, 0, in.length, keyLen);
}
}