1 package org.bouncycastle.crypto.engines;
2 
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.math.BigInteger;
6 
7 import org.bouncycastle.crypto.BasicAgreement;
8 import org.bouncycastle.crypto.BufferedBlockCipher;
9 import org.bouncycastle.crypto.CipherParameters;
10 import org.bouncycastle.crypto.DerivationFunction;
11 import org.bouncycastle.crypto.EphemeralKeyPair;
12 import org.bouncycastle.crypto.InvalidCipherTextException;
13 import org.bouncycastle.crypto.KeyParser;
14 import org.bouncycastle.crypto.Mac;
15 import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
16 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
17 import org.bouncycastle.crypto.params.IESParameters;
18 import org.bouncycastle.crypto.params.IESWithCipherParameters;
19 import org.bouncycastle.crypto.params.KDFParameters;
20 import org.bouncycastle.crypto.params.KeyParameter;
21 import org.bouncycastle.crypto.params.ParametersWithIV;
22 import org.bouncycastle.util.Arrays;
23 import org.bouncycastle.util.BigIntegers;
24 import org.bouncycastle.util.Pack;
25 
26 /**
27  * Support class for constructing integrated encryption ciphers
28  * for doing basic message exchanges on top of key agreement ciphers.
29  * Follows the description given in IEEE Std 1363a.
30  */
31 public class IESEngine
32 {
33     BasicAgreement agree;
34     DerivationFunction kdf;
35     Mac mac;
36     BufferedBlockCipher cipher;
37     byte[] macBuf;
38 
39     boolean forEncryption;
40     CipherParameters privParam, pubParam;
41     IESParameters param;
42 
43     byte[] V;
44     private EphemeralKeyPairGenerator keyPairGenerator;
45     private KeyParser keyParser;
46     private byte[] IV;
47 
48     /**
49      * Set up for use with stream mode, where the key derivation function
50      * is used to provide a stream of bytes to xor with the message.
51      *
52      * @param agree the key agreement used as the basis for the encryption
53      * @param kdf   the key derivation function used for byte generation
54      * @param mac   the message authentication code generator for the message
55      */
IESEngine( BasicAgreement agree, DerivationFunction kdf, Mac mac)56     public IESEngine(
57         BasicAgreement agree,
58         DerivationFunction kdf,
59         Mac mac)
60     {
61         this.agree = agree;
62         this.kdf = kdf;
63         this.mac = mac;
64         this.macBuf = new byte[mac.getMacSize()];
65         this.cipher = null;
66     }
67 
68 
69     /**
70      * Set up for use in conjunction with a block cipher to handle the
71      * message. It is <b>strongly</b> recommended that the cipher is not in ECB mode.
72      *
73      * @param agree  the key agreement used as the basis for the encryption
74      * @param kdf    the key derivation function used for byte generation
75      * @param mac    the message authentication code generator for the message
76      * @param cipher the cipher to used for encrypting the message
77      */
IESEngine( BasicAgreement agree, DerivationFunction kdf, Mac mac, BufferedBlockCipher cipher)78     public IESEngine(
79         BasicAgreement agree,
80         DerivationFunction kdf,
81         Mac mac,
82         BufferedBlockCipher cipher)
83     {
84         this.agree = agree;
85         this.kdf = kdf;
86         this.mac = mac;
87         this.macBuf = new byte[mac.getMacSize()];
88         this.cipher = cipher;
89     }
90 
91     /**
92      * Initialise the encryptor.
93      *
94      * @param forEncryption whether or not this is encryption/decryption.
95      * @param privParam     our private key parameters
96      * @param pubParam      the recipient's/sender's public key parameters
97      * @param params        encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
98      */
init( boolean forEncryption, CipherParameters privParam, CipherParameters pubParam, CipherParameters params)99     public void init(
100         boolean forEncryption,
101         CipherParameters privParam,
102         CipherParameters pubParam,
103         CipherParameters params)
104     {
105         this.forEncryption = forEncryption;
106         this.privParam = privParam;
107         this.pubParam = pubParam;
108         this.V = new byte[0];
109 
110         extractParams(params);
111     }
112 
113     /**
114      * Initialise the decryptor.
115      *
116      * @param publicKey      the recipient's/sender's public key parameters
117      * @param params         encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
118      * @param ephemeralKeyPairGenerator             the ephemeral key pair generator to use.
119      */
init(AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator)120     public void init(AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator)
121     {
122         this.forEncryption = true;
123         this.pubParam = publicKey;
124         this.keyPairGenerator = ephemeralKeyPairGenerator;
125 
126         extractParams(params);
127     }
128 
129     /**
130      * Initialise the encryptor.
131      *
132      * @param privateKey      the recipient's private key.
133      * @param params          encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher.
134      * @param publicKeyParser the parser for reading the ephemeral public key.
135      */
init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)136     public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)
137     {
138         this.forEncryption = false;
139         this.privParam = privateKey;
140         this.keyParser = publicKeyParser;
141 
142         extractParams(params);
143     }
144 
extractParams(CipherParameters params)145     private void extractParams(CipherParameters params)
146     {
147         if (params instanceof ParametersWithIV)
148         {
149             this.IV = ((ParametersWithIV)params).getIV();
150             this.param = (IESParameters)((ParametersWithIV)params).getParameters();
151         }
152         else
153         {
154             this.IV = null;
155             this.param = (IESParameters)params;
156         }
157     }
158 
getCipher()159     public BufferedBlockCipher getCipher()
160     {
161         return cipher;
162     }
163 
getMac()164     public Mac getMac()
165     {
166         return mac;
167     }
168 
encryptBlock( byte[] in, int inOff, int inLen)169     private byte[] encryptBlock(
170         byte[] in,
171         int inOff,
172         int inLen)
173         throws InvalidCipherTextException
174     {
175         byte[] C = null, K = null, K1 = null, K2 = null;
176         int len;
177 
178         if (cipher == null)
179         {
180             // Streaming mode.
181             K1 = new byte[inLen];
182             K2 = new byte[param.getMacKeySize() / 8];
183             K = new byte[K1.length + K2.length];
184 
185             kdf.generateBytes(K, 0, K.length);
186 
187             if (V.length != 0)
188             {
189                 System.arraycopy(K, 0, K2, 0, K2.length);
190                 System.arraycopy(K, K2.length, K1, 0, K1.length);
191             }
192             else
193             {
194                 System.arraycopy(K, 0, K1, 0, K1.length);
195                 System.arraycopy(K, inLen, K2, 0, K2.length);
196             }
197 
198             C = new byte[inLen];
199 
200             for (int i = 0; i != inLen; i++)
201             {
202                 C[i] = (byte)(in[inOff + i] ^ K1[i]);
203             }
204             len = inLen;
205         }
206         else
207         {
208             // Block cipher mode.
209             K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
210             K2 = new byte[param.getMacKeySize() / 8];
211             K = new byte[K1.length + K2.length];
212 
213             kdf.generateBytes(K, 0, K.length);
214             System.arraycopy(K, 0, K1, 0, K1.length);
215             System.arraycopy(K, K1.length, K2, 0, K2.length);
216 
217             // If iv provided use it to initialise the cipher
218             if (IV != null)
219             {
220                 cipher.init(true, new ParametersWithIV(new KeyParameter(K1), IV));
221             }
222             else
223             {
224                 cipher.init(true, new KeyParameter(K1));
225             }
226 
227             C = new byte[cipher.getOutputSize(inLen)];
228             len = cipher.processBytes(in, inOff, inLen, C, 0);
229             len += cipher.doFinal(C, len);
230         }
231 
232 
233         // Convert the length of the encoding vector into a byte array.
234         byte[] P2 = param.getEncodingV();
235         byte[] L2 = null;
236         if (V.length != 0)
237         {
238             L2 = getLengthTag(P2);
239         }
240 
241 
242         // Apply the MAC.
243         byte[] T = new byte[mac.getMacSize()];
244 
245         mac.init(new KeyParameter(K2));
246         mac.update(C, 0, C.length);
247         if (P2 != null)
248         {
249             mac.update(P2, 0, P2.length);
250         }
251         if (V.length != 0)
252         {
253             mac.update(L2, 0, L2.length);
254         }
255         mac.doFinal(T, 0);
256 
257 
258         // Output the triple (V,C,T).
259         byte[] Output = new byte[V.length + len + T.length];
260         System.arraycopy(V, 0, Output, 0, V.length);
261         System.arraycopy(C, 0, Output, V.length, len);
262         System.arraycopy(T, 0, Output, V.length + len, T.length);
263         return Output;
264     }
265 
decryptBlock( byte[] in_enc, int inOff, int inLen)266     private byte[] decryptBlock(
267         byte[] in_enc,
268         int inOff,
269         int inLen)
270         throws InvalidCipherTextException
271     {
272         byte[] M, K, K1, K2;
273         int len = 0;
274 
275         // Ensure that the length of the input is greater than the MAC in bytes
276         if (inLen < V.length + mac.getMacSize())
277         {
278             throw new InvalidCipherTextException("Length of input must be greater than the MAC and V combined");
279         }
280 
281         // note order is important: set up keys, do simple encryptions, check mac, do final encryption.
282         if (cipher == null)
283         {
284             // Streaming mode.
285             K1 = new byte[inLen - V.length - mac.getMacSize()];
286             K2 = new byte[param.getMacKeySize() / 8];
287             K = new byte[K1.length + K2.length];
288 
289             kdf.generateBytes(K, 0, K.length);
290 
291             if (V.length != 0)
292             {
293                 System.arraycopy(K, 0, K2, 0, K2.length);
294                 System.arraycopy(K, K2.length, K1, 0, K1.length);
295             }
296             else
297             {
298                 System.arraycopy(K, 0, K1, 0, K1.length);
299                 System.arraycopy(K, K1.length, K2, 0, K2.length);
300             }
301 
302             // process the message
303             M = new byte[K1.length];
304 
305             for (int i = 0; i != K1.length; i++)
306             {
307                 M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]);
308             }
309         }
310         else
311         {
312             // Block cipher mode.
313             K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
314             K2 = new byte[param.getMacKeySize() / 8];
315             K = new byte[K1.length + K2.length];
316 
317             kdf.generateBytes(K, 0, K.length);
318             System.arraycopy(K, 0, K1, 0, K1.length);
319             System.arraycopy(K, K1.length, K2, 0, K2.length);
320 
321             CipherParameters cp = new KeyParameter(K1);
322 
323             // If IV provide use it to initialize the cipher
324             if (IV != null)
325             {
326                 cp = new ParametersWithIV(cp, IV);
327             }
328 
329             cipher.init(false, cp);
330 
331             M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())];
332 
333             // do initial processing
334             len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0);
335         }
336 
337         // Convert the length of the encoding vector into a byte array.
338         byte[] P2 = param.getEncodingV();
339         byte[] L2 = null;
340         if (V.length != 0)
341         {
342             L2 = getLengthTag(P2);
343         }
344 
345         // Verify the MAC.
346         int end = inOff + inLen;
347         byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end);
348 
349         byte[] T2 = new byte[T1.length];
350         mac.init(new KeyParameter(K2));
351         mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length);
352 
353         if (P2 != null)
354         {
355             mac.update(P2, 0, P2.length);
356         }
357         if (V.length != 0)
358         {
359             mac.update(L2, 0, L2.length);
360         }
361         mac.doFinal(T2, 0);
362 
363         if (!Arrays.constantTimeAreEqual(T1, T2))
364         {
365             throw new InvalidCipherTextException("invalid MAC");
366         }
367 
368         if (cipher == null)
369         {
370             return M;
371         }
372         else
373         {
374             len += cipher.doFinal(M, len);
375 
376             return Arrays.copyOfRange(M, 0, len);
377         }
378     }
379 
380 
processBlock( byte[] in, int inOff, int inLen)381     public byte[] processBlock(
382         byte[] in,
383         int inOff,
384         int inLen)
385         throws InvalidCipherTextException
386     {
387         if (forEncryption)
388         {
389             if (keyPairGenerator != null)
390             {
391                 EphemeralKeyPair ephKeyPair = keyPairGenerator.generate();
392 
393                 this.privParam = ephKeyPair.getKeyPair().getPrivate();
394                 this.V = ephKeyPair.getEncodedPublicKey();
395             }
396         }
397         else
398         {
399             if (keyParser != null)
400             {
401                 ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen);
402 
403                 try
404                 {
405                     this.pubParam = keyParser.readKey(bIn);
406                 }
407                 catch (IOException e)
408                 {
409                     throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e);
410                 }
411                 catch (IllegalArgumentException e)
412                 {
413                     throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e);
414                 }
415 
416                 int encLength = (inLen - bIn.available());
417                 this.V = Arrays.copyOfRange(in, inOff, inOff + encLength);
418             }
419         }
420 
421         // Compute the common value and convert to byte array.
422         agree.init(privParam);
423         BigInteger z = agree.calculateAgreement(pubParam);
424         byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z);
425 
426         // Create input to KDF.
427         if (V.length != 0)
428         {
429             byte[] VZ = Arrays.concatenate(V, Z);
430             Arrays.fill(Z, (byte)0);
431             Z = VZ;
432         }
433 
434         try
435         {
436             // Initialise the KDF.
437             KDFParameters kdfParam = new KDFParameters(Z, param.getDerivationV());
438             kdf.init(kdfParam);
439 
440             return forEncryption
441                 ? encryptBlock(in, inOff, inLen)
442                 : decryptBlock(in, inOff, inLen);
443         }
444         finally
445         {
446             Arrays.fill(Z, (byte)0);
447         }
448     }
449 
450     // as described in Shroup's paper and P1363a
getLengthTag(byte[] p2)451     protected byte[] getLengthTag(byte[] p2)
452     {
453         byte[] L2 = new byte[8];
454         if (p2 != null)
455         {
456             Pack.longToBigEndian(p2.length * 8L, L2, 0);
457         }
458         return L2;
459     }
460 }
461