#e2ee #encryption #crypto #security

rgp

Enabling efficient E2EE for large-audience pub/sub

19 releases

new 0.2.0-alpha.4 Feb 22, 2024
0.2.0-alpha.2 Feb 20, 2024
0.1.13 Feb 15, 2024

#207 in Cryptography

Download history 128/week @ 2024-02-01 295/week @ 2024-02-08 656/week @ 2024-02-15

1,079 downloads per month

MIT license

26KB
279 lines

RGP

ci license crates.io docs.rs dependency status

Relatively Good Privacy

Ciphersuite

  • Blake2s256 for hashing
  • Ed25519 for signatures
  • X25519 for shared secrets
  • XChaCha20 for content keys
  • XChaCha20Poly1305 for content

Modes

There are currently three supported modes: Dh (Diffie-Hellman), Hmac, and Session. All modes provide the ability to sign content and verify the sender. Deniability is preserved by signing the plaintext and encrypting the signature alongside the plaintext.

Diffie-Hellman

Dh mode provides forward secrecy by generating a fresh/random content key for each message and encrypting a copy of that key for each recipient (similar to the way PGP session keys work).

use rgp::{
    Components, decrypt, Decrypt, encrypt, Encrypt,
    extract_components_mut, generate_dh_keys,
    generate_fingerprint
};

let (fingerprint, verifying_key) = generate_fingerprint();

let (sender_priv_key, sender_pub_key) = generate_dh_keys();
let (receiver_priv_key, receiver_pub_key) = generate_dh_keys();

let mut pub_keys = vec![receiver_pub_key];

// 8mb
let content = vec![0u8; 8_000_000];

// add another 20,000 recipients
for _ in 0..20_000 {
    let (_, pub_key) = generate_dh_keys();
    pub_keys.push(pub_key)
}

// encrypt message for all recipients
let (mut encrypted_content, content_key) = encrypt(
    fingerprint,
    content.clone(),
    Encrypt::Dh(sender_priv_key, &pub_keys),
)
.unwrap();

// extract components for position 0
if let Components::Dh(key) = extract_components_mut(0, &mut encrypted_content) {

    // decrypt message with encrypted content key
    let (decrypted_content, decrypted_content_key) = decrypt(
        Some(&verifying_key),
        &encrypted_content,
        Decrypt::Dh(key, sender_pub_key, receiver_priv_key),
    )
    .unwrap();
    
    assert_eq!(decrypted_content, content);
    assert_eq!(decrypted_content_key, content_key);
};

How it works:

  1. Generate one-time components
    • nonce
    • content key
  2. Sign plaintext to generate content signature
  3. Encrypt plaintext and content signature with content key
  4. Encrypt content key for all recipients
    • Generate shared secret with recipient public key and sender private key
    • Encrypt content key with shared secret

Format:

  • nonce = 24 bytes
  • keys count
    • int size = 2 bits
    • count
      • numbers 0-63 = 6 bits
      • numbers >63 = 1-8 bytes (big endian int)
  • encrypted copies of content key = pub_keys.len() * 32 bytes
  • encrypted content = content.len()
  • signature = 64 bytes (encrypted along with the content to preserve deniability)
  • Poly1305 MAC = 16 bytes
  • mode = 1 byte (set to 2 for Dh)

HMAC

Hmac mode provides backward secrecy, and can enable forward secrecy when both the HMAC key and value are kept secret, and only the content key is compromised.

use rgp::{
    Components, decrypt, Decrypt, encrypt, Encrypt,
    extract_components_mut, generate_dh_keys,
    generate_fingerprint
};

let (fingerprint, verifying_key) = generate_fingerprint();

let hash_key = [0u8; 32]; // use an actual key
let hash_value = [1u8; 32]; // use an actual key

let content = vec![0u8; 8_000_000];

// encrypt message keyed hash result
let (mut encrypted_content, content_key) = encrypt(
    fingerprint,
    content.clone(),
    Encrypt::Hmac(hash_key, hash_value, 42),
)
.unwrap();

if let Components::Hmac(itr) = extract_components_mut(0, &mut encrypted_content) {
    assert_eq!(itr, 42);

    // decrypt message with keyed hash result mode
    let (decrypted_content, hashed_content_key) = decrypt(
        Some(&verifying_key),
        &encrypted_content,
        rgp::Decrypt::Hmac(hash_key, hash_value),
    )
    .unwrap();

    assert_eq!(decrypted_content, content);
    assert_eq!(hashed_content_key, content_key);
};

How it works:

  1. Generate nonce
  2. Hmac the content key
  3. Sign plaintext to generate content signature
  4. Encrypt plaintext and content signature with the hashed content key

Format:

  • nonce = 24 bytes
  • iteration
    • int size = 2 bits
    • iteration
      • numbers 0-63 = 6 bits
      • numbers >63 = 1-8 bytes (big endian int)
  • encrypted content = content.len()
  • signature = 64 bytes (encrypted along with the content to preserve deniability)
  • Poly1305 MAC = 16 bytes
  • mode = 1 byte (set to 1 for Hmac)

Session

Session provides no forward or backward secrecy, and uses the provided key "as is" without any modification.

use rgp::{
    Components, decrypt, Decrypt, encrypt, Encrypt,
    extract_components_mut, generate_dh_keys,
    generate_fingerprint
};

let (fingerprint, verifying_key) = generate_fingerprint();

let session_key = [0u8; 32]; // use an actual key
let content = vec![0u8; 8_000_000];

// encrypt message with a session key
let (mut encrypted_content, _) = encrypt(
    fingerprint,
    content.clone(),
    Encrypt::Session(session_key),
)
.unwrap();

if let Components::Session = extract_components_mut(0, &mut encrypted_content) {

    // decrypt message with session key
    let (decrypted_content, _) = decrypt(
        Some(&verifying_key),
        &encrypted_content,
        Decrypt::Session(session_key),
    )
    .unwrap();
    
    assert_eq!(decrypted_content, content);
}

How it works:

  1. Generate nonce
  2. Sign plaintext to generate content signature
  3. Encrypt plaintext and content signature with the provided content key, as is

Format:

  • nonce = 24 bytes
  • encrypted content = content.len()
  • signature = 64 bytes (encrypted along with the content to preserve deniability)
  • Poly1305 MAC = 16 bytes
  • mode = 1 byte (set to 0 for Hmac)

Performance

To check performance on your machine, run cargo bench. You can also view the latest benches in the GitHub CI workflow under job/Benchmark.

All benchmarks for multi-recipient payloads are for 20,000 recipients, and all benchmarks encrypt/sign/decrypt 8mb.

License

MIT

Security

THIS CODE HAS NOT BEEN AUDITED OR REVIEWED. USE AT YOUR OWN RISK.

Dependencies

~4–5MB
~102K SLoC