Archive for February, 2012


I got to thinking about passwords last night because I couldn’t sleep and I wanted to clarify something.  Many people have asked me, “If you use numbers and special characters add to the “hardness” of a password?”

Initially I say “No, but it adds to the key space” (key space is the total number of possibilities that an attacker must try).  What I actually mean to say is “Yes, it does increase the “hardness” of the password (and increases the key space), but it is not strictly necessary to use.”

One of the tips I like to use is to create a password that is composed of several words that you like or are particularly memorable.  For example, you could probably look around your office or home and do this.  Looking around me, I could choose projectorchocolateglobemarker (you could choose something else that is easier to remember and I probably would too, but this is just an example).

Consider this: if a password was composed of lower case, upper case, numbers and special characters, that would be 72 characters to chose from.  If your mean system administrator said that your password had to be composed of 8 characters combining those 4 types of characters, then the total number of combinations would be ~7.22 x 10^14 (assuming a password length of 8 characters).  That’s a lot of possible combinations a cracker/hacker must try!

Now consider the password where I ran the words together (projectorchocolateglobemarker).  This would be resistant to a dictionary attack.  A dictionary attack uses a targeted technique of successively trying all the words in an exhaustive list called a dictionary (from a pre-arranged list of values).  Commonly, these pre-arranged lists are actually words from a dictionary because people are lazy and they will make their password is easy to remember (not through any fault of their own but humans are notoriously bad at remembering things that doesn’t make sense or they have no connection to).   While those individual words (projector, chocolate, globe and marker) would all be in the dictionary, the concatenation of them would not.  While we have fewer total characters to choose from (26 lower case characters), the total number possible combinations for the password would be orders of magnitude larger.  In fact, it would be ~1.052 x 10^41.  That is a much larger key space!

Now, if you haven’t glossed over by now, consider a computer that could do 1 key attempt per microsecond (which is certainly not out of the realm of possibilities), that is about 1 million key attempts (to crack) per second.  The first password would take 722M seconds (22 years), the second would take ~10^27 years.  Clearly, we can see which is more secure (and I didn’t need a lot of characters to do it).  These longer, harder passwords, are also more immune to what we call “rainbow table” attacks.

A lot of the password stuff is what is called “security theater.”  Security theater is a term that describes security countermeasures intended to provide the feeling of improved security while doing little or nothing to actually improve security.  Having a system administrator create policies that don’t make sense (such as the crazy combinations of letters, numbers, special chars) when the password is far less secure is an example of security theater.  Bruce Schneier uses it a lot to describe TSA security.

Another way to create a memorable password is to think of a memorable phrase such as “my sister likes to eat juicy orange every day”.  Then, take the first letter of each word and combine them to make your password.  In this example, your password would be msltejoed.

Lastly, I wanted to mention one last tip.  If a user could setup a policy that would lock out after a certain number of failed attempts, such as 3, this is the most secure way to do a pass-worded system because it wouldn’t allow an attacker to do a “brute force” attack where they try all the different types of keys.

I recently had a need to build a licensing module in Java for a project I was working on.  All of the modules out there cost money, so I figured that I would release a free one.  What is neat about this is that it is pretty simple easy and fast to implement and it comes with a license key generator.  The best part is that it relies on PKI, so unless someone were able to patch the binary to skip the authentication check, they would have to be able to break a 2048bit RSA key (which is pretty safe considering no one has broken a 1024bit RSA key).

This particular licensing module has support for multiple license types: Trial, Single Version and Lifetime.  Also, the license has support for information such as name, email, license number, license type, expiration date and version number.  There is support for blacklisted, invalid, phony, and expired keys.  The one thing I want to mention before we get into the code is that you’ll need a public and private keypair (you can use OpenSSL to do this) in .der format (an X509 certificate using OpenSSL again).  So without further ado…

Licensing code zip

License.java: This is your license object.  It will be written to disk and checked with the program starts.  It contains all the information the managers will need to check the license.

package License;

import java.io.*;
import java.util.*;

public class License implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String email;
    private String licenseNumber;
    private LicenseType licenseType;
    private Date expiration;
    private String version;

    public License() {
        name = "";
        email = "";
        licenseNumber = "";
        expiration = new Date();
        version = "";
        licenseType = LicenseType.TRIAL;
    }

    public License(String name, String email, String licenseNumber, Date expiration, LicenseType licenseType, String version) {
        this.name = name;
        this.email = email;
        this.licenseNumber = licenseNumber;
        this.expiration = expiration;
        this.licenseType = licenseType;
        this.version = version;
    }

    // getters and setters here
}

KeyStatus.java: This is an enumeration that returns the status of a key validation operation.

package License;

public enum KeyStatus {
    KEY_GOOD,
    KEY_INVALID,
    KEY_BLACKLISTED,
    KEY_PHONY,
    KEY_EXPIRED
}

LicenseType.java: This is an enumeration that represents the type of license being generated.

package License;

public enum LicenseType {
    TRIAL, SINGLE_VERSION, LIFETIME
}

LicenseFileFilter.java: This is purely for usability on the UI end.  We can provide the JFileChooser this filter and it will find only our license files.

package License;

import Utility.FileExtension;
import java.io.File;
import javax.swing.filechooser.FileFilter;

public class LicenseFileFilter extends FileFilter {
    public boolean accept(File f) {
        return f.isDirectory() || f.getName().toLowerCase().endsWith(FileExtension.ZIP);
    }

    public String getDescription() {
        return "License files";
    }
}

LicenseManager.java: This is the main workhorse for the licensing code.  It generates the keys and checks them.

package License;

import java.io.*;
import java.net.*;
import java.security.*;
import java.util.*;
import javax.swing.JOptionPane;

public class LicenseManager {
    private static LicenseManager instance;
    public static boolean IS_TRIAL = true;
    public static boolean IS_LICENSED =  false;
    public static License LICENSE = null;
    private static final int ENTROPY = 456456456;
    private static final String HEXES = "0123456789ABCDEF";

    public static final String LICENSE_FILENAME = "license";
    public static final String HASH_FILENAME = "license.sha1";
    public static final String SIGNATURE_FILENAME = "license.sig";

    private static final int KEY_LEN = 62;
    private static final byte[] def = new byte[]{24, 4, 124, 10, 91};
    private static final byte[][] params = new byte[][]{{24, 4, 127}, {10, 0, 56}, {1, 2, 91}, {7, 1, 100}};
    private static final Set blacklist = new TreeSet();

    private Timer t;
    private static final int DELAY = 900000;

    static {
        blacklist.add("11111111");
    }

    protected LicenseManager() {
        t = new Timer();
        t.scheduleAtFixedRate(new CheckLicenseTask(), DELAY, DELAY);
    }

    public static LicenseManager getLicenseManager() {
        if (instance == null) {
            instance = new LicenseManager();
        }

        return instance;
    }

    /**
     *
     * @param lic
     */
    private void writeLicenseFile(License lic, String path) {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(path + LICENSE_FILENAME)));
            oos.writeObject(lic);
            oos.close();
        }
        catch(Exception ex) { }
    }

    public static KeyStatus readLicenseFile(String licensePath, String signaturePath, String hashPath) {
        try {
            // read in file and validate the LICENSE based on the signature
            // this will remove changes of faking a LICENSE file
            // the LICENSE file has to be signed with our key
            File licenseFile = new File(licensePath);
            File signatureFile = new File(signaturePath);
            File hashFile = new File(hashPath);

            KeyStatus status = EncryptionManager.getEncryptionManager().verify(licenseFile, signatureFile, hashFile);

            if(!status.equals(KeyStatus.KEY_GOOD)) {
                return KeyStatus.KEY_INVALID;
            }

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(licenseFile));
            LICENSE = (License)ois.readObject();

            String lic = LICENSE.getLicenseNumber();
            if(LICENSE.getLicenseType().equals(LicenseType.TRIAL)) {
                IS_TRIAL = true;

                Calendar c = Calendar.getInstance();
                if(c.getTime().after(LICENSE.getExpiration())) {
                    return KeyStatus.KEY_EXPIRED;
                }

                Date expiration = LicenseManager.getLicenseManager().LICENSE.getExpiration();

                long val = expiration.getTime() - c.getTime().getTime();
                val /= (1000 * 60 * 60 * 24);
                JOptionPane.showMessageDialog(null, "This is a trial version of the software.  You have " + val + " days remaining in your trial.", "Trial Version", JOptionPane.INFORMATION_MESSAGE);
            }
            else if(LICENSE.getLicenseType().equals(LicenseType.SINGLE_VERSION)) {
                IS_TRIAL = false;
                status = checkKey(lic);
                IS_LICENSED = true;
            }
            else if(LICENSE.getLicenseType().equals(LicenseType.LIFETIME)) {
                IS_TRIAL = false;
                status = checkKey(lic);
                IS_LICENSED = true;
            }

            if(!status.equals(KeyStatus.KEY_GOOD)) {
                return status;
            }

            return KeyStatus.KEY_GOOD;
        }
        catch(Exception ex) {
            System.out.println(ex.toString());
            return KeyStatus.KEY_INVALID;
        }
    }

    /**
     *
     * @param name
     * @param email
     * @param authCode
     * @param licenseType
     * @param expiration
     * @param version
     * @param path
     */
    public void createLicense(String name, String email, String authCode, LicenseType licenseType, Date expiration, String version, String path) {
        byte[] entropy = null;

        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            digest.reset();
            entropy = digest.digest(getByteArrayFromHexString(authCode));
        }
        catch(NoSuchAlgorithmException ex) { /* this will never happen */ }

        License lic = new License(name, email, LicenseManager.makeKey(ENTROPY, entropy), expiration, licenseType, version);
        writeLicenseFile(lic, path);
    }

    /**
     *
     * @return
     */
    private static byte[] getHardwareEntropy() {
        byte[] mac;
        try {
            NetworkInterface ni = NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
            if (ni != null) {
                mac = ni.getHardwareAddress();
                if (mac == null) {
                    mac = def;
                }
            } else {
                mac = def;
            }
        }
        catch (Exception ex) {
            mac = def;
        }

        byte[] entropyEncoded = null;
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            digest.reset();
            entropyEncoded = digest.digest(mac);
        }
        catch(NoSuchAlgorithmException ex) { /* this will never happen */ }

        return entropyEncoded;
    }

    /**
     *
     * @param seed
     * @param a
     * @param b
     * @param c
     * @return
     */
    private static byte getKeyByte(final int seed, final byte a, final byte b, final byte c) {
        final int a1 = a % 25;
        final int b1 = b % 3;
        if (a1 % 2 == 0) {
            return (byte) (((seed >> a1) & 0x000000FF) ^ ((seed >> b1) | c));
        } else {
            return (byte) (((seed >> a1) & 0x000000FF) ^ ((seed >> b1) & c));
        }
    }

    /**
     *
     * @param s
     * @return
     */
    private static String getChecksum(final String s) {
        int left = 0x0056;
        int right = 0x00AF;
        for (byte b : s.getBytes()) {
            right += b;
            if (right > 0x00FF) {
                right -= 0x00FF;
            }
            left += right;
            if (left > 0x00FF) {
                left -= 0x00FF;
            }
        }
        int sum = (left << 8) + right;
        return intToHex(sum, 4);
    }

    /**
     *
     * @param seed
     * @param entropy
     * @return
     */
    public static String makeKey(final int seed, byte[] entropy) {
        // fill keyBytes with values derived from seed.
        // the parameters used here must be exactly the same
        // as the ones used in the checkKey function.
        final byte[] keyBytes = new byte[25];
        keyBytes[0] = getKeyByte(seed, params[0][0], params[0][1], params[0][2]);
        keyBytes[1] = getKeyByte(seed, params[1][0], params[1][1], params[1][2]);
        keyBytes[2] = getKeyByte(seed, params[2][0], params[2][1], params[2][2]);
        keyBytes[3] = getKeyByte(seed, params[3][0], params[3][1], params[3][2]);
        for(int i = 4, j = 0; (j + 2) < entropy.length; i++, j += 3) {
            keyBytes[i] = getKeyByte(seed, entropy[j], entropy[j + 1], entropy[j + 2]);
        }      

        // the key string begins with a hexadecimal string of the seed
        final StringBuilder result = new StringBuilder(intToHex(seed, 8));

        // then is followed by hexadecimal strings of each byte in the key
        for (byte b : keyBytes) {
            result.append(intToHex(b, 2));
        }

        // add checksum to key string
        String key = result.toString();
        key += getChecksum(key);

        return key;
    }

    /**
     *
     * @param key
     * @return
     */
    private static boolean validateKeyChecksum(final String key) {
        if (key.length() != KEY_LEN) {
            return false;
        }

        // last four characters are the checksum
        final String checksum = key.substring(KEY_LEN - 4);
        return checksum.equals(getChecksum(key.substring(0, KEY_LEN - 4)));
    }

    /**
     *
     * @param key
     * @return
     */
    public static KeyStatus checkKey(final String key) {
        if (!validateKeyChecksum(key)) {
            return KeyStatus.KEY_INVALID; // bad checksum or wrong number of
            // characters
        }

        // test against blacklist
        for (String bl : blacklist) {
            if (key.startsWith(bl)) {
                return KeyStatus.KEY_BLACKLISTED;
            }
        }

        // at this point, the key is either valid or forged,
        // because a forged key can have a valid checksum.
        // we now test the "bytes" of the key to determine if it is
        // actually valid.

        // when building your release application, use conditional defines
        // or comment out most of the byte checks! this is the heart
        // of the partial key verification system. by not compiling in
        // each check, there is no way for someone to build a keygen that
        // will produce valid keys. if an invalid keygen is released, you can
        // simply change which byte checks are compiled in, and any serial
        // number built with the fake keygen no longer works.

        // note that the parameters used for getKeyByte calls MUST
        // MATCH the values that makeKey uses to make the key in the
        // first place!

        // extract the seed from the supplied key string
        final int seed;
        try {
            seed = Integer.valueOf(key.substring(0, 8), 16);
        } catch (NumberFormatException e) {
            return KeyStatus.KEY_PHONY;
        }

        // test key 0
        final String kb0 = key.substring(8, 10);
        final byte b0 = getKeyByte(seed, params[0][0], params[0][1], params[0][2]);
        if (!kb0.equals(intToHex(b0, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test key1
        final String kb1 = key.substring(10, 12);
        final byte b1 = getKeyByte(seed, params[1][0], params[1][1], params[1][2]);
        if (!kb1.equals(intToHex(b1, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test key2
        final String kb2 = key.substring(12, 14);
        final byte b2 = getKeyByte(seed, params[2][0], params[2][1], params[2][2]);
        if (!kb2.equals(intToHex(b2, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test key3
        final String kb3 = key.substring(14, 16);
        final byte b3 = getKeyByte(seed, params[3][0], params[3][1], params[3][2]);
        if (!kb3.equals(intToHex(b3, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test the hardware entropy
        byte[] encodedEntropy = getHardwareEntropy();
        for(int i = 16, j = 0; (j + 2) < encodedEntropy.length; i += 2, j += 3) {
String kb = key.substring(i, i + 2);             byte b = getKeyByte(seed, encodedEntropy[j], encodedEntropy[j + 1], encodedEntropy[j + 2]);             if(!kb.equals(intToHex(b, 2))) {                 return KeyStatus.KEY_INVALID;             }         }         // if we get this far, then it means the key is either good, or was made         // with a keygen derived from "this" release.         return KeyStatus.KEY_GOOD;     }     /**      *       * @param n      * @param chars      * @return       */     protected static String intToHex(final Number n, final int chars) {         return String.format("%0" + chars + "x", n);     }          /**      *       * @param raw      * @return       */     public static String getHexStringFromBytes(byte[] raw) {         if ( raw == null ) {             return null;         }         final StringBuilder hex = new StringBuilder( 2 * raw.length );         for ( final byte b : raw ) {             hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    /**
     *
     * @param s
     * @return
     */
    public static byte[] getByteArrayFromHexString(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    private class CheckLicenseTask extends TimerTask {
        public CheckLicenseTask() { }

        @Override
        public void run() {
            System.out.println("checking license");
        }
    }
}

EncryptionManager.java: This module will manage encryption, decryption, signing and validation operations using your keys.  It is a singleton object.

package License;

import Utility.Logger;
import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import javax.crypto.*;

public class EncryptionManager {

    private static EncryptionManager instance;
    // this file should be in your jar
    private static final String PUBLIC_KEY_FILE = "/License/public_key.der";
    // this file will be on your hard drive
    private static final String PRIVATE_KEY_FILE = "/path/to/your/private_key.der";
    private static PublicKey publicKey;
    private static PrivateKey privateKey;

    protected EncryptionManager() throws GeneralSecurityException {
    }

    public static EncryptionManager getEncryptionManager() {
        if (instance == null) {
            try {
                instance = new EncryptionManager();

                try {
                    privateKey = loadPrivateKey(PRIVATE_KEY_FILE);
                }
                catch (Exception ex) {
                    //Logger.getLogger().ALog("private key failed to load - couldn't instantiate encryption manager.\n" + ex.toString());
                }
                try {
                    publicKey = loadPublicKey(PUBLIC_KEY_FILE);
                }
                catch (Exception ex) {
                    //Logger.getLogger().ALog("public key failed to load - couldn't instantiate encryption manager.\n" + ex.toString());
                }
            }
            catch(GeneralSecurityException ex) {
                //Logger.getLogger().ALog("couldn't instantiate encryption manager.\n" + ex.toString());
            }
        }

        return instance;
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    private static PublicKey loadPublicKey(String filename) throws Exception {
        DataInputStream dis = new DataInputStream(File.class.getResourceAsStream(filename));
        byte[] keyBytes = new byte[dis.available()];
        dis.readFully(keyBytes);
        dis.close();

        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    private static PrivateKey loadPrivateKey(String filename) throws Exception {
        File f = new File(filename);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }

    /**
     *
     * @param dataToHashPath
     * @return
     */
    public static byte[] digest(File dataToHashPath) {
        try {
            InputStream fin = new FileInputStream(dataToHashPath);
            MessageDigest md5Digest = MessageDigest.getInstance("MD5");

            byte[] buffer = new byte[1024];
            int read;

            do {
                read = fin.read(buffer);
                if (read > 0) {
                    md5Digest.update(buffer, 0, read);
                }
            } while (read != -1);
            fin.close();

            byte[] digest = md5Digest.digest();
            if (digest == null) {
                return null;
            }

            return digest;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     *
     * @param dataToVerify
     * @param signatureFile
     * @param hashFile
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws NoSuchPaddingException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static KeyStatus verify(File dataToVerify, File signatureFile, File hashFile) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
            SignatureException, NoSuchPaddingException, FileNotFoundException, IOException {
        // first validate the hash of the file
        FileInputStream hashfis = new FileInputStream(hashFile);
        byte[] hashToVerify = new byte[hashfis.available()];
        hashfis.read(hashToVerify);
        hashfis.close();

        byte[] licenseBytes = digest(dataToVerify);
        if(!Arrays.equals(licenseBytes, hashToVerify)) {
            Logger.getLogger().ALog("key failed to pass hash check");
            return KeyStatus.KEY_INVALID;
        }

        // now validate that we were the ones who shipped it
        Signature rsaSignature = Signature.getInstance("SHA1withRSA");
        rsaSignature.initVerify(publicKey);

        FileInputStream sigfis = new FileInputStream(signatureFile);
        byte[] sigToVerify = new byte[sigfis.available()];
        sigfis.read(sigToVerify);
        sigfis.close();

        FileInputStream datafis = new FileInputStream(hashFile);
        BufferedInputStream bufin = new BufferedInputStream(datafis);

        byte[] buffer = new byte[1024];
        int len;
        while (bufin.available() != 0) {
            len = bufin.read(buffer);
            rsaSignature.update(buffer, 0, len);
        };

        bufin.close();

        if (rsaSignature.verify(sigToVerify)) {
            return KeyStatus.KEY_GOOD;
        } else {
            Logger.getLogger().ALog("key failed to pass signature check");
            return KeyStatus.KEY_INVALID;
        }
    }

    /**
     *
     * @param dataToSign
     * @param signatureFilePath
     * @param hashFilePath
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void sign(byte[] dataToSign, String signatureFilePath, String hashFilePath) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
            SignatureException, FileNotFoundException, IOException {
        // initialize the signing algorithm with our private key
        Signature rsaSignature = Signature.getInstance("SHA1withRSA");
        rsaSignature.initSign(privateKey);
        rsaSignature.update(dataToSign, 0, dataToSign.length);

        // sign it
        byte[] sig = rsaSignature.sign();

        // save the signature to disk to verify later
        FileOutputStream fos = new FileOutputStream(signatureFilePath);
        fos.write(sig);
        fos.close();

        fos = new FileOutputStream(hashFilePath);
        fos.write(dataToSign);
        fos.close();
    }
}
All code owned and written by David Stites and published on this blog is licensed under MIT/BSD.