001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.geronimo.util.crypto.modes;
019    
020    import org.apache.geronimo.util.crypto.BlockCipher;
021    import org.apache.geronimo.util.crypto.CipherParameters;
022    import org.apache.geronimo.util.crypto.DataLengthException;
023    import org.apache.geronimo.util.crypto.params.ParametersWithIV;
024    
025    /**
026     * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
027     */
028    public class CBCBlockCipher
029        implements BlockCipher
030    {
031        private byte[]          IV;
032        private byte[]          cbcV;
033        private byte[]          cbcNextV;
034    
035        private int             blockSize;
036        private BlockCipher     cipher = null;
037        private boolean         encrypting;
038    
039        /**
040         * Basic constructor.
041         *
042         * @param cipher the block cipher to be used as the basis of chaining.
043         */
044        public CBCBlockCipher(
045            BlockCipher cipher)
046        {
047            this.cipher = cipher;
048            this.blockSize = cipher.getBlockSize();
049    
050            this.IV = new byte[blockSize];
051            this.cbcV = new byte[blockSize];
052            this.cbcNextV = new byte[blockSize];
053        }
054    
055        /**
056         * return the underlying block cipher that we are wrapping.
057         *
058         * @return the underlying block cipher that we are wrapping.
059         */
060        public BlockCipher getUnderlyingCipher()
061        {
062            return cipher;
063        }
064    
065        /**
066         * Initialise the cipher and, possibly, the initialisation vector (IV).
067         * If an IV isn't passed as part of the parameter, the IV will be all zeros.
068         *
069         * @param encrypting if true the cipher is initialised for
070         *  encryption, if false for decryption.
071         * @param params the key and other data required by the cipher.
072         * @exception IllegalArgumentException if the params argument is
073         * inappropriate.
074         */
075        public void init(
076            boolean             encrypting,
077            CipherParameters    params)
078            throws IllegalArgumentException
079        {
080            this.encrypting = encrypting;
081            
082            if (params instanceof ParametersWithIV)
083            {
084                    ParametersWithIV ivParam = (ParametersWithIV)params;
085                    byte[]      iv = ivParam.getIV();
086    
087                    if (iv.length != blockSize)
088                    {
089                        throw new IllegalArgumentException("initialisation vector must be the same length as block size");
090                    }
091    
092                    System.arraycopy(iv, 0, IV, 0, iv.length);
093    
094                    reset();
095    
096                    cipher.init(encrypting, ivParam.getParameters());
097            }
098            else
099            {
100                    reset();
101    
102                    cipher.init(encrypting, params);
103            }
104        }
105    
106        /**
107         * return the algorithm name and mode.
108         *
109         * @return the name of the underlying algorithm followed by "/CBC".
110         */
111        public String getAlgorithmName()
112        {
113            return cipher.getAlgorithmName() + "/CBC";
114        }
115    
116        /**
117         * return the block size of the underlying cipher.
118         *
119         * @return the block size of the underlying cipher.
120         */
121        public int getBlockSize()
122        {
123            return cipher.getBlockSize();
124        }
125    
126        /**
127         * Process one block of input from the array in and write it to
128         * the out array.
129         *
130         * @param in the array containing the input data.
131         * @param inOff offset into the in array the data starts at.
132         * @param out the array the output data will be copied into.
133         * @param outOff the offset into the out array the output will start at.
134         * @exception DataLengthException if there isn't enough data in in, or
135         * space in out.
136         * @exception IllegalStateException if the cipher isn't initialised.
137         * @return the number of bytes processed and produced.
138         */
139        public int processBlock(
140            byte[]      in,
141            int         inOff,
142            byte[]      out,
143            int         outOff)
144            throws DataLengthException, IllegalStateException
145        {
146            return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
147        }
148    
149        /**
150         * reset the chaining vector back to the IV and reset the underlying
151         * cipher.
152         */
153        public void reset()
154        {
155            System.arraycopy(IV, 0, cbcV, 0, IV.length);
156    
157            cipher.reset();
158        }
159    
160        /**
161         * Do the appropriate chaining step for CBC mode encryption.
162         *
163         * @param in the array containing the data to be encrypted.
164         * @param inOff offset into the in array the data starts at.
165         * @param out the array the encrypted data will be copied into.
166         * @param outOff the offset into the out array the output will start at.
167         * @exception DataLengthException if there isn't enough data in in, or
168         * space in out.
169         * @exception IllegalStateException if the cipher isn't initialised.
170         * @return the number of bytes processed and produced.
171         */
172        private int encryptBlock(
173            byte[]      in,
174            int         inOff,
175            byte[]      out,
176            int         outOff)
177            throws DataLengthException, IllegalStateException
178        {
179            if ((inOff + blockSize) > in.length)
180            {
181                throw new DataLengthException("input buffer too short");
182            }
183    
184            /*
185             * XOR the cbcV and the input,
186             * then encrypt the cbcV
187             */
188            for (int i = 0; i < blockSize; i++)
189            {
190                cbcV[i] ^= in[inOff + i];
191            }
192    
193            int length = cipher.processBlock(cbcV, 0, out, outOff);
194    
195            /*
196             * copy ciphertext to cbcV
197             */
198            System.arraycopy(out, outOff, cbcV, 0, cbcV.length);
199    
200            return length;
201        }
202    
203        /**
204         * Do the appropriate chaining step for CBC mode decryption.
205         *
206         * @param in the array containing the data to be decrypted.
207         * @param inOff offset into the in array the data starts at.
208         * @param out the array the decrypted data will be copied into.
209         * @param outOff the offset into the out array the output will start at.
210         * @exception DataLengthException if there isn't enough data in in, or
211         * space in out.
212         * @exception IllegalStateException if the cipher isn't initialised.
213         * @return the number of bytes processed and produced.
214         */
215        private int decryptBlock(
216            byte[]      in,
217            int         inOff,
218            byte[]      out,
219            int         outOff)
220            throws DataLengthException, IllegalStateException
221        {
222            if ((inOff + blockSize) > in.length)
223            {
224                throw new DataLengthException("input buffer too short");
225            }
226    
227            System.arraycopy(in, inOff, cbcNextV, 0, blockSize);
228    
229            int length = cipher.processBlock(in, inOff, out, outOff);
230    
231            /*
232             * XOR the cbcV and the output
233             */
234            for (int i = 0; i < blockSize; i++)
235            {
236                out[outOff + i] ^= cbcV[i];
237            }
238    
239            /*
240             * swap the back up buffer into next position
241             */
242            byte[]  tmp;
243    
244            tmp = cbcV;
245            cbcV = cbcNextV;
246            cbcNextV = tmp;
247    
248            return length;
249        }
250    }