首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

資安

cookie & sessionStorage加密 (crypto-js)

By Kurau·2023-04-10·Updated 2026-05-09·6 分鐘閱讀

Cookie / SessionStorage 加密(crypto-js)

TL;DR
前端用 crypto-js AES 加密 user/token 後存進 cookie / sessionStorage 是「障眼法」,不是真安全。真正的 secret 永遠不該存在前端。要做 auth → 用 httpOnly Secure cookie + server session,讓 JS 完全讀不到 token。
這個 pattern 的根本問題
加密金鑰也在前端 → 駭客 F12 看 source code 就能找到 → 加密只是 obfuscation,不是 security。真正的安全層必須在 server side。

還是有合理用途

雖然 不能拿來存 secret,但加密後存仍有 非安全性 的好處:

用途是否合理
存 非敏感的使用者偏好(避免明文被肉眼看到)✅ 合理
存 Auth token / Session token❌ 根本不該這樣
存 密碼❌ 永遠不要
存 信用卡資訊❌ 永遠不要
存 使用者 PII(個資)❌ 違反 GDPR/個資法
存 AI prompt 設定 / 暫存草稿✅ 合理

crypto-js AES 基本用法

npm install crypto-js
npm install -D @types/crypto-js
bash
import CryptoJS from 'crypto-js';

// 加密
const plaintext = 'Hello, world!';
const passphrase = 'mySecretPassphrase';
const ciphertext = CryptoJS.AES.encrypt(plaintext, passphrase).toString();

// 解密
const bytes = CryptoJS.AES.decrypt(ciphertext, passphrase);
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
typescript
passphrase 來源
程式碼裡 絕對不要寫死 passphrase。從 process.env.NEXT_PUBLIC_PASSPHRASE 讀? 那也只是延遲被找到的時間,bundle 裡仍然有。

如果加密金鑰必須能被前端讀到,那這個加密就不是真的安全。接受這個事實後,只把它當「不要明文存」的層級即可。


包成 wrapper(實際使用)

// utils/secure-storage.ts
import CryptoJS from 'crypto-js';

const PASSPHRASE = process.env.NEXT_PUBLIC_STORAGE_KEY ?? 'fallback-key';

export function encryptValue<T>(value: T): string {
  const json = JSON.stringify(value);
  return CryptoJS.AES.encrypt(json, PASSPHRASE).toString();
}

export function decryptValue<T>(ciphertext: string): T | null {
  try {
    const bytes = CryptoJS.AES.decrypt(ciphertext, PASSPHRASE);
    const json = bytes.toString(CryptoJS.enc.Utf8);
    return JSON.parse(json) as T;
  } catch {
    return null;
  }
}

// SessionStorage wrapper
export const SecureSessionStorage = {
  set<T>(key: string, value: T) {
    sessionStorage.setItem(key, encryptValue(value));
  },
  get<T>(key: string): T | null {
    const item = sessionStorage.getItem(key);
    return item ? decryptValue<T>(item) : null;
  },
  remove(key: string) {
    sessionStorage.removeItem(key);
  },
};
typescript

Cookie vs SessionStorage 對比

特性CookieSessionStorage
容量4KB~5-10MB
跨分頁✅ 共享❌ 每個分頁獨立
過期可設 expires / Max-Age關閉分頁清除
每次 request 自動帶✅ 是❌ 否
httpOnly 保護✅ 有(JS 讀不到)❌ 無
CSRF 風險⚠️ 有(自動帶)❌ 沒有(JS 主動讀)
適合server-side auth短期 client-only state
Auth 安全鐵則
Auth token 一定要走 httpOnly cookie。
  1. JS 完全讀不到,XSS 偷不走
  2. 加上 Secure flag,只走 HTTPS
  3. 加上 SameSite=Strict 或 Lax,擋 CSRF
  4. 設合理 Max-Age,過期自動失效
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=86400; Path=/
http

那原本程式碼裡的做法呢?

// ❌ 這段 code 看起來有加密,但仍不安全
const onLogin = async (e) => {
  e.preventDefault();
  const userCredential = await signInWithEmailAndPassword(auth, email, password);
  const user = userCredential.user;
  const ciphertext = CryptoJS.AES.encrypt(JSON.stringify(user), process.env.passphrase).toString();
  document.cookie = `user=${ciphertext}`;   // ⚠️ 不是 httpOnly!
  router.push('/');
};
typescript

這寫法有 3 個問題:

  1. document.cookie set 出來不是 httpOnly,JS 讀得到 → XSS 危險
  2. 沒設 Secure / SameSite
  3. process.env.passphrase 沒加 NEXT_PUBLIC_ 前綴,在 client 是 undefined → 加密失敗

正確做法:讓 server 設 cookie:

// server-side: app/api/login/route.ts(Next.js App Router)
import { cookies } from 'next/headers';

export async function POST(req: Request) {
  const { email, password } = await req.json();

  // 在 server 驗證(不是 client)
  const sessionToken = await createServerSession(email, password);

  cookies().set('session', sessionToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 60 * 60 * 24,
    path: '/',
  });

  return Response.json({ ok: true });
}
typescript

Client 只發 POST 請求,token 由 server 處理,JS 完全摸不到。這才是真正的 auth 安全做法。


Cookie 讀取(若真的要用)

如果只是做「使用者偏好」這類 非敏感 的東西,可以用前端讀寫:

import CryptoJS from 'crypto-js';

function getCookie(name: string): string | null {
  const match = document.cookie
    .split('; ')
    .find((row) => row.startsWith(`${name}=`));
  return match ? decodeURIComponent(match.split('=')[1]) : null;
}

const cipher = getCookie('user-prefs');
if (cipher) {
  try {
    const bytes = CryptoJS.AES.decrypt(cipher, PASSPHRASE);
    const prefs = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
    setUserPrefs(prefs);
  } catch {
    setUserPrefs(null);
  }
}
typescript

但說真的,使用者偏好沒人會想看,就直接明文 JSON 存 LocalStorage 就好,加密反而讓除錯困難。


SessionStorage 版本

const onLogin = async (e: React.FormEvent) => {
  e.preventDefault();
  const userCredential = await signInWithEmailAndPassword(auth, email, password);
  const user = userCredential.user;

  // ⚠️ 仍不是真安全(同上),但比明文好一點
  const ciphertext = CryptoJS.AES.encrypt(
    JSON.stringify(user),
    process.env.NEXT_PUBLIC_PASSPHRASE!,
  ).toString();
  sessionStorage.setItem('user', ciphertext);
  router.push('/');
};
typescript

SessionStorage 比 cookie 略安全(沒 CSRF 自動帶)但仍不抗 XSS。


何時可以用 crypto-js

情境適不適合
分享連結 內含加密參數(可被拆但設計上 OK)✅ 合理
前端表單草稿(避免重要 PII 明文 localStorage)✅ 遮蓋肉眼,不抗駭
離線 PWA 暫存敏感資料⚠️ 需配合裝置加密
Auth / 支付 / 個資❌ 絕對不行

替代:WebCrypto API(原生,更現代)

2026 年首選:瀏覽器原生 WebCrypto,不用 import 套件:

async function encryptText(plaintext: string, password: string): Promise<string> {
  const enc = new TextEncoder();
  const keyMaterial = await crypto.subtle.importKey(
    'raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
  );

  const salt = crypto.getRandomValues(new Uint8Array(16));
  const key = await crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    false, ['encrypt']
  );

  const iv = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv }, key, enc.encode(plaintext)
  );

  // 把 salt + iv + ciphertext 拼成可儲存格式
  return btoa(String.fromCharCode(...salt, ...iv, ...new Uint8Array(ciphertext)));
}
typescript

優:零依賴 + AES-GCM 比 crypto-js AES-CBC 安全(GCM 有完整性驗證) 劣:寫起來囉嗦,但可包成 utility


總結:資安鐵則

資安原則
  1. 前端任何 secret 都不是真 secret(JS 都看得到)
  2. Auth / Session / 真敏感資料 → 必須 server-side(httpOnly cookie)
  3. ==前端加密 = obfuscation,不是 encryption==
  4. 不要把使用者個資存 client side
  5. 加密金鑰 永遠在 server

目錄

    ◆ 相關文章

    • Termly 第三方cookie教學

      2026-05-09
    • 保護你的API KEY (React 前端)

      2026-05-09
    • 限制特定網頁

      2026-05-09
    ← 上一篇Npm 與 Yarn下一篇 →JavaScript 浮點數問題

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章