Google Pay API מחזיר אמצעי תשלום במטען ייעודי (payload) חתום ומוצפן.PaymentMethodToken אמצעי התשלום שמוחזרים הם כרטיסים שמורכבים מ-PAN, או כרטיסים שעברו טוקניזציה ומורכבים מ-PAN של המכשיר ומצפנים.
המטען הייעודי (payload) מכיל שדה בשם protocolVersion שמציין לנמען של המטען הייעודי אילו פרימיטיבים קריפטוגרפיים נמצאים בשימוש ומה הפורמט הצפוי.
במדריך הזה מוסבר איך ליצור מפתח ציבורי כדי לבקש טוקן מוצפן של אמצעי תשלום שחתום על ידי Google, ומפורטים השלבים שצריך לבצע כדי לאמת ולפענח את הטוקן.
המדריך הזה רלוונטי רק ל-protocolVersion = ECv2.
מכיוון שאתם מקבלים ישירות פרטי כרטיסי תשלום, לפני שתמשיכו, ודאו שהאפליקציה שלכם עומדת בתקן PCI DSS ושלשרתים שלכם יש את התשתית הנדרשת לטיפול מאובטח בפרטי התשלום של המשתמשים.
בשלבים הבאים מוסבר מה צריך לעשות כדי להשתמש במטען הייעודי (payload) של Google Pay API:ECv2 PaymentMethodToken
- מאחזרים את מפתחות החתימה של Google.
- מוודאים שהחתימה של מפתח החתימה הביניים תקפה באמצעות אחד ממפתחות החתימה הבסיסיים שלא פג תוקפם.
- מוודאים שתוקף מפתח החתימה הביניים של מטען הייעודי לא פג.
- מוודאים שהחתימה של מטען הייעודי תקפה באמצעות מפתח החתימה הביניים.
- מפענחים את התוכן של מטען הייעודי (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 |
מחרוזת | התאריך והשעה שבהם יפוג תוקף המפתח הזמני, במילישניות לפי שעון UTC מאז תקופת Unix. מערכות משולבות דוחות כל מפתח שתוקפו פג. |
הודעה חתומה
הפרמטר 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 |
מחרוזת | שיטת האימות של העסקה בכרטיס. |
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" }, "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 שמוחזר במטענים הייעודיים (payloads) של 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, עם הפרמטר הבא:
- אסור לספק מלח. לפי 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, בחלונית Direct integration, לוחצים על Manage לצד המפתח הציבורי הקיים. לוחצים על הוספת עוד מפתח.
- בוחרים את שדה הזנת הטקסט מפתח ציבורי להצפנה ומוסיפים את המפתח הציבורי החדש שנוצר בקידוד 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, בחלונית Direct integration, לוחצים על Manage לצד המפתח הציבורי הקיים. לצד המפתח הציבורי הישן, לוחצים על מחיקה ואז על שמירת מפתחות ההצפנה.
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 מאוחזרים ונשמרים במטמון בזיכרון
- אימות חתימה
- פענוח