Skip to content
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

Zeroisation #67

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 1 addition & 3 deletions .github/workflows/apple.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
# MSRV 1.47 temporarily removed
# See https://github.com/Argyle-Software/kyber/issues/34
rust: [stable]
rust: [1.56.0, stable] # MSRV 1.56

steps:
- uses: actions/checkout@v3
Expand Down
11 changes: 1 addition & 10 deletions .github/workflows/cross.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,13 @@ jobs:
aarch64-linux-android,
aarch64-unknown-linux-gnu,
aarch64-unknown-linux-musl,
arm-linux-androideabi,
arm-unknown-linux-gnueabi,
armv7-unknown-linux-gnueabihf,
mips-unknown-linux-gnu,
mips64-unknown-linux-gnuabi64,
# powerpc-unknown-linux-gnu,
# powerpc64-unknown-linux-gnu,
# riscv64gc-unknown-linux-gnu,
# s390x-unknown-linux-gnu,
# x86_64-unknown-freebsd,
# x86_64-unknown-illumos,
# x86_64-unknown-linux-musl,
# x86_64-unknown-netbsd,
]
feature: [kyber512, kyber768, kyber1024]
opt: ["", 90s]
opt: ["", 90s, "90s-fixslice"]

steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ jobs:
strategy:
matrix:
target: [i686-unknown-linux-gnu, x86_64-unknown-linux-gnu]
# MSRV 1.47
rust: [1.47.0, stable]
rust: [1.56.0, stable] # MSRV 1.56

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
aarch64-pc-windows-msvc
]

rust: [1.47.0, stable] # MSRV 1.47
rust: [1.56.0, stable] # MSRV 1.56

steps:
- uses: actions/checkout@v3
Expand Down
14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ rand_core = { version = "0.6.4", default-features = false }
wasm-bindgen = { version = "0.2.83", optional = true }
sha2 = { version = "0.10.6", optional = true , default-features = false }
getrandom = {version = "0.2.8", features = ["js"], optional = true }
zeroize = { version = "1.5.7", features = ["derive"], optional = true }
zeroize = { version = "1.5.7", optional = true }
aes = { version = "0.8.2", optional = true }
ctr = { version = "0.9.2", optional = true }
pqc_core = { version = "0.2.0", features = ["zero"]}

# Optional dev-deps, see https://github.com/rust-lang/cargo/issues/1596
criterion = { version = "0.4.0", features = ["html_reports"], optional = true }
Expand Down Expand Up @@ -54,11 +57,16 @@ kyber1024 = []
hazmat = []

### Additional features ###
# 90s mode uses AES-CTR and SHA2 as primitives instead
# 90s mode uses AES256-CTR and SHA2 as primitives instead
# Uses a bitslice implementation
90s = ["sha2"]

# Fixslice RustCrypto AES implementation offers some additional sidechannel
# attack resistance. Suggest benchmarking for comparison.
90s-fixslice = ["90s", "aes", "ctr"]

# Use avx2 intrinsics on x86 architectures
# Wont compile if the platform doesn't supprt it
# Wont compile if the platform doesn't support it
avx2 = ["cc"]

# For compiling to wasm targets
Expand Down
95 changes: 45 additions & 50 deletions benches/api.rs

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

Contributions always welcome. Checkout the development branch create a feature fork and submit a PR back to the development branch. If possible please run a benchmark first for any significant regressions.

Current areas of focus aka "aspirational TODO's":
Current areas of focus:

* **Neon ARM intrinsics** - There is a [neon library](https://github.com/cothan/kyber/tree/round3/neon) for Kyber, though currently many ARM intrinsics still don't exist in rust, so there's two branches, `neon` is a rust port of his work that will have to wait until the intrinsics are upstream, `neon_c` is using the original C code with a FFI.
* **Optimizations** - See the benchmarking readme, possibly some fat that can still be trimmed off.
* **Add RustCrypto primitives feature for 90s mode** - This is half done yet commented out, still needs some cleaning up to fit in.
* **Serde** - Implement Serialize/Deserialize traits for the structs and put it behind a feature gate.
* **NASM** - The assembly code is written in GAS, for more portability it would be ideal if it was converted NASM or MASM so it can be used on windows.
* **Mutually Exclusive Features** Currently the crate has all the variants behind feature gates that can't be used together, this is an antipattern in rust, the alternatives are to split the crate up with a lot code of code duplication and maintain them all separately, or make many functions generic, neither are ideal or easy to do.

By submitting any code to this repository you agree to have it licensed under both Apache 2.0 and MIT.
6 changes: 3 additions & 3 deletions examples/ake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ fn main() -> Result<(), KyberError> {
// Bob receives the request and authenticates Alice, sends
// encapsulated shared secret back
let server_send = bob.server_receive(
client_send, &alice_keys.public, &bob_keys.secret, &mut rng
client_send, &alice_keys.public, bob_keys.expose_secret(), &mut rng
)?;

// Alice autheticates and decapsulates
alice.client_confirm(server_send, &alice_keys.secret)?;
// Alice authenticates and decapsulates
alice.client_confirm(server_send, alice_keys.expose_secret())?;

// Both structs now have the shared secret
assert_eq!(alice.shared_secret, bob.shared_secret);
Expand Down
4 changes: 2 additions & 2 deletions examples/kem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ fn main () -> Result<(), KyberError> {
let (ciphertext, shared_secret_bob) = encapsulate(&alice_keys.public, &mut rng)?;

// Alice decapsulates the shared secret
let shared_secret_alice = decapsulate(&ciphertext, &alice_keys.secret)?;
let shared_secret_alice = decapsulate(&ciphertext, alice_keys.expose_secret())?;

// Both can now communicate symetrically
// Both can now communicate symmetrically
assert_eq!(shared_secret_alice, shared_secret_bob);
Ok(())
}
4 changes: 2 additions & 2 deletions examples/uake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ fn main() -> Result<(), KyberError> {
// Bob receives the request and authenticates Alice, sends
// encapsulated shared secret back
let server_send = bob.server_receive(
client_send, &bob_keys.secret, &mut rng
client_send, bob_keys.expose_secret(), &mut rng
)?;

// Alice autheticates and decapsulates
// Alice authenticates and decapsulates
alice.client_confirm(server_send)?;

// Both structs now have the shared secret
Expand Down
17 changes: 9 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ It is recommended to use Kyber in a hybrid system alongside a traditional key ex

Please also read the [**security considerations**](#security-considerations) before use.

**Minimum Supported Rust Version: 1.47.0**
**Minimum Supported Rust Version: 1.56.0**

---

Expand Down Expand Up @@ -60,7 +60,7 @@ let keys_bob = keypair(&mut rng);
let (ciphertext, shared_secret_alice) = encapsulate(&keys_bob.public, &mut rng)?;

// Bob decapsulates a shared secret using the ciphertext sent by Alice
let shared_secret_bob = decapsulate(&ciphertext, &keys_bob.secret)?;
let shared_secret_bob = decapsulate(&ciphertext, keys_bob.expose_secret())?;

assert_eq!(shared_secret_alice, shared_secret_bob);
```
Expand All @@ -83,7 +83,7 @@ let client_init = alice.client_init(&bob_keys.public, &mut rng);

// Bob authenticates and responds
let server_response = bob.server_receive(
client_init, &bob_keys.secret, &mut rng
client_init, bob_keys.expose_secret(), &mut rng
)?;

// Alice decapsulates the shared secret
Expand All @@ -108,10 +108,10 @@ let bob_keys = keypair(&mut rng);
let client_init = alice.client_init(&bob_keys.public, &mut rng);

let server_response = bob.server_receive(
client_init, &alice_keys.public, &bob_keys.secret, &mut rng
client_init, &alice_keys.public, bob_keys.expose_secret(), &mut rng
)?;

alice.client_confirm(server_response, &alice_keys.secret)?;
alice.client_confirm(server_response, alice_keys.expose_secret())?;

assert_eq!(alice.shared_secret, bob.shared_secret);
```
Expand Down Expand Up @@ -139,15 +139,16 @@ pqc_kyber = {version = "0.4.0", features = ["kyber512", "90s", "avx2"]}

| Feature | Description |
|-----------|------------|
| std | Enable the standard library |
| kyber512 | Enables kyber512 mode, with a security level roughly equivalent to AES-128.|
| kyber1024 | Enables kyber1024 mode, with a security level roughly equivalent to AES-256. A compile-time error is raised if more than one security level is specified.|
| 90s | Uses SHA2 and AES in counter mode as a replacement for SHAKE. This can provide hardware speedups in some cases. |
| 90s | Uses AES256 in counter mode and SHA2 as a replacement for SHAKE. This can provide hardware speedups in some cases.|
| 90s-fixslice | Uses a fixslice implementation of AES256 by RustCrypto, this provides greater side-channel attack resistance, especially on embedded platforms |
| avx2 | On x86_64 platforms enable the optimized version. This flag is will cause a compile error on other architectures. |
| wasm | For compiling to WASM targets|
| nasm | Uses Netwide Assembler avx2 code instead of GAS for portability. Requires a nasm compiler: https://www.nasm.us/ |
| zeroize | This will zero out the key exchange structs on drop using the [zeroize](https://docs.rs/zeroize/latest/zeroize/) crate |
| std | Enable the standard library |
| benchmarking | Enables the criterion benchmarking suite |
| benchmarking | Enables the criterion benchmarking suite |
---

## Testing
Expand Down
70 changes: 55 additions & 15 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
use pqc_core::zero;
use crate::{
params::*,
error::KyberError,
Expand All @@ -11,18 +14,18 @@ use crate::{
/// ### Example
/// ```
/// # use pqc_kyber::*;
/// # fn main() -> Result<(), KyberError> {
/// let mut rng = rand::thread_rng();
/// let keys = keypair(&mut rng);
/// # Ok(())}
/// ```
pub fn keypair<R>(rng: &mut R) -> Keypair
where R: RngCore + CryptoRng
{
let mut public = [0u8; KYBER_PUBLICKEYBYTES];
let mut secret = [0u8; KYBER_SECRETKEYBYTES];
crypto_kem_keypair(&mut public, &mut secret, rng, None);
Keypair { public, secret }
let keys = Keypair { public, secret };
zero!(secret);
keys
}

/// Encapsulates a public key returning the ciphertext to send
Expand All @@ -37,7 +40,7 @@ pub fn keypair<R>(rng: &mut R) -> Keypair
/// let (ciphertext, shared_secret) = encapsulate(&keys.public, &mut rng)?;
/// # Ok(())}
/// ```
pub fn encapsulate<R>(pk: &[u8], rng: &mut R) -> Encapsulated
pub fn encapsulate<R>(pk: &[u8], rng: &mut R) -> Encapsulated
where R: CryptoRng + RngCore
{
if pk.len() != KYBER_PUBLICKEYBYTES {
Expand All @@ -48,7 +51,7 @@ pub fn encapsulate<R>(pk: &[u8], rng: &mut R) -> Encapsulated
crypto_kem_enc(&mut ct, &mut ss, pk, rng, None);
Ok((ct, ss))
}

/// Decapsulates ciphertext with a secret key, the result will contain
/// a KyberError if decapsulation fails
///
Expand All @@ -59,7 +62,7 @@ pub fn encapsulate<R>(pk: &[u8], rng: &mut R) -> Encapsulated
/// let mut rng = rand::thread_rng();
/// let keys = keypair(&mut rng);
/// let (ct, ss1) = encapsulate(&keys.public, &mut rng)?;
/// let ss2 = decapsulate(&ct, &keys.secret)?;
/// let ss2 = decapsulate(&ct, keys.expose_secret())?;
/// assert_eq!(ss1, ss2);
/// # Ok(())}
/// ```
Expand All @@ -78,26 +81,63 @@ pub fn decapsulate(ct: &[u8], sk: &[u8]) -> Decapsulated
/// A public/secret keypair for use with Kyber.
///
/// Byte lengths of the keys are determined by the security level chosen.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[derive(Clone)]
pub struct Keypair {
pub public: PublicKey,
pub secret: SecretKey
secret: SecretKey,
}

impl Keypair {
/// Securely generates a new keypair`
/// ```
/// # use pqc_kyber::*;
/// # fn main() -> Result<(), KyberError> {
/// let mut rng = rand::thread_rng();
/// let keys = Keypair::generate(&mut rng);
/// # let empty_keys = Keypair{
/// public: [0u8; KYBER_PUBLICKEYBYTES], secret: [0u8; KYBER_SECRETKEYBYTES]
/// };
/// # assert!(empty_keys != keys);
/// # Ok(()) }
/// ```
pub fn generate<R: CryptoRng + RngCore>(rng: &mut R) -> Keypair {
keypair(rng)
}
}

/// Explicitly exposes the secret key
///```
/// # use pqc_kyber::*;
/// # let mut rng = rand::thread_rng();
/// let keys = Keypair::generate(&mut rng);
/// let secret = keys.expose_secret();
/// # assert!(secret.len() == KYBER_SECRETKEYBYTES);
/// ```
pub fn expose_secret(&self) -> &SecretKey {
&self.secret
}
}

#[cfg(feature = "zeroize")]
impl Drop for Keypair {
fn drop(&mut self) {
self.secret.zeroize()
}
}

/// Elides the secret key, to debug it use [`Keypair::expose_secret()`]
impl core::fmt::Debug for Keypair {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{{ public: '{:x?}',\n secret: 'ELIDED'}}", self.public)
}
}

/// Ignores secret key to avoid leakage from of a non-cryptographic hasher
impl core::hash::Hash for Keypair {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.public.hash(state);
}
}

/// Non-constant time equality comparison, only checks public keys
impl PartialEq for Keypair {
fn eq(&self, other: &Keypair) -> bool {
self.public == other.public
}
}

impl Eq for Keypair {}

9 changes: 8 additions & 1 deletion src/kem.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::rng::randombytes;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
use pqc_core::zero;
use rand_core::{RngCore, CryptoRng};
use crate::{
params::*,
indcpa::*,
symmetric::*,
error::KyberError,
rng::randombytes,
verify::*
};

Expand Down Expand Up @@ -63,19 +66,22 @@ pub fn crypto_kem_enc<R>(

// Don't release system RNG output
hash_h(&mut buf, &randbuf, KYBER_SYMBYTES);
zero!(randbuf);

// Multitarget countermeasure for coins + contributory KEM
hash_h(&mut buf[KYBER_SYMBYTES..], pk, KYBER_PUBLICKEYBYTES);
hash_g(&mut kr, &buf, 2*KYBER_SYMBYTES);

// coins are in kr[KYBER_SYMBYTES..]
indcpa_enc(ct, &buf, pk, &kr[KYBER_SYMBYTES..]);
zero!(buf);

// overwrite coins in kr with H(c)
hash_h(&mut kr[KYBER_SYMBYTES..], ct, KYBER_CIPHERTEXTBYTES);

// hash concatenation of pre-k and H(c) to k
kdf(ss, &kr, 2*KYBER_SYMBYTES);
zero!(kr);
}

// Name: crypto_kem_dec
Expand Down Expand Up @@ -117,6 +123,7 @@ pub fn crypto_kem_dec(
cmov(&mut kr, &sk[END..], KYBER_SYMBYTES, fail);
// hash concatenation of pre-k and H(c) to k
kdf(ss, &kr, 2*KYBER_SYMBYTES);
zero!(kr);

match fail {
0 => Ok(()),
Expand Down
Loading