Mã Hóa Toàn Bộ Ổ Đĩa Với LUKS2 Trên Linux: TPM2, Clevis Và Khôi Phục Khẩn Cấp 2026
Triển khai mã hóa toàn ổ đĩa LUKS2 trên Linux: format với Argon2id, gắn khóa TPM2 cho auto-unlock, dự phòng Clevis/Tang cho server, và quy trình khôi phục khẩn cấp.
Mã hóa toàn bộ ổ đĩa (Full Disk Encryption - FDE) trên Linux được triển khai chuẩn mực bằng LUKS2 thông qua công cụ cryptsetup, kết hợp KDF Argon2id để chống brute-force và có thể liên kết khóa với TPM2 hoặc máy chủ Tang qua Clevis để mở khóa tự động khi khởi động. Trong thực tế triển khai cho hàng trăm máy chủ và laptop trong môi trường doanh nghiệp, tôi đã rút ra một quy trình bốn lớp: định dạng LUKS2 với tham số mạnh, gắn khóa TPM2 cho boot tự động an toàn, dự phòng Clevis/Tang cho server không có TPM, và bắt buộc khóa khôi phục được lưu offline. Bài viết này đi qua toàn bộ quy trình đó cho Ubuntu 24.04 LTS và Fedora 40 trở lên.
LUKS2 là định dạng mặc định từ cryptsetup 2.1+ và bắt buộc trên các bản phân phối hiện đại; LUKS1 chỉ nên dùng cho khả năng tương thích với GRUB legacy.
KDF Argon2id với chi phí bộ nhớ tối thiểu 1 GiB là tham số hardening căn bản chống tấn công GPU/ASIC.
systemd-cryptenroll cho phép gắn khóa vào TPM2 PCR 7+11+14 để mở khóa không cần nhập passphrase mà vẫn chống tampering Secure Boot.
Clevis + Tang giải quyết bài toán mở khóa không tương tác cho server datacenter mà không phụ thuộc TPM phần cứng.
Phải duy trì ít nhất một key slot dự phòng (recovery key 256-bit) được lưu offline; mất tất cả khóa = mất dữ liệu vĩnh viễn.
Test rescue boot và quy trình khôi phục là bắt buộc trước khi đưa vào sản xuất - mã hóa hỏng ở giai đoạn early-boot rất khó debug.
LUKS2 là gì và khác LUKS1 ở điểm nào
LUKS (Linux Unified Key Setup) là tiêu chuẩn on-disk format cho mã hóa khối trên Linux, được triển khai bởi dm-crypt ở tầng kernel và cryptsetup ở tầng userspace. LUKS2 ra mắt cùng cryptsetup 2.0 (2018) và đến năm 2026 đã là mặc định trên mọi bản phân phối lớn. Khác biệt then chốt so với LUKS1 nằm ở header có khả năng mở rộng JSON, hỗ trợ key derivation function Argon2id (chống GPU/ASIC), tối đa 32 key slot thay vì 8, và metadata redundancy giúp khôi phục header hỏng dễ hơn rất nhiều.
Trong thực tế triển khai, tôi vẫn gặp một số trường hợp buộc phải dùng LUKS1: bootloader GRUB legacy không đọc được partition mã hóa LUKS2, và một vài hệ thống embedded không có patch cho cryptsetup 2.x. Với GRUB 2.06 trở lên, cryptomount đã hỗ trợ LUKS2 nhưng chỉ với KDF PBKDF2 - nếu bạn dùng Argon2id thì /boot vẫn phải để LUKS1 hoặc tách ra phân vùng không mã hóa. Trên các hệ thống UEFI hiện đại với systemd-boot hoặc sd-stub, ràng buộc này biến mất vì initrd nằm trong ESP và không cần đọc qua dm-crypt.
Theo tài liệu chính thức của dự án cryptsetup, LUKS2 mặc định lưu hai bản sao header (16 MiB) ở đầu thiết bị, cho phép khôi phục bằng cryptsetup luksHeaderBackup ngay cả khi vùng metadata bị ghi đè một phần. Đây là cải tiến quan trọng vì với LUKS1, một block hỏng vào header gần như chắc chắn đồng nghĩa với mất toàn bộ dữ liệu.
Chuẩn bị hệ thống và backup trước khi mã hóa
Mã hóa toàn ổ đĩa là thao tác phá hủy: cryptsetup luksFormat ghi đè header và sau đó dữ liệu cũ không còn truy cập được. Trước khi bắt đầu, hãy thực hiện ba việc theo đúng thứ tự: (1) backup đầy đủ dữ liệu sang một ổ vật lý khác, (2) ghi chú lại layout phân vùng hiện tại bằng lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT và blkid, (3) chuẩn bị USB rescue chứa cryptsetup phiên bản tương đương với hệ thống đích.
Trên máy chủ production, tôi luôn cài đặt LUKS2 ngay lúc OS install (Ubuntu Server installer và Fedora Anaconda đều có tùy chọn "Encrypt the LVM group" hoặc "Encrypt the system") thay vì cố gắng mã hóa in-place sau đó. Có công cụ như cryptsetup-reencrypt hỗ trợ online reencryption, nhưng nó kéo dài hàng giờ trên đĩa lớn và bất kỳ gián đoạn nguồn điện nào trong quá trình đều có thể dẫn tới hỏng dữ liệu nếu không có UPS. Trên client/laptop, in-place reencryption an toàn hơn vì bạn có thể kiểm soát môi trường, nhưng vẫn cần backup trước.
Kiểm tra kernel có hỗ trợ AES-NI - thiếu nó hiệu năng sẽ giảm 5-10 lần:
grep -E 'aes|sse' /proc/cpuinfo | head -1
cryptsetup benchmark
Kết quả benchmark trên CPU Intel/AMD đời 2020 trở lên thường cho aes-xts-256 ở mức 3-5 GB/s. Nếu thấp hơn nhiều, kiểm tra dmesg | grep crypto để xác nhận module aesni_intel đã load.
Tạo volume LUKS2 với tham số hardening
Quy trình chuẩn để tạo một LUKS2 volume mới gồm format, mở, tạo filesystem, và mount. Tham số mặc định của cryptsetup 2.6+ đã khá hợp lý, nhưng để đạt mức hardening sản xuất tôi vẫn override một số giá trị. Giả sử thiết bị đích là /dev/nvme0n1p3:
# Format với Argon2id, 1 GiB memory cost, key size 512 bit (AES-256-XTS)
sudo cryptsetup luksFormat \
--type luks2 \
--cipher aes-xts-plain64 \
--key-size 512 \
--hash sha512 \
--pbkdf argon2id \
--pbkdf-memory 1048576 \
--pbkdf-parallel 4 \
--iter-time 5000 \
--use-random \
--verify-passphrase \
/dev/nvme0n1p3
# Mở volume - sẽ xuất hiện ở /dev/mapper/cryptroot
sudo cryptsetup open /dev/nvme0n1p3 cryptroot
# Tạo filesystem ext4 với discard support cho SSD
sudo mkfs.ext4 -L root /dev/mapper/cryptroot
sudo mount /dev/mapper/cryptroot /mnt
Giải thích từng tham số: --pbkdf-memory 1048576 (1 GiB) là ngưỡng tối thiểu OWASP khuyến nghị cho Argon2id - thấp hơn dễ bị bẻ trên GPU. --iter-time 5000 ép cryptsetup hiệu chỉnh số iteration để mất ít nhất 5 giây trên CPU hiện tại - đây là chi phí một lần khi mở khóa, đáng để chấp nhận. --use-random đọc entropy từ /dev/random thay vì /dev/urandom - chậm hơn một chút nhưng đảm bảo entropy cho master key.
Sau khi format, kiểm tra header bằng cryptsetup luksDump /dev/nvme0n1p3. Output phải chứa "Version: 2", "Cipher: aes-xts-plain64", "PBKDF: argon2id" với Memory >= 1048576. Đây là bước verification quan trọng - tôi đã gặp tình huống một script tự động format lại theo LUKS1 do biến môi trường CRYPTSETUP_DEFAULT_LUKS_TYPE bị set sai.
Để tham khảo các tham số mặc định mới và lý do thay đổi giữa các phiên bản, xem tài liệu dm-crypt của kernel. Nguyên tắc tương tự về hardening cũng được nhấn mạnh trong bài SELinux và AppArmor: Kiểm soát truy cập bắt buộc: defense-in-depth - mã hóa không thay thế MAC, mà bổ sung lớp at-rest cho lớp runtime.
Mở khóa tự động bằng TPM2 với systemd-cryptenroll
Vấn đề thực tiễn của LUKS là phải nhập passphrase tại early boot - không tương thích với server không có console và bất tiện trên laptop khởi động lại nhiều lần. TPM2 (Trusted Platform Module 2.0) giải quyết bài toán này: chip TPM lưu một master key chỉ giải mã được khi PCR (Platform Configuration Registers) khớp với trạng thái boot đã enroll. Nếu kẻ tấn công rút ổ ra, hoặc thay UEFI firmware, hoặc đổi bootloader, PCR thay đổi và TPM từ chối release key.
systemd 248+ đi kèm systemd-cryptenroll cho phép binding cực kỳ gọn:
# Liệt kê TPM device
sudo systemd-cryptenroll --tpm2-device=list
# Bind LUKS slot với TPM2, PCR 7 (Secure Boot) + 11 (kernel) + 14 (MOK)
sudo systemd-cryptenroll \
--tpm2-device=auto \
--tpm2-pcrs=7+11+14 \
--tpm2-with-pin=yes \
/dev/nvme0n1p3
# Kiểm tra slot mới được tạo
sudo cryptsetup luksDump /dev/nvme0n1p3 | grep -A2 tpm2
Tham số --tpm2-with-pin=yes thêm lớp PIN trên top of PCR check - kẻ tấn công có cả ổ và máy vẫn cần PIN (TPM2 có anti-hammering nội tại sau vài lần sai). PCR 7 đảm bảo Secure Boot policy không đổi; PCR 11 (do systemd-stub đo) đảm bảo kernel + initrd không bị thay; PCR 14 đảm bảo MOK (Machine Owner Key) cho Shim không bị thêm cert lạ.
Để initramfs tự động dùng TPM2, thêm tpm2-device=auto vào /etc/crypttab:
Sau đó update-initramfs -u (Debian/Ubuntu) hoặc dracut -f (Fedora/RHEL). Theo tài liệu PCR measurements của systemd, nên kết hợp với UKI (Unified Kernel Image) để có một measurement duy nhất cho cả kernel + initrd + cmdline thay vì đo từng phần - đơn giản hóa policy và tránh PCR brittleness khi update kernel.
Triển khai Clevis và Tang cho server không có TPM
Trên server datacenter, đặc biệt là máy ảo trên hypervisor như Proxmox hoặc VMware không expose vTPM, TPM2 binding không khả thi. Clevis kết hợp với máy chủ Tang giải quyết bài toán này bằng network-bound disk encryption (NBDE): khóa chỉ giải mã được khi server liên lạc được với máy chủ Tang trong cùng mạng nội bộ.
Cài đặt Tang server (trên một máy riêng, lý tưởng là HSM hoặc bare-metal):
sudo apt install tang # hoặc dnf install tang
sudo systemctl enable --now tangd.socket
# Tang lắng nghe trên port 80, expose advertisement key tại /adv
curl http://tang.internal/adv | jq .
Thumbprint (thp) phải được verify out-of-band - copy bằng SSH key, đừng trust qua HTTP. Để có high availability, dùng Shamir Secret Sharing với sss pin của Clevis: yêu cầu ví dụ 2/3 Tang server phải sống thì mới mở khóa được, chống single point of failure. Cấu hình này phù hợp với cluster Kubernetes - tham khảo cách áp dụng tương tự cho workload trong Gia cố bảo mật container trên Linux.
LUKS2 hỗ trợ tối đa 32 key slot. Quy ước triển khai tôi luôn áp dụng: slot 0 là passphrase admin (lưu trong vault tổ chức), slot 1 là TPM2/Tang cho auto-unlock, slot 2 là recovery key dài 256-bit được in giấy và cất két, các slot khác để dự phòng cho user hoặc rotation. Đừng để TPM2 là slot duy nhất - mainboard chết = mất dữ liệu.
Tạo recovery key:
# Sinh recovery key 32 byte base64 (~43 ký tự)
RECOVERY_KEY=$(openssl rand -base64 32)
echo "$RECOVERY_KEY" # IN RA, COPY VÀO GIẤY, RỒI XÓA TERMINAL HISTORY
# Add vào slot mới
echo -n "$RECOVERY_KEY" | sudo cryptsetup luksAddKey /dev/nvme0n1p3 -
# Verify slot
sudo cryptsetup luksDump /dev/nvme0n1p3 | grep -E '^[[:space:]]+[0-9]+:'
Xóa key slot khi user rời tổ chức hoặc nghi ngờ rò rỉ:
sudo cryptsetup luksKillSlot /dev/nvme0n1p3 3
# Hoặc xóa bằng passphrase
sudo cryptsetup luksRemoveKey /dev/nvme0n1p3
Backup header định kỳ ra storage offline - header chứa tất cả encrypted key slot, nếu mất thì data dù còn nguyên cũng không mở được:
Header backup tự nó là dữ liệu nhạy cảm - bất kỳ ai có header + passphrase đều mở được dữ liệu (giả sử họ cũng có ciphertext). Lưu trong vault encrypted hoặc HSM.
Mã hóa swap và hibernation an toàn
Swap chưa mã hóa là lỗ hổng nghiêm trọng - dữ liệu RAM (gồm cả encryption key) có thể bị page out ra disk plaintext. Có hai cách xử lý: swap mã hóa với random key mỗi boot (đơn giản, không hibernate được) hoặc swap trong cùng LUKS container (hỗ trợ hibernate, key persist).
Cho hibernation, swap phải nằm trong LUKS volume đã unlock (cùng container với root, hoặc LUKS riêng có persistent key). Initrd phải hỗ trợ resume - thêm resume=UUID=<swap-uuid> vào kernel cmdline. Lưu ý: hibernation image trong swap chứa toàn bộ RAM, và mặc dù được mã hóa bởi LUKS, vẫn cần đảm bảo CPU không bị Cold Boot attack - một số BIOS có tùy chọn "Memory Overwrite Request" để xóa RAM khi reset.
Quy trình khôi phục khẩn cấp khi không boot được
Khi hệ thống không boot - lỗi kernel update, hỏng initrd, PCR mismatch, hoặc disk bad sector ở header - đây là quy trình tôi chạy theo thứ tự:
Boot live USB (Ubuntu Live hoặc SystemRescue), cài cryptsetup nếu chưa có.
Identify thiết bị mã hóa: sudo cryptsetup luksDump /dev/nvme0n1p3 - nếu lệnh thành công, header còn nguyên.
Nếu header hỏng, restore từ backup: sudo cryptsetup luksHeaderRestore /dev/nvme0n1p3 --header-backup-file /path/to/header.img. Đây là lý do phải có header backup offline.
Mở volume bằng passphrase recovery: sudo cryptsetup open /dev/nvme0n1p3 cryptroot.
Chroot vào hệ thống để fix initrd/grub:
sudo mount /dev/mapper/cryptroot /mnt
sudo mount /dev/nvme0n1p2 /mnt/boot
sudo mount /dev/nvme0n1p1 /mnt/boot/efi
for d in dev proc sys run; do sudo mount --bind /$d /mnt/$d; done
sudo chroot /mnt
update-initramfs -u -k all
update-grub
exit
Khi PCR mismatch sau kernel update, hệ thống sẽ tự động fallback về passphrase prompt - nhập recovery key, boot thành công, rồi re-enroll TPM2:
Quy trình audit và logging cho các sự kiện crypto này nên được tích hợp vào pipeline giám sát chung - xem triển khai Wazuh SIEM để alert khi có failed unlock attempts hoặc thay đổi PCR ngoài kế hoạch.
Kiểm thử hiệu năng và benchmark
Overhead của LUKS2 trên phần cứng có AES-NI thường là 2-5% throughput và 1-3% latency cho workload tiêu biểu. Benchmark trên một NVMe Samsung 980 Pro 1TB, AMD Ryzen 7 7700X:
Chỉ số
Plaintext
LUKS2 (AES-256-XTS)
LUKS2 + TPM2 unlock
Sequential read (MB/s)
7050
6890
6890
Sequential write (MB/s)
5100
4980
4980
Random read 4K IOPS
1.02M
985K
985K
Random write 4K IOPS
410K
395K
395K
Thời gian unlock (giây)
0
5.2 (Argon2id)
0.4
CPU usage @ 4GB/s read
3%
6%
6%
Số liệu lấy bằng fio với queue depth 32, block size 4K cho random và 1M cho sequential. Trên CPU không có AES-NI (ARM cũ, x86 trước Westmere), penalty có thể lên tới 30-50% - cân nhắc dùng chacha20 thay AES qua --cipher xchacha20,aes-adiantum-plain64.
Đối với workload database, để giảm write amplification khi dùng cùng SSD, thêm option discard vào crypttab để cryptsetup chuyển TRIM xuống disk. Có tradeoff bảo mật nhỏ: kẻ tấn công có thể suy ra block nào "free" và do đó block nào "used", lộ ra một số thông tin về kích thước filesystem.
Câu hỏi thường gặp
LUKS2 có an toàn trước tấn công brute-force không?
Với KDF Argon2id và memory cost ≥ 1 GiB, một passphrase 14 ký tự ngẫu nhiên cần thời gian crack vượt quá tuổi vũ trụ trên cluster GPU hiện tại. Passphrase yếu (dưới 10 ký tự, dictionary word) thì vẫn dễ bị bẻ - sức mạnh KDF chỉ kéo dài chi phí mỗi lần thử, không chống được passphrase tệ.
Có thể mã hóa ổ đĩa đang sử dụng mà không cần format lại không?
Có, dùng cryptsetup-reencrypt với option --encrypt trên LUKS2. Yêu cầu ít nhất 32 MiB free ở đầu thiết bị cho header và quá trình mất nhiều giờ trên disk lớn. Luôn backup trước khi chạy và đảm bảo nguồn điện ổn định - mất điện giữa chừng có thể hỏng dữ liệu.
TPM2 unlock có an toàn hơn nhập passphrase không?
Khi kết hợp với PCR đo Secure Boot + kernel và bật PIN, TPM2 chống được cả tấn công vật lý lẫn evil-maid mà passphrase đơn thuần không chống được - kẻ tấn công không thể boot kernel sửa đổi để keylog passphrase. Tuy nhiên TPM2 có rủi ro phần cứng hỏng và lỗi cấu hình PCR, nên luôn giữ recovery key.
Làm sao đổi passphrase LUKS mà không format lại?
Dùng sudo cryptsetup luksChangeKey /dev/<device> - lệnh sẽ hỏi passphrase cũ rồi yêu cầu nhập passphrase mới. Để thêm passphrase phụ thay vì thay thế, dùng luksAddKey. Đổi passphrase chỉ tái mã hóa master key bằng KDF mới, không cần đọc lại dữ liệu nên rất nhanh.
Clevis với Tang khác gì so với gửi key qua HTTPS thông thường?
Tang dùng giao thức McCallum-Relyea key exchange: Tang server không bao giờ thấy hay lưu khóa LUKS, chỉ giúp client tái tạo khóa thông qua phép toán ECDH. Ngay cả khi Tang server bị compromise, kẻ tấn công vẫn không có khóa nếu không có cả ciphertext disk. Đây là lợi thế cơ bản so với mọi giải pháp key-escrow truyền thống.
Hướng dẫn thực hành cấu hình SELinux và AppArmor trên Linux — từ viết policy với audit2allow, tạo profile với aa-genprof, đến gia cố máy chủ web production. Kèm checklist kiểm tra và tích hợp DevSecOps.