Janthelme
Janthelme

Reputation: 999

Hash of hash functions in cryptonite package

I am trying to use the Crypto.Hash module (cryptonite package) and produce "hash of hash" results.

The issue is that the signature of the module's hash function is hashlazy :: HashAlgorithm a => ByteString -> Digest a, so before I can produce a hashlazy (hashlazy x), I'll need to translate a Digest into a ByteString.

Although there is a digestFromByteString function provided, there is no such function for the reverse (maybe it doesn't make sense for all Digests?).

So the only way I found is to do this :

-- produce a hexadecimal string representation of the digest
myHash :: ByteString -> String
myHash b = show (hashlazy bs :: Digest SHA256)

-- convert a hexadecimal string into a ByteString
fromHexString :: String -> ByteString
fromHexString = undefined 

and then I can now do hashhash x = myHash $ fromHexString $ myHash x...

But that looks very cumbersome.

My question: Am I using the library functions properly? Did I miss a conversion function somewhere? Or should I do things differently?

=== EDIT 1 ===

I wanted to keep my question simple and avoided other details which I thought weren't important, but I do need to convert the hash result back into a ByteString, as I need to do manipulate it before hashing again. Eg,

-- myByteString is a constant
-- firstByteString is original input
finalResult = hash $ append myByteString (hash firstByteString)

=== EDIT 2 ===

I accepted ben's answer, and learned from it, but for future reference, one way to convert Crypto.Hash's Digest a into a ByteString is via the memory package's Data.ByteArray module and the pack function :

module Main where

import Data.ByteString
import Data.ByteString as BS (pack)
import Data.ByteArray (ByteArrayAccess)
import qualified Data.ByteArray as BA (unpack)
import Crypto.Hash (Digest, hash)
import Crypto.Hash.Algorithms (SHA256(..))

somehash = hash ("foo" :: ByteString) :: Digest SHA256

toByteString :: ByteArrayAccess a => a -> ByteString
toByteString = BS.pack . BA.unpack

somebytestring = toByteString somehash

Upvotes: 1

Views: 838

Answers (1)

ben
ben

Reputation: 2024

The hash functions there in general require a ByteArrayAccess instance for their input. Only hashlazy in particular requires a lazy ByteString specifically.

hash (hashlazy x) should work because Digest has an instance of ByteArrayAccess. To hash multiple Digests together, you can use the underlying hashInit/hashUpdates/hashFinalize functions, passing your all Digests to hashUpdates.

Quick example:

import System.IO
import Crypto.Hash
import Crypto.Hash.Algorithms
import Data.ByteString.UTF8 as B

main = do
  putStr "Enter something to hash: " >> hFlush stdout
  line1 <- fmap B.fromString getLine
  let hash1 = hash line1 :: Digest SHA512
  putStrLn $ "First hash is " ++ show hash1
  putStr "Enter something else to hash: " >> hFlush stdout
  line2 <- fmap B.fromString getLine
  let
    ctx1 :: Context SHA512
    ctx1 = hashInit
    ctx2 = hashUpdate ctx1 hash1
    ctx3 = hashUpdate ctx2 line2
    hash2 = hashFinalize ctx3
  putStrLn $ "Second hash is " ++ show hash2

Gives:

Enter something to hash: foo
First hash is f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7
Enter something else to hash: bar
Second hash is b612043d657248615f4c184407f4e5bfc0e6334f86056dfb641cf35ce1e33d86e5349d8e82b3b5018adb07e6b6d653d288e9ed883af624e7f34f12117a620c00

Verify:

 $ (echo -n foo | sha512sum | head -c 128 | xxd -p -r ; echo -n bar) | sha512sum
 b612043d657248615f4c184407f4e5bfc0e6334f86056dfb641cf35ce1e33d86e5349d8e82b3b5018adb07e6b6d653d288e9ed883af624e7f34f12117a620c00  -

Edit 2: Note that if you actually do need to manipulate the digest you get as an array of bytes, you can use the operations on the BytesArrayAccess class, or convert to regular old strict ByteStrings from there.

Upvotes: 3

Related Questions