Today I was writing a Skein hash function implementation in Go. Skein can be configured for different purposes (e.g. MAC, KDF, stream cipher, etc.), and it can accept >5 optional arguments. I wanted to expose all these configuration option to users, in addition to providing a simple version that will create hash without configuration. Here's how I solved it (there's prior art in some standard libraries).
I exported this struct:
type Args struct {
Key []byte // secret key for MAC, KDF, or stream cipher
Person []byte // personalization string
PublicKey []byte // public key for signature hashing
KeyId []byte // key identifier for KDF
Nonce []byte // nonce for stream cipher or randomized hashing
}
and then provided a function:
func New(outLen uint64, args *Args) *Hash
Users can easily call it like this:
h := skein.New(64, &skein.Args{ Key: someKey, Nonce : someNonce })
I like this design a lot.
Plus, with structs as arguments you can easily provide more than one default configurations. For example, if you want to personalize hash for different usages:
var FileHashConfig = &skein.Args{ Person: []byte("file hash for MyApp") }
var MessageHashConfig = &skein.Args{ Person: []byte("message hash for MyApp sync") }
and use them:
h := skein.New(64, FileHashConfig)
(Note: Args is a bad name for general usage -- I called it like this only because Skein authors call these "arguments", and "configuration" is reserved for something else.)
There are a few specified usages for Skein with arguments, so to avoid making users of my library read the Skein paper, I wrote "constructors" for them:
// NewMAC returns hash.Hash calculating Skein Message Authentication Code of the
// given length in bytes. A MAC is a cryptographic hash that uses a key to
// authenticate a message. The receiver verifies the hash by recomputing it
// using the same key.
func NewMAC(outLen uint64, key []byte) hash.Hash {
return hash.Hash(New(outLen, &Args{Key: key}))
}
// NewStream returns a cipher.Stream for encrypting a message with the given key
// and nonce. The same key-nonce combination must not be used to encrypt more
// than one message. There are no limits on the length of key or nonce.
func NewStream(key []byte, nonce []byte) cipher.Stream {
const streamOutLen = (1<<64 - 1) / 8 // 2^64 - 1 bits
h := New(streamOutLen, &Args{Key: key, Nonce: nonce})
return newOutputReader(h)
}
Simple.