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 |
מחרוזת | התאריך והשעה שבהם פג התוקף של מפתח הביניים, באלפיות השנייה לפי שעון UTC מאז ראשית זמן יוניקס (Unix epoch). האינטגרטורים דוחים כל מפתח שפג תוקפו. |
הודעה חתומה
השדה signedMessage הוא אובייקט JSON שעבר סריאליזציה בקידוד UTF-8, ומכיל את הערכים הבאים:
| שם | סוג | תיאור |
|---|---|---|
encryptedMessage |
מחרוזת | הודעה מוצפנת בקידוד base64 שמכילה את פרטי התשלום וכמה שדות אבטחה נוספים. |
ephemeralPublicKey |
מחרוזת | מפתח ציבורי זמני בקידוד base64 שמשויך למפתח הפרטי כדי להצפין את ההודעה בפורמט נקודה לא דחוס. מידע נוסף זמין במאמר פורמט של מפתח ציבורי להצפנה. |
tag |
מחרוזת | MAC בקידוד Base64 של encryptedMessage. |
ההודעה מוצפנת
ה-encryptedMessage שפוענח הוא אובייקט JSON שעבר סריאליזציה וקודד ב-UTF-8. ה-JSON מכיל שתי רמות. הרמה החיצונית מכילה מטא-נתונים ושדות שנכללים מטעמי אבטחה, והרמה הפנימית היא אובייקט JSON נוסף שמייצג את פרטי הכניסה בפועל לתשלום.
לפרטים נוספים על encryptedMessage, עיינו בטבלאות הבאות ובדוגמאות לאובייקטים בפורמט JSON:
| שם | סוג | תיאור |
|---|---|---|
messageExpiration |
מחרוזת | התאריך והשעה שבהם פג התוקף של ההודעה, באלפיות השנייה לפי זמן אוניברסלי מתואם (UTC) מאז ראשית זמן יוניקס (Unix epoch). האינטגרטורים צריכים לדחות כל הודעה שתוקפה פג. |
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 אומת באמצעות קריפטוגרמה של 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",
"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 אחרים של VISA ו-Mastercard שלא מופיעים בטבלה הזו לא יוחזרו.
אימות חתימה
כדי לאמת את החתימות, שכוללות את החתימות של מפתח הביניים ושל ההודעה, צריך את הפריטים הבאים:
- האלגוריתם ששימש ליצירת החתימה
- מחרוזת הבייטים ששימשה ליצירת החתימה
- המפתח הציבורי שתואם למפתח הפרטי ששימש ליצירת החתימה
- החתימה עצמה
אלגוריתם החתימה
Google משתמשת באלגוריתם חתימה דיגיטלית של עקומה אליפטית (ECDSA) כדי לחתום על ההודעות עם הפרמטרים הבאים: ECDSA מעל NIST P-256 עם SHA-256 כפונקציית הגיבוב (hash), כפי שמוגדר ב-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 משתמשת ב-Elliptic Curve Integrated Encryption Scheme (ECIES) כדי לאבטח את האסימון של אמצעי התשלום שמוחזר בתגובה מה-API של Google Pay. אלה הפרמטרים של סכימת ההצפנה:
| פרמטר | הגדרה |
|---|---|
| שיטת אנקפסולציה של מפתחות (KEM) | ECIES-KEM, כמו שמוגדר ב-ISO 18033-2.
|
| פונקציית גזירת מפתחות (KDF) | מבוסס-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, עם הפרמטר הבא:
- אסור לספק ערך גיבוב (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 class.
אפשר לקבל אותו באמצעות 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, בחלונית שילוב ישיר, לוחצים על ניהול לצד המפתח הציבורי הקיים. לוחצים על הוספת עוד מפתח.
- בוחרים את השדה להזנת טקסט Public encryption key ומוסיפים את המפתח הציבורי החדש שנוצר, בקידוד 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 כדי לנהל את התגובה המוצפנת.
כדי לבצע אימות חתימה ופענוח הודעות, צריך להשתמש בספריית Tink paymentmethodtoken. הספרייה הזו זמינה רק ב-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. מידע נוסף על יצירת מפתח פרטי מסוג PKCS8 בקידוד Base64 זמין במאמר הכנת המפתחות והרשמה ל-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 אוחזרו ונשמרו במטמון בזיכרון
- אימות חתימה
- פענוח