001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: /*
051: * EncryptionHelper.java
052: *
053: * Created on April 19, 2002, 8:46 AM
054: */
055:
056: package org.jaffa.security;
057:
058: import java.io.ByteArrayOutputStream;
059: import java.io.ObjectOutputStream;
060: import java.io.ByteArrayInputStream;
061: import java.io.ObjectInputStream;
062: import java.io.UnsupportedEncodingException;
063: import java.io.IOException;
064: import javax.crypto.SecretKey;
065: import javax.crypto.Cipher;
066: import java.io.File;
067: import java.io.FileInputStream;
068: import javax.crypto.KeyGenerator;
069: import java.io.FileOutputStream;
070: import java.security.NoSuchAlgorithmException;
071: import java.io.InputStream;
072: import java.io.FileNotFoundException;
073: import java.io.Serializable;
074: import java.io.NotSerializableException;
075:
076: /** This class has some utility functions for encrypting objects using the
077: * JCE Security Package.
078: *
079: * Its main purpose is to be able to take a Object/String and encrypt it, and then
080: * convert the encrypted data into a HexString, so that it can be passed arround as
081: * a String, and hence used in URL's.
082: *
083: * A good exmple of this is if you have an Object that you want to pass to a servlet,
084: * then you can use this routine to get a HexString version of that object and pass
085: * it accross in the URL as a paramater "data=1234567890ABC...", Data will not only be a serialization
086: * of the object, it will also be encrypted with a SecretKey, that the recievoing servlet must use
087: * when converting it back to an object.
088: *
089: * The String version of this process is optimized to convert the String in to a UTF-8 byte array.
090: * This results in a much smaller string then regular obejct serialization.
091: *
092: * @author paule
093: * @version 1.0
094: */
095: public class EncryptionHelper {
096:
097: /** This is the encryption policy that will be used */
098: public static final String ENCRYPT_POLICY = "DES/ECB/PKCS5Padding";
099: private static final char[] HEX = { '0', '1', '2', '3', '4', '5',
100: '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
101: private static final String HEX_STR = "0123456789ABCDEF";
102:
103: /** This method can be used from the command line for creating a Secret Key.
104: * @param args the command line arguments
105: * Requires one mandatory parameter, which is the file name to use to write out the SecretKey
106: */
107: public static void main(String args[]) {
108: if (args.length != 1) {
109: System.out
110: .println("Missing Parameter. Please supply the filename for writing out the SecretKey");
111: return;
112: }
113: File f = new File(args[0]);
114: if (f.exists())
115: System.out
116: .println("Warning: Existing File Will Be Replaced.");
117: try {
118: // Create Key
119: KeyGenerator kg = KeyGenerator.getInstance("DES");
120: SecretKey secretKey = kg.generateKey();
121:
122: // Convert to Raw bytearray for storage
123: byte[] rawsecretKey = secretKey.getEncoded();
124:
125: // Write the newly generated key to a file.
126: FileOutputStream fos = new FileOutputStream(f);
127: ObjectOutputStream oos = new ObjectOutputStream(fos);
128: oos.writeObject(secretKey);
129: oos.flush();
130: oos.close();
131: fos.close();
132:
133: } catch (NoSuchAlgorithmException e) {
134: System.err.println("Invalid Algorithm : " + e.getMessage());
135: } catch (IOException e) {
136: System.err.println("Error Writing Out Key : "
137: + e.getMessage());
138: }
139: }
140:
141: /** This method can be used from the command line for creating a Secret Key.
142: * @return Returns the newley generated key, or null if there was an error.
143: */
144: public static SecretKey createKey() {
145: try {
146: // Create Key
147: KeyGenerator kg = KeyGenerator.getInstance("DES");
148: SecretKey secretKey = kg.generateKey();
149: return secretKey;
150: } catch (NoSuchAlgorithmException e) {
151: // Shouldn't happen because we've hardcoded the algorithm to use - DES!
152: System.err.println("Invalid Algorithm : " + e.getMessage());
153: return null;
154: }
155: }
156:
157: /** Read a file that should contain a serialized Secret key
158: *
159: * @return The secret key object
160: * @param file The file object that points to the key file
161: * @throws ClassNotFoundException If the SecretKey class is not available
162: * @throws IOException If the specfied file can't be loaded
163: */
164: public static SecretKey readKey(File file) throws IOException,
165: ClassNotFoundException {
166: FileInputStream fis = new FileInputStream(file);
167: ObjectInputStream ois = new ObjectInputStream(fis);
168: SecretKey secretKey = (SecretKey) ois.readObject();
169: ois.close();
170: fis.close();
171: return secretKey;
172: }
173:
174: /** Read a file that should contain a serialized Secret key, the file
175: * is read as a resource on the classpath
176: *
177: * @return The secret key object
178: * @param name The resource name that points to the key file
179: * @throws ClassNotFoundException If the SecretKey class is not available
180: * @throws IOException If the specfied file can't be loaded
181: */
182: public static SecretKey readKeyClassPath(String name)
183: throws IOException, ClassNotFoundException {
184: InputStream is = EncryptionHelper.class.getClassLoader()
185: .getResourceAsStream(name);
186: if (is == null)
187: is = ClassLoader.getSystemResourceAsStream(name);
188: if (is == null)
189: throw new FileNotFoundException(name);
190: ObjectInputStream ois = new ObjectInputStream(is);
191: SecretKey secretKey = (SecretKey) ois.readObject();
192: ois.close();
193: is.close();
194: return secretKey;
195: }
196:
197: /** Creates an encrypted and encode string from the source string.
198: * This string can be used directly in a URL without encoding.
199: * @param source The source string to encrypt/encode
200: * @param key The secret key to use for encryption
201: * @throws NoSuchAlgorithmException May be thrown by the Cypher module
202: * @throws InvalidKeyException May be thrown by the Cypher module
203: * @throws NoSuchPaddingException May be thrown by the Cypher module
204: * @throws UnsupportedEncodingException May be thrown by the Cypher module
205: * @throws IllegalBlockSizeException May be thrown by the Cypher module
206: * @throws BadPaddingException May be thrown by the Cypher module
207: * @return The encoded/encrypted string
208: */
209: public static String encryptStringForURL(String source,
210: SecretKey key)
211: throws java.security.NoSuchAlgorithmException,
212: java.security.InvalidKeyException,
213: javax.crypto.NoSuchPaddingException,
214: java.io.UnsupportedEncodingException,
215: javax.crypto.IllegalBlockSizeException,
216: javax.crypto.BadPaddingException {
217:
218: Cipher desCipher = Cipher.getInstance(ENCRYPT_POLICY);
219: desCipher.init(Cipher.ENCRYPT_MODE, key);
220:
221: return intoHexString(desCipher.doFinal(intoBytes(source)));
222: }
223:
224: /** Get a String from an Encoded and Encrypted String.
225: * @param data The encoded/encrypted string to process
226: * @param key The secret key used needed to decrypt the string
227: * @throws NoSuchAlgorithmException May be thrown by the Cypher module
228: * @throws InvalidKeyException May be thrown by the Cypher module
229: * @throws NoSuchPaddingException May be thrown by the Cypher module
230: * @throws IllegalBlockSizeException May be thrown by the Cypher module
231: * @throws BadPaddingException May be thrown by the Cypher module
232: * @return The real string that the data represents
233: */
234: public static String getStringFromEncryptedURL(String data,
235: SecretKey key)
236: throws java.security.NoSuchAlgorithmException,
237: java.security.InvalidKeyException,
238: javax.crypto.NoSuchPaddingException,
239: javax.crypto.IllegalBlockSizeException,
240: javax.crypto.BadPaddingException {
241:
242: Cipher desCipher = Cipher.getInstance(ENCRYPT_POLICY);
243: desCipher.init(Cipher.DECRYPT_MODE, key);
244:
245: return intoString(desCipher.doFinal(fromHexString(data)));
246: }
247:
248: /** Creates an encrypted and encode string from the source object.
249: * This string can be used directly in a URL without encoding.
250: * This assumes that the object passed in can be serialized.
251: * @param source The source Object to encrypt/encode
252: * @param key The secret key to use for encryption
253: * @throws NoSuchAlgorithmException May be thrown by the Cypher module
254: * @throws InvalidKeyException May be thrown by the Cypher module
255: * @throws NoSuchPaddingException May be thrown by the Cypher module
256: * @throws UnsupportedEncodingException May be thrown by the Cypher module
257: * @throws IllegalBlockSizeException May be thrown by the Cypher module
258: * @throws BadPaddingException May be thrown by the Cypher module
259: * @throws NotSerializableException if the source object is not Serializable
260: * @return The encoded/encrypted string
261: */
262: public static String encryptObjectForURL(Object source,
263: SecretKey key)
264: throws java.security.NoSuchAlgorithmException,
265: java.security.InvalidKeyException,
266: javax.crypto.NoSuchPaddingException,
267: java.io.UnsupportedEncodingException,
268: javax.crypto.IllegalBlockSizeException,
269: javax.crypto.BadPaddingException,
270: java.io.NotSerializableException {
271:
272: Cipher desCipher = Cipher.getInstance(ENCRYPT_POLICY);
273: desCipher.init(Cipher.ENCRYPT_MODE, key);
274:
275: // Copy object to byte array
276: if (!(source instanceof Serializable))
277: throw new NotSerializableException();
278: try {
279: ByteArrayOutputStream b = new ByteArrayOutputStream();
280: ObjectOutputStream o = new ObjectOutputStream(b);
281: o.writeObject(source);
282: o.flush();
283: o.close();
284:
285: return intoHexString(desCipher.doFinal(b.toByteArray()));
286: } catch (IOException e) {
287: throw new RuntimeException(
288: "No IO Exception should occur, this is all in-memory!!");
289: }
290: }
291:
292: /** Get an Object from an Encoded and Encrypted String. This assumes that the
293: * object can be recreated by de-serialization, and that the original class for the
294: * object is accessable.
295: *
296: * @param data The encoded/encrypted string to process
297: * @param key The secret key used needed to decrypt the string
298: * @throws NoSuchAlgorithmException May be thrown by the Cypher module
299: * @throws InvalidKeyException May be thrown by the Cypher module
300: * @throws NoSuchPaddingException May be thrown by the Cypher module
301: * @throws IllegalBlockSizeException May be thrown by the Cypher module
302: * @throws BadPaddingException May be thrown by the Cypher module
303: * @return The real object that the data represents
304: */
305: public static Object getObjectFromEncryptedURL(String data,
306: SecretKey key)
307: throws java.security.NoSuchAlgorithmException,
308: java.security.InvalidKeyException,
309: javax.crypto.NoSuchPaddingException,
310: javax.crypto.IllegalBlockSizeException,
311: javax.crypto.BadPaddingException {
312:
313: Cipher desCipher = Cipher.getInstance(ENCRYPT_POLICY);
314: desCipher.init(Cipher.DECRYPT_MODE, key);
315:
316: try {
317: ByteArrayInputStream b = new ByteArrayInputStream(desCipher
318: .doFinal(fromHexString(data)));
319: ObjectInputStream o = new ObjectInputStream(b);
320: Object out = o.readObject();
321: o.close();
322: b.close();
323: return out;
324: } catch (IOException e) {
325: throw new RuntimeException(
326: "No IO Exception should occur, this is all in-memory!!");
327: } catch (ClassNotFoundException e) {
328: throw new RuntimeException(
329: "Can find class of encrypted object!!!");
330: }
331: }
332:
333: /** Converts a String (based on an 8-bit character set) into an byte array.
334: * There will be one byte per charater in the string.
335: * @param in The string to be converted
336: * @throws UnsupportedEncodingException Is thrown if there are any unsupported characters in the string (ie. greater that 8-bits)
337: * @return The byte[] for the string
338: */
339: public static byte[] intoBytes(String in)
340: throws UnsupportedEncodingException {
341: byte[] data = new byte[in.length()];
342: char[] a = in.toCharArray();
343: for (int i = 0, j = 0; i < a.length; i++) {
344: char c = a[i];
345: byte hi = (byte) (c >> 8);
346: if (hi != 0)
347: throw new UnsupportedEncodingException(
348: "Non UTF-8 Characters In String, Use 16-Bit version!");
349: byte lo = (byte) (c & 0xFF);
350: data[j++] = lo;
351: }
352: return data;
353: }
354:
355: /** Converts a byte array into a string. It assumes that 8-bits represents a byte.
356: * There should there for be one character per byte.
357: * @param in byte[] to be converted
358: * @return Converted string
359: */
360: public static String intoString(byte[] in) {
361: StringBuffer b = new StringBuffer();
362: for (int i = 0; i < in.length;) {
363: byte hi = 0; //in[i++];
364: byte lo = in[i++];
365: char c = (char) (hi * 0xFF + lo);
366: b.append(c);
367: }
368: return b.toString();
369: }
370:
371: /** Converts a String into an byte array.
372: * There will be two bytes per charater in the string.
373: * @param in The string to be converted
374: * @return The byte[] for the string
375: */
376: public static byte[] intoBytes16(String in) {
377: byte[] data = new byte[in.length() * 2];
378: char[] a = in.toCharArray();
379: for (int i = 0, j = 0; i < a.length; i++) {
380: char c = a[i];
381: byte hi = (byte) (c >> 8);
382: byte lo = (byte) (c & 0xFF);
383: data[j++] = hi;
384: data[j++] = lo;
385: }
386: return data;
387: }
388:
389: /** Converts a byte array into a string. It assumes that 16-bits represents a byte.
390: * @param in byte[] to be converted
391: * @return Converted string
392: */
393: public static String intoString16(byte[] in) {
394: StringBuffer b = new StringBuffer();
395: for (int i = 0; i < in.length;) {
396: byte hi = in[i++];
397: byte lo = in[i++];
398: char c = (char) (hi * 256 + lo + (lo < 0 ? 256 : 0));
399: b.append(c);
400: }
401: return b.toString();
402: }
403:
404: /** Converts a byte[] into a hex string representation. Each byte will be represented
405: * by a 2-digit hex number (00-FF).
406: * @param in The byte[] to convert
407: * @return The string containing the Hex representation
408: */
409: public static String intoHexString(byte[] in) {
410: StringBuffer b = new StringBuffer();
411: for (int i = 0; i < in.length; i++) {
412: byte bt = in[i];
413: b.append(toHex((byte) (bt >> 4)));
414: b.append(toHex(bt));
415: }
416: return b.toString();
417: }
418:
419: /** Convert a String of hex values into a byte[]. Each two characters in the string
420: * represent 1 byte.
421: * @param in The hex string to be converted
422: * @return A byte[] of the real data
423: */
424: public static byte[] fromHexString(String in) {
425: byte[] data = new byte[in.length() / 2];
426: for (int i = 0, j = 0; i < in.length();) {
427: byte hi = fromHex(in.charAt(i++));
428: byte lo = fromHex(in.charAt(i++));
429: data[j++] = (byte) ((hi << 4) + lo);
430: }
431: return data;
432: }
433:
434: /** Utility function to convert a number into a hex character.
435: * Takes the lowest 4 bits and converts it to a character '0'..'F'
436: * @param b The byte to convert
437: * @return The Hex character
438: */
439: public static char toHex(byte b) {
440: return HEX[(int) (b & 0x0F)];
441: }
442:
443: /** Utility function to convert a hex character to a number.
444: * The character must be '0'..'F', the byte will be 0-15.
445: * @param c The character to convert
446: * @return The number as a byte
447: */
448: public static byte fromHex(char c) {
449: return (byte) HEX_STR.indexOf(c);
450: }
451: }
|