KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > j2ee > blueprints > clientstate > ByteArrayGuard


1 /* Copyright 2004 Sun Microsystems, Inc. All rights reserved. You may not modify, use, reproduce, or distribute this software except in compliance with the terms of the License at:
2  http://developer.sun.com/berkeley_license.html
3  $Id: ByteArrayGuard.java,v 1.1 2005/05/17 05:23:50 gmurray71 Exp $ */

4
5 package com.sun.j2ee.blueprints.clientstate;
6
7 import java.io.ByteArrayInputStream JavaDoc;
8 import java.security.Key JavaDoc;
9 import java.security.MessageDigest JavaDoc;
10 import java.security.SecureRandom JavaDoc;
11 import java.util.Arrays JavaDoc;
12 import javax.crypto.Cipher;
13 import javax.crypto.Mac;
14 import javax.crypto.SecretKeyFactory;
15 import javax.crypto.spec.DESedeKeySpec;
16 import javax.crypto.spec.IvParameterSpec;
17 import javax.crypto.spec.SecretKeySpec;
18
19
20
21 /**
22  * This utility class provides services to encrypt or decrypt a byte array.
23  * The algorithm used to encrypt byte array is 3DES with CBC
24  * The algorithm used to create the message authentication code (MAC) is SHA1
25  *
26  * @author Inderjeet Singh
27  */

28 public class ByteArrayGuard {
29     public static final int DEFAULT_KEY_LENGTH = 24;
30     public static final int DEFAULT_MAC_LENGTH = 20;
31     public static final int DEFAULT_IV_LENGTH = 8;
32     
33     /**
34      * @param ps the password strategy to create password for encryption and decryption
35      * uses default values for the length of the encryption key, MAC key, and the initialization vector
36      * @see DEFAULT_KEY_LENGTH
37      * @see DEFAULT_MAC_LENGTH
38      * @see DEFAULT_IV_LENGTH
39      */

40     public ByteArrayGuard(PasswordStrategy ps) {
41         this(ps, DEFAULT_KEY_LENGTH, DEFAULT_MAC_LENGTH, DEFAULT_IV_LENGTH);
42     }
43     
44     /**
45      * @param ps the password strategy to create password for encryption and decryption
46      * @param keyLength the length of the key used for encryption
47      * @param macLength the length of the message authentication used
48      * @param ivLength length of the initialization vector used by the block cipher
49      * @param
50      */

51     public ByteArrayGuard(PasswordStrategy ps, int keyLength, int macLength, int ivLength) {
52         this.passwordStrategy = ps;
53         this.keyLength = keyLength;
54         this.macLength = macLength;
55         this.ivLength = ivLength;
56         if (ps == null) {
57             // Setting it to an arbitrary value. As long as the same value is used
58
// by both serializer and deserializer, the encryption will work. However,
59
// the encryption will be useless since this arbitrary value can be guessed
60
// by an attacker.
61
System.err.println("The password strategy to encrypt the client-side state is specified null. "
62                     + "We will set it to a default value. This will enable the encryption and decryption to work, but the client-side state is NO longer secure.");
63             password = "easy-to-guess-password";
64         }
65     }
66     
67     /**
68      * Encrypts the specified plaindata using the specified password. It also
69      * stores the MAC and the IV in the output. The 20-byte MAC is stored
70      * first, followed by the 8-byte IV, followed by the encrypted
71      * contents of the file.
72      * @param plaindata The plain text that needs to be encrypted
73      * @return The encrypted contents
74      */

75     public byte[] encrypt(byte[] plaindata) {
76         try {
77             // generate a key that can be used for encryption from the supplied password
78
byte[] rawKey = convertPasswordToKey(getPasswordToSecureState());
79             // choose block encryption algorithm
80
Cipher cipher = getBlockCipherForEncryption(rawKey);
81             // encrypt the plaintext
82
byte[] encdata = cipher.doFinal(plaindata);
83             // choose mac algorithm
84
Mac mac = getMac(rawKey);
85             // generate MAC for the initialization vector of the cipher
86
byte[] iv = cipher.getIV();
87             mac.update(iv);
88             // generate MAC for the encrypted data
89
mac.update(encdata);
90             // generate MAC
91
byte[] macBytes = mac.doFinal();
92             //System.out.println(macBytes.length + " bytes (in hex): " + getHexString(macBytes));
93
//System.out.println(iv.length + " bytes (in hex): " + getHexString(iv));
94

95             // concat byte arrays for MAC, IV, and encrypted data
96
// Note that the order is important here. MAC and IV are
97
// of fixed length and need to appear before the encrypted data
98
// for easy extraction while decrypting.
99
byte[] tmp = concatBytes(macBytes, iv);
100             byte[] securedata = concatBytes(tmp, encdata);
101             return securedata;
102         } catch (Exception JavaDoc e) {
103             throw new RuntimeException JavaDoc(e);
104         }
105     }
106     
107     /**
108      * Decrypts the specified byte array using the specified password, and
109      * generates an inputstream from it. The file must be encrypted by the
110      * above method for encryption. The method also verifies the MAC. It
111      * uses the IV present in the file for decryption.
112      * @param securedata The encrypted data (including mac and initialization vector) that needs to be decrypted
113      * @return A byte array containing the decrypted contents
114      */

115     public byte[] decrypt(byte[] securedata) {
116         try {
117             // Extract MAC
118
byte[] macBytes = new byte[macLength];
119             System.arraycopy(securedata, 0, macBytes, 0, macBytes.length);
120             // Extract initialization vector used for encryption
121
byte[] iv = new byte[ivLength];
122             System.arraycopy(securedata, macBytes.length, iv, 0, iv.length);
123             //System.out.println("Read " + macBytes.length + " bytes (in hex): " + getHexString(macBytes));
124
//System.out.println("Read " + iv.length + " bytes (in hex): " + getHexString(iv));
125

126             // Extract encrypted data
127
byte[] encdata = new byte[securedata.length - macBytes.length - iv.length];
128             System.arraycopy(securedata, macBytes.length + iv.length, encdata, 0, encdata.length);
129             
130             // verify MAC by regenerating it and comparing it with the received value
131
byte[] rawKey = convertPasswordToKey(getPasswordToSecureState());
132             Mac mac = getMac(rawKey);
133             mac.update(iv);
134             mac.update(encdata);
135             byte[] macBytesCalculated = mac.doFinal();
136             if (Arrays.equals(macBytes, macBytesCalculated)) {
137                 // decrypt data only if the MAC was valid
138
// System.out.println("Valid MAC found!");
139
Cipher cipher = getBlockCipherForDecryption(rawKey, iv);
140                 byte[] plaindata = cipher.doFinal(encdata);
141                 return plaindata;
142             } else {
143                 System.err.println("ERROR: MAC did not verify!");
144                 return null;
145             }
146         } catch (Exception JavaDoc e) {
147             return null;
148         }
149     }
150     
151     /** This method provides a password to be used for encryption/decryption of client-side state.
152      * The password is generated using the specified password strategy. */

153     private String JavaDoc getPasswordToSecureState() {
154         if (password == null) {
155             password = passwordStrategy.getPassword();
156         }
157         return password;
158     }
159     /**
160      * This method converts the specified password into a key in a
161      * deterministic manner. The key is then usable for creating ciphers
162      * and MACs.
163      * @return a byte array containing a key based on the specified
164      * password. The length of the returned byte array is KEY_LENGTH.
165      */

166     private byte[] convertPasswordToKey(byte[] password) {
167         try {
168             MessageDigest JavaDoc md = MessageDigest.getInstance("SHA");
169             byte[] seed = md.digest(password);
170             
171             SecureRandom JavaDoc random = SecureRandom.getInstance("SHA1PRNG");
172             random.setSeed(seed);
173             
174             byte[] rawkey = new byte[keyLength];
175             random.nextBytes(rawkey);
176             return rawkey;
177         } catch (Exception JavaDoc e) {
178             throw new RuntimeException JavaDoc(e);
179         }
180     }
181     
182     /** A convenience alias to the above method which takes a string as
183      * the password. */

184     private byte[] convertPasswordToKey(String JavaDoc password) {
185         return convertPasswordToKey(password.getBytes());
186     }
187     
188     /** @return a 3DES block cipher to be used for encryption based on the
189      * specified key
190      * @param rawKey must be 24 bytes in length. */

191     private Cipher getBlockCipherForEncryption(byte[] rawKey) {
192         try {
193             SecretKeyFactory keygen = SecretKeyFactory.getInstance("DESede");
194             DESedeKeySpec keyspec = new DESedeKeySpec(rawKey);
195             Key JavaDoc key = keygen.generateSecret(keyspec);
196             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
197             byte[] iv = new byte[ivLength];
198             getPRNG().nextBytes(iv);
199             IvParameterSpec ivspec = new IvParameterSpec(iv);
200             cipher.init(Cipher.ENCRYPT_MODE, key, ivspec, getPRNG());
201             return cipher;
202         } catch (Exception JavaDoc e) {
203             throw new RuntimeException JavaDoc(e);
204         }
205     }
206     
207     private static Cipher getBlockCipherForDecryption(byte[] rawKey, byte[]
208             iv) {
209         //assert rawKey.length == keyLength;
210
//assert iv.length == ivLength;
211
try {
212             SecretKeyFactory keygen = SecretKeyFactory.getInstance("DESede");
213             DESedeKeySpec keyspec = new DESedeKeySpec(rawKey);
214             Key JavaDoc key = keygen.generateSecret(keyspec);
215             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
216             IvParameterSpec ivspec = new IvParameterSpec(iv);
217             cipher.init(Cipher.DECRYPT_MODE, key, ivspec, getPRNG());
218             return cipher;
219         } catch (Exception JavaDoc e) {
220             throw new RuntimeException JavaDoc(e);
221         }
222     }
223     
224     private Mac getMac(byte[] rawKey) {
225         //assert rawKey.length >= 20;
226
try {
227             Mac mac = Mac.getInstance("HmacSHA1");
228             SecretKeySpec key = new SecretKeySpec(rawKey, 0, macLength, "HmacSHA1");
229             mac.init(key);
230             return mac;
231         } catch (Exception JavaDoc e) {
232             throw new RuntimeException JavaDoc(e);
233         }
234     }
235     
236     /**
237      * Generates a cryptographically random string
238      * @param size the desired length of the string
239      */

240     static String JavaDoc getRandomString(int size) {
241         byte[] data = new byte[size];
242         getPRNG().nextBytes(data);
243         return new String JavaDoc(data);
244     }
245     
246     private static int getRandomInt() {
247         byte[] data = new byte[4];
248         getPRNG().nextBytes(data);
249         return data[0] + data[1] * 256 + data[2] * 65536 + data[3] * 16777216;
250     }
251     
252     private static SecureRandom JavaDoc getPRNG() {
253         try {
254             if (prng == null) {
255                 prng = SecureRandom.getInstance("SHA1PRNG");
256             }
257             return prng;
258         } catch (Exception JavaDoc e) {
259             throw new RuntimeException JavaDoc(e);
260         }
261     }
262     
263     private static String JavaDoc getHexString(byte[] b) {
264         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(b.length);
265         for (int i = 0; i < b.length; ++i) {
266             byte2hex(b[i], buf);
267         }
268         return buf.toString();
269     }
270     
271     /**
272      * This method concatenates two byte arrays
273      * @return a byte array of array1||array2
274      * @param array1 first byte array to be concatenated
275      * @param array2 second byte array to be concatenated
276      */

277     private static byte[] concatBytes(byte[] array1, byte[] array2) {
278         byte[] cBytes = new byte[array1.length + array2.length];
279         try {
280             System.arraycopy(array1, 0, cBytes, 0, array1.length);
281             System.arraycopy(array2, 0, cBytes, array1.length, array2.length);
282         } catch(Exception JavaDoc e) {
283             System.out.println("Exception <concatBytes> " + e );
284         }
285         return cBytes;
286     }
287     
288     /**
289      * Converts a byte to hex digit and writes to the supplied buffer
290      */

291     private static void byte2hex(byte b, StringBuffer JavaDoc buf) {
292         char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'A', 'B', 'C', 'D', 'E', 'F' };
293         int high = ((b & 0xf0) >> 4);
294         int low = (b & 0x0f);
295         buf.append(hexChars[high]);
296         buf.append(hexChars[low]);
297     }
298     
299     private int keyLength;
300     private int macLength;
301     private int ivLength;
302     private String JavaDoc password = null;
303     private PasswordStrategy passwordStrategy;
304     private static SecureRandom JavaDoc prng = null;
305 }
306
Popular Tags