Andras
Andras

Reputation:

Difference between Java and php5 MD5 Hash

I'm facing kind of a strange issue which is related MD5-Hashes in Java and php5. I figured that unter certain circumstances the following code does not generate correct MD5 hashes:

public static String getMD5Hash(String string)
{
    try 
    {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update(string.getBytes());
        byte[] digest = md5.digest();

        string = byteArrToHexString(digest);
    } 
    catch (NoSuchAlgorithmException e1) 
    {
        e1.printStackTrace();
    }

    return string;
}

private static String byteArrToHexString(byte[] bArr)
{
    StringBuffer sb = new StringBuffer();

    for (int i = 0; i < bArr.length; i++) 
    {
        int unsigned = bArr[i] & 0xff;
        sb.append(Integer.toHexString((unsigned)));
    }

    return sb.toString();
}

I had to migrate an existing user database where the passwords where stored in php5 MD5. Now some of the users, not all, can't login because my Java code does not produce the correct MD5 hash.

Any ideas what's wrong with the above?

Upvotes: 1

Views: 3414

Answers (4)

android developer
android developer

Reputation: 116402

I've found 2 solutions (found from here and from the other answers) :

object MD5Util {
    private val messageDigest: MessageDigest?

    init {
        val testMd =
                try {
                    MessageDigest.getInstance("MD5")
                } catch (e: Exception) {
                    null
                }
        messageDigest = testMd
    }

    private fun hex(array: ByteArray): String {
        val sb = StringBuilder()
        for (b in array)
            sb.append(Integer.toHexString((b.toInt() and 0xFF) or 0x100).substring(1, 3))
        return sb.toString()
    }

    @JvmStatic
    fun md5Hex(message: String): String {
        if (messageDigest != null)
            try {
                return hex(messageDigest.digest(message.toByteArray(charset("CP1252"))))
            } catch (e: Exception) {
                throw e
            }
        throw Exception("messageDigest not found")
    }

    @JvmStatic
    fun md5(s: String): String {
        if (messageDigest != null)
            try {
                val hash: ByteArray = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                    messageDigest.digest(s.toByteArray(StandardCharsets.UTF_8))
                else
                    messageDigest.digest(s.toByteArray(charset("UTF-8")))
                val sb = StringBuilder()
                for (aHash in hash) {
                    val hex = Integer.toHexString(aHash.toInt())
                    if (hex.length == 1)
                        sb.append('0').append(hex[hex.length - 1])
                    else
                        sb.append(hex.substring(hex.length - 2))
                }
                return sb.toString()
            } catch (e: Exception) {
                throw e
            }
        throw Exception("messageDigest not found")
    }
}

According to some benchmarks, I can say that the md5 function is about twice faster than the md5Hex function. Here's the test:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        AsyncTask.execute {
            val emailList=ArrayList<String>()
            for (i in 0 until 100000)
                emailList.add( generateRandomEmail(10))
            var startTime = System.currentTimeMillis()
            for (email in emailList)
                MD5Util.md5(email)
            var endTime = System.currentTimeMillis()
            Log.d("AppLog", "md5 - time taken: ${endTime - startTime}")
            startTime = System.currentTimeMillis()
            for (email in emailList)
                MD5Util.md5Hex(email)
            endTime = System.currentTimeMillis()
            Log.d("AppLog", "md5Hex - time taken: ${endTime - startTime}")
        }
    }

    companion object {
        private const val ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyz" + "1234567890" + "_-."

        @Suppress("SpellCheckingInspection")
        fun generateRandomEmail(@IntRange(from = 1) localEmailLength: Int, host: String = "gmail.com"): String {
            val firstLetter = RandomStringUtils.random(1, 'a'.toInt(), 'z'.toInt(), false, false)
            val temp = if (localEmailLength == 1) "" else RandomStringUtils.random(localEmailLength - 1, ALLOWED_CHARS)
            return "$firstLetter$temp@$host"
        }
    }
}

gradle file has this:

implementation 'org.apache.commons:commons-lang3:3.7'

Upvotes: 0

Yuval
Yuval

Reputation: 8087

So funny... I just encountered a problem with MD5 hashed passwords myself. The problem in my case was the encoding of the original password into a byte[].

I advise you to find out exactly which encoding was used to hash the passwords previously, and change line 6 of the code above to

md5.update(string.getBytes("UTF-8"));

(Of course, this is just an example... find out the correct Charset to use as a parameter)

BTW, I suppose you have your reasons, but why not have the hashing method do this?

return new String(digest, "UTF-8");

Yuval =8-)

Upvotes: 1

laalto
laalto

Reputation: 152827

byteArrToHexString does not convert bytes <0x10 correctly, you need to pad them with zeros.

Example:

int unsigned = bArr[i] & 0xff;
if (unsigned < 0x10)
  sb.append("0");
sb.append(Integer.toHexString((unsigned)));

Upvotes: 10

Macarse
Macarse

Reputation: 93143

You are missing:

md5.reset();

before doing an update()

Check Java md5 example with MessageDigest

Upvotes: 0

Related Questions