I am developing a symmetric crypto library and have reached a roadblock. Looking at block ciphers, it is quite obvious that all block ciphers are trivially abstractable as a simple primitive consisting of:
- a key schedule
- a permutation function (which takes as an input a key, possibly a "tweak" and a data input)
This makes all block ciphers easy to use in any mode of operation, and makes it easy to "swap out" various algorithms in favor of others without needing to rewrite extensive amounts of code - only the above components differ between algorithms.
I am having trouble observing the same level of abstraction with hash functions. They are described by their compression function (straightforward) but also seem to have a built-in mode of operation which, while often shared between various hash functions, is not meant to be changed, for instance nobody uses the MD5 compression function with a Davies-Meyer construction, it is always used with Merkle-DamgardDamgård because that is what MD5, as a whole, is.
And these modes of operation are not quite the same, for instance Merkle-DamgardDamgård applies some simple padding at the end of the message to hash and then divides the message into blocks and processes it like that, whereas the UBI construction uses an extra "configuration input" in its compression function which requires the message to be handled quite differently.
So my question is: is there a way to nicely abstract hash functions in a specific framework as elegantly as with block ciphers, without needing to specifically write every hash function in a different way, so as to achieve optimal code reuse?
The best compromise I could come with is categorizing different hash functions in groups depending on what mode of operation they use (such as MD5, SHA1, SHA2, RIPEMD, etc.. would all go into the Merkle-Damgård category, whereas Skein would go in the UBI category, and so on), which would have code related to message padding and handling being reused when necessary, but also increases code complexity slightly.
This is also an issue for HMAC constructions. There is a fully abstract HMAC construction which works with any hash function regardless of its internals, however newer hash functions are starting to provide their own specific HMAC designs (for instance, Skein and its HMAC configuration block) which are more efficient than the "standard" method.