Криптография платежных данных для продавцов

Google Pay API передает информацию о способах оплаты в виде полезной нагрузки с подписанными и зашифрованными данными PaymentMethodToken. Оплата осуществляется картой с номером PAN или с помощью токенизированной карты, содержащей криптограммы и PAN-номер устройства.

Полезная нагрузка содержит поле protocolVersion, в котором представлена информация об использованных криптографических примитивах и ожидаемом формате.

Прочитав это руководство, вы узнаете, как создать открытый ключ для запроса токена способа оплаты, подписанного и зашифрованного Google, а также как подтвердить и расшифровать этот токен.

Инструкции действительны только для protocolVersion = ECv2.

Поскольку ваше приложение будет получать информацию о платежных картах напрямую, убедитесь, что оно соответствует требованиям стандарта PCI DSS, а серверы имеют необходимую инфраструктуру для безопасного использования платежных данных пользователей.

Ниже описано, как обрабатывать полезную нагрузку PaymentMethodToken ECv2, полученную от Google Pay API.

  1. Получите корневые ключи подписи Google.
  2. Проверьте правильность подписи промежуточного ключа с помощью одного из активных корневых ключей.
  3. Проверьте, не истек ли срок действия промежуточного ключа полезной нагрузки.
  4. Проверьте правильность подписей данных в полезной нагрузке с помощью промежуточного ключа.
  5. Расшифруйте данные полезной нагрузки.
  6. Убедитесь, что срок действия сообщения не истек, проверив поле messageExpiration в расшифрованных данных.
  7. Спишите средства с использованием способа оплаты из расшифрованного сообщения.

В библиотеке Tink можно найти образец кода, исполняющего пункты 1–6.

Структура токена способа оплаты

Сообщение, отправляемое Google в ответе PaymentData, представляет собой сериализованный объект JSON в формате UTF-8 с указанными ниже ключами.

Имя Тип Описание
protocolVersion Строка Определяет схему шифрования и подписывания данных, используемую для создания сообщения. Позволяет при необходимости вносить изменения в протокол.
signature Строка Проверяет, является ли Google отправителем сообщения. Создается промежуточным ключом с использованием кодировки Base64 и алгоритма ECDSA.
intermediateSigningKey Объект Объект JSON, содержащий промежуточный ключ подписи Google со значениями signedKey, keyValue, keyExpiration и signatures. Объект сериализован, чтобы упростить подтверждение подписи.
signedMessage Строка Сериализованный объект JSON в виде HTML-безопасной строки, содержащей значения encryptedMessage, ephemeralPublicKey и tag. Строка сериализована, чтобы упростить подтверждение подписи.

Пример

Ниже показан пример токена способа оплаты в формате JSON.

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

Промежуточный ключ подписи

intermediateSigningKey представляет собой закодированный по стандарту UTF-8 сериализованный объект JSON с указанными ниже значениями.

Имя Тип Описание
signedKey Строка Сообщение в кодировке Base64, содержащее платежное описание ключа.
signatures Строка Проверяет, является ли Google отправителем промежуточного ключа. Создается с использованием кодировки Base64 и алгоритма ECDSA.

Подписанный ключ

signedKey представляет собой закодированный по стандарту UTF-8 сериализованный объект JSON с указанными ниже значениями.

Имя Тип Описание
keyValue Строка Версия ключа в формате ASN.1, закодированная с использованием Base64. Информация в строке SubjectPublicKeyInfo определяется в соответствии со стандартом X.509.
keyExpiration Строка Дата и время окончания действия промежуточного ключа (UTC, в миллисекундах UNIX-времени). Интеграторы отклоняют все ключи с истекшим сроком действия.

Подписанное сообщение

signedMessage представляет собой закодированный по стандарту UTF-8 сериализованный объект JSON с указанными ниже значениями.

Имя Тип Описание
encryptedMessage Строка Зашифрованное сообщение в кодировке Base64, содержащее платежные данные и дополнительные поля безопасности.
ephemeralPublicKey Строка Эфемерный открытый ключ, закодированный по стандартному алгоритму Base64 и связанный с закрытым ключом шифрования, в несжатом формате. Дополнительную информацию вы можете найти в разделе Формат открытого ключа шифрования.
tag Строка Код аутентификации сообщения encryptedMessage, закодированный по стандартному алгоритму Base64.

Зашифрованное сообщение

Расшифрованное значение строки encryptedMessage представляет собой закодированный по стандарту UTF-8 сериализованный объект JSON. Объект JSON состоит из двух уровней. Внешний уровень содержит метаданные и поля, обеспечивающие безопасность. Внутренний уровень – это ещё один объект JSON, включающий фактические платежные данные.

Более подробную информацию о сообщении encryptedMessage можно найти в приведенных ниже таблицах и примерах объектов JSON.

Имя Тип Описание
gatewayMerchantId Строка

Уникальный идентификатор продавца, позволяющий обработчику данных проверить, что сообщение предназначено запросившему его продавцу. Создается обработчиком; продавец передает этот идентификатор Google в объекте PaymentMethodTokenizationSpecification для приложения Android или сайта.

messageExpiration Строка Дата и время окончания действия сообщения (UTC, в миллисекундах UNIX-времени). Интеграторы должны отклонять все сообщения с истекшим сроком действия.
messageId Строка Уникальный идентификатор, позволяющий найти сообщение в случае его отмены или переноса на более поздний срок.
paymentMethod Строка Тип платежных данных. . В настоящее время поддерживается только значение CARD.
paymentMethodDetails Объект Платежные данные. Формат этого объекта JSON определяется значением параметра paymentMethod и описывается в приведенных ниже таблицах.

Карта

Платежные данные способа оплаты CARD имеют указанные ниже свойства.

Имя Тип Описание
pan Строка Номер банковского счета, с которого осуществляется оплата. Эта строка может содержать только цифры.
expirationMonth Число Месяц истечения срока действия карты (где 1 – январь, 2 – февраль и т. д.).
expirationYear Число Состоящий из четырех цифр год истечения срока действия карты (например, 2020).
authMethod Строка Метод аутентификации транзакции по карте.
assuranceDetails AssuranceDetailsSpecifications В этом объекте содержится информация о верификации данных способа оплаты.

PAN_ONLY

Фрагмент кода в формате JSON ниже – это пример полного сообщения encryptedMessage для способа оплаты (paymentMethod) CARD с методом аутентификации (authMethod) PAN_ONLY.

  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "PAN_ONLY",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2020
  },  "gatewayMerchantId": "some-merchant-id",  "messageId": "some-message-id",
  "messageExpiration": "1577862000000"
}

CRYPTOGRAM_3DS

В случае аутентификации способа оплаты CARD с помощью криптограммы 3-D Secure (CRYPTOGRAM_3DS authMethod) используются дополнительные поля:

Имя Тип Описание
cryptogram Строка Криптограмма 3-D Secure.
eciIndicator Строка Присутствует не всегда. Используется, только если карта относится к платежной системе Visa. Это значение должно быть передано в запросе авторизации платежа.

Фрагмент кода в формате JSON ниже – это пример полного сообщения encryptedMessage для способа оплаты (paymentMethod) CARD с методом аутентификации (authMethod) CRYPTOGRAM_3DS.

{
  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "CRYPTOGRAM_3DS",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2020,
    "cryptogram": "AAAAAA...",
    "eciIndicator": "eci indicator"
    
  },
  
  "messageId": "some-message-id",
  "messageExpiration": "1577862000000"
}

Подтверждение подписи

Для подтверждения подписей с промежуточным ключом и подписями сообщений нужно следующее:

  • алгоритм, который был использован при создании подписи;
  • строка байтов, которая была использована при создании подписи;
  • открытый ключ, соответствующий закрытому ключу, который был использован при создании подписи;
  • сама подпись.

Алгоритм подписи

Google использует для подписывания сообщений алгоритм на эллиптических кривых (ECDSA) со следующими параметрами: алгоритм ECDSA на кривой NIST P-256 с хеш-функцией SHA-256 в соответствии с FIPS 186-4.

Подпись

Подпись включена во внешний уровень сообщения. Она кодируется с использованием Base64 в байтовом формате ASN.1. Чтобы получить дополнительную информацию о формате ASN.1, изучите приложение А к документу "Инструменты IETF". Подпись состоит из целых чисел r и s, рассчитанных по алгоритму ECDSA. Подробности можно найти в описании алгоритма создания подписи.

Ниже приведен пример использования байтового формата ASN.1 – стандартного формата, созданного с помощью расширения Java Cryptography Extension (JCE) в соответствии с алгоритмом ECDSA.

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_versionECv2.

Если параметр 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. Достаточно подтвердить всего одну подпись. Чтобы подтвердить signedStringForMessageSignature, используйте значение intermediateSigningKey.signedKey.keyValue. Компания Google настоятельно рекомендует использовать готовую криптографическую библиотеку, а не создавать собственный код подтверждения.

Как построить строку байтов для подписи сообщения

Чтобы проверить подпись в токене способа оплаты, приведенного в качестве примера, постройте строку signedStringForMessageSignature по следующей формуле:

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

Символ || обозначает объединение. Каждый компонент – sender_id, recipient_id, protocolVersion и signedMessage – должен быть закодирован по стандарту UTF-8. Длина каждого компонента составляет 4 байта в формате little-endian. Если вы строите строку байтов, не изменяйте компонент signedMessage и не проводите его синтаксический анализ. Например, не следует заменять \u003d символом =.

Пример

В этом примере используется следующий токен способа оплаты:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

Параметр sender_id всегда имеет значение Google, а recipient_id – merchant:merchantId. Компонент merchantId совпадает со значением, указанным в Google Pay & Wallet Console для продавцов с доступом к рабочей версии.

Поскольку параметр sender_id имеет значение Google, а recipient_id – merchant:12345, строка signedString выглядит следующим образом:

signedStringForMessageSignature =
\x06\x00\x00\x00 || Google || \x0e\x00\x00\x00 || merchant:12345 || | \x04\x00\x00\x00 || ECv2 || \xd2\x00\x00\x00 || {"tag":"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\u003d","ephemeralPublicKey":"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\u003d","encryptedMessage":"mKOoXwi8OavZ"}

Как подтвердить подпись для строки signedStringForMessageSignature

Для подтверждения созданной подписанной строки используется стандартный алгоритм ECDSA. Строка intermediateSigningKey.signedKey.keyValue, подтвержденная в предыдущем шаге, используется, чтобы подтвердить компонент signedMessage. Компания Google настоятельно рекомендует использовать готовую криптографическую библиотеку, а не создавать собственный код подтверждения.

Спецификация схемы шифрования

Для защиты токена способа оплаты в ответе Google Pay API применяется интегрированная схема шифрования на эллиптических кривых (ECIES). В схеме шифрования используются указанные ниже параметры.

Параметр Описание
Метод инкапсуляции ключа

ECIES-KEM в соответствии со стандартом ISO 18033-2.

  • Эллиптическая кривая – NIST P-256 (prime256v1 в OpenSSL).
  • Значения CheckMode, OldCofactorMode, SingleHashMode и CofactorMode равны 0.
  • Используется несжатый формат.
Функция формирования ключа

Алгоритм HMAC и SHA-256 (HKDFwithSHA256).

  • Предоставлять соль не нужно.
  • В случае применения протокола ECv2 информация должна кодироваться Google (кодировка ASCII).
  • 256 бит должны быть выделены для ключа AES256, и еще 256 бит – для ключа HMAC_SHA256.
Алгоритм симметричного шифрования

DEM2 в соответствии со стандартом ISO 18033-2.

Алгоритмы шифрования: AES-256-CTR с нулевым IV и без заполнения.

Алгоритм MAC HMAC_SHA256 с использованием 256-битного ключа, полученного с помощью функции формирования ключа.

Формат открытого ключа шифрования

Открытый ключ шифрования и ephemeralPublicKey, получаемые в полезной нагрузке Google, форматируются с использованием кодировки Base64 в несжатом формате. Он состоит из следующих элементов:

  • магическое число, указывающее на формат (0x04);
  • два больших 32-байтовых целых числа, обозначающих координаты X и Y на эллиптической кривой.

Этот формат подробно описан в документе "Криптография с открытым ключом для индустрии финансовых услуг: алгоритм цифровой подписи на эллиптических кривых (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

Как расшифровать токен способа оплаты

Выполните следующие действия:

  1. С помощью закрытого ключа и ephemeralPublicKey сформируйте 512-битный общий ключ, применив схему шифрования ECIES-KEM. Укажите следующие параметры:
    • эллиптическая кривая – NIST P-256 (prime256v1 в OpenSSL);
    • значения CheckMode, OldCofactorMode, SingleHashMode и CofactorMode равны 0;
    • функция кодировки – несжатый формат;
    • функция формирования ключа – HKDF с SHA256; она подробно описана в документе RFC 5869. Учтите следующее:
      • Предоставлять соль не нужно. (согласно стандарту RFC, массив данных должен быть эквивалентен соли на 32 нулевых байта).
  2. Разделите сгенерированный ключ на два 256-битных ключа: symmetricEncryptionKey и macKey.
  3. Убедитесь, что поле tag содержит допустимое значение MAC для encryptedMessage.

    Чтобы сгенерировать MAC, используйте алгоритм HMAC (RFC 5869) с хеш-функцией SHA256, а также ключ macKey, полученный на этапе 2.

  4. Расшифруйте encryptedMessage, используя алгоритм AES-256-CTR с учетом следующего:

    • нулевое значение IV;
    • нет заполнения;
    • ключ symmetricEncryptionKey, сформированный в шаге 2.

Управление ключами

Ключи шифрования продавца

Продавцы должны генерировать открытые ключи согласно требованиям, описанным в разделе Спецификация схемы шифрования.

Корневые ключи подписи Google

Google публикует набор действующих в настоящий момент открытых корневых ключей, которые могут быть получены по общедоступному URL. Ключи действительны, если это подтверждается заголовками кеша HTTP, возвращаемыми URL. Срок действия ключей определяется значением поля keyExpiration. Если срок действия ключей истек, запросите новые по общедоступному URL.

Примечание для пользователей протокола ECv2. Если получить ключи подписи Google не удается, перейдите по URL рабочей среды, сохраните в системе файл keys.json и периодически обновляйте его вручную. Обычно новые ключи подписи для протокола 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 – это версия ключа в формате ASN.1, закодированная с использованием Base64 (без переносов строк и заполнения). Информация в строке SubjectPublicKeyInfo определяется в соответствии со стандартом X.509. В языке Java формат кодирования ASN.1 представлен классом X509EncodedKeySpec. Его можно получить с помощью метода ECPublicKey.getEncoded().

Предоставляются URL-адреса для тестовой и рабочей среды.

Обновление ключей

Если вы расшифровываете токены способов оплаты прямо на своих серверах с прямой интеграцией, обновлять ключи нужно раз в год.

Выполните следующие действия:

  1. Создайте пару ключей с помощью OpenSSL.
  2. Откройте Google Pay & Wallet Console, войдя в аккаунт Google, который вы использовали для регистрации в качестве разработчика Google Pay.
  3. На вкладке Google Pay API в разделе Direct Integration (Прямая интеграция) нажмите Manage (Настроить) рядом с вашим существующим открытым ключом. Нажмите Add another key (Добавить другой ключ).
  4. Установите курсор в поле Открытый ключ шифрования и введите только что созданную пару ключей, закодированную по стандартному алгоритму Base64 в несжатом формате.
  5. Нажмите Save encryption keys (Сохранить ключи шифрования).
  6. Чтобы не возникло проблем, не выключайте поддержку расшифровки со старыми ключами, пока не будет завершен переход к новым.

    Если для расшифровки токенов вы пользуетесь библиотекой Tink, включите поддержку нескольких закрытых ключей. Сделать это можно с помощью следующего Java-кода:

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

    Убедитесь, что код для расшифровки развернут в рабочей среде, и следите за тем, насколько успешно проходит расшифровка.

  7. Измените открытый ключ, который используете в коде.

    Укажите в свойстве PaymentMethodTokenizationSpecification parameters новое значение атрибута publicKey:

    const tokenizationSpecification = {
      "type": "DIRECT",
      "parameters": {
        "protocolVersion": "ECv2",
        "publicKey": "BOdoXP1aiNp.....kh3JUhiSZKHYF2Y="
      }
    }
  8. Разверните код из шага 4 в рабочей среде. Когда вы это сделаете, для транзакций шифрования и расшифровки должны будут использоваться новые пары ключей.
  9. Убедитесь, что старые открытые ключи действительно не применяются для шифрования каких-либо транзакций.

  10. Удалите старый закрытый ключ.
  11. Откройте Google Pay & Wallet Console, войдя в аккаунт Google, который вы использовали для регистрации в качестве разработчика Google Pay.
  12. На вкладке Google Pay API в разделе Direct Integration (Прямая интеграция) нажмите Manage (Настроить) рядом со своим существующим открытым ключом. Нажмите Delete (Удалить) рядом со старым открытым ключом, а затем – Save encryption keys (Сохранить ключи шифрования).

Google использует ключ, указанный в свойстве publicKey, в строке PaymentMethodTokenizationSpecification parameters, как показано ниже:

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

Как использовать библиотеку Tink для управления зашифрованным ответом

Используйте криптографическую библиотеку Tink для подтверждения подписей и расшифровки сообщений. Чтобы интегрировать свое приложение с библиотекой Tink, выполните указанные ниже действия.

  1. В файле pom.xml добавьте paymentmethodtoken Tink в качестве зависимости.

    <dependencies>
      <!-- other dependencies ... -->
      <dependency>
        <groupId>com.google.crypto.tink</groupId>
        <artifactId>apps-paymentmethodtoken</artifactId>
        <version>1.2.0</version>  <!-- or latest version -->
      </dependency>
    </dependencies>
    
  2. При запуске сервера получите ключи подписи Google, чтобы они были доступны в памяти. Благодаря этому при расшифровке данных ключ будет загружаться быстрее.

    GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground();
  3. Для расшифровки данных используйте код, приведенный ниже. Предполагается, что paymentMethodToken хранится внутри переменной encryptedMessage. Вместо элементов, выделенных полужирным, подставьте собственные значения.

    Для тестирования среды замените INSTANCE_PRODUCTION на INSTANCE_TEST, а [YOUR MERCHANT ID] – на 12345678901234567890.

    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 и PrivateKey2 используйте свои собственные ключи. Переменные могут быть представлены в виде строки, закодированной в Base64 в формате PKCS8, или в виде объекта ECPrivateKey. Подробную информацию о том, как создать закрытый ключ с кодировкой Base64 в формате PKCS8, вы найдете в разделе Как создать пару ключей с помощью OpenSSL.

  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 апреля 2038 года. В случае взлома ключей Google отправляет продавцам уведомления о том, что необходимо обновить файл keys.json раньше срока. При отправке уведомлений используется контактная информация, указанная на портале.

    Поскольку перечисленные ниже операции проверки безопасности уже реализованы в приведенном фрагменте кода, вы можете сконцентрироваться на работе с платежной информацией.

    • Получение ключей подписи Google и их кеширование в памяти
    • Подтверждение подписи
    • Расшифровка