A Tag object implements a verify() function, that compares the tag to a bytes string:
computed_tag = state.squeeze_tag();
if computed_tag.verify(expected_tag_as_bytes) {
// success
}
Tag objects are only returned by squeeze_tag(). They cannot be instantiated from existing data.
Which one should we use? (more ideas welcome!)
Both have the advantage of encouraging usage of a dedicated verification function, that will be guaranteed to be constant-time.
Possible advantages of proposal 1
Encourages API consumers to compare comparable types, and to use strong typing for everything.
Can help with static analysis.
A Tag object can be tainted as something storing a secret. This is not possible for untyped bytes. In Rust, that Tag object can capture the bytes string, and automatically wipe it as soon as possible. Having that concept in the API encourages implementations to wrap secrets in types that will be handled as secrets.
Like secret keys, a Tag can be created from an opaque identifier. The guest can check if a message verifies, but cannot learn what the expected tag is without having a message that actually verifies.
Allows Tag objects to hold additional data, and import/export from/to different formats.
As an example of the last point, a Tag can be exported along with all the public parameters required to recompute and verify it.
Verification of a stretched password:
// 1) let the application figure out how to store individual parameters
// 2) retrieve these parameters
// 3) create a context for the previously used algorithm
// 4) initialize the context with these parameters
// 5) verify the password:
if computed_tag.verify(stored_tag_as_bytes) {
// success
}
Alternatively, password stretching functions already define standard ways to output the tag and all the required parameters to recompute it as a string. That string even includes the algorithm identifier.
Using this representation of a tag instead, verification becomes easier:
// 1) create a generic context for password verification
// 2) verify the password:
if computed_tag.verify(stored_tag_as_a_string) {
// success
}
Here, we have two ways to import and export a tag. But both are useful, and used.
Allowing a tag to be imported from existing data also allows the module to provide safe codecs that are constant-time for a given input size.
Possible advantages of proposal 2
Doesn’t require creating an object just for a verification function
Faster
Less opinionated
A natural fit for the most common use case
Nothing that can be done with proposal 1 cannot be implemented with that simpler interface.
Combining both?
Can we have two verification functions?
One that compares a Tag to a bytes string and another one that compares a Tag to a Tag?
What should we compare authentication tags with?
Proposal 1: compare tags with tags
A
Tag
object implements an equality function, that compares tags in constant time.For application developers, computing a tag and comparing it with an existing one will be similar to:
Proposal 2: compare tags with bytes
A
Tag
object implements averify()
function, that compares the tag to a bytes string:Tag
objects are only returned bysqueeze_tag()
. They cannot be instantiated from existing data.Which one should we use? (more ideas welcome!)
Both have the advantage of encouraging usage of a dedicated verification function, that will be guaranteed to be constant-time.
Possible advantages of proposal 1
Tag
object can be tainted as something storing a secret. This is not possible for untyped bytes. In Rust, thatTag
object can capture the bytes string, and automatically wipe it as soon as possible. Having that concept in the API encourages implementations to wrap secrets in types that will be handled as secrets.Tag
can be created from an opaque identifier. The guest can check if a message verifies, but cannot learn what the expected tag is without having a message that actually verifies.Tag
objects to hold additional data, and import/export from/to different formats.As an example of the last point, a
Tag
can be exported along with all the public parameters required to recompute and verify it.Verification of a stretched password:
Alternatively, password stretching functions already define standard ways to output the tag and all the required parameters to recompute it as a string. That string even includes the algorithm identifier.
Using this representation of a tag instead, verification becomes easier:
Here, we have two ways to import and export a tag. But both are useful, and used.
Allowing a tag to be imported from existing data also allows the module to provide safe codecs that are constant-time for a given input size.
Possible advantages of proposal 2
Combining both?
Can we have two verification functions?
One that compares a
Tag
to a bytes string and another one that compares aTag
to aTag
?