Running the message abc thru the SHA-256 hashing algorithm results in the following string:

ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

So what is this giant string? What are the components?

Here’s what we can quickly observe by a visual scan:

  • all characters are hex values: 0-9a-f
  • the total length is 64 characters
  • we know that each hexadecimal digit represents four binary digits
  • the 256 in the name is the size of the resulting hash: 64 * 4

So we can conclude that this string is just a hex representation of a 256 bit number. How can we get a different look at this number?

We can use the Scala BigInt class to view the base 10 and also the raw binary (aka base 2) representation of this hashed value:

val hash = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"

// base 10
BigInt(hash, 16)
// 84342368487090800366523834928142263660104883695016514377462985829716817089965

// base 2
BigInt(hash, 16).toString(2)
// "1011101001111000000101101011111110001111000000011100111111101010010000010100000101000000110111100101110110101110001000100010001110110000000000110110000110100011100101100001011101111010100111001011010000010000111111110110000111110010000000000001010110101101"
BigInt(hash, 16).toString(2).size
/*
* 256
*/

How is this number generated? For that we can use some Java built-in libraries like MessageDigest:

This MessageDigest class provides applications the functionality of a message digest algorithm, such as SHA-1 or SHA-256. Message digests are secure one-way hash functions that take arbitrary-sized data and output a fixed-length hash value.

https://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html

import java.security.MessageDigest
import java.nio.charset.StandardCharsets
val md256 = MessageDigest.getInstance("SHA-256")

val message = "abc"
val messageBytes = message.getBytes(StandardCharsets.UTF_8)
val hash = d.digest(messageBytes)
/*
* hash: Array[Byte] = Array(-70, 120, 22, ..., -83)
*/

So what do we have here? The output of the SHA-256 algorithm in this instance is an Array of bytes.

hash.size is 32, which tells us that each array item represents 8 bits.

mapping to a hexString, hash.map(_.toHexString).mkString, leaves us with something that looks sorta like the official hash that I began the post with, but with a bunch of extra f characters. As a reminder, f in hex equals 1111 in binary and 15 in base 10.

This is because there are some “negative” bytes in the array. Here’s a great answer I found on stack overflow to explain what that means:

byte in Java is a number between −128 and 127 (unsigned, like every integer in Java)… By anding with 0xff you’re forcing it to be a positive int between 0 and 255.

https://stackoverflow.com/questions/9949856/anding-with-0xff-clarification-needed

So let’s map the array again, applying the and transformation and also padding an extra 0 char in the case of single digit hex values.

hash.map(b => {
  val i = Integer.toHexString(0xff & b)
  if (i.size == 1) s"0$i" else i
}).mkString
/*
* ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
*/

And there’s our fully hashed value.