nstilt1 / crypto-on-the-edge

A WIP Rust crate for generating private keys from IDs based on an HKDF, and eliminates the need to store private keys.
Apache License 2.0
0 stars 0 forks source link

IDs are not inherently safe, depending on the constant parameters #9

Closed nstilt1 closed 4 months ago

nstilt1 commented 5 months ago

The issue

With the VersioningConfig, a version lifetime can be specified by a user that determines how frequently versions change. The lower bound for this is currently 600 seconds for flexibility, and IDs' constant parameters independently determine how many bits are allotted for the version number.

With the current configuration, someone could make a KeyGenerator that switches versions every 10 minutes, and they can use multiple ID types that have a different amount of bits reserved for the version. If there's one ID that can store a 32-bit version and another ID that can store a 5-bit version... the code will kind of break after about 5.3 hours, and it would be a little challenging to prevent users from doing this without making some changes.

Potential solutions

I think the solution for this is to change where the consts are defined. I want to move const VERSION_BITS: u8 to the KeyGenerator's VersioningConfig, and then add a const USE_VERSIONS: bool in BinaryId. This way, a user could opt-out of having a version encoding in an ID, but if they opt-in, it will always be the same size, and it will be able to be validated at compile time. I could add another const (like const BREAKING_POINT_YEARS: u64) that offers a compile-time validation that the user's chosen parameters will last for a minimum of BREAKING_POINT_YEARS.

I also want to move REQUIRE_EXPIRING_KEYS from the VersioningConfig to a BinaryId. This way, if a user wants to require a specific ID type to expire, but not necessarily every type of key ID, then they will be able to do so.

I kind of want to validate timestamp sizes as well because the timestamps will need to be able to represent at least VERSION_LIFETIME + MAX_KEY_EXPIRATION_TIME seconds.

nstilt1 commented 5 months ago

I'm afraid the solution might not be as simple as this. I feel like I need to recap the purpose of "versions" for both myself, and anyone who is wondering "What is the point of versions for an ID"

Versions

Versions provide 2 functionalities:

Version "epochs"

For expiring IDs, the version is associated with an "epoch" which is essentially a reference point in time for the expiration timestamps (timestamp in ID = (expiration_time - version_epoch) >> precision_loss. The epoch helps us achieve a smaller timestamp size, but it still requires a little extra space for the version number.

Version "salts"

When computing the MAC of IDs, as well as private keys, a version "salt" is added in the computation. This has a similar effect of using different MAC keys and HKDF keys, but it is a little easier and more efficient since we don't need to reinitialize the MAC and HKDF when dealing with IDs with different versions. It is probably slightly less secure than using new keys for different versions, but I believe this will suffice for most use-cases, especially given that there aren't any known feasible attacks against hash functions, even with quantum computers.

How this affects the solution

Versions probably should not be opted out of for ID types, as it would add quite a bit of complexity to an already-somewhat-complex library to make them optional for ID types... but if they were to be optional, then the ID also wouldn't be able to contain a timestamp given that timestamps have a dependency on the version epoch.

As for VERSION_LIFETIME... I intended for VERSION_LIFETIME to be about a year long because I feel like it is logical that HKDF/MAC rekeying is something that shouldn't need to be done very often. But if a VERSION_LIFETIME was much shorter than this, it would allow for an insanely complex (and possibly unnecessarily complex) cryptographic ID system... I will allow for low VERSION_LIFETIME values because it is cool, but the cryptographic algorithms/libraries this library uses are strong enough to not need daily, weekly, or even monthly HKDF rekeying.

If anyone has any comments on this subject, the floor is yours, and I'll try to work on these constant parameters a little bit more to try to ensure safety and compile-time validation...