Crystal: How can I find the SHA256 hash of a binary value?
Asked Answered
B

3

5

I'm new to Crystal. I'd like to try and find the SHA256 hash of a hex string. I've managed to get something working:

sha256 = OpenSSL::Digest.new("sha256")
puts sha256.update("abcd")

But I'm not sure how to put the binary value of "abcd" in to the hash function, or get binary out. I'd basically like to be able to recreate this Ruby function:

def hash256(hex)
  # 1. Convert hex string to array, and pack in to binary
  binary = [hex].pack("H*")

  # 2. Hash the binary value (returns binary)
  hash1 = Digest::SHA256.digest(binary)

  # 3. Hash it again (returns binary)
  hash2 = Digest::SHA256.digest(hash1)

  # 4. Convert back to hex (must unpack as array)
  result = hash2.unpack("H*")[0]

  return result
end

Is it possible to use SHA256 with binary values in Crystal?

Broken answered 17/2, 2017 at 13:20 Comment(0)
M
9

Decoding a string of hex characters into a binary slice isn't currently part of the crystal standard library, so I wrote a decoding function myself:

def hex_decode(hex)
  return unless hex.size % 2 == 0

  slice = Slice(UInt8).new(hex.size / 2)
  0.step(to: hex.size - 1, by: 2) do |i|
    high_nibble = hex.to_unsafe[i].unsafe_chr.to_u8?(16)
    low_nibble = hex.to_unsafe[i + 1].unsafe_chr.to_u8?(16)
    return unless high_nibble && low_nibble

    slice[i / 2] = (high_nibble << 4) | low_nibble
  end

  slice
end

This function takes a String containing hexadecimal characters, then decodes it into a Slice(UInt8) (or returns nil if the hex is invalid).

Then the full code equivalent to the ruby code you pasted above would be:

def hash256(hex_string)
  data = hex_decode(hex_string)
  raise "Invalid hexadecimal" unless data

  hash = OpenSSL::Digest.new("SHA256")
  hash.update(data)
  hash1 = hash.digest

  hash = OpenSSL::Digest.new("SHA256")
  hash.update(hash1)

  hash.hexdigest
end

Although I have to question why you would want to use SHA256 twice. I would recommend changing the hash function like so:

def hash256(hex_string)
  data = hex_decode(hex_string)
  raise "Invalid hexadecimal" unless data

  hash = OpenSSL::Digest.new("SHA256")
  hash.update(data)

  hash.hexdigest
end
Mccann answered 17/2, 2017 at 15:15 Comment(1)
Thank you so much. The reason for using SHA256 twice is because I'm trying to learn Crystal by writing a Bitcoin library, and for no real reason things tend to get hashed twice.Broken
B
2

For a Ruby script generating a SHA256 hash with the digest gem:

require "digest"

def calc_hash
  sha = Digest::SHA256.new
  sha.update(@index.to_s + @timestamp.to_s + @data + @previous_hash)
  sha.hexdigest
end

For Crystal, you can require openssl and use that instead:

require "openssl"

def calc_hash
  hash = OpenSSL::Digest.new("SHA256")
  hash.update(@index.to_s + @timestamp.to_s + @data + @previous_hash)
  hash.hexdigest
end
Beniamino answered 17/1, 2018 at 10:23 Comment(0)
S
0

You can just use the .hexbytes of the hex string:

# Sha256 hash of hex string
hex = "abcd"
p OpenSSL::Digest.new("sha256").update(hex).final.hexstring
# => "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589"

# Sha256 hash of hex data but on binary value
bin = hex.hexbytes
p OpenSSL::Digest.new("sha256").update(bin).final.hexstring
# => "123d4c7ef2d1600a1b3a0f6addc60a10f05a3495c9409f2ecbf4cc095d000a6b"

That way, you can use SHA256 with binary values in Crystal.

Swath answered 29/10, 2021 at 12:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.