views:

88

answers:

2

I'm having a problem with MessageDigest returning different hash values on different computers.

One computer is running 32-bit Java on Windows Vista and the other is running 64-bit Java on Mac OS. I'm not sure if it is because MessageDigest is machine dependent, or I need to explicitly specify a character encoding somewhere, or perhaps something else. Here's the code:

public static boolean authenticate(String salt, String encryptedPassword, 
    char[] plainTextPassword ) throws NoSuchAlgorithmException {

    // do I need to explcitly specify character encoding here? -->
    String saltPlusPlainTextPassword = salt + new String(plainTextPassword);

    MessageDigest sha = MessageDigest.getInstance("SHA-512");

    // is this machine dependent? -->
    sha.update(saltPlusPlainTextPassword.getBytes());
    byte[] hashedByteArray = sha.digest();

    // or... perhaps theres a translation problem here? -->
    String hashed = new String(hashedByteArray);

    return hashed.equals(encryptedPassword);
}

Should this code execute differently on these two different machines? If it is machine dependent the way I've written it, is there another way hash these passwords that is more portable? Thanks!

Edit:::::

This is the code I'm using to generate the salts:

public static String getSalt() {
   int size = 16;
   byte[] bytes = new byte[size];
   new Random().nextBytes(bytes);
   return org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString(bytes);
}

Solution:::

Thanks to the accepted solution, I was able to fix my code:

public static boolean authenticate_(String salt, String encryptedPassword, 
        char[] plainTextPassword ) throws NoSuchAlgorithmException, UnsupportedEncodingException {

        // This was ok
        String saltPlusPlainTextPassword = salt + new String(plainTextPassword);    

        MessageDigest sha = MessageDigest.getInstance("SHA-512");

        // must specify "UTF-8" encoding
        sha.update(saltPlusPlainTextPassword.getBytes("UTF-8"));
        byte[] hashedByteArray = sha.digest();

        // Use Base64 encoding here -->
        String hashed = org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString(hashedByteArray);

        return hashed.equals(encryptedPassword);
    }
+5  A: 

Encodings are causing you problems. First here:

saltPlusPlainTextPassword.getBytes()

That will use the default encoding for the machine. Bad idea. Specify "UTF-8" as a simple solution. (It's guaranteed to be present.)

Next this causes issues:

String hashed = new String(hashedByteArray);

hashedByteArray is arbitrary binary data. To safely convert it to text, either use a base-64 encoding or just hex. Again, you're currently using the default encoding, which will vary from machine to machine. There are loads of 3rd party libraries for base64 encoding in Java.

Jon Skeet
+1  A: 

Likely Jon Skeet's solution above is the cause, and his recommendations should definitely be taken into account, but another possible cause is a misunderstanding of salt.

Salt is a semi-secret random value that is applied to a String prior to hashing. This makes it harder to perform a brute force attack when trying to guess what an originating String was because the salt is presumably unknown to the attacker.

Salt values generally differ installation to installation. Its possible that the actual cause is just that you have the salt values set differently on the different machines.

tschaible
Cool, I added the code I'm using to generate the salts above. Thanks for your response, I'll upvote in a few hours when it lets me again.
DutrowLLC
@DutrowLLC: Given that you're already using commons codec, that's how you should base-64 encode the hash.
Jon Skeet