Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

securee2e

danielefrisanco560MIT0.4.2TypeScript support: included

Vue 3 composable for secure End-to-End Encryption (E2E) using Web Crypto API, featuring ECDH and authenticated key exchange with ECDSA.

vue, crypto, e2e, diffie-hellman, aes-gcm, ecdsa

readme

securee2e: Vue Composable for End-to-End Encryption

securee2e is a straightforward Vue 3 composable built on the native Web Cryptography API to facilitate secure Diffie-Hellman Key Exchange (ECDH) and AES-GCM symmetric encryption, now with ECDSA signature for public key authentication.

✨ Features

  • ECDH P-256 Key Agreement: Uses the standard Elliptic Curve Diffie-Hellman with the P-256 curve for robust key agreement.

  • ECDSA P-256 Key Authentication: Uses Elliptic Curve Digital Signature Algorithm with the P-256 curve to sign and verify public keys, preventing Man-in-the-Middle (MITM) attacks.

  • High-Level API Wrappers (v0.3.1): Simplified functions (generateLocalAuthPayload, deriveSecretFromRemotePayload, etc.) abstract the 6-step handshake into two simple calls, dramatically simplifying integration.

  • AES-256 GCM Encryption: Employs the highly secure AES-GCM (256-bit) algorithm for encrypting messages.

  • Security Focused: Private keys are generated as non-extractable by default.

  • Base64 Serialization: Helper functions for easy, network-ready transmission of keys, signatures, IVs, and ciphertext via URL-safe Base64 strings.

📦 Installation and Setup

Since this is intended to be a reusable library, you would typically install it using a package manager:

# Using npm  
npm install securee2e
# Using yarn
yarn add securee2e

Usage in Project

Import and use the composable directly in any Vue component or JavaScript file:

import { useDiffieHellman } from 'securee2e';
// ...

⚙️ Data Structures and Payloads

The library exchanges data using these required structures:

Type Structure Description
KeyAuthPayload { ecdhPublicKey: string, ecdsaPublicKey: string, signature: string } The full payload transmitted during the key exchange handshake.
EncryptedPayload { iv: string, ciphertext: string } The result of encryptData. Both fields are Base64 strings and are required for decryption.
LocalAuthResult { payload: KeyAuthPayload, ecdhPrivateKey: CryptoKey } Return object from the high-level key generation function. Contains the sharable payload and local ephemeral private key.

🚀 High-Level Usage: Simplified E2E Workflow (v0.3.4)

With the introduction of the high-level wrappers, the entire authenticated key exchange is reduced to a few calls. This approach enforces authentication (LTID signing) to prevent Man-in-the-Middle attacks.

import { useDiffieHellman, KeyAuthPayload } from 'securee2e';

const {
  // High-Level functions:
  generateLocalAuthPayload,
  deriveSecretFromRemotePayload,
  encryptMessage,
  decryptMessage
} = useDiffieHellman();


async function runSimplifiedExchange(bobPayload: KeyAuthPayload) {

  // 1. ALICE'S AUTHENTICATED KEY GENERATION (1 call)
  // The LTID key is automatically loaded/generated and used to sign the payload.
  // The LTID key is automatically loaded/generated using the IndexedDBProvider`
  const aliceLocalAuth = await generateLocalAuthPayload(); 

  // Extract the ephemeral private key and the public payload to send
  const aliceEcdhPrivateKey = aliceLocalAuth.keys[0]; // Access key from the returned 'keys' array
  const alicePayload = aliceLocalAuth.payload;

  // 3. BOB'S PAYLOAD IS RECEIVED
  // (Assuming bobPayload is a valid KeyAuthPayload received from the network)

  // 4. DERIVE SHARED SECRET (1 call: imports, verifies, and derives)
  // This function uses the LTID public key inside 'bobPayload' to verify the signature.
  const aliceSharedSecret = await deriveSecretFromRemotePayload(
      aliceEcdhPrivateKey,
      bobPayload
  );

  // NOTE: If the signature verification fails, this function throws an error 
  // and the handshake is aborted, protecting against MITM attacks.

  // 5. ENCRYPT & DECRYPT
  const plaintext = "This is the simplified secure message.";
  const encryptedPayload = await encryptMessage(aliceSharedSecret, plaintext);

  // Simulate Bob decrypting using his identical shared secret
  // (Assuming Bob has his identical sharedSecret derived from Alice's payload)
  const decryptedMessage = await decryptMessage(aliceSharedSecret, encryptedPayload);

  console.log("Decrypted Message:", decryptedMessage); 
}

💾 Persistence and Key Management

Your Long-Term Identity (LTID) keys are now persistently stored using IndexedDB by default, meaning they survive page refreshes and browser restarts.

The library achieves this using the Provider Pattern based on the IKeyStorageProvider interface, allowing you to swap out storage mechanisms easily.

Default Provider Persistence Notes
IndexedDBProvider (NEW DEFAULT) Persistent Uses the asynchronous IndexedDB API for highly secure, robust persistence of LTID keys.
LocalStorageProvider (Option) Persistent Saves LTID keys to window.localStorage. Available as an alternative.
InMemoryStorageProvider (Option) Transient Keys are lost when the page is closed/refreshed.

Swapping Storage Providers

While the default is the IndexedDBProvider, you can inject any custom storage solution that implements IKeyStorageProvider.

To switch providers, import setCurrentStorageProvider and your chosen provider class before calling useDiffieHellman().

import { setCurrentStorageProvider, InMemoryStorageProvider, IKeyStorageProvider } from 'securee2e';

// Example: Switch to non-persistent, in-memory storage
setCurrentStorageProvider(new InMemoryStorageProvider());

// Example: If you wrote a custom provider
// class IndexedDBProvider implements IKeyStorageProvider { ... }
// setCurrentStorageProvider(new IndexedDBProvider());

// Now, useDiffieHellman() will use the new provider instance
const { generateLocalAuthPayload } = useDiffieHellman();

📖 Low-Level Usage: The Authenticated E2E Workflow (6 Steps)

The E2E process now requires key generation for both encryption (ECDH) and authentication (ECDSA) and involves six sequential steps:

  1. Generate Keys: Both parties generate their own public/private ECDH key pair (for encryption) and ECDSA key pair (for authentication).

  2. Sign Public Key: Each party uses their ECDSA private key to sign their ECDH public key.

  3. Exchange Payloads: Parties send a complete payload containing their ECDH public key, ECDSA public key, and the Signature to each other.

  4. Verify Signature: The recipient uses the remote party's ECDSA public key to verify the signature on the ECDH public key. If validation fails, the exchange is aborted (MITM protection).

  5. Derive Secret: If verified, each party combines their ECDH private key with the remote party's ECDH public key to derive an identical, shared symmetric secret (AES-GCM Key).

  6. Encrypt/Decrypt: Use the shared secret to encrypt and decrypt messages.

Example: Alice Sends a Secure Message to Bob (Authenticated)

This example demonstrates the full, secure workflow including key signing and verification.


import { useDiffieHellman, KeyAuthPayload } from 'securee2e';

const {
generateKeyPair, 
generateLongTermIdentityKeys, // Added for consistency 
exportPublicKeyBase64,
exportSigningPublicKeyBase64,
importRemotePublicKeyBase64,
importRemoteSigningPublicKeyBase64,
signPublicKey,
verifySignature,
deriveSharedSecret,
encryptData,
decryptData
} = useDiffieHellman();

// KeyAuthPayload definition (as an interface for clarity)
// NOTE: This interface is already included via 'import { KeyAuthPayload } from 'securee2e''
/*
interface KeyAuthPayload {
    ecdhPublicKey: string; // Alice's ECDH key
    ecdsaPublicKey: string; // Alice's ECDSA key (LTID Public Key)
    signature: string; // Signature over the ECDH key
}
*/

async function runAuthenticatedExchange(bobPayload: KeyAuthPayload) {
  // --- 1. LOAD/GENERATE LTID KEYS & EPHEMERAL ECDH KEYS ---
  // Alice loads her persistent identity (signing) keys
  const aliceLtidKeys = await generateLongTermIdentityKeys(); // Now uses IndexedDBProvider internally

  // Alice generates her session (encryption) keys
  const aliceEcdhKeys = await generateKeyPair();

  // --- 2. SIGN PUBLIC KEY & 3. PREPARE PAYLOAD ---
  const ecdhPubKeyBase64 = await exportPublicKeyBase64(aliceEcdhKeys.publicKey);

  // Alice signs her *ephemeral* ECDH public key using her *LTID* private key
  const signature = await signPublicKey(
      aliceLtidKeys.ecdsaPrivateKey, // Use LTID Private Key for signing
      aliceEcdhKeys.publicKey
  );

  const alicePayload: KeyAuthPayload = {
      ecdhPublicKey: ecdhPubKeyBase64,
      ecdsaPublicKey: await exportSigningPublicKeyBase64(aliceLtidKeys.ecdsaPublicKey), // Use LTID Public Key
      signature: signature
  };

  // --- 4. BOB RECEIVES & ALICE VERIFIES BOB'S KEY (SIMULATED) ---
  const bobEcdhKey = await importRemotePublicKeyBase64(bobPayload.ecdhPublicKey);
  // Import the remote party's LTID public key
  const bobEcdsaKey = await importRemoteSigningPublicKeyBase64(bobPayload.ecdsaPublicKey); 

  const isSignatureValid = await verifySignature(
      bobEcdsaKey, // Use Bob's LTID Public Key for verification
      bobEcdhKey,
      bobPayload.signature
  );

  if (!isSignatureValid) {
      throw new Error("MITM ALERT: Remote key signature is invalid.");
  }
  console.log("Key Verified Successfully. Connection is authenticated.");

  // --- 5. DERIVE SHARED SECRET ---
  const aliceSharedKey = await deriveSharedSecret(
      aliceEcdhKeys.privateKey, 
      bobEcdhKey 
  );

  // --- 6. ENCRYPT & DECRYPT (ALICE SENDS) ---
  const plaintext = "This message is secretly authenticated.";
  const encryptedPayload = await encryptData(aliceSharedKey, plaintext);
  const { iv, ciphertext } = encryptedPayload; // Both are URL-safe Base64 strings

  // Simulate Bob decrypting using his identical shared secret
  const decryptedMessage = await decryptData(aliceSharedKey, iv, ciphertext);

  console.log("Decrypted Message:", decryptedMessage); 
}

⚠️ Security Notes

  1. Authentication is Crucial: This library now includes ECDSA signature and verification to prevent Man-in-the-Middle (MITM) attacks. Always verify the remote party's key using verifySignature before deriving the shared secret.

  2. Non-Extractable Private Keys: The generateKeyPair and generateSigningKeys functions set the private keys as non-extractable. This is a security best practice, preventing accidental exposure of the key material through functions like exportKey.

  3. Initialization Vector (IV) is Mandatory: For AES-GCM encryption, a unique 12-byte IV is generated for every single message. This IV is not secret and must be transmitted along with the ciphertext. Reusing the same IV will fatally compromise security, typically as part of the EncryptedPayload object. Reusing the same IV will fatally compromise security.

changelog

Changelog

[0.4.2] (Persistent Storage Migration)

  • Feature: Implemented the persistent, asynchronous IndexedDBProvider to store Long-Term Identity (LTID) key sets securely.

  • Update: Set IndexedDBProvider as the default storage mechanism, ensuring LTID keys persist across browser sessions and full page refreshes.

  • Refactor: Decoupled storage logic from the main hook by introducing the IKeyStorageProvider interface and creating modular files, stabilizing key retrieval logic for all storage types.

[0.4.1] (Dependency Update)

  • Update: Defined Vue.js as a peerDependency in the library's package configuration, aligning with best practices for Vue composables.

  • Refactor: Centralized key generation and persistence logic within useDiffieHellman.ts for clearer control flow and better preparation for async operations.

[0.4.0] - 2025-10-10

Added

  • Key Persistence (Default): The Long-Term Identity (LTID) keys are now persistent by default, surviving page refreshes and browser restarts.

  • LocalStorageProvider: Introduced LocalStorageProvider which saves LTID keys to window.localStorage. This is now the default storage provider.

  • Swappable Storage Providers: Implemented the setCurrentStorageProvider(provider) function and the IKeyStorageProvider interface, allowing users to easily swap the default storage mechanism (e.g., switching back to in-memory, or implementing custom database storage).

[0.3.4] - 2025-10-10

Changed

  • Simplified High-Level Identity Setup: The generateLocalAuthPayload() function is now truly zero-argument. It was refactored to internally call generateLongTermIdentityKeys(), automatically handling the loading, generation, and persistence of the Long-Term Identity (LTID) keys before generating and signing the ephemeral payload. This removes the manual dependency on passing LTID keys, further simplifying the authenticated handshake for the end user.

[0.3.2] - 2025-10-10

📦 Maintenance & Packaging

  • Licensing: Added the permissive MIT License to the package to ensure easy adoption by other projects.

  • Package Optimization: Implemented the "files" exclusion list in package.json to drastically reduce the size of the published package by excluding all development artifacts (tests, examples, configuration).

  • Module Compatibility: Added the "exports" mapping to package.json for superior CJS/ESM dual-package support, ensuring seamless module resolution across different build systems.

[0.3.1] - 2025-10-09

Added

  • High-Level API Wrappers (Unified Workflow): Introduced a simplified API layer to manage the entire authenticated key exchange and messaging process, wrapping the low-level crypto functions.
    • generateLocalAuthPayload(): Generates all local ECDH/ECDSA keys and the authenticated public payload for sharing.
    • deriveSecretFromRemotePayload(): Handles importing remote keys, performing the essential MITM signature verification check, and deriving the shared secret.
    • encryptMessage(): High-level function to securely encrypt a plaintext message using the shared secret.
    • decryptMessage(): High-level function to decrypt a received encrypted payload.

[0.3.0] - 2025-10-09

Added

  • Authenticated Key Exchange (MITM Protection): Implemented Elliptic Curve Digital Signature Algorithm (ECDSA) for signing and verifying the ECDH public key.
  • New Functions:
    • generateSigningKeys(): Generates ECDSA key pair for authentication.
    • signPublicKey(): Creates an ECDSA signature over the ECDH public key.
    • verifySignature(): Validates the remote party's signature to prevent MITM attacks.
  • CHANGELOG.md: Added a change log file to track release history.

Changed

  • Updated Key Exchange Workflow: The standard key exchange process is now a 6-step workflow that requires key signature and verification prior to shared secret derivation.
  • Documentation: Updated README.md to reflect the authenticated workflow, new functions, and defined payload structures (KeyAuthPayload, EncryptedPayload).

Security

  • Mitigation of MITM Attacks: Public keys are now cryptographically authenticated using ECDSA (P-256), protecting against malicious intermediate parties from swapping public keys.

0.2.0 (2025-10-09)

Added

  • Trusted Publisher Workflow: Implemented GitHub Actions workflow (.github/workflows/publish.yml) to securely and automatically publish new releases to NPM using OIDC and GitHub's Trusted Publisher feature.

Changed

  • Key Derivation Robustness: Refactored deriveSharedSecret to use the reliable two-step process (deriveBits followed by importKey), resolving runtime issues in various browser environments.
  • Testing: Updated unit tests to accurately mock and assert the two-step key derivation and ECDH key properties (extractable: false).

0.1.0 (Initial Release)

Added

  • Core Functionality: Initial implementation of the useDiffieHellman Vue 3 composable.
  • ECDH P-256 Key Agreement: Functions for generating private/public key pairs (generateKeyPair).
  • AES-256 GCM Encryption: Core methods for symmetric encryption and decryption (encryptData, decryptData).
  • Serialization Utilities: Helper functions for converting keys between CryptoKey objects and network-friendly Base64 strings.