ECIES matching go-ethereum
This commit is contained in:
parent
134589a3ca
commit
3f79565b69
|
@ -58,6 +58,7 @@ ext {
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "io.netty:netty-all:4.0.23.Final"
|
compile "io.netty:netty-all:4.0.23.Final"
|
||||||
compile "com.madgag.spongycastle:core:${scastleVersion}" // for SHA3 and SECP256K1
|
compile "com.madgag.spongycastle:core:${scastleVersion}" // for SHA3 and SECP256K1
|
||||||
|
compile "com.madgag.spongycastle:prov:${scastleVersion}" // for SHA3 and SECP256K1
|
||||||
compile "org.iq80.leveldb:leveldb:${leveldbVersion}"
|
compile "org.iq80.leveldb:leveldb:${leveldbVersion}"
|
||||||
compile "com.cedarsoftware:java-util:1.8.0" // for deep equals
|
compile "com.cedarsoftware:java-util:1.8.0" // for deep equals
|
||||||
compile "org.antlr:antlr4-runtime:4.5" // for serpent compilation
|
compile "org.antlr:antlr4-runtime:4.5" // for serpent compilation
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
package org.ethereum;
|
||||||
|
|
||||||
|
import org.spongycastle.crypto.DataLengthException;
|
||||||
|
import org.spongycastle.crypto.DerivationParameters;
|
||||||
|
import org.spongycastle.crypto.Digest;
|
||||||
|
import org.spongycastle.crypto.DigestDerivationFunction;
|
||||||
|
import org.spongycastle.crypto.params.ISO18033KDFParameters;
|
||||||
|
import org.spongycastle.crypto.params.KDFParameters;
|
||||||
|
import org.spongycastle.util.Pack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic KDF generator for derived keys and ivs as defined by NIST SP 800-56A.
|
||||||
|
*/
|
||||||
|
public class ConcatKDFBytesGenerator
|
||||||
|
implements DigestDerivationFunction
|
||||||
|
{
|
||||||
|
private int counterStart;
|
||||||
|
private Digest digest;
|
||||||
|
private byte[] shared;
|
||||||
|
private byte[] iv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a KDF Parameters generator.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param counterStart
|
||||||
|
* value of counter.
|
||||||
|
* @param digest
|
||||||
|
* the digest to be used as the source of derived keys.
|
||||||
|
*/
|
||||||
|
protected ConcatKDFBytesGenerator(int counterStart, Digest digest)
|
||||||
|
{
|
||||||
|
this.counterStart = counterStart;
|
||||||
|
this.digest = digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcatKDFBytesGenerator(Digest digest) {
|
||||||
|
this(1, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(DerivationParameters param)
|
||||||
|
{
|
||||||
|
if (param instanceof KDFParameters)
|
||||||
|
{
|
||||||
|
KDFParameters p = (KDFParameters)param;
|
||||||
|
|
||||||
|
shared = p.getSharedSecret();
|
||||||
|
iv = p.getIV();
|
||||||
|
}
|
||||||
|
else if (param instanceof ISO18033KDFParameters)
|
||||||
|
{
|
||||||
|
ISO18033KDFParameters p = (ISO18033KDFParameters)param;
|
||||||
|
|
||||||
|
shared = p.getSeed();
|
||||||
|
iv = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("KDF parameters required for KDF2Generator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the underlying digest.
|
||||||
|
*/
|
||||||
|
public Digest getDigest()
|
||||||
|
{
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fill len bytes of the output buffer with bytes generated from the
|
||||||
|
* derivation function.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the size of the request will cause an overflow.
|
||||||
|
* @throws DataLengthException
|
||||||
|
* if the out buffer is too small.
|
||||||
|
*/
|
||||||
|
public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException,
|
||||||
|
IllegalArgumentException
|
||||||
|
{
|
||||||
|
if ((out.length - len) < outOff)
|
||||||
|
{
|
||||||
|
throw new DataLengthException("output buffer too small");
|
||||||
|
}
|
||||||
|
|
||||||
|
long oBytes = len;
|
||||||
|
int outLen = digest.getDigestSize();
|
||||||
|
|
||||||
|
//
|
||||||
|
// this is at odds with the standard implementation, the
|
||||||
|
// maximum value should be hBits * (2^32 - 1) where hBits
|
||||||
|
// is the digest output size in bits. We can't have an
|
||||||
|
// array with a long index at the moment...
|
||||||
|
//
|
||||||
|
if (oBytes > ((2L << 32) - 1))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Output length too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
int cThreshold = (int)((oBytes + outLen - 1) / outLen);
|
||||||
|
|
||||||
|
byte[] dig = new byte[digest.getDigestSize()];
|
||||||
|
|
||||||
|
byte[] C = new byte[4];
|
||||||
|
Pack.intToBigEndian(counterStart, C, 0);
|
||||||
|
|
||||||
|
int counterBase = counterStart & ~0xFF;
|
||||||
|
|
||||||
|
for (int i = 0; i < cThreshold; i++)
|
||||||
|
{
|
||||||
|
digest.update(C, 0, C.length);
|
||||||
|
digest.update(shared, 0, shared.length);
|
||||||
|
|
||||||
|
if (iv != null)
|
||||||
|
{
|
||||||
|
digest.update(iv, 0, iv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
digest.doFinal(dig, 0);
|
||||||
|
|
||||||
|
if (len > outLen)
|
||||||
|
{
|
||||||
|
System.arraycopy(dig, 0, out, outOff, outLen);
|
||||||
|
outOff += outLen;
|
||||||
|
len -= outLen;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.arraycopy(dig, 0, out, outOff, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++C[3] == 0)
|
||||||
|
{
|
||||||
|
counterBase += 0x100;
|
||||||
|
Pack.intToBigEndian(counterBase, C, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
digest.reset();
|
||||||
|
|
||||||
|
return (int)oBytes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,420 @@
|
||||||
|
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.AsymmetricKeyParameter;
|
||||||
|
import org.spongycastle.crypto.params.IESParameters;
|
||||||
|
import org.spongycastle.crypto.params.IESWithCipherParameters;
|
||||||
|
import org.spongycastle.crypto.params.KDFParameters;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @param 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
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();
|
||||||
|
byte[] L2 = new byte[4];
|
||||||
|
if (V.length != 0 && P2 != null)
|
||||||
|
{
|
||||||
|
Pack.intToBigEndian(P2.length * 8, L2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Apply the MAC.
|
||||||
|
byte[] T = new byte[mac.getMacSize()];
|
||||||
|
|
||||||
|
mac.init(new KeyParameter(K2));
|
||||||
|
mac.update(C, 0, C.length);
|
||||||
|
if (P2 != null)
|
||||||
|
{
|
||||||
|
mac.update(P2, 0, P2.length);
|
||||||
|
}
|
||||||
|
if (V.length != 0)
|
||||||
|
{
|
||||||
|
mac.update(L2, 0, L2.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)
|
||||||
|
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();
|
||||||
|
byte[] L2 = new byte[4];
|
||||||
|
if (V.length != 0 && P2 != null)
|
||||||
|
{
|
||||||
|
Pack.intToBigEndian(P2.length * 8, L2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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 = new byte[hash.getDigestSize()];
|
||||||
|
hash.reset();
|
||||||
|
hash.update(K2, 0, K2.length);
|
||||||
|
hash.doFinal(K2a, 0);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
mac.update(L2, 0, L2.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
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
KDFParameters kdfParam = new KDFParameters(VZ, param.getDerivationV());
|
||||||
|
kdf.init(kdfParam);
|
||||||
|
|
||||||
|
return forEncryption
|
||||||
|
? encryptBlock(in, inOff, inLen)
|
||||||
|
: decryptBlock(in, inOff, inLen);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package test.ethereum.crypto;
|
||||||
|
|
||||||
|
import org.ethereum.ConcatKDFBytesGenerator;
|
||||||
|
import org.ethereum.crypto.ECKey;
|
||||||
|
import org.ethereum.crypto.EthereumIESEngine;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.asn1.sec.SECNamedCurves;
|
||||||
|
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.spongycastle.crypto.BufferedBlockCipher;
|
||||||
|
import org.spongycastle.crypto.KeyGenerationParameters;
|
||||||
|
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
||||||
|
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||||
|
import org.spongycastle.crypto.engines.AESFastEngine;
|
||||||
|
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.spongycastle.crypto.generators.KDF2BytesGenerator;
|
||||||
|
import org.spongycastle.crypto.macs.HMac;
|
||||||
|
import org.spongycastle.crypto.modes.SICBlockCipher;
|
||||||
|
import org.spongycastle.crypto.params.*;
|
||||||
|
import org.spongycastle.math.ec.ECPoint;
|
||||||
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class ECIESTest {
|
||||||
|
public static final int MAC_KEY_SIZE = 128;
|
||||||
|
static Logger log = LoggerFactory.getLogger("test");
|
||||||
|
private static ECDomainParameters curve;
|
||||||
|
private static final String CIPHERTEXT1 = "042a851331790adacf6e64fcb19d0872fcdf1285a899a12cdc897da941816b0ea6485402aaf6c2e0a5d98ae3af1b05c68b307d1e0eb7a426a46f1617ba5b94f90b606eee3b5e9d2b527a9ee52cfa377bcd118b9390ed27ffe7d48e8155004375cae209012c3e057bb13a478a64a201d79ad4ae83";
|
||||||
|
private static final X9ECParameters IES_CURVE_PARAM = SECNamedCurves.getByName("secp256r1");
|
||||||
|
private static final BigInteger PRIVATE_KEY1 = new BigInteger("51134539186617376248226283012294527978458758538121566045626095875284492680246");
|
||||||
|
|
||||||
|
private static ECPoint pub(BigInteger d) throws Exception {
|
||||||
|
return curve.getG().multiply(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeAll() {
|
||||||
|
if (Security.getProvider("SC") == null)
|
||||||
|
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
|
||||||
|
curve = new ECDomainParameters(IES_CURVE_PARAM.getCurve(), IES_CURVE_PARAM.getG(), IES_CURVE_PARAM.getN(), IES_CURVE_PARAM.getH());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKDF() {
|
||||||
|
ConcatKDFBytesGenerator kdf = new ConcatKDFBytesGenerator(new SHA256Digest());
|
||||||
|
kdf.init(new KDFParameters(new String("Hello").getBytes(), new byte[0]));
|
||||||
|
byte[] bytes = new byte[2];
|
||||||
|
kdf.generateBytes(bytes, 0, bytes.length);
|
||||||
|
assertArrayEquals(new byte[]{-66, -89}, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecryptTestVector() throws Throwable {
|
||||||
|
ECPoint pub1 = pub(PRIVATE_KEY1);
|
||||||
|
byte[] cipher = Hex.decode(CIPHERTEXT1);
|
||||||
|
ByteArrayInputStream is = new ByteArrayInputStream(cipher);
|
||||||
|
byte[] ephemBytes = new byte[2*((curve.getCurve().getFieldSize()+7)/8) + 1];
|
||||||
|
is.read(ephemBytes);
|
||||||
|
ECPoint ephem = curve.getCurve().decodePoint(ephemBytes);
|
||||||
|
byte[] IV = new byte[MAC_KEY_SIZE/8];
|
||||||
|
is.read(IV);
|
||||||
|
byte[] cipherBody = new byte[is.available()];
|
||||||
|
is.read(cipherBody);
|
||||||
|
|
||||||
|
byte[] plaintext = decrypt(ephem, PRIVATE_KEY1, IV, cipherBody);
|
||||||
|
assertArrayEquals(new byte[]{1,1,1}, plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] decrypt(ECPoint ephem, BigInteger prv, byte[] IV, byte[] cipher) throws Throwable {
|
||||||
|
AESFastEngine aesFastEngine = new AESFastEngine();
|
||||||
|
|
||||||
|
EthereumIESEngine iesEngine = new EthereumIESEngine(
|
||||||
|
new ECDHBasicAgreement(),
|
||||||
|
new ConcatKDFBytesGenerator(new SHA256Digest()),
|
||||||
|
new HMac(new SHA256Digest()),
|
||||||
|
new SHA256Digest(),
|
||||||
|
new BufferedBlockCipher(new SICBlockCipher(aesFastEngine)));
|
||||||
|
|
||||||
|
|
||||||
|
byte[] d = new byte[] {};
|
||||||
|
byte[] e = new byte[] {};
|
||||||
|
|
||||||
|
IESParameters p = new IESWithCipherParameters(d, e, MAC_KEY_SIZE, MAC_KEY_SIZE);
|
||||||
|
ParametersWithIV parametersWithIV =
|
||||||
|
new ParametersWithIV(p, IV);
|
||||||
|
|
||||||
|
iesEngine.init(false, new ECPrivateKeyParameters(prv, curve), new ECPublicKeyParameters(ephem, curve), parametersWithIV);
|
||||||
|
|
||||||
|
byte[] message = iesEngine.processBlock(cipher, 0, cipher.length);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encrypt(byte[] plaintext) throws Throwable {
|
||||||
|
AESFastEngine aesFastEngine = new AESFastEngine();
|
||||||
|
|
||||||
|
EthereumIESEngine iesEngine = new EthereumIESEngine(
|
||||||
|
new ECDHBasicAgreement(),
|
||||||
|
new KDF2BytesGenerator(new SHA256Digest()),
|
||||||
|
new HMac(new SHA256Digest()),
|
||||||
|
new SHA256Digest(),
|
||||||
|
new BufferedBlockCipher(new SICBlockCipher(aesFastEngine)));
|
||||||
|
|
||||||
|
|
||||||
|
byte[] d = new byte[] {};
|
||||||
|
byte[] e = new byte[] {};
|
||||||
|
|
||||||
|
IESParameters p = new IESWithCipherParameters(d, e, 256, MAC_KEY_SIZE);
|
||||||
|
ParametersWithIV parametersWithIV = new ParametersWithIV(p, new byte[256/8]);
|
||||||
|
|
||||||
|
ECKeyPairGenerator eGen = new ECKeyPairGenerator();
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
KeyGenerationParameters gParam = new ECKeyGenerationParameters(ECKey.CURVE, random);
|
||||||
|
|
||||||
|
eGen.init(gParam);
|
||||||
|
|
||||||
|
|
||||||
|
AsymmetricCipherKeyPair p1 = eGen.generateKeyPair();
|
||||||
|
AsymmetricCipherKeyPair p2 = eGen.generateKeyPair();
|
||||||
|
|
||||||
|
|
||||||
|
ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(ECKey.CURVE, random);
|
||||||
|
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||||
|
generator.init(keygenParams);
|
||||||
|
|
||||||
|
ECKeyPairGenerator gen = new ECKeyPairGenerator();
|
||||||
|
gen.init(new ECKeyGenerationParameters(ECKey.CURVE, random));
|
||||||
|
|
||||||
|
iesEngine.init(true, p1.getPrivate(), p2.getPublic(), parametersWithIV);
|
||||||
|
|
||||||
|
byte[] cipher = iesEngine.processBlock(plaintext, 0, plaintext.length);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue