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 the GOST 28147 OFB counter mode (GCTR).
10 */
11public class GOFBBlockCipher
12    implements BlockCipher
13{
14    private byte[]          IV;
15    private byte[]          ofbV;
16    private byte[]          ofbOutV;
17
18    private final int             blockSize;
19    private final BlockCipher     cipher;
20
21    boolean firstStep = true;
22    int N3;
23    int N4;
24    static final int C1 = 16843012; //00000001000000010000000100000100
25    static final int C2 = 16843009; //00000001000000010000000100000001
26
27
28    /**
29     * Basic constructor.
30     *
31     * @param cipher the block cipher to be used as the basis of the
32     * counter mode (must have a 64 bit block size).
33     */
34    public GOFBBlockCipher(
35        BlockCipher cipher)
36    {
37        this.cipher = cipher;
38        this.blockSize = cipher.getBlockSize();
39
40        if (blockSize != 8)
41        {
42            throw new IllegalArgumentException("GTCR only for 64 bit block ciphers");
43        }
44
45        this.IV = new byte[cipher.getBlockSize()];
46        this.ofbV = new byte[cipher.getBlockSize()];
47        this.ofbOutV = new byte[cipher.getBlockSize()];
48    }
49
50    /**
51     * return the underlying block cipher that we are wrapping.
52     *
53     * @return the underlying block cipher that we are wrapping.
54     */
55    public BlockCipher getUnderlyingCipher()
56    {
57        return cipher;
58    }
59
60    /**
61     * Initialise the cipher and, possibly, the initialisation vector (IV).
62     * If an IV isn't passed as part of the parameter, the IV will be all zeros.
63     * An IV which is too short is handled in FIPS compliant fashion.
64     *
65     * @param encrypting if true the cipher is initialised for
66     *  encryption, if false for decryption.
67     * @param params the key and other data required by the cipher.
68     * @exception IllegalArgumentException if the params argument is
69     * inappropriate.
70     */
71    public void init(
72        boolean             encrypting, //ignored by this CTR mode
73        CipherParameters    params)
74        throws IllegalArgumentException
75    {
76        firstStep = true;
77        N3 = 0;
78        N4 = 0;
79
80        if (params instanceof ParametersWithIV)
81        {
82                ParametersWithIV ivParam = (ParametersWithIV)params;
83                byte[]      iv = ivParam.getIV();
84
85                if (iv.length < IV.length)
86                {
87                    // prepend the supplied IV with zeros (per FIPS PUB 81)
88                    System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
89                    for (int i = 0; i < IV.length - iv.length; i++)
90                    {
91                        IV[i] = 0;
92                    }
93                }
94                else
95                {
96                    System.arraycopy(iv, 0, IV, 0, IV.length);
97                }
98
99                reset();
100
101                cipher.init(true, ivParam.getParameters());
102        }
103        else
104        {
105                reset();
106
107                cipher.init(true, params);
108        }
109    }
110
111    /**
112     * return the algorithm name and mode.
113     *
114     * @return the name of the underlying algorithm followed by "/GCTR"
115     * and the block size in bits
116     */
117    public String getAlgorithmName()
118    {
119        return cipher.getAlgorithmName() + "/GCTR";
120    }
121
122
123    /**
124     * return the block size we are operating at (in bytes).
125     *
126     * @return the block size we are operating at (in bytes).
127     */
128    public int getBlockSize()
129    {
130        return blockSize;
131    }
132
133    /**
134     * Process one block of input from the array in and write it to
135     * the out array.
136     *
137     * @param in the array containing the input data.
138     * @param inOff offset into the in array the data starts at.
139     * @param out the array the output data will be copied into.
140     * @param outOff the offset into the out array the output will start at.
141     * @exception DataLengthException if there isn't enough data in in, or
142     * space in out.
143     * @exception IllegalStateException if the cipher isn't initialised.
144     * @return the number of bytes processed and produced.
145     */
146    public int processBlock(
147        byte[]      in,
148        int         inOff,
149        byte[]      out,
150        int         outOff)
151        throws DataLengthException, IllegalStateException
152    {
153        if ((inOff + blockSize) > in.length)
154        {
155            throw new DataLengthException("input buffer too short");
156        }
157
158        if ((outOff + blockSize) > out.length)
159        {
160            throw new DataLengthException("output buffer too short");
161        }
162
163        if (firstStep)
164        {
165            firstStep = false;
166            cipher.processBlock(ofbV, 0, ofbOutV, 0);
167            N3 = bytesToint(ofbOutV, 0);
168            N4 = bytesToint(ofbOutV, 4);
169        }
170        N3 += C2;
171        N4 += C1;
172        intTobytes(N3, ofbV, 0);
173        intTobytes(N4, ofbV, 4);
174
175        cipher.processBlock(ofbV, 0, ofbOutV, 0);
176
177        //
178        // XOR the ofbV with the plaintext producing the cipher text (and
179        // the next input block).
180        //
181        for (int i = 0; i < blockSize; i++)
182        {
183            out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]);
184        }
185
186        //
187        // change over the input block.
188        //
189        System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize);
190        System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize);
191
192        return blockSize;
193    }
194
195    /**
196     * reset the feedback vector back to the IV and reset the underlying
197     * cipher.
198     */
199    public void reset()
200    {
201        System.arraycopy(IV, 0, ofbV, 0, IV.length);
202
203        cipher.reset();
204    }
205
206    //array of bytes to type int
207    private int bytesToint(
208        byte[]  in,
209        int     inOff)
210    {
211        return  ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) +
212                ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff);
213    }
214
215    //int to array of bytes
216    private void intTobytes(
217            int     num,
218            byte[]  out,
219            int     outOff)
220    {
221            out[outOff + 3] = (byte)(num >>> 24);
222            out[outOff + 2] = (byte)(num >>> 16);
223            out[outOff + 1] = (byte)(num >>> 8);
224            out[outOff] =     (byte)num;
225    }
226}
227