Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: renderkits/util/ByteArrayGuard.java


1   /*
2    * $Id: ByteArrayGuard.java,v 1.3 2005/12/14 22:27:44 rlubke Exp $
3    */
4   
5   /*
6    * The contents of this file are subject to the terms
7    * of the Common Development and Distribution License
8    * (the License). You may not use this file except in
9    * compliance with the License.
10   * 
11   * You can obtain a copy of the License at
12   * https://javaserverfaces.dev.java.net/CDDL.html or
13   * legal/CDDLv1.0.txt. 
14   * See the License for the specific language governing
15   * permission and limitations under the License.
16   * 
17   * When distributing Covered Code, include this CDDL
18   * Header Notice in each file and include the License file
19   * at legal/CDDLv1.0.txt.    
20   * If applicable, add the following below the CDDL Header,
21   * with the fields enclosed by brackets [] replaced by
22   * your own identifying information:
23   * "Portions Copyrighted [year] [name of copyright owner]"
24   * 
25   * [Name of File] [ver.__] [Date]
26   * 
27   * Copyright 2005 Sun Microsystems Inc. All Rights Reserved
28   */
29  
30  package renderkits.util;
31  
32  import javax.crypto.Cipher;
33  import javax.crypto.Mac;
34  import javax.crypto.SecretKeyFactory;
35  import javax.crypto.spec.DESedeKeySpec;
36  import javax.crypto.spec.IvParameterSpec;
37  import javax.crypto.spec.SecretKeySpec;
38  import javax.faces.context.FacesContext;
39  
40  import java.security.Key;
41  import java.security.MessageDigest;
42  import java.security.SecureRandom;
43  import java.util.Arrays;
44  import java.util.Map;
45  import java.util.logging.Level;
46  import java.util.logging.Logger;
47  
48  /**
49   * This utility class provides services to encrypt or decrypt a byte array.
50   * The algorithm used to encrypt byte array is 3DES with CBC
51   * The algorithm used to create the message authentication code (MAC) is SHA1
52   * <p/>
53   * Original author Inderjeet Singh, J2EE Blue Prints Team. Modified to suit JSF
54   * needs.
55   */
56  public class ByteArrayGuard {
57      public static final int DEFAULT_KEY_LENGTH = 24;
58      public static final int DEFAULT_MAC_LENGTH = 20;
59      public static final int DEFAULT_IV_LENGTH = 8;
60  
61      public static final String SESSION_KEY_FOR_PASSWORD =
62            "com.sun.faces.clientside-state.password-key";
63      public static final int DEFAULT_PASSWORD_LENGTH = 24;
64  
65      // Log instance for this class
66      private static Logger logger;
67  
68      static {
69          logger = Util.getLogger(Util.FACES_LOGGER);
70      }
71  
72      /**
73       * @param ps the password strategy to create password for encryption and decryption
74       *           uses default values for the length of the encryption key, MAC key, and
75       *           the initialization vector
76       *
77       * @see DEFAULT_KEY_LENGTH
78       * @see DEFAULT_MAC_LENGTH
79       * @see DEFAULT_IV_LENGTH
80       */
81      public ByteArrayGuard() {
82          this(DEFAULT_KEY_LENGTH, DEFAULT_MAC_LENGTH, DEFAULT_IV_LENGTH);
83      }
84  
85      /**
86       * @param keyLength the length of the key used for encryption
87       * @param macLength the length of the message authentication used
88       * @param ivLength  length of the initialization vector used by the block cipher
89       * @param
90       */
91      public ByteArrayGuard(int keyLength, int macLength, int ivLength) {
92          this.keyLength = keyLength;
93          this.macLength = macLength;
94          this.ivLength = ivLength;
95      }
96  
97      /**
98       * Encrypts the specified plaindata using the specified password. It also
99       * stores the MAC and the IV in the output. The 20-byte MAC is stored
100      * first, followed by the 8-byte IV, followed by the encrypted
101      * contents of the file.
102      *
103      * @param context   FacesContext for this request
104      * @param plaindata The plain text that needs to be encrypted
105      *
106      * @return The encrypted contents
107      */
108     public byte[] encrypt(FacesContext context, byte[] plaindata) {
109         try {
110             // generate a key that can be used for encryption from the 
111             // supplied password
112             byte[] rawKey =
113                   convertPasswordToKey(getPasswordToSecureState(context));
114             // choose block encryption algorithm
115             Cipher cipher = getBlockCipherForEncryption(rawKey);
116             // encrypt the plaintext
117             byte[] encdata = cipher.doFinal(plaindata);
118             // choose mac algorithm
119             Mac mac = getMac(rawKey);
120             // generate MAC for the initialization vector of the cipher
121             byte[] iv = cipher.getIV();
122             mac.update(iv);
123             // generate MAC for the encrypted data
124             mac.update(encdata);
125             // generate MAC
126             byte[] macBytes = mac.doFinal();
127 
128             // concat byte arrays for MAC, IV, and encrypted data
129             // Note that the order is important here. MAC and IV are
130             // of fixed length and need to appear before the encrypted data
131             // for easy extraction while decrypting.
132             byte[] tmp = concatBytes(macBytes, iv);
133             byte[] securedata = concatBytes(tmp, encdata);
134             return securedata;
135         } catch (Exception e) {
136             if (logger.isLoggable(Level.SEVERE)) {
137                 logger.log(Level.SEVERE, e.getMessage(), e.getCause());
138             }
139             throw new RuntimeException(e);
140         }
141     }
142 
143     /**
144      * Decrypts the specified byte array using the specified password, and
145      * generates an inputstream from it. The file must be encrypted by the
146      * above method for encryption. The method also verifies the MAC. It
147      * uses the IV present in the file for decryption.
148      *
149      * @param context    Faces Context for this request
150      * @param securedata The encrypted data (including mac and initialization
151      *                   vector) that needs to be decrypted
152      *
153      * @return A byte array containing the decrypted contents
154      */
155     public byte[] decrypt(FacesContext context, byte[] securedata) {
156         try {
157             // Extract MAC
158             byte[] macBytes = new byte[macLength];
159             System.arraycopy(securedata, 0, macBytes, 0, macBytes.length);
160             // Extract initialization vector used for encryption
161             byte[] iv = new byte[ivLength];
162             System.arraycopy(securedata, macBytes.length, iv, 0, iv.length);
163 
164             // Extract encrypted data
165             byte[] encdata =
166                   new byte[securedata.length - macBytes.length - iv.length];
167             System.arraycopy(securedata,
168                              macBytes.length + iv.length,
169                              encdata,
170                              0,
171                              encdata.length);
172 
173             // verify MAC by regenerating it and comparing it with the received value
174             byte[] rawKey =
175                   convertPasswordToKey(getPasswordToSecureState(context));
176             Mac mac = getMac(rawKey);
177             mac.update(iv);
178             mac.update(encdata);
179             byte[] macBytesCalculated = mac.doFinal();
180             if (Arrays.equals(macBytes, macBytesCalculated)) {
181                 // decrypt data only if the MAC was valid
182                 Cipher cipher = getBlockCipherForDecryption(rawKey, iv);
183                 byte[] plaindata = cipher.doFinal(encdata);
184                 return plaindata;
185             } else {
186                 if (logger.isLoggable(Level.WARNING)) {
187                     // PENDING (visvan) localize
188                     logger.warning("ERROR: MAC did not verify!");
189                 }
190                 return null;
191             }
192         } catch (Exception e) {
193             if (logger.isLoggable(Level.SEVERE)) {
194                 logger.log(Level.SEVERE, e.getMessage(), e.getCause());
195             }
196             throw new RuntimeException(e);
197         }
198     }
199 
200     /**
201      * This method provides a password to be used for encryption/decryption of
202      * client-side state.
203      */
204     private String getPasswordToSecureState(FacesContext context) {
205         Map sessionMap = context.getExternalContext().getSessionMap();
206         if (sessionMap == null) {
207             // Setting it to an arbitrary value. As long as the same value is used
208             // by both serializer and deserializer, the encryption will work. 
209             // However, the encryption will be useless since this arbitrary 
210             // value can be guessed by an attacker.
211             // PENDING (visvan) localize
212             if (logger.isLoggable(Level.WARNING)) {
213                 logger.warning(
214                       "Key to retrieve password from session could not " +
215                       "be found. Using default value. This will enable " +
216                       "the encryption and decryption to work, but the " +
217                       "client-side state saving method is NO longer secure.");
218             }
219             password = "easy-to-guess-password";
220         } else {
221             password = (String) sessionMap.get(SESSION_KEY_FOR_PASSWORD);
222             if (password == null) {
223                 password = (String)
224                       ByteArrayGuard.getRandomString(DEFAULT_PASSWORD_LENGTH);
225                 sessionMap.put(SESSION_KEY_FOR_PASSWORD, password);
226             }
227         }
228         return password;
229     }
230 
231     /**
232      * This method converts the specified password into a key in a
233      * deterministic manner. The key is then usable for creating ciphers
234      * and MACs.
235      *
236      * @return a byte array containing a key based on the specified
237      *         password. The length of the returned byte array is KEY_LENGTH.
238      */
239     private byte[] convertPasswordToKey(byte[] password) {
240         try {
241             MessageDigest md = MessageDigest.getInstance("SHA");
242             byte[] seed = md.digest(password);
243 
244             SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
245             random.setSeed(seed);
246 
247             byte[] rawkey = new byte[keyLength];
248             random.nextBytes(rawkey);
249             return rawkey;
250         } catch (Exception e) {
251             throw new RuntimeException(e);
252         }
253     }
254 
255     /**
256      * A convenience alias to the above method which takes a string as
257      * the password.
258      */
259     private byte[] convertPasswordToKey(String password) {
260         return convertPasswordToKey(password.getBytes());
261     }
262 
263     /**
264      * @param rawKey must be 24 bytes in length.
265      *
266      * @return a 3DES block cipher to be used for encryption based on the
267      *         specified key
268      */
269     private Cipher getBlockCipherForEncryption(byte[] rawKey) {
270         try {
271             SecretKeyFactory keygen = SecretKeyFactory.getInstance("DESede");
272             DESedeKeySpec keyspec = new DESedeKeySpec(rawKey);
273             Key key = keygen.generateSecret(keyspec);
274             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
275             byte[] iv = new byte[ivLength];
276             getPRNG().nextBytes(iv);
277             IvParameterSpec ivspec = new IvParameterSpec(iv);
278             cipher.init(Cipher.ENCRYPT_MODE, key, ivspec, getPRNG());
279             return cipher;
280         } catch (Exception e) {
281             throw new RuntimeException(e);
282         }
283     }
284 
285     private static Cipher getBlockCipherForDecryption(byte[] rawKey, byte[]
286           iv) {
287         try {
288             SecretKeyFactory keygen = SecretKeyFactory.getInstance("DESede");
289             DESedeKeySpec keyspec = new DESedeKeySpec(rawKey);
290             Key key = keygen.generateSecret(keyspec);
291             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
292             IvParameterSpec ivspec = new IvParameterSpec(iv);
293             cipher.init(Cipher.DECRYPT_MODE, key, ivspec, getPRNG());
294             return cipher;
295         } catch (Exception e) {
296             throw new RuntimeException(e);
297         }
298     }
299 
300     private Mac getMac(byte[] rawKey) {
301         try {
302             Mac mac = Mac.getInstance("HmacSHA1");
303             SecretKeySpec key =
304                   new SecretKeySpec(rawKey, 0, macLength, "HmacSHA1");
305             mac.init(key);
306             return mac;
307         } catch (Exception e) {
308             throw new RuntimeException(e);
309         }
310     }
311 
312     /**
313      * Generates a cryptographically random string
314      *
315      * @param size the desired length of the string
316      */
317     static String getRandomString(int size) {
318         byte[] data = new byte[size];
319         getPRNG().nextBytes(data);
320         return new String(data);
321     }
322 
323     private static int getRandomInt() {
324         byte[] data = new byte[4];
325         getPRNG().nextBytes(data);
326         return data[0] + data[1] * 256 + data[2] * 65536 + data[3] * 16777216;
327     }
328 
329     private static SecureRandom getPRNG() {
330         try {
331             if (prng == null) {
332                 prng = SecureRandom.getInstance("SHA1PRNG");
333             }
334             return prng;
335         } catch (Exception e) {
336             throw new RuntimeException(e);
337         }
338     }
339 
340     private static String getHexString(byte[] b) {
341         StringBuffer buf = new StringBuffer(b.length);
342         for (int i = 0; i < b.length; ++i) {
343             byte2hex(b[i], buf);
344         }
345         return buf.toString();
346     }
347 
348     /**
349      * This method concatenates two byte arrays
350      *
351      * @param array1 first byte array to be concatenated
352      * @param array2 second byte array to be concatenated
353      *
354      * @return a byte array of array1||array2
355      */
356     private static byte[] concatBytes(byte[] array1, byte[] array2) {
357         byte[] cBytes = new byte[array1.length + array2.length];
358         try {
359             System.arraycopy(array1, 0, cBytes, 0, array1.length);
360             System.arraycopy(array2, 0, cBytes, array1.length, array2.length);
361         } catch (Exception e) {
362             throw new RuntimeException(e);
363         }
364         return cBytes;
365     }
366 
367     /** Converts a byte to hex digit and writes to the supplied buffer */
368     private static void byte2hex(byte b, StringBuffer buf) {
369         char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
370                            'A', 'B', 'C', 'D', 'E', 'F'};
371         int high = ((b & 0xf0) >> 4);
372         int low = (b & 0x0f);
373         buf.append(hexChars[high]);
374         buf.append(hexChars[low]);
375     }
376 
377     private int keyLength;
378     private int macLength;
379     private int ivLength;
380     private String password = null;
381     private static SecureRandom prng = null;
382 }