-
Notifications
You must be signed in to change notification settings - Fork 552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Kyber PQ-Safe KEM #2872
Conversation
This is great to see! I'll get a review in ASAP. Until then, a topic for discussion -- do we really need the 90s mode? It seems like it adds a relatively large amount of complexity. Will anyone use it? |
I don't know. -90s is faster, but my benchmarks of the "normal" code showed performance sufficient for my needs. Still, if it's not too big a headache, why not support both... |
Hm, good question. I can't say that we need it specifically, but I think it's a great use case for Botan's module system. If someone does have a configuration where the more modern primitives are not available, we can support that very well. The differences between the modes are also pretty local so it shouldn't be too difficult to separate the shared code parts from the specific parts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An initial review. Many comments here and there will be more later but overall a good direction.
Another question wrt 90s mode. Currently the code makes a distinction between 90s and modern at key generation time. Would it make more sense to have this be an attribute of the KEM? That is, KyberMode
is just 512/768/1024, which encapsulates the algorithmic params like N, eta1, etc. And then there is some additional abstraction (KyberSymmetricPrimitives
or something like that) that implements the hashes, PRFs, etc, and the KEM instantiates one implementation or another (90s/modern) based on the param strings which are currently unused.
I think this design would make it a little simpler to actually realize the promise of supporting Kyber in limited configurations as it would cleanly split out the AES/SHA-2 vs SHA_3/SHAKE code into two implementations of KyberSymmetricPrimitives
and these could be implemented in submodules which in turn depend on the relevant primitives.
One concern here is that maybe the Kyber designers do not recommend mixing modes for any particular key. It's been a long time since I read the Kyber specs and I don't recall what if anything they have to say about this.
Answering my own question
After reviewing the Kyber spec I remember why this is not possible, the For this reason I think we should probably have two distinct OIDs, so the keys can be distinguished. Or possibly 6 OIDs? One per size + 90s vs modern. |
IMHO, offhand, I'd prefer 6 OIDs. |
…post-quantum project. Specification an link to NIST submission package: https://pq-crystals.org/kyber/resources.shtml Co-authored-by: Manuel Glaser <Manuel.Glaser@rohde-schwarz.com> Co-authored-by: René Meusel <rene.meusel@nexenio.com> Co-authored-by: Hannes Rantzsch <hannes.rantzsch@nexenio.com>
07f407f
to
1a41073
Compare
Regarding OIDs: We'll switch to the ones mentioned in this IETF draft standard. Also, this document specifies an ASN.1 encoding for Kyber keys. Probably we should support those as well. |
|
||
#if !defined(BOTAN_HAS_KYBER_90S) && !defined(BOTAN_HAS_KYBER) | ||
static_assert(false, "botan module 'kyber_common' is useful only when enabling modules 'kyber', 'kyber_90s' or both"); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We opted to introduce three modules kyber
(modern), kyber_90s
and kyber_common
. The latter contains the actual algorithm and supporting math code. The former two contain adapters to the required symmetric algorithms for XOF, KDF and more.
Hence, users enabling kyber_common
exclusively at ./configure.py
would end up with a dysfunctional kyber implementation. They'd need at least one of the symmetric algorithm adapters in the other mentioned modules. This static_assert
is an attempt to warn them of their mistake at build time.
To the best of our knowledge, Botan's module system doesn't have a notion of an "internal module" for such use cases, right? Is that potentially something we'd want to introduce?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replying to @mouse07410 inline, to keep the context:
IMHO, it makes no sense to allow kyber_common for explicit configuration.
I'd have only kyber and kyber_90s as "configurable", each of which would force inclusion of kyber_common. Adding it explicitly without one of the other two makes no sense, disabling it when at least one of the others is enabled doesn't make sense either.
I agree with you! Though, given the inner workings of Botan's module system, we didn't figure out how to do that without introducing the kyber_common
module. I.e. this solution is a technical crutch for the time being. Is there a better way, @randombit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ironically there used to be a good way of doing this, a module could have an A or B
dependency, which would work perfectly in this situation. It looks like I removed this in the otherwise unrelated commit ba8a26f - the functionality hadn't been used in some time at that point, and in that PR I was making other changes to dependency resolution to handle conditional dependencies based on OS features and apparently decided to remove |
at the same time.
This current setup is certainly going to be confusing to end users. I'll see how hard it would be to add alternatives back, probably not bad. Then we would have kyber
which would depend on kyber_modern | kyber_90s
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, if it's not too complicated to get alternatives back in. Would the module kyber
default to the modern mode then? Or would users have to explicity enable kyber,kyber_modern
?
Note that currently the kyber
module is the modern mode and kyber_90s
the 90s mode. So end users would need to explicitly only enable kyber_common
to get a broken build. So naively using kyber
would be fine. We hoped this would avoid confusion at least a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an alternative, we might take a look at creating "internal modules" (i.e. by adding some new keyword in info.txt
) so that the current kyber_common
module could be "required" by kyber
and kyber_90s
but wouldn't show up in the user-facing ./configure.py --list-modules
.
IMHO, it makes no sense to allow |
Codecov Report
@@ Coverage Diff @@
## master #2872 +/- ##
==========================================
+ Coverage 92.31% 92.40% +0.08%
==========================================
Files 567 569 +2
Lines 62846 63633 +787
Branches 6146 6228 +82
==========================================
+ Hits 58019 58802 +783
- Misses 4795 4799 +4
Partials 32 32
Continue to review full report at Codecov.
|
@randombit Regarding ASN.1 encoding: Do you think it makes sense to support the reference implementation's encoding (simple byte concatenation of parameters) as well? If yes, what's the preferred way of selecting which encoding should be read/emitted? Further: Should there be constructors for Edit: The (preliminary) ASN.1 spec defines a partial and full encoding of the private key. Same questions arise for that. @omarama @boricm Do you have some specific requirements here? |
I have some reservations about ASN.1 encoding from that document. Let me double-check and get back here. |
Thanks for finding this document, @reneme! Back when we thought about the API and IODs, this document was not yet published. That's the only reason why we didn't use the encoding and OIDs specified there. |
Certainly would want to support any IETF-standardized ASN.1 encoding (even if suboptimal). No strong opinion on supporting the reference implementation encoding. |
Re the ASN.1 I would much rather they have just said "everything is a single OCTET STRING which encapsulates whatever the reference implementation uses", rather than reflecting the algorithm specific details in the ASN.1 for no real reason. I'll send a comment, once I figure out what WG this is going through... |
So we decided to add a rudimentary support for the drafted ASN.1 format. Basically we support to read/write the "full" version of the encoding and depend on the (optional) embedding of the public key in the private key encoding. While playing with the implementation, a few additional problems showed up. Leaving them here, in case you want to comment on the draft: "full" and "partial" encodings aren't compatibleThe full-encoding does not contain the "partial" does not contain the Z parameterFrom our understanding, this parameter can be re-rolled by an RNG for every session. Though, this means that private keys decoded from "partial" and re-encoded to "full" will not be byte-by-byte compatible. Encoded private key without embedded public keyThe embedded public key is optional in the ASN.1 encodings of the private key. From our understanding, Kyber does not allow to recover the public key from the private key. Hence, if no public key was embedded, Botan cannot fulfil its polymorphy-based API: I.e. the resulting private key object would not be able to act as a public key. |
Co-Authored-By: Hannes Rantzsch <hannes.rantzsch@nexenio.com>
* Shake_128_Cipher as XOF * Split Kyber "modern" and "90s" modes into botan modules * copyright headers * OIDs for different kyber modes * Support ASN.1 Full encoding Co-authored-by: Hannes Rantzsch <hannes.rantzsch@nexenio.com>
Co-Authored-By: Hannes Rantzsch <hannes.rantzsch@nexenio.com>
We're done with all the improvements and refactorings we had in mind and discussed above. Ready for another round of review :) |
Hi @hrantzsch and @reneme! |
Yes, exactly. It's the random string produced by the RNG that used to be implemented in the KAT tests. We opted for this "static" approach instead of shipping an entire RNG implementation. See also @randombit's comment here.
If NIST provides more test vectors, we can do the same approach and hard-code the random outputs, no? Its a bit of extra work up-front but saves the additional homebrew RNG in the code base. |
With the misguided API that NIST enforced, I think keeping a homebrew NIST-specified (P)RNG in the code is the only way to deal with KAT. 😟 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One side channel issue that should be resolved before merge, but besides that this is looking really good.
I have not had time to work on the modules issue where we would like have the Kyber module require that at least one implementation be available at build time. I don't think that should hold up this merging.
Since the Kyber specification is still somewhat subject to change I would be against backporting this to the 2.x branch at least until NIST announces something (which I have heard should be by end of March).
Co-authored-by: René Meusel <rene.meusel@nexenio.com>
Merged! @boricm @hrantzsch @reneme @omarama thanks for all the work put into this. |
Adding support for Kyber (post-quantum-safe KEM) as specified in Round 3 of the NIST post-quantum project. Specification and link to NIST submission package: https://pq-crystals.org/kyber/resources.shtml
Contributed by:
@reneme @hrantzsch @omarama @boricm
Edit (hrantzsch):
This addresses #2500
There are a few things we would still like to address:
#pragma once
)a test that uses Kyber through the PK_En/Decryptor_* interfaceFurthermore, there are some things that are likely future work:
StreamCipher
insample_rej_uniform