
OpenSSL 3.5 introduces three post-quantum primitives - ML-DSA, SLH-DSA and ML-KEM - all standardized in FIPS-203/204/205 and believed to withstand large-scale quantum attacks. Large-scale quantum computers will eventually break RSA and ECC. OpenSSL 3.5 ships the first NIST-approved post-quantum algorithms, giving teams a concrete migration path.
This blog shows a minimal, reproducible lab build of OpenSSL 3.5 on Red Hat Enterprise Linux 9.6, validates the new algorithms and compares their performance, giving you a starting point for pilot deployments.
Policy Pressure Is Mounting
Future large-scale quantum computers will be able to break the asymmetric cryptography in use today, including RSA and elliptic-curve systems.
The National Security Agency’s Commercial National Security Algorithm Suite 2.0 (CNSA 2.0), released in September 2022, sets an aggressive timetable for migrating U.S. federal systems to quantum-resistant cryptography. In April 2025 the European Cybersecurity Certification Group (ECCG) published version 2 of The Agreed Cryptographic Mechanisms, adding recommendations on post-quantum algorithms; enabling EU legislation is expected to follow.
Organizations should treat the transition seriously even if regulation has not yet reached them; building prototypes early reduces future cut-over risk.
Here’s a straightforward first step.
Building OpenSSL 3.5 on RHEL 9.6
OpenSSL v3.5 is only a few months old and isn’t yet in most package repositories, so you’ll need to build it from source. The example below was tested on a Dell XPS 15 running Windows 11.
Not to worry, here is a step-by-step guide on how to do it, for this guide our target platform will be RedHat 9.6. Consider following the guide in a container by creating a dockerfile and using the latest UBI 9 image by adding
FROM redhat/ubi9:latest
at the beginning.
Build and Install
Start by making sure all your packages are up to date
# dnf update && dnf upgrade
There are several tools which are necessary to be able to build and install OpenSSL so make sure you got those
# dnf -y install perl-core zlib-devel kernel-headers make gcc wget
Once you got the prerequisites in order you can download version 3.5 of OpenSSL from the original website using wget and extract the files
# wget https://www.openssl.org/source/openssl-3.5.0.tar.gz && tar -xvzf openssl-3.5.0.tar.gz
Navigate to the folder you just extracted and run the following configuration, it will make sure that OpenSSL is installed to /usr/local to avoid any conflict with current installations of OpenSSL
# ./Configure --prefix=/usr/local/openssl --openssldir=/usr/local/openssl -Wl,-rpath=/usr/local/openssl/lib64 shared zlib
Build and install with make
# make && make install
have patience, this can take a while. Once completed you find it here /usr/local/openssl/bin/openssl. To add it to your PATH
# export PATH="/usr/local/openssl/bin:$PATH"
and finally, to verify that everything was successful we can check the version
# openssl -version
OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)
Here is the completed version of my dockerfile:
Performance
Once you have verified the installation was successful, let's see what is available in the release by using the list command,
# openssl list -help
will display a summary of lists that we can request. This version of OpenSSL has the -kem-algorithms flag, which will give us a list of available KEMs (Key Encapsulation Mechanism).
# openssl list -kem-algorithms
{ 1.2.840.113549.1.1.1, 2.5.8.1.1, RSA, rsaEncryption } @ default
{ 1.2.840.10045.2.1, EC, id-ecPublicKey } @ default
{ 1.3.101.110, X25519 } @ default
{ 1.3.101.111, X448 } @ default
{ 2.16.840.1.101.3.4.4.1, id-alg-ml-kem-512, ML-KEM-512, MLKEM512 } @ default
{ 2.16.840.1.101.3.4.4.2, id-alg-ml-kem-768, ML-KEM-768, MLKEM768 } @ default
{ 2.16.840.1.101.3.4.4.3, id-alg-ml-kem-1024, ML-KEM-1024, MLKEM1024 } @ default
X25519MLKEM768 @ default
X448MLKEM1024 @ default
SecP256r1MLKEM768 @ default
SecP384r1MLKEM1024 @ default
Notice how this lists several different ML-KEM versions. MLKEM512, MLKEM768 and MLKEM1024 represent ML-KEM with the parameter-sets 512, 768 and 1024 respectively which each provide a different level of security. You will also notice that there are some combinations, those being X25519MLKEM768, X448MLKEM1024, SecP256rMLKEM789 and SecP384r1MLKEM1024, these are hybrids and represents and algorithm where both algorithms provide input to a shared secret key – I will not get into the details on how exactly that is done here as these are currently in a draft state and not in scope for this short post.
Micro-Benchmarks
To see how they perform we can use the build in speed command
# openssl speed -kem-algorithms
I have summarized the output from my test run in the table below.
|
keygen |
encaps |
decaps |
SUM |
P-256 |
0,02 |
0,09 |
0,08 |
0,19 |
P-384 |
0,73 |
1,86 |
0,91 |
3,50 |
X25519 |
0,04 |
0,10 |
0,05 |
0,20 |
ML-KEM-512 |
0,02 |
0,02 |
0,02 |
0,06 |
ML-KEM-768 |
0,05 |
0,02 |
0,03 |
0,10 |
ML-KEM-1024 |
0,06 |
0,03 |
0,04 |
0,12 |
X25519-ML-KEM-768 |
0,08 |
0,09 |
0,07 |
0,24 |
SecP256r1-ML-KEM-768 |
0,06 |
0,11 |
0,10 |
0,27 |
SecP384r1-ML-KEM-1024 |
0,80 |
1,47 |
0,77 |
3,04 |
Table 1 average execution time in ms
These are of course the raw algorithms performance numbers (with respect to my personal laptop). While these are all fast it comes with a price in ciphertext sizes.
|
Ciphertex |
P-256 |
65 |
P-384 |
97 |
X25519 |
32 |
ML-KEM-512 |
768 |
ML-KEM-768 |
1088 |
ML-KEM-1024 |
1568 |
X25519-ML-KEM-768 |
1120 |
SecP256r1-ML-KEM-768 |
1185 |
SecP384r1-ML-KEM-1024 |
1665 |
Table 2 sizes in bytes
Take note that the overhead of a hybrid approach is small thus adds additional security – since you would have to break both schemes to decipher the key - at a low cost.
TLS 1.3 Handshake Latency
To put it to the test, let's have a look at the performance when incorporated in a TLS1.3 handshake. To do so we can utilize the OpenSSL tools s_time and s_server to produce some numbers, I configured my server to use TLS1.3 and AES128-GCM-SHA256, here is an average of how long it took to establish a connection with the different key exchange algorithms locally on my machine.
|
Handshake |
P-256 |
2,09 |
P-384 |
6,83 |
X25519 |
2,13 |
ML-KEM-512 |
1,86 |
ML-KEM-768 |
2,08 |
ML-KEM-1024 |
2,26 |
X25519-ML-KEM-768 |
2,36 |
SecP256r1-ML-KEM-768 |
2,27 |
SecP384r1-ML-KEM-1024 |
5,78 |
Table 3 average handshake in ms
Showing that the increase in ciphertext sizes has no significant impact on the performance in our small local test. Keep in mind that this is all running locally on my machine thus real-world examples where data needs to travel further and get through load balancers etc. will affect the numbers. Lastly, I will note that, due to the increase in the ciphertext sizes, it can be hard to fit the ClientHello into a single network package, and while this should be fine according to the standards it can cause issues when establishing connections, as some implementations wrongly does not permit splitting of the HelloClient request over multiple network packages.
To get the available signature algorithms we can use -signature-algorithms
# openssl list -signature-algorithms
[...]
{ 2.16.840.1.101.3.4.3.17, id-ml-dsa-44, ML-DSA-44, MLDSA44 } @ default
{ 2.16.840.1.101.3.4.3.18, id-ml-dsa-65, ML-DSA-65, MLDSA65 } @ default
{ 2.16.840.1.101.3.4.3.19, id-ml-dsa-87, ML-DSA-87, MLDSA87 } @ default
[...]
{ 2.16.840.1.101.3.4.3.20, id-slh-dsa-sha2-128s, SLH-DSA-SHA2-128s } @ default
{ 2.16.840.1.101.3.4.3.21, id-slh-dsa-sha2-128f, SLH-DSA-SHA2-128f } @ default
{ 2.16.840.1.101.3.4.3.22, id-slh-dsa-sha2-192s, SLH-DSA-SHA2-192s } @ default
{ 2.16.840.1.101.3.4.3.23, id-slh-dsa-sha2-192f, SLH-DSA-SHA2-192f } @ default
{ 2.16.840.1.101.3.4.3.24, id-slh-dsa-sha2-256s, SLH-DSA-SHA2-256s } @ default
{ 2.16.840.1.101.3.4.3.25, id-slh-dsa-sha2-256f, SLH-DSA-SHA2-256f } @ default
{ 2.16.840.1.101.3.4.3.26, id-slh-dsa-shake-128s, SLH-DSA-SHAKE-128s } @ default
{ 2.16.840.1.101.3.4.3.27, id-slh-dsa-shake-128f, SLH-DSA-SHAKE-128f } @ default
{ 2.16.840.1.101.3.4.3.28, id-slh-dsa-shake-192s, SLH-DSA-SHAKE-192s } @ default
{ 2.16.840.1.101.3.4.3.29, id-slh-dsa-shake-192f, SLH-DSA-SHAKE-192f } @ default
{ 2.16.840.1.101.3.4.3.30, id-slh-dsa-shake-256s, SLH-DSA-SHAKE-256s } @ default
{ 2.16.840.1.101.3.4.3.31, id-slh-dsa-shake-256f, SLH-DSA-SHAKE-256f } @ default
This returns a long list of available signature algorithms; the list is too long to reproduce here, so I’ve included only the new additions. ML-DSA-44, ML-DSA-65 and ML-DSA-87 represent ML-DSA with parameter sets 44, 65 and 87 respectively, offering increasing levels of security. For SLH-DSA, the variants follow the naming convention SLH-DSA-XXX-YYYZ.
Because SLH-DSA is a hash-based signature algorithm, the choice of hash function - SHA-2 or SHAKE, both supported by FIPS 205 - is embedded in the name. The next identifier, 128, 192 or 256, maps to NIST security levels 1, 3 or 5 (with 5 the highest), and a final letter indicates whether the profile is f for “fast” or s for “small,” reflecting the usual speed-versus-size trade-off.
OpenSSL 3.5.0.8 does not yet support the built-in benchmark command for these algorithms; to obtain performance numbers you need to integrate with the library directly. I did so to produce the figures below, measured on my personal laptop.
|
gen |
sign |
verify |
P-256 (with SHA256) |
0,02 |
0,03 |
0,09 |
P-384 (with SHA256) |
0,89 |
0,77 |
0,73 |
Ed25519 |
0,06 |
0,05 |
0,14 |
ML-DSA-44 |
0,14 |
0,73 |
0,14 |
ML-DSA-65 |
0,24 |
1,13 |
0,20 |
ML-DSA-87 |
0,32 |
1,30 |
0,53 |
SLH-DSA-SHA2-128s |
54,07 |
385,71 |
0,34 |
SLH-DSA-SHA2-128f |
0,88 |
19,13 |
1,01 |
SLH-DSA-SHA2-192s |
79,37 |
813,77 |
0,59 |
SLH-DSA-SHA2-192f |
1,12 |
34,73 |
1,61 |
SLH-DSA-SHA2-256s |
49,78 |
899,74 |
0,94 |
SLH-DSA-SHA2-256f |
3,02 |
66,19 |
1,90 |
SLH-DSA-SHAKE-128s |
138,97 |
1254,44 |
1,08 |
SLH-DSA-SHAKE-128f |
2,66 |
50,90 |
3,11 |
SLH-DSA-SHAKE-192s |
226,19 |
2186,59 |
1,41 |
SLH-DSA-SHAKE-192f |
3,16 |
85,23 |
4,72 |
SLH-DSA-SHAKE-256s |
147,44 |
1838,56 |
2,37 |
SLH-DSA-SHAKE-256f |
10,05 |
171,34 |
4,84 |
Table 4 average execution time in ms
On ML-DSA the performance impact is negligible compared to the classical algorithms, but with SLH-DSA even the “fast” variants introduce noticeable overhead. There is a catch; both key and signature sizes grow substantially, a factor that must be considered in any migration plan to quantum-resistant algorithms.
|
Public Key |
Signature |
P-256 |
64 |
64 |
Ed25519 |
32 |
64 |
ML-DSA-44 |
2420 |
1312 |
ML-DSA-65 |
3309 |
1952 |
ML-DSA-87 |
4627 |
2592 |
SLH-DSA-128s |
32 |
7856 |
SLH-DSA-128f |
32 |
17088 |
SLH-DSA-192s |
48 |
16224 |
SLH-DSA-192f |
48 |
35664 |
SLH-DSA-256s |
64 |
29792 |
SLH-DSA-256f |
64 |
49856 |
Important note
You are not required to switch to quantum-resistant signature algorithms immediately, if it is used for authenticity in a protocol where the signature can easily be replaced within months. In TLS, a signature proves trust at the moment of the handshake; a future quantum computer thus cannot retroactively break that one-off assertion. By contrast, the key-exchange step generates the symmetric key that protects the session traffic – this key exchange an attacker could harvest today and decrypt when a CRQC (cryptographic relevant quantum computer) is developed.
In other domains - firmware signing, code signing, e-passports - signatures cannot be refreshed easily, and product lifetimes may outlast the advent of a CRQC. In those cases, you should migrate to a quantum-resistant signature scheme as soon as practical (see Mosca’s inequality).
Final thoughts
Building OpenSSL 3.5 from source already gives you quantum-safe key-exchange and signature options. That removes many practical blockers and lets security teams run pilot deployments at low cost. I say pilots, not because the algorithms lack maturity, but because many protocols (including TLS) are still in draft; most browsers support these drafts today, so formal standardization is only a matter of time. TLS is just one use-case - others will require the same careful planning.
With modest effort you can benchmark the real-world cost of larger keys and signatures, pressure-test your infrastructure, protect critical links, and de-risk tomorrow’s mandatory cut-over.
Recommendations
- Get comfortable with the standardized algorithms - ML-DSA, SLH-DSA and ML-KEM - to start with. OpenSSL is an excellent sandbox.
- Create a Cryptographic Bill of Materials (CBOM): a detailed inventory of every cryptographic component used within your applications and systems.
- Run a pilot. TLS handshakes are a low-hanging fruit: migrate to a hybrid key-exchange such as X25519-ML-KEM-768. Tip: compile NGINX against OpenSSL 3.5 and use it as a reverse proxy.
- Identify hard-coded algorithms and replace them with configuration-driven choices. Greater crypto-agility removes pain points and lowers long-term risk.
Summary
- ML-KEM offers near-current performance for key exchange, with larger payloads.
- Hybrid key exchanges that include ML-KEM add security and confidence for early adopters with minimal overhead.
- Migrating key exchange to quantum-resistant methods should be prioritized, because today’s traffic can be stored and decrypted later.
- ML-DSA is a fast, size-heavier alternative to current signature schemes.
- SLH-DSA is slower and larger; you can trade speed for size via the f and s variants.
- TLS certificates need not move to quantum-resistant mechanisms until vendor support stabilizes.
- Other signature use-cases (firmware, long-life documents) may need migration sooner - consult your security experts.
Cryptomathic is an industry leader in cryptographic key management and crypto-agility solutions. Download the eBook on Post Quantum Computing and Crypto Agility or contact us to discuss your needs.