JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 50
  • Score
    100M100P100Q65970F
  • License LGPL-2.1

RSA message encryption, signing, decryption, and verification using webcrypto or node crypto

Package Exports

  • @mdaemon/rsa-message
  • @mdaemon/rsa-message/dist/rsa-message.mjs
  • @mdaemon/rsa-message/dist/rsa-message.umd.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@mdaemon/rsa-message) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@mdaemon/rsa-message - RSA message encryption, signing, decryption, verification, and ECDH key exchange using webcrypto or node crypto

Dynamic JSON Badge Static Badge install size Dynamic JSON Badge Node.js CI

[ @mdaemon/rsa-message on npm ]

Note

This library uses the browser's Web Crypto API for key generation and encryption. This means that it is not compatible with older browsers that do not support the Web Crypto API.

Keys are not persistent between browser sessions. This means that if you close your browser and reopen it, you will need to re-initialize the RSAMessage instance.

This library is ideal for temporary use cases where you don't need to persist keys between sessions. If you need to persist keys between sessions, you should use a library like localForage to store them in the browser's local storage.

Install

$ npm install @mdaemon/rsa-message --save

Usage

Node CommonJS

const RSAMessage = require("@mdaemon/emitter/dist/rsa-message.cjs");

Node Modules

import RSAMessage from "@mdaemon/emitter/dist/rsa-message.mjs";

Web

<script type="text/javascript" src="/path_to_modules/dist/rsa-message.umd.js">

Usage

// Initialize RSA for a user
const sender = new RSAMessage();
const { publicKey, verifyKey } = await sender.init();

const recipient = new RSAMessage();
const { publicKey: theirPublicKey, verifyKey: theirVerifyKey } = await recipient.init();
// Store public key from another user to use for encryption and verification
sender.setPublicKey('otherUserId', theirPublicKey, theirVerifyKey);
recipient.setPublicKey('senderId', publicKey, verifyKey);

// Encrypt a message
const encrypted = await rsa.encryptMessage('Hello, World!', 'otherUserId');

// Encode a message for transport
const encoded = rsa.exportEncryptedMessage(encrypted);

// Decode a message from transport
const decoded = rsa.importEncryptedMessage(encoded);

// Decrypt a message
const decrypted = await rsa.decryptMessage(decoded, 'senderId');

Derived Key Encryption (ECDH + AES-GCM)

For scenarios requiring perfect forward secrecy and efficient symmetric encryption, you can use ECDH key exchange:

// Initialize RSA instances for both users
const alice = new RSAMessage();
const bob = new RSAMessage();

await alice.init();
await bob.init();

// Generate ECDH key pairs for both users
const aliceECDHPublicKey = await alice.generateECDHKeyPair();
const bobECDHPublicKey = await bob.generateECDHKeyPair();

// Exchange ECDH public keys
alice.setECDHPublicKey('bob', bobECDHPublicKey);
bob.setECDHPublicKey('alice', aliceECDHPublicKey);

// Derive shared keys
await alice.deriveSharedKey('bob');
await bob.deriveSharedKey('alice');

// Encrypt and decrypt messages using shared keys
const encrypted = await alice.encryptWithSharedKey('Hello, Bob!', 'bob');
const decrypted = await bob.decryptWithSharedKey(encrypted, 'alice');

// Export/import shared key data for persistence
const sharedKeyData = alice.exportSharedKeyData('bob');
// Later, import the shared key data
alice.importSharedKeyData('bob', sharedKeyData);

Master AES Key Encryption (Group/Room Scenarios)

For scenarios where multiple users need to share the same encryption key (like group chats or rooms), you can use the master AES key functionality:

// Initialize RSA instances for group members
const admin = new RSAMessage();
const member1 = new RSAMessage();
const member2 = new RSAMessage();

await admin.init();
await member1.init();
await member2.init();

// Set up public keys for encryption/verification
admin.setPublicKey('self', admin.publickey, admin.verifykey);
admin.setPublicKey('member1', member1.publickey, member1.verifykey);
admin.setPublicKey('member2', member2.publickey, member2.verifykey);

// Admin generates a master AES key for the group
const masterKey = await admin.generateAndSetMasterAESKey();

// Admin shares the master key with group members
const keyForMember1 = await admin.exportMasterAESKeyForUser('member1');
const keyForMember2 = await admin.exportMasterAESKeyForUser('member2');

// Members receive and set up the master key
member1.setPublicKey('admin', admin.publickey, admin.verifykey);
member1.setVerifyKey('admin', admin.verifykey);
await member1.setMasterAESKeyFromEncrypted(keyForMember1, 'admin');

member2.setPublicKey('admin', admin.publickey, admin.verifykey);
member2.setVerifyKey('admin', admin.verifykey);
// Alternative: using setEncryptedMasterAESKey with encryptor parameter
member2.setEncryptedMasterAESKey(keyForMember2, 'admin');

// Now all members can encrypt/decrypt using the shared master key
const message = 'Hello, group!';

// Using dedicated master key methods (no RSA overhead)
const encrypted = await admin.encryptWithMasterAESKey(message);
const decrypted1 = await member1.decryptWithMasterAESKey(encrypted, 'admin');
const decrypted2 = await member2.decryptWithMasterAESKey(encrypted, 'admin');

// Or using regular methods with master key flag (backwards compatible)
const encrypted2 = await member1.encryptMessage(message, 'member2', true);
const decrypted3 = await member2.decryptMessage(encrypted2, 'member1', true);

API Reference

new RSAMessage()

Creates a new instance of the RSAMessage class.

init(publicKey?: string, privateKey?: string): Promise<string>

Initializes the keys for the user. Can either generate new keys or use existing keys.

  • publicKey: Optional base64 encoded public key
  • privateKey: Optional base64 encoded private key
  • Returns: Base64 encoded public key

publickey: string

Getter that returns the base64 encoded public key.

privatekey: string

Getter that returns the base64 encoded private key.

verifykey: string

Getter that returns the base64 encoded verification key used for signature verification.

signkey: string

Getter that returns the base64 encoded signing key used for creating message signatures.

signMessage(message: string): Promise<ArrayBuffer>

Signs a message using the private signing key.

  • message: The message to sign
  • Returns: Signature as ArrayBuffer

verifySignature(signature: ArrayBuffer, message: string, userId: string): Promise<boolean>

Verifies a message signature using the sender's public verification key.

  • signature: The signature to verify as ArrayBuffer
  • message: The original message that was signed
  • userId: The sender's user ID
  • Returns: Promise resolving to true if signature is valid, false otherwise

setPublicKey(userId: string, publicKey: string, verifyKey: string): void

Stores another user's public key.

  • userId: Unique identifier for the other user
  • publicKey: Base64 encoded public key
  • verifyKey: Base64 encoded verification key

hasPublicKey(userId: string): boolean

Checks if a user's public key is stored.

  • userId: Unique identifier for the user
  • Returns: true if the user's public key is stored, false otherwise

encryptMessage(message: string, userId: string, useMasterKey?: boolean): Promise<IRSAEncryptedMessage>

Encrypts and signs a message for a specific user.

  • message: The message to encrypt
  • userId: The recipient's user ID
  • useMasterKey: Optional - if true and master key is set, uses master AES key instead of generating new AES key
  • Returns: Encrypted message object

decryptMessage(encryptedData: IRSAEncryptedMessage, sender: string, useMasterKey?: boolean): Promise<string>

Decrypts and verifies a message from a specific user.

  • encryptedData: The encrypted message object
  • sender: The sender's user ID
  • useMasterKey: Optional - if true and master key is set, uses master AES key for decryption
  • Returns: Decrypted message

exportEncryptedMessage(message: IRSAEncryptedMessage): string

Exports an encrypted message object to a base64 encoded string for transport or storage.

  • message: The encrypted message object containing iv, encryptedMessage, encryptedAESKey and signature
  • Returns: Base64 encoded string representation of the encrypted message

importEncryptedMessage(encoded: string): IRSAEncryptedMessage

Imports a base64 encoded encrypted message string back into an encrypted message object.

  • encoded: Base64 encoded string previously created by exportEncryptedMessage
  • Returns: Decoded IRSAEncryptedMessage object containing iv, encryptedMessage, encryptedAESKey and signature

Master AES Key Methods

The master AES key functionality allows for efficient group or room-based encryption scenarios where multiple users share the same AES key, eliminating the need for RSA encryption/decryption of individual AES keys.

generateAndSetMasterAESKey(): Promise<string>

Generates a new AES master key, encrypts it with the current user's RSA public key, and stores it.

  • Returns: Base64 encoded encrypted master AES key

setEncryptedMasterAESKey(encryptedKey: string, encryptor?: string): void

Sets an encrypted master AES key (encrypted with this user's RSA public key).

  • encryptedKey: Base64 encoded encrypted master AES key
  • encryptor: Optional user ID who encrypted the key (defaults to "self")

getDecryptedMasterAESKey(): Promise<CryptoKey>

Decrypts and returns the master AES key as a CryptoKey for direct use.

  • Returns: The decrypted AES-GCM CryptoKey
  • Throws: Error if no master key is set

exportMasterAESKeyForUser(userId: string): Promise<string>

Encrypts the current master AES key with another user's RSA public key for sharing.

  • userId: The user ID to encrypt the master key for
  • Returns: Base64 encoded encrypted master AES key for the specified user
  • Throws: Error if no master key is set or user's public key not found

setMasterAESKeyFromEncrypted(encryptedKey: string, encryptor?: string): Promise<void>

Sets the master AES key from an encrypted key received from another user and immediately validates it by attempting decryption.

  • encryptedKey: Base64 encoded encrypted master AES key
  • encryptor: Optional user ID who encrypted the key (defaults to "self")

Note: This method is equivalent to setEncryptedMasterAESKey() but performs immediate validation by testing decryption. Both methods now accept the same parameters for consistency.

encryptWithMasterAESKey(message: string): Promise<IRSAEncryptedMessage>

Encrypts a message using the master AES key (no RSA encryption of AES key).

  • message: The plaintext message to encrypt
  • Returns: Encrypted message object without encryptedAESKey field
  • Throws: Error if no master key is set

decryptWithMasterAESKey(encryptedData: IRSAEncryptedMessage, sender: string): Promise<string>

Decrypts a message using the master AES key (no RSA decryption needed).

  • encryptedData: The encrypted message object (should not include encryptedAESKey)
  • sender: The user ID who sent the message (for signature verification)
  • Returns: Decrypted message
  • Throws: Error if no master key is set or signature verification fails

decryptWithMasterAESKeyUnsafe(encryptedData: IRSAEncryptedMessage, sender: string): Promise<IDecryptionResult>

Decrypts a message using the master AES key without throwing on signature verification failure.

  • encryptedData: The encrypted message object (should not include encryptedAESKey)
  • sender: The user ID who sent the message (for signature verification)
  • Returns: Object with { message: string, verified: boolean }
  • Throws: Error if no master key is set or decryption fails (but not signature verification)

Enhanced encryptMessage(message: string, userId: string, useMasterKey?: boolean): Promise<IRSAEncryptedMessage>

The standard encrypt method now supports an optional parameter to use the master AES key.

  • message: The message to encrypt
  • userId: The recipient's user ID
  • useMasterKey: If true and master key is set, uses master AES key instead of generating new AES key
  • Returns: Encrypted message object (encryptedAESKey will be undefined if master key is used)

Enhanced decryptMessage(encryptedData: IRSAEncryptedMessage, sender: string, useMasterKey?: boolean): Promise<string>

The standard decrypt method now supports an optional parameter to use the master AES key.

  • encryptedData: The encrypted message object
  • sender: The sender's user ID
  • useMasterKey: If true and master key is set, uses master AES key for decryption
  • Returns: Decrypted message

Derived Key Encryption Methods (ECDH + PBKDF2)

generateECDHKeyPair(): Promise<string>

Generates an ECDH key pair for key exchange and returns the public key.

  • Returns: Base64 encoded ECDH public key

setECDHPublicKey(userId: string, publicKey: string): Promise<void>

Imports and stores another user's ECDH public key for key derivation.

  • userId: Unique identifier for the other user
  • publicKey: Base64 encoded ECDH public key

deriveSharedKey(userId: string, salt?: Uint8Array): Promise<void>

Derives a shared AES key using ECDH key exchange and PBKDF2 key derivation.

  • userId: The other user's identifier
  • salt: Optional salt for key derivation (generates random salt if not provided)

encryptWithSharedKey(message: string, userId: string): Promise<string>

Encrypts a message using the shared key derived with the specified user.

  • message: The message to encrypt
  • userId: The user identifier for the shared key
  • Returns: Base64 encoded encrypted message

decryptWithSharedKey(encryptedMessage: string, userId: string): Promise<string>

Decrypts a message using the shared key derived with the specified user.

  • encryptedMessage: Base64 encoded encrypted message
  • userId: The user identifier for the shared key
  • Returns: Decrypted message

decryptWithSharedKeyUnsafe(encryptedMessage: string, userId: string): Promise<IDecryptionResult>

Unsafe variant that returns verification status. Note: SharedKeyData doesn't include signatures, so verified is always true.

  • encryptedMessage: Base64 encoded encrypted message
  • userId: The user identifier for the shared key
  • Returns: Object with { message: string, verified: boolean } (verified is always true for shared keys)

exportSharedKeyData(userId: string): string

Exports shared key data for storage or transport.

  • userId: The user identifier for the shared key
  • Returns: Base64 encoded shared key data

importSharedKeyData(userId: string, data: string): Promise<void>

Imports previously exported shared key data.

  • userId: The user identifier for the shared key
  • data: Base64 encoded shared key data

hasSharedKey(userId: string): boolean

Checks if a shared key exists for the specified user.

  • userId: The user identifier
  • Returns: true if shared key exists, false otherwise

hasECDHPublicKey(userId: string): boolean

Checks if an ECDH public key is stored for the specified user.

  • userId: The user identifier
  • Returns: true if ECDH public key exists, false otherwise

removeSharedKey(userId: string): boolean

Removes the shared key for the specified user.

  • userId: The user identifier
  • Returns: true if key was removed, false if key didn't exist

removeECDHPublicKey(userId: string): boolean

Removes the ECDH public key for the specified user.

  • userId: The user identifier
  • Returns: true if key was removed, false if key didn't exist

Security Features

  • Uses RSA-OAEP for encryption and RSA-PSS for signatures
  • AES-GCM for symmetric message encryption
  • Implements message signing and signature verification
  • Master AES key functionality for efficient group/room encryption scenarios
  • ECDH key exchange with P-256 elliptic curve for perfect forward secrecy
  • PBKDF2 key derivation with 100,000 iterations for enhanced security
  • Derived key encryption using AES-GCM with 256-bit keys
  • Base64 encoding for message transport

Unsafe Decryption Methods

For scenarios where you want to decrypt a message even if signature verification fails, the library provides "unsafe" variants of the decrypt methods. These methods return both the decrypted message and verification status instead of throwing errors on signature verification failure.

IDecryptionResult Interface

All unsafe methods return an object implementing the IDecryptionResult interface:

interface IDecryptionResult {
  message: string;    // The decrypted message
  verified: boolean;  // Whether signature verification succeeded
}

Available Unsafe Methods

  • decryptMessageUnsafe() - For regular RSA-AES encrypted messages
  • decryptWithMasterAESKeyUnsafe() - For master AES key encrypted messages
  • decryptWithSharedKeyUnsafe() - For shared key encrypted messages (always verified: true)

Usage Example

try {
  const result = await receiver.decryptMessageUnsafe(encryptedData, 'sender');
  
  if (result.verified) {
    console.log('Message verified:', result.message);
  } else {
    console.log('Message unverified but readable:', result.message);
    console.warn('Signature verification failed - treat with caution');
  }
} catch (error) {
  console.error('Decryption failed:', error.message);
}

These methods are useful when you want to read the message content regardless of signature verification status, while still being informed about the verification result.

License

Published under the LGPL-2.1 license.

Published by
MDaemon Technologies, Ltd.
Simple Secure Email

https://www.mdaemon.com