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)