| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE |
| * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file |
| * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
| * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations under the License. |
| */ |
| package org.apache.tuweni.rlpx; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.math.BigInteger; |
| |
| import org.bouncycastle.crypto.*; |
| import org.bouncycastle.crypto.digests.SHA256Digest; |
| 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 ciphers for doing basic message exchanges on top of key |
| * agreement ciphers. Follows the description given in IEEE Std 1363a. |
| */ |
| public class EthereumIESEncryptionEngine { |
| BasicAgreement agree; |
| DerivationFunction kdf; |
| Mac mac; |
| BufferedBlockCipher cipher; |
| byte[] macBuf; |
| // Ethereum addition: commonMac added when performing the MAC encryption. |
| byte[] commonMac; |
| |
| 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 commonMac the common MAC bytes to append to the mac |
| */ |
| public EthereumIESEncryptionEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac) { |
| this.agree = agree; |
| this.kdf = kdf; |
| this.mac = mac; |
| this.macBuf = new byte[mac.getMacSize()]; |
| this.commonMac = commonMac; |
| this.cipher = null; |
| } |
| |
| |
| /** |
| * Set up for use in conjunction with a block cipher to handle the message. It is <b>strongly</b> recommended that the |
| * cipher is not in ECB mode. |
| * |
| * @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 commonMac the common MAC bytes to append to the mac |
| * @param cipher the cipher to used for encrypting the message |
| */ |
| public EthereumIESEncryptionEngine( |
| BasicAgreement agree, |
| DerivationFunction kdf, |
| Mac mac, |
| byte[] commonMac, |
| BufferedBlockCipher cipher) { |
| this.agree = agree; |
| this.kdf = kdf; |
| this.mac = mac; |
| this.macBuf = new byte[mac.getMacSize()]; |
| this.commonMac = commonMac; |
| 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 decryptor. |
| * |
| * @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 = null; |
| if (V.length != 0) { |
| L2 = getLengthTag(P2); |
| } |
| |
| |
| // Apply the MAC. |
| byte[] T = new byte[mac.getMacSize()]; |
| // Ethereum change: |
| // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes. |
| // Old code: mac.init(new KeyParameter(K2)); |
| Digest hash = new SHA256Digest(); |
| byte[] K2hash = new byte[hash.getDigestSize()]; |
| hash.reset(); |
| hash.update(K2, 0, K2.length); |
| hash.doFinal(K2hash, 0); |
| |
| mac.init(new KeyParameter(K2hash)); |
| // we also update the mac with the IV: |
| mac.update(IV, 0, IV.length); |
| // end of Ethereum change. |
| 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.update(commonMac, 0, commonMac.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, K, K1, K2; |
| int len = 0; |
| |
| // Ensure that the length of the input is greater than the MAC in bytes |
| if (inLen < V.length + mac.getMacSize()) { |
| throw new InvalidCipherTextException("Length of input must be greater than the MAC and V combined"); |
| } |
| |
| // note order is important: set up keys, do simple encryptions, check mac, do final encryption. |
| 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); |
| } |
| |
| // process the message |
| M = new byte[K1.length]; |
| |
| for (int i = 0; i != K1.length; i++) { |
| M[i] = (byte) (in_enc[inOff + V.length + i] ^ K1[i]); |
| } |
| } 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); |
| |
| CipherParameters cp = new KeyParameter(K1); |
| |
| // If IV provide use it to initialize the cipher |
| if (IV != null) { |
| cp = new ParametersWithIV(cp, IV); |
| } |
| |
| cipher.init(false, cp); |
| |
| M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())]; |
| |
| // do initial processing |
| len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0); |
| } |
| |
| // Convert the length of the encoding vector into a byte array. |
| byte[] P2 = param.getEncodingV(); |
| byte[] L2 = null; |
| if (V.length != 0) { |
| L2 = getLengthTag(P2); |
| } |
| |
| // Verify the MAC. |
| int end = inOff + inLen; |
| byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end); |
| |
| byte[] T2 = new byte[T1.length]; |
| // Ethereum change: |
| // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes. |
| // Old code: mac.init(new KeyParameter(K2)); |
| Digest hash = new SHA256Digest(); |
| byte[] K2hash = new byte[hash.getDigestSize()]; |
| hash.reset(); |
| hash.update(K2, 0, K2.length); |
| hash.doFinal(K2hash, 0); |
| mac.init(new KeyParameter(K2hash)); |
| // we also update the mac with the IV: |
| mac.update(IV, 0, IV.length); |
| // end of Ethereum change. |
| |
| 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.update(commonMac, 0, commonMac.length); |
| mac.doFinal(T2, 0); |
| |
| if (!Arrays.constantTimeAreEqual(T1, T2)) { |
| throw new InvalidCipherTextException("invalid MAC"); |
| } |
| |
| if (cipher == null) { |
| return M; |
| } else { |
| len += cipher.doFinal(M, len); |
| |
| 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); |
| } catch (IllegalArgumentException 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. |
| if (V.length != 0) { |
| byte[] VZ = Arrays.concatenate(V, Z); |
| Arrays.fill(Z, (byte) 0); |
| Z = VZ; |
| } |
| |
| try { |
| // Initialise the KDF. |
| KDFParameters kdfParam = new KDFParameters(Z, param.getDerivationV()); |
| kdf.init(kdfParam); |
| |
| return forEncryption ? encryptBlock(in, inOff, inLen) : decryptBlock(in, inOff, inLen); |
| } finally { |
| Arrays.fill(Z, (byte) 0); |
| } |
| } |
| |
| // as described in Shroup's paper and P1363a |
| protected byte[] getLengthTag(byte[] p2) { |
| byte[] L2 = new byte[8]; |
| if (p2 != null) { |
| Pack.longToBigEndian(p2.length * 8L, L2, 0); |
| } |
| return L2; |
| } |
| |
| /** |
| * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 <br> |
| * This implementation is based on ISO 18033/P1363a. |
| * <p> |
| * This class has been adapted from the <tt>BaseKDFBytesGenerator</tt> implementation of Bouncy Castle. Only one |
| * change is present specifically for Ethereum. |
| */ |
| static class ECIESHandshakeKDFFunction 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 ECIESHandshakeKDFFunction(int counterStart, Digest digest) { |
| this.counterStart = counterStart; |
| this.digest = digest; |
| } |
| |
| @Override |
| 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 generator"); |
| } |
| } |
| |
| /** |
| * return the underlying digest. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException, IllegalArgumentException { |
| if ((out.length - len) < outOff) { |
| throw new OutputLengthException("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++) { |
| // only change for Ethereum: invert those 2 lines. |
| digest.update(C, 0, C.length); |
| digest.update(shared, 0, shared.length); |
| // End of change for Ethereum. |
| |
| 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; |
| } |
| } |
| } |