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

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

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

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

คำแนะนำนี้ใช้กับ protocolVersion = ECv2 เท่านั้น

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

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

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

คีย์การลงชื่อระดับกลาง

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 สตริง วิธีการตรวจสอบสิทธิ์ของธุรกรรมในบัตร

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 แบบเต็มสำหรับ CARD paymentMethod ที่มี CRYPTOGRAM_3DS authMethod

{
  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "CRYPTOGRAM_3DS",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2025,
    "cryptogram": "AAAAAA...",
    "eciIndicator": "eci indicator"
    
  },
  
  "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 Digital Signature Algorithm (ECDSA) เพื่อลงนามข้อความที่มีพารามิเตอร์ต่อไปนี้ ECDSA ผ่าน NIST P-256 ที่มี SHA-256 เป็นฟังก์ชันแฮช ตามที่กำหนดไว้ใน FIPS 186-4

ลายเซ็น

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

หาก 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 ใช้รูปแบบการเข้ารหัสแบบผสานรวมของเส้นโค้งวงรี (ECIES) เพื่อรักษาความปลอดภัย โทเค็นวิธีการชำระเงินที่ส่งคืนในการตอบกลับของ Google Pay API รูปแบบการเข้ารหัสใช้พารามิเตอร์ต่อไปนี้

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

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

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

อิงตาม HMAC ที่มี SHA-256 (HKDFwithSHA256)

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

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

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

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

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

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

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

รูปแบบนี้อธิบายไว้โดยละเอียดใน "Public Key Cryptography For The Financial Services Industry: The Elliptic Curve Digital Signature Algorithm (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

คีย์ส่วนตัวและคีย์สาธารณะที่สร้างขึ้นในขั้นตอนที่ไม่บังคับ ก่อนหน้าจะได้รับการเข้ารหัสแบบเลขฐานสิบหก หากต้องการรับคีย์สาธารณะที่เข้ารหัสแบบ 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 คาดหวังว่าคีย์ส่วนตัวของคุณจะเข้ารหัส Base64 ในรูปแบบ 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 ใช้พารามิเตอร์ต่อไปนี้
    • เส้นโค้งวงรี: NIST P-256 หรือที่รู้จักกันใน OpenSSL ว่า prime256v1
    • CheckMode, OldCofactorMode, SingleHashMode และ CofactorMode เป็น 0
    • ฟังก์ชันการเข้ารหัส: รูปแบบจุดที่ไม่มีการบีบอัด
    • ฟังก์ชันการได้คีย์: HKDFwithSHA256 ตามที่อธิบายไว้ใน RFC 5869 โดยมีพารามิเตอร์ต่อไปนี้
      • ห้ามระบุ Salt ตาม RFC แล้ว ค่านี้ต้องเทียบเท่ากับ Salt ที่มีไบต์เป็นศูนย์ 32
  2. แยกคีย์ที่สร้างขึ้นเป็นคีย์ 2 คีย์ที่มีความยาว 256 บิต ได้แก่ 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 Play
  3. ในแท็บ Google Pay API ภายใต้บานหน้าต่างการผสานรวมโดยตรง ให้คลิกจัดการข้างคีย์สาธารณะที่มีอยู่ คลิกเพิ่มคีย์อื่น
  4. เลือกช่องป้อนข้อความคีย์การเข้ารหัสสาธารณะ แล้วเพิ่มคีย์สาธารณะที่สร้างขึ้นใหม่ ซึ่งเข้ารหัส Base64 ในรูปแบบจุดที่ไม่ได้บีบอัด
  5. คลิกบันทึกคีย์การเข้ารหัส
  6. โปรดรองรับการถอดรหัสทั้งคีย์ส่วนตัวใหม่และเก่าขณะเปลี่ยนคีย์เพื่อให้การหมุนเวียนคีย์เป็นไปอย่างราบรื่น

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

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

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

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

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

    /**
     * @param publicKey public key retrieved from your server
     */
    private static JSONObject getTokenizationSpecification(String publicKey) {
      JSONObject tokenizationSpecification = new JSONObject();
      tokenizationSpecification.put("type", "DIRECT");
      tokenizationSpecification.put(
        "parameters",
        new JSONObject()
            .put("protocolVersion", "ECv2")
            .put("publicKey", publicKey));
      return tokenizationSpecification;
    }
  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 เพื่อจัดการการตอบกลับที่เข้ารหัส

หากต้องการยืนยันลายเซ็นและถอดรหัสข้อความ ให้ใช้ไลบรารีโทเค็น paymentmethod ของ 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] ด้วย

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

    อย่าแทนที่ [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 ซ้ำเร็วขึ้น

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

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