판매자의 결제 데이터 암호화

Google Pay API는 서명되고 암호화된 PaymentMethodToken 페이로드에 결제 수단을 반환합니다. 반환된 결제 수단은 PAN으로 구성된 카드이거나 기기 PAN 및 암호로 구성된 토큰화된 카드입니다.

페이로드에는 사용 중인 암호법 기본 요소 및 예상되는 형식을 페이로드 수신자에게 알려주는 필드(protocolVersion)가 있습니다.

이 가이드에서는 Google이 서명한 암호화된 결제 수단 토큰을 요청하는 공개 키를 생성하는 방법과 토큰을 확인하고 복호화하기 위해 수행할 단계를 자세히 설명합니다.

이 가이드는 protocolVersion = ECv2에만 적용됩니다.

대행업체는 결제 카드 정보를 직접 받으므로 앱이 PCI DSS를 준수하고 서버가 사용자의 결제 사용자 인증 정보를 안전하게 처리하는 데 필요한 인프라를 갖추고 있는지 확인한 후 계속 진행하세요.

다음 단계는 통합업체가 Google Pay API ECv2 PaymentMethodToken 페이로드를 사용하기 위해 수행해야 하는 작업을 설명합니다.

  1. Google 루트 서명 키를 가져옵니다.
  2. 만료되지 않은 루트 서명 키로 중간 서명 키의 서명이 유효한지 확인합니다.
  3. 페이로드의 중간 서명 키가 만료되지 않았는지 확인합니다.
  4. 중간 서명 키로 페이로드의 서명이 유효한지 확인합니다.
  5. 서명을 확인한 후에 페이로드 콘텐츠를 복호화합니다.
  6. 메시지가 만료되지 않았는지 확인합니다. 현재 시간이 복호화된 콘텐츠의 messageExpiration 필드보다 이전인지 확인하면 됩니다.
  7. 복호화된 콘텐츠의 결제 수단을 사용하여 청구합니다.

Tink 라이브러리의 샘플 코드는 1~6단계를 수행합니다.

결제 수단 토큰 구조

PaymentData 응답에서 Google이 반환하는 메시지는 다음 표에 지정된 키가 있는, UTF-8로 인코딩되고 직렬화된 JSON 객체입니다.

이름 유형 설명
protocolVersion 문자열 메시지가 생성된 암호화 또는 서명 스키마를 식별합니다. 필요한 경우 시간이 지남에 따라 프로토콜이 업그레이드될 수 있습니다.
signature 문자열 Google에서 보낸 메시지인지 확인합니다. base64로 인코딩되고 중간 서명 키에서 ECDSA를 사용하여 생성됩니다.
intermediateSigningKey 객체 Google의 중간 서명 키가 포함된 JSON 객체입니다. keyValue, keyExpiration, signatures가 있는 signedKey를 포함하며 중간 서명 키 서명 확인 프로세스를 간소화하기 위해 직렬화됩니다.
signedMessage 문자열 encryptedMessage, ephemeralPublicKey, tag가 포함된 문자열로 직렬화된 JSON 객체입니다. 서명 확인 프로세스를 간소화하기 위해 직렬화됩니다.

예시

다음은 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 밀리초로 만료되는 날짜 및 시간입니다. 통합업체는 만료된 모든 키를 거부합니다.

서명된 메시지

signedMessage는 다음 값을 포함하는 UTF-8로 인코딩되고 직렬화된 JSON 객체입니다.

이름 유형 설명
encryptedMessage 문자열 base64로 인코딩되고 암호화된 메시지로, 결제 정보와 몇 가지 추가 보안 필드를 포함합니다.
ephemeralPublicKey 문자열 base64로 인코딩된 임시 공개 키로, 비압축 포인트 형식으로 메시지를 암호화하기 위해 비공개 키와 연결되어 있습니다. 자세한 내용은 암호화 공개 키 형식을 참조하세요.
tag 문자열 encryptedMessage의 base64로 인코딩된 MAC입니다.

암호화된 메시지

복호화된 encryptedMessage는 UTF-8로 인코딩되고 직렬화된 JSON 객체입니다. JSON에는 2가지 레벨이 있습니다. 외부 레벨에는 보안을 위해 포함된 메타 데이터와 필드가 있고 내부 레벨은 실제 결제 사용자 인증 정보를 나타내는 다른 JSON 객체입니다.

encryptedMessage에 대한 자세한 내용은 다음 표 및 JSON 객체 예시를 참조하세요.

이름 유형 설명
gatewayMerchantId 문자열

판매자가 요청한 메시지인지 확인하기 위해 대행업체가 이해하고 사용하는 고유한 판매자 ID입니다. 대행업체가 만들고 판매자가 Android 또는 PaymentMethodTokenizationSpecification에서 Google에 전달합니다.

messageExpiration 문자열 메시지가 에포크 이후 UTC 밀리초로 만료되는 날짜 및 시간입니다. 통합업체는 만료된 모든 메시지를 거부해야 합니다.
messageId 문자열 취소하거나 나중에 찾아야 할 경우 메시지를 식별하는 고유 ID입니다.
paymentMethod 문자열 결제 사용자 인증 정보 유형입니다. 현재 CARD만 지원됩니다.
paymentMethodDetails 객체 결제 사용자 인증 정보입니다. 이 객체의 형식은 paymentMethod로 결정되며 다음 표에 설명되어 있습니다.

카드

다음 속성은 CARD 결제 수단의 결제 사용자 인증 정보를 구성합니다.

이름 유형 설명
pan 문자열 청구 대상 개인 계좌 번호입니다. 이 문자열에는 숫자만 포함됩니다.
expirationMonth 숫자 카드의 만료 월입니다(예: 1 = 1월, 2 = 2월 등).
expirationYear 숫자 카드의 4자리 만료 연도입니다(예: 2020).
authMethod 문자열 카드 거래의 인증 방법입니다.
assuranceDetails AssuranceDetailsSpecifications 이 객체는 반환된 결제 사용자 인증 정보에 수행된 유효성 검사에 대한 정보를 제공합니다.

PAN_ONLY

다음 JSON 스니펫은 PAN_ONLY authMethod를 사용하는 CARD paymentMethod 에 대한 전체 encryptedMessage의 예시입니다.

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

CRYPTOGRAM_3DS

3D Secure 암호(CRYPTOGRAM_3DS authMethod)를 사용하여 인증된 CARD입니다. 여기에는 다음과 같은 추가 필드가 포함됩니다.

이름 유형 설명
cryptogram 문자열 3D Secure 암호입니다.
eciIndicator 문자열 항상 존재하는 값은 아닙니다. 이 값은 Visa 카드 네트워크 토큰에 대해서만 반환되며 결제 승인 요청에서 전달됩니다.

다음 JSON 스니펫은 CRYPTOGRAM_3DS authMethod를 사용하는CARD paymentMethod에 대한 전체 encryptedMessage의 예시입니다.

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

서명 확인

중간 키 및 메시지 서명을 포함하는 서명을 확인하려면 다음 항목이 필요합니다.

  • 서명을 만드는 데 사용되는 알고리즘
  • 서명을 만드는 데 사용되는 바이트 문자열
  • 서명을 만드는 데 사용되는 비공개 키에 해당하는 공개 키
  • 서명 자체

서명 알고리즘

Google은 Elliptic Curve Digital Signature Algorithm(ECDSA)을 사용하여 메시지에 서명합니다. 이때 ECDSA에서 사용하는 매개변수는 FIPS 186-4에 정의된 대로 해시 함수가 SHA-256인 NIST P-256입니다.

서명

서명은 메시지의 가장 바깥쪽 레벨에 포함되며 ASN.1 바이트 형식으로 base64를 사용하여 인코딩됩니다. ASN.1에 대한 자세한 내용은 IETF Tools Appendix A를 참조하세요. 서명은 ECDSA 정수 r과 s로 구성됩니다. 자세한 내용은 서명 생성 알고리즘을 참조하세요.

다음은 지정된 ASN.1 바이트 형식의 예시이며 JCE(Java Cryptography Extension) 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로 인코딩되어야 합니다. signedKeyintermediateSigningKey.signedKey의 문자열이어야 합니다. 각 구성요소의 바이트 길이는 Little Endian 형식의 4바이트입니다.

예시

이 예시에서는 다음 결제 수단 토큰 샘플을 사용합니다.

{
  "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_idGoogle일 경우 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의 모든 서명을 반복해야 하며 keys.json의 만료되지 않은 Google 서명 키를 사용하여 각 서명을 검증해 보세요. 서명이 1개 이상 검증되면 확인이 완료된 것으로 간주합니다. 나중에 intermediateSigningKey.signedKey.keyValue를 사용하여 signedStringForMessageSignature를 확인하세요. 자체 인증 코드보다는 기존 암호화 라이브러리를 사용하는 것이 좋습니다.

메시지 서명에 사용할 바이트 문자열을 구성하는 방법

결제 수단 토큰 샘플에서 서명을 검증하려면 다음 식을 사용하여 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로 인코딩되어야 합니다. 각 구성요소의 바이트 길이는 Little Endian 형식의 4바이트입니다.

예시

다음은 결제 수단 토큰 샘플 예시입니다.

{
  "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_idmerchant:merchantId입니다. merchantId는 프로덕션 액세스 권한이 있는 판매자의 Google Pay 비즈니스 콘솔에 있는 값과 일치합니다.

sender_idGoogle이고 recipient_idmerchant: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.keyValuesignedMessage를 확인하는 데 사용됩니다. 자체 인증 코드보다는 기존 암호화 라이브러리를 사용하는 것이 좋습니다.

암호화 스키마 사양

Google은 Elliptic Curve Integrated Encryption Scheme(ECIES)을 사용하여 Google Pay API 응답에서 반환된 결제 수단 토큰을 보호합니다. 암호화 스키마는 다음 매개변수를 사용합니다.

매개변수 정의
키 캡슐화 메서드

ISO 18033-2에 정의된 ECIES-KEM

  • 타원 곡선: NIST P-256(OpenSSL에서 prime256v1이라고도 함)입니다.
  • CheckMode, OldCofactorMode, SingleHashMode, CofactorMode는 0입니다.
  • 포인트 형식이 압축되지 않았습니다.
키 파생 함수

SHA-256을 사용하는 HMAC 기반(HKDFwithSHA256)

  • 솔트를 제공해서는 안 됩니다.
  • 프로토콜 버전 ECv2에 대한 정보는 Google이 인코딩한 ASCII 형식이어야 합니다.
  • 256비트는 AES256 키에 대해 파생되어야 하고 다른 256비트는 HMAC_SHA256 키에 대해 파생되어야 합니다.
대칭 암호화 알고리즘

ISO 18033-2에 정의된 DEM2

암호화 알고리즘: zero IV를 포함하며 패딩되지 않은 AES-256-CTR

MAC 알고리즘 키 파생 함수에서 파생된 256비트 키를 사용하는 HMAC_SHA256

암호화 공개 키 형식

Google 페이로드에서 반환된 암호화 공개 키와 ephemeralPublicKey는 비압축 포인트 형식의 base64 키 표현을 사용하여 형식이 지정되며 다음 두 요소로 구성됩니다.

  • 형식을 지정하는 1개의 매직 넘버(0x04)
  • 타원 곡선에서 X 및 Y 좌표를 나타내는 32바이트의 큰 정수 2개

이 형식에 대한 자세한 설명은 '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로 인코딩된 공개 키 생성

이전의 선택적 단계 예시에서 생성된 비공개 및 공개 키는 16진수로 인코딩됩니다. 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단계: PKCS # 8 형식의 base64로 인코딩된 비공개 키 생성

Tink 라이브러리에서 비공개 키는 PKCS # 8 형식의 base64로 인코딩되어야 합니다. 1단계에서 생성된 비공개 키에서 이 형식의 비공개 키를 생성하려면 다음 명령어를 사용하세요.

openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -nocrypt | base64 | paste -sd "\0" -

이 명령어는 다음과 유사한 결과를 생성합니다.

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n

결제 수단 토큰을 복호화하는 방법

토큰을 복호화하려면 다음 단계를 따르세요.

  1. 비공개 키와 제공된 ephemeralPublicKey를 사용하여 ECIES-KEM을 사용하는 512비트의 공유 키를 가져옵니다. 다음 매개변수를 사용하세요.
    • 타원 곡선: NIST P-256(OpenSSL에서 prime256v1이라고도 함)입니다.
    • CheckMode, OldCofactorMode, SingleHashMode, CofactorMode0입니다.
    • 인코딩 함수: 비압축 포인트 형식입니다.
    • 키 파생 함수: RFC 5869에 다음 매개변수와 함께 설명되어 있는 HKDFwithSHA256입니다.
      • 솔트를 제공해서는 안 됩니다. RFC에 따라 이 값은 0으로 된 32바이트 솔트와 같아야 합니다.
  2. 생성된 키를 256비트의 키 2개(symmetricEncryptionKeymacKey)로 분할합니다.
  3. tag 필드가 encryptedMessage의 유효한 MAC인지 확인합니다.

    예상 MAC를 생성하려면 해시 함수 SHA256 및 2단계에서 얻은 macKey와 함께 HMAC(RFC 5869)를 사용하세요.

  4. AES-256-CTR 모드를 사용하고 다음을 사용하여 encryptedMessage를 복호화합니다.

    • zero IV
    • 패딩되지 않음
    • 2단계에서 파생된 symmetricEncryptionKey

키 관리

판매자 암호화 키

판매자는 암호화 스키마 사양에 설명된 사양에 따라 공개 키를 생성합니다.

Google 루트 서명 키

Google은 공개 URL에서 가져올 수 있는 현재 유효한 루트 서명 공개 키 집합을 게시합니다. 키는 URL에서 반환한 HTTP 캐시 헤더가 표시하는 한 유효하며 keyExpiration 필드에 명시된 만료일 전까지 캐시됩니다. 가져온 키가 만료되면 공개 URL에서 키를 다시 가져와 현재 유효한 키 목록을 수신하는 것이 좋습니다.

ECv2 프로토콜 예외: 런타임 시 Google에서 키를 가져올 수 없는 경우 프로덕션 URL에서 keys.json을 가져와 시스템에 저장하고 정기적으로 수동 새로고침을 수행합니다. 일반적으로 Google은 만료일이 가장 많이 남아 있는 키가 만료되기 5년 전에 ECv2의 새 루트 서명 키를 발급합니다. 키가 손상된 경우 Google은 keys.json을 신속하게 새로고침하도록 셀프서비스 포털에 있는 연락처 정보를 통해 모든 판매자에게 알립니다. keys.json의 콘텐츠에 Google 키를 저장하도록 선택한 판매자는 연간 자체 키 순환의 일부로 매년 새로고침하여 정기 순환 시기를 놓치지 않도록 합니다.

공개 URL을 통해 제공되는 키는 다음 형식으로 매핑됩니다.

{
  "keys": [
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv2"
      "keyExpiration":"2000000000000"
    },
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv2"
      "keyExpiration":"3000000000000"
    }
  ]
}

keyValue는 X.509 표준에 정의된 ASN.1 유형 SubjectPublicKeyInfo로 인코딩되고, 래핑되거나 패딩되지 않은 base64 키 버전입니다. 자바에서 참조된 ASN.1 인코딩은 X509EncodedKeySpec 클래스로 표현되며 ECPublicKey.getEncoded()로 얻을 수 있습니다.

테스트 환경과 프로덕션 환경에 대한 URL은 다음 링크에서 제공됩니다.

키 순환

직접 통합하여 서버에서 직접 결제 수단 토큰을 복호화하는 경우 키를 매년 순환해야 합니다.

암호화 키를 순환하려면 다음 단계를 완료하세요.

  1. OpenSSL을 사용하여 새 키 쌍을 생성합니다.
  2. 이전에 Google Pay에 개발자로 가입할 때 사용한 Google 계정에 로그인한 상태에서 Google Pay 비즈니스 콘솔을 엽니다.
  3. 통합 탭의 직접 통합 창에서 기존 공개 키 옆에 있는 관리를 클릭합니다. 다른 키 추가를 클릭합니다.
  4. 공개 암호화 키 텍스트 입력 필드를 선택하고 비압축 포인트 형식의 base64로 인코딩된 신규 생성 공개 키를 추가합니다.
  5. 암호화 키 저장을 클릭합니다.
  6. 원활한 키 순환을 위해 키를 전환하는 동안 새 비공개 키 및 이전 비공개 키의 복호화를 모두 지원합니다.

    Tink 라이브러리를 사용하여 토큰을 복호화하는 경우 다음 자바 코드를 사용하여 여러 비공개 키를 지원하세요.

    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에 개발자로 가입할 때 사용한 Google 계정에 로그인한 상태에서 Google Pay 비즈니스 콘솔을 엽니다.
  12. 통합 탭의 직접 통합 창에서 기존 공개 키 옆에 있는 관리를 클릭합니다. 이전 공개 키 옆의 삭제를 클릭하고 암호화 키 저장을 클릭합니다.

Google은 다음 예시와 같이 PaymentMethodTokenizationSpecification parameters 객체 내 publicKey 속성에 지정된 키를 사용합니다.

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

Tink 라이브러리를 사용하여 암호화된 응답 관리

서명 확인 및 메시지 복호화를 수행하려면 Tink 암호화 라이브러리를 사용합니다. Tink와 통합하고 확인 및 복호화를 수행하려면 다음 단계를 완료하세요.

  1. pom.xml에서 Tink paymentmethodtoken 앱을 종속 항목으로 추가합니다.

    <dependencies>
      <!-- other dependencies ... -->
      <dependency>
        <groupId>com.google.crypto.tink</groupId>
        <artifactId>apps-paymentmethodtoken</artifactId>
        <version>1.2.0</version>
      </dependency>
    </dependencies>
    
  2. 서버 시작 시 메모리에서 사용할 수 있도록 Google 서명 키를 프리페치합니다. 이렇게 하면 복호화 프로세스에서 키를 가져오는 동안 네트워크 지연 시간이 발생하지 않습니다.

    GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground();
  3. 다음 코드를 사용하여 키를 복호화합니다. 이때 paymentMethodTokenencryptedMessage 변수에 저장되어 있다고 가정하고 굵게 표시된 섹션을 상황에 따라 바꿉니다.

    환경 테스트의 경우 INSTANCE_PRODUCTIONINSTANCE_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. PrivateKey1PrivateKey2를 자체 키로 바꿉니다. 변수는 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);
    

    프로덕션 환경의 현재 키는 키가 손상된 경우를 제외하고는 일반적으로 2038년 4월 14일까지 유효합니다. 키가 손상된 경우 Google은 keys.json을 신속하게 새로고침하도록 셀프서비스 포털에 있는 연락처 정보를 통해 모든 판매자에게 알립니다.

    코드 스니펫에서 다음 보안 세부정보를 처리하므로 대행업체는 페이로드 사용에 집중할 수 있습니다.

    • 메모리에서 가져오고 캐시된 Google 서명 키
    • 서명 확인
    • 복호화