1 package org.bouncycastle.crypto.engines;
2 
3 
4 import java.io.ByteArrayInputStream;
5 import java.io.IOException;
6 import java.math.BigInteger;
7 
8 import org.bouncycastle.crypto.BasicAgreement;
9 import org.bouncycastle.crypto.BufferedBlockCipher;
10 import org.bouncycastle.crypto.CipherParameters;
11 import org.bouncycastle.crypto.DataLengthException;
12 import org.bouncycastle.crypto.DerivationFunction;
13 import org.bouncycastle.crypto.DerivationParameters;
14 import org.bouncycastle.crypto.Digest;
15 import org.bouncycastle.crypto.DigestDerivationFunction;
16 import org.bouncycastle.crypto.EphemeralKeyPair;
17 import org.bouncycastle.crypto.InvalidCipherTextException;
18 import org.bouncycastle.crypto.KeyParser;
19 import org.bouncycastle.crypto.Mac;
20 import org.bouncycastle.crypto.OutputLengthException;
21 import org.bouncycastle.crypto.digests.SHA256Digest;
22 import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
23 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
24 import org.bouncycastle.crypto.params.IESParameters;
25 import org.bouncycastle.crypto.params.IESWithCipherParameters;
26 import org.bouncycastle.crypto.params.ISO18033KDFParameters;
27 import org.bouncycastle.crypto.params.KDFParameters;
28 import org.bouncycastle.crypto.params.KeyParameter;
29 import org.bouncycastle.crypto.params.ParametersWithIV;
30 import org.bouncycastle.util.Arrays;
31 import org.bouncycastle.util.BigIntegers;
32 import org.bouncycastle.util.Pack;
33 
34 /**
35  * Support class for constructing integrated encryption ciphers for doing basic message exchanges on top of key
36  * agreement ciphers. Follows the description given in IEEE Std 1363a.
37  * <p>
38  * Some tweaks added to IESEngine to conform to the Ethereum encryption approach.
39  */
40 public class EthereumIESEngine
41 {
42     BasicAgreement agree;
43     DerivationFunction kdf;
44     Mac mac;
45     BufferedBlockCipher cipher;
46     byte[] macBuf;
47     // Ethereum addition: commonMac added when performing the MAC encryption.
48     byte[] commonMac;
49 
50     boolean forEncryption;
51     CipherParameters privParam, pubParam;
52     IESParameters param;
53 
54     byte[] V;
55     private EphemeralKeyPairGenerator keyPairGenerator;
56     private KeyParser keyParser;
57     private byte[] IV;
58 
59     /**
60      * Set up for use with stream mode, where the key derivation function is used to provide a stream of bytes to xor with
61      * the message.
62      *
63      * @param agree     the key agreement used as the basis for the encryption
64      * @param kdf       the key derivation function used for byte generation
65      * @param mac       the message authentication code generator for the message
66      * @param commonMac the common MAC bytes to append to the mac
67      */
EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac)68     public EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac)
69     {
70         this.agree = agree;
71         this.kdf = kdf;
72         this.mac = mac;
73         this.macBuf = new byte[mac.getMacSize()];
74         this.commonMac = commonMac;
75         this.cipher = null;
76     }
77 
78     /**
79      * Set up for use in conjunction with a block cipher to handle the message. It is <b>strongly</b> recommended that the
80      * cipher is not in ECB mode.
81      *
82      * @param agree     the key agreement used as the basis for the encryption
83      * @param kdf       the key derivation function used for byte generation
84      * @param mac       the message authentication code generator for the message
85      * @param commonMac the common MAC bytes to append to the mac
86      * @param cipher    the cipher to used for encrypting the message
87      */
EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac, BufferedBlockCipher cipher)88     public EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac, BufferedBlockCipher cipher)
89     {
90         this.agree = agree;
91         this.kdf = kdf;
92         this.mac = mac;
93         this.macBuf = new byte[mac.getMacSize()];
94         this.commonMac = commonMac;
95         this.cipher = cipher;
96     }
97 
98     /**
99      * Initialise the encryptor.
100      *
101      * @param forEncryption whether or not this is encryption/decryption.
102      * @param privParam     our private key parameters
103      * @param pubParam      the recipient's/sender's public key parameters
104      * @param params        encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
105      */
init( boolean forEncryption, CipherParameters privParam, CipherParameters pubParam, CipherParameters params)106     public void init(
107         boolean forEncryption,
108         CipherParameters privParam,
109         CipherParameters pubParam,
110         CipherParameters params)
111     {
112         this.forEncryption = forEncryption;
113         this.privParam = privParam;
114         this.pubParam = pubParam;
115         this.V = new byte[0];
116 
117         extractParams(params);
118     }
119 
120     /**
121      * Initialise the decryptor.
122      *
123      * @param publicKey                 the recipient's/sender's public key parameters
124      * @param params                    encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
125      * @param ephemeralKeyPairGenerator the ephemeral key pair generator to use.
126      */
init( AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator)127     public void init(
128         AsymmetricKeyParameter publicKey,
129         CipherParameters params,
130         EphemeralKeyPairGenerator ephemeralKeyPairGenerator)
131     {
132         this.forEncryption = true;
133         this.pubParam = publicKey;
134         this.keyPairGenerator = ephemeralKeyPairGenerator;
135 
136         extractParams(params);
137     }
138 
139     /**
140      * Initialise the encryptor.
141      *
142      * @param privateKey      the recipient's private key.
143      * @param params          encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
144      * @param publicKeyParser the parser for reading the ephemeral public key.
145      */
init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)146     public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)
147     {
148         this.forEncryption = false;
149         this.privParam = privateKey;
150         this.keyParser = publicKeyParser;
151 
152         extractParams(params);
153     }
154 
extractParams(CipherParameters params)155     private void extractParams(CipherParameters params)
156     {
157         if (params instanceof ParametersWithIV)
158         {
159             this.IV = ((ParametersWithIV)params).getIV();
160             this.param = (IESParameters)((ParametersWithIV)params).getParameters();
161         }
162         else
163         {
164             this.IV = null;
165             this.param = (IESParameters)params;
166         }
167     }
168 
getCipher()169     public BufferedBlockCipher getCipher()
170     {
171         return cipher;
172     }
173 
getMac()174     public Mac getMac()
175     {
176         return mac;
177     }
178 
encryptBlock(byte[] in, int inOff, int inLen)179     private byte[] encryptBlock(byte[] in, int inOff, int inLen)
180         throws InvalidCipherTextException
181     {
182         byte[] C = null, K = null, K1 = null, K2 = null;
183         int len;
184 
185         if (cipher == null)
186         {
187             // Streaming mode.
188             K1 = new byte[inLen];
189             K2 = new byte[param.getMacKeySize() / 8];
190             K = new byte[K1.length + K2.length];
191 
192             kdf.generateBytes(K, 0, K.length);
193 
194             if (V.length != 0)
195             {
196                 System.arraycopy(K, 0, K2, 0, K2.length);
197                 System.arraycopy(K, K2.length, K1, 0, K1.length);
198             }
199             else
200             {
201                 System.arraycopy(K, 0, K1, 0, K1.length);
202                 System.arraycopy(K, inLen, K2, 0, K2.length);
203             }
204 
205             C = new byte[inLen];
206 
207             for (int i = 0; i != inLen; i++)
208             {
209                 C[i] = (byte)(in[inOff + i] ^ K1[i]);
210             }
211             len = inLen;
212         }
213         else
214         {
215             // Block cipher mode.
216             K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
217             K2 = new byte[param.getMacKeySize() / 8];
218             K = new byte[K1.length + K2.length];
219 
220             kdf.generateBytes(K, 0, K.length);
221             System.arraycopy(K, 0, K1, 0, K1.length);
222             System.arraycopy(K, K1.length, K2, 0, K2.length);
223 
224             // If iv provided use it to initialise the cipher
225             if (IV != null)
226             {
227                 cipher.init(true, new ParametersWithIV(new KeyParameter(K1), IV));
228             }
229             else
230             {
231                 cipher.init(true, new KeyParameter(K1));
232             }
233 
234             C = new byte[cipher.getOutputSize(inLen)];
235             len = cipher.processBytes(in, inOff, inLen, C, 0);
236             len += cipher.doFinal(C, len);
237         }
238 
239 
240         // Convert the length of the encoding vector into a byte array.
241         byte[] P2 = param.getEncodingV();
242         byte[] L2 = null;
243         if (V.length != 0)
244         {
245             L2 = getLengthTag(P2);
246         }
247 
248 
249         // Apply the MAC.
250         byte[] T = new byte[mac.getMacSize()];
251         // Ethereum change:
252         // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes.
253         // Old code: mac.init(new KeyParameter(K2));
254         Digest hash = new SHA256Digest();
255         byte[] K2hash = new byte[hash.getDigestSize()];
256         hash.reset();
257         hash.update(K2, 0, K2.length);
258         hash.doFinal(K2hash, 0);
259 
260         mac.init(new KeyParameter(K2hash));
261         // we also update the mac with the IV:
262         mac.update(IV, 0, IV.length);
263         // end of Ethereum change.
264         mac.update(C, 0, C.length);
265         if (P2 != null)
266         {
267             mac.update(P2, 0, P2.length);
268         }
269         if (V.length != 0)
270         {
271             mac.update(L2, 0, L2.length);
272         }
273         // Ethereum change
274         mac.update(commonMac, 0, commonMac.length);
275         mac.doFinal(T, 0);
276 
277 
278         // Output the triple (V,C,T).
279         byte[] Output = new byte[V.length + len + T.length];
280         System.arraycopy(V, 0, Output, 0, V.length);
281         System.arraycopy(C, 0, Output, V.length, len);
282         System.arraycopy(T, 0, Output, V.length + len, T.length);
283         return Output;
284     }
285 
decryptBlock(byte[] in_enc, int inOff, int inLen)286     private byte[] decryptBlock(byte[] in_enc, int inOff, int inLen)
287         throws InvalidCipherTextException
288     {
289         byte[] M, K, K1, K2;
290         int len = 0;
291 
292         // Ensure that the length of the input is greater than the MAC in bytes
293         if (inLen < V.length + mac.getMacSize())
294         {
295             throw new InvalidCipherTextException("length of input must be greater than the MAC and V combined");
296         }
297 
298         // note order is important: set up keys, do simple encryptions, check mac, do final encryption.
299         if (cipher == null)
300         {
301             // Streaming mode.
302             K1 = new byte[inLen - V.length - mac.getMacSize()];
303             K2 = new byte[param.getMacKeySize() / 8];
304             K = new byte[K1.length + K2.length];
305 
306             kdf.generateBytes(K, 0, K.length);
307 
308             if (V.length != 0)
309             {
310                 System.arraycopy(K, 0, K2, 0, K2.length);
311                 System.arraycopy(K, K2.length, K1, 0, K1.length);
312             }
313             else
314             {
315                 System.arraycopy(K, 0, K1, 0, K1.length);
316                 System.arraycopy(K, K1.length, K2, 0, K2.length);
317             }
318 
319             // process the message
320             M = new byte[K1.length];
321 
322             for (int i = 0; i != K1.length; i++)
323             {
324                 M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]);
325             }
326         }
327         else
328         {
329             // Block cipher mode.
330             K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
331             K2 = new byte[param.getMacKeySize() / 8];
332             K = new byte[K1.length + K2.length];
333 
334             kdf.generateBytes(K, 0, K.length);
335             System.arraycopy(K, 0, K1, 0, K1.length);
336             System.arraycopy(K, K1.length, K2, 0, K2.length);
337 
338             CipherParameters cp = new KeyParameter(K1);
339 
340             // If IV provide use it to initialize the cipher
341             if (IV != null)
342             {
343                 cp = new ParametersWithIV(cp, IV);
344             }
345 
346             cipher.init(false, cp);
347 
348             M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())];
349 
350             // do initial processing
351             len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0);
352         }
353 
354         // Convert the length of the encoding vector into a byte array.
355         byte[] P2 = param.getEncodingV();
356         byte[] L2 = null;
357         if (V.length != 0)
358         {
359             L2 = getLengthTag(P2);
360         }
361 
362         // Verify the MAC.
363         int end = inOff + inLen;
364         byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end);
365 
366         byte[] T2 = new byte[T1.length];
367         // Ethereum change:
368         // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes.
369         // Old code: mac.init(new KeyParameter(K2));
370         Digest hash = new SHA256Digest();
371         byte[] K2hash = new byte[hash.getDigestSize()];
372         hash.reset();
373         hash.update(K2, 0, K2.length);
374         hash.doFinal(K2hash, 0);
375         mac.init(new KeyParameter(K2hash));
376         // we also update the mac with the IV:
377         mac.update(IV, 0, IV.length);
378         // end of Ethereum change.
379 
380         mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length);
381 
382         if (P2 != null)
383         {
384             mac.update(P2, 0, P2.length);
385         }
386         if (V.length != 0)
387         {
388             mac.update(L2, 0, L2.length);
389         }
390         // Ethereum change
391         mac.update(commonMac, 0, commonMac.length);
392         mac.doFinal(T2, 0);
393 
394         if (!Arrays.constantTimeAreEqual(T1, T2))
395         {
396             throw new InvalidCipherTextException("invalid MAC");
397         }
398 
399         if (cipher == null)
400         {
401             return M;
402         }
403         else
404         {
405             len += cipher.doFinal(M, len);
406 
407             return Arrays.copyOfRange(M, 0, len);
408         }
409     }
410 
411 
processBlock(byte[] in, int inOff, int inLen)412     public byte[] processBlock(byte[] in, int inOff, int inLen)
413         throws InvalidCipherTextException
414     {
415         if (forEncryption)
416         {
417             if (keyPairGenerator != null)
418             {
419                 EphemeralKeyPair ephKeyPair = keyPairGenerator.generate();
420 
421                 this.privParam = ephKeyPair.getKeyPair().getPrivate();
422                 this.V = ephKeyPair.getEncodedPublicKey();
423             }
424         }
425         else
426         {
427             if (keyParser != null)
428             {
429                 ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen);
430 
431                 try
432                 {
433                     this.pubParam = keyParser.readKey(bIn);
434                 }
435                 catch (IOException e)
436                 {
437                     throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e);
438                 }
439                 catch (IllegalArgumentException e)
440                 {
441                     throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e);
442                 }
443 
444                 int encLength = (inLen - bIn.available());
445                 this.V = Arrays.copyOfRange(in, inOff, inOff + encLength);
446             }
447         }
448 
449         // Compute the common value and convert to byte array.
450         agree.init(privParam);
451         BigInteger z = agree.calculateAgreement(pubParam);
452         byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z);
453 
454         // Create input to KDF.
455         if (V.length != 0)
456         {
457             byte[] VZ = Arrays.concatenate(V, Z);
458             Arrays.fill(Z, (byte)0);
459             Z = VZ;
460         }
461 
462         try
463         {
464             // Initialise the KDF.
465             KDFParameters kdfParam = new KDFParameters(Z, param.getDerivationV());
466             kdf.init(kdfParam);
467 
468             return forEncryption ? encryptBlock(in, inOff, inLen) : decryptBlock(in, inOff, inLen);
469         }
470         finally
471         {
472             Arrays.fill(Z, (byte)0);
473         }
474     }
475 
476     // as described in Shroup's paper and P1363a
getLengthTag(byte[] p2)477     protected byte[] getLengthTag(byte[] p2)
478     {
479         byte[] L2 = new byte[8];
480         if (p2 != null)
481         {
482             Pack.longToBigEndian(p2.length * 8L, L2, 0);
483         }
484         return L2;
485     }
486 
487     /**
488      * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 <br>
489      * This implementation is based on ISO 18033/P1363a.
490      * <p>
491      * This class has been adapted from the <tt>BaseKDFBytesGenerator</tt> implementation of Bouncy Castle. Only one
492      * change is present specifically for Ethereum.
493      */
494     public static class HandshakeKDFFunction
495         implements DigestDerivationFunction
496     {
497         private int counterStart;
498         private Digest digest;
499         private byte[] shared;
500         private byte[] iv;
501 
502         /**
503          * Construct a KDF Parameters generator.
504          * <p>
505          *
506          * @param counterStart value of counter.
507          * @param digest       the digest to be used as the source of derived keys.
508          */
HandshakeKDFFunction(int counterStart, Digest digest)509         public HandshakeKDFFunction(int counterStart, Digest digest)
510         {
511             this.counterStart = counterStart;
512             this.digest = digest;
513         }
514 
init(DerivationParameters param)515         public void init(DerivationParameters param)
516         {
517             if (param instanceof KDFParameters)
518             {
519                 KDFParameters p = (KDFParameters)param;
520 
521                 shared = p.getSharedSecret();
522                 iv = p.getIV();
523             }
524             else if (param instanceof ISO18033KDFParameters)
525             {
526                 ISO18033KDFParameters p = (ISO18033KDFParameters)param;
527 
528                 shared = p.getSeed();
529                 iv = null;
530             }
531             else
532             {
533                 throw new IllegalArgumentException("KDF parameters required for generator");
534             }
535         }
536 
537         /**
538          * return the underlying digest.
539          */
getDigest()540         public Digest getDigest()
541         {
542             return digest;
543         }
544 
545         /**
546          * fill len bytes of the output buffer with bytes generated from the derivation function.
547          *
548          * @throws IllegalArgumentException if the size of the request will cause an overflow.
549          * @throws DataLengthException if the out buffer is too small.
550          */
generateBytes(byte[] out, int outOff, int len)551         public int generateBytes(byte[] out, int outOff, int len)
552             throws DataLengthException, IllegalArgumentException
553         {
554             if ((out.length - len) < outOff)
555             {
556                 throw new OutputLengthException("output buffer too small");
557             }
558 
559             long oBytes = len;
560             int outLen = digest.getDigestSize();
561 
562             //
563             // this is at odds with the standard implementation, the
564             // maximum value should be hBits * (2^32 - 1) where hBits
565             // is the digest output size in bits. We can't have an
566             // array with a long index at the moment...
567             //
568             if (oBytes > ((2L << 32) - 1))
569             {
570                 throw new IllegalArgumentException("output length too large");
571             }
572 
573             int cThreshold = (int)((oBytes + outLen - 1) / outLen);
574 
575             byte[] dig = new byte[digest.getDigestSize()];
576 
577             byte[] C = new byte[4];
578             Pack.intToBigEndian(counterStart, C, 0);
579 
580             int counterBase = counterStart & ~0xFF;
581 
582             for (int i = 0; i < cThreshold; i++)
583             {
584                 // only change for Ethereum: invert those 2 lines.
585                 digest.update(C, 0, C.length);
586                 digest.update(shared, 0, shared.length);
587                 // End of change for Ethereum.
588 
589                 if (iv != null)
590                 {
591                     digest.update(iv, 0, iv.length);
592                 }
593 
594                 digest.doFinal(dig, 0);
595 
596                 if (len > outLen)
597                 {
598                     System.arraycopy(dig, 0, out, outOff, outLen);
599                     outOff += outLen;
600                     len -= outLen;
601                 }
602                 else
603                 {
604                     System.arraycopy(dig, 0, out, outOff, len);
605                 }
606 
607                 if (++C[3] == 0)
608                 {
609                     counterBase += 0x100;
610                     Pack.intToBigEndian(counterBase, C, 0);
611                 }
612             }
613 
614             digest.reset();
615 
616             return (int)oBytes;
617         }
618     }
619 }
620