If your integration uses local communications, you need to protect your integration against man-in-the-middle attacks, eavesdropping, and tampering.
To protect the communications between your POS app and the terminal, you:
- Validate the terminal certificate. This confirms that your POS app is communicating directly with an Adyen-supplied payment terminal, and not an impostor.
- Encrypt communications. This prevents intruders from reading the messages transmitted between the POS app and the terminal.
Requirements
- Make sure that you have:
Validate the terminal certificate
Every time your POS app connects to the terminal, you have to check the certificate on the terminal against Adyen's root certificate.
-
Verify that the certificate on the terminal is signed by the trusted root certificate that you installed. This step automatically includes checking of the intermediate certificate provided by the terminal at the start of the connection.
You will see a Common Name mismatch error during verification. This is normal, and happens because the certificate's Common Name does not resolve in the DNS.
-
Validate the Common Name. The Common Name is in one of the following formats:
-
Test terminal Common Name formats:
- legacy-terminal-certificate.test.terminal.adyen.com
- [POIID].test.terminal.adyen.com
[POIID] = [Terminal model]-[Serial number]
For example, P400Plus-123456789.test.terminal.adyen.com
-
Live terminal Common Name formats:
- legacy-terminal-certificate.live.terminal.adyen.com
- [POIID].live.terminal.adyen.com
[POIID] = [Terminal model]-[Serial number]
For example, P400Plus-123456789.live.terminal.adyen.com
Depending on the number of terminals you are integrating, you may want to use regular expressions in your code to validate the Common Name.
-
If the certificate on the connected terminal passes verification, your POS app is connected to an Adyen-supplied payment terminal.
Encrypt communications
To prevent others from being able to read requests and responses sent between your POS app and the payment terminal, you have to:
- Encrypt Terminal API messages before sending.
- Decrypt and validate Terminal API messages that you receive.
In the next sections, we use PHP code fragments to explain encryption and decryption step by step. If you want to copy code to your project, the section Full code samples has the complete code in several languages, as well as links to GitHub libraries that include this code.
Encrypt Terminal API messages
To encrypt Terminal API messages, proceed as follows:
- Derive key material by running an HMAC-based key derivation function on the shared key.
- Encrypt the message using the derived key material.
- Calculate the HMAC signature for the message, using the derived key material.
- Create a security trailer that contains the HMAC signature and identifies the shared key that was used for the encryption and the HMAC signature.
- Compose a new message containing the encrypted message as a Base64-encoded blob and the security trailer.
The result is a new message ready for sending.
Derive key material
To encrypt and decrypt messages, you need key material consisting of an HMAC key algorithm, a cipher key, and an initialization vector to initialize the encryption and decryption algorithms.
This key material is derived from the shared key passphrase
and only changes when the shared key changes. This means you do not need to re-derive key material for each message. However, the derived key material is secret, so if you do not derive key material with every message you need to store the key material securely in your system. Also, you need to add a function to your code to look up the key material.
To derive key material:
-
Apply the key derivation function PBKDF2-HMAC-SHA1 to the
passphrase
using the following parameters:Parameter Value Salt AdyenNexoV1Salt Salt length 15 Rounds 4000 Key length 80 This returns a three-element array containing the 32-byte
hmac_key
, the 32-bytecipher_key
and the 16-byte initialization vectoriv
.
Encrypt the message
-
Generate a nonce of the same length as the
iv
(16 bytes). -
Add an XOR helper function. You will use this function later with the nonce and the
iv
from the derived key material as input, to calculate the initialization vector for the encryption and decryption algorithms. -
Encrypt the message with AES265 in CBC mode using the full original message, the derived key material, and the nonce as input.
Calculate the signature
-
Calculate the HMAC signature using the full original message and the
hmac_key
from the derived key material as input.
Create the security trailer
-
Create a security trailer containing the version number and identifier of the shared secret, the nonce, and the HMAC signature.
Compose the new message
-
Create a new message that consists of:
- The same body key as the original message:
SaleToPOIRequest
orSaleToPOIResonse
. - The same
MessageHeader
as the original message. - A Base64-encoded blob with the encrypted original message.
- The security trailer.
The result is the new message, ready for sending. Here is an example:
{ "SaleToPOIRequest":{ "MessageHeader":{ "MessageClass":"Service", "ProtocolVersion":"3.0", "ServiceID":"6158", "MessageCategory":"Abort", "SaleID":"POSSystemID12345", "MessageType":"Request", "POIID":"M400-260193322" }, "NexoBlob":"ae8b41wcH9ZH18CRTHSPXi4FdN5Hd2vOQ9ZTKS+GsHvXFqyrAPtVZtmlyI5fWzxpzLMYOyZIAbaSFuasmGi2WcvFO5DBIWvstaQyIfDgcs9oVCuSWvgLXqnCocV8juZNjYGWllY1t0HKuym0I1lCeQRPehzyNbQn5aUp7fr6AuUTgLC+bAZWh/DqnxCCW5wcyNq9QFC8H+1Gm9R4weJH8zEBMTxldh1BDwp/5Xabz5nkfvDYranT463PTw9czge5VcgE7sGaBLaMWzYU9HI9QVlShceasOZo18rohNRdeaJVuJ1JJme2ZY1ZWav44rXi77NN3QuC5mbj0bUCKFOhTCOVcxKdNlIlmF0tAmCcmNiPwmSLL6kmygZNcgQ7zKjWVJsFhQ+2I4hOWCE4ZbJ6jAxyGbLnCpSjzhfFpLBQvGRuFiaCNMNbAUh2iL9Ep6jMlf5/SqpYXhji+8hQF8jXMF9i6oYJ1G/WUQRSajVklpHk7KoTpH2JjtuG7jZmPxzVGj1/vKPSaT90WiVPOay1vMLKb6V3Tc+DpjG0Y2sNj+bc6PvqnXmUPPlyiA+I65XkawdXR3qcsm2AFNLRTLZR4Q0og5FXXpZxOBFtfmDNFQ+Ygtb/JqsB960HaWhQkAyBxZKJ0nfWBeriiF4t1c6ppgejqmIqxAauOmAJjng+5hcA3x2kSFl9MT6kGx21Kt04ijAFX7OTyfBggJFEhnphHQ==", "SecurityTrailer":{ "KeyVersion":0, "KeyIdentifier":"mykey", "Hmac":"h6ehPJOASK4NXGESERmXo5mP9YFxpox7VoAFGIb9s8Y=", "Nonce":"BoBZRF2QmDlNnmeo1QYeZQ==", "AdyenCryptoVersion":1 } } }
- The same body key as the original message:
Decrypt Terminal API messages
Decrypting messages requires the following steps:
- Decompose the received message.
- Identify the shared key and use that to derive key material.
- Decrypt the original message using the derived key material
- Validate the message by checking that the HMAC signature and
MessageHeader
of the received message are the same as those of the decrypted original message.
The result is the decrypted original message ready for processing.
-
Create some functions you'll need later on in the
NexoReceiver
function:- A function to look up the
passphrase
.
- A function to decrypt the message with AES265 in CBC mode. This function reuses the
XorBytes
function from the encryption steps to set the initialization vector for the decryption algorithm.
- A function to look up the
Decompose the message
-
Parse the received message and decompose it into three parts::
- The
MessageHeader
. - The blob of the encrypted original message.
- The security trailer.
- The
Derive key material
-
Look up the
passphrase
of the shared key based on the version number and identifier in the security trailer, and Base64-decode the HMAC signature and the nonce from the security trailer. -
Reuse the
NexoDeriveKeymaterial
function to derive the key material based on thepassphrase
.
Decrypt the original message
-
Base64-decode the blob and decrypt the original message using the
cipher_key
and the nonce as input, and reusing theXorBytes
function from the encryption steps to set the initialization vector for the decryption algorithm.
Validate the message
-
Validate the HMAC signature: Base64-decode the HMAC from the received message, reuse the
NexoHMAC
function to calculate the HMAC of the decrypted message, and compare the two. -
Verify that the plaintext
MessageHeader
matches theMessageHeader
in the decrypted message, and then return the decrypted message.If the validation succeeds, the result is the decrypted message, ready for processing.
Full encryption code samples
The next examples show the full code for encrypting and decrypting messages. This code is also available in the C# library and Java library on our GitHub.
Troubleshooting
Crypto errors and SSL connection errors indicate a problem with the protection of the local communications.
Crypto errors
Example:
Exception: System.Net.WebException: The remote server returned an error: (401) Unauthorized.
The response body contains:
{
"errors":[
"Nexo Service: crypto error (9)"
],
"ServiceID":"1234567890"
}
Cause: Crypto errors are related to the shared key. After you set up the shared key in your Customer Area, the shared key values in your code must match the shared key values in the Customer Area.
If you are using a library, check the values for the relevant object:
- With the .NET library, check the
EncryptionCredentialDetails
object. - With the Java library, check the
SecurityKey
object. - With the Node library, check the
SecurityKey
object.
If you are using your own code:
- Check the key derivation function. This uses the passphrase of the shared key.
- Check the security trailer function. This uses the version and the identifier of the shared key.
Crypto error | Cause |
---|---|
crypto error (1) | There is a problem with parsing the request. This can be due to a syntax error. |
crypto error (2) | The version number of the shared key in your code is unknown. |
crypto error (3) | There is a problem with the message header of the request. |
crypto error (4) | There is a problem with the body of the request. |
crypto error (5) | There is a problem with the security trailer of the encrypted message. The trailer uses the version and identifier of the shared key. |
crypto error (6) | There is a problem with the passphrase of the shared key. |
crypto error (7) | The nonce is missing or incorrect. The nonce must have a length of 16 bytes. |
crypto error (8) | The HMAC key is missing or incorrect. The HMAC key must have a length of 32 bytes. |
crypto error (9) | The shared key details in your code do not match the shared key that is set up in your Customer Area. |
SSL connection error
Example:
Exception : System.Net.WebException: The SSL connection could not be established
Possible cause: Adyen's root certificate is not installed correctly.