HMAC/SHA256: Khi "Bản gốc" và "Chìa khóa" tạo nên lời thề bảo mật
Hồi mới làm Payment, mình ngây thơ lắm. Mình nghĩ đơn giản: Client gửi amount=100000, Server nhận đúng 100000 là xong. Cho đến một ngày, một "người bạn vui tính" nào đó dùng công cụ bắt gói tin, chặn giữa đường và sửa con số thành 100 rồi mới cho đi tiếp.
Hệ thống của mình vẫn xử lý đơn hàng 100k, nhưng thực tế khách chỉ trả 100đ. Lúc đó mình mới hiểu: Trong môi trường Internet đầy rẫy rủi ro, không bao giờ được tin vào những gì bạn nhận được, trừ khi nó có một "dấu triện" không thể giả mạo.
1. HMAC/SHA256: Nó thực sự hoạt động như thế nào?
Bạn cứ tưởng tượng thế này: Bạn gửi cho đối tác một thùng hàng (Dữ liệu). Để chắc chắn không ai mở ra tráo đổi, bạn và đối tác bí mật giữ riêng một cái khuôn đúc (Secret Key).
- Trước khi gửi bạn lấy dữ liệu trộn với cái khuôn đó, bỏ vào một cái máy xay thịt (SHA256).
- Kết quả ra một đống "thịt xay" (Signature) có hình thù duy nhất.
- Bạn dán đống thịt đó lên thùng hàng.
- Khi đối tác nhận được, họ cũng lấy dữ liệu trong thùng trộn với cái khuôn họ đang giữ, bỏ vào máy xay. Nếu kết quả ra đống thịt y hệt như bạn dán trên thùng, nghĩa là hàng còn nguyên.
Tại sao phải là HMAC chứ không phải SHA256 đơn thuần? Vì nếu chỉ băm (hash) dữ liệu, thằng trộm nó cũng có máy xay y hệt bạn. Nó sửa đồ xong nó xay lại rồi dán lên thì bạn "ăn quả lừa" ngay. Nhưng cái Secret Key thì chỉ bạn và đối tác biết, thằng trộm không có cái khuôn đó nên không bao giờ xay ra được kết quả giống bạn.
2. Xương máu" khi triển khai code
Đừng nhìn mấy dòng code mẫu trên mạng mà tưởng bở. Đây là những cái "bẫy" mình đã từng dẫm phải:
Quy tắc "Alphabet" (Sắp xếp tham số)
Hầu hết các cổng thanh toán yêu cầu bạn phải sắp xếp các cặp Key-Value theo thứ tự bảng chữ cái trước khi ký.
- Bạn gửi: amount=100&orderId=123 -> Ký ra một kiểu.
- Server nhận: orderId=123&amount=100 -> Ký ra kiểu khác hoàn toàn! Chỉ cần lệch một dấu &, một dấu = hay một ký tự hoa/thường, coi như "toang".
Bí mật phải là... bí mật
Đừng bao giờ hard-code cái Secret Key vào trong source code rồi đẩy lên GitHub. Mình từng thấy có ông bạn phải đi reset lại toàn bộ hệ thống vì để lộ Key trong file .env bản demo. Hãy dùng Secret Manager hoặc biến môi trường được mã hóa.
4. Hiện thực hóa bằng Golang (Phong cách thực chiến)
Trong Go, việc này khá gọn gàng, nhưng hãy nhớ dùng crypto/hmac thay vì tự chế nhé:
func CreateSignature(payload string, secret string) string {
// Nhớ nhé: Secret Key là linh hồn của hàm này
key := []byte(secret)
h := hmac.New(sha256.New, key)
// Đổ dữ liệu vào máy xay
h.Write([]byte(payload))
// Lấy kết quả ra dưới dạng chuỗi Hex để gửi đi
return hex.EncodeToString(h.Sum(nil))
}
5. Lời khuyên cuối cho anh em
Nếu bạn đang xây dựng API cho bên thứ ba dùng, hãy luôn ép họ dùng HMAC. Đừng tặc lưỡi bảo "Dự án nhỏ, không cần đâu". Trong thanh toán, cái giá của sự tặc lưỡi thường tính bằng tiền mặt đấy.
Và nhớ nhé, khi so sánh hai Signature ở phía Server, hãy dùng hàm so sánh an toàn (Constant Time) để tránh bị hacker "đo thời gian" phản hồi của CPU rồi đoán ra Key (Timing Attack). Trong Go là subtle.ConstantTimeCompare.
Hy vọng chút trải nghiệm "đau thương" này giúp bạn tự tin hơn khi chạm vào HMAC/SHA256.
All Rights Reserved