org.ethereum.crypto.EthereumIESEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ethereumj-core Show documentation
Show all versions of ethereumj-core Show documentation
Java implementation of the Ethereum protocol adapted to use for Hedera Smart Contract Service
The newest version!
/*
* Copyright (c) [2016] [ ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see .
*/
package org.ethereum.crypto;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import org.spongycastle.crypto.*;
import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
import org.spongycastle.crypto.params.*;
import org.spongycastle.util.Arrays;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.Pack;
/**
* Support class for constructing integrated encryption cipher
* for doing basic message exchanges on top of key agreement ciphers.
* Follows the description given in IEEE Std 1363a with a couple of changes
* specific to Ethereum:
* - Hash the MAC key before use
* - Include the encryption IV in the MAC computation
*/
public class EthereumIESEngine
{
private final Digest hash;
BasicAgreement agree;
DerivationFunction kdf;
Mac mac;
BufferedBlockCipher cipher;
byte[] macBuf;
boolean forEncryption;
CipherParameters privParam, pubParam;
IESParameters param;
byte[] V;
private EphemeralKeyPairGenerator keyPairGenerator;
private KeyParser keyParser;
private byte[] IV;
boolean hashK2 = true;
/**
* set up for use with stream mode, where the key derivation function
* is used to provide a stream of bytes to xor with the message.
* @param agree the key agreement used as the basis for the encryption
* @param kdf the key derivation function used for byte generation
* @param mac the message authentication code generator for the message
* @param hash hash ing function
* @param cipher the actual cipher
*/
public EthereumIESEngine(
BasicAgreement agree,
DerivationFunction kdf,
Mac mac, Digest hash, BufferedBlockCipher cipher)
{
this.agree = agree;
this.kdf = kdf;
this.mac = mac;
this.hash = hash;
this.macBuf = new byte[mac.getMacSize()];
this.cipher = cipher;
}
public void setHashMacKey(boolean hashK2) {
this.hashK2 = hashK2;
}
/**
* Initialise the encryptor.
*
* @param forEncryption whether or not this is encryption/decryption.
* @param privParam our private key parameters
* @param pubParam the recipient's/sender's public key parameters
* @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
*/
public void init(
boolean forEncryption,
CipherParameters privParam,
CipherParameters pubParam,
CipherParameters params)
{
this.forEncryption = forEncryption;
this.privParam = privParam;
this.pubParam = pubParam;
this.V = new byte[0];
extractParams(params);
}
/**
* Initialise the encryptor.
*
* @param publicKey the recipient's/sender's public key parameters
* @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
* @param ephemeralKeyPairGenerator the ephemeral key pair generator to use.
*/
public void init(AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator)
{
this.forEncryption = true;
this.pubParam = publicKey;
this.keyPairGenerator = ephemeralKeyPairGenerator;
extractParams(params);
}
/**
* Initialise the encryptor.
*
* @param privateKey the recipient's private key.
* @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
* @param publicKeyParser the parser for reading the ephemeral public key.
*/
public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)
{
this.forEncryption = false;
this.privParam = privateKey;
this.keyParser = publicKeyParser;
extractParams(params);
}
private void extractParams(CipherParameters params)
{
if (params instanceof ParametersWithIV)
{
this.IV = ((ParametersWithIV)params).getIV();
this.param = (IESParameters)((ParametersWithIV)params).getParameters();
}
else
{
this.IV = null;
this.param = (IESParameters)params;
}
}
public BufferedBlockCipher getCipher()
{
return cipher;
}
public Mac getMac()
{
return mac;
}
private byte[] encryptBlock(
byte[] in,
int inOff,
int inLen,
byte[] macData)
throws InvalidCipherTextException
{
byte[] C = null, K = null, K1 = null, K2 = null;
int len;
if (cipher == null)
{
// Streaming mode.
K1 = new byte[inLen];
K2 = new byte[param.getMacKeySize() / 8];
K = new byte[K1.length + K2.length];
kdf.generateBytes(K, 0, K.length);
// if (V.length != 0)
// {
// System.arraycopy(K, 0, K2, 0, K2.length);
// System.arraycopy(K, K2.length, K1, 0, K1.length);
// }
// else
{
System.arraycopy(K, 0, K1, 0, K1.length);
System.arraycopy(K, inLen, K2, 0, K2.length);
}
C = new byte[inLen];
for (int i = 0; i != inLen; i++)
{
C[i] = (byte)(in[inOff + i] ^ K1[i]);
}
len = inLen;
}
else
{
// Block cipher mode.
K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
K2 = new byte[param.getMacKeySize() / 8];
K = new byte[K1.length + K2.length];
kdf.generateBytes(K, 0, K.length);
System.arraycopy(K, 0, K1, 0, K1.length);
System.arraycopy(K, K1.length, K2, 0, K2.length);
// If iv provided use it to initialise the cipher
if (IV != null)
{
cipher.init(true, new ParametersWithIV(new KeyParameter(K1), IV));
}
else
{
cipher.init(true, new KeyParameter(K1));
}
C = new byte[cipher.getOutputSize(inLen)];
len = cipher.processBytes(in, inOff, inLen, C, 0);
len += cipher.doFinal(C, len);
}
// Convert the length of the encoding vector into a byte array.
byte[] P2 = param.getEncodingV();
// Apply the MAC.
byte[] T = new byte[mac.getMacSize()];
byte[] K2a;
if (hashK2) {
K2a = new byte[hash.getDigestSize()];
hash.reset();
hash.update(K2, 0, K2.length);
hash.doFinal(K2a, 0);
} else {
K2a = K2;
}
mac.init(new KeyParameter(K2a));
mac.update(IV, 0, IV.length);
mac.update(C, 0, C.length);
if (P2 != null)
{
mac.update(P2, 0, P2.length);
}
if (V.length != 0 && P2 != null) {
byte[] L2 = new byte[4];
Pack.intToBigEndian(P2.length * 8, L2, 0);
mac.update(L2, 0, L2.length);
}
if (macData != null) {
mac.update(macData, 0, macData.length);
}
mac.doFinal(T, 0);
// Output the triple (V,C,T).
byte[] Output = new byte[V.length + len + T.length];
System.arraycopy(V, 0, Output, 0, V.length);
System.arraycopy(C, 0, Output, V.length, len);
System.arraycopy(T, 0, Output, V.length + len, T.length);
return Output;
}
private byte[] decryptBlock(
byte[] in_enc,
int inOff,
int inLen,
byte[] macData)
throws InvalidCipherTextException
{
byte[] M = null, K = null, K1 = null, K2 = null;
int len;
// Ensure that the length of the input is greater than the MAC in bytes
if (inLen <= (param.getMacKeySize() / 8))
{
throw new InvalidCipherTextException("Length of input must be greater than the MAC");
}
if (cipher == null)
{
// Streaming mode.
K1 = new byte[inLen - V.length - mac.getMacSize()];
K2 = new byte[param.getMacKeySize() / 8];
K = new byte[K1.length + K2.length];
kdf.generateBytes(K, 0, K.length);
// if (V.length != 0)
// {
// System.arraycopy(K, 0, K2, 0, K2.length);
// System.arraycopy(K, K2.length, K1, 0, K1.length);
// }
// else
{
System.arraycopy(K, 0, K1, 0, K1.length);
System.arraycopy(K, K1.length, K2, 0, K2.length);
}
M = new byte[K1.length];
for (int i = 0; i != K1.length; i++)
{
M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]);
}
len = K1.length;
}
else
{
// Block cipher mode.
K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
K2 = new byte[param.getMacKeySize() / 8];
K = new byte[K1.length + K2.length];
kdf.generateBytes(K, 0, K.length);
System.arraycopy(K, 0, K1, 0, K1.length);
System.arraycopy(K, K1.length, K2, 0, K2.length);
// If IV provide use it to initialize the cipher
if (IV != null)
{
cipher.init(false, new ParametersWithIV(new KeyParameter(K1), IV));
}
else
{
cipher.init(false, new KeyParameter(K1));
}
M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())];
len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0);
len += cipher.doFinal(M, len);
}
// Convert the length of the encoding vector into a byte array.
byte[] P2 = param.getEncodingV();
// Verify the MAC.
int end = inOff + inLen;
byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end);
byte[] T2 = new byte[T1.length];
byte[] K2a;
if (hashK2) {
K2a = new byte[hash.getDigestSize()];
hash.reset();
hash.update(K2, 0, K2.length);
hash.doFinal(K2a, 0);
} else {
K2a = K2;
}
mac.init(new KeyParameter(K2a));
mac.update(IV, 0, IV.length);
mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length);
if (P2 != null)
{
mac.update(P2, 0, P2.length);
}
if (V.length != 0 && P2 != null) {
byte[] L2 = new byte[4];
Pack.intToBigEndian(P2.length * 8, L2, 0);
mac.update(L2, 0, L2.length);
}
if (macData != null) {
mac.update(macData, 0, macData.length);
}
mac.doFinal(T2, 0);
if (!Arrays.constantTimeAreEqual(T1, T2))
{
throw new InvalidCipherTextException("Invalid MAC.");
}
// Output the message.
return Arrays.copyOfRange(M, 0, len);
}
public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
return processBlock(in, inOff, inLen, null);
}
public byte[] processBlock(
byte[] in,
int inOff,
int inLen,
byte[] macData)
throws InvalidCipherTextException
{
if (forEncryption)
{
if (keyPairGenerator != null)
{
EphemeralKeyPair ephKeyPair = keyPairGenerator.generate();
this.privParam = ephKeyPair.getKeyPair().getPrivate();
this.V = ephKeyPair.getEncodedPublicKey();
}
}
else
{
if (keyParser != null)
{
ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen);
try
{
this.pubParam = keyParser.readKey(bIn);
}
catch (IOException e)
{
throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e);
}
int encLength = (inLen - bIn.available());
this.V = Arrays.copyOfRange(in, inOff, inOff + encLength);
}
}
// Compute the common value and convert to byte array.
agree.init(privParam);
BigInteger z = agree.calculateAgreement(pubParam);
byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z);
// Create input to KDF.
byte[] VZ;
// if (V.length != 0)
// {
// VZ = new byte[V.length + Z.length];
// System.arraycopy(V, 0, VZ, 0, V.length);
// System.arraycopy(Z, 0, VZ, V.length, Z.length);
// }
// else
{
VZ = Z;
}
// Initialise the KDF.
DerivationParameters kdfParam;
if (kdf instanceof MGF1BytesGeneratorExt) {
kdfParam = new MGFParameters(VZ);
} else {
kdfParam = new KDFParameters(VZ, param.getDerivationV());
}
kdf.init(kdfParam);
return forEncryption
? encryptBlock(in, inOff, inLen, macData)
: decryptBlock(in, inOff, inLen, macData);
}
}