@amirafa/encryption-service
v1.0.6
Published
An Encryption Service library built with Vite
Maintainers
Readme
Encryption Service
A lightweight library to use E2EE
🚀 Installation
From npm
npm install @amirafa/encryption-serviceFrom yarn
yarn add @amirafa/encryption-serviceFrom pnpm
pnpm add @amirafa/encryption-service🧩 Import and Usage
import { EncryptionService } from "@amirafa/encryption-service";
const enc = EncryptionService();
// generate user keys
await enc.generateAndStoreIdentityKeyPair("alice");
await enc.generateAndStoreECDHKeyPair("alice");
// example: encrypt and decrypt a message
const alicePriv = await enc.importPrivateKey(localStorage.getItem("alice-privateKey")!);
const bobPub = await enc.importPublicKey(localStorage.getItem("bob-publicKey")!);
const sharedKey = await enc.deriveSharedKey(alicePriv, bobPub);
const { encryptedMessage, iv } = await enc.encryptMessageAES("Hello Bob!", sharedKey);
const plain = await enc.decryptMessageAES(encryptedMessage, sharedKey, iv);
console.log(plain); // "Hello Bob!"Full Flow & Backend Schema
This document describes how the EncryptionServiceClass implements end-to-end encryption (E2EE) for a secure chat system and how the backend should store and handle related data.
🧩 Architecture Overview
The system uses three cryptographic layers:
- ECDSA (Identity) — for signing and verifying identity keys.
- ECDH (Shared Key) — for deriving a mutual secret key between two users.
- AES-GCM (Message Encryption) — for encrypting and decrypting messages.
All encryption/decryption logic happens in the browser (client-side).
The backend only stores public keys, signatures, and encrypted data — never plaintext or private keys.
🧱 Function Reference (Client-Side)
generateAndStoreIdentityKeyPair(username)
Generates an ECDSA key pair (identity keys) and stores both in localStorage.
await enc.generateAndStoreIdentityKeyPair("alice");Stored keys:
alice-id-publicKey
alice-id-privateKeygenerateAndStoreECDHKeyPair(username)
Generates an ECDH key pair for secure message key exchange.
await enc.generateAndStoreECDHKeyPair("alice");Stored keys:
alice-publicKey
alice-privateKeysignEcdhPublicKey(identityPriv, ecdhPub)
Signs the ECDH public key using the identity private key (ECDSA).
const sig = await enc.signEcdhPublicKey(aliceIdPriv, aliceEcdhPub);verifyEcdhPublicKeySignature(identityPub, ecdhPub, signature)
Verifies that an ECDH public key truly belongs to a given identity.
const ok = await enc.verifyEcdhPublicKeySignature(bobIdPub, bobEcdhPub, sig);deriveSharedKey(privateKey, publicKey)
Derives a shared AES key using your private ECDH key and the other user’s public ECDH key.
const aesKey = await enc.deriveSharedKey(alicePrivEcdh, bobPubEcdh);Both sides independently derive the same AES key.
encryptMessageAES(message, aesKey)
Encrypts a plaintext message using AES-GCM.
const { encryptedMessage, iv } = await enc.encryptMessageAES("Hello Bob", aesKey);iv (Initialization Vector) must be sent along with the ciphertext.
decryptMessageAES(encryptedMessage, aesKey, iv)
Decrypts a message using the same AES key and IV.
const plain = await enc.decryptMessageAES(encryptedMessage, aesKey, iv);getIdentityFingerprint(identityPub)
Generates a SHA-256 fingerprint of an identity key for display and manual verification (TOFU).
const fpr = await enc.getIdentityFingerprint(aliceIdentityPub);
console.log("Alice fingerprint:", fpr);🧭 Full Message Exchange Flow
1️⃣ Key Generation (first time only)
await enc.generateAndStoreIdentityKeyPair("alice");
await enc.generateAndStoreECDHKeyPair("alice");
await enc.generateAndStoreIdentityKeyPair("bob");
await enc.generateAndStoreECDHKeyPair("bob");2️⃣ Alice signs her ECDH key
const idPriv = await enc.importIdentityPrivateKey(localStorage.getItem("alice-id-privateKey")!);
const ecdhPub = await enc.importPublicKey(localStorage.getItem("alice-publicKey")!);
const signature = await enc.signEcdhPublicKey(idPriv, ecdhPub);
const alicePackage = {
identityPublicKey: localStorage.getItem("alice-id-publicKey")!,
ecdhPublicKey: localStorage.getItem("alice-publicKey")!,
signature
};Alice sends this JSON package to Bob via the server.
3️⃣ Bob verifies Alice’s identity
const aliceIdPub = await enc.importIdentityPublicKey(alicePackage.identityPublicKey);
const aliceEcdhPub = await enc.importPublicKey(alicePackage.ecdhPublicKey);
const valid = await enc.verifyEcdhPublicKeySignature(aliceIdPub, aliceEcdhPub, alicePackage.signature);
if (!valid) throw new Error("Fake Alice detected!");4️⃣ Both derive the shared AES key
const bobPriv = await enc.importPrivateKey(localStorage.getItem("bob-privateKey")!);
const sharedKey = await enc.deriveSharedKey(bobPriv, aliceEcdhPub);Both parties now hold the same symmetric AES key.
5️⃣ Alice encrypts and sends the message
const { encryptedMessage, iv } = await enc.encryptMessageAES("Hi Bob!", sharedKey);
const chatEntry = {
sender: "alice",
recipient: "bob",
message: Array.from(new Uint8Array(encryptedMessage)),
iv: Array.from(iv)
};
// send chatEntry to server6️⃣ Bob receives and decrypts
const msgBuf = new Uint8Array(chatEntry.message).buffer;
const ivBuf = new Uint8Array(chatEntry.iv);
const plain = await enc.decryptMessageAES(msgBuf, sharedKey, ivBuf);
console.log("Decrypted:", plain); // "Hi Bob!"🗄 Recommended Database Schema (Backend)
Users Table
{
username: string,
identityPublicKey: string,
ecdhPublicKey: string,
signature: string
}Messages Table
{
sender: string,
recipient: string,
message: number[],
iv: number[]
}💾 SQL Implementation Example
users
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
identity_public_key TEXT NOT NULL,
ecdh_public_key TEXT NOT NULL,
signature TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);messages
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
sender VARCHAR(50) NOT NULL,
recipient VARCHAR(50) NOT NULL,
message BYTEA NOT NULL,
iv BYTEA NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
FOREIGN KEY (sender) REFERENCES users(username),
FOREIGN KEY (recipient) REFERENCES users(username)
);🔄 Example API Flow
Client → Server (Send Message)
{
"sender": "alice",
"recipient": "bob",
"message": "BASE64_ENCRYPTED_DATA",
"iv": "BASE64_IV"
}Server → Client (Retrieve Message)
{
"sender": "alice",
"message": "BASE64_ENCRYPTED_DATA",
"iv": "BASE64_IV"
}Client Decrypts
const msg = await enc.decryptMessageAES(
base64ToArrayBuffer(message),
aesKey,
base64ToUint8Array(iv)
);
console.log(msg); // "Hello Bob!"🔒 Security Notes
- The server stores only public keys and encrypted data.
- Private keys and plaintext never leave the user’s device.
- Messages must be delivered only to their intended recipient.
- The backend is zero-knowledge — even a data leak reveals nothing readable.
✅ Summary
| Component | Responsibility | Location | | --------------------- | --------------------------- | -------- | | ECDSA | Identity and signature | Client | | ECDH | Shared key derivation | Client | | AES-GCM | Message encryption/decryption | Client | | Key & message storage | Persistence only | Backend | | Plaintext visibility | Sender & recipient devices only | — |
🧾 License
MIT © 2025 — Encryption Service by Amirafa
Feel free to use, modify, and distribute under the terms of the MIT license.
Changelog
[Version 1.0.6] - 2025-10-28
- Add
git repository
[Version 1.0.5] - 2025-10-26
- Update
README.md - Add
logo
[Version 1.0.3] - 2025-10-26
- Add
type declaration - Update
README.md
[Version 1.0.2] - 2025-10-26
- Add
README.md
[Version 1.0.1] - 2025-10-26
- Minor
fixes
[Version 1.0.0] - 2025-10-26
- published
@amirafa/encryption-service
