tdunnick.phinmsx.crypt.Encryptor.java Source code

Java tutorial

Introduction

Here is the source code for tdunnick.phinmsx.crypt.Encryptor.java

Source

/*
 *  Copyright (c) 2012-2013 Thomas Dunnick (https://mywebspace.wisc.edu/tdunnick/web)
 *  
 *  This file is part of PhinmsX.
 *
 *  PhinmsX is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  PhinmsX is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with PhinmsX.  If not, see <http://www.gnu.org/licenses/>.
 */
package tdunnick.phinmsx.crypt;

import java.util.*;
import java.io.*;
import java.util.logging.*;
import javax.naming.*;
import javax.naming.directory.*;
import java.security.*;
import java.security.cert.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.*;

import tdunnick.phinmsx.util.*;

public class Encryptor {
    public final static String RSA_TRANSFORM = "RSA/ECB/PKCS1PADDING";
    public final static String DES_TRANSFORM = "DESede/CBC/PKCS5Padding";
    private String[] transform = { RSA_TRANSFORM, DES_TRANSFORM };
    Logger logger;

    /**
     * add any needed security providers
     */
    public Encryptor() {
        init(null);
    }

    public Encryptor(Logger logger) {
        init(logger);
    }

    public boolean init(Logger l) {
        if (l == null)
            logger = Logger.getLogger("");
        else
            logger = l;
        if (Security.getProvider("BC") == null) {
            if (Security.addProvider(new BouncyCastleProvider()) < 0) {
                logger.severe("Couldn't add BC security provider");
                return false;
            }
        }
        return true;
    }

    /**
     * get the key algorithm for this transform
     * 
     * @param transform
     * @return
     */
    private String getAlgorithm(String transform) {
        int i = transform.indexOf('/');
        if (i < 0)
            return transform;
        return transform.substring(0, i);
    }

    /**
     * get the transform for this key
     * @param key
     * @return
     */
    private String getTransform(Key key) {
        logger.finest("Algorithm: " + key.getAlgorithm());
        for (int i = 0; i < transform.length; i++) {
            if (transform[i].startsWith(key.getAlgorithm()))
                return transform[i];
        }
        logger.severe("No matching transform found - assuming " + DES_TRANSFORM);
        return DES_TRANSFORM;
    }

    /** 
     * Generate a secret TripleDES encryption/decryption key 
     * 
     * @return a new secret key
     */
    public SecretKey generateDESKey() {
        try {
            KeyGenerator keygen = KeyGenerator.getInstance(getAlgorithm(DES_TRANSFORM));
            return keygen.generateKey();
        } catch (Exception e) {
            logger.severe("Can't generate key for " + DES_TRANSFORM + ": " + e.getMessage());
        }
        return null;
    }

    private KeyStore loadKeyStore(File f, String pw, String type) {
        KeyStore ks = null;
        FileInputStream is = null;
        try {
            is = new FileInputStream(f);
        } catch (IOException e) {
            logger.severe("Failed loading " + f.getAbsolutePath() + ": " + e.getMessage());
            return null;
        }
        try {
            ks = KeyStore.getInstance(type);
            ks.load(is, pw.toCharArray());
        } catch (Exception e) {
            String s = e.getMessage();
            if (!s.equals("Invalid keystore format"))
                logger.severe("Failed loading " + f.getAbsolutePath() + ": " + s);
            ks = null;
        }
        try {
            is.close();
        } catch (IOException e) {
            logger.severe("Failed closeing " + f.getAbsolutePath() + ": " + e.getMessage());
        }
        return ks;
    }

    /**
     * Load a keystore given the path and password.  First try a JKS, then
     * try a PKCS12.
     * 
     * @param path to keystore
     * @param passwd for keystore
     * @return the keystore
     * @throws Exception
     */
    public KeyStore getKeyStore(String path, String passwd) {
        logger.finest("getting keystore..");
        if ((path == null) || (passwd == null)) {
            logger.finest("path or password is null");
            return null;
        }
        File f = new File(path);
        if (!f.canRead()) {
            logger.severe("Can't open keystore " + path + " for read");
            return null;
        }
        KeyStore ks = loadKeyStore(f, passwd, "JKS");
        if (ks == null)
            ks = loadKeyStore(f, passwd, "PKCS12");
        return (ks);
    }

    /**
     * Compare two distinguished names in an order/format independent way
     * 
     * @param dn1
     * @param dn2
     * @return true if they match
     */
    public boolean dnequals(String dn1, String dn2) {
        String[] d1 = dn1.split("[ ,]+");
        String[] d2 = dn2.split("[ ,]+");
        Arrays.sort(d1);
        Arrays.sort(d2);
        for (int i = 0; i < d1.length; i++) {
            if (i >= d2.length)
                return false;
            if (!d1[i].equalsIgnoreCase(d2[i]))
                return false;
        }
        return true;
    }

    /**
     * Find the alias associate with a DN in a keystore.  If the DN
     * is empty or null, pick the first entry and fill in a non-null DN.
     * 
     * @param ks keystore to search
     * @param dn DN to match
     * @return the alias
     * @throws KeyStoreException
     */
    public String getAlias(KeyStore ks, StringBuffer dn) {
        String alias;
        X509Certificate cert;
        String pdn = null;
        if ((dn != null) && (dn.length() > 0))
            pdn = dn.toString();

        if (ks == null)
            return null;
        try {
            Enumeration a1 = ks.aliases();
            while (a1.hasMoreElements()) {
                alias = (String) (a1.nextElement());
                cert = (X509Certificate) ks.getCertificate(alias);
                // if no dn is specified, return the first one found
                if (pdn == null) {
                    if (dn != null)
                        dn.append(cert.getSubjectDN().getName());
                    return alias;
                }
                if (dnequals(pdn, cert.getSubjectDN().getName()))
                    return alias;
                logger.finest("Skipping " + cert.getSubjectDN().getName());
            }
        } catch (KeyStoreException e) {
            logger.severe("Can't find " + dn + ": " + e.getMessage());
        }
        return null;
    }

    /**
     * Gets the private key from a keystore.  This assumes the password for
     * the key is the same as for the keystore. If the DN
     * is empty or null, pick the first entry and fill in a non-null DN.
     * 
     * @param path to keystore
     * @param passwd for keystore and entry
     * @param dn of the entry
     * @return private key
     */
    public Key getPrivateKey(String path, String passwd, StringBuffer dn) {
        return getPrivateKey(path, passwd, passwd, dn);
    }

    /**
     * Gets the private key from a keystore.  If the DN
     * is empty or null, pick the first entry and fill in a non-null DN.
     * 
     * @param path to keystore
     * @param passwd for keystore
     * @param keypass for entry
     * @param dn of the entry
     * @return private key
     */
    public Key getPrivateKey(String path, String passwd, String keypass, StringBuffer dn) {
        KeyStore ks = getKeyStore(path, passwd);
        String alias = getAlias(ks, dn);
        if (alias == null)
            return null;
        try {
            return ks.getKey(alias, keypass.toCharArray());
        } catch (Exception e) {
            logger.severe("Can't find " + dn + " in " + path + ": " + e.getMessage());
        }
        return null;
    }

    /**
     * Get a public key from an LDAP server
     * 
     * @param host (and port) of LDAP
     * @param baseDN tree to search
     * @param cn comman name to match
     * @param dn returned distinguished name on certificate
     * @return the public key
     */
    public Key getLdapKey(String host, String baseDN, String cn, StringBuffer dn) {
        Hashtable env = new Hashtable();
        String url = "ldap://" + host + "/" + baseDN;
        try {
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, url);
            DirContext context = new InitialDirContext(env);

            SearchControls ctrl = new SearchControls();
            ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE);
            NamingEnumeration enumeration = context.search("", cn, ctrl);
            if (enumeration.hasMore()) {
                SearchResult result = (SearchResult) enumeration.next();
                Attributes attribs = result.getAttributes();
                Attribute attr = attribs.get("userCertificate;binary");
                Object cert = null;
                if (attr != null)
                    cert = attr.get();
                if ((cert == null) || !cert.getClass().equals(byte[].class)) {
                    logger.severe("LDAP " + url + " has no certificate for " + cn);
                    return null;
                }
                return getPublicKey(new ByteArrayInputStream((byte[]) cert), dn);
            }
        } catch (Exception e) {
            logger.severe("Can't get key from " + url + " for " + cn + ": " + e.getMessage());
        }
        return null;
    }

    /**
      * Gets the public key from a java keystore.  If the DN
      * is empty or null, pick the first entry and fill in a non-null DN.
      * 
      * @param path to keystore
      * @param passwd for keystore and entry
      * @param dn of the entry
      * @return public key
      */
    public Key getKeyStoreKey(String path, String passwd, StringBuffer dn) {
        KeyStore ks = getKeyStore(path, passwd);
        String alias = getAlias(ks, dn);
        if (alias == null)
            return null;
        try {
            X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
            return cert.getPublicKey();
        } catch (Exception e) {
            logger.severe("Can't find " + dn + " in " + path + ": " + e.getMessage());
            return null;
        }
    }

    /**
     * Gets the public key from a DER/PEM certificate.  If the DN is not null
     * and empty fill it in.  Otherwise check to see that it matches.
     * 
     * @param path to the certificate
     * @param dn buffer to retrieve distinguished name
     * @return the public key
     */
    public Key getDerKey(String path, StringBuffer dn) {
        try {
            FileInputStream fs = new FileInputStream(path);
            return getPublicKey(fs, dn);
        } catch (Exception e) {
            logger.severe("Can't get key from " + path + ": " + e.getMessage());
            return null;
        }
    }

    /**
     * Get the public key from an input stream X509 certificate
     * 
     * @param fs stream of certificate
     * @param dn distinguished name to match or return
     * @return the public key
     */
    public Key getPublicKey(InputStream fs, StringBuffer dn) {
        if (fs == null)
            return null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(fs);
            fs.close();
            if (dn != null) {
                String n = cert.getSubjectDN().getName();
                if (dn.length() == 0)
                    dn.append(n);
                else if (!dn.toString().equals(n))
                    return null;
            }
            return cert.getPublicKey();
        } catch (Exception e) {
            logger.severe("Can't get public key: " + e.getMessage());
            return null;
        }
    }

    private IvParameterSpec getIv(int mode) {
        byte[] b = new byte[8];
        if (mode != Cipher.DECRYPT_MODE) {
            Random rand = new Random();
            rand.nextBytes(b);
        }
        return new IvParameterSpec(b);

    }

    private Cipher getCipher(int mode, Key key) {
        try {
            Cipher cipher = Cipher.getInstance(getTransform(key));
            logger.finest("cipher algorithm is: " + cipher.getAlgorithm());
            // triple DES CBC block uses 8 byte initial vector
            if (key.getAlgorithm().startsWith("DESede")) {
                cipher.init(mode, key, getIv(mode));
            } else {
                cipher.init(mode, key);
            }
            return cipher;
        } catch (Exception e) {
            logger.severe("Can't get cipher for " + key.getAlgorithm() + ": " + e.getMessage());
        }
        return null;
    }

    /**
     * Encrypts some data
     * @param data to be encrypted
     * @param key to use for the encryption
     * @return base64 encoded encrypted data
     */
    public String encrypt(byte[] data, Key key) {
        if ((data == null) || (key == null))
            return null;
        try {
            Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key);
            if (cipher == null)
                return null;
            // triple DES CBC block uses 8 byte pre-pended initial vector
            byte[] iv = cipher.getIV();
            if (iv != null)
                data = ByteArray.append(iv, data);
            byte[] d = cipher.doFinal(data);
            return new String(Base64.encode(d));
        } catch (Exception e) {
            logger.severe("Can't encrypt with " + getTransform(key) + ": " + e.getMessage());
        }
        return null;
    }

    /**
     * Encrypts a secret (DES) key, normally using an RSA method
     * 
     * @param skey the secret key
     * @param key to encrypt with
     * @return base64 encoded encryption of key
     */
    public String encryptKey(SecretKey skey, Key key) {
        byte[] d = skey.getEncoded();
        return encrypt(d, key);
    }

    /**
     * Decrypts some data
     * @param data base64 encoded to be decrypted
     * @param key to use for decryption
     * @return the decrypted data
     */
    public byte[] decrypt(String data, Key key) {
        if ((data == null) || (key == null))
            return null;
        try {
            Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key);
            if (cipher == null)
                return null;
            // triple DES CBC block uses 8 byte pre-pended initial vector
            byte[] iv = cipher.getIV();
            byte[] d = Base64.decode(data.getBytes());
            d = cipher.doFinal(d);
            if (iv != null)
                d = ByteArray.copy(d, iv.length);
            return d;
        } catch (Exception e) {
            logger.severe("Can't decrypt with " + getTransform(key) + ": " + e.getMessage());
        }
        return null;
    }

    /**
     * gets an encrypted key.  Normally decrypts using RSA for a DES key
     * 
     * @param data holding encrypted key
     * @param key to decrypt with
     * @param keymethod of key being decrypted
     * @return
     */
    public SecretKey decryptKey(String data, Key key, String transform) {
        byte[] b = decrypt(data, key);
        if (b == null)
            return null;
        return new SecretKeySpec(b, getAlgorithm(transform));
    }
}