001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.core.security.strongencryption;
066:
067: import com.jcorporate.expresso.core.misc.ByteArrayCounter;
068: import com.jcorporate.expresso.core.security.AbstractStringEncryption;
069: import com.jcorporate.expresso.kernel.exception.ChainedException;
070: import com.jcorporate.expresso.kernel.util.ClassLocator;
071: import org.apache.log4j.Logger;
072:
073: import javax.crypto.BadPaddingException;
074: import javax.crypto.Cipher;
075: import javax.crypto.IllegalBlockSizeException;
076: import javax.crypto.KeyGenerator;
077: import javax.crypto.NoSuchPaddingException;
078: import javax.crypto.SecretKey;
079: import javax.crypto.ShortBufferException;
080: import javax.crypto.spec.IvParameterSpec;
081: import javax.crypto.spec.SecretKeySpec;
082: import java.io.UnsupportedEncodingException;
083: import java.security.InvalidAlgorithmParameterException;
084: import java.security.InvalidKeyException;
085: import java.security.NoSuchAlgorithmException;
086: import java.security.NoSuchProviderException;
087: import java.security.Provider;
088: import java.security.SecureRandom;
089: import java.security.Security;
090: import java.util.Hashtable;
091:
092: /**
093: * <p>StringEncryption.java</p>
094: * <p/>
095: * <p>Copyright 2000, 2001 Jcorporate Ltd.</p>
096: * <p>This class provides basic string encryption. It'll provide the services of
097: * password whitening and automatic selection of encryption.</p>
098: * <p/>
099: * <p>Known Vulnerabilities. The actual whitened password remains in memory for
100: * performance sake. An attacker may find the actual password by looking at swap
101: * files looking for Base64 encoded strings. (Not too hard to grep out) but it
102: * requires an attacker to gain access to the swap partition of the server. Do not
103: * use this class for a personal encryption program.</p>
104: * <br />
105: * <p/>
106: * <p>Byte Array Format Information:</p>
107: * <p>An encrypted string has the following format:</p>
108: * <p/>
109: * <p>Byte 0: File Version Number(whole number only)</p>
110: * <p>Bytes 1-6: 3 character desgination for the encryption mode used. UTF-16 BE</p>
111: * <p>Bytes 7-14/22: The 8/16 byte random input vector to the encrypted system.</p>
112: * <p>Bytes 15+/23++ : The Actual Encrypted Data</p>
113: *
114: * @author Michael Rimov
115: */
116: public class StringEncryption extends AbstractStringEncryption {
117: static protected ByteArrayCounter ivCounter64 = new ByteArrayCounter(
118: 8);
119: static protected ByteArrayCounter ivCounter128 = new ByteArrayCounter(
120: 16);
121:
122: static final private Logger log = Logger
123: .getLogger(StringEncryption.class);
124: /**
125: * An array of prepared secret keys depending on the mode required.
126: */
127: private Hashtable keyMap;
128:
129: /**
130: * a way to quickly get the mode we're going to use.
131: */
132: private Hashtable modeMap;
133:
134: /**
135: * An array of modes used here.
136: */
137: static final private String[] modes = {
138: "Rijndael/OFB/PKCS5Padding", "Blowfish/CFB/PKCS5Padding",
139: "Twofish/CFB/PKCS5Padding", "RC6/CBC/PKCS5Padding" };
140:
141: static final private String[] methodKeys = { "AES", "BLO", "TWO",
142: "RC6" };
143:
144: private Hashtable ivMap;
145:
146: private static final String PROVIDER_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider";
147:
148: // private Provider theProvider = new cryptix.jce.provider.CryptixCrypto();
149: private Provider theProvider; // = new org.bouncycastle.jce.provider.BouncyCastleProvider();
150:
151: boolean providerAlreadyInstalled = false;
152:
153: /**
154: * class for encapsulating encryption.
155: * constructor will throw if JCE jar is not found; see Ant target 'get-crypto'
156: * for easy means to download appropriate crypto library. it is under
157: * export restriction, so it cannot be added to the expresso library as
158: * publicly distributed
159: */
160: public StringEncryption() {
161:
162: //We test if the JDK already has it installed, if it is then we
163: //don't try to load it ourselves because some security systems
164: //may not allow us install our own provider
165: if (log.isDebugEnabled()) {
166: log.debug("Initializing Strong String Encryption");
167: log.debug("System Provider Dump -----------------------");
168: Provider allProviders[] = Security.getProviders();
169: for (int i = 0; i < allProviders.length; i++) {
170: log.debug(allProviders[i].toString());
171: }
172: log.debug("--------------------------------------------");
173: }
174:
175: theProvider = Security.getProvider(PROVIDER_NAME);
176: if (theProvider != null) {
177: providerAlreadyInstalled = true;
178: } else {
179: try {
180: theProvider = (Provider) ClassLocator.loadClass(
181: PROVIDER_NAME).newInstance();
182: } catch (Exception ex) {
183: log.error("Error loading bouncycastle crypto", ex);
184: throw new RuntimeException(
185: "constructor will throw if JCE jar is"
186: + " not found or if the security manager doesn't allow a new provider;"
187: + " see Ant target 'get-crypto' for easy means to download the appropriate"
188: + " crypto library from http://www.bouncycastle.org/download . "
189: + " It is under export restriction, so it cannot be "
190: + "added to the expresso library as publicly distributed.",
191: ex);
192: }
193: }
194: try {
195: jbInit();
196: } catch (Exception ex) {
197: ex.printStackTrace();
198: }
199: } /* StringEncryption() */
200:
201: public void init() throws ChainedException {
202: super .init();
203: keyMap = new Hashtable();
204: modeMap = new Hashtable();
205: ivMap = new Hashtable();
206: byte[] passKey = this .getPreparedPassKey();
207:
208: //Install the Cryptographic Provider if not set up properly
209: if (!providerAlreadyInstalled) {
210: try {
211: Security.addProvider(theProvider);
212: } catch (SecurityException e) {
213: throw new ChainedException(
214: "Error Installing Bouncy Castle Provider", e);
215: }
216: }
217:
218: SecureRandom random = null;
219: try {
220: random = SecureRandom.getInstance("SHA1PRNG");
221: } catch (NoSuchAlgorithmException ex1) {
222: throw new ChainedException("Error initiating sha1-random",
223: ex1);
224: }
225:
226: for (int i = 0; i < modes.length; i++) {
227: try {
228: Cipher c = Cipher.getInstance(modes[i]);
229: String cipherAlgorithm = modes[i].substring(0, modes[i]
230: .indexOf("/"));
231:
232: //
233: //We have to independantly figure out the maximum key
234: //available for each cipher in this JVM. [In case export
235: //regulations are intact]
236: //So we have the KeyGenerator generate an encoded key and
237: //we adjust down the keysize appropriately for what
238: //it comes up with size-wise.
239: //
240: KeyGenerator kg = KeyGenerator
241: .getInstance(cipherAlgorithm);
242: kg.init(random);
243: SecretKey key = kg.generateKey();
244: byte encoded[] = key.getEncoded();
245: byte myKey[] = passKey;
246: if (passKey.length > encoded.length) {
247: myKey = new byte[encoded.length];
248:
249: //Can't use system.arrayCopy because of using primative
250: //type.
251: for (int j = 0; j < myKey.length; j++) {
252: myKey[j] = passKey[j];
253: }
254: }
255: keyMap.put(methodKeys[i], new SecretKeySpec(myKey,
256: cipherAlgorithm));
257:
258: modeMap.put(methodKeys[i], modes[i]);
259:
260: int counterSize = c.getBlockSize();
261: switch (counterSize) {
262: case 16:
263: ivMap.put(methodKeys[i], ivCounter128);
264: break;
265: case 8:
266: ivMap.put(methodKeys[i], ivCounter64);
267: break;
268: default:
269: throw new IllegalStateException(
270: "Unsupported block size: " + counterSize);
271: }
272: } catch (NoSuchAlgorithmException ex) {
273: log.warn("No such algorithm supported: " + modes[0]);
274: } catch (NoSuchPaddingException ex) {
275: log.warn("Invalid padding: " + modes[0]);
276: }
277: }
278: }
279:
280: /**
281: * Unregisters the cryptographic handler
282: */
283: public void destroy() {
284: try {
285: if (!providerAlreadyInstalled) {
286: Security.removeProvider(theProvider.getName());
287: }
288: keyMap.clear();
289: ivMap.clear();
290: modeMap.clear();
291: keyMap = null;
292: ivMap = null;
293: modeMap = null;
294: } catch (SecurityException e) {
295: e.printStackTrace();
296: System.err.println(e.getMessage());
297: }
298: }
299:
300: /**
301: * Same as decryptString, but only deals in byte arrays. This method must be
302: * implemented by descendants of this class.
303: *
304: * @param inputData The input data to decrypt
305: * @return the decrypted data byte
306: * @throws ChainedException Upn encryption error
307: */
308: public byte[] decrypt(byte[] inputData) throws ChainedException {
309:
310: if (inputData.length < 16) {
311: throw new IllegalArgumentException("Improper Data Header");
312: }
313:
314: //
315: //Check Version Header
316: //
317: int version = inputData[0];
318:
319: if (version > 1) {
320: throw new IllegalArgumentException("Invalid Crypto Version");
321: }
322:
323: //
324: //Check Cipher Header
325: //
326: SecretKeySpec theKey = null;
327: Cipher theCipher = null;
328: String theMode = null;
329:
330: try {
331: theMode = new String(inputData, 1, 6, "UTF-16BE")
332: .toUpperCase();
333: } catch (UnsupportedEncodingException ex) {
334: throw new ChainedException("Unsupported Encoding", ex);
335: }
336:
337: String cipherName = (String) modeMap.get(theMode);
338:
339: if (cipherName == null) {
340: throw new IllegalArgumentException(
341: "Either invalid input data "
342: + "or unsupported key mode: " + cipherName);
343: }
344:
345: theKey = (SecretKeySpec) keyMap.get(theMode);
346:
347: try {
348:
349: //Initialize the appropriate cipher by name
350: theCipher = Cipher.getInstance(cipherName, theProvider
351: .getName());
352: } catch (NoSuchProviderException ex) {
353: throw new ChainedException(
354: "Error Loading BouncyCastle Provider. ", ex);
355: } catch (NoSuchAlgorithmException ex) {
356: throw new ChainedException(
357: "Error loading crypto algorithms."
358: + " You may not have installed the"
359: + " Cryptography Extensions Properly", ex);
360: } catch (NoSuchPaddingException ex) {
361: throw new ChainedException("Error loading Padding."
362: + " You may not have installed the"
363: + " Cryptography Extensions Properly", ex);
364: }
365: try {
366:
367: //
368: //Get the appropriate ivCounter. Don't do anything with it, but use
369: //it's bytes to get the appropriate size input vector
370: //Old: new byte[8];
371: ByteArrayCounter bc = (ByteArrayCounter) ivMap.get(theMode);
372: byte[] ivData = new byte[bc.getBytes().length];
373: int ivLength = ivData.length;
374:
375: //Copy over the input vector
376: for (int i = 7; i < ivData.length + 7; i++) {
377: ivData[i - 7] = inputData[i];
378: }
379:
380: IvParameterSpec ivParam = new IvParameterSpec(ivData);
381: theCipher.init(Cipher.DECRYPT_MODE, theKey, ivParam);
382:
383: //When decrypting, skip the header
384: byte[] returnValue = theCipher.doFinal(inputData,
385: 7 + ivLength, inputData.length - (7 + ivLength));
386:
387: return returnValue;
388: } catch (InvalidKeyException ex) {
389: throw new ChainedException("Invalid Key", ex);
390: } catch (BadPaddingException ex) {
391:
392: //TODO: Will this cause people to get locked out if their browser
393: //has a corrupt cookie?
394: throw new ChainedException("Bad Padding", ex);
395: } catch (IllegalBlockSizeException ex) {
396: return "".getBytes();
397: } catch (InvalidAlgorithmParameterException ex) {
398: throw new ChainedException("Invalid Algorithm Parameter",
399: ex);
400: } catch (Exception e) {
401: return "".getBytes();
402: }
403: } /* decrypt(byte) */
404:
405: /**
406: * Encrypts a byte array of data based upon the method desired. See the main
407: * notes for this class for valid encryptMethod strings.
408: * <p/>
409: * Note that a similar decrypt is not needed because the encryptMethod is included
410: * in the header of the encrypted byte array.
411: *
412: * @param inputData The data to encrypt
413: * @param theShortCipherString The 'short name' of the cipher to use
414: * @return The encrypted byte array
415: * @throws ChainedException Upon error encrypting
416: */
417: public byte[] encrypt(byte[] inputData, String theShortCipherString)
418: throws ChainedException {
419:
420: if (inputData.length == 0) {
421: throw new IllegalArgumentException(
422: "inputData must not be zero length");
423: }
424: if (theShortCipherString == null
425: || theShortCipherString.length() < 3) {
426: throw new IllegalArgumentException("encryptMethod must be "
427: + "a valid encryption header string");
428: }
429:
430: Cipher theCipher = null;
431: String theCipherString = null;
432: SecretKeySpec theKey = null;
433: IvParameterSpec ivParam = null;
434: byte[] ivBytes;
435: int ivLength;
436:
437: try {
438:
439: //Load the Cipher String
440: if (theShortCipherString != null) {
441: theCipherString = (String) modeMap
442: .get(theShortCipherString);
443: }
444: if (theCipherString == null) {
445: throw new IllegalArgumentException(
446: "theShortCipherString "
447: + "must be a valid cipher description. Received :"
448: + theShortCipherString);
449: }
450:
451: //Initialize Blowfish in OFB mode with PKCS5 Padding
452: theCipher = Cipher.getInstance(theCipherString, theProvider
453: .getName());
454: theKey = (SecretKeySpec) keyMap.get(theShortCipherString);
455:
456: ByteArrayCounter bc = (ByteArrayCounter) ivMap
457: .get(theShortCipherString);
458: bc.increment();
459: ivBytes = bc.getBytes();
460: ivLength = ivBytes.length;
461: ivParam = new IvParameterSpec(ivBytes);
462: } catch (NoSuchProviderException ex) {
463: throw new ChainedException(
464: "Error loading cryptix provider."
465: + " You may not have installed the"
466: + " Cryptography Extensions Properly", ex);
467: } catch (NoSuchAlgorithmException ex) {
468: throw new ChainedException(
469: "Error loading crypto algorithms."
470: + " You may not have installed the"
471: + " Cryptography Extensions Properly", ex);
472: } catch (NoSuchPaddingException ex) {
473: throw new ChainedException("Error loading Padding."
474: + " You may not have installed the"
475: + " Cryptography Extensions Properly", ex);
476: }
477: try {
478: theCipher.init(Cipher.ENCRYPT_MODE, theKey, ivParam);
479:
480: int dataLength = theCipher.getOutputSize(inputData.length);
481: byte[] finalData = new byte[dataLength + 7 + ivLength];
482:
483: //Assemble the final byte array by concatentating the
484: //intput vector and the algorithm outputs.
485: //Set Version Number
486: finalData[0] = 1;
487:
488: //Copy over the mode
489: byte[] modeHeader = theShortCipherString
490: .getBytes("UTF-16BE");
491:
492: for (int i = 1; i < 7; i++) {
493: finalData[i] = modeHeader[i - 1];
494: }
495: //Copy the iv
496: for (int i = 7; i < 7 + ivLength; i++) {
497: finalData[i] = ivBytes[i - 7];
498: }
499:
500: theCipher.doFinal(inputData, 0, inputData.length,
501: finalData, (7 + ivLength));
502:
503: return finalData;
504: } catch (ShortBufferException ex) {
505: throw new ChainedException("Short Buffer", ex);
506: } catch (InvalidKeyException ex) {
507: throw new ChainedException("Invalid Key", ex);
508: } catch (InvalidAlgorithmParameterException ex) {
509: throw new ChainedException("Invalid Algorithm", ex);
510: } catch (BadPaddingException ex) {
511: throw new ChainedException("Bad Padding", ex);
512: } catch (IllegalBlockSizeException ex) {
513: throw new ChainedException("Illegal Block Size", ex);
514: } catch (UnsupportedEncodingException ex) {
515: throw new ChainedException("Unsupported Encoding", ex);
516: }
517: } /* encrypt(byte, String) */
518:
519: /**
520: * Same as encryptString, but only deals in byte arrays. This must be implemented
521: * by the descendants of this class.
522: *
523: * @param inputData The data to encrypt
524: * @return The encrypted byte array.
525: */
526: public byte[] encrypt(byte[] inputData) throws ChainedException {
527:
528: //Load the Cipher String
529: String theShortCipherString = null;
530:
531: theShortCipherString = this .getCryptoManager().getEncryptMode();
532:
533: String theCipherString = null;
534:
535: if (theShortCipherString != null) {
536: theCipherString = (String) modeMap
537: .get(theShortCipherString);
538: }
539: if (theCipherString == null) {
540: theShortCipherString = "AES";
541: }
542:
543: return encrypt(inputData, theShortCipherString);
544: } /* encrypt(byte) */
545:
546: private void jbInit() throws Exception {
547: }
548:
549: }
550:
551: /* StringEncryption */
|