Quy trình mã hoá dữ liệu thanh toán dành cho người bán

Google Pay API trả về các phương thức thanh toán trong một tải trọng PaymentMethodToken đã ký và mã hoá. Phương thức thanh toán được trả về là thẻ bao gồm PAN hoặc thẻ được mã hoá bao gồm PAN của thiết bị và mật mã.

Tải trọng chứa một trường có tên là protocolVersion cho người nhận tải trọng biết các dữ liệu mật mã học gốc nào đang được sử dụng và định dạng dự kiến.

Hướng dẫn này cung cấp thông tin về cách tạo khoá công khai để yêu cầu mã thông báo phương thức thanh toán được mã hoá và có chữ ký của Google, đồng thời trình bày chi tiết các bước cần thực hiện để xác minh và giải mã mã thông báo.

Hướng dẫn này chỉ áp dụng cho protocolVersion = ECv2.

Vì bạn trực tiếp nhận thông tin thẻ thanh toán, hãy đảm bảo ứng dụng của bạn tuân thủ PCI DSS và máy chủ của bạn có cơ sở hạ tầng cần thiết để xử lý thông tin thanh toán của người dùng một cách an toàn trước khi bạn tiếp tục.

Các bước sau đây trình bày những việc mà một nhà tích hợp phải làm để sử dụng tải trọng ECv2 PaymentMethodToken của Google Pay API:

  1. Tìm nạp khoá ký gốc của Google.
  2. Xác minh rằng chữ ký của khoá ký trung gian là hợp lệ bằng bất kỳ khoá ký gốc nào chưa hết hạn.
  3. Xác minh rằng khoá ký trung gian của tải trọng chưa hết hạn.
  4. Xác minh rằng chữ ký của tải trọng là hợp lệ theo khoá ký trung gian.
  5. Giải mã nội dung của tải trọng sau khi bạn xác minh chữ ký.
  6. Xác minh rằng thông báo chưa hết hạn. Bạn cần kiểm tra để đảm bảo thời gian hiện tại nhỏ hơn trường messageExpiration trong nội dung đã giải mã.
  7. Sử dụng phương thức thanh toán trong nội dung đã giải mã và tính phí.

Mã mẫu trong thư viện Tink của chúng tôi thực hiện các bước từ 1 đến 6.

Cấu trúc mã thông báo phương thức thanh toán

Thông báo mà Google trả về trong phản hồi PaymentData là một đối tượng JSON được chuyển đổi tuần tự, mã hoá UTF-8 với các khoá được chỉ định trong bảng sau:

Tên Loại Mô tả
protocolVersion Chuỗi Xác định giao thức mã hoá hoặc ký mà theo đó thư được tạo. Điều này cho phép giao thức phát triển theo thời gian, nếu cần.
signature Chuỗi Xác minh rằng thư đến từ Google. Chữ ký này được mã hoá theo Base64 và được tạo bằng ECDSA bằng khoá ký trung gian.
intermediateSigningKey Đối tượng Đối tượng JSON chứa khoá ký trung gian của Google. Tệp này chứa signedKey có keyValue, keyExpiration và signatures. Khoá này được chuyển đổi tuần tự để đơn giản hoá quy trình xác minh chữ ký của khoá ký trung gian.
signedMessage Chuỗi Một đối tượng JSON được chuyển đổi tuần tự dưới dạng chuỗi an toàn cho HTML, chứa encryptedMessage, ephemeralPublicKey và tag. Nó được chuyển đổi tuần tự để đơn giản hoá quy trình xác minh chữ ký.

Ví dụ

Sau đây là phản hồi mã thông báo phương thức thanh toán ở định dạng JSON:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

Khoá ký trung gian

intermediateSigningKey là một đối tượng JSON được chuyển đổi tuần tự, mã hoá UTF-8, chứa các giá trị sau:

Tên Loại Mô tả
signedKey Chuỗi Thông báo được mã hoá base64 chứa nội dung mô tả về khoản thanh toán của khoá.
signatures Chuỗi Xác minh rằng khoá ký trung gian đến từ Google. Khoá này được mã hoá theo Base64 và được tạo bằng ECDSA.

Khoá đã ký

signedKey là một đối tượng JSON được chuyển đổi tuần tự, mã hoá UTF-8, chứa các giá trị sau:

Tên Loại Mô tả
keyValue Chuỗi Phiên bản base64 của khoá được mã hoá theo loại ASN.1. SubjectPublicKeyInfo được xác định trong tiêu chuẩn X.509.
keyExpiration Chuỗi Ngày và giờ khi khoá trung gian hết hạn, tính bằng mili giây theo giờ UTC kể từ thời gian bắt đầu của hệ thống. Nhà tích hợp từ chối mọi khoá đã hết hạn.

Thư đã ký

signedMessage là một đối tượng JSON được chuyển đổi tuần tự, mã hoá UTF-8, chứa các giá trị sau:

Tên Loại Mô tả
encryptedMessage Chuỗi Một thông báo được mã hoá base64 chứa thông tin thanh toán và một số trường bảo mật bổ sung.
ephemeralPublicKey Chuỗi Khoá công khai tạm thời được mã hoá dạng base64 liên kết với khoá riêng tư để mã hoá thông báo ở định dạng điểm không nén. Để biết thêm thông tin, hãy xem phần Định dạng khoá công khai mã hoá.
tag Chuỗi MAC được mã hoá base64 của encryptedMessage.

Tin nhắn đã mã hóa

encryptedMessage đã giải mã là một đối tượng JSON được chuyển đổi tuần tự, mã hoá UTF-8. JSON chứa hai cấp. Cấp bên ngoài chứa siêu dữ liệu và các trường được đưa vào để bảo mật, trong khi cấp bên trong là một đối tượng JSON khác đại diện cho thông tin xác thực thanh toán thực tế.

Để biết thêm thông tin chi tiết về encryptedMessage, hãy xem các bảng và ví dụ về đối tượng JSON sau đây:

Tên Loại Mô tả
messageExpiration Chuỗi Ngày và giờ hết hạn của thông báo, tính bằng mili giây theo giờ UTC kể từ thời gian bắt đầu của hệ thống. Nhà tích hợp nên từ chối mọi thông báo đã hết hạn.
messageId Chuỗi Mã nhận dạng duy nhất giúp xác định thông báo trong trường hợp cần thu hồi hoặc tìm thông báo vào lúc khác.
paymentMethod Chuỗi Loại thông tin xác thực thanh toán. Hiện tại, chúng tôi chỉ hỗ trợ CARD.
paymentMethodDetails Đối tượng Bản thân thông tin thanh toán. Định dạng của đối tượng này do paymentMethod xác định và được mô tả trong các bảng sau.

Thẻ

Các thuộc tính sau đây tạo nên thông tin xác thực thanh toán cho phương thức thanh toán CARD:

Tên Loại Mô tả
pan Chuỗi Số tài khoản cá nhân bị tính phí. Chuỗi này chỉ chứa chữ số.
expirationMonth Số Tháng hết hạn của thẻ, trong đó 1 là tháng 1, 2 là tháng 2, v.v.
expirationYear Số Năm hết hạn của thẻ gồm 4 chữ số, chẳng hạn như 2020.
authMethod Chuỗi Phương pháp xác thực của giao dịch bằng thẻ.
merchantTokenId Chuỗi

Mã thông báo người bán của giao dịch thẻ. Trường tuỳ chọn này chỉ được trả về nếu đáp ứng tất cả các điều kiện sau:

  • Người bán chỉ định một đối tượng MIT *TransactionInfo trong yêu cầu.
  • Người bán thêm tokenUpdateUrl vào yêu cầu.
  • Người dùng chọn một phương thức thanh toán được mã hoá (ví dụ: DPAN).

PAN_ONLY

Đoạn mã JSON sau đây là ví dụ về encryptedMessage đầy đủ cho một CARD paymentMethod có PAN_ONLY authMethod.

{
  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "PAN_ONLY",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2025
  },
  "gatewayMerchantId": "some-merchant-id",
  "messageId": "some-message-id",
  "messageExpiration": "1759309000000"
}

CRYPTOGRAM_3DS

CARD được xác thực bằng mật mã 3-D Secure, CRYPTOGRAM_3DS authMethod. Tệp này bao gồm các trường bổ sung sau:

Tên Loại Mô tả
cryptogram Chuỗi Mật mã 3-D Secure.
eciIndicator Chuỗi Chuỗi này không phải lúc nào cũng xuất hiện. Trường này chỉ trả về cho các giao dịch mã thông báo thiết bị đã xác thực trên Android (CRYPTOGRAM_3DS). Giá trị này phải được chuyển xuống quy trình xử lý thanh toán.

Đoạn mã JSON sau đây là ví dụ về encryptedMessage đầy đủ cho một CARD paymentMethod có CRYPTOGRAM_3DS authMethod:

{
  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "CRYPTOGRAM_3DS",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2025,
    "cryptogram": "AAAAAA...",
    "eciIndicator": "eci indicator",
    "merchantTokenId": "merchant-token-id",
    
  },
  
  "messageId": "some-message-id",
  "messageExpiration": "1759309000000"
}

eciIndicator

Mạng thẻ có thể cung cấp eciIndicator cho các giao dịch mã thông báo thiết bị đã xác thực (CRYPTOGRAM_3DS).

Bạn phải chuyển giá trị eciIndicator trong giao dịch uỷ quyền mà không được thay đổi hoặc cố định giá trị trong mã; nếu không, giao dịch sẽ không thành công. Bảng sau đây trình bày chi tiết các giá trị của eciIndicator.

Giá trị eciIndicator Mạng thẻ Bên chịu trách nhiệm authMethod
""(empty) Mastercard Người bán/Bên thanh toán CRYPTOGRAM_3DS
02 Mastercard Tổ chức phát hành thẻ CRYPTOGRAM_3DS
06 Mastercard Người bán/Bên thanh toán CRYPTOGRAM_3DS
05 Visa Tổ chức phát hành thẻ CRYPTOGRAM_3DS
07 Visa Người bán/Bên thanh toán CRYPTOGRAM_3DS
""(empty) Các mạng khác Người bán/Bên thanh toán CRYPTOGRAM_3DS

Mọi giá trị ECI khác cho VISA và Mastercard không có trong bảng này sẽ không được trả về.

Xác minh chữ ký

Để xác minh chữ ký, bao gồm cả chữ ký thông báo và khoá trung gian, bạn cần có các mục sau:

  • Thuật toán dùng để tạo chữ ký
  • Chuỗi byte dùng để tạo chữ ký
  • Khoá công khai tương ứng với khoá riêng tư dùng để tạo chữ ký
  • Chữ ký

Thuật toán chữ ký

Google sử dụng Giải thuật ký số đường cong elip (ECDSA) để ký các thông báo bằng các tham số sau: ECDSA qua NIST P-256 với SHA-256 làm hàm băm, như được xác định trong FIPS 186-4.

Chữ ký

Chữ ký được đưa vào cấp ngoài cùng của thư. Tệp này được mã hoá bằng base64 ở định dạng byte ASN.1. Để biết thêm thông tin về ASN.1, hãy xem Phụ lục A về Công cụ IETF. Chữ ký bao gồm các số nguyên r và s của ECDSA. Để biết thêm thông tin, hãy xem phần Thuật toán tạo chữ ký.

Sau đây là ví dụ về định dạng byte ASN.1 được chỉ định, đây là định dạng chuẩn do các phương thức triển khai ECDSA của Java Cryptography Extension (JCE) tạo ra.

ECDSA-Sig-Value :: = SEQUENCE {
 r INTEGER,
 s INTEGER
}

Cách tạo chuỗi byte cho chữ ký khoá ký trung gian

Để xác thực chữ ký của khoá ký trung gian trong mã thông báo phương thức thanh toán mẫu, hãy tạo signedStringForIntermediateSigningKeySignature bằng công thức sau:

signedStringForIntermediateSigningKeySignature =
length_of_sender_id || sender_id || length_of_protocol_version || protocol_version || length_of_signed_key || signed_key

Ký hiệu "||" có nghĩa là nối. Mỗi thành phần (sender_id, protocolVersion, signedKey) phải được mã hoá theo UTF-8. signedKey phải là chuỗi intermediateSigningKey.signedKey. Độ dài byte của mỗi thành phần là 4 byte ở định dạng little-endian.

Ví dụ

Ví dụ này sử dụng mã thông báo phương thức thanh toán mẫu sau:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

sender_id luôn là Google và protocol_version là ECv2.

Nếu sender_id là Google, thì signedString sẽ xuất hiện như trong ví dụ sau:

signedStringForIntermediateSigningKeySignature =
\x06\x00\x00\x00 || Google || | \x04\x00\x00\x00 || ECv2 || \xb5\x00\x00\x00 || {"keyExpiration":"1542323393147","keyValue":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\u003d\u003d"}

Cách xác minh chữ ký trên signedStringForIntermediateSigningKeySignature

Thuật toán xác minh ECDSA chuẩn được dùng khi chuỗi đã ký cho chữ ký khoá ký trung gian được tập hợp. Đối với giao thức ECv2, bạn cần lặp lại tất cả chữ ký trong intermediateSigningKey.signatures và thử xác thực từng chữ ký bằng khoá ký của Google chưa hết hạn trong keys.json. Nếu có ít nhất một quy trình xác thực chữ ký hoạt động, hãy coi như quy trình xác minh đã hoàn tất. Sử dụng intermediateSigningKey.signedKey.keyValue sau để xác minh signedStringForMessageSignature. Bạn nên sử dụng thư viện mật mã hiện có thay vì mã xác minh của riêng bạn.

Cách tạo chuỗi byte cho chữ ký thông báo

Để xác thực chữ ký trong mã thông báo phương thức thanh toán mẫu, hãy tạo signedStringForMessageSignature bằng công thức sau:

signedStringForMessageSignature =
length_of_sender_id || sender_id || length_of_recipient_id || recipient_id || length_of_protocolVersion || protocolVersion || length_of_signedMessage || signedMessage

Ký hiệu "||" có nghĩa là nối. Mỗi thành phần (sender_id, recipient_id, protocolVersion, signedMessage) phải được mã hoá theo UTF-8. Độ dài byte của mỗi thành phần là 4 byte ở định dạng little-endian. Khi bạn tạo chuỗi byte, đừng phân tích cú pháp hoặc sửa đổi signedMessage. Ví dụ: không thay thế \u003d bằng ký tự =.

Ví dụ

Sau đây là ví dụ về mã thông báo phương thức thanh toán:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

sender_id luôn là Google và recipient_id là merchant:merchantId. merchantId khớp với giá trị trong Bảng điều khiển Google Pay và Wallet dành cho người bán có quyền truy cập vào kênh phát hành công khai.

Nếu sender_id là Google và recipient_id là merchant:12345, thì signedString sẽ xuất hiện như trong ví dụ sau:

signedStringForMessageSignature =
\x06\x00\x00\x00 || Google || \x0e\x00\x00\x00 || merchant:12345 || | \x04\x00\x00\x00 || ECv2 || \xd2\x00\x00\x00 || {"tag":"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\u003d","ephemeralPublicKey":"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\u003d","encryptedMessage":"mKOoXwi8OavZ"}

Cách xác minh chữ ký trên signedStringForMessageSignature

Thuật toán xác minh ECDSA tiêu chuẩn được sử dụng khi chuỗi đã ký được tập hợp. intermediateSigningKey.signedKey.keyValue đã xác minh ở bước trước được dùng để xác minh signedMessage. Bạn nên sử dụng thư viện mật mã hiện có thay vì mã xác minh của riêng bạn.

Thông số kỹ thuật của lược đồ mã hoá

Google sử dụng Lược đồ mã hoá tích hợp đường cong elip (ECIES) để bảo mật mã thông báo phương thức thanh toán được trả về trong phản hồi của API Google Pay. Lược đồ mã hoá sử dụng các tham số sau:

Tham số Định nghĩa
Phương thức đóng gói khoá

ECIES-KEM, như được định nghĩa trong ISO 18033-2.

  • Đường cong elip: NIST P-256 (còn được gọi trong OpenSSL là prime256v1).
  • CheckMode, OldCofactorMode, SingleHashMode và CofactorMode là 0.
  • Định dạng điểm không được nén.
Hàm dẫn xuất khoá

Dựa trên HMAC với SHA-256 (HKDFwithSHA256).

  • Không được cung cấp muối.
  • Thông tin phải là "Google", được mã hoá bằng ASCII cho phiên bản giao thức ECv2.
  • Bạn phải lấy 256 bit cho khoá AES256 và 256 bit khác cho khoá HMAC_SHA256.
Thuật toán mã hoá đối xứng

DEM2, như được xác định trong ISO 18033-2

Thuật toán mã hoá: AES-256-CTR không có IV và không được đệm.

Thuật toán MAC HMAC_SHA256 có khoá 256 bit được lấy từ hàm dẫn xuất khoá.

Định dạng khoá công khai mã hoá

Khoá công khai mã hoá và ephemeralPublicKey được trả về trong tải trọng của Google được định dạng bằng cách biểu thị base64 của khoá ở định dạng điểm không nén. Nó bao gồm hai phần tử sau:

  • Một số ma thuật chỉ định định dạng (0x04).
  • Hai số nguyên lớn 32 byte đại diện cho toạ độ X và Y trong Đường cong elip.

Định dạng này được mô tả chi tiết hơn trong "Mật mã khoá công khai cho ngành dịch vụ tài chính: Thuật toán chữ ký số đường cong elip (ECDSA)", ANSI X9.62, 1998.

Sử dụng OpenSSL để tạo khoá công khai

Bước 1: Tạo khoá riêng tư

Ví dụ sau đây tạo một khoá riêng tư Đường cong elip phù hợp để sử dụng với NIST P-256 và ghi khoá đó vào key.pem:

openssl ecparam -name prime256v1 -genkey -noout -out key.pem

Không bắt buộc: Xem khoá riêng tư và khoá công khai

Sử dụng lệnh sau để xem cả khoá riêng tư và khoá công khai:

openssl ec -in key.pem -pubout -text -noout

Lệnh này tạo ra kết quả tương tự như sau:

read EC key
Private-Key: (256 bit)
priv:
    08:f4:ae:16:be:22:48:86:90:a6:b8:e3:72:11:cf:
    c8:3b:b6:35:71:5e:d2:f0:c1:a1:3a:4f:91:86:8a:
    f5:d7
pub:
    04:e7:68:5c:ff:bd:02:ae:3b:dd:29:c6:c2:0d:c9:
    53:56:a2:36:9b:1d:f6:f1:f6:a2:09:ea:e0:fb:43:
    b6:52:c6:6b:72:a3:f1:33:df:fa:36:90:34:fc:83:
    4a:48:77:25:48:62:4b:42:b2:ae:b9:56:84:08:0d:
    64:a1:d8:17:66
ASN1 OID: prime256v1

Bước 2: Tạo khoá công khai được mã hoá dạng Base64

Khoá riêng tư và khoá công khai được tạo trong ví dụ về bước không bắt buộc trước đó được mã hoá theo hệ thập lục phân. Để nhận khoá công khai được mã hoá dạng base64 ở định dạng điểm không nén, hãy dùng lệnh sau:

openssl ec -in key.pem -pubout -text -noout 2> /dev/null | grep "pub:" -A5 | sed 1d | xxd -r -p | base64 | paste -sd "\0" - | tr -d '\n\r ' > publicKey.txt

Lệnh này tạo ra một tệp publicKey.txt có nội dung là phiên bản base64 của khoá ở định dạng điểm không nén, tương tự như sau:

BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=

Nội dung tệp không được có thêm dấu cách hoặc ký tự trả lại vị trí đầu dòng. Để xác minh điều này, hãy chạy lệnh sau trong Linux hoặc MacOS:

od -bc publicKey.txt

Bước 3: Tạo khoá riêng tư được mã hoá base64 ở định dạng PKCS #8

Thư viện Tink yêu cầu khoá riêng tư của bạn phải được mã hoá base64 ở định dạng PKCS #8. Sử dụng lệnh sau để tạo khoá riêng tư ở định dạng này từ khoá riêng tư được tạo ở bước đầu tiên:

openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -nocrypt | base64 | paste -sd "\0" -

Lệnh này tạo ra kết quả tương tự như sau:

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n

Cách giải mã mã thông báo phương thức thanh toán

Hãy làm theo các bước sau để giải mã mã thông báo:

  1. Sử dụng khoá riêng tư của bạn và ephemeralPublicKey đã cho để lấy khoá dùng chung dài 512 bit sử dụng ECIES-KEM. Sử dụng các tham số sau:
    • Đường cong elip: NIST P-256, còn được gọi trong OpenSSL là prime256v1.
    • CheckMode, OldCofactorMode, SingleHashMode và CofactorMode là 0.
    • Hàm mã hoá: Định dạng điểm không nén.
    • Hàm dẫn xuất khoá: HKDFwithSHA256, như mô tả trong RFC 5869, với tham số sau:
      • Không được cung cấp muối. Theo RFC, giá trị này phải tương đương với một muối gồm 32 byte bằng 0.
  2. Tách khoá đã tạo thành hai khoá dài 256 bit: symmetricEncryptionKey và macKey.
  3. Xác minh rằng trường tag là MAC hợp lệ cho encryptedMessage.

    Để tạo MAC dự kiến, hãy sử dụng HMAC (RFC 5869) với hàm băm SHA256 và macKey thu được ở Bước 2.

  4. Giải mã encryptedMessage bằng chế độ AES-256-CTR và bằng những thông tin sau:

    • IV bằng 0.
    • Không có khoảng đệm.
    • symmetricEncryptionKey được lấy từ Bước 2.

Quản lý khoá

Khoá mã hoá của người bán

Người bán tạo khoá công khai theo thông số kỹ thuật được nêu trong Thông số kỹ thuật của lược đồ mã hoá.

Khoá ký gốc của Google

Google phát hành một bộ khoá công khai ký gốc hiện hợp lệ mà bạn có thể tìm nạp từ một URL công khai. Các khoá này có hiệu lực trong khoảng thời gian mà tiêu đề bộ nhớ đệm HTTP do URL trả về cho biết. Các khoá này được lưu vào bộ nhớ đệm cho đến khi hết hạn, thời gian hết hạn được xác định bằng trường keyExpiration. Khi một lần tìm nạp hết hạn, bạn nên tìm nạp lại các khoá từ URL công khai để nhận danh sách khoá hợp lệ hiện tại.

Trường hợp ngoại lệ đối với giao thức ECv2: Nếu bạn không thể tìm nạp khoá từ Google trong thời gian chạy, hãy tìm nạp keys.json từ URL chính thức của chúng tôi, lưu vào hệ thống của bạn và định kỳ làm mới theo cách thủ công. Trong trường hợp bình thường, Google sẽ phát hành khoá ký gốc mới cho ECv2 5 năm trước khi khoá có ngày hết hạn dài nhất hết hạn. Trong trường hợp khoá bị xâm phạm, Google sẽ thông báo cho tất cả người bán thông qua thông tin liên hệ được cung cấp trong cổng tự phục vụ để yêu cầu tải lại keys.json nhanh hơn. Để đảm bảo bạn không bỏ lỡ quy trình xoay vòng thông thường, những người bán chọn lưu khoá của Google trong nội dung của keys.json nên làm mới hằng năm trong quy trình xoay vòng khoá hằng năm của riêng họ.

Các khoá được cung cấp thông qua URL công khai được liên kết theo định dạng sau:

{
  "keys": [
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv2"
      "keyExpiration":"2000000000000"
    },
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv2"
      "keyExpiration":"3000000000000"
    }
  ]
}

keyValue là phiên bản base64, không được gói hoặc đệm, của khoá được mã hoá theo loại ASN.1 SubjectPublicKeyInfo được xác định trong tiêu chuẩn X.509. Trong Java, phương thức mã hoá ASN.1 được tham chiếu được biểu thị bằng lớp X509EncodedKeySpec. Bạn có thể lấy mã này bằng ECPublicKey.getEncoded().

URL cho cả môi trường thử nghiệm và môi trường thực tế được cung cấp qua các đường liên kết sau:

Xoay vòng khoá

Nếu giải mã mã thông báo phương thức thanh toán trực tiếp trên máy chủ bằng tính năng tích hợp trực tiếp, thì bạn phải xoay vòng khoá hằng năm.

Hãy hoàn tất các bước sau để xoay vòng khoá mã hoá:

  1. Sử dụng OpenSSL để tạo một cặp khoá mới.
  2. Mở Bảng điều khiển Google Pay và Wallet trong khi đăng nhập bằng Tài khoản Google mà bạn đã dùng trước đó để đăng ký làm nhà phát triển cho Google Pay.
  3. Trong thẻ Google Pay API, trong ngăn Tích hợp trực tiếp, hãy nhấp vào Quản lý bên cạnh khoá công khai hiện có của bạn. Nhấp vào Thêm khoá khác.
  4. Chọn trường nhập văn bản Khoá mã hoá công khai rồi thêm khoá công khai mới tạo được mã hoá dạng base64 ở định dạng điểm không nén.
  5. Nhấp vào Lưu khoá mã hoá.
  6. Để đảm bảo việc xoay vòng khoá diễn ra liền mạch, hãy hỗ trợ giải mã cả khoá riêng tư mới và cũ trong khi bạn chuyển đổi khoá.

    Nếu bạn sử dụng thư viện Tink để giải mã mã thông báo, hãy sử dụng mã Java sau để hỗ trợ nhiều khoá riêng tư:

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
            .addRecipientPrivateKey(newPrivateKey)
            .addRecipientPrivateKey(oldPrivateKey);

    Đảm bảo rằng mã giải mã được triển khai cho phiên bản chính thức và bạn theo dõi các lượt giải mã thành công.

  7. Thay đổi khoá công khai được dùng trong mã của bạn.

    Thay thế giá trị của thuộc tính publicKey trong thuộc tính PaymentMethodTokenizationSpecification parameters:

    const tokenizationSpecification = {
      "type": "DIRECT",
      "parameters": {
        "protocolVersion": "ECv2",
        "publicKey": "BOdoXP1aiNp.....kh3JUhiSZKHYF2Y="
      }
    }
  8. Triển khai mã từ bước 4 vào môi trường thực tế. Sau khi mã được triển khai, các giao dịch mã hoá và giải mã sẽ sử dụng cặp khoá mới.
  9. Xác nhận rằng khoá công khai cũ không còn được dùng để mã hoá bất kỳ giao dịch nào.

  10. Xoá khoá riêng tư cũ.
  11. Mở Bảng điều khiển Google Pay và Wallet trong khi đăng nhập bằng Tài khoản Google mà bạn đã dùng trước đó để đăng ký làm nhà phát triển với Google Pay.
  12. Trong thẻ Google Pay API, trong ngăn Tích hợp trực tiếp, hãy nhấp vào Quản lý bên cạnh khoá công khai hiện có. Nhấp vào Xoá bên cạnh khoá công khai cũ của bạn rồi nhấp vào Lưu khoá mã hoá.

Google sử dụng khoá được chỉ định trong thuộc tính publicKey trong đối tượng PaymentMethodTokenizationSpecification parameters, như trong ví dụ sau:

{
  "protocolVersion": "ECv2",
  "publicKey": "BOdoXP+9Aq473SnGwg3JU1..."
}

Sử dụng thư viện Tink để quản lý phản hồi đã mã hoá

Để thực hiện việc xác minh chữ ký và giải mã thông báo, hãy sử dụng thư viện Tink paymentmethodtoken. Thư viện này chỉ có trong Java. Để sử dụng, hãy hoàn tất các bước sau:

  1. Trong pom.xml, hãy thêm ứng dụng Tink paymentmethodtoken làm phần phụ thuộc:

    <dependencies>
      <!-- other dependencies ... -->
      <dependency>
        <groupId>com.google.crypto.tink</groupId>
        <artifactId>apps-paymentmethodtoken</artifactId>
        <version>1.9.1</version>  <!-- or latest version -->
      </dependency>
    </dependencies>
  2. Khi khởi động máy chủ, hãy tìm nạp trước khoá ký của Google để khoá này có sẵn trong bộ nhớ. Điều này giúp người dùng không thấy độ trễ mạng trong khi quá trình giải mã tìm nạp khoá.

    GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground();
  3. Giải mã thông báo bằng mã sau, giả sử paymentMethodToken được lưu trữ trong biến encryptedMessage và thay thế các phần in đậm theo trường hợp của bạn.

    Đối với các thử nghiệm không phải trong môi trường thực tế, hãy thay thế INSTANCE_PRODUCTION bằng INSTANCE_TEST. Nếu hoạt động tích hợp của bạn không hoạt động hoặc không có khoá mã hoá được định cấu hình, hãy thay thế [YOUR MERCHANT ID] bằng 12345678901234567890.

    • Đang hoạt động
    • Đã bật tính năng tích hợp TRỰC TIẾP
    • Đã định cấu hình khoá mã hoá

    không thay thế [YOUR MERCHANT ID].

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
        .fetchSenderVerifyingKeysWith(
            GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
        .recipientId("merchant:[YOUR MERCHANT ID]")
        // This guide applies only to protocolVersion = ECv2
        .protocolVersion("ECv2")
        // Multiple private keys can be added to support graceful
        // key rotations.
        .addRecipientPrivateKey(PrivateKey1)
        .addRecipientPrivateKey(PrivateKey2)
        .build()
        .unseal(encryptedMessage);
  4. Thay thế PrivateKey1 bằng giá trị khoá riêng tư thích hợp liên kết với giá trị khoá công khai đã đăng ký với Google trong phần Chuẩn bị khoá và đăng ký với Google. Bạn có thể thêm nhiều giá trị khoá riêng tư khác sau này khi bạn cần Xoay vòng khoá với Google. Các biến có thể là một chuỗi PKCS8 được mã hoá base64 hoặc một đối tượng ECPrivateKey. Để biết thêm thông tin về cách tạo khoá riêng tư PKCS8 được mã hoá base64, hãy xem bài viết Chuẩn bị khoá và đăng ký với Google.

  5. Nếu bạn không thể gọi máy chủ Google mỗi khi giải mã khoá, hãy giải mã bằng mã sau và thay thế các phần in đậm theo trường hợp của bạn.

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
        .addSenderVerifyingKey("ECv2 key fetched from test or production url")
        .recipientId("merchant:[YOUR MERCHANT ID]")
        // This guide applies only to protocolVersion = ECv2
        .protocolVersion("ECv2")
        // Multiple private keys can be added to support graceful
        // key rotations.
        .addRecipientPrivateKey(PrivateKey1)
        .addRecipientPrivateKey(PrivateKey2)
        .build()
        .unseal(encryptedMessage);

    Khoá hiện tại trong môi trường thực tế có hiệu lực đến ngày 14/4/2038 trong điều kiện bình thường, ngoại trừ trường hợp khoá bị xâm phạm. Trong trường hợp khoá bị xâm phạm, Google sẽ thông báo cho tất cả người bán thông qua thông tin liên hệ được cung cấp trong cổng tự phục vụ để yêu cầu tải lại keys.json nhanh hơn.

    Đoạn mã này xử lý các chi tiết bảo mật sau đây để bạn có thể tập trung vào việc sử dụng tải trọng:

    • Khoá ký của Google được tìm nạp và lưu vào bộ nhớ đệm
    • Xác minh chữ ký
    • Giải mã