Java tutorial
/* @(#)CyperUtils.java - zac@zacwolf.com * * _CRYPTOfactory handles providing thread safe encryption/decryption methods for streams and byte arrays * Licensed under the MIT License (MIT) Copyright (c) 2014 Zac Morris 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. */ package com.zacwolf.commons.crypto; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.SecureRandom; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.logging.Logger; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.ShortBufferException; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.apache.commons.io.IOUtils; import com.zacwolf.commons.utils.JVM; /** * This class acts as a wrapper for the encryption/decryption of * objects/strings/byte-arrays providing easy encrypt/decrypt methods * * @author zac@zacwolf.com * */ public final class _CRYPTOfactory { final static public String ENCODING = "UTF-8"; final static public String STORETYPE = "JCEKS"; final static public int MAXACTIVE = Runtime.getRuntime().availableProcessors(); static private int activecrypts = 0; public final Crypter crypter; final public static KeyStore genNewKeyStore(final File keystorefile, final String keystorepass) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { return genNewKeyStore(keystorefile, keystorepass.toCharArray()); } final public static KeyStore genNewKeyStore(final File keystorefile, final char[] keystorepass) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { final KeyStore ks = KeyStore.getInstance(STORETYPE); ks.load((InputStream) null, keystorepass); final FileOutputStream out = new FileOutputStream(keystorefile); try { ks.store(out, keystorepass); } finally { out.close(); } return ks; } public static KeyStore addSiteTrustChain(final String sitehostname, final KeyStore keystore, final String passphrase) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { return addSiteTrustChain(sitehostname, 443, keystore, passphrase.toCharArray()); } public static KeyStore addSiteTrustChain(final String sitehostname, final KeyStore keystore, final char[] passphrase) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { return addSiteTrustChain(sitehostname, 443, keystore, passphrase); } public static KeyStore addSiteTrustChain(final String sitehostname, final int httpsport, final KeyStore keystore, final String passphrase) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { return addSiteTrustChain(sitehostname, httpsport, keystore, passphrase.toCharArray()); } public static KeyStore addSiteTrustChain(final String sitehostname, final int httpsport, final KeyStore keystore, final char[] passphrase) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { final SSLContext context = SSLContext.getInstance("TLS"); final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keystore); final X509TrustManager dtm = (X509TrustManager) tmf.getTrustManagers()[0]; final MyTrustManager tm = new MyTrustManager(dtm); context.init(null, new TrustManager[] { tm }, null); final SSLSocketFactory factory = context.getSocketFactory(); final SSLSocket socket = (SSLSocket) factory.createSocket(sitehostname, httpsport); socket.setSoTimeout(10000); try { System.out.println("Starting SSL handshake..."); socket.startHandshake(); socket.close(); System.out.println("Certificate for server " + sitehostname + " is already trusted"); } catch (SSLException e) { final X509Certificate[] chain = tm.chain; if (chain == null) { System.err.println("Could not obtain server certificate chain"); return keystore; } System.out.println("Server sent " + chain.length + " certificate(s):"); for (int i = 0; i < chain.length; i++) { final X509Certificate cert = chain[i]; MessageDigest.getInstance("SHA1").update(cert.getEncoded()); MessageDigest.getInstance("MD5").update(cert.getEncoded()); final String alias = sitehostname + "-" + (i + 1); keystore.setCertificateEntry(alias, cert); System.out.println("Added certificate to keystore using alias '" + alias + "'"); } } return keystore; } final public static _CRYPTOfactory getInstanceFromKeystore(final File keystorefile, final String keystorepass, final String alias) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { return getInstanceFromKeystore(new FileInputStream(keystorefile), keystorepass.toCharArray(), alias); } final public static _CRYPTOfactory getInstanceFromKeystore(final InputStream ksin, final String keystorepass, final String alias) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { return getInstanceFromKeystore(ksin, keystorepass.toCharArray(), alias); } final public static _CRYPTOfactory getInstanceFromKeystore(final KeyStore keystore, final String keystorepass, final String alias) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { return getInstanceFromKeystore(keystore, keystorepass.toCharArray(), alias); } final public static _CRYPTOfactory getInstanceFromKeystore(final File keystorefile, final char[] keystorepass, final String alias) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { return getInstanceFromKeystore(new FileInputStream(keystorefile), keystorepass, alias); } final public static _CRYPTOfactory getInstanceFromKeystore(final InputStream ksin, final char[] keystorepass, final String alias) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { final KeyStore keystore = KeyStore.getInstance(STORETYPE); keystore.load(ksin, keystorepass); return getInstanceFromKeystore(keystore, keystorepass, alias); } final public static _CRYPTOfactory getInstanceFromKeystore(final KeyStore keystore, final char[] keystorepass, final String alias) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { final Key keyFromStore = keystore.getKey(alias, keystorepass); final String type = keyFromStore.getAlgorithm(); return new _CRYPTOfactory( (Crypter) Class.forName(_CRYPTOfactory.class.getPackage().getName() + "." + type + "Crypter") .getConstructor(byte[].class).newInstance(keyFromStore.getEncoded())); } final public static _CRYPTOfactory getInstance(byte[] CHKeyBytes) throws ClassNotFoundException, NoSuchAlgorithmException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { return getInstance(new ByteArrayInputStream(CHKeyBytes)); } final public static _CRYPTOfactory getInstance(File keyfile) throws ClassNotFoundException, NoSuchAlgorithmException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { return getInstance(new FileInputStream(keyfile)); } final public static _CRYPTOfactory getInstance(InputStream in) throws ClassNotFoundException, NoSuchAlgorithmException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { final DataInputStream d = new DataInputStream(in); int start = 0; final int type_length = d.readInt(); start = start + 4; final int cipher_length = d.readInt(); start = start + 4; final int salter_length = d.readInt(); start = start + 4; final int key_length = d.readInt(); start = start + 4; final StringBuilder type = new StringBuilder(); final StringBuilder cipher = new StringBuilder(); final StringBuilder salter = new StringBuilder(); final byte[] key = new byte[key_length]; for (int i = 0; i < type_length; i++) type.append((char) d.readByte()); for (int i = 0; i < cipher_length; i++) cipher.append((char) d.readByte()); for (int i = 0; i < salter_length; i++) salter.append((char) d.readByte()); d.readFully(key); return new _CRYPTOfactory( (Crypter) Class.forName(_CRYPTOfactory.class.getPackage().getName() + "." + type + "Crypter") .getConstructor(byte[].class, String.class, SecureRandom.class) .newInstance(key, cipher.toString(), SecureRandom.getInstance(salter.toString()))); } public _CRYPTOfactory(final Crypter crypter) { this.crypter = crypter; } public final byte[] decrypt(final byte[] bytes) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException { ready(); activecrypts++; try { final int saltsize = bytes[0]; final byte[] salt; if (saltsize > 0) { salt = new byte[saltsize]; for (int i = 0; i < salt.length; i++) salt[i] = bytes[i + 1]; return crypter.getDcipher(salt).doFinal(bytes, salt.length + 1, bytes.length - (salt.length + 1)); } return crypter.getDcipher(null).doFinal(bytes, 1, bytes.length - 1); } finally { activecrypts--; } } public final byte[] decryptFromInputStream(final InputStream is) throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException { ready(); activecrypts++; try { final int saltsize = is.read(); final byte[] salt; if (saltsize > 0) { salt = new byte[saltsize]; is.read(salt); } else salt = null; final CipherInputStream cip = new CipherInputStream(is, crypter.getDcipher(salt)); try { return IOUtils.toByteArray(cip); } finally { try { is.close(); } catch (final Exception e) { } } } finally { activecrypts--; } } public final Object decryptObjFromInputStream(final InputStream is) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, ClassNotFoundException { ready(); activecrypts++; try { final int saltsize = is.read(); final byte[] salt; if (saltsize > 0) { salt = new byte[saltsize]; is.read(salt); } else salt = null; ObjectInputStream ois = null; try { ois = is instanceof ObjectInputStream ? (ObjectInputStream) is : new ObjectInputStream(new CipherInputStream(is, crypter.getDcipher(salt))); return ois.readObject(); } finally { if (ois != null) { ois.close(); ois = null; } } } finally { activecrypts--; } } public final byte[] encrypt(final byte[] bytes) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException { ready(); activecrypts++; try { final Cipher ecipher = this.crypter.getEcipher(); byte[] salt = ecipher.getIV(); final ByteBuffer outbuf = ByteBuffer .allocate(ecipher.getOutputSize(bytes.length) + (salt != null ? salt.length : 0) + 1); if (salt != null) { outbuf.put((byte) salt.length); outbuf.put(salt); } else outbuf.put((byte) 0); try { ecipher.doFinal(ByteBuffer.wrap(bytes), outbuf); } catch (ShortBufferException e) { // not going to happen since we specifically allocate based on the cipher output size } return outbuf.array(); } finally { activecrypts--; } } public final void encryptToOutputStream(final byte[] bytes, final OutputStream outputStream) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IOException { ready(); activecrypts++; try { final Cipher ecipher = crypter.getEcipher(); final byte[] salt = ecipher.getIV(); if (salt == null) { outputStream.write(0); } else { outputStream.write(salt.length); outputStream.write(salt); } outputStream.flush(); CipherOutputStream cop = null; try { cop = new CipherOutputStream(outputStream, ecipher) { /* * WebSphere 7 has a known bug with it's implementation of ibmjceprovider.jar * concerning writing byte-arrays in a serialized object when the byte-array length * is zero. * see: http://www.ibm.com/developerworks/forums/thread.jspa?messageID=14597510 * * Added an override of the CipherOutputStream write method so that it is only called when * the byte array has length > 0 */ @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (len > 0) { super.write(b, off, len); //super.flush(); Do NOT flush here, as it slows the process down exponentially } } }; cop.write(bytes); cop.flush(); } finally { if (cop != null) { cop.flush(); cop.close(); } outputStream.flush(); outputStream.close(); } } finally { activecrypts--; } } public final void encryptObjToOutputStream(final Serializable obj, final OutputStream outputStream) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IOException { if (outputStream instanceof ObjectOutputStream) throw new IOException( "encryptObjToOutputStream already wraps the outputStream in an ObjectOutputStream, so only pass-in a non-ObjectOutputStream wrapped stream"); ready(); activecrypts++; try { final Cipher ecipher = crypter.getEcipher(); final byte[] salt = ecipher.getIV(); if (salt == null) { outputStream.write(0); } else { outputStream.write(salt.length); for (byte s : salt) outputStream.write(s); } final CipherOutputStream cos = new CipherOutputStream(outputStream, ecipher) { /* * WebSphere 7 has a known bug with it's implementation of ibmjceprovider.jar * concerning writing byte-arrays in a serialized object when the byte-array length * is zero. * see: http://www.ibm.com/developerworks/forums/thread.jspa?messageID=14597510 * * Added an override of the CipherOutputStream write method so that it is only called when * the byte array has length > 0 */ @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (len > 0) { super.write(b, off, len); //super.flush(); } } }; final ObjectOutputStream oos = new ObjectOutputStream(cos); oos.writeObject(obj); oos.flush(); oos.close(); } finally { activecrypts--; } } public final void addCrypterToKeyStore(final File keyStoreFile, final String keystorepass) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { addCrypterToKeyStore(keyStoreFile, keystorepass, null); } public final void addCrypterToKeyStore(final File keyStoreFile, final String keystorepass, final String alias) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { addCrypterToKeyStore(keyStoreFile, keystorepass.toCharArray(), alias); } public final void addCrypterToKeyStore(final File keyStoreFile, final char[] keystorepass, final String alias) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { final KeyStore ks = KeyStore.getInstance("JCEKS"); final InputStream in = new FileInputStream(keyStoreFile); ks.load(in, keystorepass); in.close(); addCrypterToKeyStore(ks, keystorepass, alias); final OutputStream out = new FileOutputStream(keyStoreFile); try { ks.store(out, keystorepass); } finally { out.close(); } } public final void addCrypterToKeyStore(final KeyStore keyStore, final String keystorepass, final String alias) throws KeyStoreException, CertificateException { addCrypterToKeyStore(keyStore, keystorepass.toCharArray(), alias); } public final void addCrypterToKeyStore(final KeyStore keyStore, final char[] keystorepass, final String alias) throws KeyStoreException, CertificateException { crypter.addToKeystore(keyStore, keystorepass, alias); } public final String getType() { return crypter.getType(); } public final byte[] getAsBytes() throws IOException { return crypter.getCHKey(); } public final _CRYPTOfactory test(Logger logger, String[] testargs) { try { final File testfile = createTempFile("_CRYPTOfactory" + this.getType() + "_Test", ".tmp"); logger.finer(this.getClass().getName() + ":TEST:" + this.getType() + ":.crypt(\"" + testargs[0] + "\"):RESULT:" + new String(this.decrypt(this.encrypt(testargs[0].getBytes(_CRYPTOfactory.ENCODING))), _CRYPTOfactory.ENCODING)); this.encryptToOutputStream(testargs[0].getBytes(_CRYPTOfactory.ENCODING), new FileOutputStream(testfile)); logger.finer(this.getClass().getName() + ":TEST:" + this.getType() + ":.cryptToOutputStream(\"" + testargs[0] + "\"):RESULT:" + new String( this.decryptFromInputStream(new FileInputStream(testfile)), _CRYPTOfactory.ENCODING)); testfile.delete(); if (!this.getType().equalsIgnoreCase("RSA")) { this.encryptObjToOutputStream(new TestSerObj(testargs), new FileOutputStream(testfile)); logger.finer("_CRYPTOfactory:TEST:" + this.getType() + ":.cryptObjToOutputStream:RESULT:" + this.decryptObjFromInputStream(new FileInputStream(testfile))); logger.finer(this.getClass().getName() + ":TEST:Fully initialized " + this.crypter.getType() + " cipher\n"); testfile.delete(); } } catch (Exception e) { if (e instanceof InvalidKeyException && this.crypter instanceof Crypter_RSA) { logger.fine("Unable to test an RSACypter with only a public key"); } else { logger.severe(this.getClass().getName() + ":TEST:" + this.crypter.getType() + ":ERROR:" + e + "\n"); e.printStackTrace(); } } finally { logger.exiting(this.getClass().getName(), "test()", JVM.getMemoryStats()); } return this;//So that it can be chained with the constructor call } /** * Since crypt operations are very CPU intensive, we keep the number * of threads actually doing crypt operations to a max number */ private void ready() { while (activecrypts > MAXACTIVE) try { Thread.sleep(500); } catch (InterruptedException e) { // If interrupted (shutting down), drop out of waiting } } static final File createTempFile(String name, String extension) throws IOException { final File file = File.createTempFile(name, extension); file.deleteOnExit(); return file; } private static class MyTrustManager implements X509TrustManager { final private X509TrustManager tm; private X509Certificate[] chain; MyTrustManager(X509TrustManager tm) { this.tm = tm; } /* (non-Javadoc) * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String) */ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { throw new UnsupportedOperationException(); } /* (non-Javadoc) * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String) */ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { this.chain = chain; this.tm.checkServerTrusted(chain, authType); } /* (non-Javadoc) * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() */ public X509Certificate[] getAcceptedIssuers() { throw new UnsupportedOperationException(); } } private static class TestSerObj implements Serializable { private static final long serialVersionUID = -4385040795343680455L; public String[] args; public TestSerObj(String args[]) { this.args = args; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); for (String arg : this.args) if (builder.length() < 245) builder.append(arg + " "); return builder.toString(); } } public static void dumpCiphers() { for (Provider provider : Security.getProviders()) { System.out.println(provider.getName()); for (String key : provider.stringPropertyNames()) System.out.println("\t" + key + "\t" + provider.getProperty(key)); } } public static void main(String[] args) { final File myaeskey = new File("CypherUtilsTest_AES.key"); final File mybfkey = new File("CypherUtilsTest_Blowfish.key"); final File myrsakey = new File("CyperUtilsTest_RSA.key"); try { /* if (!myrsakey.exists()) Crypter_RSA.generateNewKeyFile(myrsakey); if (!myaeskey.exists()) Crypter_AES.generateNewKeyFile(myaeskey); if (!mybfkey.exists()) Crypter_Blowfish.generateNewKeyFile(mybfkey); //_CRYPTOfactory.dumpCiphers(); final Logger logger = Logger.getAnonymousLogger(); logger.setLevel(Level.ALL); final _CRYPTOfactory rsacypher = new _CRYPTOfactory(new Crypter_RSA(myrsakey)).test(logger,args); final _CRYPTOfactory aescypher = new _CRYPTOfactory(new Crypter_AES(myaeskey)).test(logger,args); System.out.println("Crypt the AES crypter via RSA, then backagain to a new AES crypter..."); _CRYPTOfactory.getInstance(rsacypher.decrypt(rsacypher.encrypt(aescypher.getAsBytes()))).test(logger,args); final _CRYPTOfactory blocypher = new _CRYPTOfactory(new Crypter_Blowfish(mybfkey)).test(logger,args); System.out.println("Crypt the Blowfish crypter RSA rsa, then backagain to a new Blowfish crypter..."); _CRYPTOfactory.getInstance(rsacypher.decrypt(rsacypher.encrypt(blocypher.getAsBytes()))).test(logger,args); final File keystorefile = new File("keystore.jks"); final String keystorepass = args[0]; _CRYPTOfactory.genNewKeyStore(keystorefile,keystorepass); rsacypher.addCrypterToKeyStore(keystorefile, keystorepass); aescypher.addCrypterToKeyStore(keystorefile, keystorepass); blocypher.addCrypterToKeyStore(keystorefile, keystorepass); */ } catch (OutOfMemoryError oom) { System.err.println(JVM.getMemoryStats()); oom.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { myaeskey.delete(); mybfkey.delete(); myrsakey.delete(); } } }