Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng một buổi sáng thứ Hai, bạn — Technical PM của một ví điện tử tại TP.HCM — nhận được tin nhắn từ Security Lead: "Một ổ cứng trong datacenter bị tháo ra ngoài quy trình, có thể đã rời khỏi tòa nhà." Câu hỏi đầu tiên mà CEO, phòng pháp chế và cả Ngân hàng Nhà nước sẽ hỏi bạn không phải là "ai làm" mà là: "Dữ liệu trên ổ đó có được mã hóa không?"
Nếu câu trả lời là "có, full disk encryption, khóa nằm ở hệ thống quản lý khóa riêng", thì sự cố trở thành một bài tập kiểm soát nội bộ. Nếu câu trả lời là "không", bạn đang đối mặt với một vụ rò rỉ dữ liệu cá nhân quy mô lớn, phải thông báo cho cơ quan chức năng theo Nghị định 13/2023, và có thể mất giấy phép.
Encryption (mã hóa) là một trong số ít chủ đề kỹ thuật mà một PM bắt buộc phải hiểu đến mức ra quyết định được — không phải để tự viết thuật toán, mà để biết khi nào cần bật mã hóa, mã hóa cái gì, và quan trọng nhất: ai giữ chìa khóa. Trong bài này chúng ta tập trung hẹp vào hai trạng thái của dữ liệu mà mọi sản phẩm đều có: dữ liệu khi lưu trữ (at rest) và dữ liệu khi truyền đi (in transit). Đây là nền tảng để sau này bạn nói chuyện được với security team về compliance, threat modeling hay incident response.
Khái niệm cốt lõi
Mã hóa là gì, và nó giải quyết bài toán nào
Mã hóa là quá trình biến dữ liệu đọc được (plaintext) thành dữ liệu vô nghĩa (ciphertext) bằng một thuật toán và một khóa (key). Chỉ ai có đúng khóa mới giải mã ngược lại được. Điểm mấu chốt để bạn ghi nhớ với tư cách PM: độ an toàn nằm ở việc bảo vệ khóa, không nằm ở việc giấu thuật toán. Các thuật toán như AES-256 là công khai, được cả thế giới soi xét; thứ phải bí mật là cái khóa.
Có hai kiểu mã hóa:
- Đối xứng (symmetric): dùng cùng một khóa để mã hóa và giải mã. Nhanh, phù hợp cho khối dữ liệu lớn. Chuẩn phổ biến nhất là AES-256. Đây là thứ dùng cho dữ liệu at rest.
- Bất đối xứng (asymmetric): dùng cặp khóa public/private. Chậm hơn, nhưng giải được bài toán "làm sao hai bên chưa từng gặp nhau lại thống nhất được khóa". Đây là nền của TLS, dùng cho dữ liệu in transit.
Encryption at Rest — dữ liệu nằm yên trên ổ đĩa
"At rest" là khi dữ liệu được lưu trên đĩa cứng, SSD, backup tape, hay snapshot trong cloud — nó "nằm yên" chứ không di chuyển. Mối đe dọa ở đây là truy cập vật lý hoặc truy cập tầng lưu trữ: ai đó tháo ổ cứng, sao chép một snapshot, hoặc lấy được file backup.
Có ba cấp độ mã hóa at rest mà PM cần phân biệt rõ vì chúng bảo vệ chống lại những mối đe dọa khác nhau:
1. Full Disk Encryption (FDE) — mã hóa toàn bộ ổ đĩa ở tầng thấp nhất. Trên cloud, đây là tùy chọn "encryption at rest" mặc định của các dịch vụ như AWS EBS, RDS, S3, hay Google Cloud. Nó bảo vệ chống lại đúng một kịch bản: ai đó lấy được ổ đĩa vật lý. Nhưng nó không bảo vệ chống lại kẻ tấn công đã đăng nhập được vào hệ điều hành — vì với hệ điều hành, đĩa đã được giải mã trong suốt. Đây là lý do nhiều PM hiểu sai: "Chúng ta đã bật encryption at rest rồi mà?" — đúng, nhưng nó không cứu bạn khi ứng dụng bị SQL injection.
2. Database-level / Transparent Data Encryption (TDE) — database engine tự mã hóa file dữ liệu của nó. Tương tự FDE về mặt mối đe dọa nó chống lại (lấy được file vật lý), nhưng tiện hơn vì gắn vào vòng đời database và backup.
3. Column-level / Field-level encryption — mã hóa từng trường nhạy cảm cụ thể trước khi ghi xuống, ví dụ số thẻ (PAN), số CMND/CCCD, ngày sinh. Đây là lớp bảo vệ mạnh nhất nhưng cũng đắt nhất về vận hành. Khác biệt then chốt: dữ liệu vẫn được mã hóa ngay cả khi đã ở trong database và kẻ tấn công đã đọc được bảng — vì nội dung trong cột đó là ciphertext. Chỉ tầng ứng dụng (giữ khóa) mới giải mã được. Đây là yêu cầu gần như bắt buộc với dữ liệu thẻ thanh toán theo PCI DSS.
Encryption in Transit — dữ liệu đang di chuyển
"In transit" là khi dữ liệu di chuyển qua mạng: từ trình duyệt đến server, giữa các microservice, hay từ app đến database. Mối đe dọa là nghe lén (eavesdropping) và man-in-the-middle — ai đó chen vào giữa đường truyền để đọc hoặc sửa dữ liệu.
Giải pháp chuẩn là TLS (Transport Layer Security) — chính là chữ "S" trong HTTPS. TLS làm hai việc: mã hóa kênh truyền, và xác thực rằng bạn đang nói chuyện với đúng server (qua certificate). Khi bạn thấy HTTPS, ổ khóa trên trình duyệt, đó là TLS đang hoạt động.
Một sai lầm phổ biến: nghĩ rằng "in transit" chỉ là chuyện browser-to-server. Trong kiến trúc microservice, dữ liệu còn di chuyển nội bộ giữa hàng chục service. Mã hóa các kết nối nội bộ này (gọi là mTLS — mutual TLS) là phần thường bị bỏ quên, đặc biệt khi đội ngũ nghĩ "mạng nội bộ thì an toàn rồi". Triết lý zero-trust hiện đại nói thẳng: không có mạng nào đáng tin tuyệt đối.
Vấn đề trung tâm: Key Management
Dù at rest hay in transit, câu hỏi quan trọng nhất luôn là: ai giữ khóa và khóa được bảo vệ thế nào? Mã hóa AES-256 mạnh đến đâu cũng vô nghĩa nếu khóa được hardcode trong source code hoặc nằm cạnh dữ liệu đã mã hóa. Đây là lúc các hệ thống KMS (Key Management Service) như AWS KMS, Google Cloud KMS, HashiCorp Vault, hay HSM (Hardware Security Module) xuất hiện — chúng tách khóa ra khỏi dữ liệu và kiểm soát chặt ai được dùng khóa, khi nào, có log đầy đủ.
Tình huống thực tế
Tình huống 1: Ví điện tử và chiếc ổ cứng "biến mất"
Một công ty fintech giả định tên VietPay, có khoảng 4 triệu người dùng, lưu thông tin KYC gồm số CCCD và ảnh chân dung. Trong một lần thay phần cứng tại datacenter thuê ngoài, một ổ SSD chứa replica database bị kỹ thuật viên của nhà cung cấp tháo ra mà không tuân thủ quy trình hủy.
Vì VietPay đã bật TDE ở tầng database và file dữ liệu trên ổ đó là ciphertext, đồng thời khóa giải mã nằm trong AWS KMS — không bao giờ rời khỏi datacenter của VietPay — nên ổ SSD đó về cơ bản là một cục dữ liệu rác với bất kỳ ai cầm được nó. Sự cố được phân loại là "low severity", không cần thông báo rò rỉ dữ liệu cá nhân.
Bài học: FDE/TDE không bảo vệ bạn khỏi hacker đã vào được hệ thống, nhưng nó cực kỳ hiệu quả với đúng kịch bản nó sinh ra: mất kiểm soát vật lý ổ đĩa và backup. Với chi phí gần như bằng 0 (chỉ là một checkbox trên cloud), không bật nó là một quyết định khó biện minh.
Tình huống 2: Số thẻ nằm trần trong database — bài học PCI DSS
Một sàn thương mại điện tử khu vực Đông Nam Á (tình huống dựa trên các vụ thực tế trong ngành) lưu số thẻ tín dụng đầy đủ trong một cột card_number dạng plaintext để "tiện cho việc thanh toán định kỳ". Họ đã bật encryption at rest ở tầng disk, nên về kỹ thuật "dữ liệu được mã hóa khi lưu". Đội ngũ tin rằng họ đã an toàn.
Rồi một lỗ hổng API cho phép kẻ tấn công truy vấn database thông qua ứng dụng. Vì disk encryption là transparent với ứng dụng, mọi truy vấn trả về số thẻ ở dạng plaintext. Hàng trăm nghìn số thẻ rò rỉ. Khi đối chiếu với PCI DSS, công ty vi phạm nghiêm trọng vì PAN bắt buộc phải được render unreadable — không thể đọc được — ngay cả khi đã ở trong database, điều mà chỉ column-level encryption (hoặc tokenization) mới đáp ứng được.
Bài học: Đây là minh họa kinh điển cho việc "đã mã hóa at rest" có nhiều cấp độ rất khác nhau. PM phụ trách sản phẩm thanh toán phải biết rằng với dữ liệu thẻ, FDE là chưa đủ — cần mã hóa ở tầng trường dữ liệu hoặc tốt hơn là không tự lưu thẻ mà dùng token từ cổng thanh toán.
Tình huống 3: API nội bộ "quên" bật TLS
Một startup logistics ở Hà Nội (giả định, tên GiaoNhanh) chạy 20 microservice trong một Kubernetes cluster. Browser-to-server đã là HTTPS chuẩn chỉnh. Nhưng các service nội bộ gọi nhau qua HTTP trần vì "đằng nào cũng trong VPC riêng".
Trong một đợt pentest trước vòng gọi vốn, đội security thuê ngoài cài được một pod độc hại vào cluster (mô phỏng kịch bản một dependency bị nhiễm). Từ pod đó, họ "sniff" được lưu lượng nội bộ và đọc trọn vẹn token phiên, thông tin đơn hàng, số điện thoại khách. Báo cáo pentest đánh giá đây là rủi ro cao và làm chậm vòng gọi vốn hai tháng để khắc phục bằng mTLS qua service mesh.
Bài học: "In transit" không dừng ở biên giới hệ thống. Trong kiến trúc phân tán, giả định "mạng nội bộ an toàn" là một trong những giả định nguy hiểm nhất. PM nên đưa "mã hóa kết nối nội bộ" vào tiêu chuẩn kiến trúc ngay từ đầu, vì gắn vào sau luôn đắt hơn.
Hướng dẫn từng bước
Khi bạn là PM cần đảm bảo một sản phẩm xử lý mã hóa đúng cách, đây là quy trình thực dụng:
Bước 1 — Lập bản đồ dữ liệu (data inventory). Liệt kê tất cả loại dữ liệu sản phẩm xử lý và gắn nhãn mức nhạy cảm: công khai, nội bộ, nhạy cảm (CCCD, số thẻ, sức khỏe, sinh trắc). Bạn không thể bảo vệ thứ bạn không biết mình đang giữ.
Bước 2 — Với mỗi loại dữ liệu, xác định trạng thái cần bảo vệ. Hỏi: dữ liệu này khi lưu trên đĩa có cần mã hóa không? Khi truyền qua mạng thì sao? Dữ liệu nhạy cảm cần "có" cho cả hai.
Bước 3 — Chọn cấp độ mã hóa at rest tương ứng mức nhạy cảm. Dữ liệu thường: bật FDE/TDE ở tầng cloud (gần như miễn phí, bật mặc định). Dữ liệu cực nhạy cảm như PAN, CCCD: yêu cầu column-level encryption hoặc tokenization, và ghi rõ điều này thành acceptance criteria.
Bước 4 — Bắt buộc TLS ở mọi nơi. Đặt yêu cầu: không có endpoint nào nhận HTTP trần (redirect 301 sang HTTPS), TLS 1.2 trở lên, và cân nhắc mTLS cho giao tiếp service-to-service.
Bước 5 — Làm rõ chiến lược quản lý khóa. Đây là câu hỏi bạn phải đặt cho engineering: khóa nằm ở đâu? KMS/Vault/HSM? Ai có quyền dùng khóa? Có log truy cập khóa không? Lịch xoay khóa (key rotation) thế nào? Nếu câu trả lời là "khóa nằm trong file config", đó là một red flag.
Bước 6 — Đưa vào Definition of Done và kiểm chứng. Mã hóa phải là tiêu chí nghiệm thu có thể kiểm tra được, không phải lời hứa. Ví dụ: "Cột national_id được kiểm tra bằng cách query trực tiếp DB và xác nhận giá trị là ciphertext."
Lỗi thường gặp & mẹo
Lỗi 1 — Nhầm encoding với encryption. Base64 không phải mã hóa, nó chỉ là cách biểu diễn dữ liệu và ai cũng giải ngược được trong một giây. Nếu engineer nói "dữ liệu đã được Base64 hóa cho an toàn", hãy dừng lại và hỏi lại.
Lỗi 2 — Nghĩ "đã bật encryption at rest" là xong. Như tình huống 2 cho thấy, FDE không bảo vệ khỏi tấn công qua tầng ứng dụng. Luôn hỏi: mã hóa này chống lại mối đe dọa cụ thể nào?
Lỗi 3 — Bỏ quên dữ liệu in transit nội bộ. HTTPS ra ngoài thì có, nhưng service-to-service và kết nối tới database thường bị quên.
Lỗi 4 — Quản lý khóa cẩu thả. Hardcode khóa trong code, commit khóa lên Git, hoặc để khóa cạnh dữ liệu đã mã hóa. Mã hóa mạnh + quản lý khóa yếu = không an toàn.
Lỗi 5 — Mã hóa cột rồi mất khả năng tìm kiếm. Một cột đã mã hóa thì không thể WHERE hay LIKE trực tiếp được. PM cần lường trước trade-off này; giải pháp thường là tách phần cần tìm kiếm hoặc dùng kỹ thuật blind index — và đây là quyết định sản phẩm, không chỉ kỹ thuật.
Mẹo: Với dữ liệu thẻ, lựa chọn an toàn nhất thường là không tự lưu — dùng tokenization từ cổng thanh toán. Không lưu thì không phải mã hóa, không phải lo rò rỉ, và giảm mạnh phạm vi tuân thủ PCI DSS.
Mẹo: Khi viết spec, thay vì viết "dữ liệu phải được mã hóa", hãy viết rõ ba thứ: trạng thái (at rest/in transit), cấp độ (disk/column), và quản lý khóa (KMS nào). Sự mơ hồ ở đây luôn dẫn đến lỗ hổng.
Bài tập thực hành
- Phân loại dữ liệu: Lấy một sản phẩm bạn đang làm. Liệt kê 5 loại dữ liệu nó lưu, gắn nhãn mức nhạy cảm, và với mỗi loại ghi rõ: cần mã hóa at rest cấp nào (FDE hay column-level), có cần mã hóa in transit không.
- Soi một sự cố: Đọc lại tình huống 2 (số thẻ plaintext). Viết 3 câu hỏi bạn sẽ hỏi engineering team để phát hiện sớm vấn đề này trước khi nó xảy ra.
- Viết acceptance criteria: Cho tính năng "lưu số CCCD của người dùng khi KYC", viết 3 acceptance criteria liên quan đến mã hóa, đủ cụ thể để QA kiểm chứng được (gợi ý: nói về trạng thái, cách kiểm tra ciphertext, và nơi giữ khóa).
- Tình huống quyết định: Sếp muốn lưu số thẻ đầy đủ để "thanh toán định kỳ tiện hơn". Soạn một đoạn phản biện ngắn (5–7 câu) đề xuất dùng tokenization thay vì tự lưu, nêu rõ rủi ro compliance.
Tóm tắt
Mã hóa bảo vệ dữ liệu ở hai trạng thái: at rest (trên đĩa) và in transit (trên đường truyền). At rest có ba cấp — Full Disk Encryption và TDE chống mất ổ đĩa vật lý nhưng vô dụng khi kẻ tấn công vào được tầng ứng dụng; còn column-level encryption bảo vệ từng trường nhạy cảm như PAN hay CCCD ngay cả trong database, và thường là bắt buộc theo PCI DSS. In transit dựa vào TLS/HTTPS, và đừng quên mã hóa cả các kết nối nội bộ giữa các service. Xuyên suốt tất cả, câu hỏi quan trọng nhất luôn là ai giữ khóa và khóa được bảo vệ thế nào — vì mã hóa mạnh đến đâu cũng sụp đổ nếu quản lý khóa cẩu thả. Là Technical PM, bạn không cần tự cài thuật toán, nhưng bạn phải biết phân loại dữ liệu, chọn đúng cấp độ mã hóa cho từng mức nhạy cảm, và biến nó thành acceptance criteria kiểm chứng được — đó chính là khác biệt giữa "chúng ta có bật encryption" và "chúng ta thực sự an toàn".