org.ethereum.crypto.EthereumIESEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.ethereum.crypto.EthereumIESEngine.java

Source

/*
 * This file is part of RskJ
 * Copyright (C) 2017 RSK Labs Ltd.
 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.ethereum.crypto;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;

import org.bouncycastle.crypto.*;
import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.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;
    CipherParameters 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;
        byte[] k = null;
        byte[] k1 = null;
        byte[] 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[] inEnc, int inOff, int inLen, byte[] macData)
            throws InvalidCipherTextException {
        byte[] m = null;
        byte[] k = null;
        byte[] k1 = null;
        byte[] 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) (inEnc[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(inEnc, 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(inEnc, 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(inEnc, 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);
    }
}