cryptography

Implementing End-to-End Encryption for Messaging

End-to-end encryption (E2EE) ensures that only the communicating parties can read messages, with no intermediary (including the server) able to decrypt them. This skill implements a simplified version of the Signal Protocol's Double Ratchet algorithm, using X25519 for key exchange, HKDF for key derivation, and AES-256-GCM for message encryption.

cryptographye2eencryptionmessagingsignal-protocol
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

End-to-end encryption (E2EE) ensures that only the communicating parties can read messages, with no intermediary (including the server) able to decrypt them. This skill implements a simplified version of the Signal Protocol's Double Ratchet algorithm, using X25519 for key exchange, HKDF for key derivation, and AES-256-GCM for message encryption.

When to Use

  • When deploying or configuring implementing end to end encryption for messaging capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Familiarity with cryptography concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Objectives

  • Implement X25519 Diffie-Hellman key exchange for session establishment
  • Build the Double Ratchet key management algorithm
  • Encrypt and decrypt messages with per-message keys
  • Implement forward secrecy (compromise of current key does not reveal past messages)
  • Handle out-of-order message delivery
  • Implement key agreement using X3DH (Extended Triple Diffie-Hellman)

Key Concepts

Signal Protocol Components

Component Purpose Algorithm
X3DH Initial key agreement X25519
Double Ratchet Ongoing key management X25519 + HKDF + AES-GCM
Sending Chain Per-message encryption keys HMAC-SHA256 chain
Receiving Chain Per-message decryption keys HMAC-SHA256 chain
Root Chain Derives new chain keys on DH ratchet HKDF

Forward Secrecy

Each message uses a unique encryption key derived from a ratcheting chain. After a key is used, it is deleted, ensuring that compromise of the current state does not reveal previously sent/received messages.

Security Considerations

  • Delete message keys immediately after decryption
  • Implement message ordering and replay protection
  • Use authenticated encryption (AES-GCM) for all messages
  • Protect identity keys with device-level security
  • Verify identity keys out-of-band (safety numbers)

Validation Criteria

  • X25519 key exchange produces shared secret
  • Messages encrypt and decrypt correctly between two parties
  • Different messages produce different ciphertexts
  • Forward secrecy: old keys cannot decrypt new messages
  • Out-of-order messages can be decrypted
  • Tampered messages are rejected by authentication
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.8 KB

API Reference — Implementing End-to-End Encryption for Messaging

Libraries Used

  • cryptography: X25519 key exchange, HKDF key derivation, AES-256-GCM encryption

CLI Interface

python agent.py keygen                                    # Generate X25519 key pair
python agent.py exchange                                  # Simulate key exchange
python agent.py demo                                      # Full E2EE demo flow
python agent.py encrypt --message <text> --key <hex>      # Encrypt message
python agent.py decrypt --nonce <hex> --ciphertext <hex> --key <hex>

Core Functions

generate_keypair()

Generates X25519 key pair for Diffie-Hellman key exchange.

  • X25519PrivateKey.generate() -> private key
  • private_key.public_key() -> public key
  • Returns hex-encoded private and public keys.

derive_shared_secret(my_private_hex, their_public_hex)

Performs X25519 ECDH key exchange and derives symmetric key via HKDF-SHA256.

  • my_private.exchange(their_public) -> 32-byte raw shared secret
  • HKDF(algorithm=SHA256(), length=32, info=b"e2ee-messaging-v1").derive(shared)

encrypt_message(message, shared_key_hex)

Encrypts plaintext using AES-256-GCM with random 12-byte nonce.

  • AESGCM(key).encrypt(nonce, plaintext, None) -> ciphertext with GCM tag

decrypt_message(nonce_hex, ciphertext_hex, shared_key_hex)

Decrypts and authenticates ciphertext. Raises InvalidTag if tampered.

Cryptography API Calls

Class Module Purpose
X25519PrivateKey cryptography.hazmat.primitives.asymmetric.x25519 ECDH private key
X25519PublicKey same ECDH public key
AESGCM cryptography.hazmat.primitives.ciphers.aead Authenticated encryption
HKDF cryptography.hazmat.primitives.kdf.hkdf Key derivation

Dependencies

pip install cryptography>=41.0
standards.md1.4 KB

Standards and References - End-to-End Encryption for Messaging

Signal Protocol Specifications

The Double Ratchet Algorithm

The X3DH Key Agreement Protocol

The Sesame Algorithm

Cryptographic Standards

RFC 7748 - Elliptic Curves for Security (X25519)

RFC 5869 - HKDF (HMAC-based Key Derivation Function)

RFC 8032 - Edwards-Curve Digital Signature Algorithm (Ed25519)

NIST SP 800-38D - AES-GCM

Python Libraries

cryptography

  • X25519: cryptography.hazmat.primitives.asymmetric.x25519
  • HKDF: cryptography.hazmat.primitives.kdf.hkdf
  • AES-GCM: cryptography.hazmat.primitives.ciphers.aead.AESGCM
workflows.md3.0 KB

Workflows - End-to-End Encryption for Messaging

Workflow 1: X3DH Key Agreement

Alice (initiator)                 Server                  Bob (responder)
  |                                 |                         |
  |                                 |<-- Register:            |
  |                                 |    Identity Key (IK_B)  |
  |                                 |    Signed PreKey (SPK_B)|
  |                                 |    One-Time PreKeys     |
  |                                 |                         |
  |-- Fetch Bob's Keys ----------->|                         |
  |<-- IK_B, SPK_B, OPK_B --------|                         |
  |                                 |                         |
  [Compute shared secret]:                                    |
  DH1 = DH(IK_A, SPK_B)                                     |
  DH2 = DH(EK_A, IK_B)                                      |
  DH3 = DH(EK_A, SPK_B)                                     |
  DH4 = DH(EK_A, OPK_B)                                     |
  SK = HKDF(DH1 || DH2 || DH3 || DH4)                      |
  |                                 |                         |
  |-- Send Initial Message ------->|-- Forward to Bob ------>|
  |   (IK_A, EK_A, OPK_id, msg)   |                         |
  |                                 |   [Bob computes same SK]|

Workflow 2: Double Ratchet (Sending)

[Message to Send]
      |
[Check: Do we have recipient's new DH public key?]
  YES --> [DH Ratchet Step]
          - Generate new DH key pair
          - Compute DH shared secret
          - Derive new root key + sending chain key via HKDF
  NO  --> [Continue with current sending chain]
      |
[Symmetric Ratchet: Derive message key from sending chain]
(chain_key, message_key) = HMAC(chain_key, constants)
      |
[Encrypt message with AES-256-GCM using message_key]
      |
[Include header: DH public key, previous chain length, message number]
      |
[Delete message_key from memory]

Workflow 3: Double Ratchet (Receiving)

[Received Encrypted Message + Header]
      |
[Check DH public key in header]
  [New key?]
    YES --> [DH Ratchet Step]
            - Compute DH shared secret
            - Derive new root key + receiving chain key
    NO  --> [Use current receiving chain]
      |
[Symmetric Ratchet: Derive message key]
      |
[Decrypt message with AES-256-GCM]
      |
[Verify authentication tag]
  FAIL --> Reject message
  PASS --> Return plaintext
      |
[Delete message_key from memory]

Workflow 4: Session Lifecycle

[Initial Contact] --> [X3DH Key Exchange]
                            |
                      [Initialize Double Ratchet]
                            |
                      [Exchange Messages]
                      (DH ratchet + symmetric ratchet)
                            |
                      [Periodic DH Ratchet]
                      (every N messages or on reply)
                            |
                      [Session End / Archive]

Scripts 2

agent.py5.2 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for implementing end-to-end encryption (E2EE) for messaging using X25519 + AES-GCM."""

import json
import argparse
import os

try:
    from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    from cryptography.hazmat.primitives.kdf.hkdf import HKDF
    from cryptography.hazmat.primitives import hashes, serialization
    HAS_CRYPTO = True
except ImportError:
    HAS_CRYPTO = False

NONCE_SIZE = 12
KEY_SIZE = 32
HKDF_INFO = b"e2ee-messaging-v1"


def generate_keypair():
    """Generate X25519 key pair for Diffie-Hellman key exchange."""
    private_key = X25519PrivateKey.generate()
    public_key = private_key.public_key()
    priv_bytes = private_key.private_bytes(
        serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption()
    )
    pub_bytes = public_key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
    return {
        "private_key_hex": priv_bytes.hex(),
        "public_key_hex": pub_bytes.hex(),
        "algorithm": "X25519",
    }


def derive_shared_secret(my_private_hex, their_public_hex):
    """Derive shared secret using X25519 ECDH + HKDF-SHA256."""
    my_private = X25519PrivateKey.from_private_bytes(bytes.fromhex(my_private_hex))
    their_public = X25519PublicKey.from_public_bytes(bytes.fromhex(their_public_hex))
    shared_key = my_private.exchange(their_public)
    derived_key = HKDF(
        algorithm=hashes.SHA256(), length=KEY_SIZE, salt=None, info=HKDF_INFO
    ).derive(shared_key)
    return derived_key


def encrypt_message(message, shared_key_hex):
    """Encrypt a message using AES-256-GCM with a shared key."""
    key = bytes.fromhex(shared_key_hex)
    nonce = os.urandom(NONCE_SIZE)
    aesgcm = AESGCM(key)
    ciphertext = aesgcm.encrypt(nonce, message.encode("utf-8"), None)
    return {
        "nonce_hex": nonce.hex(),
        "ciphertext_hex": ciphertext.hex(),
        "algorithm": "AES-256-GCM",
    }


def decrypt_message(nonce_hex, ciphertext_hex, shared_key_hex):
    """Decrypt a message using AES-256-GCM."""
    key = bytes.fromhex(shared_key_hex)
    nonce = bytes.fromhex(nonce_hex)
    ciphertext = bytes.fromhex(ciphertext_hex)
    aesgcm = AESGCM(key)
    plaintext = aesgcm.decrypt(nonce, ciphertext, None)
    return {"plaintext": plaintext.decode("utf-8")}


def simulate_key_exchange(alice_name="Alice", bob_name="Bob"):
    """Simulate a complete key exchange between two parties."""
    alice_kp = generate_keypair()
    bob_kp = generate_keypair()

    alice_shared = derive_shared_secret(alice_kp["private_key_hex"], bob_kp["public_key_hex"])
    bob_shared = derive_shared_secret(bob_kp["private_key_hex"], alice_kp["public_key_hex"])

    keys_match = alice_shared == bob_shared
    return {
        "alice_public_key": alice_kp["public_key_hex"],
        "bob_public_key": bob_kp["public_key_hex"],
        "shared_secret_match": keys_match,
        "shared_key_hex": alice_shared.hex() if keys_match else None,
        "key_exchange": "X25519 ECDH",
        "kdf": "HKDF-SHA256",
        "encryption": "AES-256-GCM",
    }


def demo_full_flow():
    """Demonstrate complete E2EE message flow."""
    kx = simulate_key_exchange()
    if not kx["shared_secret_match"]:
        return {"error": "Key exchange failed"}
    shared_key = kx["shared_key_hex"]
    test_message = "Hello, this is an end-to-end encrypted message."
    encrypted = encrypt_message(test_message, shared_key)
    decrypted = decrypt_message(encrypted["nonce_hex"], encrypted["ciphertext_hex"], shared_key)
    return {
        "key_exchange": kx,
        "original_message": test_message,
        "encrypted": encrypted,
        "decrypted": decrypted,
        "integrity_check": decrypted["plaintext"] == test_message,
    }


def main():
    if not HAS_CRYPTO:
        print(json.dumps({"error": "cryptography library not installed"}))
        return
    parser = argparse.ArgumentParser(description="E2EE Messaging Agent (X25519 + AES-256-GCM)")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("keygen", help="Generate X25519 key pair")
    sub.add_parser("exchange", help="Simulate key exchange")
    sub.add_parser("demo", help="Full E2EE demo flow")
    e = sub.add_parser("encrypt", help="Encrypt message")
    e.add_argument("--message", required=True)
    e.add_argument("--key", required=True, help="Shared key hex")
    d = sub.add_parser("decrypt", help="Decrypt message")
    d.add_argument("--nonce", required=True)
    d.add_argument("--ciphertext", required=True)
    d.add_argument("--key", required=True, help="Shared key hex")
    args = parser.parse_args()
    if args.command == "keygen":
        result = generate_keypair()
    elif args.command == "exchange":
        result = simulate_key_exchange()
    elif args.command == "demo":
        result = demo_full_flow()
    elif args.command == "encrypt":
        result = encrypt_message(args.message, args.key)
    elif args.command == "decrypt":
        result = decrypt_message(args.nonce, args.ciphertext, args.key)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
process.py12.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
End-to-End Encryption for Messaging (Simplified Double Ratchet)

Implements a simplified version of the Signal Protocol's Double Ratchet
algorithm using X25519 key exchange, HKDF key derivation, and AES-256-GCM.

Requirements:
    pip install cryptography

Usage:
    python process.py demo
    python process.py benchmark --messages 1000
"""

import os
import sys
import json
import time
import struct
import hashlib
import argparse
import logging
from dataclasses import dataclass, field
from typing import Dict, Optional, Tuple, List

from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes, hmac, serialization
from cryptography.hazmat.backends import default_backend

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

INFO_ROOT_KEY = b"DoubleRatchetRootKey"
INFO_CHAIN_KEY = b"DoubleRatchetChainKey"
CHAIN_KEY_CONSTANT = b"\x01"
MESSAGE_KEY_CONSTANT = b"\x02"


def generate_x25519_keypair() -> Tuple[X25519PrivateKey, bytes]:
    """Generate an X25519 key pair, returning (private_key, public_key_bytes)."""
    private_key = X25519PrivateKey.generate()
    public_bytes = private_key.public_key().public_bytes(
        serialization.Encoding.Raw, serialization.PublicFormat.Raw
    )
    return private_key, public_bytes


def dh(private_key: X25519PrivateKey, public_key_bytes: bytes) -> bytes:
    """Perform X25519 Diffie-Hellman key exchange."""
    public_key = X25519PublicKey.from_public_bytes(public_key_bytes)
    return private_key.exchange(public_key)


def hkdf_derive(input_key: bytes, info: bytes, length: int = 64) -> bytes:
    """Derive key material using HKDF-SHA256."""
    derived = HKDF(
        algorithm=hashes.SHA256(),
        length=length,
        salt=b"\x00" * 32,
        info=info,
        backend=default_backend(),
    ).derive(input_key)
    return derived


def hmac_derive(key: bytes, constant: bytes) -> bytes:
    """Derive a key using HMAC-SHA256."""
    h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
    h.update(constant)
    return h.finalize()


def kdf_rk(root_key: bytes, dh_output: bytes) -> Tuple[bytes, bytes]:
    """Root key KDF: derive new root key and chain key from DH output."""
    derived = hkdf_derive(dh_output + root_key, INFO_ROOT_KEY, 64)
    new_root_key = derived[:32]
    new_chain_key = derived[32:]
    return new_root_key, new_chain_key


def kdf_ck(chain_key: bytes) -> Tuple[bytes, bytes]:
    """Chain key KDF: derive next chain key and message key."""
    new_chain_key = hmac_derive(chain_key, CHAIN_KEY_CONSTANT)
    message_key = hmac_derive(chain_key, MESSAGE_KEY_CONSTANT)
    return new_chain_key, message_key


def encrypt_message(message_key: bytes, plaintext: bytes, associated_data: bytes = b"") -> bytes:
    """Encrypt a message using AES-256-GCM."""
    nonce = os.urandom(12)
    aesgcm = AESGCM(message_key)
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
    return nonce + ciphertext


def decrypt_message(message_key: bytes, data: bytes, associated_data: bytes = b"") -> bytes:
    """Decrypt a message using AES-256-GCM."""
    nonce = data[:12]
    ciphertext = data[12:]
    aesgcm = AESGCM(message_key)
    return aesgcm.decrypt(nonce, ciphertext, associated_data)


@dataclass
class MessageHeader:
    """Header included with each encrypted message."""
    dh_public_key: bytes
    previous_chain_length: int
    message_number: int

    def serialize(self) -> bytes:
        return (
            self.dh_public_key
            + struct.pack(">II", self.previous_chain_length, self.message_number)
        )

    @classmethod
    def deserialize(cls, data: bytes) -> "MessageHeader":
        dh_public_key = data[:32]
        prev_chain_len, msg_num = struct.unpack(">II", data[32:40])
        return cls(dh_public_key, prev_chain_len, msg_num)


@dataclass
class DoubleRatchetState:
    """State for one side of the Double Ratchet."""
    dh_self_private: Optional[X25519PrivateKey] = None
    dh_self_public: bytes = b""
    dh_remote_public: bytes = b""
    root_key: bytes = b""
    sending_chain_key: Optional[bytes] = None
    receiving_chain_key: Optional[bytes] = None
    send_count: int = 0
    recv_count: int = 0
    previous_send_count: int = 0
    skipped_keys: Dict[Tuple[bytes, int], bytes] = field(default_factory=dict)
    max_skip: int = 100


def initialize_alice(shared_secret: bytes, bob_dh_public: bytes) -> DoubleRatchetState:
    """Initialize the ratchet for Alice (initiator)."""
    state = DoubleRatchetState()
    state.dh_remote_public = bob_dh_public

    state.dh_self_private, state.dh_self_public = generate_x25519_keypair()

    dh_output = dh(state.dh_self_private, bob_dh_public)
    state.root_key, state.sending_chain_key = kdf_rk(shared_secret, dh_output)
    state.receiving_chain_key = None
    state.send_count = 0
    state.recv_count = 0
    state.previous_send_count = 0

    return state


def initialize_bob(shared_secret: bytes, bob_dh_keypair: Tuple[X25519PrivateKey, bytes]) -> DoubleRatchetState:
    """Initialize the ratchet for Bob (responder)."""
    state = DoubleRatchetState()
    state.dh_self_private = bob_dh_keypair[0]
    state.dh_self_public = bob_dh_keypair[1]
    state.root_key = shared_secret
    state.sending_chain_key = None
    state.receiving_chain_key = None
    state.send_count = 0
    state.recv_count = 0
    state.previous_send_count = 0

    return state


def ratchet_encrypt(state: DoubleRatchetState, plaintext: bytes) -> Tuple[MessageHeader, bytes]:
    """Encrypt a message using the Double Ratchet."""
    state.sending_chain_key, message_key = kdf_ck(state.sending_chain_key)

    header = MessageHeader(
        dh_public_key=state.dh_self_public,
        previous_chain_length=state.previous_send_count,
        message_number=state.send_count,
    )

    ciphertext = encrypt_message(message_key, plaintext, header.serialize())
    state.send_count += 1

    return header, ciphertext


def dh_ratchet_step(state: DoubleRatchetState, header: MessageHeader):
    """Perform a DH ratchet step when receiving a new public key."""
    state.previous_send_count = state.send_count
    state.send_count = 0
    state.recv_count = 0
    state.dh_remote_public = header.dh_public_key

    dh_recv = dh(state.dh_self_private, state.dh_remote_public)
    state.root_key, state.receiving_chain_key = kdf_rk(state.root_key, dh_recv)

    state.dh_self_private, state.dh_self_public = generate_x25519_keypair()

    dh_send = dh(state.dh_self_private, state.dh_remote_public)
    state.root_key, state.sending_chain_key = kdf_rk(state.root_key, dh_send)


def skip_message_keys(state: DoubleRatchetState, until: int):
    """Skip and store message keys for out-of-order messages."""
    if state.receiving_chain_key is None:
        return
    while state.recv_count < until:
        state.receiving_chain_key, mk = kdf_ck(state.receiving_chain_key)
        state.skipped_keys[(state.dh_remote_public, state.recv_count)] = mk
        state.recv_count += 1
        if len(state.skipped_keys) > state.max_skip:
            oldest = next(iter(state.skipped_keys))
            del state.skipped_keys[oldest]


def ratchet_decrypt(state: DoubleRatchetState, header: MessageHeader, ciphertext: bytes) -> bytes:
    """Decrypt a message using the Double Ratchet."""
    # Check skipped keys
    skip_key = (header.dh_public_key, header.message_number)
    if skip_key in state.skipped_keys:
        mk = state.skipped_keys.pop(skip_key)
        return decrypt_message(mk, ciphertext, header.serialize())

    # DH ratchet step if new public key
    if header.dh_public_key != state.dh_remote_public:
        if state.receiving_chain_key is not None:
            skip_message_keys(state, header.previous_chain_length)
        dh_ratchet_step(state, header)

    skip_message_keys(state, header.message_number)
    state.receiving_chain_key, message_key = kdf_ck(state.receiving_chain_key)
    state.recv_count += 1

    return decrypt_message(message_key, ciphertext, header.serialize())


def demo_conversation():
    """Demonstrate a complete E2EE conversation."""
    print("=== End-to-End Encryption Demo ===\n")

    # Simulate X3DH: both parties derive the same shared secret
    alice_ik_private, alice_ik_public = generate_x25519_keypair()
    bob_ik_private, bob_ik_public = generate_x25519_keypair()
    bob_spk_private, bob_spk_public = generate_x25519_keypair()
    alice_ek_private, alice_ek_public = generate_x25519_keypair()

    # Alice computes shared secret
    dh1 = dh(alice_ik_private, bob_spk_public)
    dh2 = dh(alice_ek_private, bob_ik_public)
    dh3 = dh(alice_ek_private, bob_spk_public)
    alice_shared = hkdf_derive(dh1 + dh2 + dh3, b"X3DH", 32)

    # Bob computes same shared secret
    bob_ik_pub_obj = X25519PublicKey.from_public_bytes(alice_ik_public)
    bob_ek_pub_obj = X25519PublicKey.from_public_bytes(alice_ek_public)
    dh1_b = bob_spk_private.exchange(bob_ik_pub_obj)
    dh2_b = bob_ik_private.exchange(bob_ek_pub_obj)
    dh3_b = bob_spk_private.exchange(bob_ek_pub_obj)
    bob_shared = hkdf_derive(dh1_b + dh2_b + dh3_b, b"X3DH", 32)

    assert alice_shared == bob_shared, "X3DH shared secret mismatch!"
    print("[OK] X3DH key agreement: shared secrets match\n")

    # Initialize Double Ratchet
    bob_dh_private, bob_dh_public = generate_x25519_keypair()
    alice_state = initialize_alice(alice_shared, bob_dh_public)
    bob_state = initialize_bob(bob_shared, (bob_dh_private, bob_dh_public))

    # Alice sends messages to Bob
    messages = [
        b"Hello Bob! This is an encrypted message.",
        b"Can you read this?",
        b"This uses the Double Ratchet algorithm.",
    ]

    print("--- Alice sends to Bob ---")
    for msg in messages:
        header, ct = ratchet_encrypt(alice_state, msg)
        pt = ratchet_decrypt(bob_state, header, ct)
        print(f"  Alice -> Bob: {pt.decode()}")
        assert pt == msg

    # Bob replies to Alice
    replies = [
        b"Hi Alice! Yes, I can read your messages.",
        b"The DH ratchet just advanced!",
    ]

    print("\n--- Bob sends to Alice ---")
    for msg in replies:
        header, ct = ratchet_encrypt(bob_state, msg)
        pt = ratchet_decrypt(alice_state, header, ct)
        print(f"  Bob -> Alice: {pt.decode()}")
        assert pt == msg

    # Alice sends again (another DH ratchet)
    print("\n--- Alice sends again (new DH ratchet) ---")
    msg = b"This message uses a new DH ratchet step."
    header, ct = ratchet_encrypt(alice_state, msg)
    pt = ratchet_decrypt(bob_state, header, ct)
    print(f"  Alice -> Bob: {pt.decode()}")
    assert pt == msg

    print("\n[OK] All messages encrypted and decrypted successfully")
    print("[OK] Forward secrecy: each message uses a unique key")
    print("[OK] DH ratchet advanced on direction change")


def benchmark(num_messages: int = 1000):
    """Benchmark encryption/decryption throughput."""
    alice_ik_private, _ = generate_x25519_keypair()
    bob_spk_private, bob_spk_public = generate_x25519_keypair()
    shared = dh(alice_ik_private, bob_spk_public)
    shared_key = hkdf_derive(shared, b"benchmark", 32)

    bob_dh_private, bob_dh_public = generate_x25519_keypair()
    alice_state = initialize_alice(shared_key, bob_dh_public)
    bob_state = initialize_bob(shared_key, (bob_dh_private, bob_dh_public))

    message = b"Benchmark message for throughput testing. " * 10

    start = time.time()
    for _ in range(num_messages):
        header, ct = ratchet_encrypt(alice_state, message)
        pt = ratchet_decrypt(bob_state, header, ct)
    elapsed = time.time() - start

    print(f"Messages: {num_messages}")
    print(f"Time: {elapsed:.3f}s")
    print(f"Throughput: {num_messages / elapsed:.0f} msg/s")
    print(f"Latency: {elapsed / num_messages * 1000:.2f} ms/msg")


def main():
    parser = argparse.ArgumentParser(description="E2EE Messaging Demo")
    subparsers = parser.add_subparsers(dest="command")

    subparsers.add_parser("demo", help="Run E2EE conversation demo")

    bench = subparsers.add_parser("benchmark", help="Benchmark throughput")
    bench.add_argument("--messages", type=int, default=1000, help="Number of messages")

    args = parser.parse_args()

    if args.command == "demo":
        demo_conversation()
    elif args.command == "benchmark":
        benchmark(args.messages)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 1.4 KB
Keep exploring