+2

Đừng Để "Invalid Signature" Làm Bạn Trầm Cảm: Hiểu Sâu Về Signature Method Trong Tích Hợp API

Chào anh em! Nếu bạn là một Backend Developer, sớm muộn gì bạn cũng sẽ phải tích hợp hệ thống của mình với một bên thứ ba (Third-party API). Đó có thể là cổng thanh toán (VNPay, MoMo, Stripe), webhook từ đối tác, hoặc các hệ thống phần cứng đẩy dữ liệu giao dịch về server.

Và tôi cá là 99% anh em sẽ gặp một cái lỗi kinh điển: 401 Unauthorized - Invalid Signature. Bạn check đi check lại, data gửi đi y hệt document, secret key copy chuẩn không trượt ký tự nào, nhưng API vẫn từ chối.

Hôm nay, chúng ta sẽ mổ xẻ khái niệm Signature Method, tại sao các hệ thống tài chính/giao dịch lại "phát cuồng" vì nó, và cách để bạn không bao giờ bị nó hành hạ nữa.

1. Phân biệt nhanh: Đừng nhầm lẫn 2 khái niệm "Signature"

Trước khi đi sâu, hãy làm rõ định nghĩa để tránh "ông nói gà bà nói vịt" trong các buổi họp kỹ thuật:

  • Method Signature (Chữ ký phương thức - Trong OOP): Nếu bạn code C++, Java hay Go, đây đơn giản là "cấu trúc nhận diện" của một hàm (bao gồm tên hàm, số lượng và kiểu dữ liệu của tham số). Ví dụ: int calculateTotal(int price, int tax) có chữ ký khác với float calculateTotal(float price).
  • Signature Method (Phương thức ký điện tử - Trong API/Security): Đây là thuật toán mã hóa (ví dụ: HMAC-SHA256, RSA-SHA256) dùng để tạo ra một chuỗi băm (hash) mã hóa đính kèm vào mỗi request API, nhằm chứng minh request này là "hàng real" và dữ liệu chưa bị can thiệp.

Trong bài viết này, chúng ta tập trung vào cái số 2: Bảo mật API.

2. Tại sao lại cần Signature Method? Bản chất của "Sự toàn vẹn dữ liệu" (Data Integrity)

Giả sử bạn đang viết API nhận dữ liệu từ một máy bán vé tự động (TVM). Khi khách quẹt thẻ, máy bắn API về Backend của bạn:

{
  "ticket_id": "T12345",
  "amount": 50000,
  "status": "success"
}

Nếu chỉ dùng SSL/TLS (HTTPS) thông thường, hacker không đọc được data trên đường truyền, nhưng điều gì xảy ra nếu hacker chặn request này lại, dùng một công cụ can thiệp (như Burp Suite) và sửa giá trị "amount": 0, sau đó mới đẩy tiếp về server của bạn?

Hệ thống của bạn sẽ ghi nhận giao dịch thành công với giá 0 đồng! Thảm họa tài chính bắt đầu từ đây.

Giải pháp: Kẹp thêm một "chữ ký" (Signature). Thay vì gửi trần trụi cục data kia, hệ thống của bạn (Client) và đối tác (Server) sẽ bí mật thỏa thuận với nhau một cái chìa khóa (Secret Key) và một Signature Method (ví dụ: HMAC-SHA256).

  1. Trước khi gửi data, Client lấy cục JSON kia, cộng thêm Secret_Key, nhét vào cối xay HMAC-SHA256. Cối xay nhả ra một chuỗi ngẫu nhiên: abc123xyz... (Đây chính là Signature).
  2. Client gửi data kèm cái Signature đó đi.
  3. Server nhận được data, nó cũng lấy data đó cộng với Secret_Key (nó tự lưu) và nhét vào cái cối xay HMAC-SHA256 y hệt.
  4. Nếu kết quả server tự xay ra GIỐNG HỆT abc123xyz... -> Dữ liệu khớp 100%, không bị sửa. Nếu khác -> Giao dịch bị giả mạo, vứt!

3. Các loại Signature Method phổ biến nhất hiện nay

Khi đọc tài liệu tích hợp (API Integration Docs), bạn sẽ thường thấy yêu cầu các phương thức ký sau:

  • HMAC-SHA256 (Symmetric - Khóa đối xứng): Phổ biến nhất. Cả 2 bên (Server của bạn và API đối tác) dùng chung một chuỗi Secret Key. Ưu điểm: Nhanh, dễ implement (trong PHP gọi hàm hash_hmac() là xong). Nhược điểm: Lộ key là toang cả hệ thống.
  • RSA-SHA256 (Asymmetric - Khóa bất đối xứng): Chuẩn mực cho các hệ thống ngân hàng hoặc OIDC. Bạn giữ Private Key để tạo chữ ký (Sign), đối tác dùng Public Key của bạn để kiểm tra (Verify). Ưu điểm: Cực kỳ bảo mật, không sợ lộ Public Key. Nhược điểm: Code phức tạp hơn và tính toán nặng hơn.

4. Kinh nghiệm thực chiến: Những "bãi mìn" làm chữ ký luôn sai

Nắm lý thuyết thì dễ, nhưng lúc code thực tế, hàm hash của bạn sinh ra chữ ký toàn bị đối tác báo lỗi. Với kinh nghiệm cá nhân, đây là 3 nguyên nhân cốt lõi khiến bạn tốn cả ngày gãi đầu:

Mìn 1: Thứ tự tham số (Param Ordering) Thuật toán Hash rất nhạy cảm. Chuỗi amount=50000&ticket_id=T12345 sẽ sinh ra chữ ký HOÀN TOÀN KHÁC với chuỗi ticket_id=T12345&amount=50000. Mẹo thực chiến: Hầu hết các cổng thanh toán bắt buộc bạn phải sort (sắp xếp) các key theo thứ tự bảng chữ cái A-Z (Alphabetical order) trước khi nối chuỗi. Trong PHP, dùng ksort($data) trước khi tạo chữ ký nhé!

Mìn 2: Khoảng trắng tàng hình (Whitespace) Dữ liệu của bạn là {"amount": 50000} (có 1 dấu cách sau dấu hai chấm), nhưng thuật toán nối chuỗi của bạn lại bỏ mất dấu cách thành amount=50000. Sai một byte thôi là Signature vứt đi. Hãy đọc kỹ document xem đối tác yêu cầu nối chuỗi theo format URL-encoded (http_build_query trong PHP) hay raw string.

Mìn 3: Mã hóa URL (URL Encoding) Tham số có chứa dấu @ (như email), ký tự đặc biệt, hay dấu tiếng Việt. Bạn encode lúc tạo chữ ký, nhưng lại quên encode lúc gửi đi, hoặc ngược lại. Hệ thống nhận tự động decode trước khi băm lại, dẫn đến 2 bên hash 2 chuỗi khác nhau.

Lời kết

Signature Method không chỉ là một cái "check-box" bắt buộc để API hoạt động, nó là hàng rào phòng ngự cuối cùng bảo vệ tính toàn vẹn và túi tiền của dự án.

Lần tới, nếu có ai đó bảo bạn: "Bỏ cái check signature đi cho dễ test" ở môi trường Production, hãy thẳng thừng từ chối. Một hệ thống Backend xịn là hệ thống không chấp nhận bất kỳ dữ liệu nào thiếu chữ ký minh bạch.

Chúc anh em tích hợp API một phát ăn ngay!


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í