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:

  1. Participant Enumeration: Retrieves current member public keys via talk.getLastE2EEPublicKeys.
  2. Secret Generation: Creates a new random 32-byte group secret using private_key generation.
  3. Per-User Encryption: For each member, derives a shared secret using Curve25519 (generateSharedSecret) and encrypts the group secret with AES-CBC (lines 41-49).
  4. Atomic Registration: Transmits all encrypted secrets to LINE servers via talk.registerE2EEGroupKey, returning a Pb1_U3 struct containing the new groupKeyId, creator, and receiverKeyId.

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 E2EE class in packages/linejs/base/e2ee/mod.ts provides the primary interface for all cryptographic operations, including key storage, negotiation, and encryption.
  • Self-key management uses getE2EESelfKeyData to retrieve or create your Curve25519 key pair, cached under e2eeKeys:<mid>.
  • Peer key negotiation occurs through getE2EELocalPublicKey, which calls talk.negotiateE2EEPublicKey and caches results as e2eePublicKeys:<keyId>.
  • Group key rotation is handled by tryRegisterE2EEGroupKey, which generates fresh 32-byte secrets, encrypts them per-participant using AES-CBC, and registers them via talk.registerE2EEGroupKey.
  • Storage prefixes follow the pattern e2eeKeys:* for private keys, e2eePublicKeys:* for cached public keys, and e2eeGroupKeys:* 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →