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. Thescala-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.