You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js加密数据存IndexedDB,WebCrypto离线解密及RSA密钥复用方法

Hey there! Let's walk through each of your questions step by step with working code examples and key notes to make sure everything works seamlessly between Node.js and WebCrypto.


1. Encrypt in Node.js, Decrypt with WebCrypto

The trick here is to use an algorithm supported by both environments—AES-GCM is a perfect pick since it’s widely supported and provides authenticated encryption. You’ll need to ensure the key, IV (initialization vector), and encryption parameters match exactly on both sides.

Node.js Encryption Code

We’ll use Node’s built-in crypto module to generate a random AES key and IV, then encrypt the data. We’ll export values as base64 strings for easy transfer to the browser:

const crypto = require('crypto');

async function encryptDataNode(plaintext) {
  // Generate 256-bit AES key
  const key = crypto.randomBytes(32);
  // Generate 12-byte IV (recommended standard for AES-GCM)
  const iv = crypto.randomBytes(12);
  
  // Create cipher and encrypt
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  let encrypted = cipher.update(plaintext, 'utf8', 'base64');
  encrypted += cipher.final('base64');
  // Grab authentication tag (required for valid decryption)
  const authTag = cipher.getAuthTag().toString('base64');

  return {
    key: key.toString('base64'),
    iv: iv.toString('base64'),
    ciphertext: encrypted,
    authTag: authTag
  };
}

// Usage example:
encryptDataNode('Hello from Node.js!').then(result => {
  console.log('Encryption output:', result);
  // Pass these values to your browser frontend
});

WebCrypto Decryption Code

In the browser, convert the base64 strings back to ArrayBuffers, import the key, then decrypt using WebCrypto:

async function decryptDataWeb(ciphertextB64, keyB64, ivB64, authTagB64) {
  // Convert base64 to Uint8Array (compatible with WebCrypto)
  const ciphertext = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
  const key = Uint8Array.from(atob(keyB64), c => c.charCodeAt(0));
  const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
  const authTag = Uint8Array.from(atob(authTagB64), c => c.charCodeAt(0));

  // Import AES key into WebCrypto
  const cryptoKey = await window.crypto.subtle.importKey(
    'raw',
    key,
    { name: 'AES-GCM', length: 256 },
    false,
    ['decrypt']
  );

  // Perform decryption
  const decryptedBuffer = await window.crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: iv, tag: authTag },
    cryptoKey,
    ciphertext
  );

  // Convert buffer back to readable string
  return new TextDecoder().decode(decryptedBuffer);
}

// Usage example (using the result from Node.js):
decryptDataWeb(result.ciphertext, result.key, result.iv, result.authTag)
  .then(plaintext => console.log('Decrypted text:', plaintext));

Critical Notes:

  • Never reuse an IV with the same AES key—always generate a new one for each encryption
  • The authentication tag is non-negotiable for AES-GCM; omitting it will cause decryption to fail

2. Node.js Encrypt → Store in IndexedDB → WebCrypto Offline Decrypt

This builds on the first question, adding IndexedDB storage for offline access. The workflow is: encrypt in Node.js, send the encrypted payload + metadata to the browser, store it in IndexedDB, then retrieve and decrypt even when offline.

Step 1: Node.js Encryption (Same as Question 1)

Use the encryptDataNode function above to generate your encrypted data, key, IV, and auth tag.

Step 2: Browser - Store in IndexedDB

We’ll use IndexedDB’s native API (you can also use a promise-based wrapper like idb for cleaner code):

async function storeInIndexedDB(data) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('CryptoStorage', 1);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      // Create an object store with auto-incrementing ID
      db.createObjectStore('encryptedData', { keyPath: 'id', autoIncrement: true });
    };

    request.onsuccess = (event) => {
      const db = event.target.result;
      const transaction = db.transaction('encryptedData', 'readwrite');
      const store = transaction.objectStore('encryptedData');
      store.add({ ...data });

      transaction.oncomplete = () => {
        db.close();
        resolve('Data saved to IndexedDB successfully');
      };
      transaction.onerror = (err) => reject(err);
    };

    request.onerror = (err) => reject(err);
  });
}

// Usage example:
encryptDataNode('Sensitive offline content').then(encryptedData => {
  storeInIndexedDB(encryptedData).then(msg => console.log(msg));
});

Step 3: Browser - Offline Decryption from IndexedDB

Retrieve the stored data and decrypt it using the decryptDataWeb function from Question 1:

async function getAndDecryptOffline(entryId) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('CryptoStorage', 1);

    request.onsuccess = async (event) => {
      const db = event.target.result;
      const transaction = db.transaction('encryptedData', 'readonly');
      const store = transaction.objectStore('encryptedData');
      const getRequest = store.get(entryId);

      getRequest.onsuccess = async () => {
        const storedData = getRequest.result;
        if (!storedData) return reject('No data found with that ID');
        
        const plaintext = await decryptDataWeb(
          storedData.ciphertext,
          storedData.key,
          storedData.iv,
          storedData.authTag
        );
        db.close();
        resolve(plaintext);
      };

      getRequest.onerror = (err) => reject(err);
    };

    request.onerror = (err) => reject(err);
  });
}

// Usage example (works offline!):
getAndDecryptOffline(1) // Replace with your entry ID
  .then(plaintext => console.log('Offline decrypted text:', plaintext));

Key Notes:

  • For symmetric AES keys, consider storing them in sessionStorage instead of IndexedDB if they don’t need to persist across browser restarts (better security)
  • If you need to persist keys, use WebCrypto’s extractable: false option when importing keys to prevent accidental exposure

3. Reuse RSA Keys Between Node.js and WebCrypto

RSA is great for asymmetric encryption (encrypt with public key, decrypt with private key). To reuse keys across environments, use a format supported by both—JWK (JSON Web Key) is the simplest, or PKCS#8/PEM for private keys and SPKI/PEM for public keys.

Option 1: JWK Format (Most Straightforward)

Node.js: Generate and Export RSA Key Pair

const crypto = require('crypto');

async function generateRSAKeyPair() {
  const { publicKey, privateKey } = await crypto.generateKeyPair('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
  });

  // Convert PEM keys to JWK for cross-environment compatibility
  const publicJwk = await crypto.webcrypto.subtle.importKey(
    'spki',
    Buffer.from(publicKey, 'utf8'),
    { name: 'RSA-OAEP', hash: 'SHA-256' },
    true,
    ['encrypt']
  ).then(key => crypto.webcrypto.subtle.exportKey('jwk', key));

  const privateJwk = await crypto.webcrypto.subtle.importKey(
    'pkcs8',
    Buffer.from(privateKey, 'utf8'),
    { name: 'RSA-OAEP', hash: 'SHA-256' },
    true,
    ['decrypt']
  ).then(key => crypto.webcrypto.subtle.exportKey('jwk', key));

  return { publicJwk, privateJwk };
}

// Usage example:
generateRSAKeyPair().then(keys => {
  console.log('Public JWK:', keys.publicJwk);
  console.log('Private JWK:', keys.privateJwk);
  // Send publicJWK to browser; keep privateJWK secure on your server
});

Browser: Import JWK and Decrypt

async function importRsaKey(jwk, isPrivate) {
  return window.crypto.subtle.importKey(
    'jwk',
    jwk,
    { name: 'RSA-OAEP', hash: 'SHA-256' },
    false,
    isPrivate ? ['decrypt'] : ['encrypt']
  );
}

async function decryptWithRsaWeb(ciphertextB64, privateJwk) {
  const privateKey = await importRsaKey(privateJwk, true);
  const ciphertext = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
  
  const decryptedBuffer = await window.crypto.subtle.decrypt(
    { name: 'RSA-OAEP' },
    privateKey,
    ciphertext
  );

  return new TextDecoder().decode(decryptedBuffer);
}

// Usage example:
decryptWithRsaWeb(encryptedTextB64, privateJwk)
  .then(plaintext => console.log('RSA Decrypted text:', plaintext));

Option 2: PEM Format

If you prefer PEM keys, convert them to ArrayBuffer in the browser first:

// Browser: Convert PEM public key to ArrayBuffer
function pemToArrayBuffer(pem) {
  const cleanedPem = pem.replace(/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----|\n/g, '');
  return Uint8Array.from(atob(cleanedPem), c => c.charCodeAt(0));
}

// Import PEM public key to WebCrypto
async function importRsaPublicKey(pem) {
  const buffer = pemToArrayBuffer(pem);
  return window.crypto.subtle.importKey(
    'spki',
    buffer,
    { name: 'RSA-OAEP', hash: 'SHA-256' },
    true,
    ['encrypt']
  );
}

Critical Notes:

  • Use RSA-OAEP instead of RSA-PKCS1-v1_5 for stronger security
  • Never expose private keys to untrusted clients—only send them to authenticated users if absolutely necessary
  • A 2048-bit modulus is a good balance between security and performance; 4096 is more secure but slower

内容的提问来源于stack exchange,提问作者n kavinit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:41:14