The Shop Pay Wallet will return plain (untokenized) credit card information for use in processing payments. This will consist of:
- [Primary Account Number (PAN)](https://en.wikipedia.org/wiki/Payment_card_number) (i.e. the card number)
- Full name on the card
- Expiry month and year
- The card brand
This setup allows your integration to be used for placing orders with both Shopify and non-Shopify merchants on an external surface, since the information returned can be used within any payment processor to extract payment. But, for the same reason, it also presents a significant security risk.
Your requests to our API need to occur via an encrypted SSL/TLS HTTP connection, which ensures a layer of security. But, in order to further mitigate the risks of passing raw PANs over the wire, we have added an additional layer of encryption to protect this data. We use an integrated encryption process based on the [Elliptic Curve Integrated Encryption Scheme (ECIES)](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme) to additionally encrypt the information required to process payment.
With this setup, we guarantee the following:
- All requests between your systems and the Shop Pay Wallet are done using a secure connection
- We'll only return encrypted payment information if we can verify your system's identity
- We'll never return unencrypted payment information to you
- Only your system that requested payment information from the Shop Pay Wallet will be able to decrypt the payload we provide
This guide explains how to work with this payment method encryption setup. This guide uses OpenSSL and Ruby in its examples.
## Encryption setup
This section details setup steps you must take to request and decrypt encrypted payment information from the Shop Pay Wallet. You will need to work with a Shop team member for part of this.
The end result of this setup work will be:
- A private encryption key known only to you, which your systems will use to decrypt and verify the payload
- A PEM-formatted public certificate signed by Shopify's Certificate Authority (CA), which Shop Pay will use to encrypt the payload
### Step 1: Generate a public/private key-pair and CSR
You'll start by generating a public/private elliptic curve (EC) keypair using the NIST P-256 method, which is implemented as the `prime256v1` extension in OpenSSL. You'll use both keys to additionally generate a Certificate Signing Request (CSR), which will later be exchanged with Shopify for a PEM-formatted public certificate used in encryption requests. You'll also later use the private key during the decryption process.
It's important that the CSR has a complete Subject and that the Subject's CN is formatted as `Shopify Payments Partner - {partner_name}`.
You can generate a CSR that has a shopping platform named `Foobar Shopping`, which is maintained by `Company, Inc.`, and headquartered in `CA` (Canada), `ON` (Ontario), `Ottawa`, using the following Ruby code or the `openssl` shell command:
```ruby?title: 'Ruby', filename: 'keypair.rb'
require 'openssl'
# Generate the keypair
key = OpenSSL::PKey::EC.generate('prime256v1')
ec_group = OpenSSL::PKey::EC::Group.new('prime256v1')
# Build the public key for signing the CSR
public_key = OpenSSL::PKey::EC.new(ec_group)
public_key.public_key = OpenSSL::PKey::EC::Point.new(
ec_group,
OpenSSL::BN.new(key.public_key.to_bn.to_s(16), 16)
)
# Build the private key for signing the CSR
private_key = OpenSSL::PKey::EC.new(ec_group)
private_key.private_key = OpenSSL::BN.new(key.private_key.to_s(16), 16)
# Define the Subject
csr_subject = '/C=CA/ST=ON/L=Ottawa/O=Company, Inc./OU=Shopping/CN=Shopify Payments Partner - Foobar Shopping'
# Create and sign the CSR
csr = OpenSSL::X509::Request.new
csr.version = 1
csr.subject = OpenSSL::X509::Name.parse(csr_subject)
csr.public_key = public_key
csr.sign(private_key, OpenSSL::Digest::SHA256.new)
# Write the private key and CSR to files for later use
File.open('./private_key.pem', 'wb') { |f| f.print(key.to_pem) }
File.open('./csr.pem', 'wb') { |f| f.print(csr.to_pem) }
```
```sh?title: 'sh', filename: 'keypair.sh'
openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
openssl ec -in private_key.pem -pubout -out public_key.pem
openssl req \
-new \
-sha256 \
-key private_key.pem \
-out csr.pem \
-subj "/C=CA/ST=ON/L=Ottawa/O=Company, Inc./OU=Shopping/CN=Shopify Payments Partner - Foobar Shopping"
```
The methods above, for example, create an EC private key file and a CSR file with the following contents:
```
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAJa4IU/2qt/Eot8+/tqvyDZwF6Bm2gn17L0tq3LWNFroAoGCCqGSM49
AwEHoUQDQgAE64Qk3dBVKeP9WajKkhNNtLltP8UWZfbG50nTRyUuJ7X06yrTlFzR
b+HtarJjDWcD9kTNi+mz+1kiQE57L3HuLw==
-----END EC PRIVATE KEY-----
```
```
-----BEGIN CERTIFICATE REQUEST-----
MIIBRjCB7gIBATCBizELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMQ8wDQYDVQQH
DAZPdHRhd2ExFjAUBgNVBAoMDUNvbXBhbnksIEluYy4xETAPBgNVBAsMCFNob3Bw
aW5nMTMwMQYDVQQDDCpTaG9waWZ5IFBheW1lbnRzIFBhcnRuZXIgLSBGb29iYXIg
U2hvcHBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATrhCTd0FUp4/1ZqMqS
E020uW0/xRZl9sbnSdNHJS4ntfTrKtOUXNFv4e1qsmMNZwP2RM2L6bP7WSJATnsv
ce4voAAwCgYIKoZIzj0EAwIDRwAwRAIgdZuoUvDo5G9810z9UDRgKVhy6QqY5UMW
0owwbapNr5cCIGNB3E8zQWawwQpjsHgFSt1QN5Nl7ZCmUtY1C9gx3zBo
-----END CERTIFICATE REQUEST-----
```
You can introspect these two credentials to assert that the CSR signing process was successful. When you introspect the private key, you should see a `pub` and `priv` value:
```ruby?title: 'Ruby', filename: 'introspect_key.rb'
require 'openssl'
private_key = OpenSSL::PKey::EC.new(File.read('./private_key.pem'))
puts private_key.to_text
```
```sh?title: 'sh', filename: 'introspect_key.sh'
openssl pkey -noout -text -in private_key.pem
```
```changeme
Private-Key: (256 bit)
priv:
02:5a:e0:85:3f:da:ab:7f:12:8b:7c:fb:fb:6a:bf:
20:d9:c0:5e:81:9b:68:27:d7:b2:f4:b6:ad:cb:58:
d1:6b
pub:
04:eb:84:24:dd:d0:55:29:e3:fd:59:a8:ca:92:13:
4d:b4:b9:6d:3f:c5:16:65:f6:c6:e7:49:d3:47:25:
2e:27:b5:f4:eb:2a:d3:94:5c:d1:6f:e1:ed:6a:b2:
63:0d:67:03:f6:44:cd:8b:e9:b3:fb:59:22:40:4e:
7b:2f:71:ee:2f
ASN1 OID: prime256v1
NIST CURVE: P-256
```
Then, if you introspect the CSR, you should see:
1. The same `pub` value encoded into the CSR
2. The full Subject string
```ruby?title: 'Ruby', filename: 'introspect_csr.rb'
require 'openssl'
csr = OpenSSL::X509::Request.new(File.read('./csr.pem'))
puts csr.to_text
```
```sh?title: 'CLI', filename: 'introspect_csr.sh'
openssl req -noout -text -in csr.pem
```
```
Certificate Request:
Data:
Version: 1 (0x1)
Subject: C=CA, ST=ON, L=Ottawa, O=Company, Inc., OU=Shopping, CN=Shopify Payments Partner - Foobar Shopping
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:eb:84:24:dd:d0:55:29:e3:fd:59:a8:ca:92:13:
4d:b4:b9:6d:3f:c5:16:65:f6:c6:e7:49:d3:47:25:
2e:27:b5:f4:eb:2a:d3:94:5c:d1:6f:e1:ed:6a:b2:
63:0d:67:03:f6:44:cd:8b:e9:b3:fb:59:22:40:4e:
7b:2f:71:ee:2f
ASN1 OID: prime256v1
NIST CURVE: P-256
Attributes:
a0:00
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:75:9b:a8:52:f0:e8:e4:6f:7c:d7:4c:fd:50:34:
60:29:58:72:e9:0a:98:e5:43:16:d2:8c:30:6d:aa:4d:af:97:
02:20:63:41:dc:4f:33:41:66:b0:c1:0a:63:b0:78:05:4a:dd:
50:37:93:65:ed:90:a6:52:d6:35:0b:d8:31:df:30:68
```
Before moving on to the next step, you must preserve the private key in a secure location. You will later use the private key to decrypt and verify encrypted payment payloads.
#### Private key formatting
OpenSSL and elliptic curve encryption support multiple formats for private keys. In the examples above, we generate and use a private key using Elliptic Curve (EC) format. You can use EC private keys only for elliptic curve encryption. Private keys in the EC format have the following PEM-file header:
```
-----BEGIN EC PRIVATE KEY-----
```
Another commonly used private key format is PKCS8 formatting. PKCS8 is a generic formatting, meaning private keys in this format work with multiple encryption schemes. These private keys are lengthier when encoded into a PEM-file, and have a different PEM-file header:
```
-----BEGIN PRIVATE KEY-----
```
You can use either of these two formats for the private key you generate for the Shop Pay Wallet ECIES. We recognize that some teams have strong opinions on private key formats. If you wish to use PKCS8 formatting specifically, you can convert an EC formatted private key PEM-file into a PKCS8 formatted PEM-file using the following OpenSSL command:
```sh
openssl pkcs8 -topk8 -nocrypt -in private_key.pem -out private_key_pkcs8.pem
```
If we were to convert our example EC formatted private key from above into a PKCS8 formatted key, it should look like the following:
```
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgAlrghT/aq38Si3z7
+2q/INnAXoGbaCfXsvS2rctY0WuhRANCAATrhCTd0FUp4/1ZqMqSE020uW0/xRZl
9sbnSdNHJS4ntfTrKtOUXNFv4e1qsmMNZwP2RM2L6bP7WSJATnsvce4v
-----END PRIVATE KEY-----
```
You need to convert your EC private key to a PKCS8 private key before signing your CSR with your private key.
#### Private key signing bytes
In the examples above, we are using a private key without a signing byte.
Specific versions of OpenSSL preprend a "signing byte" to private key values. The signing byte indicates the private key version. The signing byte is not actually part of the private key, but rather encoded into the PEM-file and included in the introspected textual output of the private key in OpenSSL commands.
As a comparative example, here is an EC formatted private key encoded with a "signing byte" in a PEM-file:
```
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJ3d4MDjMYtfq4an8J7biudUvr4qD5Hwjp+5WNaMovbyoAoGCCqGSM49
AwEHoUQDQgAEr96eU7+1mx7gTV0V8T3wdtN5/fOxGZP+jGmk4Db4N7LMUUF43fYn
eTINwcHdaLOrpFqcTswPSqDFd61V6PEqFw==
-----END EC PRIVATE KEY-----
```
And here is that same private key introspected into a textual output. Notice the `00:` prefixing the private key text.
```
Private-Key: (256 bit)
priv:
00:9d:dd:e0:c0:e3:31:8b:5f:ab:86:a7:f0:9e:db:
8a:e7:54:be:be:2a:0f:91:f0:8e:9f:b9:58:d6:8c:
a2:f6:f2
pub:
04:af:de:9e:53:bf:b5:9b:1e:e0:4d:5d:15:f1:3d:
f0:76:d3:79:fd:f3:b1:19:93:fe:8c:69:a4:e0:36:
f8:37:b2:cc:51:41:78:dd:f6:27:79:32:0d:c1:c1:
dd:68:b3:ab:a4:5a:9c:4e:cc:0f:4a:a0:c5:77:ad:
55:e8:f1:2a:17
ASN1 OID: prime256v1
NIST CURVE: P-256
```
Similar to how we support both EC and PKCS8 private key formats, you can use private keys with or without a signing byte encoded.
### Step 2: Joint validation of the CSR
Next, you should share the CSR PEM-file with Shopify. This should be done via a secure channel, such as company email.
Before [you generate a PEM-formatted public certificate signed by Shopify's CA](#step-3-shopify-generates-a-public-certificate), both parties must jointly verify the CSR's authenticity using a separate communications platform. You should verify the CSR's authenticity on a separate communication platform to ensure that the CSR wasn't tampered with when the CSR was shared with Shopify.
You should generate a fingerprint of the CSR file, so you can verify that you and a Shopify team member have generated identical CSR fingerprints. For example, you can use Ruby or the `openssl` shell command to generate a fingerprint of the CSR:
```ruby?title: 'Ruby', filename: 'csr_to_text.rb'
require 'openssl'
puts OpenSSL::Digest::SHA256.new(File.read('./csr.pem'))
```
```sh?title: 'sh', filename: 'csr_to_text.sh'
openssl dgst -sha256 csr.pem
```
```
SHA256(csr.pem)= f1e1279b4fe392bcc0123cc96c01a3430d9600cc22c7fc9ca6a34be6208c0a11
```
### Step 3: Shopify generates a public certificate
Next, a Shopify team member will use your CSR to generate a public certificate signed by Shopify's Certificate Authority. They will share it back with you in a PEM-file.
The following text is an example of a PEM-formatted public certificate:
```
-----BEGIN CERTIFICATE-----
MIICDDCCAbOgAwIBAgIUR352YtBzbvY9dhcRHwn4lWyinaQwCgYIKoZIzj0EAwIw
bzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90dGF3
YTEQMA4GA1UEChMHU2hvcGlmeTErMCkGA1UEAxMiU2hvcGlmeSBQYXltZW50IFBh
cnRuZXIgQ0EgU3RhZ2luZzAeFw0yMTA5MjMxMjEzMzdaFw0yMjA5MjMxMjE0MDda
MDUxMzAxBgNVBAMTKlNob3BpZnkgUGF5bWVudHMgUGFydG5lciAtIEZvb2JhciBT
aG9wcGluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOuEJN3QVSnj/VmoypIT
TbS5bT/FFmX2xudJ00clLie19Osq05Rc0W/h7WqyYw1nA/ZEzYvps/tZIkBOey9x
7i+jZzBlMA4GA1UdDwEB/wQEAwIDuDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNV
HQ4EFgQUeI1U+LdZT6+qBN8VRK6tdW4/hvowHwYDVR0jBBgwFoAUkIAYCIcnGnIa
a0z8DtxEg//XLWYwCgYIKoZIzj0EAwIDRwAwRAIga+EqwdSNtbdVZ3r+7yPoMFVk
ubv/HO2+zH/BMzt247sCICWHJCXv74zrZccSWFuPKBBtywTEa67S9GKlfL4+q+kQ
-----END CERTIFICATE-----
```
You can also introspect the public certificate to validate that it's encoded with the expected metadata. In the example above, the introspected public certificate has the following contents:
- The private key's `pub` fingerprint under the `Subject Public Key Info` section.
- The CSR's Subject CN value (`Shopify Payments Partner - Foobar Shopping`).
- An `Issuer` that identifies Shopify's CA. In the examples below, the CA is from Shopify's staging environment. In production, the CA signature looks different.
```ruby?title: 'Ruby', filename: 'introspect_cert.rb'
require 'openssl'
cert = OpenSSL::X509::Certificate.new(File.read('./cert.pem'))
puts cert.to_text
```
```sh?title: 'CLI', filename: 'introspect_cert.sh'
openssl x509 -noout -text -in cert.pem
```
```
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
47:7e:76:62:d0:73:6e:f6:3d:76:17:11:1f:09:f8:95:6c:a2:9d:a4
Signature Algorithm: ecdsa-with-SHA256
Issuer: C=CA, ST=Ontario, L=Ottawa, O=Shopify, CN=Shopify Payment Partner CA Staging
Validity
Not Before: Sep 23 12:13:37 2021 GMT
Not After : Sep 23 12:14:07 2022 GMT
Subject: CN=Shopify Payments Partner - Foobar Shopping
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:eb:84:24:dd:d0:55:29:e3:fd:59:a8:ca:92:13:
4d:b4:b9:6d:3f:c5:16:65:f6:c6:e7:49:d3:47:25:
2e:27:b5:f4:eb:2a:d3:94:5c:d1:6f:e1:ed:6a:b2:
63:0d:67:03:f6:44:cd:8b:e9:b3:fb:59:22:40:4e:
7b:2f:71:ee:2f
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Data Encipherment, Key Agreement
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Subject Key Identifier:
78:8D:54:F8:B7:59:4F:AF:AA:04:DF:15:44:AE:AD:75:6E:3F:86:FA
X509v3 Authority Key Identifier:
keyid:90:80:18:08:87:27:1A:72:1A:6B:4C:FC:0E:DC:44:83:FF:D7:2D:66
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:6b:e1:2a:c1:d4:8d:b5:b7:55:67:7a:fe:ef:23:
e8:30:55:64:b9:bb:ff:1c:ed:be:cc:7f:c1:33:3b:76:e3:bb:
02:20:25:87:24:25:ef:ef:8c:eb:65:c7:12:58:5b:8f:28:10:
6d:cb:04:c4:6b:ae:d2:f4:62:a5:7c:be:3e:ab:e9:10
```
## Generating the Encryption Certificate HTTP header
Before you confirm an order with Shop Pay to receive the encrypted credit card information, you'll need to ensure that
the HTTP request's `Certificate` header for the [Confirm an order and retrieve payment](/docs/api/shop-pay-wallet/reference/index#confirm-an-order-and-retrieve-payment-information) endpoint is using the correct format.
When you make an HTTP request to this endpoint, you need to include the public certificate using one of the following methods:
- Encode the PEM-formatted public certificate as a Base64-encoded string in an HTTP request header named `Certificate`
- Send the PEM-formatted public certificate body that has the newlines stripped from it in an HTTP request header named `Certificate`
The following text is an example of a PEM-formatted public certificate:
```
-----BEGIN CERTIFICATE-----
MIICDDCCAbOgAwIBAgIUR352YtBzbvY9dhcRHwn4lWyinaQwCgYIKoZIzj0EAwIw
bzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90dGF3
YTEQMA4GA1UEChMHU2hvcGlmeTErMCkGA1UEAxMiU2hvcGlmeSBQYXltZW50IFBh
cnRuZXIgQ0EgU3RhZ2luZzAeFw0yMTA5MjMxMjEzMzdaFw0yMjA5MjMxMjE0MDda
MDUxMzAxBgNVBAMTKlNob3BpZnkgUGF5bWVudHMgUGFydG5lciAtIEZvb2JhciBT
aG9wcGluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOuEJN3QVSnj/VmoypIT
TbS5bT/FFmX2xudJ00clLie19Osq05Rc0W/h7WqyYw1nA/ZEzYvps/tZIkBOey9x
7i+jZzBlMA4GA1UdDwEB/wQEAwIDuDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNV
HQ4EFgQUeI1U+LdZT6+qBN8VRK6tdW4/hvowHwYDVR0jBBgwFoAUkIAYCIcnGnIa
a0z8DtxEg//XLWYwCgYIKoZIzj0EAwIDRwAwRAIga+EqwdSNtbdVZ3r+7yPoMFVk
ubv/HO2+zH/BMzt247sCICWHJCXv74zrZccSWFuPKBBtywTEa67S9GKlfL4+q+kQ
-----END CERTIFICATE-----
```
### Format 1
You can encode the PEM-formatted public certificate as Base64, and then use the Base64-encoded, PEM-formatted public certificate as your HTTP request's `Certificate` header.
To encode the PEM-formatted public certificate as Base64, you can use the `base64` shell command:
```bash
base64 certificate.pem
```
```
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNERENDQWJPZ0F3SUJBZ0lVUjM1Mll0Qnpidlk5ZGhjUkh3bjRsV3lpbmFRd0NnWUlLb1pJemowRUF3SXcKYnpFTE1Ba0dBMVVFQmhNQ1EwRXhFREFPQmdOVkJBZ1RCMDl1ZEdGeWFXOHhEekFOQmdOVkJBY1RCazkwZEdGMwpZVEVRTUE0R0ExVUVDaE1IVTJodmNHbG1lVEVyTUNrR0ExVUVBeE1pVTJodmNHbG1lU0JRWVhsdFpXNTBJRkJoCmNuUnVaWElnUTBFZ1UzUmhaMmx1WnpBZUZ3MHlNVEE1TWpNeE1qRXpNemRhRncweU1qQTVNak14TWpFME1EZGEKTURVeE16QXhCZ05WQkFNVEtsTm9iM0JwWm5rZ1VHRjViV1Z1ZEhNZ1VHRnlkRzVsY2lBdElFWnZiMkpoY2lCVAphRzl3Y0dsdVp6QlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJPdUVKTjNRVlNuai9WbW95cElUClRiUzViVC9GRm1YMnh1ZEowMGNsTGllMTlPc3EwNVJjMFcvaDdXcXlZdzFuQS9aRXpZdnBzL3RaSWtCT2V5OXgKN2kralp6QmxNQTRHQTFVZER3RUIvd1FFQXdJRHVEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFkQmdOVgpIUTRFRmdRVWVJMVUrTGRaVDYrcUJOOFZSSzZ0ZFc0L2h2b3dId1lEVlIwakJCZ3dGb0FVa0lBWUNJY25HbklhCmEwejhEdHhFZy8vWExXWXdDZ1lJS29aSXpqMEVBd0lEUndBd1JBSWdhK0Vxd2RTTnRiZFZaM3IrN3lQb01GVmsKdWJ2L0hPMit6SC9CTXp0MjQ3c0NJQ1dISkNYdjc0enJaY2NTV0Z1UEtCQnR5d1RFYTY3UzlHS2xmTDQrcStrUQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
```
### Format 2
You can use the body of the PEM-formatted public certificate with newlines removed as the HTTP request's `Certificate` header. For example, to remove newlines from the Base64-encoded body of the PEM-formatted public certificate, and output the result to STDOUT, you can run the following shell command:
```bash
cat certificate.pem | sed '1,1d;$ d;' | tr -d '\n'
```
```
MIICDDCCAbOgAwIBAgIUR352YtBzbvY9dhcRHwn4lWyinaQwCgYIKoZIzj0EAwIwbzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xDzANBgNVBAcTBk90dGF3YTEQMA4GA1UEChMHU2hvcGlmeTErMCkGA1UEAxMiU2hvcGlmeSBQYXltZW50IFBhcnRuZXIgQ0EgU3RhZ2luZzAeFw0yMTA5MjMxMjEzMzdaFw0yMjA5MjMxMjE0MDdaMDUxMzAxBgNVBAMTKlNob3BpZnkgUGF5bWVudHMgUGFydG5lciAtIEZvb2JhciBTaG9wcGluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOuEJN3QVSnj/VmoypITTbS5bT/FFmX2xudJ00clLie19Osq05Rc0W/h7WqyYw1nA/ZEzYvps/tZIkBOey9x7i+jZzBlMA4GA1UdDwEB/wQEAwIDuDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUeI1U+LdZT6+qBN8VRK6tdW4/hvowHwYDVR0jBBgwFoAUkIAYCIcnGnIaa0z8DtxEg//XLWYwCgYIKoZIzj0EAwIDRwAwRAIga+EqwdSNtbdVZ3r+7yPoMFVkubv/HO2+zH/BMzt247sCICWHJCXv74zrZccSWFuPKBBtywTEa67S9GKlfL4+q+kQ
```
## Retrieving encrypted credit card information
After finishing the steps above, you can then request encrypted credit card information and decrypt it when retrieved, using the [Confirm an order](/docs/api/shop-pay-wallet/reference/index#confirm-an-order-and-retrieve-payment-information) endpoint.
Shopify uses the PEM-formatted public certificate included in the HTTP request headers to generate the encrypted credit card payload. If the request is successful, then the API response is returned in the following format:
```json
{
"user": {
"uuid": "..."
},
"card": {
"uuid": "...",
"billingAddress": {
"addressLine": ["123 High Street"],
"city": "Mont Royal",
"country": "CA",
"dependentLocality": "",
"organization": "",
"phone": "+15145551212",
"postalCode": "123456",
"recipient": "Jane Doe",
"region": "QC",
"sortingCode": ""
},
"lastFourDigits": "3949",
"network": "VISA",
"type": "CREDIT",
"fingerprint": "v1:protected:fingerprint=",
"encryptedPayload": {
"encryptedMessage": "Jl0/NqxGDpbh6tL1h2NMIlAEciumZabIyMz0stfthjAsx7eerCtyGBDUEd383GNnQX+YKVIyo7QNjJOSA+0jbpX11BoIjcxy8G/jmMbC/0a+D08ZGyM555AXSeuwmh2wcBjdFVzEEA==",
"ephemeralPublicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENCkzCU6e8svEmMYSi37ZK+khnbSE\nPiK+edwCv4CTVsDaEeComLP8cPNWcTcdmDHunr2lqBZDO4f1NWUGRyA/fg==\n-----END PUBLIC KEY-----\n",
"tag": "UgumzY6zONdSfH7aDrEDWw=="
}
},
"shippingAddress": {
"addressLine": ["123 High Street"],
"city": "Mont Royal",
"country": "CA",
"dependentLocality": "",
"organization": "",
"phone": "+15145551212",
"postalCode": "123456",
"recipient": "Jane Doe",
"region": "QC",
"sortingCode": ""
}
}
```
The `card.encryptedPayload` blob contains the encrypted credit card information, along with all the data you need to decrypt it! It can be documented as follows:
| Attribute | Type | Description |
| ------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| encryptedMessage | `Base64String` | A base64-encoded, encrypted, JSON-formatted string of the credit card data. |
| ephemeralPublicKey | `String (PEM)` | A plaintext, PEM-formatted string that is used to derive:
1. A shared secret for decrypting the `encryptedMessage`
2. An HMAC key used for verifying the `tag`
This attribute is labeled "ephemeral" as it will be unique to each encrypted payload. |
| tag | `Base64String` | A base64-encoded HMAC tag used to verify the authenticity of the payload. |
## Decrypting
You now have all the tools needed to decrypt and verify the payment information. This will involve the following steps:
1. Deriving a shared secret using the private key and the `ephemeralPublicKey`
2. Expanding the shared secret to find the encryption key and the HMAC key
3. Using the HMAC key to verify the encrypted payload authenticity
4. Using the encryption key to decrypt the `encryptedMessage`
Let's run through what this will look like:
```ruby?title: 'Ruby', filename: 'decryption.rb'
require 'openssl'
require 'base64'
### Setup ###
cipher = OpenSSL::Cipher.new('AES-256-CTR')
cipher_length_bytes = cipher.key_len
mac_digest = OpenSSL::Digest.new('SHA256')
mac_length_bytes = mac_digest.digest_length / 2
# Read the private key into a Ruby object
private_key = OpenSSL::PKey::EC.new(File.read('./private_key.pem'))
# Our values in the `encryptedPayload` in the API response
encrypted_message = 'Jl0/NqxGDpbh6tL1h2NMIlAEciumZabIyMz0stfthjAsx7eerCtyGBDUEd383GNnQX+YKVIyo7QNjJOSA+0jbpX11BoIjcxy8G/jmMbC/0a+D08ZGyM555AXSeuwmh2wcBjdFVzEEA=='
tag = 'UgumzY6zONdSfH7aDrEDWw=='
ephemeral_public_key = <<~EOC
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENCkzCU6e8svEmMYSi37ZK+khnbSE
PiK+edwCv4CTVsDaEeComLP8cPNWcTcdmDHunr2lqBZDO4f1NWUGRyA/fg==
-----END PUBLIC KEY-----
EOC
# Decoded data from the API response
encrypted_message_text = Base64.strict_decode64(encrypted_message)
mac = Base64.strict_decode64(tag)
### Decryption ###
# 1) Deriving the shared secret from the ephemeral public key
shared_secret = private_key.dh_compute_key(
OpenSSL::PKey::EC.new(ephemeral_public_key).public_key
)
# 2) Expanding the shared secret to find the encryption key and HMAC key. Salt and info should be empty.
key_pair = OpenSSL::KDF.hkdf(
shared_secret,
length: cipher_length_bytes + mac_length_bytes,
hash: 'SHA256',
salt: '',
info: ''
)
encryption_key = key_pair.byteslice(0, cipher_length_bytes)
hmac_key = key_pair.byteslice(-mac_length_bytes, mac_length_bytes)
# 3) Verify authenticity via the HMAC key
computed_mac = OpenSSL::HMAC.digest(
mac_digest,
hmac_key,
encrypted_message_text
).byteslice(0, mac_length_bytes)
raise OpenSSL::PKey::ECError, "Invalid Message Authenticaton Code" unless OpenSSL.secure_compare(computed_mac, mac)
# 4) Decrypt!
cipher.decrypt
cipher.iv = ("\x00" * 16).force_encoding(Encoding::BINARY)
cipher.key = encryption_key
decrypted_string = cipher.update(encrypted_message_text) + cipher.final
puts JSON.parse(decrypted_string)
```
```sh?title: 'CLI', filename: 'decryption.sh'
# TODO
```
The last line should print out a JSON blob of the decrypted credit card info. Our decrypted example response should look as follows:
```json
{
"type": "card",
"full_name": "Eren Yeager",
"pan": "4761209980003949",
"month": 9,
"year": 2024,
"brand": "visa"
}
```
## Related resources
- [Getting started](/docs/api/shop-pay-wallet/getting-started)
- [Authorization](/docs/api/shop-pay-wallet/authorization)
- [Shop Pay Wallet API reference](/docs/api/shop-pay-wallet/reference/index)
- [Testing the integration](/docs/api/shop-pay-wallet/testing)
- [Shop Pay Wallet ecosystem](/docs/api/shop-pay-wallet/ecosystem)