ephy-rsa
Version:
A hybrid RSA + AES encryption service using jose for large data encryption on the browser.
199 lines (132 loc) β’ 6.03 kB
Markdown
# Ephy-RSA
Ephy-RSA is a lightweight encryption library for securely exchanging data between a frontend application and a backend server. It generates **ephemeral RSA key pairs** in browser memory, ensuring that sensitive data is **encrypted before transmission** and that **no keys are persistently stored**. This prevents plaintext exposure in browser network logs and enhances security.
## tl;dr
Below is a **Sequence Diagram** illustrating the encryption and decryption process:

## π Features
- π **Ephemeral RSA Key Pairs** β No keys are stored persistently on the frontend.
- π **Client-Side Encryption** β Data is encrypted before leaving the browser.
- π **Easy Integration** β Works seamlessly with JSON Web Encryption (JWE) using `jose`.
- π **No Plaintext Exposure** β Data remains encrypted in transit.
## π Installation
```sh
npm install ephy-rsa
```
OR
```sh
yarn add ephy-rsa
```
## π Usage
### π₯ Frontend (Client-Side)
1οΈβ£ **Initialize the Library and Generate an Ephemeral Key Pair**
```javascript
import RSAKeyService from "ephy-rsa";
(async () => {
const rsaService = await RSAKeyService();
console.log("Public Key:", rsaService.getPublicKey());
})();
```
2οΈβ£ **Encrypt Request Payload with the Server's Public Key**
```javascript
(async () => {
const rsaService = await RSAKeyService();
const serverPublicKey = "your-server-public-key-in-JWK-or-PEM-format";
const encryptedData = await rsaService.encryptWithServerPublicKey(
"Sensitive Data",
serverPublicKey,
);
console.log("Encrypted Data:", encryptedData);
})();
```
3οΈβ£ **Decrypt Response Data with the Ephemeral Private Key**
```javascript
(async () => {
const rsaService = await RSAKeyService();
const encryptedData = "..."; // Encrypted string received from backend
const decryptedData = await rsaService.decryptWithPrivateKey(encryptedData);
console.log("Decrypted Data:", decryptedData);
})();
```
### π₯ Backend (Server-Side)
The backend should:
1. **Generate an RSA Key Pair** and expose the **public key** to the frontend.
2. **Decrypt incoming encrypted messages** using the **private key**.
3. **Encrypt responses** using **client's public key**.
#### Example Backend Code (Node.js using `jose`)
```javascript
import * as jose from "jose";
import fs from "fs";
// Load RSA Private Key (PEM format)
const privateKeyPem = fs.readFileSync("./private.pem", "utf8");
const privateKey = await jose.importPKCS8(privateKeyPem, "RSA-OAEP");
// Load RSA Public Key (PEM format)
const publicKeyPem = fs.readFileSync("./public.pem", "utf8");
const publicKey = await jose.importSPKI(publicKeyPem, "RSA-OAEP");
// Decrypt received encrypted data
async function decryptData(encryptedData) {
const decrypted = await jose.compactDecrypt(encryptedData, privateKey);
console.log("Decrypted Data:", new TextDecoder().decode(decrypted.plaintext));
}
// Encrypt responses
async function encryptResponse(response, clientPublicKey) {
const encrypted = await jose.compactEncrypt(
JSON.stringify(response),
clientPublicKey,
);
console.log("Encrypted Response:", new TextDecoder().decode(encrypted));
}
export { publicKey, decryptData, encryptResponse };
```
The **public key** should be **exposed via an API endpoint**, so the frontend can retrieve it and use it for encryption.
## π API Reference
### `RSAKeyService(algorithm?: RsaHashedKeyGenParams): Promise<RSAKeyService>`
Creates an instance of `RSAKeyService`. You can optionally specify an RSA key generation algorithm.
### `getPublicKey(): string | null`
Returns the **Base64-encoded PEM public key** of the generated RSA key pair.
### `encryptWithServerPublicKey(data: string, jwkPublicKey: JsonWebKey | string, alg?: "RSA-OAEP" | "RSA-OAEP-256", enc?: "A128GCM" | "A256GCM"): Promise<string>`
Encrypts the given data using the serverβs **public key**. The key can be in **JWK** or **PEM/Base64** format.
### `decryptWithPrivateKey(encryptedData: string): Promise<string>`
Decrypts the given encrypted data using the **ephemeral private key** stored in memory.
## π Security Considerations
- **Frontend:**
- Private keys are never stored persistently β They exist only in memory and are lost when the page refreshes.
- Protect against XSS attacks β Since in-memory keys can be accessed if malicious scripts are injected.
- Use HTTPS β To prevent man-in-the-middle attacks.
- Implement a strong Content Security Policy (CSP) β To restrict script execution and mitigate injection risks.
- **Backend:**
- Store the private key securely.
- Restrict access to the decryption endpoint.
- Ensure proper authentication and authorization before decrypting data.
## π TypeScript Interface
```typescript
interface RSAKeyService {
getPublicKey(): string | null;
encryptWithServerPublicKey(
data: string,
jwkPublicKey: JsonWebKey | string,
alg?: "RSA-OAEP" | "RSA-OAEP-256",
enc?: "A128GCM" | "A256GCM",
): Promise<string>;
decryptWithPrivateKey(encryptedData: string): Promise<string>;
}
```
## π Additional Resources
- **Github Repository**: [GitHub - aman-eth/ephy-rsa](https://github.com/aman-eth/ephy-rsa)
- **Demo Repository**: [GitHub - aman-eth/ephy-rsa-example](https://github.com/aman-eth/ephy-rsa-example)
- **Blog Article**: [Secure Client-Side Encryption with Ephemeral RSA Key Pairs: A Modern Approach](https://aman-eth.medium.com/secure-client-side-encryption-with-ephemeral-rsa-key-pairs-a-modern-approach-ed8001fce2e2)
## π· Keywords
`reactjs`, `nextjs`, `RSA`, `AES`, `encryption`, `decryption`, `hybrid`, `jose`, `cryptography`, `browser`, `secure`, `ephemeral`
## π License
This project is licensed under the MIT License
## β€οΈ Contributing
Contributions are welcome! Feel free to open issues or pull requests or reach out to me via GitHub Issues.