Se sua integração usar comunicações locais, você precisará executar algumas etapas adicionais para ajudar a proteger sua integração contra ataques, interceptação e adulteração.
Para proteger as comunicações entre sua caixa registradora e o terminal, você precisa:
- Validar o certificado do terminal. Isso confirma que sua caixa registradora está se comunicando diretamente com um terminal da Adyen e não com um impostor.
- Criptografar comunicações . Isso impede que os invasores leiam as mensagens transmitidas entre a caixa registradora e o terminal.
Validar certificado do terminal
Todo terminal da Adyen vem pré-instalado com um certificado assinado pela Adyen. Para confirmar que sua caixa registradora está se comunicando diretamente com um terminal da Adyen, é necessário:
- Instalar o certificado raiz público da Adyen trust store.
- Autenticar a conexão entre a caixa registradora e o terminal, verificando o certificado no terminal .
Etapa 1: Instalar o certificado
Para instalar o certificado raiz público da Adyen em seu trust store:
- 
Faça o download dos certificados raiz públicos da Adyen em sua caixa registradora: - Certificado do terminal TEST: Para validar o certificado nos terminais Adyen TEST.
- Certificado do terminal LIVE: FPara validar o certificado nos terminais Adyen LIVE.
 Esses certificados podem ser convertidos para formatos como .crt, .cer, ou .p12, se necessário. 
- 
Verifique se a assinatura SHA-256 nos certificados corresponde ao seguinte: - Certificado de terminal TEST: 3A 33 C3 34 C3 0F 69 46 E9 75 4B 6B B1 67 2B 54 6F BA A9 66 FB 6A 4B 58 AA 4E 3A BE 80 A7 EC BE
- 
Certificado de terminal LIVE: 06 D4 86 41 95 4B 95 7D 7A F5 F5 E4 5A 58 D8 61 DB 0D E3 CC ED BB 98 36 60 BB 01 6C E6 14 2D A1
 Em um ambiente Windows a impressão digital SHA-256 pode não estar prontamente disponível por código ou MMC. Nesse caso, verifique a assinatura SHA-1 nos certificados: - Certificado de terminal TEST: D5 02 7F A8 B3 93 96 DB 2A 4F B1 86 EF 61 E4 A4 40 A7 30 51
- 
Certificado de terminal LIVE: 62 61 0D 88 27 8E 95 B7 F8 57 9A 9B 5E 07 85 D7 72 87 66 42
 
- Certificado de terminal TEST: 
- 
Instale os certificados no trust store da sua caixa registradora. Siga as instruções do fornecedor para adicionar certificados raiz ao trust store do sistema ou do usuário. 
 Por exemplo, a documentação da Microsoft para adicionar certificados usando o snap-in MMC, ou importar certificados com o PowerShell.
 Para iOS, consulte: Manipulação de certificado iOS.
Depois de instalar os certificados em seu trust store, verifique o certificado no terminal da sua caixa registradora.
Etapa 2: Verificar o certificado
Quando sua caixa registradora se conecta ao terminal, ela deve:
- 
Verificar se o certificado no terminal está assinado pelo certificado raiz confiável que você instalou. Essa verificação inclui a verificação automática do certificado intermediário fornecido pelo terminal no início da conexão. Você verá um erro de incompatibilidade de nome comum durante a verificação. Isso é normal e acontece porque o Nome Comum do certificado não é resolvido no DNS. 
- 
Valide o nome comum. O nome comum está em um dos seguintes formatos: - 
Formatos de nome comum do terminal TEST: - legacy-terminal-certificate.test.terminal.adyen.com
- [POIID].test.terminal.adyen.com
[POIID] = [Modelo do terminal]-[Número de série] 
 Por exemplo, P400Plus-123456789.test.terminal.adyen.com
 
- 
Formatos de nome comum do terminal LIVE: - legacy-terminal-certificate.live.terminal.adyen.com
- [POIID].live.terminal.adyen.com
[POIID] = [Modelo do terminal]-[Número de série] 
 For example P400Plus-123456789.live.terminal.adyen.com
 
 Dependendo do número de terminais que você está integrando, convém usar expressões regulares no seu código para validar o Nome Comum. 
- 
Se o certificado no terminal conectado passar na verificação, sua caixa registradora será conectada a um terminal Adyen.
Criptografar comunicações
Para impedir que outras pessoas possam ler as comunicações entre sua caixa registradora e o terminal, é necessário criptografar as mensagens enviadas entre esses dispositivos. Criptografar essas comunicações envolve:
Etapa 1: Derivar chave de criptografia
Derive uma chave de criptografia para proteger a comunicação entre dispositivos usando a API do Terminal.
Para derivar uma chave de criptografia, as duas partes devem:
- Compartilhar um segredo de comprimento variável para obter o material principal.
- Derivar a chave usando PBKDF2_HMAC_SHA1
- 
Incluir os seguintes parâmetros: Parâmetro Valor salt AdyenNexoV1Salt salt length 15; rounds 4000; Se o programa salvar a chave derivada resultante, esse cálculo será realizado uma vez para cada segredo compartilhado. O material da chave derivada consiste em 80 bytes: uma chave de cifra de 32 bytes, uma chave HMAC de 32 bytes e um vetor de inicialização (IV) de 16 bytes. O IV é XORed com um nonce aleatório que é gerado por mensagem na criptografia. Uma chave derivada é identificada por sua KeyIdentifiere sua versão.
O exemplo de código C a seguir demonstra como você usaria o OpenSSL para derivar o material principal:
/* The struct to hold the derived keys is defined like so: */
struct nexo_derived_keys {
    uint8_t hmac_key[NEXO_HMAC_KEY_LENGTH];
    uint8_t cipher_key[NEXO_CIPHER_KEY_LENGTH];
    uint8_t iv[NEXO_IV_LENGTH];
};
/* Function to derive the keys: */
int nexo_derive_key_material(const char * passphrase, size_t len, struct nexo_derived_keys *dk) {
    const unsigned char salt[] = "AdyenNexoV1Salt";
    const int saltlen = sizeof(salt) - 1;
    const int rounds = 4000;
    return PKCS5_PBKDF2_HMAC_SHA1(passphrase, len, salt, saltlen, rounds,
             sizeof(struct nexo_derived_keys), (unsigned char *)dk);
}Etapa 2: Criptografar e descriptografar mensagens
Configure a criptografia na API do terminal para proteger as mensagens comunicadas entre o terminal e a caixa registradora.
Criptografar mensagens
Para criptografar uma mensagem:
- Criptografar o corpo da mensagem usando um vetor de inicialização dk.iv (IV) do Nonce XOR, em que dk.iv é o IV no material da chave derivada.
 O nonce (número usado uma vez) é uma sequência aleatória de bytes do mesmo tamanho que dk.iv gerado por mensagem.
- Coloque o nonce na SecurityTrailercomo uma cadeia de caracteres codificada Base64.
Descriptografar e validar mensagens
Para descriptografar e validar uma mensagem:
- 
Decomponha a mensagem em suas várias partes. 
- 
Verifique o AdyenCryptoVersionnoSecurityTrailer.
- 
Recupere o material principal com base em KeyIdentifiereKeyVersion.
- 
Descriptografe o blob usando o IV e digite o material da chave e o Nonce no SecurityTrailer.
- 
Valide o HMAC computando-o no blob descriptografado. Não valide no formulário serializado. 
- 
Compare o resultado ao blob no SecurityTrailer. A mensagem contém uma cópia em texto não criptografadoMessageHeaderpara fins de roteamento.
- 
Uma vez emparelhado, compare o texto não criptografado MessageHeader com oMessageHeaderdo corpo descriptografado e verifique se eles não são iguais aosMessageHeader` da parte descriptografado.
Configure uma chave na área do cliente
A chave que o terminal utiliza, recuperada com a configuração do terminal, é definida na Customer Area. Atualmente, o software do terminal é capaz de usar apenas uma chave. Se vários dispositivos estiverem se comunicando com o mesmo terminal, eles deverão compartilhar a mesma chave.
Para configurar a chave:
- Abra a Customer Area e vá para In-person payments > Terminals.
- Clique na linha do terminal que você deseja configurar. A página de configuração desse terminal será aberta.
- Clique em View decrypted properties.
 A página é atualizada e retorna ao menu da API do terminal.
- Clique em Terminal API tab.
- Clique em Edit.
- Digite o identificador da chave de criptografia e a senha da chave.
- Clique em Save.
- Adicione sua chave no campo Security Keys.
A seguir, é apresentado um exemplo da propriedade das Chaves de Segurança:
{
   "defaultKey":{
      "passphrase":"mysupersecretpassphrase",
      "keyIdentifier":"mykey",
      "version":0
   }
}Exemplo de criptografia
O código de exemplo abaixo implementa criptografia de mensagens, computação e construção do HMAC, além da operação reversa: divisão, validação e descriptografia do HMAC.
Por padrão, o Java tem restrição AES 128. Pode ser necessário fazer o download de um novo arquivo de políticas para ativar o AES 256, caso contrário, a exceção de chave ilegal será lançada.
import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.text.ParseException;
import java.util.Base64;
import java.util.Random;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.json.*;
public class NexoCrypto {
    public static final int NEXO_HMAC_KEY_LENGTH = 32;
    public static final int NEXO_CIPHER_KEY_LENGTH = 32;
    public static final int NEXO_IV_LENGTH = 16;
   /**
    * Where to look for pre-derived key files
    */
   private String keyDirectory;
   /**
    * The keys material to use when we are sending
    */
   private String keyIdentifier;
   private long keyVersion;
   private NexoDerivedKeys derivedKeys;
    /**
     * A container for Nexo derived keys
     *
     * Nexo derived keys is a 80 byte struct containing key data. These 80
     * bytes are derived from a passsphrase.
     */
   public class NexoDerivedKeys {
      public byte hmac_key[];
      public byte cipher_key[];
      public byte iv[];
      public NexoDerivedKeys() {
         hmac_key = new byte[NEXO_HMAC_KEY_LENGTH];
         cipher_key = new byte[NEXO_CIPHER_KEY_LENGTH];
         iv = new byte[NEXO_IV_LENGTH];
      }
      /**
       * Read a key material file of 80 bytes, splitting it in the hmac_key, cipher_key and iv
       */
      public void readKeyData(String keyId, long keyV) throws IOException {
         String filename = keyDirectory + '/' + keyId + "." + Long.toString(keyV) + ".key";
         FileInputStream stream = null;
         try {
            stream = new FileInputStream(filename);
            stream.read(this.hmac_key);
            stream.read(this.cipher_key);
            stream.read(this.iv);
         } finally {
            if (stream != null) {
               try {
                  stream.close();
               }
               catch (Exception ignored) {
               }
            }
         }
      }
   };
   /**
    * Use this constructor if you want to be able to both encryption and decryption
    */
   public NexoCrypto(String dir, String keyID, long keyV) throws IOException {
      keyDirectory = dir;
      keyIdentifier = keyID;
      keyVersion = keyV;
      derivedKeys = new NexoDerivedKeys();
      derivedKeys.readKeyData(keyIdentifier, keyVersion);
   }
   /**
    * Use this constructor if you a decrypt-only NexoCrypto object
    */
   public NexoCrypto(String dir) {
      keyDirectory = dir;
   }
   /**
    * Given a passphrase, compute 80 byte key of key material according to crypto.md
    */
   public static byte[] deriveKeyMaterial(char[] passphrase) throws NoSuchAlgorithmException, InvalidKeySpecException {
      byte[] salt = "AdyenNexoV1Salt".getBytes();
      int iterations = 4000;
      PBEKeySpec spec = new PBEKeySpec(passphrase, salt, iterations, (NEXO_HMAC_KEY_LENGTH +
            NEXO_CIPHER_KEY_LENGTH + NEXO_IV_LENGTH) * 8);
      SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
      byte[] keymaterial = skf.generateSecret(spec).getEncoded();
      return keymaterial;
   }
   /**
    * Encrypt or decrypt data given a iv modifier and using the specified key
    *
    * The actual iv is computed by taking the iv from the key material and xoring it with ivmod
    */
   private byte[] crypt(byte[] bytes, NexoDerivedKeys dk, byte[] ivmod, int mode)
         throws NoSuchAlgorithmException, NoSuchPaddingException,
            IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
      SecretKeySpec s = new SecretKeySpec(dk.cipher_key, "AES");
      // xor dk.iv and the iv modifier
      byte[] actualIV = new byte[NEXO_IV_LENGTH];
      for (int i = 0; i < NEXO_IV_LENGTH; i++) {
         actualIV[i] = (byte) (dk.iv[i] ^ ivmod[i]);
      }
      IvParameterSpec i = new IvParameterSpec(actualIV);
      cipher.init(mode, s, i);
      return cipher.doFinal(bytes);
   }
   /**
    * Compute a hmac using the hmac_key
    */
   private byte[] hmac(byte[] bytes, NexoDerivedKeys dk) throws NoSuchAlgorithmException, InvalidKeyException {
      Mac mac = Mac.getInstance("HmacSHA256");
      SecretKeySpec s = new SecretKeySpec(dk.hmac_key, "HmacSHA256");
      mac.init(s);
      return mac.doFinal(bytes);
   }
   /**
    * Encrypt and compose a secured Nexo message
    *
    * This functions takes the original message, encrypts it and converts the encrypted form to Base64 and
    * names it NexoBlob.
    * After that, a new message is created with a copy of the header, the NexoBlob and an added SecurityTrailer.
    *
    * @param in is the byte representation of the unprotected Nexo message
    * @returns a byte representation of the secured Nexo message
    */
   public byte[] encrypt_and_hmac(byte in[]) throws InvalidKeyException, NoSuchAlgorithmException,
         NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
         InvalidAlgorithmParameterException {
      Base64.Encoder encb64 = Base64.getEncoder();
      // parse the json and determined if it is a request or responce
      JsonReader jsonreader = Json.createReader(new ByteArrayInputStream(in));
      JsonObject body = jsonreader.readObject();
      boolean request = true;
      JsonObject saletopoirequest = body.getJsonObject("SaleToPOIRequest");
      if (saletopoirequest == null) {
         request = false;
         saletopoirequest = body.getJsonObject("SaleToPOIResponse");
      }
      // pick up the MessageHeader
      JsonObject messageheader = saletopoirequest.getJsonObject("MessageHeader");
      // Generate a random iv nonce
      byte[] ivmod = new byte[NEXO_IV_LENGTH];
      new Random().nextBytes(ivmod);
      // encrypt taking the original bytes as input
      byte[] encbytes = crypt(in, this.derivedKeys, ivmod, Cipher.ENCRYPT_MODE);
      // compute mac over cleartext bytes
      byte[] hmac = hmac(in, this.derivedKeys);
      // Construct the inner Json object containing a MessageHeader, a NexoBlob and a SecurityTrailer
      JsonObject msg = Json.createObjectBuilder()
            .add("MessageHeader", messageheader)
            .add("NexoBlob", new String(encb64.encode(encbytes)))
            .add("SecurityTrailer", Json.createObjectBuilder()
                  .add("Hmac", new String(encb64.encode(hmac)))
                  .add("KeyIdentifier", keyIdentifier)
                  .add("KeyVersion", keyVersion)
                  .add("AdyenCryptoVersion", 1)
                  .add("Nonce", new String(encb64.encode(ivmod)))
                  ).build();
      // Wrap the inner message in a SaleToPOIRequest or SaleToPOIResponse object
      JsonObject total = Json.createObjectBuilder()
            .add(request ?  "SaleToPOIRequest" : "SaleToPOIResponse" , msg)
            .build();
      ByteArrayOutputStream stream = new ByteArrayOutputStream();
      JsonWriter writer = Json.createWriter(stream);
      writer.writeObject(total);
      writer.close();
      return stream.toByteArray();
   }
   /**
    * A helper class to return a decrypted mesasage and the outer header from the
    * secured Nexo message
    */
   public class BytesAndOuterHeader {
      public byte[] packet;
      public JsonObject outer_header;
      public BytesAndOuterHeader(byte[] packet, JsonObject outer_header) {
         this.packet = packet;
         this.outer_header = outer_header;
      }
   }
   /*
    * Validate and decrypt a secured Nexo message
    *
    * @returns a BytesAndOuterHeader object or null on failure
    */
   public BytesAndOuterHeader decrypt_and_validate_hmac(byte in[]) throws InvalidKeyException,
         NoSuchAlgorithmException, IOException, NoSuchPaddingException, IllegalBlockSizeException,
         BadPaddingException, InvalidAlgorithmParameterException {
      Base64.Decoder b64dec = Base64.getDecoder();
      // Parse bytes and retrieve MessageHeader
      InputStream stream = new ByteArrayInputStream(in);
      JsonReader jsonreader = Json.createReader(stream);
      JsonObject total = jsonreader.readObject();
      if (total == null) {
         throw new IOException("Faulty JSON");
      }
      JsonObject saletopoirequest = total.getJsonObject("SaleToPOIRequest");
      if (saletopoirequest == null) {
         saletopoirequest = total.getJsonObject("SaleToPOIResponse");
      }
      if (saletopoirequest == null) {
         throw new IOException("No SaleToPOIRequest or SaleToPOIResponse");
      }
      JsonObject messageheader = saletopoirequest.getJsonObject("MessageHeader");
      if (messageheader == null) {
         throw new IOException("MessageHeader not found");
      }
      // Get the encrypted actual message and base64 decode it
      JsonString payload = saletopoirequest.getJsonString("NexoBlob");
      if (payload == null) {
         throw new IOException("NexoBlob not found");
      }
      byte[] ciphertext = b64dec.decode(payload.getString());
      // Get the SecurityTrailer and its values
      JsonObject jsonTrailer = saletopoirequest.getJsonObject("SecurityTrailer");
      if (jsonTrailer == null) {
         throw new IOException("SecurityTrailer not found");
      }
      JsonNumber version = jsonTrailer.getJsonNumber("AdyenCryptoVersion");
      if (version == null || version.intValue() != 1) {
         throw new IOException("AdyenCryptoVersion version not found or not supported");
      }
      JsonString nonce = jsonTrailer.getJsonString("Nonce");
      if (nonce == null) {
         throw new IOException("Nonce not found");
      }
      JsonString keyId = jsonTrailer.getJsonString("KeyIdentifier");
      if (keyId == null) {
         throw new IOException("KeyIdentifier not found");
      }
      JsonNumber kversion = jsonTrailer.getJsonNumber("KeyVersion");
      if (kversion == null) {
         throw new IOException("KeyVersion not found");
      }
      JsonString b64 = jsonTrailer.getJsonString("Hmac");
      if (b64 == null) {
         throw new IOException("Hmac not found");
      }
      // Read the key from disk
      NexoDerivedKeys dk = new NexoDerivedKeys();
      dk.readKeyData(keyId.getString(), kversion.longValue());
      // Decrypt the actual message with the base64 decoded ivmod as found in the securitytrailer
      byte[] ivmod = b64dec.decode(nonce.getString());
      byte[] ret = crypt(ciphertext, dk, ivmod, Cipher.DECRYPT_MODE);
      // Base64 decode the received HMAC and compare it to a computed hmac
      // Use a timing safe compare, this is to mitigate a (theoretical) timing based attack
      byte[] receivedmac = b64dec.decode(b64.getString());
      byte[] hmac = hmac(ret, dk);
      if (receivedmac.length != hmac.length) {
         throw new IOException("Validation failed");
      }
      boolean equal = true;
      for (int i = 0; i < hmac.length; i++) {
         if (receivedmac[i] != hmac[i]) {
            equal = false;
         }
      }
      if (!equal) {
         throw new IOException("Validation failed");
      }
      // Return decrypted message and outer header
      return new BytesAndOuterHeader(ret, messageheader);
   }
   /**
    * Compare an inner and outer MessageHeader
    * @param the inner  MessageHeader
    * @param outer the outer MessageHeader
    * @return
    */
   public boolean validateInnerAndOuterHeader(JsonObject inner, JsonObject outer) {
      if (inner == null || outer == null) {
         return false;
      }
      String[] fields = {
            "DeviceID",
            "MessageCategory",
            "MessageClass",
            "MessageType",
            "SaleID",
            "ServiceID",
            "POIID",
            "ProtocolVersion",
      };
      for (String field : fields) {
         try {
            JsonString a = inner.getJsonString(field);
            JsonString b = outer.getJsonString(field);
            if (a == null && b == null) {
               continue;
            }
            if (a == null || !a.equals(b)) {
               return false;
            }
         }
         catch (ClassCastException ex) {
            return false;
         }
      }
      return true;
   }
}