KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jcorporate > expresso > core > security > strongencryption > StringEncryption


1 /* ====================================================================
2  * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
3  *
4  * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  *
18  * 3. The end-user documentation included with the redistribution,
19  * if any, must include the following acknowledgment:
20  * "This product includes software developed by Jcorporate Ltd.
21  * (http://www.jcorporate.com/)."
22  * Alternately, this acknowledgment may appear in the software itself,
23  * if and wherever such third-party acknowledgments normally appear.
24  *
25  * 4. "Jcorporate" and product names such as "Expresso" must
26  * not be used to endorse or promote products derived from this
27  * software without prior written permission. For written permission,
28  * please contact info@jcorporate.com.
29  *
30  * 5. Products derived from this software may not be called "Expresso",
31  * or other Jcorporate product names; nor may "Expresso" or other
32  * Jcorporate product names appear in their name, without prior
33  * written permission of Jcorporate Ltd.
34  *
35  * 6. No product derived from this software may compete in the same
36  * market space, i.e. framework, without prior written permission
37  * of Jcorporate Ltd. For written permission, please contact
38  * partners@jcorporate.com.
39  *
40  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
41  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
46  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This software consists of voluntary contributions made by many
55  * individuals on behalf of the Jcorporate Ltd. Contributions back
56  * to the project(s) are encouraged when you make modifications.
57  * Please send them to support@jcorporate.com. For more information
58  * on Jcorporate Ltd. and its products, please see
59  * <http://www.jcorporate.com/>.
60  *
61  * Portions of this software are based upon other open source
62  * products and are subject to their respective licenses.
63  */

64
65 package com.jcorporate.expresso.core.security.strongencryption;
66
67 import com.jcorporate.expresso.core.misc.ByteArrayCounter;
68 import com.jcorporate.expresso.core.security.AbstractStringEncryption;
69 import com.jcorporate.expresso.kernel.exception.ChainedException;
70 import com.jcorporate.expresso.kernel.util.ClassLocator;
71 import org.apache.log4j.Logger;
72
73 import javax.crypto.BadPaddingException;
74 import javax.crypto.Cipher;
75 import javax.crypto.IllegalBlockSizeException;
76 import javax.crypto.KeyGenerator;
77 import javax.crypto.NoSuchPaddingException;
78 import javax.crypto.SecretKey;
79 import javax.crypto.ShortBufferException;
80 import javax.crypto.spec.IvParameterSpec;
81 import javax.crypto.spec.SecretKeySpec;
82 import java.io.UnsupportedEncodingException JavaDoc;
83 import java.security.InvalidAlgorithmParameterException JavaDoc;
84 import java.security.InvalidKeyException JavaDoc;
85 import java.security.NoSuchAlgorithmException JavaDoc;
86 import java.security.NoSuchProviderException JavaDoc;
87 import java.security.Provider JavaDoc;
88 import java.security.SecureRandom JavaDoc;
89 import java.security.Security JavaDoc;
90 import java.util.Hashtable JavaDoc;
91
92
93 /**
94  * <p>StringEncryption.java</p>
95  * <p/>
96  * <p>Copyright 2000, 2001 Jcorporate Ltd.</p>
97  * <p>This class provides basic string encryption. It'll provide the services of
98  * password whitening and automatic selection of encryption.</p>
99  * <p/>
100  * <p>Known Vulnerabilities. The actual whitened password remains in memory for
101  * performance sake. An attacker may find the actual password by looking at swap
102  * files looking for Base64 encoded strings. (Not too hard to grep out) but it
103  * requires an attacker to gain access to the swap partition of the server. Do not
104  * use this class for a personal encryption program.</p>
105  * <br />
106  * <p/>
107  * <p>Byte Array Format Information:</p>
108  * <p>An encrypted string has the following format:</p>
109  * <p/>
110  * <p>Byte 0: File Version Number(whole number only)</p>
111  * <p>Bytes 1-6: 3 character desgination for the encryption mode used. UTF-16 BE</p>
112  * <p>Bytes 7-14/22: The 8/16 byte random input vector to the encrypted system.</p>
113  * <p>Bytes 15+/23++ : The Actual Encrypted Data</p>
114  *
115  * @author Michael Rimov
116  */

117 public class StringEncryption
118         extends AbstractStringEncryption {
119     static protected ByteArrayCounter ivCounter64 = new ByteArrayCounter(8);
120     static protected ByteArrayCounter ivCounter128 = new ByteArrayCounter(16);
121
122     static final private Logger log = Logger.getLogger(StringEncryption.class);
123     /**
124      * An array of prepared secret keys depending on the mode required.
125      */

126     private Hashtable JavaDoc keyMap;
127
128     /**
129      * a way to quickly get the mode we're going to use.
130      */

131     private Hashtable JavaDoc modeMap;
132
133     /**
134      * An array of modes used here.
135      */

136     static final private String JavaDoc[] modes = {
137         "Rijndael/OFB/PKCS5Padding", "Blowfish/CFB/PKCS5Padding",
138         "Twofish/CFB/PKCS5Padding", "RC6/CBC/PKCS5Padding"
139     };
140
141     static final private String JavaDoc[] methodKeys = {"AES", "BLO", "TWO", "RC6"};
142
143     private Hashtable JavaDoc ivMap;
144
145     private static final String JavaDoc PROVIDER_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider";
146
147     // private Provider theProvider = new cryptix.jce.provider.CryptixCrypto();
148
private Provider JavaDoc theProvider; // = new org.bouncycastle.jce.provider.BouncyCastleProvider();
149

150     boolean providerAlreadyInstalled = false;
151
152     /**
153      * class for encapsulating encryption.
154      * constructor will throw if JCE jar is not found; see Ant target 'get-crypto'
155      * for easy means to download appropriate crypto library. it is under
156      * export restriction, so it cannot be added to the expresso library as
157      * publicly distributed
158      */

159     public StringEncryption() {
160
161         //We test if the JDK already has it installed, if it is then we
162
//don't try to load it ourselves because some security systems
163
//may not allow us install our own provider
164
if (log.isDebugEnabled()) {
165             log.debug("Initializing Strong String Encryption");
166             log.debug("System Provider Dump -----------------------");
167             Provider JavaDoc allProviders[] = Security.getProviders();
168             for (int i = 0; i < allProviders.length; i++) {
169                 log.debug(allProviders[i].toString());
170             }
171             log.debug("--------------------------------------------");
172         }
173
174         theProvider = Security.getProvider(PROVIDER_NAME);
175         if (theProvider != null) {
176             providerAlreadyInstalled = true;
177         } else {
178             try {
179                 theProvider = (Provider JavaDoc) ClassLocator.loadClass(PROVIDER_NAME).newInstance();
180             } catch (Exception JavaDoc ex) {
181                 log.error("Error loading bouncycastle crypto", ex);
182                 throw new RuntimeException JavaDoc("constructor will throw if JCE jar is" +
183                         " not found or if the security manager doesn't allow a new provider;" +
184                         " see Ant target 'get-crypto' for easy means to download the appropriate" +
185                         " crypto library from http://www.bouncycastle.org/download . " +
186                         " It is under export restriction, so it cannot be " +
187                         "added to the expresso library as publicly distributed.", ex);
188             }
189         }
190         try {
191             jbInit();
192         } catch (Exception JavaDoc ex) {
193             ex.printStackTrace();
194         }
195     } /* StringEncryption() */
196
197     public void init() throws ChainedException {
198         super.init();
199         keyMap = new Hashtable JavaDoc();
200         modeMap = new Hashtable JavaDoc();
201         ivMap = new Hashtable JavaDoc();
202         byte[] passKey = this.getPreparedPassKey();
203
204         //Install the Cryptographic Provider if not set up properly
205
if (!providerAlreadyInstalled) {
206             try {
207                 Security.addProvider(theProvider);
208             } catch (SecurityException JavaDoc e) {
209                 throw new ChainedException("Error Installing Bouncy Castle Provider",
210                         e);
211             }
212         }
213
214         SecureRandom JavaDoc random = null;
215         try {
216             random = SecureRandom.getInstance("SHA1PRNG");
217         } catch (NoSuchAlgorithmException JavaDoc ex1) {
218             throw new ChainedException("Error initiating sha1-random", ex1);
219         }
220
221         for (int i = 0; i < modes.length; i++) {
222             try {
223                 Cipher c = Cipher.getInstance(modes[i]);
224                 String JavaDoc cipherAlgorithm = modes[i].substring(0, modes[i].indexOf("/"));
225
226                 //
227
//We have to independantly figure out the maximum key
228
//available for each cipher in this JVM. [In case export
229
//regulations are intact]
230
//So we have the KeyGenerator generate an encoded key and
231
//we adjust down the keysize appropriately for what
232
//it comes up with size-wise.
233
//
234
KeyGenerator kg = KeyGenerator.getInstance(cipherAlgorithm);
235                 kg.init(random);
236                 SecretKey key = kg.generateKey();
237                 byte encoded[] = key.getEncoded();
238                 byte myKey[] = passKey;
239                 if (passKey.length > encoded.length) {
240                     myKey = new byte[encoded.length];
241
242                     //Can't use system.arrayCopy because of using primative
243
//type.
244
for (int j = 0; j < myKey.length; j++) {
245                         myKey[j] = passKey[j];
246                     }
247                 }
248                 keyMap.put(methodKeys[i],
249                         new SecretKeySpec(myKey, cipherAlgorithm));
250
251
252                 modeMap.put(methodKeys[i], modes[i]);
253
254
255                 int counterSize = c.getBlockSize();
256                 switch (counterSize) {
257                     case 16:
258                         ivMap.put(methodKeys[i], ivCounter128);
259                         break;
260                     case 8:
261                         ivMap.put(methodKeys[i], ivCounter64);
262                         break;
263                     default:
264                         throw new IllegalStateException JavaDoc("Unsupported block size: " + counterSize);
265                 }
266             } catch (NoSuchAlgorithmException JavaDoc ex) {
267                 log.warn("No such algorithm supported: " + modes[0]);
268             } catch (NoSuchPaddingException ex) {
269                 log.warn("Invalid padding: " + modes[0]);
270             }
271         }
272     }
273
274     /**
275      * Unregisters the cryptographic handler
276      */

277     public void destroy() {
278         try {
279             if (!providerAlreadyInstalled) {
280                 Security.removeProvider(theProvider.getName());
281             }
282             keyMap.clear();
283             ivMap.clear();
284             modeMap.clear();
285             keyMap = null;
286             ivMap = null;
287             modeMap = null;
288         } catch (SecurityException JavaDoc e) {
289             e.printStackTrace();
290             System.err.println(e.getMessage());
291         }
292     }
293
294     /**
295      * Same as decryptString, but only deals in byte arrays. This method must be
296      * implemented by descendants of this class.
297      *
298      * @param inputData The input data to decrypt
299      * @return the decrypted data byte
300      * @throws ChainedException Upn encryption error
301      */

302     public byte[] decrypt(byte[] inputData)
303             throws ChainedException {
304
305         if (inputData.length < 16) {
306             throw new IllegalArgumentException JavaDoc("Improper Data Header");
307         }
308
309         //
310
//Check Version Header
311
//
312
int version = inputData[0];
313
314         if (version > 1) {
315             throw new IllegalArgumentException JavaDoc("Invalid Crypto Version");
316         }
317
318         //
319
//Check Cipher Header
320
//
321
SecretKeySpec theKey = null;
322         Cipher theCipher = null;
323         String JavaDoc theMode = null;
324
325         try {
326             theMode = new String JavaDoc(inputData, 1, 6, "UTF-16BE").toUpperCase();
327         } catch (UnsupportedEncodingException JavaDoc ex) {
328             throw new ChainedException("Unsupported Encoding", ex);
329         }
330
331         String JavaDoc cipherName = (String JavaDoc) modeMap.get(theMode);
332
333         if (cipherName == null) {
334             throw new IllegalArgumentException JavaDoc("Either invalid input data " +
335                     "or unsupported key mode: " + cipherName);
336         }
337
338         theKey = (SecretKeySpec) keyMap.get(theMode);
339
340         try {
341
342             //Initialize the appropriate cipher by name
343
theCipher = Cipher.getInstance(cipherName, theProvider.getName());
344         } catch (NoSuchProviderException JavaDoc ex) {
345             throw new ChainedException("Error Loading BouncyCastle Provider. ",
346                     ex);
347         } catch (NoSuchAlgorithmException JavaDoc ex) {
348             throw new ChainedException("Error loading crypto algorithms." +
349                     " You may not have installed the" +
350                     " Cryptography Extensions Properly", ex);
351         } catch (NoSuchPaddingException ex) {
352             throw new ChainedException("Error loading Padding." +
353                     " You may not have installed the" +
354                     " Cryptography Extensions Properly", ex);
355         }
356         try {
357
358             //
359
//Get the appropriate ivCounter. Don't do anything with it, but use
360
//it's bytes to get the appropriate size input vector
361
//Old: new byte[8];
362
ByteArrayCounter bc = (ByteArrayCounter) ivMap.get(theMode);
363             byte[] ivData = new byte[bc.getBytes().length];
364             int ivLength = ivData.length;
365
366             //Copy over the input vector
367
for (int i = 7; i < ivData.length + 7; i++) {
368                 ivData[i - 7] = inputData[i];
369             }
370
371             IvParameterSpec ivParam = new IvParameterSpec(ivData);
372             theCipher.init(Cipher.DECRYPT_MODE, theKey, ivParam);
373
374             //When decrypting, skip the header
375
byte[] returnValue = theCipher.doFinal(inputData, 7 + ivLength,
376                     inputData.length -
377                     (7 + ivLength));
378
379             return returnValue;
380         } catch (InvalidKeyException JavaDoc ex) {
381             throw new ChainedException("Invalid Key", ex);
382         } catch (BadPaddingException ex) {
383
384             //TODO: Will this cause people to get locked out if their browser
385
//has a corrupt cookie?
386
throw new ChainedException("Bad Padding", ex);
387         } catch (IllegalBlockSizeException ex) {
388             return "".getBytes();
389         } catch (InvalidAlgorithmParameterException JavaDoc ex) {
390             throw new ChainedException("Invalid Algorithm Parameter", ex);
391         } catch (Exception JavaDoc e) {
392             return "".getBytes();
393         }
394     } /* decrypt(byte) */
395
396
397     /**
398      * Encrypts a byte array of data based upon the method desired. See the main
399      * notes for this class for valid encryptMethod strings.
400      * <p/>
401      * Note that a similar decrypt is not needed because the encryptMethod is included
402      * in the header of the encrypted byte array.
403      *
404      * @param inputData The data to encrypt
405      * @param theShortCipherString The 'short name' of the cipher to use
406      * @return The encrypted byte array
407      * @throws ChainedException Upon error encrypting
408      */

409     public byte[] encrypt(byte[] inputData, String JavaDoc theShortCipherString)
410             throws ChainedException {
411
412         if (inputData.length == 0) {
413             throw new IllegalArgumentException JavaDoc("inputData must not be zero length");
414         }
415         if (theShortCipherString == null ||
416                 theShortCipherString.length() < 3) {
417             throw new IllegalArgumentException JavaDoc("encryptMethod must be " +
418                     "a valid encryption header string");
419         }
420
421         Cipher theCipher = null;
422         String JavaDoc theCipherString = null;
423         SecretKeySpec theKey = null;
424         IvParameterSpec ivParam = null;
425         byte[] ivBytes;
426         int ivLength;
427
428         try {
429
430             //Load the Cipher String
431
if (theShortCipherString != null) {
432                 theCipherString = (String JavaDoc) modeMap.get(theShortCipherString);
433             }
434             if (theCipherString == null) {
435                 throw new IllegalArgumentException JavaDoc("theShortCipherString " +
436                         "must be a valid cipher description. Received :" +
437                         theShortCipherString);
438             }
439
440             //Initialize Blowfish in OFB mode with PKCS5 Padding
441
theCipher = Cipher.getInstance(theCipherString,
442                     theProvider.getName());
443             theKey = (SecretKeySpec) keyMap.get(theShortCipherString);
444
445             ByteArrayCounter bc = (ByteArrayCounter) ivMap.get(theShortCipherString);
446             bc.increment();
447             ivBytes = bc.getBytes();
448             ivLength = ivBytes.length;
449             ivParam = new IvParameterSpec(ivBytes);
450         } catch (NoSuchProviderException JavaDoc ex) {
451             throw new ChainedException("Error loading cryptix provider." +
452                     " You may not have installed the" +
453                     " Cryptography Extensions Properly", ex);
454         } catch (NoSuchAlgorithmException JavaDoc ex) {
455             throw new ChainedException("Error loading crypto algorithms." +
456                     " You may not have installed the" +
457                     " Cryptography Extensions Properly", ex);
458         } catch (NoSuchPaddingException ex) {
459             throw new ChainedException("Error loading Padding." +
460                     " You may not have installed the" +
461                     " Cryptography Extensions Properly", ex);
462         }
463         try {
464             theCipher.init(Cipher.ENCRYPT_MODE, theKey, ivParam);
465
466             int dataLength = theCipher.getOutputSize(inputData.length);
467             byte[] finalData = new byte[dataLength + 7 + ivLength];
468
469             //Assemble the final byte array by concatentating the
470
//intput vector and the algorithm outputs.
471
//Set Version Number
472
finalData[0] = 1;
473
474             //Copy over the mode
475
byte[] modeHeader = theShortCipherString.getBytes("UTF-16BE");
476
477             for (int i = 1; i < 7; i++) {
478                 finalData[i] = modeHeader[i - 1];
479             }
480             //Copy the iv
481
for (int i = 7; i < 7 + ivLength; i++) {
482                 finalData[i] = ivBytes[i - 7];
483             }
484
485             theCipher.doFinal(inputData, 0, inputData.length, finalData,
486                     (7 + ivLength));
487
488             return finalData;
489         } catch (ShortBufferException ex) {
490             throw new ChainedException("Short Buffer", ex);
491         } catch (InvalidKeyException JavaDoc ex) {
492             throw new ChainedException("Invalid Key", ex);
493         } catch (InvalidAlgorithmParameterException JavaDoc ex) {
494             throw new ChainedException("Invalid Algorithm", ex);
495         } catch (BadPaddingException ex) {
496             throw new ChainedException("Bad Padding", ex);
497         } catch (IllegalBlockSizeException ex) {
498             throw new ChainedException("Illegal Block Size", ex);
499         } catch (UnsupportedEncodingException JavaDoc ex) {
500             throw new ChainedException("Unsupported Encoding", ex);
501         }
502     } /* encrypt(byte, String) */
503
504
505     /**
506      * Same as encryptString, but only deals in byte arrays. This must be implemented
507      * by the descendants of this class.
508      *
509      * @param inputData The data to encrypt
510      * @return The encrypted byte array.
511      */

512     public byte[] encrypt(byte[] inputData)
513             throws ChainedException {
514
515         //Load the Cipher String
516
String JavaDoc theShortCipherString = null;
517
518         theShortCipherString = this.getCryptoManager().getEncryptMode();
519
520         String JavaDoc theCipherString = null;
521
522         if (theShortCipherString != null) {
523             theCipherString = (String JavaDoc) modeMap.get(theShortCipherString);
524         }
525         if (theCipherString == null) {
526             theShortCipherString = "AES";
527         }
528
529         return encrypt(inputData, theShortCipherString);
530     } /* encrypt(byte) */
531
532     private void jbInit()
533             throws Exception JavaDoc {
534     }
535
536
537 }
538
539 /* StringEncryption */
540
Popular Tags