Java tutorial
/* * Copyright (c) 2008 Princeton University * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE EXPECTED TO * PROVIDE ANY MANNER OF SUPPORT OR ASSISTANCE IN THE USE, MANAGEMENT, * MAINTENANCE, MODIFICATION, INSTALLATION OR REPAIR OF THE SOFTWARE * OR ANY OTHER ACTIVITIES ARISING FROM, OUT OF OR IN CONNECTION WITH * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Collection of methods to encrypt and decrypt data attached to a URL. */ package test; import java.io.FileInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Calendar; import java.util.Properties; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; /** * @author tdtrue * */ public class PBEncryptLink { /** * @author tdtrue * */ public enum CipherMode { /** * */ ENCRYPT, /** * */ DECRYPT, } /** * @param args * @throws InvalidAlgorithmParameterException * @throws UnsupportedEncodingException * Shouldn't happen. */ public static void main(String[] args) throws InvalidAlgorithmParameterException, UnsupportedEncodingException { String keyString = "Password"; String iv = null; if (args.length > 0) { keyString = args[0]; } if (args.length > 1) { iv = args[1]; } String testEncryptedLink = null; String testDecryptedLink = null; String urlEncoded = null; String urlDecoded = null; String encodedIV = null; String decodedIV = null; byte[] encryptKeyBytes = null; byte[] decryptKeyBytes = null; try { /* * Instantiate two PBEncryptLink objects */ PBEncryptLink encryptLink = new PBEncryptLink(); PBEncryptLink decryptLink = new PBEncryptLink(); /* * Convert password string to byte array; one copy for each action. */ encryptKeyBytes = encryptLink.stringTo_iso8859_1_Bytes(keyString); decryptKeyBytes = encryptKeyBytes.clone(); /* * Set object mode. */ encryptLink.setCipherMode(CipherMode.ENCRYPT); decryptLink.setCipherMode(CipherMode.DECRYPT); /* * Set Initialization vector if one was passed. */ if (iv != null) { encryptLink.setIV(iv); } /* * Initialize encrypt object and zero password bytes. */ encryptLink.initCipher(encryptKeyBytes); zeroBytes(encryptKeyBytes); /* * Do the same for the decrypt object and also print out the IV. */ decryptLink.setIV(encryptLink.getIV()); encodedIV = new String(encryptLink.getIV(), "ISO8859_1"); decodedIV = new String(Base64.encodeBase64(encryptLink.getIV())); decryptLink.initCipher(decryptKeyBytes); zeroBytes(decryptKeyBytes); /* * Encrypt the link and decrypt it. */ String testCourse = "GEN101"; String testUser = "ci_test"; Logger testLogger = null; testEncryptedLink = encryptLink.encryptContextString(testCourse, testUser, testLogger); urlEncoded = URLEncoder.encode(testEncryptedLink, "UTF-8"); urlDecoded = URLDecoder.decode(urlEncoded, "UTF-8"); testDecryptedLink = decryptLink.decryptString(urlDecoded); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } /* * Display results. */ System.out.println("Results:"); System.out.println("Unencoded IV: " + decodedIV); System.out.println("Encoded IV: " + encodedIV); System.out.println("Encrypted data: " + testEncryptedLink); System.out.println("Encrypted data url encoded: " + urlEncoded); System.out.println("encrypted data decoded: " + urlDecoded); System.out.println("Decrypted data: " + testDecryptedLink); System.exit(0); } /** * Zero out an array of bytes. * * @param encryptKeyBytes */ public static void zeroBytes(byte[] keyBytes) { for (int i = 0; i < keyBytes.length; i++) { keyBytes[i] = 0; } } private CipherMode cipherMode_ = null; private Cipher blowfishCipher_ = null; private IvParameterSpec ivParmSpec_ = null; private static Properties prop = null; /** * Zero-Argument Constructor. * * @throws NoSuchPaddingException * Bad padding specified in Cipher.getInstance. * @throws NoSuchAlgorithmException * Bad Cipher name in Cipher.getInstance. */ public PBEncryptLink() throws NoSuchAlgorithmException, NoSuchPaddingException { InputStream in = null; try { in = new FileInputStream( "/usr/local/xythos/wfs/webapps/xythosticketagent/WEB-INF/src/blowfish.properties"); prop = new Properties(); prop.load(in); } catch (Exception ex) { // ignore } blowfishCipher_ = Cipher.getInstance("blowfish/cbc/PKCS5Padding"); } public static String getPropValue(String inKey) { if (prop == null) return ""; return prop.getProperty(inKey); } /** * Decrypt a Base64 string of ISO8859_1 data * * @param encryptedString * @return decrypted string * @throws IllegalBlockSizeException * Not clear if Blowfish can throw this. * @throws BadPaddingException * Not clear if or when this can be thrown. */ public String decryptString(String encryptedString) throws IllegalBlockSizeException, BadPaddingException { Cipher cipher = getBlowfishCipher(); byte[] encryptedBytesBase64 = stringTo_iso8859_1_Bytes(encryptedString); byte[] encryptedBytes = Base64.decodeBase64(encryptedBytesBase64); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); String decryptedString = iso8859_1_BytesToString(decryptedBytes); return decryptedString; } /** * Given a course and user value, insert them in the context string, add the * current time and encrypt using the currently initialized blowfish Cipher * object. Note that the String is converted to an arry of bytes and treated * as ISO8859_1 -- reasonable in our environment. The result is returned as * a Base64 String. * * @param courseName * @param user_id * @return String in Base64 representing the encrypted data. * @throws IllegalBlockSizeException * Is this possible in Blowfish? * @throws BadPaddingException * Shouldn't see this since default is used. */ public String encryptContextString(String courseName, String user_id) throws IllegalBlockSizeException, BadPaddingException { return encryptContextString(courseName, user_id, null); } /** * Given a user value, insert it in the context string, add the * current time and encrypt using the currently initialized blowfish Cipher * object. Note that the String is converted to an arry of bytes and treated * as ISO8859_1 -- reasonable in our environment. The result is returned as * a Base64 String. * * @param user_id * @param log * @return String in Base64 representing the encrypted data. * @throws IllegalBlockSizeException * Is this possible in Blowfish? * @throws BadPaddingException * Shouldn't see this since default is used. */ public String encryptContextString(String user_id, Logger log) throws IllegalBlockSizeException, BadPaddingException { return encryptContextString(null, user_id, log); } /** * Given a course and user value, insert them in the context string, add the * current time and encrypt using the currently initialized blowfish Cipher * object. Note that the String is converted to an arry of bytes and treated * as ISO8859_1 -- reasonable in our environment. The result is returned as * a Base64 String. * * @param courseName * @param user_id * @param log * @return String in Base64 representing the encrypted data. * @throws IllegalBlockSizeException * Is this possible in Blowfish? * @throws BadPaddingException * Shouldn't see this since default is used. */ public String encryptContextString(String courseName, String user_id, Logger log) throws IllegalBlockSizeException, BadPaddingException { Long curTime = new Long(Calendar.getInstance().getTimeInMillis()); long rndTime = Math.round(curTime.doubleValue() / 60000); if (log != null) { log.info("Default date and time " + (Calendar.getInstance()).toString()); } Cipher cipher = getBlowfishCipher(); String value = "login=" + user_id + (courseName != null ? ("&course=" + courseName) : "") + "&time=" + rndTime; if (log != null) { log.info("string before encrypting=" + value.toLowerCase()); } byte[] bArray = stringTo_iso8859_1_Bytes(value.toLowerCase()); byte[] encryptedData = cipher.doFinal(bArray); byte[] bBase64Array = Base64.encodeBase64(encryptedData); String base64String = iso8859_1_BytesToString(bBase64Array); return base64String; } /** * @return the blowfishCipher */ public Cipher getBlowfishCipher() { return blowfishCipher_; } /** * @return the cipherMode */ public CipherMode getCipherMode() { return (cipherMode_ != null ? cipherMode_ : CipherMode.ENCRYPT); } /** * Retrieve the Initialization Vector from the stored IvParameterSpec. * * @return the iV */ public byte[] getIV() { IvParameterSpec ivs = getIvParmSpec(); byte[] iv = null; if (ivs == null) { iv = new byte[0]; } else { iv = ivs.getIV(); } return iv; } /** * Retrieve the current IvParameterSpec for this object. * * @return the ivParmSpec_ */ public IvParameterSpec getIvParmSpec() { return ivParmSpec_; } /** * Initialize the Cipher object using the given key. If the Initialization * vaector has not already been stored (as an IvParameterSpec), then, for * encryption, one is created and stored. If the CipherMode is DECRYPT and * no initialization vector has been set, then an * InvalidAlgorithmParameterException is thrown. * * @param key * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException * Hmm. */ public void initCipher(byte[] key) throws InvalidKeyException, InvalidAlgorithmParameterException { SecretKeySpec skeySpec = new SecretKeySpec(key, "Blowfish"); Cipher cipher = getBlowfishCipher(); IvParameterSpec ivs = getIvParmSpec(); int intCipherMode = -1; switch (getCipherMode()) { case ENCRYPT: intCipherMode = Cipher.ENCRYPT_MODE; if (ivs == null) { cipher.init(intCipherMode, skeySpec); setIV(cipher.getIV()); } else { cipher.init(intCipherMode, skeySpec, ivs); } break; case DECRYPT: intCipherMode = Cipher.DECRYPT_MODE; cipher.init(intCipherMode, skeySpec, ivs); default: break; } if (intCipherMode == -1) { return; } } /** * Given an array of bytes, treat them as an array of ISO8859_1 characters * and convert them to a Java String. * * @param input * array of bytes to convert. * @return String representing the array of bytes */ public String iso8859_1_BytesToString(byte[] input) { Charset charSet = Charset.forName("ISO-8859-1"); ByteBuffer bb = ByteBuffer.wrap(input); CharBuffer cb = charSet.decode(bb); String output = cb.toString(); return output; } /** * Store a Blowfish cipher object. * * @param blowfishCipher * the blowfishCipher to set */ public void setBlowfishCipher(Cipher blowfishCipher) { blowfishCipher_ = blowfishCipher; } /** * Determine whether the object will be used for encryption or decryption. * * @param cipherMode * the cipherMode to set */ public void setCipherMode(CipherMode cipherMode) { cipherMode_ = cipherMode; } /** * Store the contents of a byte array as the IvParameterSpec. The array is * copied and can be safely modified after the IV has been set. * * @param iv */ public void setIV(byte[] iv) { if (iv.length != 8) { throw new IllegalArgumentException("The byte array supplied to setIV must be exactly 8 " + "elements long. The supplied array was " + iv.length + " elements."); } IvParameterSpec ivs = new IvParameterSpec(iv); ivParmSpec_ = ivs; } /** * Use a string as an Initialization vector; the string is assumed to * contain only ISO8859_1 characters. * * @param iv */ public void setIV(String strIv) { if (strIv.length() != 8) { throw new IllegalArgumentException("String supplied to setIV must be exactly 8 characters long. " + "The supplied string was " + strIv.length() + " characters."); } byte[] iv = stringTo_iso8859_1_Bytes(strIv); setIV(iv); } /** * Given a String containing only characters that can be represented in * ISO8859_1, return an array of bytes encoded in ISO8859_1 that reprsents * the content of the string. * * @param input * String of ISO8859_1 characters * @return array of bytes representing the String in ISO8859_1 */ public byte[] stringTo_iso8859_1_Bytes(String input) { Charset charSet = Charset.forName("ISO-8859-1"); ByteBuffer bb = charSet.encode(input); byte[] output = new byte[bb.limit()]; bb.get(output); return output; } }