I'm planning on storing a bunch of records in a file, where each record is then signed with libsodium. However, I would like future versions of my program to be able to check signatures the current version has made, and ideally vice-versa.
For the current version of Sodium, signatures are made using the Ed25519 algorithm. I imagine that the default primitive can change in new versions of Sodium (otherwise libsodium wouldn't expose a way to choose a particular one, I think).
Should I...
crypto_sign
)crypto_sign_ed25519
)sodium_library_version_major()
in the file (either in a dedicated 'sodium version' field or a general 'file format revision' field) and quit if the currently running version is lowercrypto_sign_primitive()
crypto_sign_bytes()
and friends...or should I do something else entirely?
My program will be written in C.
Libsodium makes it very easy to encrypt messages. It uses a Box object constructed from your private key and the recipient's public key. The box is then used to encrypt a message.
Libsodium Examples Digital signature: To generate a digital signature for a string, such as a message/file, Sodium uses an Elliptic Curve algorithm, Ed25519.
Let's first identify the set of possible problems and then try to solve it. We have some data (a record) and a signature. The signature can be computed with different algorithms. The program can evolve and change its behaviour, the libsodium can also (independently) evolve and change its behaviour. On the signature generation front we have:
crypto_sign()
, which uses some default algorithm to produce signatures (at the moment of writing is just invokes crypto_sign_ed25519()
)crypto_sign_ed25519()
, which produces signatures based on specific ed25519
algorithmI assume that for one particular algorithm given the same input data and the same key we'll always get the same result, as it's math and any deviation from this rule would make the library completely unusable.
Let's take a look at the two main options:
crypto_sign_ed25519()
all the time and never changing this. Not that bad of an option, because it's simple and as long as crypto_sign_ed25519()
exists in libsodium and is stable in its output you have nothing to worry about with stable fixed-size signature and zero management overhead for this. Of course, in future someone can discover some horrible problem with this algorithm and if you're not prepared to change the algorithm that could mean horrible problem for you.crypto_sign()
. With this we suddenly have a lot of problems, because the algorithm can change, so you must store some metadata along with the signature, which opens up a set of questions:
What do we have in mentioned functions for the second approach?
sodium_library_version_major()
is a function to tell us the library API version. It's not directly related to changes in supported/default algorithms so it's of little use for our problems.crypto_sign_primitive()
is a function that returns a string identifying the algorithm used in crypto_sign()
. That's a perfect match for what we need, because supposedly its output will change at exactly the time when the algorithm would change.crypto_sign_bytes()
is a function that returns the size of signature produced by crypto_sign()
in bytes. That's useful for determining the amount of storage needed for the signature, but it can easily stay the same if algorithm changes, so it's not the metadata we need to store explicitly.Now that we know what to store there is a question of processing that stored data. You need to get the algorithm name and use that to invoke matching verification function. Unfortunately, from what I see, libsodium itself doesn't provide any simple way to get the proper function given the algorithm name (like EVP_get_cipherbyname()
or EVP_get_digestbyname()
in openssl), so you need to make one yourself (which of course should fail for unknown name). And if you have to make one yourself maybe it would be even easier to store some numeric identifier instead of the name from library (more code though).
Now let's get back to file-level vs record-level. To solve that there are another two questions to ask — can you generate new signatures for old records at any given time (is that technically possible, is that allowed by policy) and do you need to append new records to old files?
If you can't generate new signatures for old records or you need to append new records and don't want the performance penalty of signature regeneration, then you don't have much choice and you need to:
If you can generate new signatures or especially if you don't need to append new records, then you can get away with simpler file-level approach when you store the algorithm used in a special file-level field and, if the signature algorithm changes, regenerate all signatures when saving the file (or use the old one when appending new records, that's also more of a compatibility policy question).
Other options? Well, what's so special about crypto_sign()
? It's that its behaviour is not under your control, libsodium developers choose the algorithm for you (no doubt they choose good one), but if you have any versioning information in your file structure (not signature-specific, I mean) nothing prevents you from making your own particular choice and using one algorithm with one file version and another with another (with conversion code when needed, of course). Again, that's also based on the assumption that you can generate new signature and that's allowed by policy.
Which brings us back to the original two choices with question of whether it's worth the trouble of doing all that compared to just using crypto_sign_ed25519()
. That mostly depends on your program life span, I'd probably say (just as an opinion) that if that's less than 5 years then it's easier to just use one particular algorithm. If it can easily be more than 10 years, then no, you really need to be able to survive algorithm (and probably even whole crypto library) changes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With