Mục tiêu học tập
- Phân biệt symmetric (AES-256-GCM) và asymmetric encryption (RSA, ECC) — khi nào dùng cái nào
- Hiểu envelope encryption pattern: DEK + KEK, cách KMS hoạt động
- So sánh AWS KMS, GCP KMS, Azure Key Vault — IAM-controlled key access
- Biết database encryption at rest: TDE (Transparent Data Encryption) vs application-level
- Hiểu E2EE (end-to-end encryption) ở mức cơ bản — Signal protocol intro
- Tránh các sai lầm phổ biến: ECB mode, hardcoded keys, IV yếu, dùng RSA encrypt data lớn
1. Symmetric vs Asymmetric
Trade-off và use case
| Tiêu chí | Symmetric (AES) | Asymmetric (RSA/ECC) |
|---|---|---|
| Tốc độ | Rất nhanh | Chậm |
| Key size | 128/256 bit | 2048-4096 bit (RSA), 256 bit (ECC) |
| Encrypt data lớn | Có | KHÔNG — chỉ encrypt key ngắn |
| Key distribution | Khó (cần kênh an toàn) | Dễ (public key share công khai) |
| Use case chính | Bulk encryption (file, network) | Key exchange, digital signature |
Tại sao thực tế dùng cả hai (hybrid)?
TLS chính là ví dụ:
- ECDHE (asymmetric, slow) chỉ để negotiate session key
- AES-256-GCM (symmetric, fast) để encrypt dữ liệu chính
Asymmetric giải quyết vấn đề "làm sao share key an toàn", symmetric đảm nhận heavy lifting.
2. AES-GCM — AEAD chuẩn hiện đại
Block cipher mode
Vì sao ECB là tai họa kinh điển
Image gốc (Tux): Sau khi encrypt với AES-ECB:
████████████████ █▒█▓█▒▓█▒█▓█▒
█ ████ ▒▓▒ ▓███
█ ████ ▒▓▒ ▓███
█ ████ ▒▓▒ ▓███
████████████████ █▒█▓█▒▓█▒█▓█▒
→ Pattern lộ rõ! Cùng plaintext block → cùng ciphertext block.
ECB là sai lầm cơ bản nhất trong cryptography. Mọi mode hiện đại (CBC, GCM, XTS) đều có IV (Initialization Vector) ngẫu nhiên để cùng plaintext cho ra ciphertext khác nhau.
AES-GCM trong Node.js
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
function encrypt(plaintext, key) {
// KEY: 32 bytes (256 bit), bí mật
// IV: 12 bytes (96 bit) ngẫu nhiên, KHÔNG được reuse với cùng key
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag(); // 16 bytes
// Lưu: iv + authTag + ciphertext (iv và tag KHÔNG cần bí mật)
return Buffer.concat([iv, authTag, encrypted]);
}
function decrypt(data, key) {
const iv = data.subarray(0, 12);
const authTag = data.subarray(12, 28);
const ciphertext = data.subarray(28);
const decipher = createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
// Nếu authTag mismatch → throw → biết data bị tamper
}
IV/Nonce rules
RULE: Với cùng một key, IV/nonce KHÔNG được reuse.
Reuse IV trong GCM = catastrophic, attacker có thể recover key stream.
Cách sinh IV đúng:
1. Random 96-bit (cho GCM) — đủ entropy nếu < 2^32 message với cùng key
2. Counter (incremented mỗi message) — cần synchronization
Cách sinh IV SAI:
- Hardcoded "000000000000"
- Lấy từ timestamp với độ phân giải thấp
- Dùng IV cũ
3. Asymmetric: RSA và ECC
RSA
Key generation:
- Chọn 2 prime lớn p, q
- n = p * q (modulus)
- chọn e (public exponent, thường = 65537)
- tính d (private exponent)
- public_key = (n, e), private_key = (n, d)
Encrypt: c = m^e mod n
Decrypt: m = c^d mod n
- Key size khuyến nghị: 3072 bit (~128-bit security). RSA-2048 vẫn được, sẽ deprecate dần.
- Padding: BẮT BUỘC dùng OAEP (Optimal Asymmetric Encryption Padding). PKCS#1 v1.5 padding có lỗ hổng (Bleichenbacher attack).
- Giới hạn: RSA-3072 chỉ encrypt được ≤ 384 bytes — 256 bytes sau khi trừ padding. Không bao giờ dùng RSA encrypt data thật, chỉ encrypt symmetric key.
ECC (Elliptic Curve Cryptography)
Curves phổ biến:
- secp256r1 / P-256 / NIST P-256 (TLS, web standard)
- secp384r1 / P-384
- Curve25519 (Signal, modern, an toàn hơn NIST curves)
- Curve448 (high security)
- ECC-256 ≈ RSA-3072 về security level
- Key nhỏ hơn rất nhiều → tốt cho mobile/IoT
- Operation nhanh hơn RSA
- Ed25519 cho signature, X25519 cho key exchange — đang là chuẩn mới
So sánh
| RSA-3072 | ECDSA P-256 | Ed25519 | |
|---|---|---|---|
| Public key size | 384 bytes | 64 bytes | 32 bytes |
| Sign speed | Chậm | Trung bình | Rất nhanh |
| Verify speed | Nhanh | Trung bình | Rất nhanh |
| Security level | ~128-bit | ~128-bit | ~128-bit |
| Recommended | OK | OK | Best (cho new project) |
4. Envelope Encryption — cách KMS hoạt động
Vấn đề trực tiếp dùng KMS
Nếu mỗi byte data đều gọi KMS encrypt:
- KMS có rate limit (~10,000 req/giây)
- Latency cao (mỗi gọi 10-50ms)
- Chi phí cao ($0.03/10,000 request)
- KMS không thiết kế để encrypt data lớn (giới hạn 4 KB)
Envelope encryption pattern
Flow encrypt
1. App gọi KMS GenerateDataKey(KeyId="my-kek", KeySpec=AES_256)
2. KMS trả về:
- plaintext_DEK (32 bytes — chỉ tồn tại trong memory app)
- ciphertext_DEK (DEK đã được encrypt bằng KEK trong KMS)
3. App encrypt data:
encrypted_data = AES-256-GCM(plaintext_DEK, data)
4. App lưu xuống storage:
{ ciphertext_DEK, iv, authTag, encrypted_data }
5. App XÓA plaintext_DEK khỏi memory (zeroize)
Flow decrypt
1. App đọc { ciphertext_DEK, iv, authTag, encrypted_data } từ storage
2. App gọi KMS Decrypt(CiphertextBlob=ciphertext_DEK)
→ KMS verify IAM, decrypt bằng KEK, trả về plaintext_DEK
3. App decrypt data:
data = AES-256-GCM-decrypt(plaintext_DEK, iv, authTag, encrypted_data)
4. App XÓA plaintext_DEK khỏi memory
Lợi ích
- Performance: KMS chỉ gọi 1 lần / object (encrypt key 4 KB), không phải mỗi byte
- Caching: có thể cache DEK in memory cho nhiều object trong session
- Key rotation: rotate KEK trong KMS không cần re-encrypt data — chỉ cần re-encrypt DEK (nhanh)
- Audit: KMS log mỗi Decrypt call → biết ai access data, khi nào
Code ví dụ (AWS KMS + Node.js)
import { KMSClient, GenerateDataKeyCommand, DecryptCommand } from "@aws-sdk/client-kms";
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
const kms = new KMSClient({ region: "ap-southeast-1" });
const KEY_ID = "alias/my-app-kek";
async function encryptObject(plaintext) {
// Bước 1: Generate DEK
const { Plaintext: dek, CiphertextBlob: encryptedDek } = await kms.send(
new GenerateDataKeyCommand({ KeyId: KEY_ID, KeySpec: "AES_256" })
);
// Bước 2: Encrypt data bằng DEK
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", dek, iv);
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
// Bước 3: Trả về envelope (DEK đã encrypt + ciphertext)
return {
encryptedDek: Buffer.from(encryptedDek).toString("base64"),
iv: iv.toString("base64"),
authTag: authTag.toString("base64"),
ciphertext: ciphertext.toString("base64"),
};
}
async function decryptObject(envelope) {
// Bước 1: Decrypt DEK bằng KMS
const { Plaintext: dek } = await kms.send(
new DecryptCommand({
CiphertextBlob: Buffer.from(envelope.encryptedDek, "base64"),
})
);
// Bước 2: Decrypt data bằng DEK
const decipher = createDecipheriv("aes-256-gcm", dek, Buffer.from(envelope.iv, "base64"));
decipher.setAuthTag(Buffer.from(envelope.authTag, "base64"));
return Buffer.concat([
decipher.update(Buffer.from(envelope.ciphertext, "base64")),
decipher.final(),
]).toString("utf8");
}
5. KMS so sánh: AWS, GCP, Azure
| Tính chất | AWS KMS | GCP Cloud KMS | Azure Key Vault |
|---|---|---|---|
| Auth | IAM | GCP IAM | Azure AD / Managed Identity |
| HSM | FIPS 140-2 Level 2 (3 với HSM tier) | FIPS 140-2 Level 3 (HSM keys) | FIPS 140-2 Level 2 (Level 3 với Managed HSM) |
| BYOK (Bring Your Own Key) | Có (import) | Có | Có |
| Hold Your Own Key (HYOK) | XKS (External Key Store) | EKM (External Key Manager) | Managed HSM + customer key |
| Key rotation | Auto (1 năm) cho symmetric | Auto (configurable) | Auto (configurable) |
| Cross-region | Multi-region keys | Multi-region keyring | Geo-replicated vault |
| Pricing | $1/key/month + API calls | $0.06/key version/month + API calls | $0.03/10K transactions, premium tier có HSM |
IAM-controlled key access
// AWS KMS key policy — ai được phép dùng key này
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAppRoleToUseKey",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::123456789012:role/app-role" },
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "DenyAuditorFromDecrypt",
"Effect": "Deny",
"Principal": { "AWS": "arn:aws:iam::123456789012:role/auditor" },
"Action": "kms:Decrypt",
"Resource": "*"
}
]
}
Key policy + IAM policy đồng thời được evaluate. Mặc định KMS key chỉ trao quyền cho root account → phải explicit grant.
6. Database encryption at rest
Ba lớp encryption
Bảng so sánh
| Lớp | Threat protected | Threat KHÔNG protected |
|---|---|---|
| Full-disk (EBS encryption, LUKS) | Disk bị steal | DB connection compromise, SQL injection, admin xem data |
| TDE (DB-level) | Backup steal, disk steal | App có access → đọc plaintext bình thường |
| Application-level | Mọi threat trên + DBA xem | Application bug làm leak |
TDE — Transparent Data Encryption
PostgreSQL: pgcrypto extension (column-level), hoặc dùng RDS encryption (full DB)
MySQL: native TDE từ 5.7+
SQL Server: TDE built-in (Enterprise edition)
Oracle: TDE built-in (Advanced Security option)
AWS RDS: tick "Enable encryption" khi tạo DB → toàn bộ storage, snapshot, replica đều encrypt
Dùng AWS KMS key (default hoặc CMK của bạn)
TDE protection model:
Disk steal scenario:
Attacker steal raw disk → đọc EBS snapshot → encrypted nonsense ✓ Safe
App compromise scenario:
Attacker SQL injection → SELECT * FROM users → DB decrypt on the fly → plaintext ✗ Not safe
Application-level encryption (column-level)
-- Lưu thẳng encrypted blob vào column
CREATE TABLE users (
id UUID PRIMARY KEY,
email TEXT NOT NULL,
ssn_encrypted BYTEA, -- AES-256-GCM ciphertext
ssn_encrypted_dek BYTEA -- DEK encrypted by KMS
);
// App encrypt trước khi INSERT
const { encryptedDek, iv, authTag, ciphertext } = await encryptObject(user.ssn);
await db.query(
"INSERT INTO users (id, email, ssn_encrypted, ssn_encrypted_dek) VALUES ($1, $2, $3, $4)",
[user.id, user.email, Buffer.concat([iv, authTag, ciphertext]), encryptedDek]
);
// Decrypt khi cần dùng — DBA query thẳng DB chỉ thấy ciphertext
Trade-off:
- Pros: DBA và backup compromise không lộ data
- Cons: query/index trên encrypted field rất khó, latency cao hơn, complexity tăng
Đặc biệt: searchable encryption / deterministic encryption
Nếu cần WHERE email = ? trên encrypted column, có thể dùng deterministic encryption (cùng plaintext → cùng ciphertext). Nhưng deterministic mode lộ pattern (như ECB ở mức row) — chỉ dùng cho lookup, không cho data nhạy cảm chính.
7. End-to-End Encryption (E2EE)
Khái niệm
Signal Protocol — chuẩn de facto
Concepts:
- Double Ratchet: mỗi message dùng key khác, key forward-secret
- X3DH (Extended Triple Diffie-Hellman): key agreement async
- Pre-keys: cho phép initiate session khi recipient offline
Properties đạt được:
- Forward secrecy: leak key hiện tại không decrypt được message cũ
- Future secrecy (post-compromise security): leak key tạm → tự healing sau vài turn
- Deniability: không thể prove ai gửi message cụ thể nào
E2EE trong app phổ biến
| App | E2EE default | Group chat | Backup E2EE |
|---|---|---|---|
| Signal | Có | Có | Có (PIN-encrypted) |
| Có | Có | Có (optional, từ 2021) | |
| iMessage | Có | Có | Có (Advanced Data Protection, opt-in) |
| Telegram | KHÔNG (chỉ Secret Chat) | KHÔNG | KHÔNG |
| Slack | KHÔNG | - | - |
| Zoom | Có (opt-in từ 2020) | Limited | - |
Khi nào dev cần biết E2EE?
- Build app messaging / collaboration nhạy cảm
- Build storage app cho user (Standard Notes, Bitwarden, ProtonMail)
- Healthcare, legal — compliance yêu cầu provider không đọc được content
Library: libsignal, Matrix/Olm, Tink.
8. Sai lầm crypto phổ biến
1. Dùng ECB mode
# ❌ NGUY HIỂM
from Crypto.Cipher import AES
cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(plaintext, 16))
# ✅ ĐÚNG
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
nonce = get_random_bytes(12)
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
2. Hardcode key trong source
// ❌ NGUY HIỂM
const KEY = Buffer.from("0123456789abcdef0123456789abcdef", "hex");
// ✅ ĐÚNG — load từ KMS/secret manager
const key = await loadKeyFromKMS();
3. IV/Nonce reuse
// ❌ NGUY HIỂM — cùng IV cho mọi message
const IV = Buffer.alloc(12, 0);
const cipher = createCipheriv("aes-256-gcm", key, IV);
// ✅ ĐÚNG
const iv = randomBytes(12); // random mỗi lần
4. Dùng RSA encrypt data thật
# ❌ NGUY HIỂM (chỉ encrypt được vài trăm byte)
from cryptography.hazmat.primitives.asymmetric import padding
ciphertext = public_key.encrypt(huge_data, padding.OAEP(...))
# ✅ ĐÚNG — hybrid encryption
# 1. Generate random AES key
# 2. Encrypt data bằng AES-GCM
# 3. Encrypt AES key bằng RSA-OAEP
5. Roll your own crypto
Quy tắc: KHÔNG TỰ VIẾT crypto primitive.
Dùng library đã được audit: libsodium, Tink, OpenSSL, BoringSSL, ring (Rust).
6. MD5/SHA-1 cho password hashing
// ❌ NGUY HIỂM — MD5/SHA-1 broken, không slow, không salted
const hash = createHash("md5").update(password).digest("hex");
// ✅ ĐÚNG — password hash chuyên dụng
import { hash, verify } from "@node-rs/argon2";
const hashed = await hash(password); // argon2id, salt tự động
const ok = await verify(hashed, password);
// Hoặc bcrypt, scrypt, PBKDF2
7. Weak random (Math.random, time)
// ❌ NGUY HIỂM — Math.random predictable
const token = Math.random().toString(36);
// ✅ ĐÚNG
import { randomBytes } from "node:crypto";
const token = randomBytes(32).toString("hex");
8. So sánh hash/MAC bằng ==
// ❌ NGUY HIỂM — timing attack
if (computedHmac === providedHmac) { ... }
// ✅ ĐÚNG — constant-time compare
import { timingSafeEqual } from "node:crypto";
if (timingSafeEqual(Buffer.from(computedHmac), Buffer.from(providedHmac))) { ... }
9. Câu hỏi ôn tập
-
Vì sao thực tế dùng hybrid encryption (asymmetric + symmetric) thay vì chỉ một loại?
Xem đáp án
Symmetric (AES) nhanh, encrypt được data lớn (GB/s), nhưng có vấn đề key distribution — làm sao share key bí mật qua kênh không an toàn? Asymmetric (RSA, ECC) giải quyết key distribution (public key có thể public), nhưng cực chậm và chỉ encrypt được data nhỏ (vài trăm byte).
Hybrid pattern: dùng asymmetric chỉ để trao đổi/encrypt một symmetric key ngắn, sau đó dùng symmetric key đó encrypt data lớn. TLS, PGP, S/MIME, age tool đều dùng pattern này. Envelope encryption (KMS) cũng là một biến thể: KEK trong HSM "asymmetric-like" còn DEK là symmetric.
-
Trong envelope encryption với AWS KMS, vì sao thiết kế này hiệu quả hơn việc gọi
kms:Encrypttrực tiếp lên data?Xem đáp án
(1) Performance: KMS có giới hạn 4 KB/request và rate limit ~10K req/s. Encrypt object 100 MB trực tiếp không khả thi. Với envelope, KMS chỉ gọi 1 lần để encrypt/decrypt DEK (32 bytes), bulk encryption làm bằng AES local.
(2) Cost: KMS request có phí ($0.03/10K), envelope giảm số request KMS đáng kể.
(3) Key rotation: rotate KEK trong KMS chỉ cần re-encrypt các DEK (nhanh), không cần đụng data. Rotate trực tiếp = phải đọc, decrypt, re-encrypt mọi byte data.
(4) Audit: KMS log mỗi
Decrypt→ trace được ai access data, khi nào. -
Sự khác biệt giữa TDE và application-level encryption — và threat nào bảo vệ được/không?
Xem đáp án
TDE (Transparent Data Encryption): DB tự encrypt khi ghi disk, decrypt khi đọc — app không biết. Protect được: disk/snapshot/backup bị steal. KHÔNG protect: app/DBA query thẳng DB sẽ thấy plaintext (DB decrypt on-the-fly).
Application-level: app encrypt data trước khi
INSERT, decrypt sau khiSELECT. Protect: cả DBA và compromised backup đều chỉ thấy ciphertext. Trade-off: query/index trên encrypted field rất khó, latency cao hơn.Quy tắc thường dùng: TDE cho mọi DB (default), application-level thêm cho các column siêu nhạy cảm (SSN, credit card, health record).
-
Tại sao dùng AES-ECB là sai lầm cơ bản, và chế độ nào nên dùng?
Xem đáp án
ECB encrypt mỗi block độc lập, cùng plaintext block → cùng ciphertext block. Hệ quả: pattern trong plaintext (ví dụ vùng pixel màu giống nhau trong ảnh, header lặp lại) lộ ra trong ciphertext. Hình "ECB-encrypted Tux" là minh hoạ kinh điển — vẫn nhận ra hình con chim cánh cụt sau khi "encrypt".
Nên dùng: AES-GCM (AEAD — authenticated encryption với associated data) hoặc ChaCha20-Poly1305 cho mobile. Cả hai có IV/nonce ngẫu nhiên (cùng plaintext → ciphertext khác nhau) và có MAC tag chống tampering. Tránh CBC mode (legacy, dễ bị padding oracle attack nếu implement sai).
-
Vì sao E2EE khác với "encryption at rest + TLS in transit" mà các SaaS thường quảng cáo là "encrypted"?
Xem đáp án
Với encryption at rest + TLS:
- TLS bảo vệ data trên đường truyền giữa client và server
- At-rest encryption bảo vệ data trên disk của server
- Nhưng: server vẫn decrypt data trong memory để xử lý → server có khả năng đọc plaintext bất cứ lúc nào. Nhân viên service provider, government subpoena, hoặc attacker chiếm server đều có thể đọc.
E2EE: data được encrypt tại client (ví dụ Signal app trên điện thoại A), chỉ decrypt tại client nhận (điện thoại B). Server chỉ relay ciphertext, không có khả năng decrypt — kể cả Signal/WhatsApp staff cũng không đọc được.
Trade-off của E2EE: server-side search/preview/spam filtering khó hơn nhiều, password recovery thường yêu cầu device cũ (vì server không có key).
Bài tập thực hành
# 1. AES-GCM encrypt/decrypt với openssl
echo "Hello secret" | openssl enc -aes-256-gcm -e \
-K $(openssl rand -hex 32) \
-iv $(openssl rand -hex 12) \
-base64
# 2. Generate RSA và ECDSA key pair
openssl genrsa -out rsa-private.pem 3072
openssl rsa -in rsa-private.pem -pubout -out rsa-public.pem
openssl ecparam -name secp256r1 -genkey -noout -out ec-private.pem
openssl ec -in ec-private.pem -pubout -out ec-public.pem
# 3. Encrypt file bằng hybrid (RSA + AES)
openssl rand -out aes.key 32
openssl rand -out aes.iv 16
# Encrypt data
openssl enc -aes-256-cbc -in document.txt -out document.enc \
-K $(xxd -p aes.key | tr -d '\n') \
-iv $(xxd -p aes.iv | tr -d '\n')
# Encrypt AES key bằng RSA
openssl pkeyutl -encrypt -inkey rsa-public.pem -pubin \
-in aes.key -out aes.key.enc
# 4. Demo AWS KMS envelope encryption với CLI
aws kms create-key --description "demo-kek"
KEY_ID=<from output>
# Generate DEK
aws kms generate-data-key --key-id $KEY_ID --key-spec AES_256
# → Plaintext (base64 32 bytes), CiphertextBlob (encrypted DEK)
# 5. Demo password hashing đúng cách
npm install argon2
node -e "
const argon2 = require('argon2');
(async () => {
const hash = await argon2.hash('mypassword', { type: argon2.argon2id });
console.log('Hash:', hash);
console.log('Verify:', await argon2.verify(hash, 'mypassword'));
})();
"
# 6. Check RDS encryption status
aws rds describe-db-instances --query \
'DBInstances[*].[DBInstanceIdentifier,StorageEncrypted,KmsKeyId]'
Tài liệu tham khảo chính thức
- AWS KMS Concepts — Envelope Encryption
- GCP Cloud KMS
- NIST SP 800-38D — AES GCM
- Signal Protocol specifications
- Cryptography Right Answers (Latacora)
- PostgreSQL pgcrypto
Tiếp theo: Ngày 16 — Supply Chain Attacks