0

Thực chiến Device Authentication Bài 2: Giải phẫu luồng xác thực bằng Chữ ký điện tử (Asymmetric Cryptography) & Cách chống Replay Attack.

Đồng thời, chúng ta sẽ giải quyết một lỗ hổng chí mạng mà các hệ thống máy trạm, đặc biệt là các thiết bị thanh toán tự động hay máy bán vé (Ticket Vending Machines - TVM), rất dễ mắc phải nếu chỉ kiểm tra chữ ký đơn thuần: Tấn công nhai lại (Replay Attack).


1. Bản chất của Chữ ký điện tử trong giao tiếp M2M

Hãy quên username và password đi. Giao tiếp giữa thiết bị và Backend giờ đây giống như việc đóng dấu sáp lên một bức thư tuyệt mật.

Khi thiết bị muốn gửi một request (ví dụ: yêu cầu mở cổng hoặc trừ tiền thẻ), nó sẽ sử dụng Private Key được nhúng sâu trong phần cứng để tạo ra một "chữ ký". Backend, vốn đã lưu sẵn Public Key của thiết bị đó từ trước, sẽ dùng nó để xác minh.

Quá trình này giải quyết được 2 vấn đề cốt lõi:

  • Tính xác thực (Authentication): Chỉ có thiết bị cầm Private Key mới tạo ra được chữ ký hợp lệ.
  • Tính toàn vẹn (Integrity): Chữ ký được tạo ra dựa trên chính nội dung của request. Nếu hacker sửa dù chỉ một byte dữ liệu (ví dụ: sửa số tiền trừ từ 50.000 thành 10.000), chữ ký sẽ lập tức mất hiệu lực khi Backend đối chiếu.

2. Kịch bản ác mộng: Lỗ hổng Replay Attack

Giả sử bạn thiết kế một API hoàn hảo để máy bán vé báo cáo một giao dịch thành công về trung tâm:

{
  "device_id": "TVM-STATION-01",
  "action": "deduct_balance",
  "amount": 50000,
  "signature": "abc123xyz..."
}
  • Backend nhận request, dùng Public Key kiểm tra chữ ký abc123xyz... -> Hợp lệ.
  • Backend trừ 50.000đ trong database. Mọi thứ có vẻ an toàn vì hacker không có Private Key để tự tạo request mới.

Nhưng hacker không cần tạo request mới.

Hacker chỉ cần cắm cáp mạng, "đứng giữa" (Man-in-the-Middle) và copy lại nguyên xi gói tin hợp lệ trên. Sau đó, họ bắn lại gói tin y hệt này về Backend 100 lần.

Vì nội dung không đổi, chữ ký vẫn hoàn toàn hợp lệ! Backend ngây thơ xử lý request và trừ tiền 100 lần cho cùng một giao dịch. Đây chính là Replay Attack.


3. Vũ khí chống Replay Attack: Timestamp & Nonce

Để phá vỡ đòn tấn công này, chúng ta phải đảm bảo: Mỗi request được gửi đi phải là duy nhất và có hạn sử dụng.

Chúng ta ép thiết bị phải nhét thêm 2 tham số vào Payload trước khi ký:

Tham số Ý nghĩa Cách Backend xử lý
Timestamp Thời gian (Unix Epoch) thiết bị tạo request. Nếu request gửi đến muộn hơn thời gian hiện tại của Server quá một ngưỡng cho phép (ví dụ: 30 giây), Server lập tức từ chối (báo lỗi Request Expired). Điều này khiến gói tin của hacker bị "ôi thiu" rất nhanh.
Nonce "Number used once" - Một chuỗi UUID ngẫu nhiên do thiết bị sinh ra cho riêng request đó. Backend lưu Nonce này vào bộ nhớ Cache (như Redis). Nếu thấy một request mới gửi lên mà Nonce đã tồn tại trong Redis, Backend biết ngay đây là hàng copy và từ chối.

4. Luồng xử lý chuẩn mực (Best Practice)

Dưới đây là trình tự bắt buộc phải có cho một API giao tiếp thiết bị an toàn. Bạn tuyệt đối không được đảo lộn thứ tự của các bước kiểm tra này trên Backend.

  1. Thiết bị đóng gói Payload: Thiết bị tạo JSON chứa dữ liệu nghiệp vụ, cộng thêm timestamp và nonce.

    Ví dụ: {"amount": 50000, "timestamp": 1715340000, "nonce": "uuid-999"}.

  2. Thiết bị tạo Chữ ký: Thiết bị băm (Hash SHA-256) chuỗi JSON trên, sau đó dùng Private Key (RSA/ECC) mã hóa chuỗi Hash đó để tạo thành signature. Gửi toàn bộ lên API Header.
  3. Backend kiểm tra Timestamp (Chặn request hết hạn): Backend nhận Request, việc đầu tiên là lấy timestamp ra so sánh với giờ hệ thống. Nếu abs(server_time - timestamp) > 30s -> Reject.
  4. Backend kiểm tra Nonce (Chặn Replay Attack): Backend query Redis xem nonce này đã tồn tại chưa. Nếu có -> Reject. (Lưu ý: Chưa lưu vội Nonce mới vào Redis ở bước này).
  5. Backend xác minh Chữ ký: Lấy Public Key của thiết bị từ Database. Dùng Public Key để giải mã signature và đối chiếu với nội dung Request. Nếu sai -> Reject.
  6. Lưu Nonce và Xử lý nghiệp vụ: Chỉ khi chữ ký hợp lệ, Backend mới lưu nonce vào Redis với thời gian sống (TTL) đúng bằng 30 giây (bằng độ lệch Timestamp tối đa). Cuối cùng, Backend an tâm thực thi logic trừ tiền hoặc cấp vé.

Bí quyết tối ưu hệ thống: Tại sao lại set TTL cho Redis key là 30 giây? Vì sau 30 giây, gói tin đó đã tự động bị block bởi bước kiểm tra Timestamp. Việc tự động xóa Nonce trong Redis giúp tiết kiệm RAM (memory cache) đáng kể cho các hệ thống có lưu lượng tải cao.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí