15

I need to generate an SHA-256 checksum from a string that will be sent as a get param.

If found this link to generate the checksum.

Genrating the checksum like so:

  val digest = MessageDigest.getInstance("SHA-256");      
  private def getCheckSum() = {
    println(new String(digest.digest(("Some String").getBytes(StandardCharsets.UTF_8))))        
  }

prints checksum similar to this:

*║┼¼┬]9AòdJb:#↓o6↓T╞B5C♀¼O~╟╙àÿG

The API that we need to send this to says the checksum should look like this:

45e00158bc8454049b7208e76670466d49a5dfb2db4196

What am I doing wrong?

Please advise. Thanks.

An Illusion
  • 769
  • 1
  • 10
  • 24
  • 1
    She message digest will return raw bytes. You expected format requires them to be encoded as hex – puhlen Sep 20 '17 at 19:08

4 Answers4

27

Equivalent, but a bit more efficient:

MessageDigest.getInstance("SHA-256")
  .digest("some string".getBytes("UTF-8"))
  .map("%02x".format(_)).mkString
Dima
  • 39,570
  • 6
  • 44
  • 70
  • 1
    Hash code generated using your mehtod doesnt seem to be equivalent to what is generated using the method from previous answer: https://imgur.com/a/a3Gfv – An Illusion Sep 25 '17 at 16:42
  • Yeah, sorry about that. Fixed now. – Dima Sep 25 '17 at 17:14
  • 2
    @Regressor that's a `Sting.format` instruction. `x` means hexademical, `02` means two digits, padded with 0 if necessary. So, say, 10 (a in hex) is printed as "0a", and 255 (ff) is "ff" – Dima Jul 12 '18 at 14:32
13

java.security.MessageDigest#digest gives a byte array.

scala> import java.security.MessageDigest
scala> import java.math.BigInteger

scala> MessageDigest.getInstance("SHA-256").digest("some string".getBytes("UTF-8"))
res1: Array[Byte] = Array(97, -48, 52, 71, 49, 2, -41, -38, -61, 5, -112, 39, 112, 71, 31, -43, 15, 76, 91, 38, -10, -125, 26, 86, -35, -112, -75, 24, 75, 60, 48, -4)

To create the hex, use String.format,

scala> val hash = String.format("%032x", new BigInteger(1, MessageDigest.getInstance("SHA-256").digest("some string".getBytes("UTF-8"))))
hash: String = 61d034473102d7dac305902770471fd50f4c5b26f6831a56dd90b5184b3c30fc

You can verify hash with command line tool in linux, unix

$ echo -n "some string" | openssl dgst -sha256
61d034473102d7dac305902770471fd50f4c5b26f6831a56dd90b5184b3c30fc

NOTE:

In case java returns hash of length lesser than 64 chars you can left pad with 0. (eg. 39)

def hash64(data: String) = {
  val hash = String.format(
               "%032x", 
               new BigInteger(1, MessageDigest.getInstance("SHA-256").digest(data.getBytes("UTF-8")))
             )
  val hash64 = hash.reverse.padTo(64, "0").reverse.mkString 
  hash64      
}
prayagupa
  • 30,204
  • 14
  • 155
  • 192
  • 3
    Small bug here, string representation is missing leading 0's, for example string `39` will return `b918943df0962bc7a1824c0555a389347b4febdc7cf9d1254406d80ce44e3f9` instead of `0b918943df0962bc7a1824c0555a389347b4febdc7cf9d1254406d80ce44e3f9` – Gustek Feb 01 '18 at 14:53
  • 2
    Seems like you meant to use `"%064x"` instead of `"%032x"`, that should solve the padding so you don't need `padTo` – Alex Hall Feb 16 '21 at 14:51
1

Can use DatatypeConverter.printHexBinary.

Something like:

DatatypeConverter.printHexBinary(
  MessageDigest
    .getInstance(algorithm)
    .digest("some string").getBytes("UTF-8")))
Aaron_ab
  • 3,450
  • 3
  • 28
  • 42
0

Since jdk 17, we can use java.util.HexFormat

import java.security.MessageDigest
import java.util.HexFormat

val bytes = MessageDigest.getInstance("SHA-256")
  .digest("any string".getBytes("UTF-8"))

val sha256 =  HexFormat.of().formatHex(bytes)
// 1e57a452a094728c291bc42bf2bc7eb8d9fd8844d1369da2bf728588b46c4e75

val another = HexFormat.ofDelimiter(":").withUpperCase().formatHex(bytes)
// 1E:57:A4:52:A0:94:72:8C:29:1B:C4:2B:F2:BC:7E:B8:D9:FD:88:44:D1:36:9D:A2:BF:72:85:88:B4:6C:4E:75

counter2015
  • 869
  • 7
  • 18