Sunday, January 22, 2012

Difference between hashing and encryption

Neither MD5 nor SHA-1 is an "encryption" algorithm. Both are "hashing" algorithms.

In the simple sense, something that is encrypted can later be decrypted to get the original back.

Something that is properly hashed stays that way; it is a 'fingerprint' of the original pattern that cannot be 'dehashed' to reconstruct the original.

Ideally, there is only a single unique hash value that can be derived from each unique string of characters.

Of course, it's easy to prove that that's not possible when hash values are small -- small as in 160 or 256 or 512 bits. But in practice, because the strings of characters aren't random, because they're patterns of words more than just patterns of letters, the number of likely hash values gets smaller.

The value of unique hashes is that they can be used to verify that the original string hasn't been changed. If I send you a message along with a hash, you can run the message through the hash algorithm to see if the same hash value comes out. If it does, you can feel confident that you're reading exactly what was written.

Writing a password encryptor in Java

Happy New Year everyone!

In a recent project, I need to write a password encryptor so that passwords using my shell scripts are not exposed in clear text. Note that this does not stop people from getting the passwords - it just does not expose the password in clear text.

After doing some research, I've decided to use AES to encrypt my password. It will take in 2 parameters - the key and the password.

Two problems arise:
1. The encryption method outputs in bytes - I need to handle this so that I can encode the byte output as a String (text) and then be able to decode it back to byte to decrypt it back. Many of the examples out there uses sun.misc.Base64Encoder but this is not encouraged (do a google to find out why). There are alternatives. Here's a good link on this. Basically using Apache Commons Codec or Base64.

But the better way if you are using Java6 is to use import javax.xml.bind.DatatypeConverter or else use JavaMail which provides Base64 encoding/decoding via MimeUtility.

In the end I used javax.xml.bind.DatatypeConverter since I am using Java6.

2. The second problem is since I am letting the users decide what key to use, I need to be able to hash the key via MD5 or SHA, then extract the bytes from the hash into the key byte[].

A good discussion on this can be found here.

Note: here's a good link on using DES to do this.

Anyway, here's my final code:
import javax.crypto.Cipher;
import javax.crypto.spec.*;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.bind.DatatypeConverter;


public class AES {

// init vector for randomness
static byte[] iv = { (byte) 0xcb, (byte) 0x53, (byte) 0x03, (byte) 0x0f,
(byte) 0xe0, (byte) 0x79, (byte) 0x9d, (byte) 0xdc, (byte) 0x80,
(byte) 0xa9, (byte) 0x83, (byte) 0xf1, (byte) 0x03, (byte) 0xb6,
(byte) 0x59, (byte) 0x83 };


public static void encryptText(String text, String password) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(getKeyFromPassword(password), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
byte[] encrypted = aes.doFinal((text.getBytes()));
//System.out.println("Encrypted string: " + DatatypeConverter.printBase64Binary(encrypted));
System.out.println(DatatypeConverter.printBase64Binary(encrypted));
}

public static void decryptText(String encrypted, String password) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(getKeyFromPassword(password), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
//Cipher cipher = Cipher.getInstance("AES");
//cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] original = aes.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
String originalString = new String(original);
// System.out.println("Decrypted string: " + originalString + " " + asHex(original));
System.out.println(originalString);
}


public static byte[] md5sum(byte[] buffer) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(buffer);
return md5.digest();
} catch (NoSuchAlgorithmException e) {
System.out.println("MD5 Algorithm not available on this machine");
}
return null;
}

// converts key to machine readable form
public static byte[] getKeyFromPassword(String s) {
return md5sum(s.toString().getBytes());
}

// prints pretty error messages and exits
public static void errorExit(String s) {
System.out.println(s);
System.exit(0);
}

public static void main(String[] args) throws Exception {
String usage = "Usage: java AES [-e | -d] [text] [password]";

if (args.length < 2 || args.length > 3) {
errorExit(usage);
}
if (args[0].equalsIgnoreCase("-e")) {
encryptText(args[1], args[2]);
} else if (args[0].equalsIgnoreCase("-d")) {
decryptText(args[1], args[2]);
} else {
System.out.println(usage);
}
Runtime.getRuntime().gc(); // force flush
}
}