How to Manage E2EE Keys and Key Rotation in LINEJS: A Complete Guide
LINEJS provides a complete end-to-end encryption (E2EE) implementation through the E2EE class in packages/linejs/base/e2ee/mod.ts, enabling secure key generation, storage, negotiation, and rotation using Curve25519 and AES-CBC/GCM cryptography.
Managing cryptographic keys securely is essential for building privacy-focused LINE bots and clients. The LINEJS library implements the official LINE client's full E2EE workflow, allowing developers to manage E2EE keys and perform key rotation programmatically. This guide walks through the architecture, lifecycle management, and practical implementation details based on the actual source code in the evex-dev/linejs repository.
Understanding the E2EE Architecture in LINEJS
The E2EE system in LINEJS consists of four primary components working together to handle cryptographic operations:
| Component | Responsibility | Key Source |
|---|---|---|
E2EE class |
Manages local key pairs, fetches remote public keys, generates shared secrets using Curve25519 from curve25519-js, and handles AES-CBC/GCM encryption/decryption. |
[packages/linejs/base/e2ee/mod.ts](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts) |
| Talk Service | Provides backend API wrappers for public-key negotiation, group-key registration, and key lookup operations. | [packages/linejs/base/service/talk/mod.ts](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/talk/mod.ts) |
| Storage Layer | Persists key material locally using keys like e2eeKeys:<id>, e2eePublicKeys:<id>, and e2eeGroupKeys:<chatMid>. |
packages/linejs/base/storage/* |
| Type Definitions | Defines Thrift structs and error codes such as E2EE_UPDATE_PRIMARY_DEVICE for protocol compliance. |
[packages/types/line_types.ts](https://github.com/evex-dev/linejs/blob/main/packages/types/line_types.ts) |
Key Lifecycle Management in LINEJS
LINEJS handles the complete lifecycle of E2EE keys through specific methods in the E2EE class. Understanding these stages ensures proper implementation of secure messaging.
Self-Key Retrieval and Creation
The E2EE.getE2EESelfKeyData(mid) method (lines 23-46 in mod.ts) manages your local identity key pair. It first attempts to read existing keys from storage using the prefix e2eeKeys:<mid>. If no local key exists, it enumerates all registered public keys via client.talk.getE2EEPublicKeys() and attempts to load the matching private key through getE2EESelfKeyDataByKeyId. Once located, the key is cached locally using saveE2EESelfKeyData for subsequent operations.
Remote Public Key Negotiation
For one-to-one encrypted chats, E2EE.getE2EELocalPublicKey(mid) (lines 73-110) handles peer key acquisition. This method calls talk.negotiateE2EEPublicKey to obtain the recipient's public key and associated keyId, then caches the result under e2eePublicKeys:<keyId> to avoid repeated network requests.
Group Key Acquisition and Registration
Group chats utilize shared secrets rather than pairwise keys. The system first checks e2eeGroupKeys:<mid> for cached entries (lines 112-124). If missing, it retrieves the current group key via talk.getLastE2EEGroupSharedKey (lines 130-147). For new groups without existing keys, tryRegisterE2EEGroupKey automatically generates and registers a fresh group key.
How to Rotate E2EE Keys in LINEJS
Key rotation is critical for maintaining security when group membership changes or after defined time intervals. LINEJS implements atomic key rotation through the tryRegisterE2EEGroupKey method.
When you call tryRegisterE2EEGroupKey(chatMid) (lines 207-260), the following occurs:
- Participant Enumeration: Retrieves current member public keys via
talk.getLastE2EEPublicKeys. - Secret Generation: Creates a new random 32-byte group secret using
private_keygeneration. - Per-User Encryption: For each member, derives a shared secret using Curve25519 (
generateSharedSecret) and encrypts the group secret with AES-CBC (lines 41-49). - Atomic Registration: Transmits all encrypted secrets to LINE servers via
talk.registerE2EEGroupKey, returning aPb1_U3struct containing the newgroupKeyId,creator, andreceiverKeyId.
To rotate keys after a member leaves, simply re-invoke tryRegisterE2EEGroupKey with the same chatMid. The server handles distribution atomically, ensuring all remaining participants can decrypt the new secret with their private keys.
Practical Implementation Examples
The following examples demonstrate how to manage E2EE keys and perform rotation using LINEJS.
Initializing the Client and E2EE Helper
import { LineClient } from "@evex/linejs";
const client = await LineClient.loginWithQrCode();
const e2ee = client.base.e2ee; // E2EE class instance
Retrieving Your Private Key
// mid is your user ID (e.g., "Uxxxxxxxxxxxxxxxx")
const myKey = await e2ee.getE2EESelfKeyData(client.profile!.mid);
console.log("Key ID:", myKey.keyId);
Reference: [mod.ts lines 23-46](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts#L23-L46)
Negotiating Peer Public Keys
const peerMid = "Uabcdef1234567890";
const peerKey = await e2ee.getE2EELocalPublicKey(peerMid);
console.log("Peer key (base64):", peerKey.toString("base64"));
Reference: [mod.ts lines 73-110](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts#L73-L110)
Registering and Rotating Group Keys
const groupMid = "C1234567890123456";
// Initial registration or rotation
const result = await e2ee.tryRegisterE2EEGroupKey(groupMid);
console.log("Group Key ID:", result.groupKeyId);
// Post-rotation cleanup (optional)
await client.storage.delete("e2eeGroupKeys:" + groupMid);
Reference: [mod.ts lines 207-260](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts#L207-L260)
Sending Encrypted Messages
const encryptedParts = await e2ee.encryptE2EEMessage(
groupMid,
"Secure message content",
undefined // default content type
);
await client.base.talk.sendMessage({
to: groupMid,
contentType: 0, // TEXT
content: encryptedParts, // Buffer array
});
Reference: [mod.ts lines 382-421](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts#L382-L421)
Registering New User Public Keys
To publish a new public key to the LINE backend (typically during initial setup), use the service method directly:
// Register a new public key (lines 629-639 in talk service)
await client.base.talk.registerE2EEPublicKey({
keyData: publicKeyBuffer,
keyId: newKeyId,
});
Reference: [talk/mod.ts lines 629-639](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/talk/mod.ts#L629-L639)
Summary
- The
E2EEclass inpackages/linejs/base/e2ee/mod.tsprovides the primary interface for all cryptographic operations, including key storage, negotiation, and encryption. - Self-key management uses
getE2EESelfKeyDatato retrieve or create your Curve25519 key pair, cached undere2eeKeys:<mid>. - Peer key negotiation occurs through
getE2EELocalPublicKey, which callstalk.negotiateE2EEPublicKeyand caches results ase2eePublicKeys:<keyId>. - Group key rotation is handled by
tryRegisterE2EEGroupKey, which generates fresh 32-byte secrets, encrypts them per-participant using AES-CBC, and registers them viatalk.registerE2EEGroupKey. - Storage prefixes follow the pattern
e2eeKeys:*for private keys,e2eePublicKeys:*for cached public keys, ande2eeGroupKeys:*for shared group secrets.
Frequently Asked Questions
How does LINEJS store E2EE keys locally?
LINEJS persists cryptographic material through a pluggable storage layer located in packages/linejs/base/storage/*. Private keys are stored under e2eeKeys:<mid>, cached public keys under e2eePublicKeys:<keyId>, and group shared secrets under e2eeGroupKeys:<chatMid>. The storage backend can be memory-based, file-based, or use IndexedDB depending on your configuration.
What cryptographic algorithms does LINEJS use for E2EE?
According to the source code in packages/linejs/base/e2ee/mod.ts, LINEJS uses Curve25519 for elliptic-curve Diffie-Hellman key exchange (via sharedKey from curve25519-js) and AES-CBC/GCM for symmetric encryption of messages and group secrets. This matches the cryptographic suite used by the official LINE client.
When should I rotate group keys in LINEJS?
You should invoke tryRegisterE2EEGroupKey to rotate keys whenever group membership changes (such as when a member leaves or joins) or according to your application's security policy time interval. The method automatically regenerates secrets and re-encrypts them for all current participants, ensuring forward secrecy.
Can I register new public keys for my LINEJS client?
Yes. Use the talk.registerE2EEPublicKey method (defined in packages/linejs/base/service/talk/mod.ts lines 629-639) to publish new public keys to the LINE backend. This is typically handled automatically during initial setup but can be invoked manually if you need to update your client's cryptographic identity.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →