Abstracting primitives and modes of operation 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-Damgård because that is what MD5, as a whole, is.

And these modes of operation are not quite the same, for instance Merkle-Damgå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.
It is not a good idea to try and find abstractions at places where there is no fundamental reason for them to exist. That there is some "accidental" structure in the way encryption or hash methods are build up does not say anything about future encryption or hash functions.

Besides, you may need to apply specific protections against e.g. side channel attacks. It would be a shame if your carefully build software design would be in the way of creating a more secure library.

Note that if there is no fundamental reason for specific abstractions, there may not be any advantage in creating them. E.g. you would not see too many advantages regarding code duplication.

Many algorithms have specific naming conventions and reference implementations. You should not want to shoehorn those into your design.