Google Pay API מחזיר אמצעי תשלום במטען ייעודי (payload) חתום ומוצפן
PaymentMethodToken. אמצעי התשלום שמוחזרים הם כרטיסים שמורכבים ממספר PAN, או כרטיסים שעברו טוקניזציה ומורכבים ממספר PAN של המכשיר ומקריפטוגרמות.
המטען הייעודי מכיל שדה בשם protocolVersion שמציין לנמען של המטען הייעודי אילו פרימיטיבים קריפטוגרפיים נמצאים בשימוש ומה הפורמט הצפוי.
במדריך הזה מוסבר איך ליצור מפתח ציבורי כדי לבקש טוקן של אמצעי תשלום מוצפן שחתום על ידי Google, ומפורטים בו השלבים לאימות ולפענוח הטוקן.
המדריך הזה רלוונטי רק ל-protocolVersion = ECv2.
מכיוון שאתם מקבלים ישירות פרטים של כרטיסי תשלום, חשוב לוודא שהאפליקציה שלכם תואמת לתקן PCI DSS, ולפני שתמשיכו, לוודא שלשרתים שלכם יש את התשתית הנדרשת לטיפול מאובטח בפרטי התשלום של המשתמשים.
בשלבים הבאים מוסבר מה צריך לעשות כדי להשתמש במטען הייעודי (payload) של Google Pay API:
ECv2 PaymentMethodToken
- מאחזרים את מפתחות החתימה של שורש Google.
- מוודאים שהחתימה של מפתח החתימה הביניים תקפה באמצעות אחד ממפתחות החתימה הבסיסיים שלא פג תוקפם.
- מוודאים שתוקף מפתח החתימה הביניים של ה-payload לא פג.
- מוודאים שהחתימה של המטען הייעודי (payload) תקפה באמצעות מפתח החתימה הביניים.
- מפענחים את התוכן של מטען הייעודי (payload) אחרי שמאמתים את החתימה.
- מוודאים שתוקף ההודעה לא פג. לשם כך, צריך לבדוק שהשעה הנוכחית קטנה מהערך בשדה
messageExpirationבתוכן המפוענח. - להשתמש באמצעי התשלום בתוכן המפוענח ולחייב אותו.
קוד לדוגמה בספריית 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 |
מחרוזת | התאריך והשעה שבהם יפוג התוקף של המפתח הזמני, במילישניות מאז ראשית זמן יוניקס (Unix epoch) לפי שעון UTC. מערכות שמשולבות עם Google דוחות כל מפתח שתוקפו פג. |
הודעה חתומה
הפרמטר signedMessage הוא אובייקט JSON שעבר סריאליזציה בקידוד UTF-8, ומכיל את הערכים הבאים:
| שם | סוג | תיאור |
|---|---|---|
encryptedMessage |
מחרוזת | הודעה מוצפנת בקידוד base64 שמכילה פרטי תשלום ושדות אבטחה נוספים. |
ephemeralPublicKey |
מחרוזת | מפתח ציבורי אפימרי בקידוד base64 שמשויך למפתח הפרטי להצפנת ההודעה בפורמט נקודה לא דחוס. מידע נוסף זמין במאמר בנושא פורמט של מפתח ציבורי להצפנה. |
tag |
מחרוזת | קוד אימות הודעה (MAC) של encryptedMessage בקידוד Base64. |
ההודעה מוצפנת
הערך המפוענח encryptedMessage הוא אובייקט JSON שעבר סריאליזציה בקידוד UTF-8. קובץ ה-JSON מכיל שתי רמות. הרמה החיצונית מכילה מטא-נתונים ושדות שכלולים לצורכי אבטחה, בעוד שהרמה הפנימית היא אובייקט JSON נוסף שמייצג את פרטי התשלום בפועל.
פרטים נוספים על encryptedMessage מופיעים בטבלאות הבאות ובדוגמאות לאובייקטים בפורמט JSON:
| שם | סוג | תיאור |
|---|---|---|
messageExpiration |
מחרוזת | התאריך והשעה שבהם תוקף ההודעה פג, במילישניות מאז ראשית זמן יוניקס (Unix epoch) לפי UTC. מפתחי שילובים צריכים לדחות כל הודעה שתוקפה פג. |
messageId |
מחרוזת | מזהה ייחודי שמזהה את ההודעה למקרה שיהיה צורך לבטל אותה או לאתר אותה בשלב מאוחר יותר. |
paymentMethod |
מחרוזת | סוג פרטי התשלום.
בשלב הזה, יש תמיכה רק ב-CARD.
|
paymentMethodDetails |
אובייקט | פרטי התשלום עצמם. הפורמט של האובייקט הזה נקבע על ידי paymentMethod ומתואר בטבלאות הבאות. |
קלפים
המאפיינים הבאים מרכיבים את פרטי התשלום של אמצעי התשלום CARD:
| שם | סוג | תיאור |
|---|---|---|
pan |
מחרוזת | מספר החשבון האישי שחויב. המחרוזת הזו מכילה רק ספרות. |
expirationMonth |
מספר | חודש התפוגה של הכרטיס, כאשר 1 מייצג את ינואר, 2 מייצג את פברואר וכן הלאה. |
expirationYear |
מספר | שנת התפוגה של הכרטיס בת ארבע ספרות, לדוגמה: 2020. |
authMethod |
מחרוזת | שיטת האימות של עסקת הכרטיס. |
merchantTokenId |
מחרוזת | מזהה הטוקן של המוכר בעסקת הכרטיס. השדה האופציונלי הזה מוחזר רק אם מתקיימים כל התנאים הבאים:
|
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 אימות באמצעות קריפטוגרמה של 3D Secure,
CRYPTOGRAM_3DS authMethod. הוא כולל את השדות הנוספים הבאים:
| שם | סוג | תיאור |
|---|---|---|
cryptogram |
מחרוזת | קריפטוגרמה של 3D 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",
"merchantTokenId": "merchant-token-id",
},
"messageId": "some-message-id",
"messageExpiration": "1759309000000"
}eciIndicator
יכול להיות שרשת הכרטיסים תספק את eciIndicator לעסקאות מאומתות של טוקנים של מכשירים (CRYPTOGRAM_3DS).
אתם צריכים להעביר את הערך eciIndicator בעסקת ההרשאה בלי לשנות אותו או להגדיר אותו כקבוע. אחרת, העסקה תיכשל. בטבלה הבאה מפורטים הערכים של eciIndicator.
| הערך של eciIndicator | רשת כרטיסי אשראי | הגורם האחראי | authMethod |
|---|---|---|---|
""(empty) |
מאסטרקארד | מוֹכר/רוכש | CRYPTOGRAM_3DS |
02 |
מאסטרקארד | מנפיק הכרטיס | CRYPTOGRAM_3DS |
06 |
מאסטרקארד | מוֹכר/רוכש | CRYPTOGRAM_3DS |
05 |
ויזה | מנפיק הכרטיס | CRYPTOGRAM_3DS |
07 |
ויזה | מוֹכר/רוכש | CRYPTOGRAM_3DS |
""(empty) |
רשתות אחרות | מוֹכר/רוכש | CRYPTOGRAM_3DS |
המערכת לא תחזיר ערכים אחרים של ECI עבור כרטיסי ויזה ומאסטרקארד שלא מופיעים בטבלה הזו.
אימות חתימה
כדי לאמת את החתימות, שכוללות את מפתח הביניים ואת חתימות ההודעות, צריך את הפריטים הבאים:
- האלגוריתם ששימש ליצירת החתימה
- מחרוזת הבייטים שמשמשת ליצירת החתימה
- המפתח הציבורי שמתאים למפתח הפרטי ששימש ליצירת החתימה
- החתימה עצמה
אלגוריתם החתימה
Google משתמשת באלגוריתם לחתימה דיגיטלית של עקומות אליפטיות (ECDSA) כדי לחתום על ההודעות עם הפרמטרים הבאים: ECDSA over 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. אם לפחות אימות חתימה אחד פועל, האימות נחשב להשלמה. אפשר להשתמש ב-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.
|
| פונקציה לגזירת מפתח | מבוסס על HMAC עם SHA-256 (
|
| אלגוריתם הצפנה סימטרי |
DEM2, כפי שמוגדר ב-ISO 18033-2 אלגוריתם הצפנה: AES-256-CTR עם IV אפס וללא ריפוד. |
| אלגוריתם MAC | HMAC_SHA256 עם מפתח באורך 256 ביט שנוצר מפונקציית גזירת המפתח. |
פורמט של מפתח הצפנה ציבורי
מפתח ההצפנה הציבורי והערך ephemeralPublicKey שמוחזר במטען הייעודי (payload) של Google מפורמטים עם ייצוג base64 של המפתח בפורמט נקודה לא דחוס. הוא מורכב משני הרכיבים הבאים:
- מספר קסם אחד שמציין את הפורמט (0x04).
- שני מספרים שלמים גדולים בגודל 32 בייט שמייצגים את קואורדינטות X ו-Y בעקומה אליפטית.
הפורמט הזה מתואר בפירוט במאמר Public Key Cryptography For The Financial Services Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA), ANSI X9.62, 1998.
שימוש ב-OpenSSL ליצירת מפתח ציבורי
שלב 1: יצירת מפתח פרטי
בדוגמה הבאה נוצר מפתח פרטי של עקומה אליפטית שמתאים לשימוש עם 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
איך מפענחים את טוקן אמצעי התשלום
כדי לפענח את האסימון:
- משתמשים במפתח הפרטי וב-
ephemeralPublicKeyשצוין כדי לגזור מפתח משותף באורך 512 ביט שמשתמש ב-ECIES-KEM. משתמשים בפרמטרים הבאים: - עקומה אליפטית: NIST P-256, שנקראת גם prime256v1 ב-OpenSSL.
-
CheckMode,OldCofactorMode,SingleHashModeוגםCofactorModeהם0. - פונקציית קידוד: פורמט נקודה לא דחוס.
- פונקציית נגזרת של מפתח: HKDFwithSHA256, כפי שמתואר ב-RFC 5869, עם הפרמטר הבא:
- אסור לספק Salt. לפי ה-RFC, הערך הזה צריך להיות שווה ל-salt של 32 בייטים מאופסים.
- מפצלים את המפתח שנוצר לשני מפתחות באורך 256 ביט:
symmetricEncryptionKeyו-macKey. בודקים שהשדה
tagהוא כתובת MAC תקינה שלencryptedMessage.כדי ליצור את ה-MAC הצפוי, משתמשים ב-HMAC (RFC 5869) עם פונקציית הגיבוב SHA256 ועם
macKeyשהתקבל בשלב 2.מפענחים את
encryptedMessageבאמצעות מצב AES-256-CTR, עם הפרטים הבאים:- ערך IV אפס.
- ללא ריפוד.
- הערך
symmetricEncryptionKeyשנגזר בשלב 2.
ניהול מפתחות
מפתחות הצפנה של מוֹכרים
המוֹכרים יוצרים מפתח ציבורי בהתאם למפרט שמתואר במפרט תוכנית ההצפנה.
מפתחות חתימה של שורש Google
Google מפרסמת את קבוצת המפתחות הציבוריים התקפים כרגע לחתימה על שורש, שאפשר לאחזר מכתובת URL ציבורית. המפתחות תקפים כל עוד כותרות המטמון של HTTP שמוחזרות על ידי כתובת ה-URL מציינות. הם נשמרים במטמון עד שתוקף שלהם פג, וזה נקבע לפי השדה keyExpiration. מומלץ, כשמאחזרים מפתחות ופג תוקף האיחזור, לאחזר שוב את המפתחות מכתובת ה-URL הציבורית כדי לקבל את הרשימה העדכנית של מפתחות תקפים.
חריג לפרוטוקול ECv2: אם אי אפשר לאחזר את המפתחות מ-Google בזמן הריצה, צריך לאחזר את
keys.json מכתובת ה-URL של הייצור שלנו, לשמור אותו במערכת ולרענן אותו באופן ידני מעת לעת. בנסיבות רגילות, Google מנפיקה מפתח חדש לחתימת בסיס ל-ECv2 חמש שנים לפני שתוקף המפתח עם תאריך התפוגה הארוך ביותר פג. במקרה של פריצה למפתחות, 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 של סביבות הבדיקה וסביבות הייצור:
- מבחן:
https://payments.developers.google.com/paymentmethodtoken/test/keys.json - סביבת ייצור:
https://payments.developers.google.com/paymentmethodtoken/keys.json
רוטציית מפתחות
אם אתם מפענחים אסימון של אמצעי תשלום ישירות בשרתים שלכם באמצעות שילוב ישיר, אתם צריכים להחליף את המפתחות מדי שנה.
כדי להחליף את מפתחות ההצפנה:
- משתמשים ב-OpenSSL כדי ליצור זוג מפתחות חדש.
- פותחים את מסוף Google Pay ו-Wallet כשנכנסים לחשבון Google ששימש בעבר לניהול האפליקציה ב-Google Play.
- בכרטיסייה Google Pay API, בחלונית שילוב ישיר, לוחצים על ניהול לצד המפתח הציבורי הקיים. לוחצים על הוספת עוד מפתח.
- בוחרים את שדה להזנת קלט מפתח ציבורי להצפנה ומוסיפים את המפתח הציבורי החדש שנוצר בקידוד base64 בפורמט נקודה לא דחוס.
- לוחצים על שמירת מפתחות ההצפנה.
כדי להבטיח רוטציית מפתחות חלקה, תומכים בפענוח של המפתחות הפרטיים החדשים והישנים בזמן המעבר בין המפתחות.
אם אתם משתמשים בספריית Tink כדי לפענח את האסימון, אתם יכולים להשתמש בקוד Java הבא כדי לתמוך בכמה מפתחות פרטיים:
String decryptedMessage = new PaymentMethodTokenRecipient.Builder() .addRecipientPrivateKey(newPrivateKey) .addRecipientPrivateKey(oldPrivateKey);
חשוב לוודא שהקוד לפענוח נפרס בסביבת הייצור ושהפענוחים מתבצעים בהצלחה.
משנים את המפתח הציבורי שבו משתמשים בקוד.
מחליפים את הערך של מאפיין
publicKeyבמאפייןPaymentMethodTokenizationSpecificationparameters:/** * @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; }
- פורסים את הקוד משלב 4 בסביבת הייצור. אחרי פריסת הקוד, עסקאות ההצפנה והפענוח משתמשות בצמדי המפתחות החדשים.
מוודאים שהמפתח הציבורי הישן לא משמש יותר להצפנת עסקאות.
- מסירים את המפתח הפרטי הישן.
- פותחים את מסוף Google Pay ו-Wallet כשנכנסים לחשבון Google שאיתו נרשמתם בעבר כמפתחים ב-Google Pay.
- בכרטיסייה Google Pay API, בחלונית שילוב ישיר, לוחצים על ניהול לצד המפתח הציבורי הקיים. לצד המפתח הציבורי הישן, לוחצים על מחיקה ואז על שמירת מפתחות ההצפנה.
Google משתמשת במפתח שצוין במאפיין publicKey באובייקט PaymentMethodTokenizationSpecification parameters, כמו שמוצג בדוגמה הבאה:
{
"protocolVersion": "ECv2",
"publicKey": "BOdoXP+9Aq473SnGwg3JU1..."
}משתמשים בספריית Tink כדי לנהל את התגובה המוצפנת
כדי לבצע אימות חתימה ופענוח של הודעות, אפשר להשתמש בספריית paymentmethodtoken של Tink. הספרייה הזו זמינה רק ב-Java. כדי להשתמש בו, צריך לבצע את השלבים הבאים:
ב-
pom.xml, מוסיפים את אפליקציית Tinkpaymentmethodtokenכתלות:<dependencies> <!-- other dependencies ... --> <dependency> <groupId>com.google.crypto.tink</groupId> <artifactId>apps-paymentmethodtoken</artifactId> <version>1.9.1</version> <!-- or latest version --> </dependency> </dependencies>כשמפעילים את השרת, צריך לבצע אחזור מראש של מפתחות החתימה של Google כדי שהמפתח יהיה זמין בזיכרון. כך נמנעת הצגה של השהיית רשת למשתמש בזמן שתהליך הפענוח מאחזר את המפתחות.
GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground();
מפענחים את ההודעה באמצעות הקוד הבא, שבו מניחים ש-
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);
מחליפים את
PrivateKey1בערך המתאים של המפתח הפרטי שמשויך לערך של המפתח הציבורי הרשום ב-Google מתוך הכנת המפתחות והרשמה ב-Google. אפשר להוסיף עוד ערכים של מפתחות פרטיים בהמשך, כשיהיה צורך להחליף מפתחות עם Google. המשתנים יכולים להיות מחרוזת PKCS8 בקידוד base64 או אובייקטECPrivateKey. מידע נוסף על יצירת מפתח פרטי בקידוד Base64 מסוג PKCS8 זמין במאמר הכנת המפתחות והרשמה ב-Google.אם אתם לא יכולים להתקשר לשרת 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 באפריל 2038 בנסיבות רגילות, למעט מקרים של פגיעה במפתח. במקרה של פגיעה במפתחות, Google מודיעה לכל המוכרים באמצעות פרטי הקשר שסופקו בפורטל השירות העצמי, כדי לבקש טעינה מהירה יותר של
keys.json.קטע הקוד מטפל בפרטי האבטחה הבאים, כדי שתוכלו להתמקד בצריכה של מטען הייעודי (payload):
- מפתחות החתימה של Google מאוחזרים ונשמרים במטמון בזיכרון
- אימות חתימה
- פענוח