Files
SMATEC-FRONTEND/docs/encryption-flow.md

19 KiB

🔐 Luồng Hoạt Động - Hệ Thống Lưu Mật Khẩu AES-256

Version: 2.0 (AES-256 Encryption)
File: src/utils/rememberMe.ts
Loại Bảo Mật: AES-256 (Advanced Encryption Standard)


📋 Mục Lục

  1. Tổng Quan
  2. Kiến Thức Nền Tảng
  3. Luồng Lưu Thông Tin
  4. Luồng Tải Thông Tin
  5. Chi Tiết Mã Hóa AES-256
  6. Ví Dụ Cụ Thể
  7. Xử Lý Lỗi
  8. So Sánh Bảo Mật

🎯 Tổng Quan

Hệ thống lưu mật khẩu "Remember Me" sử dụng AES-256 encryption để bảo vệ thông tin đăng nhập người dùng khi lưu vào localStorage.

Các Bước Chính:

┌─────────────────────────────────────────────────────────┐
│  1. Nhập Email + Password từ form đăng nhập            │
├─────────────────────────────────────────────────────────┤
│  2. Tạo Object: { email, password }                    │
├─────────────────────────────────────────────────────────┤
│  3. Chuyển thành JSON string                           │
├─────────────────────────────────────────────────────────┤
│  4. Mã hóa AES-256 (với SECRET_KEY)                   │
├─────────────────────────────────────────────────────────┤
│  5. Lưu ciphertext vào localStorage                   │
├─────────────────────────────────────────────────────────┤
│  6. Khi cần: Giải mã và lấy lại thông tin            │
└─────────────────────────────────────────────────────────┘

🏫 Kiến Thức Nền Tảng

AES-256 là gì?

AES = Advanced Encryption Standard (chuẩn mã hóa quốc tế)
256 = Khóa bí mật dài 256 bit (32 bytes)

Tại sao dùng AES-256?

Tiêu Chí Chi Tiết
Độ An Toàn Hầu như không thể bẻ khóa (2^256 khả năng)
Tốc Độ Nhanh (xử lý được tỷ tấn dữ liệu/giây)
Tiêu Chuẩn Được chính phủ Mỹ & thế giới sử dụng
IV (Initialization Vector) Tự động tạo ngẫu nhiên mỗi lần
Pattern Hiding Cùng plaintext → khác ciphertext

IV (Initialization Vector) là gì?

  • Định nghĩa: Một chuỗi bit ngẫu nhiên 128-bit (16 bytes)

  • Chức năng: Đảm bảo cùng plaintext lại được mã hóa khác

  • Ví dụ:

    Plaintext: "password"
    
    Lần 1: IV = random_1 → Ciphertext = "aB3dE7..."
    Lần 2: IV = random_2 → Ciphertext = "xY9pQ2..." (khác!)
    Lần 3: IV = random_3 → Ciphertext = "kL5mR8..." (khác!)
    
    Cả 3 lần đếu giải mã lại thành "password"
    

💾 Luồng Lưu Thông Tin

Hàm: saveCredentials(email, password)

Đầu Vào:
  email = "user@gmail.com"
  password = "MyPassword123"
         ↓
┌────────────────────────────────┐
│ Bước 1: Tạo Object Credentials │
└────────────────────────────────┘
  credentials = {
    email: "user@gmail.com",
    password: "MyPassword123"
  }
         ↓
┌─────────────────────────────────┐
│ Bước 2: Chuyển Object → JSON    │
└─────────────────────────────────┘
  json = '{"email":"user@gmail.com","password":"MyPassword123"}'
         ↓
         ↓
┌───────────────────────────────────────────┐
│ Bước 3: Mã Hóa (AES-256)                  │
│                                           │
│ Hàm: encrypt(json)                       │
│ CryptoJS.AES.encrypt(json, SECRET_KEY)   │
└───────────────────────────────────────────┘
         ↓
         ↓
  encrypted = "U2FsdGVkX1+ZzO2jNxNKbvH..."
  (output = Salt + IV + Ciphertext, tất cả dạng Base64)
         ↓
┌─────────────────────────────────────────┐
│ Bước 4: Lưu vào localStorage            │
│ localStorage.setItem(REMEMBER_ME_KEY,   │
│   encrypted)                            │
└─────────────────────────────────────────┘
         ↓
  localStorage['smatec_remember_login'] = "U2FsdGVkX1+ZzO2jNxNKbvH..."

Đầu Ra: ✅ Mật khẩu được mã hóa & lưu an toàn

Code Chi Tiết:

export function saveCredentials(email: string, password: string): void {
  try {
    // Bước 1: Tạo object
    const credentials: RememberedCredentials = { email, password };

    // Bước 2: JSON stringify
    const json = JSON.stringify(credentials);
    // Kết quả: '{"email":"user@gmail.com","password":"MyPassword123"}'

    // Bước 3: Mã hóa AES-256
    const encrypted = encrypt(json);
    // Kết quả: "U2FsdGVkX1+ZzO2jNxNKbvH..."

    // Bước 4: Lưu vào localStorage
    localStorage.setItem(REMEMBER_ME_KEY, encrypted);
    // Lưu thành công ✅
  } catch (error) {
    console.error('Error saving credentials:', error);
  }
}

📂 Luồng Tải Thông Tin

Hàm: loadCredentials()

Đầu Vào: (Không có tham số - tự động từ localStorage)
         ↓
┌────────────────────────────────────────┐
│ Bước 1: Lấy Dữ Liệu Từ localStorage   │
└────────────────────────────────────────┘
  encrypted = localStorage.getItem(REMEMBER_ME_KEY)
  // Kết quả: "U2FsdGVkX1+ZzO2jNxNKbvH..." hoặc null
         ↓
┌────────────────────────────────┐
│ Bước 2: Kiểm Tra Null         │
└────────────────────────────────┘
  if (!encrypted) {
    return null  ❌ (Không có dữ liệu lưu)
  }
         ↓
┌───────────────────────────────────────────┐
│ Bước 3: Giải Mã (AES-256)                 │
│                                           │
│ Hàm: decrypt(encrypted)                  │
│ CryptoJS.AES.decrypt(encrypted,          │
│   SECRET_KEY)                            │
└───────────────────────────────────────────┘
         ↓
         ↓
  decrypted_text = '{"email":"user@gmail.com","password":"MyPassword123"}'
         ↓
┌──────────────────────────────────┐
│ Bước 4: Kiểm Tra Decode Hợp Lệ  │
└──────────────────────────────────┘
  if (!decrypted_text) {
    clearCredentials()  // Xóa dữ liệu lỗi
    return null  ❌
  }
         ↓
┌──────────────────────────────┐
│ Bước 5: Parse JSON           │
└──────────────────────────────┘
  credentials = JSON.parse(decrypted_text)
  // Kết quả:
  // {
  //   email: "user@gmail.com",
  //   password: "MyPassword123"
  // }
         ↓
  return credentials  ✅

Đầu Ra: RememberedCredentials object hoặc null

Code Chi Tiết:

export function loadCredentials(): RememberedCredentials | null {
  try {
    // Bước 1: Lấy từ localStorage
    const encrypted = localStorage.getItem(REMEMBER_ME_KEY);

    // Bước 2: Kiểm tra null
    if (!encrypted) {
      return null;
    }

    // Bước 3: Giải mã AES-256
    const decrypted = decrypt(encrypted);
    // Kết quả: '{"email":"user@gmail.com","password":"MyPassword123"}'

    // Bước 4: Kiểm tra giải mã hợp lệ
    if (!decrypted) {
      clearCredentials();
      return null;
    }

    // Bước 5: Parse JSON
    const credentials: RememberedCredentials = JSON.parse(decrypted);

    // Trả về object credentials ✅
    return credentials;
  } catch (error) {
    console.error('Error loading credentials:', error);
    clearCredentials();
    return null;
  }
}

🔐 Chi Tiết Mã Hóa AES-256

Hàm Encrypt

function encrypt(data: string): string {
  try {
    // CryptoJS.AES.encrypt() tự động:
    // 1. Tạo salt ngẫu nhiên (8 bytes)
    // 2. Từ salt + SECRET_KEY → tạo key & IV (PBKDF2)
    // 3. Mã hóa data bằng AES-256-CBC
    // 4. Trả về: Salt + IV + Ciphertext (tất cả Base64)

    const encrypted = CryptoJS.AES.encrypt(data, SECRET_KEY).toString();
    return encrypted;
  } catch (error) {
    console.error('Encryption error:', error);
    return '';
  }
}

Cấu Trúc Output:

Ciphertext cuối cùng = "U2FsdGVkX1+ZzO2jNxNKbvH..."
                        ↓
                   Được chia thành 3 phần:
│ Part 1       │ Part 2    │ Part 3          │
│ Magic String │ Salt (8)  │ IV + Ciphertext │
│ "Salted__"   │  random   │                 │
└──────────────┴───────────┴─────────────────┘
                    ↓
            Tất cả encode Base64

Hàm Decrypt

function decrypt(encrypted: string): string {
  try {
    // CryptoJS.AES.decrypt() tự động:
    // 1. Decode Base64 ciphertext
    // 2. Trích xuất Salt từ đầu
    // 3. Từ salt + SECRET_KEY → tạo key & IV (PBKDF2)
    // 4. Giải mã dữ liệu bằng AES-256-CBC
    // 5. Trả về plaintext

    const decrypted = CryptoJS.AES.decrypt(encrypted, SECRET_KEY);

    // Chuyển từ WordArray sang UTF8 string
    const result = decrypted.toString(CryptoJS.enc.Utf8);
    return result;
  } catch (error) {
    console.error('Decryption error:', error);
    return '';
  }
}

Biểu Đồ Quy Trình AES-256

ENCRYPTION SIDE (Lưu Mật Khẩu)
═══════════════════════════════════════════════════════

Plaintext: "password"
    │
    ├─→ [PBKDF2 + Salt] ──→ Key (256-bit) + IV (128-bit)
    │                              │
    │                              ↓
    └──────────────────→ [AES-256-CBC Engine]
                              │
                              ↓
                         Ciphertext
                              │
                              ↓
                         [Base64 Encode]
                              │
                              ↓
                    "U2FsdGVkX1+ZzO2jNxNKbvH..."


DECRYPTION SIDE (Tải Mật Khẩu)
═══════════════════════════════════════════════════════

Ciphertext: "U2FsdGVkX1+ZzO2jNxNKbvH..."
    │
    ├─→ [Base64 Decode]
    │
    ├─→ Extract Salt (8 bytes)
    │        │
    │        └──→ [PBKDF2] ──→ Key (256-bit) + IV (128-bit)
    │
    ├─→ Extract Ciphertext
    │        │
    │        └──→ [AES-256-CBC Engine]
    │                  │
    │                  ↓
    └──────────→ Plaintext: "password"

💡 Ví Dụ Cụ Thể

Ví Dụ 1: Lưu Email & Mật Khẩu

Input:

saveCredentials('john@example.com', 'SecurePass2024!');

Bước Bước:

Bước Dữ Liệu Mô Tả
1 { email: "john@example.com", password: "SecurePass2024!" } Object credentials
2 '{"email":"john@example.com","password":"SecurePass2024!"}' JSON stringify
3 U2FsdGVkX1...abc123... AES-256 encrypt + Base64
4 localStorage Lưu thành công

Trong localStorage:

localStorage = {
  smatec_remember_login: 'U2FsdGVkX1+ZzO2jNxNKbvHJmVaFptBWc0p...',
};

Ví Dụ 2: Tải Email & Mật Khẩu

Input:

loadCredentials();

Bước Bước:

Bước Dữ Liệu Mô Tả
1 "U2FsdGVkX1+ZzO2jNxNKbvHJmVaFptBWc0p..." Lấy từ localStorage
2 Tồn tại Kiểm tra có dữ liệu
3 '{"email":"john@example.com","password":"SecurePass2024!"}' AES-256 decrypt
4 Valid JSON Kiểm tra hợp lệ
5 { email: "john@example.com", password: "SecurePass2024!" } JSON parse
6 Trả về object

Output:

{
  email: "john@example.com",
  password: "SecurePass2024!"
}

⚠️ Xử Lý Lỗi

Trường Hợp 1: Không Có Dữ Liệu Lưu

loadCredentials()
    ↓
Kiểm tra localStorage
    ↓
encrypted === null
    ↓
return null  ← Không có gì để tải

Trường Hợp 2: Dữ Liệu Bị Hỏng

loadCredentials()
    ↓
Lấy được dữ liệu
    ↓
Giải mã thất bại (decrypt() trả về '')
    ↓
clearCredentials()  ← Xóa dữ liệu lỗi
    ↓
return null  ← Không thể tải

Trường Hợp 3: JSON Parse Lỗi

loadCredentials()
    ↓
Giải mã thành công
    ↓
JSON.parse() thất bại (dữ liệu không phải JSON)
    ↓
catch block → clearCredentials()
    ↓
return null  ← Lỗi JSON

Trường Hợp 4: LocalStorage Không Khả Dụng

Các hàm try-catch sẽ bắt lỗi
    ↓
Trả về false / null / log error
    ↓
Ứng dụng vẫn hoạt động bình thường (graceful degradation)

📊 So Sánh Bảo Mật

XOR Cipher (Cũ) vs AES-256 (Mới)

╔════════════════════════════════════════════════════════════════╗
║              XOR CIPHER (CŨ)           │    AES-256 (MỚI)     ║
╠════════════════════════════════════════════════════════════════╣
║ Mã Hóa Kiểu   │ XOR từng ký tự        │    Mã hóa khối       ║
║ Độ An Toàn    │ ⚠️ Rất Thấp           │    ✅ Cực Cao        ║
║ Key Length    │ Xoay vòng bằng khóa   │    256-bit (32 bytes) ║
║ IV            │ ❌ Không có           │    ✅ Ngẫu nhiên     ║
║ Brute Force   │ ⚠️ Dễ (2^key_length) │    ✅ Không Thể      ║
║ Pattern       │ ⚠️ Có pattern         │    ✅ Ẩn pattern    ║
║               │   (cùng text → lại)   │       (random IV)     ║
║ Block Mode    │ N/A                   │    ✅ CBC Mode       ║
║ Salt          │ ❌ Không              │    ✅ Có PBKDF2      ║
║ Performance   │ ✅ Cực Nhanh          │    ✅ Nhanh (HW opt) ║
║ Standard      │ ❌ Custom/Weak        │    ✅ NIST/Military  ║
╚════════════════════════════════════════════════════════════════╝

Ví Dụ Tấn Công:

XOR Cipher:

Nếu biết:
- Ciphertext = "abc123"
- Plaintext đối với ciphertext khác = "password"

Có thể dễ dàng suy ra SECRET_KEY bằng XOR:
plaintext XOR ciphertext = key

AES-256:

Ngay cả biết:
- Ciphertext = "U2FsdGVkX1+ZzO2jNxNKbvH..."
- Plaintext = "password"
- IV được sử dụng

Vẫn không thể suy ra SECRET_KEY (cần brute force 2^256 khả năng)
≈ 10^77 năm với máy tính hiện đại

🔧 Các Hàm Hỗ Trợ

1. clearCredentials()

export function clearCredentials(): void {
  localStorage.removeItem(REMEMBER_ME_KEY);
  // Xóa hoàn toàn dữ liệu khỏi localStorage
  // Dùng khi: Người dùng logout hoặc "Forget Me"
}

2. hasSavedCredentials()

export function hasSavedCredentials(): boolean {
  try {
    const encrypted = localStorage.getItem(REMEMBER_ME_KEY);
    return encrypted !== null;
    // Kiểm tra xem có dữ liệu lưu hay không
    // Dùng khi: Hiển thị checkbox "Remember Me"
  } catch (error) {
    return false;
  }
}

🎯 Luồng Sử Dụng Trong Ứng Dụng

Sơ Đồ Hoàn Chỉnh:

LOGIN PAGE
    │
    ├─→ User nhập email + password
    │
    ├─→ Tick checkbox "Remember Me"?
    │         │
    │         ├─→ YES: saveCredentials(email, pwd)
    │         │         └─→ Mã hóa + Lưu localStorage ✅
    │         │
    │         └─→ NO: Không lưu gì
    │
    ├─→ Gửi login request lên server
    │
    └─→ Server xác thực OK ✅
              │
              └─→ Redirect tới Dashboard


SUBSEQUENT LOGIN (Lần Đăng Nhập Tiếp Theo)
    │
    ├─→ Check: hasSavedCredentials()?
    │         │
    │         ├─→ YES: loadCredentials()
    │         │         └─→ Giải mã + Parse ✅
    │         │            └─→ Auto-fill form
    │         │
    │         └─→ NO: Form trống (User nhập thủ công)
    │
    └─→ User xác nhận + submit
              │
              └─→ Login

📝 Tóm Tắt

Chức Năng Hàm Mô Tả
Lưu saveCredentials(email, pwd) Mã hóa AES-256 + lưu localStorage
Tải loadCredentials() Lấy từ localStorage + giải mã
Xóa clearCredentials() Xóa hoàn toàn từ localStorage
Kiểm Tra hasSavedCredentials() Kiểm tra có dữ liệu hay không

🛡️ Kết Luận

AES-256 được sử dụng để mã hóa thông tin sensitive
Random IV đảm bảo mã hóa khác lần nữa
PBKDF2 làm việc với SECRET_KEY để tạo key mạnh
Try-Catch xử lý tất cả lỗi gracefully
localStorage là vị trí lưu phù hợp (with encrypted data)

Bảo mật hiện tại: (Military Grade)