1package org.bouncycastle.crypto.engines;
2
3import java.math.BigInteger;
4
5import org.bouncycastle.crypto.BasicAgreement;
6import org.bouncycastle.crypto.BufferedBlockCipher;
7import org.bouncycastle.crypto.CipherParameters;
8import org.bouncycastle.crypto.DerivationFunction;
9import org.bouncycastle.crypto.InvalidCipherTextException;
10import org.bouncycastle.crypto.Mac;
11import org.bouncycastle.crypto.params.IESParameters;
12import org.bouncycastle.crypto.params.IESWithCipherParameters;
13import org.bouncycastle.crypto.params.KDFParameters;
14import org.bouncycastle.crypto.params.KeyParameter;
15
16/**
17 * support class for constructing intergrated encryption ciphers
18 * for doing basic message exchanges on top of key agreement ciphers
19 */
20public class IESEngine
21{
22    BasicAgreement      agree;
23    DerivationFunction  kdf;
24    Mac                 mac;
25    BufferedBlockCipher cipher;
26    byte[]              macBuf;
27
28    boolean             forEncryption;
29    CipherParameters    privParam, pubParam;
30    IESParameters       param;
31
32    /**
33     * set up for use with stream mode, where the key derivation function
34     * is used to provide a stream of bytes to xor with the message.
35     *
36     * @param agree the key agreement used as the basis for the encryption
37     * @param kdf the key derivation function used for byte generation
38     * @param mac the message authentication code generator for the message
39     */
40    public IESEngine(
41        BasicAgreement      agree,
42        DerivationFunction  kdf,
43        Mac                 mac)
44    {
45        this.agree = agree;
46        this.kdf = kdf;
47        this.mac = mac;
48        this.macBuf = new byte[mac.getMacSize()];
49        this.cipher = null;
50    }
51
52    /**
53     * set up for use in conjunction with a block cipher to handle the
54     * message.
55     *
56     * @param agree the key agreement used as the basis for the encryption
57     * @param kdf the key derivation function used for byte generation
58     * @param mac the message authentication code generator for the message
59     * @param cipher the cipher to used for encrypting the message
60     */
61    public IESEngine(
62        BasicAgreement      agree,
63        DerivationFunction  kdf,
64        Mac                 mac,
65        BufferedBlockCipher cipher)
66    {
67        this.agree = agree;
68        this.kdf = kdf;
69        this.mac = mac;
70        this.macBuf = new byte[mac.getMacSize()];
71        this.cipher = cipher;
72    }
73
74    /**
75     * Initialise the encryptor.
76     *
77     * @param forEncryption whether or not this is encryption/decryption.
78     * @param privParam our private key parameters
79     * @param pubParam the recipient's/sender's public key parameters
80     * @param param encoding and derivation parameters.
81     */
82    public void init(
83        boolean                     forEncryption,
84        CipherParameters            privParam,
85        CipherParameters            pubParam,
86        CipherParameters            param)
87    {
88        this.forEncryption = forEncryption;
89        this.privParam = privParam;
90        this.pubParam = pubParam;
91        this.param = (IESParameters)param;
92    }
93
94    private byte[] decryptBlock(
95        byte[]  in_enc,
96        int     inOff,
97        int     inLen,
98        byte[]  z)
99        throws InvalidCipherTextException
100    {
101        byte[]          M = null;
102        KeyParameter    macKey = null;
103        KDFParameters   kParam = new KDFParameters(z, param.getDerivationV());
104        int             macKeySize = param.getMacKeySize();
105
106        kdf.init(kParam);
107
108        inLen -= mac.getMacSize();
109
110        if (cipher == null)     // stream mode
111        {
112            byte[]  buf = new byte[inLen + (macKeySize / 8)];
113
114            M = new byte[inLen];
115
116            kdf.generateBytes(buf, 0, buf.length);
117
118            for (int i = 0; i != inLen; i++)
119            {
120                M[i] = (byte)(in_enc[inOff + i] ^ buf[i]);
121            }
122
123            macKey = new KeyParameter(buf, inLen, (macKeySize / 8));
124        }
125        else
126        {
127            int     cipherKeySize = ((IESWithCipherParameters)param).getCipherKeySize();
128            byte[]  buf = new byte[(cipherKeySize / 8) + (macKeySize / 8)];
129
130            cipher.init(false, new KeyParameter(buf, 0, (cipherKeySize / 8)));
131
132            byte[] tmp = new byte[cipher.getOutputSize(inLen)];
133
134            int off = cipher.processBytes(in_enc, inOff, inLen, tmp, 0);
135
136            off += cipher.doFinal(tmp, off);
137
138            M = new byte[off];
139
140            System.arraycopy(tmp, 0, M, 0, off);
141
142            macKey = new KeyParameter(buf, (cipherKeySize / 8), (macKeySize / 8));
143        }
144
145        byte[]  macIV = param.getEncodingV();
146
147        mac.init(macKey);
148        mac.update(in_enc, inOff, inLen);
149        mac.update(macIV, 0, macIV.length);
150        mac.doFinal(macBuf, 0);
151
152        inOff += inLen;
153
154        for (int t = 0; t < macBuf.length; t++)
155        {
156            if (macBuf[t] != in_enc[inOff + t])
157            {
158                throw (new InvalidCipherTextException("Mac codes failed to equal."));
159            }
160        }
161
162        return M;
163    }
164
165    private byte[] encryptBlock(
166        byte[]  in,
167        int     inOff,
168        int     inLen,
169        byte[]  z)
170        throws InvalidCipherTextException
171    {
172        byte[]          C = null;
173        KeyParameter    macKey = null;
174        KDFParameters   kParam = new KDFParameters(z, param.getDerivationV());
175        int             c_text_length = 0;
176        int             macKeySize = param.getMacKeySize();
177
178        kdf.init(kParam);
179
180        if (cipher == null)     // stream mode
181        {
182            byte[]  buf = new byte[inLen + (macKeySize / 8)];
183
184            C = new byte[inLen + mac.getMacSize()];
185            c_text_length = inLen;
186
187            kdf.generateBytes(buf, 0, buf.length);
188
189            for (int i = 0; i != inLen; i++)
190            {
191                C[i] = (byte)(in[inOff + i] ^ buf[i]);
192            }
193
194            macKey = new KeyParameter(buf, inLen, (macKeySize / 8));
195        }
196        else
197        {
198            int     cipherKeySize = ((IESWithCipherParameters)param).getCipherKeySize();
199            byte[]  buf = new byte[(cipherKeySize / 8) + (macKeySize / 8)];
200
201            cipher.init(true, new KeyParameter(buf, 0, (cipherKeySize / 8)));
202
203            c_text_length = cipher.getOutputSize(inLen);
204
205            C = new byte[c_text_length + mac.getMacSize()];
206
207            int off = cipher.processBytes(in, inOff, inLen, C, 0);
208
209            cipher.doFinal(C, off);
210
211            macKey = new KeyParameter(buf, (cipherKeySize / 8), (macKeySize / 8));
212        }
213
214        byte[]  macIV = param.getEncodingV();
215
216        mac.init(macKey);
217        mac.update(C, 0, c_text_length);
218        mac.update(macIV, 0, macIV.length);
219        //
220        // return the message and it's MAC
221        //
222        mac.doFinal(C, c_text_length);
223        return C;
224    }
225
226    public byte[] processBlock(
227        byte[]  in,
228        int     inOff,
229        int     inLen)
230        throws InvalidCipherTextException
231    {
232        agree.init(privParam);
233
234        BigInteger  z = agree.calculateAgreement(pubParam);
235
236        if (forEncryption)
237        {
238            return encryptBlock(in, inOff, inLen, z.toByteArray());
239        }
240        else
241        {
242            return decryptBlock(in, inOff, inLen, z.toByteArray());
243        }
244    }
245}
246