Post-Quantum Cryptography (PQC) support #720

Open
karel-m wants to merge 2 commits from pr/pqc1 into develop
karel-m commented 2026-04-12 10:17:49 -05:00 (Migrated from github.com)

This pull request introduces an initial proposal for Post-Quantum Cryptography (PQC) support

It adds support for the following NIST PQC standards:

  • FIPS 203: Module-Lattice-Based Key Encapsulation Mechanism (ML-KEM)
  • FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA)
  • FIPS 205: Stateless Hash-Based Digital Signature Standard (SLH-DSA)

Further details on the standards can be found here: https://csrc.nist.gov/projects/post-quantum-cryptography#pqc-standards

The implementation is based on the official reference implementations (all of them public domain):

I also considered adding support for NTRU Prime (as currently used for hybrid kex in OpenSSH 9), but it appears that the OpenSSH project is planning to move to ML-KEM in version 10. Therefore, I kept only the NIST PQC stuff.

A new public API has been introduced in: src/headers/tomcrypt_pqc.h The aim was to keep the interface simple/compact (unfortunately PQC itself is far from being simple/compact), while aligning with existing LTC design patterns.

At present, the test coverage is limited to basic functional checks:

  • key generation + encapsulation + decapsulation (ML-KEM)
  • key generation + signing + verification (ML-DSA, SLH-DSA)

MISSING: Official NIST test vectors still need to be identified (ACVP?) and added to the test suite.

MISSING: Interoperability with other crypto libraries was not tested at all.

MISSING: Functions for key import/export currently handle only raw data; sooner or later PKCS#8 (for privkeys) and/or SPKI/X.509 (for pubkeys) will be probably needed.

IMPORTANT: The code in this initial PR is very lightly tested and is not suitable for production use.

This change is intended as a first step towards PQC support and is open for discussion, review, and further improvements.

Checklist

  • documentation is added or updated
  • tests are added or updated
This pull request introduces an initial proposal for Post-Quantum Cryptography (PQC) support It adds support for the following NIST PQC standards: * FIPS 203: Module-Lattice-Based Key Encapsulation Mechanism (ML-KEM) * FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA) * FIPS 205: Stateless Hash-Based Digital Signature Standard (SLH-DSA) Further details on the standards can be found here: https://csrc.nist.gov/projects/post-quantum-cryptography#pqc-standards The implementation is based on the official reference implementations (all of them public domain): * https://github.com/pq-crystals/kyber (ML-KEM a.k.a. CRYSTALS-Kyber) * https://github.com/pq-crystals/dilithium (ML-DSA a.k.a. CRYSTALS-Dilithium) * https://github.com/sphincs/sphincsplus (SLH-DSA a.k.a. SPHINCS+) I also considered adding support for NTRU Prime (as currently used for hybrid kex in OpenSSH 9), but it appears that the OpenSSH project is planning to move to ML-KEM in version 10. Therefore, I kept only the NIST PQC stuff. A new public API has been introduced in: `src/headers/tomcrypt_pqc.h` The aim was to keep the interface simple/compact (unfortunately PQC itself is far from being simple/compact), while aligning with existing LTC design patterns. At present, the test coverage is limited to basic functional checks: * key generation + encapsulation + decapsulation (ML-KEM) * key generation + signing + verification (ML-DSA, SLH-DSA) **MISSING:** Official NIST test vectors still need to be identified (ACVP?) and added to the test suite. ~~**MISSING:** Interoperability with other crypto libraries was not tested at all.~~ ~~**MISSING:** Functions for key import/export currently handle only raw data; sooner or later PKCS#8 (for privkeys) and/or SPKI/X.509 (for pubkeys) will be probably needed.~~ **IMPORTANT:** The code in this initial PR is very lightly tested and is not suitable for production use. This change is intended as a first step towards PQC support and is open for discussion, review, and further improvements. ### Checklist * [x] documentation is added or updated * [x] tests are added or updated
karel-m commented 2026-04-12 12:36:33 -05:00 (Migrated from github.com)

As for key formats, there are some very recent standards:

  • RFC 9935 (March 2026) defines ML-KEM in X.509/PKIX, including subject public keys and private keys.
  • RFC 9909 (December 2025) defines SLH-DSA in X.509/PKIX, including public keys, signatures and private keys.
  • RFC 9881 (October 2025) defines ML-DSA for X.509 certificates and CRLs, including subject public keys, signatures and private keys.

It looks doable with the infrastructure we have in LTC, but quite a lot of work. It may be worth creating a separate PR.

As for key formats, there are some very recent standards: * [RFC 9935](https://www.rfc-editor.org/rfc/rfc9935.html) (March 2026) defines ML-KEM in X.509/PKIX, including subject public keys and private keys. * [RFC 9909](https://www.rfc-editor.org/rfc/rfc9909.html) (December 2025) defines SLH-DSA in X.509/PKIX, including public keys, signatures and private keys. * [RFC 9881](https://www.rfc-editor.org/rfc/rfc9881.html) (October 2025) defines ML-DSA for X.509 certificates and CRLs, including subject public keys, signatures and private keys. It looks doable with the infrastructure we have in LTC, but quite a lot of work. It may be worth creating a separate PR.
VA1DER commented 2026-04-13 18:09:52 -05:00 (Migrated from github.com)

I don't know what OpenSSH's plan is for hybridizing any or all of these, but sntrup761x25519 was a very, very good idea in that it is provably at least as secure as both. There are still questions about the FIPS PQ algos' classic security. I would encourage dialog with the OpenSSH project to replicate this great idea. And with Jan Mojžíš of TinySSH who was, I understand, the driver behind that idea originally. Curve448 would be a great primitive to use for this, as it has some support already in several implementations (and in OpenSSL).

Secondly, does your implementation include the 256-bit (classic-strength) versions of these? ML-KEM-1024 & ML-DSA-87?

I don't know what OpenSSH's plan is for hybridizing any or all of these, but sntrup761x25519 was a very, **very** good idea in that it is provably at least as secure as both. There are still questions about the FIPS PQ algos' classic security. I would encourage dialog with the OpenSSH project to replicate this great idea. And with Jan Mojžíš of TinySSH who was, I understand, the driver behind that idea originally. Curve448 would be a great primitive to use for this, as it has some support already in several implementations (and in OpenSSL). Secondly, does your implementation include the 256-bit (classic-strength) versions of these? ML-KEM-1024 & ML-DSA-87?
karel-m commented 2026-04-14 03:01:12 -05:00 (Migrated from github.com)

We support all parameter sets defined in NIST FIPS 203:

  • ML-KEM-512 = LTC_MLKEM_512
  • ML-KEM-768 = LTC_MLKEM_768
  • ML-KEM-1024 = LTC_MLKEM_1024

We also support all parameter sets defined in NIST FIPS 204:

  • ML-DSA-44 = LTC_MLDSA_44
  • ML-DSA-65 = LTC_MLDSA_65
  • ML-DSA-87 = LTC_MLDSA_87

And all from NIST FIPS 205:

  • SLH-DSA-SHAKE-128s = LTC_SLHDSA_SHAKE_128S
  • SLH-DSA-SHAKE-128f = LTC_SLHDSA_SHAKE_128F
  • SLH-DSA-SHAKE-192s = LTC_SLHDSA_SHAKE_192S
  • SLH-DSA-SHAKE-192f = LTC_SLHDSA_SHAKE_192F
  • SLH-DSA-SHAKE-256s = LTC_SLHDSA_SHAKE_256S
  • SLH-DSA-SHAKE-256f = LTC_SLHDSA_SHAKE_256F
  • SLH-DSA-SHA2-128s = LTC_SLHDSA_SHA2_128S
  • SLH-DSA-SHA2-128f = LTC_SLHDSA_SHA2_128F
  • SLH-DSA-SHA2-192s = LTC_SLHDSA_SHA2_192S
  • SLH-DSA-SHA2-192f = LTC_SLHDSA_SHA2_192F
  • SLH-DSA-SHA2-256s = LTC_SLHDSA_SHA2_256S
  • SLH-DSA-SHA2-256f = LTC_SLHDSA_SHA2_256F

So yes, this includes the higher-security parameter sets such as ML-KEM-1024 and ML-DSA-87.

As for OpenSSh see https://www.openssh.org/releasenotes.html it looks as they already switched the default to mlkem768x25519-sha256 in OpenSSH 9.9.

On adding further algorithms such as NTRU Prime 761, our preference is to follow widely adopted standards. NIST remains a credible standards body (even if some people see it negatively for geopolitical reasons). In the past, we added a few algorithms that were close to becoming standards but never quite got there and that usually created extra maintenance work.

We support all parameter sets defined in NIST FIPS 203: * ML-KEM-512 = LTC_MLKEM_512 * ML-KEM-768 = LTC_MLKEM_768 * ML-KEM-1024 = LTC_MLKEM_1024 We also support all parameter sets defined in NIST FIPS 204: * ML-DSA-44 = LTC_MLDSA_44 * ML-DSA-65 = LTC_MLDSA_65 * ML-DSA-87 = LTC_MLDSA_87 And all from NIST FIPS 205: * SLH-DSA-SHAKE-128s = LTC_SLHDSA_SHAKE_128S * SLH-DSA-SHAKE-128f = LTC_SLHDSA_SHAKE_128F * SLH-DSA-SHAKE-192s = LTC_SLHDSA_SHAKE_192S * SLH-DSA-SHAKE-192f = LTC_SLHDSA_SHAKE_192F * SLH-DSA-SHAKE-256s = LTC_SLHDSA_SHAKE_256S * SLH-DSA-SHAKE-256f = LTC_SLHDSA_SHAKE_256F * SLH-DSA-SHA2-128s = LTC_SLHDSA_SHA2_128S * SLH-DSA-SHA2-128f = LTC_SLHDSA_SHA2_128F * SLH-DSA-SHA2-192s = LTC_SLHDSA_SHA2_192S * SLH-DSA-SHA2-192f = LTC_SLHDSA_SHA2_192F * SLH-DSA-SHA2-256s = LTC_SLHDSA_SHA2_256S * SLH-DSA-SHA2-256f = LTC_SLHDSA_SHA2_256F So yes, this includes the higher-security parameter sets such as ML-KEM-1024 and ML-DSA-87. As for OpenSSh see https://www.openssh.org/releasenotes.html it looks as they already switched the default to mlkem768x25519-sha256 in OpenSSH 9.9. On adding further algorithms such as NTRU Prime 761, our preference is to follow widely adopted standards. NIST remains a credible standards body (even if some people see it negatively for geopolitical reasons). In the past, we added a few algorithms that were close to becoming standards but never quite got there and that usually created extra maintenance work.
karel-m commented 2026-04-14 16:36:11 -05:00 (Migrated from github.com)

Update:

  • Added import/export functions (in the end, not that complicated) + RFC 9935, RFC 9909, RFC 9881
  • Generated a bunch of PQC keys using OpenSSL 4.0, all of which we are able to import
  • Extracted some PQC test vectors from OpenSSL 4.0

However, we are now getting some segfaults in the GitLab tests (edit: fixed)

Update: * Added import/export functions (in the end, not that complicated) + RFC 9935, RFC 9909, RFC 9881 * Generated a bunch of PQC keys using OpenSSL 4.0, all of which we are able to import * Extracted some PQC test vectors from OpenSSL 4.0 ~~However, we are now getting some segfaults in the GitLab tests~~ (edit: fixed)
karel-m commented 2026-05-07 01:13:30 -05:00 (Migrated from github.com)

latest version now passes all MLKEM / MLDSA wycheproof tests

latest version now passes all MLKEM / MLDSA wycheproof tests
karel-m commented 2026-07-07 06:48:27 -05:00 (Migrated from github.com)

@sjaeckel ping

@sjaeckel ping
NickBrighten commented 2026-07-10 10:05:27 -05:00 (Migrated from github.com)
https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/round-4-submissions in all submissions there are KATs (vectors) included
sjaeckel (Migrated from github.com) requested changes 2026-07-26 06:19:36 -05:00
sjaeckel (Migrated from github.com) left a comment

This isn't a deep review, I only skimmed over it. It basically looks fine, just some nitpicks here and there, but I'll do a deep review hopefully soon'ish.

Additional to those comments I'd prefer to see the different algos put in a separate folder and duplicated stuff like those oid-enum maps being de-duplicated. I guess I can spend some time next week and bring this in shape as well.

This isn't a deep review, I only skimmed over it. It basically looks fine, just some nitpicks here and there, but I'll do a deep review hopefully soon'ish. Additional to those comments I'd prefer to see the different algos put in a separate folder and duplicated stuff like those oid-enum maps being de-duplicated. I guess I can spend some time next week and bring this in shape as well.
sjaeckel (Migrated from github.com) commented 2026-07-26 05:51:27 -05:00

What's the reasoning to put them in the middle of the enum and not at the end?

What's the reasoning to put them in the middle of the `enum` and not at the end?
sjaeckel (Migrated from github.com) commented 2026-07-26 05:53:47 -05:00

Please move this to the top of the file.

Please move this to the top of the file.
sjaeckel (Migrated from github.com) commented 2026-07-26 05:55:37 -05:00

Please put this in a separate commit.

Please put this in a separate commit.
sjaeckel (Migrated from github.com) commented 2026-07-26 05:55:45 -05:00

and this

and this
sjaeckel (Migrated from github.com) commented 2026-07-26 06:04:12 -05:00

That's a near copy of github.com/libtom/libtomcrypt@a8ed78c2ca/src/pk/ecc/ecc_find_curve.c (L193) ... maybe make this a private API function (not in pqc namespace) instead and use that?

That's a near copy of https://github.com/libtom/libtomcrypt/blob/a8ed78c2ca8a7dcb50ad789702ac0586125544f0/src/pk/ecc/ecc_find_curve.c#L193 ... maybe make this a private API function (not in pqc namespace) instead and use that?
karel-m commented 2026-07-27 03:20:40 -05:00 (Migrated from github.com)

As for "separate folders" you mean to turn this:

src/pqc/mldsa.c
src/pqc/mldsa_export.c
src/pqc/mldsa_import.c
src/pqc/mldsa_import_pkcs8.c
src/pqc/mldsa_import_x509.c
src/pqc/mlkem.c
src/pqc/mlkem_export.c
src/pqc/mlkem_import.c
src/pqc/mlkem_import_pkcs8.c
src/pqc/mlkem_import_x509.c
src/pqc/slhdsa.c
src/pqc/slhdsa_export.c
src/pqc/slhdsa_import.c
src/pqc/slhdsa_import_pkcs8.c
src/pqc/slhdsa_import_x509.c

into:

src/pqc/mldsa/mldsa.c
src/pqc/mldsa/mldsa_export.c
src/pqc/mldsa/mldsa_import.c
src/pqc/mldsa/mldsa_import_pkcs8.c
src/pqc/mldsa/mldsa_import_x509.c
src/pqc/mlkem/mlkem.c
src/pqc/mlkem/mlkem_export.c
src/pqc/mlkem/mlkem_import.c
src/pqc/mlkem/mlkem_import_pkcs8.c
src/pqc/mlkem/mlkem_import_x509.c
src/pqc/slhdsa/slhdsa.c
src/pqc/slhdsa/slhdsa_export.c
src/pqc/slhdsa/slhdsa_import.c
src/pqc/slhdsa/slhdsa_import_pkcs8.c
src/pqc/slhdsa/slhdsa_import_x509.c

?

As for "separate folders" you mean to turn this: ``` src/pqc/mldsa.c src/pqc/mldsa_export.c src/pqc/mldsa_import.c src/pqc/mldsa_import_pkcs8.c src/pqc/mldsa_import_x509.c src/pqc/mlkem.c src/pqc/mlkem_export.c src/pqc/mlkem_import.c src/pqc/mlkem_import_pkcs8.c src/pqc/mlkem_import_x509.c src/pqc/slhdsa.c src/pqc/slhdsa_export.c src/pqc/slhdsa_import.c src/pqc/slhdsa_import_pkcs8.c src/pqc/slhdsa_import_x509.c ``` into: ``` src/pqc/mldsa/mldsa.c src/pqc/mldsa/mldsa_export.c src/pqc/mldsa/mldsa_import.c src/pqc/mldsa/mldsa_import_pkcs8.c src/pqc/mldsa/mldsa_import_x509.c src/pqc/mlkem/mlkem.c src/pqc/mlkem/mlkem_export.c src/pqc/mlkem/mlkem_import.c src/pqc/mlkem/mlkem_import_pkcs8.c src/pqc/mlkem/mlkem_import_x509.c src/pqc/slhdsa/slhdsa.c src/pqc/slhdsa/slhdsa_export.c src/pqc/slhdsa/slhdsa_import.c src/pqc/slhdsa/slhdsa_import_pkcs8.c src/pqc/slhdsa/slhdsa_import_x509.c ``` ?
karel-m (Migrated from github.com) reviewed 2026-07-27 03:42:55 -05:00
karel-m (Migrated from github.com) commented 2026-07-27 03:42:55 -05:00

Can move it a bit higher but it has to stay after "typedef struct password_ctx {...}"

Can move it a bit higher but it has to stay after "typedef struct password_ctx {...}"
sjaeckel (Migrated from github.com) reviewed 2026-07-27 04:02:20 -05:00
sjaeckel (Migrated from github.com) commented 2026-07-27 04:02:20 -05:00

How about forward declaring the typedef in the pqc header?

diff --git a/src/headers/tomcrypt.h b/src/headers/tomcrypt.h
index 35d08d484877..3892b41391fd 100644
--- a/src/headers/tomcrypt.h
+++ b/src/headers/tomcrypt.h
@@ -89,2 +89,3 @@ enum {
 #include "tomcrypt_prng.h"
+#include "tomcrypt_pqc.h"
 #include "tomcrypt_pk.h"
diff --git a/src/headers/tomcrypt_pk.h b/src/headers/tomcrypt_pk.h
index 9b07a403715e..4dacc8bdc585 100644
--- a/src/headers/tomcrypt_pk.h
+++ b/src/headers/tomcrypt_pk.h
@@ -799,4 +799,2 @@ int dsa_shared_secret(void          *private_key, void *base,
 
-#include "tomcrypt_pqc.h"
-
 typedef struct {
diff --git a/src/headers/tomcrypt_pqc.h b/src/headers/tomcrypt_pqc.h
index 9c39a47a8e68..34e20d257fa0 100644
--- a/src/headers/tomcrypt_pqc.h
+++ b/src/headers/tomcrypt_pqc.h
@@ -9,2 +9,5 @@
 
+/* Forward declare */
+typedef struct password_ctx password_ctx;
+
 /* ---- ML-KEM (FIPS 203) ---- */
How about forward declaring the typedef in the pqc header? ``` diff --git a/src/headers/tomcrypt.h b/src/headers/tomcrypt.h index 35d08d484877..3892b41391fd 100644 --- a/src/headers/tomcrypt.h +++ b/src/headers/tomcrypt.h @@ -89,2 +89,3 @@ enum { #include "tomcrypt_prng.h" +#include "tomcrypt_pqc.h" #include "tomcrypt_pk.h" diff --git a/src/headers/tomcrypt_pk.h b/src/headers/tomcrypt_pk.h index 9b07a403715e..4dacc8bdc585 100644 --- a/src/headers/tomcrypt_pk.h +++ b/src/headers/tomcrypt_pk.h @@ -799,4 +799,2 @@ int dsa_shared_secret(void *private_key, void *base, -#include "tomcrypt_pqc.h" - typedef struct { diff --git a/src/headers/tomcrypt_pqc.h b/src/headers/tomcrypt_pqc.h index 9c39a47a8e68..34e20d257fa0 100644 --- a/src/headers/tomcrypt_pqc.h +++ b/src/headers/tomcrypt_pqc.h @@ -9,2 +9,5 @@ +/* Forward declare */ +typedef struct password_ctx password_ctx; + /* ---- ML-KEM (FIPS 203) ---- */ ```
karel-m (Migrated from github.com) reviewed 2026-07-27 05:48:28 -05:00
karel-m (Migrated from github.com) commented 2026-07-27 05:48:28 -05:00
#include "tomcrypt_pk.h"
#include "tomcrypt_pqc.h"

ends up with: tomcrypt_pk.h:823:7: error: unknown type name ‘mlkem_key’

#include "tomcrypt_pqc.h"
#include "tomcrypt_pk.h"

ends up with: tomcrypt_pqc.h:197:77: error: unknown type name ‘password_ctx’

``` #include "tomcrypt_pk.h" #include "tomcrypt_pqc.h" ``` ends up with: `tomcrypt_pk.h:823:7: error: unknown type name ‘mlkem_key’` ``` #include "tomcrypt_pqc.h" #include "tomcrypt_pk.h" ``` ends up with: `tomcrypt_pqc.h:197:77: error: unknown type name ‘password_ctx’`
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 'pr/pqc1:pr/pqc1'
git switch 'pr/pqc1'

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch 'develop'
git merge --no-ff 'pr/pqc1'
git switch 'pr/pqc1'
git rebase 'develop'
git switch 'develop'
git merge --ff-only 'pr/pqc1'
git switch 'pr/pqc1'
git rebase 'develop'
git switch 'develop'
git merge --no-ff 'pr/pqc1'
git switch 'develop'
git merge --squash 'pr/pqc1'
git switch 'develop'
git merge --ff-only 'pr/pqc1'
git switch 'develop'
git merge 'pr/pqc1'
git push origin 'develop'
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-libtom/libtomcrypt!720
No description provided.