npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

securee2e

v0.4.2

Published

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

Downloads

13

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.