A Scala method to create an MD5 hash of a string

If you happen to need a Scala method to perform an MD5 hash on a string, I believe this method works as desired:

// returns a 32-character MD5 hash version of the input string
def md5HashPassword(usPassword: String): String = {
    import java.math.BigInteger
    import java.security.MessageDigest
    val md = MessageDigest.getInstance("MD5")
    val digest: Array[Byte] = md.digest(usPassword.getBytes)
    val bigInt = new BigInteger(1, digest)
    val hashedPassword = bigInt.toString(16).trim
    prependWithZeros(hashedPassword)
}

/**
 * This uses a little magic in that the string I start with is a
 * “format specifier,” and it states that the string it returns
 * should be prepended with blank spaces as needed to make the
 * string length equal to 32. Then I replace those blank spaces
 * with the character `0`.
 */
private def prependWithZeros(pwd: String): String =
    "%1$32s".format(pwd).replace(' ', '0')

Note that this code fixes a bug that my previous version had. The previous version of the code could return a string that was less than 32 characters in length. But this version correctly returns a string that is padded with zeros at the beginning, as necessary, as shown with this Scala REPL example:

scala> md5HashPassword("a")
val res0: String = 0cc175b9c0f1b6a831c399e269772661

The code needs more testing ... and fortunately there’s Scala-CLI.

Testing this with Scala-CLI

Thanks to the awesome Scala-CLI project I was able to easily create some tests for this. Given this file named MD5Hash.scala:

package foo

object MD5 {

    def md5HashPassword(usPassword: String): String = {
        import java.math.BigInteger
        import java.security.MessageDigest
        val md = MessageDigest.getInstance("MD5")
        val digest: Array[Byte] = md.digest(usPassword.getBytes)
        val bigInt = new BigInteger(1, digest)
        val hashedPassword = bigInt.toString(16).trim
        prependWithZeros(hashedPassword)
    }

    /**
     * This uses a little magic in that the string I start with is a
     * format specifier, and it states that the string it returns
     * should be prepended with blank spaces as needed to make the
     * string length equal to 32. Then I replace those blank spaces
     * with the character `0`.
     */
    private def prependWithZeros(pwd: String): String =
        "%1$32s".format(pwd).replace(' ', '0')

}

And this file named MD5Hash.test.scala:

//> using lib "org.scalameta::munit::0.7.27"
package foo

import MD5._

class MD5Tests extends munit.FunSuite {

    test("a, b") {
        assert(md5HashPassword("a") == "0cc175b9c0f1b6a831c399e269772661")
        assert(md5HashPassword("b") == "92eb5ffee6ae2fec3ad71c777531578f")
    }

    test("foo, bar, foobar") {
        assert(md5HashPassword("foo")    == "acbd18db4cc2f85cedef654fccc4a4d8")
        assert(md5HashPassword("bar")    == "37b51d194a7513e45b56f6524f2d51f2")
        assert(md5HashPassword("foobar") == "3858f62230ac3c915f300c664312c63f")
    }

    test("known to return leading zeros") {
        assert(md5HashPassword("363") == "00411460f7c92d2124a67ea0f4cb5f85")
        assert(md5HashPassword("jk8ssl") == "0000000018e6137ac2caab16074784a6")
        assert(md5HashPassword("j(R1wzR*y[^GxWJ5B>L{-HLETRD") == "00000000000003695b3ae70066f60d42")
    }

}

Note that the //> statement needs to be the first line of this file. The scala-cli command sees that line and then does the magic that it needs to do to make MUnit (and all of its dependencies) available to your project.

I was able to run those MUnit tests by putting those two files in the same directory and then running this Scala-CLI test command:

$ scala-cli test . --scala 2.13

In March, 2022, the output of that command looks like this:

Compiling project (test, Scala 2.13.8, JVM)
Compiled project (test, Scala 2.13.8, JVM)
foo.MD5Tests:
  + a, b 0.084s
  + foo, bar, foobar 0.005s
  + known to return leading zeros 0.003s

Technically it’s not necessary to specify the Scala version, so you can also use this command if you prefer:

$ scala-cli test .

It’s really cool how easy Scala-CLI makes it to create and test a small project like this!

Notes: How I came down this MD5 hash road

I’m currently writing a Play Framework (Scala) web application that sits on top of an old Drupal 6 database, and that version of Drupal stored the user passwords in the database using MD5 (without any sort of salt), and I can confirm that this Scala method converts a given string to the same MD5 string that Drupal 6 put into that database.

Please note that this Scala code is a pretty direct translation of the Java code at this SO URL; I didn’t look into trying to translate anything into a more Scala-like approach.