Introduction
Even after the global wake-up call in 2012, when companies LinkedIn and eHarmony learned the hard way that it is really advisable to properly hash passswords using salts, insecure unsalted methods are still in use by 2014. This sample program shows that it is not so hard to improve.
To simplify our life, we use the built-in base64 function in Java, just know that we are discouraged to do so (compiler warnings). For production use, there are various alternatives to implement base64, see also here.
Also, the 1000 hashing iterations are not absolutely necessary. Per OWASP, they are advisable to further strengthen the process against brute-force attacks.
Code
/* --------------------------------------------------------------------- *
* file: pw_hashing.java v1.0 *
* purpose: Demonstrates secure password encoding, using salts *
* author: 04/22/2014 Frank4DD *
* *
* This program encodes a sample password string using SHA-256 algorithm *
* and a salt. Base64 function uses the internal, discouraged version *
* for simplicity. Code Ref: http://www.owasp.org/index.php/Hashing_Java *
* *
* D:\Code\Java>java pw_hashing *
* Raw Password: 12345 *
* SHA-256 Hash: 0Hcopge+zUa9C3w24MjhyAxC31GxPNLV+KknAVtb/nE= *
* Random Salt: IZp4cgDoAQQ= *
* --------------------------------------------------------------------- */
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import sun.misc.BASE64Encoder;
import java.util.Arrays;
import java.security.SecureRandom;
public class pw_hashing {
public static void main (String args[]) {
// The number of hash iterations
private static final int iterations = 1000;
// Our exemplary test password
private static final String password="12345";
// Algorithm is either MD5, SHA-1, SHA-256 (MD5 is discouraged)
private static final String algorithm="SHA-256";
// Create a random salt, returning 64-bit (8 bytes) of binary data
SecureRandom sr = new SecureRandom();
byte[] bSalt = new byte[8];
sr.nextBytes(bSalt);
try {
// Select the digest for the hash computation
MessageDigest digest = MessageDigest.getInstance(algorithm);
digest.reset();
// Pass the salt to the digest for the computation
digest.update(bSalt);
byte[] bDigest = digest.digest(password.getBytes("UTF-8"));
// Iterate (makes brute-force computation more time consuming)
for (int i = 0; i < iterations; i++) {
digest.reset();
bDigest = digest.digest(bDigest);
}
// Since it is best to use string representations over
// raw binary data, we convert the digest and salt to base64.
BASE64Encoder b64encoder = new BASE64Encoder();
System.out.println ("Raw Password: " + password);
System.out.println (algorithm + " Hash: " + b64encoder.encode(bDigest));
System.out.println (" Random Salt: " + b64encoder.encode(bSalt));
} catch (java.security.NoSuchAlgorithmException | java.io.UnsupportedEncodingException e) {
System.out.println ("No such Algorithm or Encoding.");
}
}
}
A run of this test program returns the following output:
D:\Code\Java>java pw_hashing Raw Password: 12345 SHA-256 Hash: 0Hcopge+zUa9C3w24MjhyAxC31GxPNLV+KknAVtb/nE= Random Salt: IZp4cgDoAQQ=