{"title":"Protect with your own code","category":"default","creationDate":1779706500,"content":"<p>If your integration uses local communications, you need to protect your integration against <a href=\"https:\/\/en.wikipedia.org\/wiki\/Man-in-the-middle_attack\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">man-in-the-middle attacks<\/a>, eavesdropping, and tampering.<\/p>\n<p>To protect the communications between your POS app and the terminal, you:<\/p>\n<ul>\n<li><a href=\"#validate-cert\">Validate the terminal certificate<\/a>. This confirms that your POS app is communicating directly with an Adyen-supplied payment terminal, and not an impostor.<\/li>\n<li><a href=\"#encrypt-communications\">Encrypt communications<\/a>. This prevents intruders from reading the messages transmitted between the POS app and the terminal.<\/li>\n<\/ul>\n<h2>Requirements<\/h2>\n<ul>\n<li>Make sure that you have:\n<ul>\n<li><a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local#install-root-cert\">Installed Adyen's root certificate<\/a>.<\/li>\n<li><a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local#set-up-shared-key\">Set up a shared key<\/a>.<\/li>\n<\/ul><\/li>\n<\/ul>\n<h2 id=\"validate-cert\">Validate the terminal certificate<\/h2>\n<p>Every time your POS app connects to the terminal, you have to check the certificate on the terminal against Adyen's root certificate.<\/p>\n<ol>\n<li>\n<p>Verify that the certificate on the terminal is signed by the trusted <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local#install-root-cert\">root certificate that you installed<\/a>. This step automatically includes checking of the intermediate certificate provided by the terminal at the start of the connection.<\/p>\n<div class=\"notices yellow\">\n<p>You will see a <em>Common Name mismatch error<\/em> during verification. This is normal, and happens because the certificate's Common Name does not resolve in the DNS.<\/p>\n<\/div>\n<\/li>\n<li>\n<p>Validate the Common Name. The Common Name is in one of the following formats:<\/p>\n<ul>\n<li>\n<p>Test terminal Common Name formats:<\/p>\n<ul>\n<li><span translate=\"no\"><strong>legacy-terminal-certificate.test.terminal.adyen.com<\/strong><\/span><\/li>\n<li><strong>[POIID]<\/strong><span translate=\"no\"><strong>.test.terminal.adyen.com<\/strong><\/span>\n<div class=\"sc-notice info\"><div>\n<p><strong>[POIID]<\/strong> = <em>[Terminal model]-[Serial number]<\/em><br \/>\nFor example, <span translate=\"no\"><strong>P400Plus-123456789.test.terminal.adyen.com<\/strong><\/span><\/p>\n<\/div><\/div><\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Live terminal Common Name formats:<\/p>\n<ul>\n<li><span translate=\"no\"><strong>legacy-terminal-certificate.live.terminal.adyen.com<\/strong><\/span><\/li>\n<li><strong>[POIID]<\/strong><span translate=\"no\"><strong>.live.terminal.adyen.com<\/strong><\/span>\n<div class=\"sc-notice info\"><div>\n<p><strong>[POIID]<\/strong> = <em>[Terminal model]-[Serial number]<\/em><br \/>\nFor example, <span translate=\"no\"><strong>P400Plus-123456789.live.terminal.adyen.com<\/strong><\/span><\/p>\n<\/div><\/div><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Depending on the number of terminals you are integrating, you may want to use regular expressions in your code to validate the Common Name.<\/p>\n<\/li>\n<\/ol>\n<p>If the certificate on the connected terminal passes verification, your POS app is connected to an Adyen-supplied payment terminal.<\/p>\n<h2>Encrypt communications<\/h2>\n<p>To prevent others from being able to read requests and responses sent between your POS app and the payment terminal, you have to:<\/p>\n<ol>\n<li><a href=\"#encrypt-message\">Encrypt Terminal API messages<\/a> before sending.<\/li>\n<li><a href=\"#decrypt-and-validate-messages\">Decrypt and validate Terminal API messages<\/a> that you receive.<\/li>\n<\/ol>\n<p>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 <a href=\"#full-code-samples\">Full code samples<\/a> has the complete code in several languages, as well as links to GitHub libraries that include this code.<\/p>\n<h3 id=\"encrypt-message\">Encrypt Terminal API messages<\/h3>\n<p>To encrypt Terminal API messages, proceed as follows:<\/p>\n<ol>\n<li><a href=\"#derive-key-material\">Derive key material<\/a> by running an HMAC-based key derivation function on the shared key.<\/li>\n<li><a href=\"#encrypt\">Encrypt the message<\/a> using the derived key material.<\/li>\n<li><a href=\"#calculate-the-signature\">Calculate the HMAC signature<\/a> for the message, using the derived key material.<\/li>\n<li><a href=\"#create-the-security-trailer\">Create a security trailer<\/a> that contains the HMAC signature and identifies the shared key that was used for the encryption and the HMAC signature.<\/li>\n<li><a href=\"#compose-new-message\">Compose a new message<\/a> containing the encrypted message as a Base64-encoded blob and the security trailer.<\/li>\n<\/ol>\n<p>The result is a new message ready for sending.<\/p>\n<h4 id=\"derive-key-material\">Derive key material<\/h4>\n<p>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.<\/p>\n<p>This key material is derived from the shared key <code>passphrase<\/code> 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.<\/p>\n<p>To derive key material:<\/p>\n<ol>\n<li>\n<p>Apply the key derivation function PBKDF2-HMAC-SHA1 to the <code>passphrase<\/code> using the following parameters:<\/p>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Parameter<\/th>\n<th style=\"text-align: left;\">Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\">Salt<\/td>\n<td style=\"text-align: left;\">AdyenNexoV1Salt<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left;\">Salt length<\/td>\n<td style=\"text-align: left;\">15<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left;\">Rounds<\/td>\n<td style=\"text-align: left;\">4000<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left;\">Key length<\/td>\n<td style=\"text-align: left;\">80<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"''\" :id=\"'key-derivation'\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoDeriveKeymaterial($passphrase) {\\n    $outlen = 80;\\n    $salt =  \\&quot;AdyenNexoV1Salt\\&quot;;\\n    $rounds = 4000;\\n    $bytes = openssl_pbkdf2($passphrase, $salt, $outlen, $rounds, \\&quot;sha1\\&quot;);\\n\\n    $hmac_key = substr($bytes, 0, 32);\\n    $cipher_key = substr($bytes, 32, 32);\\n    $iv = substr($bytes, 64, 16);\\n\\n    return array('hmac_key' =&gt; $hmac_key, 'cipher_key' =&gt; $cipher_key, 'iv' =&gt; $iv);\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<p>This returns a three-element array containing the 32-byte <code>hmac_key<\/code>, the 32-byte <code>cipher_key<\/code> and the 16-byte initialization vector <code>iv<\/code>.<\/p>\n<\/li>\n<\/ol>\n<h4 id=\"encrypt\">Encrypt the message<\/h4>\n<ol start=\"2\">\n<li>\n<p>Generate a nonce of the same length as the <code>iv<\/code> (16 bytes).<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data='[{\"language\":\"php\",\"tabTitle\":\"\",\"content\":\"function NexoDrawNonce() {\\n\\treturn openssl_random_pseudo_bytes(16);\\n}\"}]' :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<li>\n<p>Add an XOR helper function. You will use this function later with the nonce and the <code>iv<\/code> from the derived key material as input, to calculate the initialization vector for the encryption and decryption algorithms.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data='[{\"language\":\"php\",\"tabTitle\":\"\",\"content\":\"function XorBytes($a, $b) {\\n\\t$r = $a;\\n\\tfor ($i = 0; $i &lt; 16; $i++) {\\n\\t\\t$r[$i] = $r[$i] ^ $b[$i];\\n\\t}\\n\\treturn $r;\\n}\"}]' :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<li>\n<p>Encrypt the message with AES265 in CBC mode using the full original message, the derived key material, and the nonce as input.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoEncrypt($message, $keymaterial, $nonce) {\\n\\t$realiv = XorBytes($keymaterial['iv'], $nonce);\\n\\treturn openssl_encrypt($message, \\&quot;AES-256-CBC\\&quot;, $keymaterial['cipher_key'], OPENSSL_RAW_DATA, $realiv);\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<\/ol>\n<h4 id=\"calculate-the-signature\">Calculate the signature<\/h4>\n<ol start=\"5\">\n<li>\n<p>Calculate the HMAC signature using the full original message and the <code>hmac_key<\/code> from the derived key material as input.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoHMAC($message, $keymaterial) {\\n\\treturn hash_hmac(\\&quot;sha256\\&quot;, $message, $keymaterial['hmac_key'], true);\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<\/ol>\n<h4 id=\"create-the-security-trailer\">Create the security trailer<\/h4>\n<ol start=\"6\">\n<li>\n<p>Create a security trailer containing the version number and identifier of the shared secret, the nonce, and the HMAC signature.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoTrailer($keyid, $keyversion, $nonce, $hmac) {\\n\\treturn array('KeyVersion' =&gt; $keyversion,\\n\\t\\t'KeyIdentifier' =&gt; $keyid,\\n\\t\\t'Hmac' =&gt; base64_encode($hmac),\\n\\t\\t'Nonce' =&gt; base64_encode($nonce),\\n\\t\\t'AdyenCryptoVersion' =&gt; 1);\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<\/ol>\n<h4 id=\"compose-new-message\">Compose the new message<\/h4>\n<ol start=\"7\">\n<li>\n<p>Create a new message that consists of:<\/p>\n<ul>\n<li>The same body key as the original message: <code>SaleToPOIRequest<\/code> or <code>SaleToPOIResonse<\/code>.<\/li>\n<li>The same <code>MessageHeader<\/code> as the original message.<\/li>\n<li>A Base64-encoded blob with the encrypted original message.<\/li>\n<li>The security trailer.<\/li>\n<\/ul>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoSender($message, $keyid, $keyversion, $keymaterial) {\\n\\t$jsonin = json_decode($message, true);\\n\\t$isrequest = isset($jsonin['SaleToPOIRequest']);\\n\\t$bodykey = $isrequest ? 'SaleToPOIRequest' : 'SaleToPOIResponse';\\n\\t$body = $jsonin[$bodykey];\\n\\n\\t$header = $body['MessageHeader'];\\n\\n\\t\\\/\\\/ Encrypt the original message and compute its hmac signature\\n\\t$nonce = NexoDrawNonce();\\n\\t$nexoblob = NexoEncrypt($message, $keymaterial, $nonce);\\n\\t$hmac = NexoHMAC($message, $keymaterial);\\n\\n\\t$trailer = NexoTrailer($keyid, $keyversion, $nonce, $hmac);\\n\\n\\t\\\/\\\/ The result has three parts: header, blob, and trailer\\n\\t$result = array('MessageHeader' =&gt; $header, 'NexoBlob' =&gt; base64_encode($nexoblob), 'SecurityTrailer' =&gt; $trailer);\\n\\n\\treturn json_encode(array($bodykey =&gt; $result), JSON_PRETTY_PRINT);\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<p>The result is the new message, ready for sending. Here is an example:<\/p>\n<pre><code class=\"language-json\">{\n    \"SaleToPOIRequest\":{\n        \"MessageHeader\":{\n            \"MessageClass\":\"Service\",\n            \"ProtocolVersion\":\"3.0\",\n            \"ServiceID\":\"6158\",\n            \"MessageCategory\":\"Abort\",\n            \"SaleID\":\"POSSystemID12345\",\n            \"MessageType\":\"Request\",\n            \"POIID\":\"M400-260193322\"\n        },\n        \"NexoBlob\":\"ae8b41wcH9ZH18CRTHSPXi4FdN5Hd2vOQ9ZTKS+GsHvXFqyrAPtVZtmlyI5fWzxpzLMYOyZIAbaSFuasmGi2WcvFO5DBIWvstaQyIfDgcs9oVCuSWvgLXqnCocV8juZNjYGWllY1t0HKuym0I1lCeQRPehzyNbQn5aUp7fr6AuUTgLC+bAZWh\/DqnxCCW5wcyNq9QFC8H+1Gm9R4weJH8zEBMTxldh1BDwp\/5Xabz5nkfvDYranT463PTw9czge5VcgE7sGaBLaMWzYU9HI9QVlShceasOZo18rohNRdeaJVuJ1JJme2ZY1ZWav44rXi77NN3QuC5mbj0bUCKFOhTCOVcxKdNlIlmF0tAmCcmNiPwmSLL6kmygZNcgQ7zKjWVJsFhQ+2I4hOWCE4ZbJ6jAxyGbLnCpSjzhfFpLBQvGRuFiaCNMNbAUh2iL9Ep6jMlf5\/SqpYXhji+8hQF8jXMF9i6oYJ1G\/WUQRSajVklpHk7KoTpH2JjtuG7jZmPxzVGj1\/vKPSaT90WiVPOay1vMLKb6V3Tc+DpjG0Y2sNj+bc6PvqnXmUPPlyiA+I65XkawdXR3qcsm2AFNLRTLZR4Q0og5FXXpZxOBFtfmDNFQ+Ygtb\/JqsB960HaWhQkAyBxZKJ0nfWBeriiF4t1c6ppgejqmIqxAauOmAJjng+5hcA3x2kSFl9MT6kGx21Kt04ijAFX7OTyfBggJFEhnphHQ==\",\n        \"SecurityTrailer\":{\n            \"KeyVersion\":0,\n            \"KeyIdentifier\":\"mykey\",\n            \"Hmac\":\"h6ehPJOASK4NXGESERmXo5mP9YFxpox7VoAFGIb9s8Y=\",\n            \"Nonce\":\"BoBZRF2QmDlNnmeo1QYeZQ==\",\n            \"AdyenCryptoVersion\":1\n        }\n    }\n}<\/code><\/pre>\n<\/li>\n<\/ol>\n<h3 id=\"decrypt-and-validate-messages\">Decrypt Terminal API messages<\/h3>\n<p>Decrypting messages requires the following steps:<\/p>\n<ol>\n<li><a href=\"#decompose-the-message\">Decompose the received message<\/a>.<\/li>\n<li>Identify the shared key and use that to <a href=\"#derive-for-decryption\">derive key material<\/a>.<\/li>\n<li><a href=\"#decrypt\">Decrypt the original message<\/a> using the derived key material<\/li>\n<li><a href=\"#validate\">Validate the message<\/a> by checking that the HMAC signature and <code>MessageHeader<\/code> of the received message are the same as those of the decrypted original message.<\/li>\n<\/ol>\n<p>The result is the decrypted original message ready for processing.<\/p>\n<ol>\n<li>\n<p>Create some functions you'll need later on in the <code>NexoReceiver<\/code> function:<\/p>\n<ul>\n<li>A function to look up the <code>passphrase<\/code>.<\/li>\n<\/ul>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data='[{\"language\":\"php\",\"tabTitle\":\"\",\"content\":\"function NexoLookupKeybyIdAndVersion($keyid, $keyversion) {\\n\\t\\\/\\\/ This function should do a lookup based on key id and version.\\n\\t\\\/\\\/ But for demonstration purposes, we just return a given test passphrase.\\n\\treturn NexoDeriveKeyMaterial(\\\"mysupersecretpassphrase\\\");\\n}\"}]' :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<ul>\n<li>A function to decrypt the message with AES265 in CBC mode. This function reuses the <code>XorBytes<\/code> function from the encryption steps to set the initialization vector for the decryption algorithm.<\/li>\n<\/ul>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoDecrypt($message, $keymaterial, $nonce) {\\n\\t$realiv = XorBytes($keymaterial['iv'], $nonce);\\n\\treturn openssl_decrypt($message, \\&quot;AES-256-CBC\\&quot;, $keymaterial['cipher_key'], OPENSSL_RAW_DATA, $realiv);\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<\/ol>\n<h4 id=\"decompose-the-message\">Decompose the message<\/h4>\n<ol start=\"2\">\n<li>\n<p>Parse the received message and decompose it into three parts::<\/p>\n<ul>\n<li>The <code>MessageHeader<\/code>.<\/li>\n<li>The blob of the encrypted original message.<\/li>\n<li>The security trailer.<\/li>\n<\/ul>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;function NexoReceiver($message) {\\n\\t$jsonin = json_decode($message, true);\\n\\t$isrequest = isset($jsonin['SaleToPOIRequest']);\\n\\t$bodykey = $isrequest ? 'SaleToPOIRequest' : 'SaleToPOIResponse';\\n\\t$body = $jsonin[$bodykey];\\n    $header = $body['MessageHeader'];\\n\\t$blob = $body['NexoBlob'];\\n\\t$trailer = $body['SecurityTrailer'];\\n    \\\/\\\/ NexoReceiver function continues in the next steps&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<\/ol>\n<h4 id=\"derive-for-decryption\">Derive key material<\/h4>\n<ol start=\"3\">\n<li>\n<p>Look up the <code>passphrase<\/code> 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.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;    \\tif ($trailer['AdyenCryptoVersion'] != 1) {\\n    \\t\\treturn null;\\n    \\t}\\n    \\t$keymaterial = NexoLookupKeybyIdAndVersion($trailer['KeyIdentifier'], $trailer['KeyVersion']);\\n    \\t$nonce = base64_decode($trailer['Nonce']);\\n    \\t$hmac = base64_decode($trailer['Hmac']);&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<li>\n<p>Reuse the <a href=\"#derive-key-material\"><code>NexoDeriveKeymaterial<\/code> function<\/a> to derive the key material based on the <code>passphrase<\/code>.<\/p>\n<\/li>\n<\/ol>\n<h4 id=\"decrypt\">Decrypt the original message<\/h4>\n<ol start=\"5\">\n<li>\n<p>Base64-decode the blob and decrypt the original message using the <code>cipher_key<\/code> and the nonce as input, and reusing the <code>XorBytes<\/code> function from the encryption steps to set the initialization vector for the decryption algorithm.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;\\t    $nexoblob = base64_decode($body['NexoBlob']);\\n\\t    $decrypted = NexoDecrypt($nexoblob, $keymaterial, $nonce);&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<\/ol>\n<h4 id=\"validate\">Validate the message<\/h4>\n<ol start=\"6\">\n<li>\n<p>Validate the HMAC signature: Base64-decode the HMAC from the received message, reuse the <code>NexoHMAC<\/code> function to calculate the HMAC of the decrypted message, and compare the two.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data='[{\"language\":\"php\",\"tabTitle\":\"\",\"content\":\"$computed_hmac = NexoHMAC($decrypted, $keymaterial);\\nif ($computed_hmac != $hmac) {\\n    \\t    return null;\\n}\"}]' :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<\/li>\n<li>\n<p>Verify that the plaintext <code>MessageHeader<\/code> matches the <code>MessageHeader<\/code> in the decrypted message, and then return the decrypted message.<\/p>\n<div data-component-wrapper=\"code-sample\">\n<code-sample :title=\"'PHP'\" :id=\"''\" :code-data=\"[{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;&quot;,&quot;content&quot;:&quot;    \\t$decrypted_json = json_decode($decrypted, true);\\n    \\tif ($decrypted_json[$bodykey]['MessageHeader'] !== $header) {\\n    \\t\\treturn null;\\n    \\t}\\n    \\treturn $decrypted;\\n    }\\n    \\\/\\\/ end of the NexoReceiver function&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<p>If the validation succeeds, the result is the decrypted message, ready for processing.<\/p>\n<\/li>\n<\/ol>\n<h2 id=\"full-code-samples\">Full encryption code samples<\/h2>\n<p>The next examples show the full code for encrypting and decrypting messages. <\/p>\n<div data-component-wrapper=\"code-sample\">\n    <code-sample :title=\"''\" :id=\"'encryption-decryption'\" :code-data=\"[{&quot;language&quot;:&quot;java&quot;,&quot;tabTitle&quot;:&quot;Java&quot;,&quot;content&quot;:&quot;import java.io.*;\\nimport java.security.*;\\nimport java.security.spec.*;\\nimport java.text.ParseException;\\nimport java.util.Base64;\\nimport java.util.Random;\\nimport javax.crypto.*;\\nimport javax.crypto.spec.IvParameterSpec;\\nimport javax.crypto.spec.PBEKeySpec;\\nimport javax.crypto.spec.SecretKeySpec;\\nimport javax.json.*;\\n\\npublic class NexoCrypto {\\n\\n    public static final int NEXO_HMAC_KEY_LENGTH = 32;\\n    public static final int NEXO_CIPHER_KEY_LENGTH = 32;\\n    public static final int NEXO_IV_LENGTH = 16;\\n\\n    \\\/**\\n     * Where to look for pre-derived key files\\n     *\\\/\\n    private String keyDirectory;\\n\\n    \\\/**\\n     * The key material to use when sending\\n     *\\\/\\n    private String keyIdentifier;\\n    private long keyVersion;\\n    private NexoDerivedKeys derivedKeys;\\n\\n    \\\/**\\n     * A container for Nexo derived keys\\n     *\\n     * Nexo derived keys is a 80 byte struct containing key data. These 80\\n     * bytes are derived from a passsphrase.\\n     *\\\/\\n    public class NexoDerivedKeys {\\n        public byte hmac_key[];\\n        public byte cipher_key[];\\n        public byte iv[];\\n\\n        public NexoDerivedKeys() {\\n            hmac_key = new byte[NEXO_HMAC_KEY_LENGTH];\\n            cipher_key = new byte[NEXO_CIPHER_KEY_LENGTH];\\n            iv = new byte[NEXO_IV_LENGTH];\\n        }\\n\\n        \\\/**\\n         * Read a key material file of 80 bytes, splitting it in the hmac_key, cipher_key and iv\\n         *\\\/\\n        public void readKeyData(String keyId, long keyV) throws IOException {\\n            String filename = keyDirectory + '\\\/' + keyId + \\&quot;.\\&quot; + Long.toString(keyV) + \\&quot;.key\\&quot;;\\n\\n            FileInputStream stream = null;\\n            try {\\n                stream = new FileInputStream(filename);\\n                stream.read(this.hmac_key);\\n                stream.read(this.cipher_key);\\n                stream.read(this.iv);\\n            } finally {\\n                if (stream != null) {\\n                    try {\\n                        stream.close();\\n                    }\\n                    catch (Exception ignored) {\\n                    }\\n                }\\n            }\\n        }\\n\\n    };\\n\\n    \\\/**\\n     * Use this constructor if you want to do both encryption and decryption\\n     *\\\/\\n    public NexoCrypto(String dir, String keyID, long keyV) throws IOException {\\n        keyDirectory = dir;\\n        keyIdentifier = keyID;\\n        keyVersion = keyV;\\n        derivedKeys = new NexoDerivedKeys();\\n        derivedKeys.readKeyData(keyIdentifier, keyVersion);\\n    }\\n\\n    \\\/**\\n     * Use this constructor if you want to decrypt-only NexoCrypto object\\n     *\\\/\\n    public NexoCrypto(String dir) {\\n        keyDirectory = dir;\\n    }\\n\\n    \\\/**\\n     * Given a passphrase, compute 80 byte key of key material according to crypto.md\\n     *\\\/\\n    public static byte[] deriveKeyMaterial(char[] passphrase) throws NoSuchAlgorithmException, InvalidKeySpecException {\\n        byte[] salt = \\&quot;AdyenNexoV1Salt\\&quot;.getBytes();\\n        int iterations = 4000;\\n        PBEKeySpec spec = new PBEKeySpec(passphrase, salt, iterations, (NEXO_HMAC_KEY_LENGTH +\\n            NEXO_CIPHER_KEY_LENGTH + NEXO_IV_LENGTH) * 8);\\n        SecretKeyFactory skf = SecretKeyFactory.getInstance(\\&quot;PBKDF2WithHmacSHA1\\&quot;);\\n        byte[] keymaterial = skf.generateSecret(spec).getEncoded();\\n        return keymaterial;\\n    }\\n\\n    \\\/**\\n     * Encrypt or decrypt data given an iv modifier and using the specified key\\n     * The actual iv is computed by taking the iv from the key material and xoring it with ivmod\\n     *\\\/\\n    private byte[] crypt(byte[] bytes, NexoDerivedKeys dk, byte[] ivmod, int mode)\\n        throws NoSuchAlgorithmException, NoSuchPaddingException,\\n        IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {\\n\\n        Cipher cipher = Cipher.getInstance(\\&quot;AES\\\/CBC\\\/PKCS5Padding\\&quot;);\\n        SecretKeySpec s = new SecretKeySpec(dk.cipher_key, \\&quot;AES\\&quot;);\\n\\n        \\\/\\\/ xor dk.iv and the iv modifier\\n        byte[] actualIV = new byte[NEXO_IV_LENGTH];\\n        for (int i = 0; i &lt; NEXO_IV_LENGTH; i++) {\\n            actualIV[i] = (byte) (dk.iv[i] ^ ivmod[i]);\\n        }\\n\\n        IvParameterSpec i = new IvParameterSpec(actualIV);\\n        cipher.init(mode, s, i);\\n        return cipher.doFinal(bytes);\\n    }\\n\\n    \\\/**\\n     * Compute an hmac using the hmac_key\\n     *\\\/\\n    private byte[] hmac(byte[] bytes, NexoDerivedKeys dk) throws NoSuchAlgorithmException, InvalidKeyException {\\n        Mac mac = Mac.getInstance(\\&quot;HmacSHA256\\&quot;);\\n        SecretKeySpec s = new SecretKeySpec(dk.hmac_key, \\&quot;HmacSHA256\\&quot;);\\n\\n        mac.init(s);\\n        return mac.doFinal(bytes);\\n    }\\n\\n    \\\/**\\n     * Encrypt and compose a secured Nexo message\\n     *\\n     * This functions takes the original message, encrypts it and converts the encrypted form to Base64 and\\n     * names it NexoBlob.\\n     * After that, a new message is created with a copy of the header, the NexoBlob, and an added SecurityTrailer.\\n     *\\n     * @param in is the byte representation of the unprotected Nexo message\\n     * @returns a byte representation of the secured Nexo message\\n     *\\\/\\n    public byte[] encrypt_and_hmac(byte in[]) throws InvalidKeyException, NoSuchAlgorithmException,\\n        NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,\\n        InvalidAlgorithmParameterException {\\n        Base64.Encoder encb64 = Base64.getEncoder();\\n\\n        \\\/\\\/ parse the json and determine if it is a request or responce\\n        JsonReader jsonreader = Json.createReader(new ByteArrayInputStream(in));\\n        JsonObject body = jsonreader.readObject();\\n        boolean request = true;\\n        JsonObject saletopoirequest = body.getJsonObject(\\&quot;SaleToPOIRequest\\&quot;);\\n        if (saletopoirequest == null) {\\n            request = false;\\n            saletopoirequest = body.getJsonObject(\\&quot;SaleToPOIResponse\\&quot;);\\n        }\\n        \\\/\\\/ pick up the MessageHeader\\n        JsonObject messageheader = saletopoirequest.getJsonObject(\\&quot;MessageHeader\\&quot;);\\n\\n        \\\/\\\/ Generate a random iv nonce\\n        byte[] ivmod = new byte[NEXO_IV_LENGTH];\\n        new Random().nextBytes(ivmod);\\n\\n        \\\/\\\/ encrypt taking the original bytes as input\\n        byte[] encbytes = crypt(in, this.derivedKeys, ivmod, Cipher.ENCRYPT_MODE);\\n\\n        \\\/\\\/ compute mac over cleartext bytes\\n        byte[] hmac = hmac(in, this.derivedKeys);\\n\\n        \\\/\\\/ Construct the inner Json object containing a MessageHeader, a NexoBlob and a SecurityTrailer\\n        JsonObject msg = Json.createObjectBuilder()\\n            .add(\\&quot;MessageHeader\\&quot;, messageheader)\\n            .add(\\&quot;NexoBlob\\&quot;, new String(encb64.encode(encbytes)))\\n            .add(\\&quot;SecurityTrailer\\&quot;, Json.createObjectBuilder()\\n                .add(\\&quot;Hmac\\&quot;, new String(encb64.encode(hmac)))\\n                .add(\\&quot;KeyIdentifier\\&quot;, keyIdentifier)\\n                .add(\\&quot;KeyVersion\\&quot;, keyVersion)\\n                .add(\\&quot;AdyenCryptoVersion\\&quot;, 1)\\n                .add(\\&quot;Nonce\\&quot;, new String(encb64.encode(ivmod)))\\n            ).build();\\n        \\\/\\\/ Wrap the inner message in a SaleToPOIRequest or SaleToPOIResponse object\\n        JsonObject total = Json.createObjectBuilder()\\n            .add(request ?  \\&quot;SaleToPOIRequest\\&quot; : \\&quot;SaleToPOIResponse\\&quot; , msg)\\n            .build();\\n\\n        ByteArrayOutputStream stream = new ByteArrayOutputStream();\\n        JsonWriter writer = Json.createWriter(stream);\\n        writer.writeObject(total);\\n        writer.close();\\n        return stream.toByteArray();\\n    }\\n\\n    \\\/**\\n     * A helper class to return a decrypted mesasage and the outer header from the\\n     * secured Nexo message\\n     *\\\/\\n    public class BytesAndOuterHeader {\\n        public byte[] packet;\\n        public JsonObject outer_header;\\n\\n        public BytesAndOuterHeader(byte[] packet, JsonObject outer_header) {\\n            this.packet = packet;\\n            this.outer_header = outer_header;\\n        }\\n    }\\n\\n    \\\/**\\n     * Validate and decrypt a secured Nexo message\\n     * @returns a BytesAndOuterHeader object or null on failure\\n     *\\\/\\n    public BytesAndOuterHeader decrypt_and_validate_hmac(byte in[]) throws InvalidKeyException,\\n        NoSuchAlgorithmException, IOException, NoSuchPaddingException, IllegalBlockSizeException,\\n        BadPaddingException, InvalidAlgorithmParameterException {\\n        Base64.Decoder b64dec = Base64.getDecoder();\\n\\n        \\\/\\\/ Parse bytes and retrieve MessageHeader\\n        InputStream stream = new ByteArrayInputStream(in);\\n        JsonReader jsonreader = Json.createReader(stream);\\n        JsonObject total = jsonreader.readObject();\\n        if (total == null) {\\n            throw new IOException(\\&quot;Faulty JSON\\&quot;);\\n        }\\n        JsonObject saletopoirequest = total.getJsonObject(\\&quot;SaleToPOIRequest\\&quot;);\\n        if (saletopoirequest == null) {\\n            saletopoirequest = total.getJsonObject(\\&quot;SaleToPOIResponse\\&quot;);\\n        }\\n        if (saletopoirequest == null) {\\n            throw new IOException(\\&quot;No SaleToPOIRequest or SaleToPOIResponse\\&quot;);\\n        }\\n        JsonObject messageheader = saletopoirequest.getJsonObject(\\&quot;MessageHeader\\&quot;);\\n        if (messageheader == null) {\\n            throw new IOException(\\&quot;MessageHeader not found\\&quot;);\\n        }\\n        \\\/\\\/ Get the encrypted actual message and base64 decode it\\n        JsonString payload = saletopoirequest.getJsonString(\\&quot;NexoBlob\\&quot;);\\n        if (payload == null) {\\n            throw new IOException(\\&quot;NexoBlob not found\\&quot;);\\n        }\\n        byte[] ciphertext = b64dec.decode(payload.getString());\\n\\n        \\\/\\\/ Get the SecurityTrailer and its values\\n        JsonObject jsonTrailer = saletopoirequest.getJsonObject(\\&quot;SecurityTrailer\\&quot;);\\n        if (jsonTrailer == null) {\\n            throw new IOException(\\&quot;SecurityTrailer not found\\&quot;);\\n        }\\n        JsonNumber version = jsonTrailer.getJsonNumber(\\&quot;AdyenCryptoVersion\\&quot;);\\n        if (version == null || version.intValue() != 1) {\\n            throw new IOException(\\&quot;AdyenCryptoVersion version not found or not supported\\&quot;);\\n        }\\n        JsonString nonce = jsonTrailer.getJsonString(\\&quot;Nonce\\&quot;);\\n        if (nonce == null) {\\n            throw new IOException(\\&quot;Nonce not found\\&quot;);\\n        }\\n\\n        JsonString keyId = jsonTrailer.getJsonString(\\&quot;KeyIdentifier\\&quot;);\\n        if (keyId == null) {\\n            throw new IOException(\\&quot;KeyIdentifier not found\\&quot;);\\n        }\\n        JsonNumber kversion = jsonTrailer.getJsonNumber(\\&quot;KeyVersion\\&quot;);\\n        if (kversion == null) {\\n            throw new IOException(\\&quot;KeyVersion not found\\&quot;);\\n        }\\n        JsonString b64 = jsonTrailer.getJsonString(\\&quot;Hmac\\&quot;);\\n        if (b64 == null) {\\n            throw new IOException(\\&quot;Hmac not found\\&quot;);\\n        }\\n\\n        \\\/\\\/ Read the key from disk\\n        NexoDerivedKeys dk = new NexoDerivedKeys();\\n        dk.readKeyData(keyId.getString(), kversion.longValue());\\n\\n        \\\/\\\/ Decrypt the actual message with the base64 decoded ivmod as found in the securitytrailer\\n        byte[] ivmod = b64dec.decode(nonce.getString());\\n        byte[] ret = crypt(ciphertext, dk, ivmod, Cipher.DECRYPT_MODE);\\n\\n        \\\/\\\/ Base64 decode the received HMAC and compare it to a computed hmac\\n        \\\/\\\/ Use a timing safe compare, this is to mitigate a (theoretical) timing based attack\\n        byte[] receivedmac = b64dec.decode(b64.getString());\\n        byte[] hmac = hmac(ret, dk);\\n        if (receivedmac.length != hmac.length) {\\n            throw new IOException(\\&quot;Validation failed\\&quot;);\\n        }\\n        boolean equal = true;\\n        for (int i = 0; i &lt; hmac.length; i++) {\\n            if (receivedmac[i] != hmac[i]) {\\n                equal = false;\\n            }\\n        }\\n        if (!equal) {\\n            throw new IOException(\\&quot;Validation failed\\&quot;);\\n        }\\n\\n        \\\/\\\/ Return decrypted message and outer header\\n        return new BytesAndOuterHeader(ret, messageheader);\\n    }\\n\\n    \\\/**\\n     * Compare an inner and outer MessageHeader\\n     * @param the inner  MessageHeader\\n     * @param outer the outer MessageHeader\\n     * @return\\n     *\\\/\\n    public boolean validateInnerAndOuterHeader(JsonObject inner, JsonObject outer) {\\n        if (inner == null || outer == null) {\\n            return false;\\n        }\\n        String[] fields = {\\n            \\&quot;DeviceID\\&quot;,\\n            \\&quot;MessageCategory\\&quot;,\\n            \\&quot;MessageClass\\&quot;,\\n            \\&quot;MessageType\\&quot;,\\n            \\&quot;SaleID\\&quot;,\\n            \\&quot;ServiceID\\&quot;,\\n            \\&quot;POIID\\&quot;,\\n            \\&quot;ProtocolVersion\\&quot;,\\n        };\\n        for (String field : fields) {\\n\\n            try {\\n                JsonString a = inner.getJsonString(field);\\n                JsonString b = outer.getJsonString(field);\\n                if (a == null &amp;&amp; b == null) {\\n                    continue;\\n                }\\n                if (a == null || !a.equals(b)) {\\n                    return false;\\n                }\\n            }\\n            catch (ClassCastException ex) {\\n                return false;\\n            }\\n        }\\n        return true;\\n    }\\n}&quot;},{&quot;language&quot;:&quot;php&quot;,&quot;tabTitle&quot;:&quot;PHP&quot;,&quot;content&quot;:&quot;\\\/**\\n * Derive key material given a passphrase.\\n * @var $passphrase string\\n * @returns a 3-element array containing the derived key material\\n *\\\/\\nfunction NexoDeriveKeymaterial($passphrase) {\\n\\t$outlen = 80;\\n\\t$salt =  \\&quot;AdyenNexoV1Salt\\&quot;;\\n\\t$rounds = 4000;\\n\\t$bytes = openssl_pbkdf2($passphrase, $salt, $outlen, $rounds, \\&quot;sha1\\&quot;);\\n\\n\\t$hmac_key = substr($bytes, 0, 32);\\n\\t$cipher_key = substr($bytes, 32, 32);\\n\\t$iv = substr($bytes, 64, 16);\\n\\n\\treturn array('hmac_key' =&gt; $hmac_key, 'cipher_key' =&gt; $cipher_key, 'iv' =&gt; $iv);\\n}\\n\\n\\\/**\\n * Take a plaintext Nexo message and create a secure message from it.\\n * @var $message string the input Nexo message in serialized form\\n * @var $keyid the key identifier\\n * @var $keyversion int the version of the key. The pair $keyid-keyversion identifies a key\\n * @var $keymaterial array an array as returned by NexoDeriveKeymaterial()\\n * @returns a secured, serizled Nexo message\\n *\\\/\\nfunction NexoSender($message, $keyid, $keyversion, $keymaterial) {\\n\\t$jsonin = json_decode($message, true);\\n\\t$isrequest = isset($jsonin['SaleToPOIRequest']);\\n\\t$bodykey = $isrequest ? 'SaleToPOIRequest' : 'SaleToPOIResponse';\\n\\t$body = $jsonin[$bodykey];\\n\\n\\t$header = $body['MessageHeader'];\\n\\n\\t\\\/\\\/ Encrypt the original message and compute its hmac\\n\\t$nonce = openssl_random_pseudo_bytes(16);\\n\\t$nexoblob = NexoEncrypt($message, $keymaterial, $nonce);\\n\\t$hmac = NexoHMAC($message, $keymaterial);\\n\\n\\t$trailer = NexoTrailer($keyid, $keyversion, $nonce, $hmac);\\n\\n\\t\\\/\\\/ result has three parts: header, blob and trailer\\n\\t$result = array('MessageHeader' =&gt; $header, 'NexoBlob' =&gt; base64_encode($nexoblob), 'SecurityTrailer' =&gt; $trailer);\\n\\n\\treturn json_encode(array($bodykey =&gt; $result), JSON_PRETTY_PRINT);\\n}\\n\\n\\\/**\\n * Take a secured Nexo message, decrypt and validate it.\\n * @var @message string a serialized, secured Nexo message\\n * @returns string or a serialzed nexo message or null if validaton failed\\n *\\\/\\nfunction NexoReceiver($message) {\\n\\t\\\/\\\/ Warning: almost all validation is missing!\\n\\t\\\/\\\/ Parse the incoming message and decompose it\\n\\t$jsonin = json_decode($message, true);\\n\\t$isrequest = isset($jsonin['SaleToPOIRequest']);\\n\\t$bodykey = $isrequest ? 'SaleToPOIRequest' : 'SaleToPOIResponse';\\n\\t$body = $jsonin[$bodykey];\\n\\t$blob = $body['NexoBlob'];\\n\\t$header = $body['MessageHeader'];\\n\\t$trailer = $body['SecurityTrailer'];\\n\\n\\t\\\/\\\/ Get the information from the SecurityTrailer\\n\\tif ($trailer['AdyenCryptoVersion'] != 1) {\\n\\t\\treturn null;\\n\\t}\\n\\t$keymaterial = NexoLookupKeybyIdAndVersion($trailer['KeyIdentifier'], $trailer['KeyVersion']);\\n\\t$nonce = base64_decode($trailer['Nonce']);\\n\\t$hmac = base64_decode($trailer['Hmac']);\\n\\n\\t\\\/\\\/ Decrypt the blob\\n\\t$nexoblob = base64_decode($body['NexoBlob']);\\n\\t$decrypted = NexoDecrypt($nexoblob, $keymaterial, $nonce);\\n\\n\\t\\\/\\\/ Validate the received hmac against the computed hmac\\n\\t$computed_hmac = NexoHMac($decrypted, $keymaterial);\\n\\tif ($computed_hmac != $hmac) {\\n\\t\\treturn null;\\n\\t}\\n\\n\\t\\\/\\\/ Make sure that the plaintext header and the header in the decrypted message match\\n\\t$decrypted_json = json_decode($decrypted, true);\\n\\tif ($decrypted_json[$bodykey]['MessageHeader'] !== $header) {\\n\\t\\treturn null;\\n\\t}\\n\\treturn $decrypted;\\n}\\n\\n\\n\\\/\\\/ Basic encryption\\\/decryption: AES-256-CBC with default padding\\nfunction NexoEncrypt($message, $keymaterial, $nonce) {\\n\\t$realiv = XorBytes($keymaterial['iv'], $nonce);\\n\\treturn openssl_encrypt($message, \\&quot;AES-256-CBC\\&quot;, $keymaterial['cipher_key'], OPENSSL_RAW_DATA, $realiv);\\n}\\n\\nfunction NexoDecrypt($message, $keymaterial, $nonce) {\\n\\t$realiv = XorBytes($keymaterial['iv'], $nonce);\\n\\treturn openssl_decrypt($message, \\&quot;AES-256-CBC\\&quot;, $keymaterial['cipher_key'], OPENSSL_RAW_DATA, $realiv);\\n}\\n\\n\\\/\\\/ The authentication function, standard SHA256 HMAC\\nfunction NexoHMAC($message, $keymaterial) {\\n\\treturn hash_hmac(\\&quot;sha256\\&quot;, $message, $keymaterial['hmac_key'], true);\\n}\\n\\n\\\/\\\/ Helper funtion: construct a SecurityTrailer\\nfunction NexoTrailer($keyid, $keyversion, $nonce, $hmac) {\\n\\treturn array('KeyVersion' =&gt; $keyversion,\\n\\t\\t'KeyIdentifier' =&gt; $keyid,\\n\\t\\t'Hmac' =&gt; base64_encode($hmac),\\n\\t\\t'Nonce' =&gt; base64_encode($nonce),\\n\\t\\t'AdyenCryptoVersion' =&gt; 1);\\n}\\n\\n\\\/\\\/ Helper funtion: xor two arrays of bytes\\nfunction XorBytes($a, $b) {\\n\\t$r = $a;\\n\\tfor ($i = 0; $i &lt; 16; $i++) {\\n\\t\\t$r[$i] = $r[$i] ^ $b[$i];\\n\\t}\\n\\treturn $r;\\n}\\n\\n\\\/\\\/ Lookup key material based on keyIdentifier and KeyVersion\\nfunction NexoLookupKeybyIdAndVersion($keyid, $keyversion) {\\n\\t\\\/\\\/ Actually, this function should do a lookup based on key id and version.\\n\\t\\\/\\\/ But for demonstration purposes we just return the derived keymaterial for\\n\\t\\\/\\\/ the given test passphrase.\\n\\treturn NexoDeriveKeyMaterial(\\&quot;mysupersecretpassphrase\\&quot;);\\n}&quot;},{&quot;language&quot;:&quot;cs&quot;,&quot;tabTitle&quot;:&quot;C#&quot;,&quot;content&quot;:&quot;using Newtonsoft.Json;\\nusing Newtonsoft.Json.Linq;\\nusing System.Security.Cryptography;\\nusing System.Text\\n\\npublic class NexoCrypto\\n{\\n    const int NEXO_HMAC_KEY_LEN = 32;\\n    const int NEXO_CIPHER_KEY_LEN = 32;\\n    const int NEXO_IV_LEN = 16;\\n\\n    static readonly byte[] PBKDF2_SALT = Encoding.ASCII.GetBytes(\\&quot;AdyenNexoV1Salt\\&quot;);\\n    const int PBKDF2_ITERS = 4000;\\n\\n    byte[]? hmacKey, cipherKey, baseIv;\\n    string? keyId;\\n    int keyVersion;\\n\\n    \\\/\\\/\\\/ &lt;summary&gt;\\n    \\\/\\\/\\\/ Derive keys from a shared passphrase and set key metadata.\\n    \\\/\\\/\\\/ &lt;\\\/summary&gt;\\n    public void Configure(string passphrase, string keyIdentifier, int keyVersion = 0)\\n    {\\n        if (string.IsNullOrEmpty(passphrase)) throw new ArgumentException(\\&quot;passphrase required\\&quot;);\\n\\n        var passBytes = Encoding.UTF8.GetBytes(passphrase);\\n        using var kdf = new Rfc2898DeriveBytes(passBytes, PBKDF2_SALT, PBKDF2_ITERS, HashAlgorithmName.SHA1);\\n        var material = kdf.GetBytes(NEXO_HMAC_KEY_LEN + NEXO_CIPHER_KEY_LEN + NEXO_IV_LEN);\\n\\n        hmacKey = Slice(material, 0, NEXO_HMAC_KEY_LEN);\\n        cipherKey = Slice(material, NEXO_HMAC_KEY_LEN, NEXO_CIPHER_KEY_LEN);\\n        baseIv = Slice(material, NEXO_HMAC_KEY_LEN + NEXO_CIPHER_KEY_LEN, NEXO_IV_LEN);\\n\\n        keyId = keyIdentifier ?? string.Empty;\\n        this.keyVersion = keyVersion;\\n    }\\n\\n    \\\/\\\/\\\/ &lt;summary&gt;\\n    \\\/\\\/\\\/ Encrypts a clear SaleToPOI(Request|Response) JSON and returns a secured JSON string\\n    \\\/\\\/\\\/ { SaleToPOIRequest|SaleToPOIResponse: { MessageHeader, NexoBlob, SecurityTrailer } }.\\n    \\\/\\\/\\\/ &lt;\\\/summary&gt;\\n    public string EncryptAndHmac(string cleartextJson)\\n    {\\n        if (string.IsNullOrWhiteSpace(cleartextJson)) throw new ArgumentException(\\&quot;cleartextJson required\\&quot;);\\n        if (cipherKey == null || hmacKey == null || baseIv == null)\\n            throw new InvalidOperationException(\\&quot;Call Configure() first\\&quot;);\\n\\n        var clearBytes = Encoding.UTF8.GetBytes(cleartextJson);\\n\\n        var root = JToken.Parse(cleartextJson);\\n        var isReq = root[\\&quot;SaleToPOIRequest\\&quot;] != null;\\n        var wrapName = isReq ? \\&quot;SaleToPOIRequest\\&quot; : \\&quot;SaleToPOIResponse\\&quot;;\\n        var inner = (JObject)(root[wrapName] ??\\n          throw new InvalidDataException(\\&quot;Missing SaleToPOI wrapper\\&quot;));\\n        var messageHeader = inner[\\&quot;MessageHeader\\&quot;]?.DeepClone() ??\\n          throw new InvalidDataException(\\&quot;Missing MessageHeader\\&quot;);\\n\\n        var nonce = RandomBytes(NEXO_IV_LEN);\\n        var mac = Hmac(clearBytes);\\n        var cipherBytes = Crypt(clearBytes, nonce, encrypt: true);\\n\\n        var secured = new JObject\\n        {\\n            [\\&quot;MessageHeader\\&quot;] = messageHeader,\\n            [\\&quot;NexoBlob\\&quot;] = Convert.ToBase64String(cipherBytes),\\n            [\\&quot;SecurityTrailer\\&quot;] = new JObject\\n            {\\n                [\\&quot;Hmac\\&quot;] = Convert.ToBase64String(mac),\\n                [\\&quot;KeyIdentifier\\&quot;] = keyId,\\n                [\\&quot;KeyVersion\\&quot;] = keyVersion,\\n                [\\&quot;AdyenCryptoVersion\\&quot;] = 1,\\n                [\\&quot;Nonce\\&quot;] = Convert.ToBase64String(nonce)\\n            }\\n        };\\n\\n        var top = new JObject { [wrapName] = secured };\\n        return top.ToString(Formatting.None);\\n    }\\n\\n    \\\/**\\n     * A helper class to return a decrypted message and the outer header from the\\n     * secured Nexo message\\n     *\\\/\\n    public class BytesAndOuterHeader\\n    {\\n        public byte[] Packet; \\\/\\\/ UTF-8 JSON bytes of inner message\\n        public JObject OuterHeader; \\\/\\\/ The outer MessageHeader\\n\\n        public BytesAndOuterHeader(byte[] packet, JObject outerHeader)\\n        {\\n            Packet = packet;\\n            OuterHeader = outerHeader;\\n        }\\n    }\\n\\n    \\\/\\\/\\\/ &lt;summary&gt;\\n    \\\/\\\/\\\/ Validates the HMAC (over plaintext) and decrypts a secured Nexo message;\\n    \\\/\\\/\\\/ returns inner JSON bytes plus the echoed outer header.\\n    \\\/\\\/\\\/ &lt;\\\/summary&gt;\\n    public BytesAndOuterHeader DecryptAndValidateHmac(string securedJson)\\n    {\\n        if (string.IsNullOrWhiteSpace(securedJson)) throw new ArgumentException(\\&quot;securedJson required\\&quot;);\\n        if (cipherKey == null || hmacKey == null || baseIv == null)\\n            throw new InvalidOperationException(\\&quot;Call Configure() first\\&quot;);\\n\\n        var root = JToken.Parse(securedJson);\\n        var container = (JObject)(root[\\&quot;SaleToPOIRequest\\&quot;] ?? root[\\&quot;SaleToPOIResponse\\&quot;] ??\\n          throw new InvalidDataException(\\&quot;Missing SaleToPOI wrapper\\&quot;));\\n\\n        var messageHeader = (JObject)(container[\\&quot;MessageHeader\\&quot;] ??\\n          throw new InvalidDataException(\\&quot;Missing MessageHeader\\&quot;));\\n        var blobB64 = container.Value&lt;string&gt;(\\&quot;NexoBlob\\&quot;) ??\\n          throw new InvalidDataException(\\&quot;Missing NexoBlob\\&quot;);\\n        var trailer = container[\\&quot;SecurityTrailer\\&quot;] as JObject ??\\n          throw new InvalidDataException(\\&quot;Missing SecurityTrailer\\&quot;);\\n\\n        var nonce = Convert.FromBase64String(trailer.Value&lt;string&gt;(\\&quot;Nonce\\&quot;) ??\\n          throw new InvalidDataException(\\&quot;Missing Nonce\\&quot;));\\n        var received = Convert.FromBase64String(trailer.Value&lt;string&gt;(\\&quot;Hmac\\&quot;) ??\\n          throw new InvalidDataException(\\&quot;Missing Hmac\\&quot;));\\n        var cipher = Convert.FromBase64String(blobB64);\\n\\n        \\\/\\\/ Decrypt first\\n        var clear = Crypt(cipher, nonce, encrypt: false);\\n\\n        \\\/\\\/ HMAC must be validated over the PLAINTEXT\\n        var computed = Hmac(clear);\\n        if (!FixedTimeEquals(received, computed))\\n            throw new CryptographicException(\\&quot;HMAC validation failed\\&quot;);\\n\\n        return new BytesAndOuterHeader(clear, messageHeader);\\n    }\\n\\n    \\\/**\\n     * A helper to compare the inner MessageHeader vs the echoed outer MessageHeader.\\n     * Returns true when all common fields match.\\n     *\\\/\\n    public static bool ValidateInnerAndOuterHeader(JObject innerHeader, JObject outerHeader)\\n    {\\n        if (innerHeader == null || outerHeader == null) return false;\\n\\n        var fields = new[] {\\n            \\&quot;DeviceID\\&quot;,\\n            \\&quot;MessageCategory\\&quot;,\\n            \\&quot;MessageClass\\&quot;,\\n            \\&quot;MessageType\\&quot;,\\n            \\&quot;SaleID\\&quot;,\\n            \\&quot;ServiceID\\&quot;,\\n            \\&quot;POIID\\&quot;,\\n            \\&quot;ProtocolVersion\\&quot;\\n        };\\n\\n        foreach (var f in fields)\\n        {\\n            var a = innerHeader[f]?.ToString();\\n            var b = outerHeader[f]?.ToString();\\n            if (a == null &amp;&amp; b == null) continue;\\n            if (!string.Equals(a, b, StringComparison.Ordinal)) return false;\\n        }\\n        return true;\\n    }\\n\\n    \\\/** Copies a slice from a byte[]; used to split derived key material. *\\\/\\n    static byte[] Slice(byte[] src, int offset, int len)\\n    {\\n        var dst = new byte[len];\\n        Buffer.BlockCopy(src, offset, dst, 0, len);\\n        return dst;\\n    }\\n\\n    \\\/** Returns cryptographically strong random bytes; used for Nonce generation. *\\\/\\n    static byte[] RandomBytes(int len)\\n    {\\n        var b = new byte[len];\\n        RandomNumberGenerator.Fill(b);\\n        return b;\\n    }\\n\\n    \\\/** AES-256-CBC encrypt\\\/decrypt with PKCS7; IV is baseIv XOR nonce as per NEXO Protect. *\\\/\\n    byte[] Crypt(byte[] input, byte[] nonce, bool encrypt)\\n    {\\n        if (cipherKey == null || baseIv == null) throw new InvalidOperationException(\\&quot;Call Configure() first\\&quot;);\\n        if (nonce == null || nonce.Length != NEXO_IV_LEN) throw new ArgumentException(\\&quot;Nonce must be 16 bytes\\&quot;);\\n\\n        var iv = new byte[NEXO_IV_LEN];\\n        for (int i = 0; i &lt; NEXO_IV_LEN; i++) iv[i] = (byte)(baseIv[i] ^ nonce[i]);\\n\\n        using var aes = Aes.Create();\\n        aes.KeySize = 256;\\n        aes.BlockSize = 128;\\n        aes.Mode = CipherMode.CBC;\\n        aes.Padding = PaddingMode.PKCS7;\\n        aes.Key = cipherKey;\\n        aes.IV = iv;\\n\\n        using var transform = encrypt ? aes.CreateEncryptor() : aes.CreateDecryptor();\\n        using var ms = new MemoryStream();\\n        using var cs = new CryptoStream(ms, transform, CryptoStreamMode.Write);\\n        cs.Write(input, 0, input.Length);\\n        cs.FlushFinalBlock();\\n        return ms.ToArray();\\n    }\\n\\n    \\\/** Computes HMAC-SHA256 over the input bytes; used for SecurityTrailer.Hmac. *\\\/\\n    byte[] Hmac(byte[] input)\\n    {\\n        if (hmacKey == null) throw new InvalidOperationException(\\&quot;Call Configure() first\\&quot;);\\n        using var h = new HMACSHA256(hmacKey);\\n        return h.ComputeHash(input);\\n    }\\n\\n    \\\/** Constant\\u2011time compare for MACs to avoid timing leakage. *\\\/\\n    static bool FixedTimeEquals(byte[] a, byte[] b)\\n    {\\n        if (a == null || b == null || a.Length != b.Length) return false;\\n        int diff = 0;\\n        for (int i = 0; i &lt; a.Length; i++) diff |= a[i] ^ b[i];\\n        return diff == 0;\\n    }\\n}&quot;},{&quot;language&quot;:&quot;js&quot;,&quot;tabTitle&quot;:&quot;NodeJS (JavaScript)&quot;,&quot;content&quot;:&quot;const crypto = require('crypto');\\n\\nconst NEXO_HMAC_KEY_LENGTH = 32;\\nconst NEXO_CIPHER_KEY_LENGTH = 32;\\nconst NEXO_IV_LENGTH = 16;\\n\\nclass NexoCrypto {\\n\\n\\tconstructor() {\\n\\n\\t}\\n\\n\\tconfigure(passphrase, keyIdentifier, keyVersion) {\\n\\t\\tif (passphrase) {\\n\\t\\t\\tthis.derivedKeys = this.deriveKeyMaterial(passphrase);\\n\\t\\t}\\n\\n\\t\\tthis.keyIdentifier = keyIdentifier;\\n\\t\\tthis.keyVersion = keyVersion || 0;\\n\\t}\\n\\n\\n\\t\\\/**\\n\\t * Derive key material given a passphrase.\\n\\t * @var $passphrase string\\n\\t * @returns a 3-element array containing the derived key material\\n\\t *\\\/\\n\\tderiveKeyMaterial(passphrase) {\\n\\n\\t\\tvar pass = Buffer.from(passphrase, 'binary');\\n\\t\\tconst salt =  Buffer.from(\\&quot;AdyenNexoV1Salt\\&quot;, 'binary');\\n\\t\\tconst iterations = 4000;\\n\\t\\tconst keylen = NEXO_HMAC_KEY_LENGTH + NEXO_CIPHER_KEY_LENGTH + NEXO_IV_LENGTH;\\n\\n\\t\\tconst key = crypto.pbkdf2Sync(pass, salt, iterations, keylen, 'sha1');\\n\\n\\t\\tvar ret = {\\n\\t\\t\\tkey: key,\\n\\t\\t\\thmac_key: key.slice(0, 32),\\n\\t\\t\\tcipher_key: key.slice(32, 64),\\n\\t\\t\\tiv: key.slice(64, 80)\\n\\t\\t};\\n\\t\\treturn ret;\\n\\t}\\n\\n\\t\\\/**\\n\\t * Encrypt or decrypt data given a iv modifier and using the specified key\\n\\t * The actual iv is computed by taking the iv from the key material and xoring it with ivmod\\n\\t *\\\/\\n\\tcrypt(bytes, dk, ivmod, encrypt) {\\n\\n\\t\\t\\\/\\\/ xor dk.iv and the iv modifier\\n\\t\\tvar actualIV = Buffer.alloc(NEXO_IV_LENGTH);\\n\\t\\tfor (var i = 0; i &lt; NEXO_IV_LENGTH; i++) {\\n\\t\\t\\t actualIV[i] = dk.iv[i] ^ ivmod[i];\\n\\t\\t}\\n\\n\\t\\tvar cipher;\\n\\t\\tif (encrypt) {\\n\\t\\t\\tcipher = crypto.createCipheriv('aes-256-cbc', dk.cipher_key, actualIV);\\n\\t\\t} else {\\n\\t\\t\\tcipher = crypto.createDecipheriv('aes-256-cbc', dk.cipher_key, actualIV);\\n\\t\\t}\\n\\n\\t\\tvar data = cipher.update(bytes);\\n    data = Buffer.concat([data, cipher.final()]);\\n\\n\\t\\treturn data;\\n\\t}\\n\\n\\t\\\/**\\n\\t * Compute a hmac using the hmac_key\\n\\t *\\\/\\n\\thmac(bytes, dk) {\\n\\t\\tvar mac = crypto.createHmac('sha256', dk.hmac_key)\\n\\t\\tvar hmac = mac.update(bytes).digest(); \\\/\\\/hex ?\\n\\t\\treturn hmac;\\n\\t}\\n\\n\\n\\t\\\/**\\n\\t * Encrypt and compose a secured Nexo message\\n\\t *\\n\\t * This functions takes the original message, encrypts it and converts the encrypted form to Base64 and\\n\\t * names it NexoBlob.\\n\\t * After that, a new message is created with a copy of the header, the NexoBlob and an added SecurityTrailer.\\n\\t *\\n\\t * @param in is the byte representation of the unprotected Nexo message\\n\\t * @returns a byte representation of the secured Nexo message\\n\\t *\\\/\\n\\t encrypt_and_hmac(bytes) {\\n\\n\\t\\t \\\/\\\/ parse the json\\n\\t\\t var body = JSON.parse(bytes);\\n\\n\\t\\t \\\/\\\/ and determined if it is a request or responce\\n\\t\\t var request = true;\\n\\t\\t var saletopoirequest = body[\\&quot;SaleToPOIRequest\\&quot;];\\n\\t\\t if (!saletopoirequest) {\\n\\t\\t\\t\\trequest = false;\\n\\t\\t\\t\\tsaletopoirequest = body[\\&quot;SaleToPOIResponse\\&quot;];\\n\\t\\t }\\n\\n\\t\\t \\\/\\\/ pick up the MessageHeader\\n\\t\\t var messageHeader = saletopoirequest[\\&quot;MessageHeader\\&quot;];\\n\\n\\t\\t \\\/\\\/ Generate a random iv nonce\\n\\t\\t var ivmod = (this.derivedKeys.nonce) ? this.derivedKeys.nonce : crypto.randomBytes(NEXO_IV_LENGTH);\\n\\n\\t\\t \\\/\\\/ encrypt taking the original bytes as input\\n\\t\\t var encbytes = this.crypt(bytes, this.derivedKeys, ivmod, true);\\n\\n\\t\\t \\\/\\\/ compute mac over cleartext bytes\\n\\t\\t var hmac = this.hmac(bytes, this.derivedKeys);\\n\\n\\t\\t \\\/\\\/ Construct the inner Json object containing a MessageHeader, a NexoBlob and a SecurityTrailer\\n\\n\\t\\t var msg = {\\n \\t\\t\\t\\tMessageHeader: messageHeader,\\n \\t\\t\\t\\tNexoBlob: encbytes.toString('base64'),\\n \\t\\t\\t\\tSecurityTrailer: {\\n\\t\\t\\t\\t\\tHmac: hmac.toString('base64'),\\n\\t\\t\\t\\t\\tKeyIdentifier: this.keyIdentifier,\\n\\t\\t\\t\\t\\tKeyVersion: parseInt(this.keyVersion),\\n\\t\\t\\t\\t\\tAdyenCryptoVersion: 1,\\n\\t\\t\\t\\t\\tNonce: ivmod.toString('base64')\\n\\t\\t\\t\\t}\\n \\t\\t\\t};\\n\\n\\t\\t \\\/\\\/ Wrap the inner message in a SaleToPOIRequest or SaleToPOIResponse object\\n\\t\\t var reqWrap = (request) ? \\&quot;SaleToPOIRequest\\&quot; : \\&quot;SaleToPOIResponse\\&quot;\\n\\t\\t var total = {};\\n\\t\\t total[reqWrap] = msg;\\n\\n\\t\\t return JSON.stringify(total);\\n\\t }\\n\\n\\t \\\/*\\n    * Validate and decrypt a secured Nexo message\\n    *\\n    *\\\/\\n\\t decrypt_and_validate_hmac(bytes) {\\n\\t\\t \\\/\\\/ parse the json\\n\\t\\t var body = JSON.parse(bytes);\\n\\n\\t\\t \\\/\\\/ and determined if it is a request or responce\\n\\t\\t var request = true;\\n\\t\\t var saletopoirequest = body[\\&quot;SaleToPOIRequest\\&quot;];\\n\\t\\t if (!saletopoirequest) {\\n\\t\\t\\t\\trequest = false;\\n\\t\\t\\t\\tsaletopoirequest = body[\\&quot;SaleToPOIResponse\\&quot;];\\n\\t\\t }\\n\\n\\t\\t \\\/\\\/ pick up the MessageHeader\\n\\t\\t var messageHeader = saletopoirequest[\\&quot;MessageHeader\\&quot;];\\n\\t\\t var payload = saletopoirequest[\\&quot;NexoBlob\\&quot;];\\n\\t\\t var ciphertext = Buffer.from(payload, 'base64');\\n\\n\\t\\t \\\/\\\/ Get the SecurityTrailer and its values\\n\\t\\t var jsonTrailer = saletopoirequest[\\&quot;SecurityTrailer\\&quot;];\\n\\t\\t var version = jsonTrailer[\\&quot;AdyenCryptoVersion\\&quot;];\\n\\n\\t\\t var nonceB64 = jsonTrailer[\\&quot;Nonce\\&quot;];\\n\\t\\t var ivmod = Buffer.from(nonceB64, 'base64');\\n\\n\\t\\t var keyId = jsonTrailer[\\&quot;KeyIdentifier\\&quot;];\\n\\t\\t var kversion = jsonTrailer[\\&quot;KeyVersion\\&quot;];\\n\\t\\t var hmacB64 = jsonTrailer[\\&quot;Hmac\\&quot;];\\n\\n\\n\\t\\t var ret = this.crypt(ciphertext, this.derivedKeys, ivmod, false);\\n\\t\\t var json = JSON.parse(ret);\\n\\n\\t\\t \\\/\\\/ Base64 decode the received HMAC and compare it to a computed hmac\\n\\t\\t \\\/\\\/ Use a timing safe compare, this is to mitigate a (theoretical) timing based attack\\n\\t\\t var receivedmac = Buffer.from(hmacB64, 'base64');\\n\\t\\t var hmac = this.hmac(ret, this.derivedKeys);\\n\\n\\t\\t \\\/\\\/console.log(receivedmac);\\n\\t\\t \\\/\\\/console.log(hmac);\\n\\n\\t\\t if (receivedmac.length != hmac.length) {\\n\\t\\t\\t\\t\\\/\\\/ console.log(\\&quot;HMAC Validation failed - Length mismatch\\&quot;);\\n\\t\\t\\t\\treturn;\\n\\t\\t }\\n\\t\\t var equal = true;\\n\\t\\t for (var i = 0; i &lt; hmac.length; i++) {\\n\\t\\t\\t\\tif (receivedmac[i] != hmac[i]) {\\n\\t\\t\\t\\t\\t equal = false;\\n\\t\\t\\t\\t}\\n\\t\\t }\\n\\t\\t if (!equal) {\\n\\t\\t\\t\\t\\\/\\\/ console.log(\\&quot;HMAC Validation failed - Not Equal\\&quot;);\\n\\t\\t\\t\\treturn;\\n\\t\\t }\\n\\n\\t\\t return JSON.stringify(json);\\n\\t }\\n}&quot;}]\" :enable-copy-link-to-code-block=\"true\" :code-sample-card-size=\"'fullsize'\"><\/code-sample>\n<\/div>\n<h2>Troubleshooting<\/h2>\n<p><a href=\"#crypto-errors\">Crypto errors<\/a> and <a href=\"#ssl-connection-error\">SSL connection errors<\/a> indicate a problem with the protection of the local communications.<\/p>\n<h3>Crypto errors<\/h3>\n<p><strong>Example<\/strong>:<\/p>\n<pre><code class=\"language-bash\">Exception: System.Net.WebException: The remote server returned an error: (401) Unauthorized.<\/code><\/pre>\n<p>The response body contains:<\/p>\n<pre><code class=\"language-json\">{\n   \"errors\":[\n      \"Nexo Service: crypto error\"\n   ],\n   \"ServiceID\":\"1234567890\"\n}<\/code><\/pre>\n<p><strong>Cause<\/strong>: Crypto errors are related to the shared key. After you <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local#set-up-shared-key\">set up the shared key in your Customer Area<\/a>, the shared key values in your code must match the shared key values in the Customer Area.<\/p>\n<p>If you are using a library, check the values for the relevant object:<\/p>\n<ul>\n<li>With the <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect-with-library?tab=_net_1\">.NET library<\/a>, check the <code>EncryptionCredentialDetails<\/code> object.<\/li>\n<li>With the <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect-with-library?tab=java_2\">Java library<\/a>, check the<code>SecurityKey<\/code> object.<\/li>\n<li>With the <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect-with-library?tab=node_3\">Node library<\/a>, check the <code>SecurityKey<\/code> object.<\/li>\n<\/ul>\n<p><\/p>\n<p>If you are using your own code:<\/p>\n<ul>\n<li>Check the <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect#derive-key-material\">key derivation function<\/a>. This uses the passphrase of the shared key.<\/li>\n<li>Check the <a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect#create-the-security-trailer\">security trailer function<\/a>. This uses the version and the identifier of the shared key.<\/li>\n<\/ul>\n<h3>SSL connection error<\/h3>\n<p><strong>Example<\/strong>:<\/p>\n<pre><code class=\"language-bash\">Exception : System.Net.WebException: The SSL connection could not be established<\/code><\/pre>\n<p><strong>Possible cause<\/strong>: Adyen's root certificate is not installed correctly.<\/p>\n<h2>See also<\/h2>\n<div class=\"see-also-links output-inline\" id=\"see-also\">\n<ul><li><a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\"\n                        target=\"_self\"\n                        >\n                    Building a local integration\n                <\/a><\/li><li><a href=\"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect-with-library\"\n                        target=\"_self\"\n                        >\n                    Set up protection of local communications using an Adyen GitHub library\n                <\/a><\/li><\/ul><\/div>\n","url":"https:\/\/docs.adyen.com\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect","articleFields":{"description":"Write your own code to protect local communications between your POS app and terminal.","feedback_component":true,"id":"47489964","type":"page","_expandable":{"operations":""},"status":"current","last_edit_on":"17-06-2021 09:44"},"algolia":{"url":"https:\/\/docs.adyen.com\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect","title":"Protect with your own code","content":"If your integration uses local communications, you need to protect your integration against man-in-the-middle attacks, eavesdropping, and tampering.\nTo protect the communications between your POS app and the terminal, you:\n\nValidate the terminal certificate. This confirms that your POS app is communicating directly with an Adyen-supplied payment terminal, and not an impostor.\nEncrypt communications. This prevents intruders from reading the messages transmitted between the POS app and the terminal.\n\nRequirements\n\nMake sure that you have:\n\nInstalled Adyen's root certificate.\nSet up a shared key.\n\n\nValidate the terminal certificate\nEvery time your POS app connects to the terminal, you have to check the certificate on the terminal against Adyen's root certificate.\n\n\nVerify 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.\n\nYou 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.\n\n\n\nValidate the Common Name. The Common Name is in one of the following formats:\n\n\nTest terminal Common Name formats:\n\nlegacy-terminal-certificate.test.terminal.adyen.com\n[POIID].test.terminal.adyen.com\n\n[POIID] = [Terminal model]-[Serial number]\nFor example, P400Plus-123456789.test.terminal.adyen.com\n\n\n\n\nLive terminal Common Name formats:\n\nlegacy-terminal-certificate.live.terminal.adyen.com\n[POIID].live.terminal.adyen.com\n\n[POIID] = [Terminal model]-[Serial number]\nFor example, P400Plus-123456789.live.terminal.adyen.com\n\n\n\n\nDepending on the number of terminals you are integrating, you may want to use regular expressions in your code to validate the Common Name.\n\n\nIf the certificate on the connected terminal passes verification, your POS app is connected to an Adyen-supplied payment terminal.\nEncrypt communications\nTo prevent others from being able to read requests and responses sent between your POS app and the payment terminal, you have to:\n\nEncrypt Terminal API messages before sending.\nDecrypt and validate Terminal API messages that you receive.\n\nIn 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.\nEncrypt Terminal API messages\nTo encrypt Terminal API messages, proceed as follows:\n\nDerive key material by running an HMAC-based key derivation function on the shared key.\nEncrypt the message using the derived key material.\nCalculate the HMAC signature for the message, using the derived key material.\nCreate a security trailer that contains the HMAC signature and identifies the shared key that was used for the encryption and the HMAC signature.\nCompose a new message containing the encrypted message as a Base64-encoded blob and the security trailer.\n\nThe result is a new message ready for sending.\nDerive key material\nTo 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.\nThis 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.\nTo derive key material:\n\n\nApply the key derivation function PBKDF2-HMAC-SHA1 to the passphrase using the following parameters:\n\n\n\nParameter\nValue\n\n\n\n\nSalt\nAdyenNexoV1Salt\n\n\nSalt length\n15\n\n\nRounds\n4000\n\n\nKey length\n80\n\n\n\n\n\n\nThis returns a three-element array containing the 32-byte hmac_key, the 32-byte cipher_key and the 16-byte initialization vector iv.\n\n\nEncrypt the message\n\n\nGenerate a nonce of the same length as the iv (16 bytes).\n\n\n\n\n\nAdd 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.\n\n\n\n\n\nEncrypt the message with AES265 in CBC mode using the full original message, the derived key material, and the nonce as input.\n\n\n\n\n\nCalculate the signature\n\n\nCalculate the HMAC signature using the full original message and the hmac_key from the derived key material as input.\n\n\n\n\n\nCreate the security trailer\n\n\nCreate a security trailer containing the version number and identifier of the shared secret, the nonce, and the HMAC signature.\n\n\n\n\n\nCompose the new message\n\n\nCreate a new message that consists of:\n\nThe same body key as the original message: SaleToPOIRequest or SaleToPOIResonse.\nThe same MessageHeader as the original message.\nA Base64-encoded blob with the encrypted original message.\nThe security trailer.\n\n\n\n\nThe result is the new message, ready for sending. Here is an example:\n{\n    \"SaleToPOIRequest\":{\n        \"MessageHeader\":{\n            \"MessageClass\":\"Service\",\n            \"ProtocolVersion\":\"3.0\",\n            \"ServiceID\":\"6158\",\n            \"MessageCategory\":\"Abort\",\n            \"SaleID\":\"POSSystemID12345\",\n            \"MessageType\":\"Request\",\n            \"POIID\":\"M400-260193322\"\n        },\n        \"NexoBlob\":\"ae8b41wcH9ZH18CRTHSPXi4FdN5Hd2vOQ9ZTKS+GsHvXFqyrAPtVZtmlyI5fWzxpzLMYOyZIAbaSFuasmGi2WcvFO5DBIWvstaQyIfDgcs9oVCuSWvgLXqnCocV8juZNjYGWllY1t0HKuym0I1lCeQRPehzyNbQn5aUp7fr6AuUTgLC+bAZWh\/DqnxCCW5wcyNq9QFC8H+1Gm9R4weJH8zEBMTxldh1BDwp\/5Xabz5nkfvDYranT463PTw9czge5VcgE7sGaBLaMWzYU9HI9QVlShceasOZo18rohNRdeaJVuJ1JJme2ZY1ZWav44rXi77NN3QuC5mbj0bUCKFOhTCOVcxKdNlIlmF0tAmCcmNiPwmSLL6kmygZNcgQ7zKjWVJsFhQ+2I4hOWCE4ZbJ6jAxyGbLnCpSjzhfFpLBQvGRuFiaCNMNbAUh2iL9Ep6jMlf5\/SqpYXhji+8hQF8jXMF9i6oYJ1G\/WUQRSajVklpHk7KoTpH2JjtuG7jZmPxzVGj1\/vKPSaT90WiVPOay1vMLKb6V3Tc+DpjG0Y2sNj+bc6PvqnXmUPPlyiA+I65XkawdXR3qcsm2AFNLRTLZR4Q0og5FXXpZxOBFtfmDNFQ+Ygtb\/JqsB960HaWhQkAyBxZKJ0nfWBeriiF4t1c6ppgejqmIqxAauOmAJjng+5hcA3x2kSFl9MT6kGx21Kt04ijAFX7OTyfBggJFEhnphHQ==\",\n        \"SecurityTrailer\":{\n            \"KeyVersion\":0,\n            \"KeyIdentifier\":\"mykey\",\n            \"Hmac\":\"h6ehPJOASK4NXGESERmXo5mP9YFxpox7VoAFGIb9s8Y=\",\n            \"Nonce\":\"BoBZRF2QmDlNnmeo1QYeZQ==\",\n            \"AdyenCryptoVersion\":1\n        }\n    }\n}\n\n\nDecrypt Terminal API messages\nDecrypting messages requires the following steps:\n\nDecompose the received message.\nIdentify the shared key and use that to derive key material.\nDecrypt the original message using the derived key material\nValidate the message by checking that the HMAC signature and MessageHeader of the received message are the same as those of the decrypted original message.\n\nThe result is the decrypted original message ready for processing.\n\n\nCreate some functions you'll need later on in the NexoReceiver function:\n\nA function to look up the passphrase.\n\n\n\n\n\nA 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.\n\n\n\n\n\n\nDecompose the message\n\n\nParse the received message and decompose it into three parts::\n\nThe MessageHeader.\nThe blob of the encrypted original message.\nThe security trailer.\n\n\n\n\n\n\nDerive key material\n\n\nLook 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.\n\n\n\n\n\nReuse the NexoDeriveKeymaterial function to derive the key material based on the passphrase.\n\n\nDecrypt the original message\n\n\nBase64-decode the blob and decrypt the original message using the cipher_key and the nonce as input, and reusing the XorBytes function from the encryption steps to set the initialization vector for the decryption algorithm.\n\n\n\n\n\nValidate the message\n\n\nValidate 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.\n\n\n\n\n\nVerify that the plaintext MessageHeader matches the MessageHeader in the decrypted message, and then return the decrypted message.\n\n\n\nIf the validation succeeds, the result is the decrypted message, ready for processing.\n\n\nFull encryption code samples\nThe next examples show the full code for encrypting and decrypting messages. \n\n    \n\nTroubleshooting\nCrypto errors and SSL connection errors indicate a problem with the protection of the local communications.\nCrypto errors\nExample:\nException: System.Net.WebException: The remote server returned an error: (401) Unauthorized.\nThe response body contains:\n{\n   \"errors\":[\n      \"Nexo Service: crypto error\"\n   ],\n   \"ServiceID\":\"1234567890\"\n}\nCause: 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.\nIf you are using a library, check the values for the relevant object:\n\nWith the .NET library, check the EncryptionCredentialDetails object.\nWith the Java library, check theSecurityKey object.\nWith the Node library, check the SecurityKey object.\n\n\nIf you are using your own code:\n\nCheck the key derivation function. This uses the passphrase of the shared key.\nCheck the security trailer function. This uses the version and the identifier of the shared key.\n\nSSL connection error\nExample:\nException : System.Net.WebException: The SSL connection could not be established\nPossible cause: Adyen's root certificate is not installed correctly.\nSee also\n\n\n                    Building a local integration\n                \n                    Set up protection of local communications using an Adyen GitHub library\n                \n","type":"page","locale":"en","boost":15,"hierarchy":{"lvl0":"Home","lvl1":"In-person payments","lvl2":"Design your integration","lvl3":"Choose an integration architecture","lvl4":"Building a local integration","lvl5":"Protect with your own code"},"hierarchy_url":{"lvl0":"https:\/\/docs.adyen.com\/","lvl1":"https:\/\/docs.adyen.com\/point-of-sale","lvl2":"https:\/\/docs.adyen.com\/point-of-sale\/design-your-integration","lvl3":"https:\/\/docs.adyen.com\/point-of-sale\/design-your-integration\/choose-your-architecture","lvl4":"https:\/\/docs.adyen.com\/point-of-sale\/design-your-integration\/choose-your-architecture\/local","lvl5":"\/point-of-sale\/design-your-integration\/choose-your-architecture\/local\/protect"},"levels":6,"category":"In-person payments","category_color":"green","tags":["Protect"]},"articleFiles":{"encryption-decryption.js":"<p alt=\"\">encryption-decryption.js<\/p>"}}
