การเข้ารหัสข้อมูลการชำระเงินสำหรับผู้ขาย

Google Pay API จะแสดงผลวิธีการชำระเงินในเพย์โหลด PaymentMethodToken ที่ลงชื่อและเข้ารหัสแล้ว วิธีการชำระเงินที่แสดงผลจะเป็นบัตรที่ประกอบด้วย PAN หรือบัตรที่แปลงเป็นโทเค็นซึ่งประกอบด้วย PAN ของอุปกรณ์และคริปโตแกรม

เพย์โหลดมีฟิลด์ชื่อ protocolVersion ซึ่งจะบอกผู้รับเพย์โหลดว่ามีการใช้พื้นฐานการเข้ารหัสใดและรูปแบบที่คาดไว้

คู่มือนี้จะให้ข้อมูลเกี่ยวกับวิธีสร้างคีย์สาธารณะเพื่อขอโทเค็นวิธีการชำระเงินที่เข้ารหัสและลงนามโดย Google รวมถึงรายละเอียดขั้นตอนในการยืนยันและถอดรหัสโทเค็น

คู่มือนี้ใช้กับ protocolVersion = ECv2 เท่านั้น

เนื่องจากคุณได้รับข้อมูลบัตรสำหรับชำระเงินโดยตรง โปรดตรวจสอบว่าแอปเป็นไปตามมาตรฐาน PCI DSS และเซิร์ฟเวอร์มีโครงสร้างพื้นฐานที่จำเป็นในการจัดการข้อมูลเข้าสู่ระบบการชำระเงินของผู้ใช้อย่างปลอดภัยก่อนดำเนินการต่อ

ขั้นตอนต่อไปนี้สรุปสิ่งที่ผู้ผสานรวมต้องทำเพื่อใช้เพย์โหลด ECv2 PaymentMethodToken ของ Google Pay API

  1. ดึงข้อมูลคีย์การรับรองรูทของ Google
  2. ยืนยันว่าลายเซ็นของคีย์การรับรองระดับกลางถูกต้องโดยใช้คีย์การรับรองรูทที่ยังไม่หมดอายุ
  3. ตรวจสอบว่าคีย์การรับรองระดับกลางของเพย์โหลดยังไม่หมดอายุ
  4. ยืนยันว่าลายเซ็นของเพย์โหลดถูกต้องตามคีย์การลงนามระดับกลาง
  5. ถอดรหัสเนื้อหาของเพย์โหลดหลังจากยืนยันลายเซ็นแล้ว
  6. ตรวจสอบว่าข้อความยังไม่หมดอายุ ซึ่งกำหนดให้คุณต้องตรวจสอบว่าเวลาปัจจุบันน้อยกว่าฟิลด์ messageExpiration ในเนื้อหาที่ถอดรหัสแล้ว
  7. ใช้วิธีการชำระเงินในเนื้อหาที่ถอดรหัสแล้วและเรียกเก็บเงิน

โค้ดตัวอย่างในไลบรารี Tink จะทำตามขั้นตอนที่ 1-6

โครงสร้างโทเค็นของวิธีการชำระเงิน

ข้อความที่ Google แสดงในการตอบกลับ PaymentData เป็นออบเจ็กต์ JSON ที่เข้ารหัส UTF-8 และเรียงลำดับแล้ว โดยมีคีย์ที่ระบุไว้ในตารางต่อไปนี้

ชื่อ ประเภท คำอธิบาย
protocolVersion สตริง ระบุรูปแบบการเข้ารหัสหรือการลงนามที่ใช้สร้างข้อความ ซึ่งช่วยให้โปรโตคอลพัฒนาขึ้นเมื่อเวลาผ่านไป หากจำเป็น
signature สตริง ยืนยันว่าข้อความมาจาก Google ซึ่งเข้ารหัส base64 และสร้างด้วย ECDSA โดยคีย์การรับรองระดับกลาง
intermediateSigningKey วัตถุ ออบเจ็กต์ JSON ที่มีคีย์การรับรองระดับกลางจาก Google โดยมี signedKey พร้อมด้วย keyValue, keyExpiration และ signatures ซึ่งจะได้รับการจัดเรียงเป็นอนุกรมเพื่อลดความซับซ้อนของกระบวนการยืนยันลายเซ็นคีย์การรับรองระดับกลาง
signedMessage สตริง ออบเจ็กต์ JSON ที่เรียงลำดับเป็นสตริงที่ปลอดภัยสำหรับ HTML ซึ่งมี encryptedMessage, ephemeralPublicKey และ tag ซึ่งจะได้รับการจัดเรียงเป็นอนุกรมเพื่อลดความซับซ้อนของกระบวนการยืนยันลายเซ็น

ตัวอย่าง

ต่อไปนี้คือการตอบกลับโทเค็นวิธีการชำระเงินใน 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\"}"
}

คีย์ Signing ระดับกลาง

intermediateSigningKey คือออบเจ็กต์ JSON ที่เข้ารหัส UTF-8 และเรียงลำดับแล้ว ซึ่งมีค่าต่อไปนี้

ชื่อ ประเภท คำอธิบาย
signedKey สตริง ข้อความที่เข้ารหัส Base64 ซึ่งมีคำอธิบายการชำระเงินของคีย์
signatures สตริง ยืนยันว่าคีย์การรับรองระดับกลางมาจาก Google ซึ่งเข้ารหัส Base64 และสร้างด้วย ECDSA

คีย์ที่ลงนามแล้ว

signedKey คือออบเจ็กต์ JSON ที่เข้ารหัส UTF-8 และเรียงลำดับแล้ว ซึ่งมีค่าต่อไปนี้

ชื่อ ประเภท คำอธิบาย
keyValue สตริง คีย์เวอร์ชัน base64 ที่เข้ารหัสในประเภท ASN.1 SubjectPublicKeyInfo กำหนดไว้ในมาตรฐาน X.509
keyExpiration สตริง วันที่และเวลาที่คีย์ระดับกลางจะหมดอายุเป็นมิลลิวินาที UTC นับตั้งแต่ Epoch ผู้ผสานรวมจะปฏิเสธคีย์ที่หมดอายุ

ข้อความที่ลงชื่อกำกับ

signedMessage คือออบเจ็กต์ JSON ที่เข้ารหัส UTF-8 และเรียงลำดับแล้ว ซึ่งมีค่าต่อไปนี้

ชื่อ ประเภท คำอธิบาย
encryptedMessage สตริง ข้อความที่เข้ารหัสแบบ Base64 ซึ่งมีข้อมูลการชำระเงินและช่องความปลอดภัยเพิ่มเติมบางช่อง
ephemeralPublicKey สตริง คีย์สาธารณะชั่วคราวที่เข้ารหัส Base64 ซึ่งเชื่อมโยงกับคีย์ส่วนตัวเพื่อเข้ารหัสข้อความในรูปแบบจุดที่ไม่มีการบีบอัด ดูข้อมูลเพิ่มเติมได้ที่รูปแบบคีย์สาธารณะสำหรับการเข้ารหัส
tag สตริง MAC ที่เข้ารหัส Base64 ของ encryptedMessage

ข้อความที่เข้ารหัส

encryptedMessage ที่ถอดรหัสแล้วคือออบเจ็กต์ JSON ที่เข้ารหัสแบบ UTF-8 และแปลงเป็นอนุกรม JSON มี 2 ระดับ ระดับภายนอกประกอบด้วยข้อมูลเมตาและช่องที่รวมไว้เพื่อความปลอดภัย ส่วนระดับภายในเป็นออบเจ็กต์ JSON อีกรายการหนึ่งที่แสดงข้อมูลเข้าสู่ระบบการชำระเงินจริง

ดูรายละเอียดเพิ่มเติมเกี่ยวกับ encryptedMessage ได้ในตารางและตัวอย่างออบเจ็กต์ JSON ต่อไปนี้

ชื่อ ประเภท คำอธิบาย
messageExpiration สตริง วันที่และเวลาที่ข้อความหมดอายุเป็นมิลลิวินาที UTC นับตั้งแต่ Epoch ผู้ผสานรวมควรปฏิเสธข้อความที่หมดอายุ
messageId สตริง รหัสที่ไม่ซ้ำกันซึ่งระบุข้อความในกรณีที่ต้องเพิกถอนหรือค้นหาในภายหลัง
paymentMethod สตริง ประเภทของข้อมูลเข้าสู่ระบบสำหรับการชำระเงิน ปัจจุบันรองรับเฉพาะ CARD เท่านั้น
paymentMethodDetails วัตถุ ข้อมูลเข้าสู่ระบบสำหรับการชำระเงิน รูปแบบของออบเจ็กต์นี้จะกำหนดโดย paymentMethod และอธิบายไว้ในตารางต่อไปนี้

การ์ด

พร็อพเพอร์ตี้ต่อไปนี้ประกอบขึ้นเป็นข้อมูลเข้าสู่ระบบสำหรับการชำระเงินด้วยวิธีการชำระเงิน CARD

ชื่อ ประเภท คำอธิบาย
pan สตริง หมายเลขบัญชีส่วนตัวที่เรียกเก็บเงิน สตริงนี้มีเฉพาะตัวเลขเท่านั้น
expirationMonth ตัวเลข เดือนที่บัตรหมดอายุ โดย 1 แทนเดือนมกราคม 2 แทนเดือนกุมภาพันธ์ และอื่นๆ
expirationYear ตัวเลข ปีที่หมดอายุของบัตรซึ่งเป็นตัวเลข 4 หลัก เช่น 2020
authMethod สตริง วิธีการตรวจสอบสิทธิ์ของธุรกรรมบัตร
merchantTokenId สตริง

รหัสโทเค็นของผู้ขายของธุรกรรมบัตร ระบบจะแสดงช่องที่ไม่บังคับนี้ก็ต่อเมื่อเป็นไปตามเงื่อนไขต่อไปนี้ทั้งหมด

  • ผู้ขายระบุออบเจ็กต์ MIT *TransactionInfo ในคำขอ
  • ผู้ขายระบุ tokenUpdateUrl ในคำขอ
  • ผู้ใช้เลือกเครื่องมือที่แปลงเป็นโทเค็น (เช่น DPAN)

PAN_ONLY

ข้อมูลโค้ด JSON ต่อไปนี้เป็นตัวอย่างของ encryptedMessage แบบเต็มสำหรับ CARD paymentMethod ที่มี 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 ได้รับการตรวจสอบสิทธิ์โดยใช้คริปโตแกรม 3-D Secure CRYPTOGRAM_3DS authMethod โดยจะมีช่องเพิ่มเติมต่อไปนี้

ชื่อ ประเภท คำอธิบาย
cryptogram สตริง คริปโตแกรม 3-D Secure
eciIndicator สตริง สตริงนี้ไม่ได้แสดงอยู่เสมอ โดยจะแสดงผลเฉพาะธุรกรรมโทเค็นของอุปกรณ์ที่ตรวจสอบสิทธิ์แล้วใน Android (CRYPTOGRAM_3DS) เท่านั้น ค่านี้ต้องส่งผ่านขั้นตอนการประมวลผลการชำระเงิน

ข้อมูลโค้ด JSON ต่อไปนี้เป็นตัวอย่างของ encryptedMessage แบบเต็มสำหรับ paymentMethod CARD ที่มี authMethod CRYPTOGRAM_3DS

{
  "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

เครือข่ายบัตรอาจระบุ eciIndicator สำหรับธุรกรรมโทเค็นอุปกรณ์ที่ตรวจสอบสิทธิ์แล้ว (CRYPTOGRAM_3DS)

คุณต้องส่งค่า eciIndicator ในธุรกรรมการให้สิทธิ์โดยไม่มีการแก้ไขหรือฮาร์ดโค้ด มิฉะนั้นธุรกรรมจะดำเนินการไม่สำเร็จ ตารางต่อไปนี้แสดงรายละเอียดค่าของ eciIndicator

ค่า eciIndicator เครือข่ายบัตร ฝ่ายที่รับผิดชอบ authMethod
""(empty) Mastercard ผู้ขาย/ผู้รับบัตร CRYPTOGRAM_3DS
02 Mastercard ผู้ออกบัตร CRYPTOGRAM_3DS
06 Mastercard ผู้ขาย/ผู้รับบัตร CRYPTOGRAM_3DS
05 Visa ผู้ออกบัตร CRYPTOGRAM_3DS
07 Visa ผู้ขาย/ผู้รับบัตร CRYPTOGRAM_3DS
""(empty) เครือข่ายอื่น ผู้ขาย/ผู้รับบัตร CRYPTOGRAM_3DS

ระบบจะไม่แสดงค่า ECI อื่นๆ สำหรับ VISA และ Mastercard ที่ไม่อยู่ในตารางนี้

การยืนยันลายเซ็น

หากต้องการยืนยันลายเซ็น ซึ่งรวมถึงคีย์ระดับกลางและลายเซ็นข้อความ คุณต้องมีรายการต่อไปนี้

  • อัลกอริทึมที่ใช้สร้างลายเซ็น
  • สตริงไบต์ที่ใช้สร้างลายเซ็น
  • คีย์สาธารณะที่สอดคล้องกับคีย์ส่วนตัวที่ใช้สร้างลายเซ็น
  • ลายเซ็น

อัลกอริทึมลายเซ็น

Google ใช้อัลกอริทึมของลายเซ็นดิจิทัลแบบ Elliptic Curve (ECDSA) เพื่อลงนามข้อความด้วยพารามิเตอร์ต่อไปนี้ ECDSA ผ่าน NIST P-256 โดยมี SHA-256 เป็นฟังก์ชันแฮชตามที่กำหนดไว้ใน FIPS 186-4

ลายเซ็น

ลายเซ็นจะรวมอยู่ในระดับนอกสุดของข้อความ โดยจะเข้ารหัสด้วย base64 ในรูปแบบไบต์ ASN.1 ดูข้อมูลเพิ่มเติมเกี่ยวกับ ASN.1 ได้ที่ภาคผนวก A ของเครื่องมือ IETF ลายเซ็นประกอบด้วยจำนวนเต็ม r และ s ของ ECDSA ดูข้อมูลเพิ่มเติมได้ที่อัลกอริทึมการสร้างลายเซ็น

ต่อไปนี้เป็นตัวอย่างของรูปแบบไบต์ ASN.1 ที่ระบุ ซึ่งเป็นรูปแบบมาตรฐานที่สร้างโดยการใช้งาน ECDSA ของ Java Cryptography Extension (JCE)

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

วิธีสร้างสตริงไบต์สำหรับลายเซ็นคีย์การรับรองระดับกลาง

หากต้องการตรวจสอบลายเซ็นคีย์การรับรองระดับกลางในโทเค็นวิธีการชำระเงินตัวอย่าง ให้สร้าง signedStringForIntermediateSigningKeySignature ด้วยสูตรต่อไปนี้

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

สัญกรณ์ "||" หมายถึงการเชื่อมโยง แต่ละคอมโพเนนต์ ได้แก่ sender_id, protocolVersion, signedKey ต้องเข้ารหัส UTF-8 signedKey ต้องเป็นสตริงของ intermediateSigningKey.signedKey ความยาวไบต์ของแต่ละคอมโพเนนต์คือ 4 ไบต์ในรูปแบบ Little-Endian

ตัวอย่าง

ตัวอย่างนี้ใช้โทเค็นวิธีการชำระเงินตัวอย่างต่อไปนี้

{
  "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 จะเป็น Google เสมอ และ protocol_version จะเป็น ECv2

หาก sender_id เป็น Google signedString จะปรากฏดังตัวอย่างต่อไปนี้

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

วิธียืนยันลายเซ็นใน signedStringForIntermediateSigningKeySignature

ระบบจะใช้อัลกอริทึมการยืนยัน ECDSA มาตรฐานเมื่อประกอบสตริงที่ลงนามสำหรับลายเซ็นคีย์การลงนามระดับกลาง สำหรับโปรโตคอล ECv2 คุณต้องทำซ้ำลายเซ็นทั้งหมดใน intermediateSigningKey.signatures และพยายามตรวจสอบลายเซ็นแต่ละรายการด้วยคีย์การรับรองของ Google ที่ยังไม่หมดอายุใน keys.json หากการตรวจสอบลายเซ็นอย่างน้อย 1 รายการทำงานได้ ให้ถือว่าการยืนยันเสร็จสมบูรณ์ ใช้ intermediateSigningKey.signedKey.keyValue ในภายหลังเพื่อยืนยัน signedStringForMessageSignature Google ขอแนะนำอย่างยิ่งให้คุณใช้ไลบรารีการเข้ารหัสที่มีอยู่แทนรหัสยืนยันของคุณเอง

วิธีสร้างสตริงไบต์สำหรับลายเซ็นข้อความ

หากต้องการตรวจสอบลายเซ็นในโทเค็นวิธีการชำระเงินตัวอย่าง ให้สร้าง signedStringForMessageSignature ด้วยสูตรต่อไปนี้

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

สัญกรณ์ "||" หมายถึงการเชื่อมโยง แต่ละคอมโพเนนต์ ได้แก่ sender_id, recipient_id, protocolVersion, signedMessage ต้องเข้ารหัส UTF-8 ความยาวไบต์ของแต่ละคอมโพเนนต์คือ 4 ไบต์ในรูปแบบ Little-Endian เมื่อสร้างสตริงไบต์ อย่าแยกวิเคราะห์หรือแก้ไข signedMessage เช่น อย่าแทนที่ \u003d ด้วยอักขระ =

ตัวอย่าง

ตัวอย่างต่อไปนี้คือโทเค็นวิธีการชำระเงินตัวอย่าง

{
  "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 จะเป็น Google เสมอ และ recipient_id จะเป็น merchant:merchantId merchantId ตรงกับค่าที่พบใน Google Pay & Wallet Console สำหรับผู้ขายที่มีสิทธิ์เข้าถึงเวอร์ชันที่ใช้งานจริง

หาก sender_id คือ Google และ recipient_id คือ merchant:12345 signedString จะปรากฏตามตัวอย่างต่อไปนี้

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"}

วิธียืนยันลายเซ็นใน signedStringForMessageSignature

ระบบจะใช้อัลกอริทึมการยืนยัน ECDSA มาตรฐานเมื่อประกอบสตริงที่ลงชื่อแล้ว ระบบจะใช้ intermediateSigningKey.signedKey.keyValue ที่ยืนยันแล้วในขั้นตอนก่อนหน้าเพื่อยืนยัน signedMessage Google ขอแนะนำอย่างยิ่งให้คุณใช้ไลบรารีการเข้ารหัสที่มีอยู่แทนรหัสยืนยันของคุณเอง

ข้อกำหนดรูปแบบการเข้ารหัส

Google ใช้ Elliptic Curve Integrated Encryption Scheme (ECIES) เพื่อรักษาความปลอดภัยของโทเค็นวิธีการชำระเงินที่แสดงในการตอบกลับจาก API ของ Google Pay รูปแบบการเข้ารหัสใช้พารามิเตอร์ต่อไปนี้

พารามิเตอร์ คำจำกัดความ
วิธีการห่อหุ้มคีย์

ECIES-KEM ตามที่กำหนดไว้ใน ISO 18033-2

  • เส้นโค้งวงรี: NIST P-256 (หรือที่เรียกว่า prime256v1 ใน OpenSSL)
  • CheckMode, OldCofactorMode, SingleHashMode และ CofactorMode เป็น 0
  • รูปแบบจุดไม่มีการบีบอัด
ฟังก์ชันการรับคีย์

HMAC-based with SHA-256 (HKDFwithSHA256)

  • ต้องไม่ระบุ Salt
  • ข้อมูลต้องเป็น "Google" ซึ่งเข้ารหัสเป็น ASCII สำหรับเวอร์ชันโปรโตคอล ECv2
  • ต้องได้รับ 256 บิตสำหรับคีย์ AES256 และต้องได้รับอีก 256 บิตสำหรับคีย์ HMAC_SHA256
อัลกอริทึมการเข้ารหัสแบบสมมาตร

DEM2 ตามที่กำหนดไว้ใน ISO 18033-2

อัลกอริทึมการเข้ารหัส: AES-256-CTR ที่มี IV เป็น 0 และไม่มีการเติม

อัลกอริทึม MAC HMAC_SHA256 พร้อมคีย์ 256 บิตที่ได้มาจากฟังก์ชันการรับคีย์

รูปแบบคีย์สาธารณะการเข้ารหัส

คีย์สาธารณะสำหรับการเข้ารหัสและ ephemeralPublicKey ที่แสดงในเพย์โหลดของ Google จะอยู่ในรูปแบบการแสดงคีย์ base64 ในรูปแบบจุดที่ไม่มีการบีบอัด ซึ่งประกอบด้วย 2 องค์ประกอบต่อไปนี้

  • หมายเลขวิเศษ 1 หมายเลขที่ระบุรูปแบบ (0x04)
  • จำนวนเต็มขนาดใหญ่ 32 ไบต์ 2 จำนวนที่แสดงพิกัด X และ Y ในเส้นโค้งวงรี

รูปแบบนี้อธิบายไว้อย่างละเอียดใน "วิทยาการเข้ารหัสคีย์สาธารณะสำหรับอุตสาหกรรมบริการทางการเงิน: อัลกอริทึมของลายเซ็นดิจิทัลแบบเส้นโค้งวงรี (ECDSA)" ANSI X9.62, 1998

ใช้ OpenSSL เพื่อสร้างคีย์สาธารณะ

ขั้นตอนที่ 1: สร้างคีย์ส่วนตัว

ตัวอย่างต่อไปนี้จะสร้างคีย์ส่วนตัว Elliptic Curve ที่เหมาะสำหรับใช้กับ NIST P-256 และเขียนลงใน key.pem

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

ไม่บังคับ: ดูคีย์ส่วนตัวและคีย์สาธารณะ

ใช้คำสั่งต่อไปนี้เพื่อดูทั้งคีย์ส่วนตัวและคีย์สาธารณะ

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

คำสั่งจะสร้างเอาต์พุตที่คล้ายกับต่อไปนี้

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

ขั้นตอนที่ 2: สร้างคีย์สาธารณะที่เข้ารหัส Base64

คีย์ส่วนตัวและคีย์สาธารณะที่สร้างขึ้นในตัวอย่างขั้นตอนที่ไม่บังคับก่อนหน้านี้จะเข้ารหัสเป็นเลขฐาน 16 หากต้องการรับคีย์สาธารณะที่เข้ารหัส Base64 ในรูปแบบจุดที่ไม่มีการบีบอัด ให้ใช้คำสั่งต่อไปนี้

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

คำสั่งจะสร้างไฟล์ publicKey.txt ซึ่งมีเนื้อหาเป็นคีย์เวอร์ชัน base64 ในรูปแบบจุดที่ไม่มีการบีบอัด โดยมีลักษณะดังนี้

BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=

เนื้อหาไฟล์ต้องไม่มีการเว้นวรรคหรืออักขระขึ้นบรรทัดใหม่เกินมา หากต้องการยืนยัน ให้เรียกใช้คำสั่งต่อไปนี้ใน Linux หรือ MacOS

od -bc publicKey.txt

ขั้นตอนที่ 3: สร้างคีย์ส่วนตัวที่เข้ารหัส Base64 ในรูปแบบ PKCS #8

ไลบรารี Tink คาดหวังให้คีย์ส่วนตัวของคุณเข้ารหัสฐาน 64 ในรูปแบบ PKCS #8 ใช้คำสั่งต่อไปนี้เพื่อสร้างคีย์ส่วนตัวในรูปแบบนี้จากคีย์ส่วนตัวที่สร้างขึ้นในขั้นตอนแรก

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

คำสั่งจะสร้างเอาต์พุตที่คล้ายกับต่อไปนี้

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n

วิธีถอดรหัสโทเค็นวิธีการชำระเงิน

ทำตามขั้นตอนต่อไปนี้เพื่อถอดรหัสโทเค็น

  1. ใช้คีย์ส่วนตัวและ ephemeralPublicKey ที่ระบุเพื่อรับคีย์ที่ใช้ร่วมกันแบบ 512 บิตซึ่งใช้ ECIES-KEM ใช้พารามิเตอร์ต่อไปนี้
    • Elliptic curve: NIST P-256 หรือที่เรียกว่า prime256v1 ใน OpenSSL
    • CheckMode, OldCofactorMode, SingleHashMode และ CofactorMode เป็น0
    • ฟังก์ชันการเข้ารหัส: รูปแบบจุดที่ไม่มีการบีบอัด
    • ฟังก์ชันการสืบทอดคีย์: HKDFwithSHA256 ตามที่อธิบายไว้ใน RFC 5869 โดยมีพารามิเตอร์ต่อไปนี้
      • ต้องไม่ระบุ Salt ตาม RFC ค่านี้ต้องเทียบเท่ากับ Salt ที่มีไบต์เป็นศูนย์ 32 ไบต์
  2. แยกคีย์ที่สร้างขึ้นออกเป็นคีย์ยาว 256 บิต 2 คีย์ ได้แก่ symmetricEncryptionKey และ macKey
  3. ตรวจสอบว่าช่อง tag เป็น MAC ที่ถูกต้องสำหรับ encryptedMessage

    หากต้องการสร้าง MAC ที่คาดไว้ ให้ใช้ HMAC (RFC 5869) กับฟังก์ชันแฮช SHA256 และ macKey ที่ได้รับในขั้นตอนที่ 2

  4. ถอดรหัส encryptedMessage โดยใช้โหมด AES-256-CTR และใช้สิ่งต่อไปนี้

    • IV เป็น 0
    • ไม่มีการเว้นระยะห่างจากขอบ
    • symmetricEncryptionKey ที่ได้มาจากขั้นตอนที่ 2

การจัดการคีย์

คีย์การเข้ารหัสของผู้ขาย

ผู้ขายจะสร้างคีย์สาธารณะตามข้อกำหนดที่ระบุไว้ในข้อกำหนดของรูปแบบการเข้ารหัส

คีย์การรับรองรูทของ Google

Google เผยแพร่ชุดคีย์สาธารณะสำหรับการลงนามรูทที่ถูกต้องในปัจจุบันซึ่งดึงข้อมูลได้จาก URL สาธารณะ คีย์จะใช้ได้นานเท่าที่ส่วนหัวของแคช HTTP ที่ URL แสดงผลระบุไว้ ระบบจะแคชคีย์ไว้จนกว่าจะหมดอายุ ซึ่งกำหนดโดยช่อง keyExpiration เราขอแนะนำว่าเมื่อการดึงข้อมูลหมดอายุ ให้ดึงข้อมูลคีย์จาก URL สาธารณะอีกครั้งเพื่อรับรายการคีย์ที่ถูกต้องในปัจจุบัน

ข้อยกเว้นสำหรับโปรโตคอล ECv2: หากคุณไม่สามารถดึงข้อมูลคีย์จาก Google ขณะรันไทม์ ให้ดึงข้อมูล keys.json จาก URL เวอร์ชันที่ใช้งานจริงของเรา บันทึกลงในระบบ และรีเฟรชด้วยตนเองเป็นระยะๆ ในสถานการณ์ปกติ Google จะออกคีย์การรับรองรูทใหม่สำหรับ ECv2 เป็นเวลา 5 ปีก่อนที่คีย์ที่มีวันที่หมดอายุนานที่สุดจะหมดอายุ ในกรณีที่คีย์ถูกบุกรุก Google จะแจ้งให้ผู้ขายทุกรายทราบผ่านข้อมูลติดต่อที่ระบุไว้ในพอร์ทัลแบบบริการตนเองเพื่อขอให้โหลด keys.json ซ้ำได้เร็วขึ้น เพื่อให้แน่ใจว่าคุณจะไม่พลาดการหมุนเวียนคีย์ตามปกติ เราขอแนะนำให้ผู้ขายที่เลือกบันทึกคีย์ Google ไว้ในเนื้อหาของ keys.json รีเฟรชคีย์เป็นประจำทุกปีโดยเป็นส่วนหนึ่งของการหมุนเวียนคีย์ประจำปีของตนเอง

คีย์ที่ระบุผ่าน URL สาธารณะจะแมปในรูปแบบต่อไปนี้

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

keyValue คือเวอร์ชัน base64 ที่ไม่ได้รวมหรือเสริมของคีย์ที่เข้ารหัสในประเภท ASN.1 SubjectPublicKeyInfo ซึ่งกำหนดไว้ในมาตรฐาน X.509 ใน Java การเข้ารหัส ASN.1 ที่อ้างอิงจะแสดงด้วยคลาส X509EncodedKeySpec คุณสามารถรับได้ด้วย ECPublicKey.getEncoded()

URL สำหรับทั้งสภาพแวดล้อมการทดสอบและสภาพแวดล้อมฮาร์ดแวร์และซอฟต์แวร์มีอยู่ในลิงก์ต่อไปนี้

การหมุนเวียนคีย์

หากคุณถอดรหัสโทเค็นวิธีการชำระเงินโดยตรงในเซิร์ฟเวอร์ด้วยการผสานรวมโดยตรง คุณต้องหมุนเวียนคีย์เป็นประจำทุกปี

ทำตามขั้นตอนต่อไปนี้เพื่อหมุนเวียนคีย์การเข้ารหัส

  1. ใช้ OpenSSL เพื่อสร้างคู่คีย์ใหม่
  2. เปิด Google Pay & Wallet Console ขณะลงชื่อเข้าใช้ด้วยบัญชี Google ที่คุณใช้ลงชื่อสมัครเป็นนักพัฒนาแอปสำหรับ Google Pay ก่อนหน้านี้
  3. ในแท็บ Google Pay API ในแผงการผสานรวมโดยตรง ให้คลิกจัดการข้างคีย์สาธารณะที่มีอยู่ คลิกเพิ่มคีย์อื่น
  4. เลือกช่องป้อนข้อความคีย์การเข้ารหัสสาธารณะ แล้วเพิ่มคีย์สาธารณะที่สร้างขึ้นใหม่ซึ่งเข้ารหัส Base64 ในรูปแบบจุดที่ไม่มีการบีบอัด
  5. คลิกบันทึกคีย์การเข้ารหัส
  6. เพื่อรับประกันการหมุนเวียนคีย์ที่ราบรื่น ให้รองรับทั้งการถอดรหัสคีย์ส่วนตัวใหม่และเก่าขณะเปลี่ยนคีย์

    หากใช้ไลบรารี Tink เพื่อถอดรหัสโทเค็น ให้ใช้โค้ด Java ต่อไปนี้เพื่อรองรับคีย์ส่วนตัวหลายรายการ

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

    ตรวจสอบว่าได้ติดตั้งใช้งานโค้ดสำหรับการถอดรหัสในเวอร์ชันที่ใช้งานจริงแล้ว และคุณตรวจสอบการถอดรหัสที่สำเร็จ

  7. เปลี่ยนคีย์สาธารณะที่ใช้ในโค้ด

    แทนที่ค่าของแอตทริบิวต์ publicKey ในพร็อพเพอร์ตี้ PaymentMethodTokenizationSpecification parameters ดังนี้

    const tokenizationSpecification = {
      "type": "DIRECT",
      "parameters": {
        "protocolVersion": "ECv2",
        "publicKey": "BOdoXP1aiNp.....kh3JUhiSZKHYF2Y="
      }
    }
  8. ทำให้โค้ดจากขั้นตอนที่ 4 ใช้งานได้จริง เมื่อติดตั้งใช้งานโค้ดแล้ว ธุรกรรมการเข้ารหัสและการถอดรหัสจะใช้คู่คีย์ใหม่
  9. ยืนยันว่าไม่มีการใช้คีย์สาธารณะเก่าเพื่อเข้ารหัสธุรกรรมอีกต่อไป

  10. นำคีย์ส่วนตัวเก่าออก
  11. เปิด Google Pay & Wallet Console ขณะลงชื่อเข้าใช้ด้วยบัญชี Google ที่คุณเคยใช้ลงชื่อสมัครเป็นนักพัฒนาแอปกับ Google Pay
  12. ในแท็บ Google Pay API ในแผงการผสานรวมโดยตรง ให้คลิกจัดการข้างคีย์สาธารณะที่มีอยู่ คลิกลบข้างคีย์สาธารณะเก่า แล้วคลิกบันทึกคีย์การเข้ารหัส

Google จะใช้คีย์ที่ระบุในพร็อพเพอร์ตี้ publicKey ภายในออบเจ็กต์ PaymentMethodTokenizationSpecification parameters ดังที่แสดงในตัวอย่างต่อไปนี้

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

ใช้ไลบรารี Tink เพื่อจัดการการตอบกลับที่เข้ารหัส

หากต้องการตรวจสอบลายเซ็นและถอดรหัสข้อความ ให้ใช้ไลบรารี paymentmethodtoken ของ Tink ไลบรารีนี้ใช้ได้เฉพาะใน Java เท่านั้น หากต้องการใช้ ให้ทำตามขั้นตอนต่อไปนี้

  1. ใน pom.xml ให้เพิ่มแอป Tink paymentmethodtoken เป็นทรัพยากร Dependency ดังนี้

    <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. เมื่อเริ่มต้นเซิร์ฟเวอร์ ให้ดึงข้อมูลคีย์การรับรองของ Google ล่วงหน้าเพื่อให้คีย์พร้อมใช้งานในหน่วยความจำ ซึ่งจะช่วยป้องกันไม่ให้ผู้ใช้เห็นเวลาในการตอบสนองของเครือข่ายขณะที่กระบวนการถอดรหัสกำลังดึงข้อมูลคีย์

    GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground();
  3. ถอดรหัสข้อความด้วยโค้ดต่อไปนี้ ซึ่งถือว่า paymentMethodToken จัดเก็บอยู่ในตัวแปร encryptedMessage และแทนที่ส่วนที่เป็นตัวหนาตามสถานการณ์ของคุณ

    สำหรับการทดสอบที่ไม่ใช่เวอร์ชันที่ใช้งานจริง ให้แทนที่ INSTANCE_PRODUCTION ด้วย INSTANCE_TEST และหากการผสานรวมของคุณไม่ได้ใช้งานหรือไม่ได้กำหนดค่าคีย์การเข้ารหัสไว้ ให้แทนที่ [YOUR MERCHANT ID] ด้วย 12345678901234567890

    • ใช้งานอยู่
    • เปิดใช้การผสานรวมโดยตรงแล้ว
    • มีการกำหนดค่าคีย์การเข้ารหัส

    อย่าแทนที่ [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. แทนที่ PrivateKey1 ด้วยค่าคีย์ส่วนตัวที่เหมาะสมซึ่งเชื่อมโยงกับค่าคีย์สาธารณะที่ลงทะเบียนไว้กับ Google จากหัวข้อเตรียมคีย์และลงทะเบียนกับ Google คุณสามารถเพิ่มค่าคีย์ส่วนตัวอื่นๆ ได้ในภายหลังเมื่อระบบกำหนดให้คุณหมุนเวียนคีย์กับ Google ตัวแปรอาจเป็นสตริง PKCS8 ที่เข้ารหัส base64 หรือออบเจ็กต์ ECPrivateKey ก็ได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีสร้างคีย์ส่วนตัว PKCS8 ที่เข้ารหัส Base64 ได้ที่เตรียมคีย์และลงทะเบียนกับ Google

  5. หากไม่สามารถเรียกใช้เซิร์ฟเวอร์ Google ทุกครั้งที่ถอดรหัสคีย์ ให้ถอดรหัสด้วยโค้ดต่อไปนี้และแทนที่ส่วนที่เป็นตัวหนาตามสถานการณ์ของคุณ

    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);

    คีย์ปัจจุบันในสภาพแวดล้อมฮาร์ดแวร์และซอฟต์แวร์จะใช้ได้จนถึงวันที่ 14/04/2038 ภายใต้สถานการณ์ปกติ ยกเว้นกรณีที่คีย์ถูกบุกรุก ในกรณีที่คีย์ถูกบุกรุก Google จะแจ้งให้ผู้ขายทั้งหมดทราบผ่านข้อมูลติดต่อที่ระบุไว้ในพอร์ทัลแบบบริการตนเองเพื่อขอให้โหลด keys.json ซ้ำได้เร็วขึ้น

    ข้อมูลโค้ดจะจัดการรายละเอียดด้านความปลอดภัยต่อไปนี้เพื่อให้คุณมุ่งเน้นที่การใช้เพย์โหลดได้

    • ดึงข้อมูลคีย์ Signing ของ Google และแคชไว้ในหน่วยความจำแล้ว
    • การยืนยันลายเซ็น
    • การถอดรหัส