1package org.bouncycastle.crypto.modes;
2
3import org.bouncycastle.crypto.BlockCipher;
4import org.bouncycastle.crypto.CipherParameters;
5import org.bouncycastle.crypto.DataLengthException;
6import org.bouncycastle.crypto.params.ParametersWithIV;
7
8/**
9 * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher.
10 */
11public class CFBBlockCipher
12    implements BlockCipher
13{
14    private byte[]          IV;
15    private byte[]          cfbV;
16    private byte[]          cfbOutV;
17
18    private int             blockSize;
19    private BlockCipher     cipher = null;
20    private boolean         encrypting;
21
22    /**
23     * Basic constructor.
24     *
25     * @param cipher the block cipher to be used as the basis of the
26     * feedback mode.
27     * @param bitBlockSize the block size in bits (note: a multiple of 8)
28     */
29    public CFBBlockCipher(
30        BlockCipher cipher,
31        int         bitBlockSize)
32    {
33        this.cipher = cipher;
34        this.blockSize = bitBlockSize / 8;
35
36        this.IV = new byte[cipher.getBlockSize()];
37        this.cfbV = new byte[cipher.getBlockSize()];
38        this.cfbOutV = new byte[cipher.getBlockSize()];
39    }
40
41    /**
42     * return the underlying block cipher that we are wrapping.
43     *
44     * @return the underlying block cipher that we are wrapping.
45     */
46    public BlockCipher getUnderlyingCipher()
47    {
48        return cipher;
49    }
50
51    /**
52     * Initialise the cipher and, possibly, the initialisation vector (IV).
53     * If an IV isn't passed as part of the parameter, the IV will be all zeros.
54     * An IV which is too short is handled in FIPS compliant fashion.
55     *
56     * @param encrypting if true the cipher is initialised for
57     *  encryption, if false for decryption.
58     * @param params the key and other data required by the cipher.
59     * @exception IllegalArgumentException if the params argument is
60     * inappropriate.
61     */
62    public void init(
63        boolean             encrypting,
64        CipherParameters    params)
65        throws IllegalArgumentException
66    {
67        this.encrypting = encrypting;
68
69        if (params instanceof ParametersWithIV)
70        {
71                ParametersWithIV ivParam = (ParametersWithIV)params;
72                byte[]      iv = ivParam.getIV();
73
74                if (iv.length < IV.length)
75                {
76                    // prepend the supplied IV with zeros (per FIPS PUB 81)
77                    System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
78                    for (int i = 0; i < IV.length - iv.length; i++)
79                    {
80                        IV[i] = 0;
81                    }
82                }
83                else
84                {
85                    System.arraycopy(iv, 0, IV, 0, IV.length);
86                }
87
88                reset();
89
90                cipher.init(true, ivParam.getParameters());
91        }
92        else
93        {
94                reset();
95
96                cipher.init(true, params);
97        }
98    }
99
100    /**
101     * return the algorithm name and mode.
102     *
103     * @return the name of the underlying algorithm followed by "/CFB"
104     * and the block size in bits.
105     */
106    public String getAlgorithmName()
107    {
108        return cipher.getAlgorithmName() + "/CFB" + (blockSize * 8);
109    }
110
111    /**
112     * return the block size we are operating at.
113     *
114     * @return the block size we are operating at (in bytes).
115     */
116    public int getBlockSize()
117    {
118        return blockSize;
119    }
120
121    /**
122     * Process one block of input from the array in and write it to
123     * the out array.
124     *
125     * @param in the array containing the input data.
126     * @param inOff offset into the in array the data starts at.
127     * @param out the array the output data will be copied into.
128     * @param outOff the offset into the out array the output will start at.
129     * @exception DataLengthException if there isn't enough data in in, or
130     * space in out.
131     * @exception IllegalStateException if the cipher isn't initialised.
132     * @return the number of bytes processed and produced.
133     */
134    public int processBlock(
135        byte[]      in,
136        int         inOff,
137        byte[]      out,
138        int         outOff)
139        throws DataLengthException, IllegalStateException
140    {
141        return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
142    }
143
144    /**
145     * Do the appropriate processing for CFB mode encryption.
146     *
147     * @param in the array containing the data to be encrypted.
148     * @param inOff offset into the in array the data starts at.
149     * @param out the array the encrypted data will be copied into.
150     * @param outOff the offset into the out array the output will start at.
151     * @exception DataLengthException if there isn't enough data in in, or
152     * space in out.
153     * @exception IllegalStateException if the cipher isn't initialised.
154     * @return the number of bytes processed and produced.
155     */
156    public int encryptBlock(
157        byte[]      in,
158        int         inOff,
159        byte[]      out,
160        int         outOff)
161        throws DataLengthException, IllegalStateException
162    {
163        if ((inOff + blockSize) > in.length)
164        {
165            throw new DataLengthException("input buffer too short");
166        }
167
168        if ((outOff + blockSize) > out.length)
169        {
170            throw new DataLengthException("output buffer too short");
171        }
172
173        cipher.processBlock(cfbV, 0, cfbOutV, 0);
174
175        //
176        // XOR the cfbV with the plaintext producing the cipher text
177        //
178        for (int i = 0; i < blockSize; i++)
179        {
180            out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]);
181        }
182
183        //
184        // change over the input block.
185        //
186        System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize);
187        System.arraycopy(out, outOff, cfbV, cfbV.length - blockSize, blockSize);
188
189        return blockSize;
190    }
191
192    /**
193     * Do the appropriate processing for CFB mode decryption.
194     *
195     * @param in the array containing the data to be decrypted.
196     * @param inOff offset into the in array the data starts at.
197     * @param out the array the encrypted data will be copied into.
198     * @param outOff the offset into the out array the output will start at.
199     * @exception DataLengthException if there isn't enough data in in, or
200     * space in out.
201     * @exception IllegalStateException if the cipher isn't initialised.
202     * @return the number of bytes processed and produced.
203     */
204    public int decryptBlock(
205        byte[]      in,
206        int         inOff,
207        byte[]      out,
208        int         outOff)
209        throws DataLengthException, IllegalStateException
210    {
211        if ((inOff + blockSize) > in.length)
212        {
213            throw new DataLengthException("input buffer too short");
214        }
215
216        if ((outOff + blockSize) > out.length)
217        {
218            throw new DataLengthException("output buffer too short");
219        }
220
221        cipher.processBlock(cfbV, 0, cfbOutV, 0);
222
223        //
224        // change over the input block.
225        //
226        System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize);
227        System.arraycopy(in, inOff, cfbV, cfbV.length - blockSize, blockSize);
228
229        //
230        // XOR the cfbV with the plaintext producing the plain text
231        //
232        for (int i = 0; i < blockSize; i++)
233        {
234            out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]);
235        }
236
237        return blockSize;
238    }
239
240    /**
241     * reset the chaining vector back to the IV and reset the underlying
242     * cipher.
243     */
244    public void reset()
245    {
246        System.arraycopy(IV, 0, cfbV, 0, IV.length);
247
248        cipher.reset();
249    }
250}
251