This specification defines an approach for encrypting sensitive payment method response data. It relies on JSON Web Encryption [[!RFC7516]] for algorithms and message structure.
The working group maintains a list of all bug reports that the group has not yet addressed. Pull requests with proposed specification text for outstanding issues are strongly encouraged.
This specification defines a way to encrypt payment method response data in conjunction with the PaymentRequest API [[!payment-request]].
End-to-end encryption from payment handler to processor can help address several use cases:
This document describes a standardized approach, that leverages a limited profile of JSON Web Encryption, to be used by a variety of payment method specifications. At a high level, it works as follows:
The profile defined in this specification limits the set of allowed algorithms. This is to prevent the use of algorithms that have been shown, since [[!RFC7518]] was published, to have security vulnerabilities.
The following examples illustrate how data from the Tokenized Card Payment Specification [[tokenized-card]] would be encrypted using this specification.
For simplicity, the sample code uses the open source [[!nimbus-jose-jwt]], however the use of any similar library that is conformant with [[!RFC7516]] will work.
This Java code sample shows how to generate a new 2048 bit RSA key pair using the default JVM security provider. This is not a JSON Web Token specific function and should be repeatable using any standard encryption library that conforms with [[!RFC3447]].
// Key generation KeyPairGenerator keyGenerator = null; try { keyGenerator = KeyPairGenerator.getInstance("RSA"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } keyGenerator.initialize(2048); KeyPair kp = keyGenerator.genKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) kp.getPrivate();
The output of this process, which will be performed by the merchant or their processor before creating a PaymentRequest, is a pair of RSA keys (one public and one private).
This public key is the "key encryption key" and is referenced from the request data of the PaymentRequest. The payment handler uses it to create the encrypted JSON Web Token.
The private key is retained by the merchant or processor and used to decrypt the encrypted JSON Web Token.
This example shows how to encrypt a JSON Web Token (JWT) claim set using an RSA public key (like the one generated above). This code is based on the example at [[!jwe-with-rsa-example]].
//Create an empty claims set JWTClaimsSet claimsSet = new JWTClaimsSet(); //Add claims to the claims set claimsSet.setCustomClaim("cardNumber", "5413339000001513"); claimsSet.setCustomClaim("expiryMonth", "12"); claimsSet.setCustomClaim("expiryYear", "20"); claimsSet.setCustomClaim("cryptogram", "AlhlvxmN2ZKuAAESNFZ4GoABFA=="); claimsSet.setCustomClaim("typeOfCryptogram", "UCAF"); claimsSet.setCustomClaim("trid", "9812345678"); claimsSet.setCustomClaim("eci", "242"); //Print JSON representation of claims set System.out.println(claimsSet.toJSONObject()); // Request that JWE is created RSA-OAEP-256 and 128-bit AES/GCM JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM); // Create the JWT object (not yet encrypted) EncryptedJWT jwt = new EncryptedJWT(header, claimsSet); // Create an encrypter with the public RSA key RSAEncrypter encrypter = new RSAEncrypter(publicKey); // Do the encryption try { //This library will generate a random content encryption key during this step //Other libararies may require that both the RSA key and the CEK are provided jwt.encrypt(encrypter); } catch (JOSEException e) { e.printStackTrace(); } // Serialise to JWT compact form String jwtString = jwt.serialize(); System.out.println(jwtString);
The output of this process is a new JSON Web Token where the content is encrypted using AES GCM and a content encryption key that is encrypted using RSA OAEP 256 and the public key provided.
This sample shows how to decrypt an encrypted JSON Web Token (JWT) using an RSA private key (like the one generated above). This would be performed by the merchant or their processor upon receiving the PaymentResponse.
// Parse the JWT from serialized string format jwt = EncryptedJWT.parse(jwtString); //Verify the JWT uses safe algorithms if(!jwt.getHeader().getAlgorithm().equals(JWEAlgorithm.RSA-OAEP-256)) throw new Exception("Invalid 'alg' header, only RSA-OAEP-256 is allowed.") if(!jwt.getHeader().getEncryptionMethod().equals(EncryptionMethod.A128GCM)) throw new Exception("Invalid 'enc' header, only A128GCM is allowed.") // Create a decrypter with the specified private RSA key RSADecrypter decrypter = new RSADecrypter(privateKey); //Decrypt JWT jwt.decrypt(decrypter); // Retrieve JWT claims String cardNumber = jwt.getJWTClaimsSet().getCustomClaim("cardNumber"); String expiryMonth = jwt.getJWTClaimsSet().getCustomClaim("expiryMonth"); String expiryYear = jwt.getJWTClaimsSet().getCustomClaim("expiryYear"); String cryptogram = jwt.getJWTClaimsSet().getCustomClaim("cryptogram"); String typeOfCryptogram = jwt.getJWTClaimsSet().getCustomClaim("typeOfCryptogram"); String trid = jwt.getJWTClaimsSet().getCustomClaim("trid"); String eci = jwt.getJWTClaimsSet().getCustomClaim("eci");
The output of this process is a decrypted JWT containing the claims set that was originally encrypted by the payment handler.
The key encryption key MUST be a 2048-bit RSA public key shared via an X.509 certificate in a file encoded according to [[!RFC7468]].
How a payment handler acquires the key encryption key is outside the scope of this specification. See the section on how to use this specification within a payment method specification.
This specification leverages JSON Web Encryption to create an encrypted message structure. The result of using this specification is a JWE Compact Serialization with the following structure:
(header).(encrypted key).(initialization vector).(ciphertext).(authentication tag)
Each component is BASE64URL encoded. The components are concatenated and separated by a period (".").
The header is a JOSE Header, which describes the encryption to be applied during plaintext encryption. Implementations of this specification MUST use the following parameters and values:
enc
: AES GCM (using a 256-bit key)
alg
: RSA OAEP 256
During this process, the payment handler generates and uses a content encryption key (CEK) to produce the ciphertext. The CEK is then encrypted using the RSA OAEP 256 algorithm and the resulting JWE Encrypted Key becomes part of the message structure.
The initialization vector is a randomly generated object used during encryption, and is shared for the decryption of the cipher text. It is a JWE initialization vector.
The ciphertext is the result of plaintext encryption of the sensitive data of the payment method, using the algorithm named in the header, the content encryption key, and the initialization vector.
The authentication tag allows the receiver to detect whether the message has been altered. It is a JWE Authentication TAG and is generated during the encryption operation.
@@Todo: Describe how to decrypt the encrypted data and the JSON form it takes.
@@Todo: Describe how to reference this specification from within a payment method specification.@@
Each payment method definition identifies which part of the PaymentResponse is encrypted. One member of the PaymentResponse should hold the encrypted message structure.
A payment method may take different approaches to adding data fields to the JWT claims set, including:
Each payment method definition describes how the payment handler acquires the key encryption key. For example, the payment handler may retrieve the key encryption key via a URL provided in a PaymentRequest.
This specification relies on several other underlying specifications.