This document explains the complete end‑to‑end flow of the CryptShare E2E system, including authentication, key exchange, message/file encryption, replay protection, attack defenses (MITM), and security logging. It is written for demo, review, and educational purposes.
- Client: React app with Web Crypto API for all cryptography
- Transport: REST (Express.js) + WebSocket (Socket.IO)
- Server: Zero‑knowledge relay and encrypted storage (MongoDB)
- Keys: Long‑term identity keys; long‑term ECDH (Long-Term (Static) ECDH: The same public-private key pair is used across multiple sessions, often tied to a digital certificate. The key advantage is that the key exchange can be performed "offline" without the recipient needing to be actively involved at the moment of key derivation.)(conversation); ephemeral ECDH (session) (Ephemeral ECDH (ECDHE): A new, temporary key pair is generated for every single session and discarded after the session ends. This provides a critical security feature called forward secrecy. )
- Encryption: AES‑256‑GCM (AES-256-GCM is a symmetric encryption standard that combines the Advanced Encryption Standard (AES) with a 256-bit key and the Galois/Counter Mode (GCM) for authenticated encryption. It encrypts data for confidentiality while using GCM to provide both encryption and authentication in a single step, which ensures the data is both confidential and has not been tampered with. This is achieved by generating an authentication tag that the receiver can use to verify the data's integrity. How it works AES-256: The encryption algorithm is AES, a widely used standard, with a 256-bit key size. A 256-bit key is 32 bytes long. GCM: This is the mode of operation for AES that provides authenticated encryption. It combines encryption with authentication, ensuring the data is both secret and has not been altered. Authentication Tag: GCM generates a unique authentication tag for each message. The receiver uses the same key and initialization vector to decrypt the message and recalculate the tag. Integrity Verification: If the calculated tag matches the tag sent with the message, the receiver can be certain that the data came from the sender and was not changed during transmission. Key and Initialization Vector: Encryption requires a secret key, which is shared by both sender and receiver, and an initialization vector (IV) or nonce, which should be unique for each message and is also shared) for messages and files
-
Derivation: HKDF‑SHA256 for keys from ECDH shared secrets (HKDF-SHA256 is an HMAC-based Key Derivation Function that uses the SHA-256 hash algorithm to derive cryptographic keys from a potentially low-entropy or long input key. It is specified in RFC 5869 and is commonly used to convert shared secrets into keys for applications like encryption, and it is being specified for use in protocols like CMS and COSE. How it works HKDF-SHA256 is a two-stage process: an "extract" phase and an "expand" phase. Extract: Takes an initial key material (like a shared secret) and a salt to produce a fixed-length, pseudorandom key. The salt is a non-secret value that is used to improve security. Expand: Takes the pseudorandom key from the first stage and expands it into one or more new keys of the desired length. Why it is used Key derivation: It allows a strong, fixed-length key to be derived from a potentially weak or short input, such as a password or a pre-shared key. Key expansion: It can generate multiple, independent keys from a single secret. Security: Using a secure hash function like SHA-256 makes the derived keys resistant to various cryptographic attacks. )
-
Protection: Nonce (A "nonce" is an arbitrary number that is used only once in a cryptographic communication to ensure security.) + Timestamp + Sequence (triple‑layer replay defense)
-
Auditing: Security logs for key exchange, messages, files, and attack detection
-
Each user has:
-
ECDSA P‑256 identity keypair (sign/verify) (ECDSA, or the Elliptic Curve Digital Signature Algorithm, is a cryptographic algorithm used to create and verify digital signatures. It's a variant of the Digital Signature Algorithm (DSA) that uses elliptic curve cryptography, allowing for stronger security with smaller key sizes compared to older methods like RSA)
-
ECDH P‑256 keypair (long‑term key exchange)
-
-
Private keys: Stored only in the browser (IndexedDB). Never sent to server.
-
Public keys: Stored in MongoDB; fetched by peers to derive conversation/session keys.
- AES‑256‑GCM: Authenticated encryption (confidentiality + integrity via 128‑bit tag)
- ECDH P‑256: Key agreement to derive shared secret
- ECDSA P‑256: Digital signatures for KEX authentication ( It refers to the process of securely establishing a shared secret key between two parties over an insecure communication channel, which is then used to encrypt and authenticate the subsequent communication session. )
- HKDF‑SHA256: Derive AES keys from ECDH shared secret using salt+info
- CSPRNG: Web Crypto API for nonces, IVs, random ephemeral keys
1) Elliptic Curve (P‑256) (Elliptic curve cryptography (ECC) is a public-key encryption method based on the algebraic structure of elliptic curves, providing high security with smaller key sizes compared to traditional methods like RSA. )
- An elliptic curve over a finite field defines a cyclic group with a generator point
G. - Private key: random scalar
d; Public key:Q = d · G. - Security: ~128‑bit strength for P‑256.
2) ECDH (Elliptic Curve Diffie–Hellman) (How ECDH works Agreement on parameters: Both parties agree on an elliptic curve and a base point ((G)) on that curve.Private key generation: Each party generates a random private key, which is a large integer.Alice chooses a private key (a).Bob chooses a private key (b).Public key generation: Each party calculates their public key by multiplying their private key with the base point (G).Alice computes her public key (A=aG).Bob computes his public key (B=bG).Public key exchange: Alice and Bob exchange their public keys ((A) and (B)) over the insecure channel.Shared secret calculation: Each party uses their own private key and the other party's public key to compute the shared secret.Alice computes the shared secret as (S=aB).Bob computes the shared secret as (S=bA).Result: Due to the properties of elliptic curve multiplication, both parties arrive at the same shared secret ((aB=bA)). This secret can then be used for symmetric encryption. )
- Alice private
a, publicA = a·G; Bob privateb, publicB = b·G. - Shared secret:
S = a·B = b·A = (a·b)·G. - Both parties derive the same
Swithout revealingaorb.
Key Generation: A user generates a private key (a random number) and a corresponding public key derived from the private key and a "generator point" on an elliptic curve. Signature Generation: To sign a message, a user uses their private key and the message's hash to generate a unique digital signature (a pair of integers, {r, s}). Signature Verification: Anyone can use the sender's public key to verify that the signature is valid for a specific message, confirming the sender's identity and that the message hasn't been tampered with. )
- Sign data with private key; verify with public key.
- Used to authenticate KEX messages so attackers cannot substitute keys.
- Symmetric encryption with 256‑bit key, 96‑bit IV, 128‑bit auth tag.
- Provides both confidentiality and integrity.
5) Initialization Vector (IV) (An Initialization Vector (IV) is a random or pseudorandom, non-secret input for a cryptography algorithm that ensures identical plaintexts encrypt to different ciphertexts.)
- Random 96‑bit value per encryption operation.
- MUST be unique per key; guarantees GCM security and non‑determinism.
6) HKDF (Key Derivation) (HKDF is the HMAC-based Key Derivation Function, a standard cryptographic tool that creates new, secure keys from a single, master key)
- Inputs: shared secret, salt, info.
- Output: AES‑256‑GCM key (and optionally more keys) deterministically.
- Large files are split into fixed‑size chunks (e.g., 64KB).
- Each chunk encrypted independently with fresh IV and same session/conversation key.
- Pros: Lower memory use, resumable processing, streaming‑friendly.
Chunk size: trade‑off between overhead (many IVs/tags) and memory footprint.
-
Conversation Key (long‑term):
- Derived via ECDH using my long‑term private key and peer’s long‑term public key.
- HKDF with deterministic salt/info → stable across sessions for the same user pair.
- Used to decrypt historical messages/files across logins.
-
Session Key (ephemeral):
- Derived via ECDH using fresh ephemeral keypairs during KEX.
- HKDF with session salt/info → unique per session.
- Provides forward secrecy: compromising identity keys later does not reveal past sessions.
Three messages: KEX_INIT, KEX_RESPONSE, KEX_CONFIRM.
- Initiation (KEX_INIT)
- Initiator generates ephemeral ECDH keypair.
- Signs the message (ephemeral public key + metadata) with ECDSA identity private key.
- Sends to responder via server (server relays, does not verify content).
- Response (KEX_RESPONSE)
- Responder generates ephemeral ECDH keypair.
- Signs response (includes initiator nonce for binding) using ECDSA identity private key.
- Sends to initiator.
- Confirmation (KEX_CONFIRM)
- Both sides compute ECDH shared secret from ephemeral keys.
- Derive session key via HKDF.
- Initiator sends confirmation (optionally an AEAD‑protected token).
Security Properties:
- MITM Protection: ECDSA signatures authenticate keys; altered KEX fails verification.
- Forward Secrecy: New ephemeral keys per session.
- Binding: Nonces/timestamps tie messages in order.
REPLAY ATTACK: A replay attack is a cyberattack where a malicious actor intercepts and retransmits a valid data transmission to trick a system into performing an unauthorized action, such as granting access or duplicating a transaction. The attacker doesn't need to decipher the data, but rather simply reuses the captured information to impersonate a legitimate user, even if the data itself is encrypted.
- User selects peer → ensure keys available / start KEX if needed.
- On send:
- Build plaintext payload.
- Generate IV (random 96‑bit).
- Encrypt with AES‑256‑GCM using session or conversation key.
- Attach replay protection metadata (nonce, timestamp, sequence).
- Emit via Socket.IO:
{ciphertext, iv, nonce, timestamp, sequence}.
- Server:
- Validates replay (best‑effort if metadata present).
- Stores ciphertext + IV + metadata in MongoDB.
- Relays to recipient room.
- Recipient:
- Fetches sender public keys as needed.
- Derives appropriate key.
- Decrypts with AES‑GCM.
- Renders plaintext; falls back to
[Decryption failed]on tag mismatch.
- User selects file.
- Encrypt in chunks:
- For each chunk:
- Read chunk → ArrayBuffer.
- Generate IV.
- Encrypt with AES‑256‑GCM.
- Collect
{index, iv, ciphertext}.
- For each chunk:
- Upload:
- Send metadata + encrypted chunks to server.
- Server stores encrypted blob and metadata (never plaintext).
- Download/Decrypt:
- Client requests file.
- For each chunk:
- Decrypt with stored IV and conversation/session key.
- Reassemble and offer to user (Blob/URL).
- Nonce (Uniqueness): Reject duplicates.
- Timestamp (Freshness): Reject stale messages beyond window (e.g., 5 minutes).
- Sequence (Order): Reject out‑of‑order numbers per conversation.
Client Behavior:
- Generates nonce/timestamp; maintains per‑conversation
lastSequence. - On receive: checks duplicates/staleness/order before accept.
Server Behavior:
- Best‑effort validation; logs any detected replay and blocks relay when configured.
- Maintains temporary cache (nonce set, last sequence per conversation). In prod, use Redis.
- Goal of attacker: replace ephemeral/public keys during KEX to read traffic.
- Defense:
- ECDSA signatures over KEX content prevent undetected modification.
- Verification uses known identity public keys from server.
- Any mismatch → KEX rejected, handshake fails.
Evidence in Demo:
- Attempted MITM → signature verification fails → event logged.
- Logged Events:
- Server start, DB connect
- Key exchange stages (INIT/RESPONSE/CONFIRM)
- Message relay + storage
- File upload/download
- Replay detection (nonce/timestamp/sequence)
- Errors (decryption/authentication failures)
- Log Fields:
timestamp,eventType,severity,userId,details
- Usage:
- Demonstrate detections in terminal/DB screenshots
- Provide audit trail for security review
- Start backend (port 5000) and frontend (port 3000).
- Register/login two users.
- Send messages → observe encrypted payloads on network; plaintext only on client.
- Share a file → download and decrypt on the recipient.
- Run MITM script → observe signature failures and logs.
- Run replay script → observe nonce/timestamp/sequence blocking in logs.
- Capture with Wireshark: filter
tcp.port == 5000 || websocket; payload shows ciphertext.
- IV Reuse: Never reuse IV for AES‑GCM with the same key.
- Key Lifecycle: Rotate session keys; keep conversation keys for history.
- StrictMode/React: Effects may run twice in dev; guard socket listener duplication.
- Race Conditions: When merging histories (files + text), avoid overwriting state.
- Performance: Choose chunk size (e.g., 64KB) appropriate to device and network.
- ECDH: Key agreement using elliptic curves.
- ECDSA: Digital signature algorithm on elliptic curves.
- AES‑GCM: Symmetric AEAD cipher mode with 128‑bit auth tag.
- IV: Initialization Vector (nonce) for ciphers; unique per encryption.
- HKDF: Key derivation function based on HMAC.
- Nonce: Random value to prevent replay; tracked for uniqueness.
- Sequence: Monotonic counter per conversation to enforce order.
- Forward Secrecy: Past sessions remain secure even if long‑term keys are later compromised.
// Encrypt a chat message
const iv = crypto.getRandomValues(new Uint8Array(12));
const { ciphertext } = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
sessionKey,
new TextEncoder().encode(plaintext)
);
socket.emit('message', {
to: peerId,
ciphertext,
iv,
nonce: genNonce(),
timestamp: Date.now(),
sequence: nextSeq(conversationId)
});// Derive conversation key (long‑term ECDH + HKDF)
const myPriv = await importKey(myPrivJwk, { name: 'ECDH', namedCurve: 'P-256' }, ['deriveBits']);
const peerPub = await importKey(peerPubJwk, { name: 'ECDH', namedCurve: 'P-256' }, []);
const bits = await crypto.subtle.deriveBits({ name: 'ECDH', public: peerPub }, myPriv, 256);
const material = await crypto.subtle.importKey('raw', bits, 'HKDF', false, ['deriveKey']);
const convKey = await crypto.subtle.deriveKey(
{ name: 'HKDF', hash: 'SHA-256', salt, info },
material,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);// Chunked file encryption (simplified)
const CHUNK_SIZE = 64 * 1024;
for (let i = 0; i < Math.ceil(file.size / CHUNK_SIZE); i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = await file.slice(start, end).arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, chunk);
uploadChunk({ index: i, iv, data: encrypted });
}