classic-mceliece-rust
This is a pure-rust safe-rust implementation of the Classic McEliece post-quantum scheme.
- Classic McEliece is a code-based key encapsulation mechanism (KEM)
- The implementation is based on the Classic McEliece reference implementation of NIST round 3
- The implementation does not utilize any concurrency techniques (SIMD/threading/…, except maybe auto-vectorization on your CPU)
- It depends on
sha3
as SHA-3 implementation andaes
as AES block cipher (used as RNG) implementation - It passes the 100 testcases of the C reference implementation
- It implements all 10 variants of the Classic McEliece KEM
- The implementation takes between 100 milliseconds (
mceliece348864
) and 500 milliseconds (mceliece8192128f
) to run on a modern computer - The implementation is constant-time on software instruction level
- The random number generator is based on AES256 in counter mode
- First described in 1978, the cryptographic scheme has a rich history in security analysis. Its large public key size, however, often limits adoption.
The 10 variants have the following designated identifiers:
mceliece348864
mceliece348864f
mceliece460896
mceliece460896f
mceliece6688128
mceliece6688128f
mceliece6960119
mceliece6960119f
mceliece8192128
mceliece8192128f
Who should use it?
Anyone, how wants to use Classic McEliece to negotiate a key between two parties.
How does one use it storing keys on the heap (default feature alloc
)?
Add this to your Cargo.toml
:
[]
= "2.0"
To use a specific Classic McEliece variant, you need to import it with the corresponding feature flag:
[]
= { = "2.0", = ["mceliece6960119"] }
Assuming this dependency, the simplest and most ergonomic way of using the library
is with heap allocated keys and the *_boxed
KEM step functions. First, we import them:
use ;
Followingly, we run the KEM and provide generated keys accordingly. Here, we consider an example where we run it in a separate thread (be aware that the example also depends on the rand crate):
Pay attention that public keys in Classic McEliece are huge (between 255 KB and 1.3 MB). As a result, running the algorithm requires a lot of memory. You need to consider where you store it. In case of this API, the advantages are …
- you don't need to handle the memory manually
- on Windows, the call to
keypair
uses more stack than is available by default. Such stack size limitations can be avoided with the heap-allocation API (see Windows remark below).
How does one use it storing keys on the stack (disabled feature alloc
)?
The other option is that you exclude the heap-allocation API and use the provided stack-allocation API. Its advantages are:
- stack allocation also works in a
no_std
environment. - on some microcontroller platforms, a heap is not available.
- stack [de]allocation in general is faster than heap [de]allocation
Thus, in this section we want to show you how to use this API without the heap. For this, you need to disable feature alloc
which is enabled per default (this line retains default feature zeroize
but removes default feature alloc
):
= { = "2.0", = false, = ["zeroize"] }
How does one use the API then (be aware that the example also depends on the rand crate)?
use ;
use ;
Here, you can see how the keys are allocated explicitly.
A remark on Windows
If you want your program to be portable with stack allocation and not unexpectedly crash, you should probably run the entire key exchange in a dedicated thread with a large enough stack size. This code snippet shows the idea:
.stack_size
.spawn
.unwrap;
new
Feature kem: RustCrypto APIs
If the kem
feature is enabled, key encapsulation and decapsulation can also be done via
the standard traits in the kem
crate.
Feature zeroize: Clear out secrets from memory
If the zeroize
feature is enabled (it is by default), all key types that contain anything secret
implements Zeroize
and ZeroizeOnDrop
. This makes them clear their memory when they go out of
scope, and lowers the risk of secret key material leaking in one way or another.
Please mind that this of course makes any buffers you pass into the library useless for reading
out the key from. Instead of trying to fetch the key material from the buffers you pass in,
get it from the as_array
method.
How does one run it?
This library comes with two examples:
The output annotates messages with Alice/Bob to illustrate which data is processed by which party.
The katkem
example implements the classic request/response file structure which is part of the NIST PQC framework.
The different variants can be enabled through feature flags:
mceliece348864
is the default variant. You cannot enable two variants simultaneously.
How fast is it?
All data uses clock cycles as unit (the smaller the better). The rust implementation yielded the following runtime results:
The C reference implementation yielded the following runtime results:
The tests were done on a Lenovo Thinkpad x260 (Intel Core i5-6200U CPU @ 2.30GHz). In the case of rust, criterion 0.3.5 has been used as given in benches/
and in case of C, Google's benchmark with PFM support and disabled CPU frequency scaling. You can run the benchmark suite yourself with the bench
subcommand and optionally some variant feature flag:
Is it correct?
Yes, besides passing unittests (derived from the C implementation), the generated KAT KEM test files have equivalent MD5 hashes. Namely …
Where is the source code?
On github.
What is the content's license?
Changelog
- 2023-01-29 version 2.0.2: exclude testdata folder & files in order to reduce published crate size
- 2022-09-08 version 2.0.1: fix README documentation
- 2022-09-06 version 2.0.0: refined API with heap-allocated keys and RustCrypto integration
- 2022-09-06 version 1.1.0: add CI, clippy, infallible SHAKE impl, forbid unsafe code
- 2022-04-12 version 1.0.1: fix C&P mistakes in documentation
- 2022-04-01 version 1.0.0: public release (no April fools though)
Where can I ask you to fix a bug?
On github.