Signer et déchiffrer les retours API avec votre clé secrète

Pour des raisons de sécurité et afin de garantir la confidentialité des échanges pour vos clients, toutes les requêtes API doivent être signés et les retours API d’AR24 sont chiffrés. Vous trouverez ci-dessous la marche à suivre pour signer vos requêtes ainsi que déchiffrer et exploiter les retours obtenus.

1. Faire un appel à l’API

Lors des appels à l’API (aussi bien les routes API que les liens obtenus dans les retour de l’API comme les preuves), il est nécessaire de nous transmettre le paramètre date. Ce paramètre doit contenir la date courante au format YYYY-MM-DD HH:MM:SS avec un delta compris entre le moment de l’appel à l’API et +10min. (Exemple lors du chargement de cette page : )
Veuillez notez qu’il faut donc être synchronisé avec notre serveur qui utilise le fuseau horaire d’Europe Centrale (UTC+1, et UTC+2 en heure d’été).

Il est important pour vous de sauvegarder cette donnée qui sera utilisée pour déchiffrer le retour API que vous obtiendrez. Ce paramètre permet de se prémunir d’une attaque par rejeu.

Vous devez ensuite chiffrer cette donnée et l’envoyer dans le header signature de votre requête HTTP :

Générer votre signature

L’idée est de chiffrer (en utilisant votre clé privée) la date transmise avec votre requête.
Vous serez amené à utiliser les fonctions de chiffrement standard (cipher AES256) avec :
– le contenu à chiffrer : la date
– la clé de chiffrement : un hash (sha256) de votre clé privée tronqué à 32
– le vecteur d’initialisation : un double hash (sha256) de votre clé privée sur 16 octets

Voici un exemple en PHP pour générer votre signature en utilisant le standard AES256 à l’aide de votre private_key (clé privée transmise avec votre token à la création de votre accès API) et le paramètre date renseigné lors de l’appel à l’API.

$date = '2021-05-26 14:00:00'; /* La date qui doit correspondre à la date exacte transmise lors de l'appel */
$private_key = '7X9gx9E3Qx4EiUdB63nc'; /* Votre clé privée obtenue lors de la création de votre accès à l'API */
$key = hash('sha256', $private_key); /* Création de la clé en concaténant la clé privée */
$iv = mb_strcut(hash('sha256', hash('sha256', $private_key)), 0, 16, 'UTF-8'); /* Création du vecteur d'initialisation (2x hash de la clé privée) en prenant 16 octets */
$key = substr($key, 0, 32); /* La clé doit avoir exactement la même longueur que le cipher utilisé (dans le cas de AES256 ce sera 32 bytes) */
echo(openssl_encrypt($date, 'aes-256-cbc', $key, false, $iv)); /* Affichage du retour en utilisant openssl_encrypt */

Vous devriez alors obtenir le résultat suivant : bDop0cbjKpkySlpvnNGvBMg7PuYFFgPPqTTS2RAHoY0=. Il vous suffit maintenant de transmettre cette donnée dans les headers, dans le champ signature.

Cette signature doit être transmise sur l’ensemble de vos requêtes réalisées à distance de l’API et des ressources telles que les téléchargements des preuves. Cela nous permet de nous assurer de l’origine des requêtes.

2. Déchiffrer un retour API

Le retour API est chiffré en utilisant le standard AES256 avec l’utilisation de votre private_key (clé privée transmise avec votre token à la création de votre accès API) et le paramètre date renseigné lors de l’appel à l’API.

Voici un exemple en PHP pour déchiffrer le retour chiffré WwBOU6s8DaMWmYdctBJwfuoujFgVygBUjhsbdf8eWqQ= :

$output = 'WwBOU6s8DaMWmYdctBJwfuoujFgVygBUjhsbdf8eWqQ='; /* Le retour chiffré que nous allons déchiffrer */
$date = '2021-05-26 14:00:00'; /* La date qui doit correspondre à la date exacte transmise lors de l'appel */
$private_key = '7X9gx9E3Qx4EiUdB63nc'; /* Votre clé privée obtenue lors de la création de votre accès à l'API */
$key = hash('sha256', $date.$private_key); /* Création de la clé en concaténant la date et la clé privée */
$iv = mb_strcut(hash('sha256', hash('sha256', $private_key)), 0, 16, 'UTF-8'); /* Création du vecteur d'initialisation (2x hash de la clé privée) en prenant 16 octets */
echo(openssl_decrypt($output, 'aes-256-cbc', $key, false, $iv)); /* Affichage du retour en utilisant openssl_decrypt */

Le JSON {status: "SUCCESS"} s’affiche.

Attention à ne pas confondre votre token qui correspond à votre clé d’accès à l’API et votre clé privée qui vous sert à déchiffrer les retours API.

Les valeurs key et iv sont, dans certains langages (JAVA par exemple), la représentation hexa du hash obtenus (la fonction hash en PHP possède un argument « binary » à false par défaut dans notre exemple de code).

Dans l’exemple PHP, l’iv passé en paramètre de la fonction openssl_decrypt est automatiquement tronqué à 16 octets par la fonction. La valeur de key passé en paramètre est également automatiquement tronquée à 32 octets.
Si votre langage n’effectue pas ces transformations automatiquement, vous devrez tronquer l’iv et la key avant de les utiliser.

Le mode utilisé pour les fonctions openSSL est CBC (aes-256-cbc, la valeur par défaut en PHP dans l’exemple).
Enfin le padding utilisé est PKCS#7 (valeur par défaut en PHP mais pas forcément dans d’autres langages).

3. Utiliser Postman avec le chiffrement

Postman est un outil qui sert à exécuter des appels HTTP directement depuis une interface graphique. Il est généralement utilisé pour manipuler et tester les différentes routes d’une API dans le but de comprendre comment celle-ci fonctionne, avant de se lancer dans la phase de développement.

Le chiffrement de nos retours est susceptible de complexifier la découverte de notre API, c’est pour cela que nous vous avons préparé une configuration de Postman vous permettant de déchiffrer automatiquement les retours.

Comment pré-remplir automatiquement le paramètre date attendu lors de chaque appel ?

Comme évoqué précédemment, pour permettre le chiffrement vous devez systématiquement transmettre un champ date correspondant à la date courante (Heure française : UTC+1 en hiver et UTC+2 en été).

Vous trouverez ci-dessous un script qui sera exécuté automatiquement avant l’envoi d’une requête et qui pré-remplira une variable globale {{currentdate}} ainsi que {{signature}}.

var moment = require('moment');

var private_key = '';
if(private_key === '') {
    private_key = pm.globals.get('private_key');
}

var date = moment().format(("YYYY-MM-DD HH:mm:ss"));
pm.environment.set('currentdate', date);

if(private_key !== undefined) {
    var key = CryptoJS.SHA256(private_key).toString().substring(0, 32);
    key = CryptoJS.enc.Hex.parse(key);

    var iv = CryptoJS.SHA256(CryptoJS.SHA256(private_key.toString(CryptoJS.enc.Hex)).toString(CryptoJS.enc.Hex)).toString(CryptoJS.enc.Hex).substring(0, 16);
    iv = CryptoJS.enc.Hex.parse(iv);
    
    var signature_encrypted = CryptoJS.AES.encrypt(
        date,
        CryptoJS.enc.Utf8.parse(key),
        {
            iv: CryptoJS.enc.Utf8.parse(iv),
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }
    );
    pm.environment.set('signature', signature_encrypted.toString());
}

Ce script est a renseigner dans la partie « Pre-request Script », cependant nous vous recommandons de l’éditer directement dans la collection Postman pour que celui-ci soit actif sur l’ensemble de vos routes (Clique droit / trois points verticaux, « Edit », puis l’onglet « Pre-request Scripts »).
En savoir plus sur les Pre-request scripts chez Postman

Comment déchiffrer automatiquement en console ?

Pour déchiffrer nos retours, vous aurez besoin de renseigner votre clé privée directement dans le code, ou dans une variable globale {{private_key}}.

Vous trouverez ci-dessous un script qui permet d’automatiquement déchiffrer le résultat de vos requêtes puis de l’afficher dans l’onglet « Test Results » de la requête et dans la console Postman (« View », « Show Postman Console » / Alt+Ctrl+C).

/* Private_key */
var private_key = '';

/* Take the private_key from the variables if not defined above */
if(private_key === '') {
    private_key = pm.globals.get('private_key');
}

/* Decrypts the result if the private key is set */
if(private_key !== undefined) {
    /* Get encrypted result */
    var encrypted_result = responseBody;
    //console.log('encrypted result : '+encrypted_result);

    /* Get date sent */
    if(pm.request.method == 'GET') {
        var date = decodeURI(pm.request.url.query.toObject().date);
    }
    else {
    var date = pm.request.body.formdata.get("date");
    }
    //console.log('date sent : '+date);

    /* Key (Passphrase) */
    var key = CryptoJS.SHA256(date + private_key).toString().substring(0, 32);
    //console.log('key : '+key);
    key = CryptoJS.enc.Hex.parse(key);

    /* IV (Initialization Vector) */
    var iv = CryptoJS.SHA256(CryptoJS.SHA256(private_key.toString(CryptoJS.enc.Hex)).toString(CryptoJS.enc.Hex)).toString(CryptoJS.enc.Hex).substring(0, 16);
    //console.log('iv : '+iv);
    iv = CryptoJS.enc.Hex.parse(iv);

    /* Decrypt */
    var decrypted = CryptoJS.AES.decrypt(
        encrypted_result ,CryptoJS.enc.Utf8.parse(key),
        {
            iv: CryptoJS.enc.Utf8.parse(iv),
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }
    );

    /* Print result */
    if(decrypted.toString().length > 0) {
        try {
            var res = JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));
            console.log('✔️ Decrypted result :');
            console.log(res);
 
            pm.test("✔️ Decrypted result available in Postman Console (Alt+Ctrl+C) and below", function(){
                pm.expect(1).to.eql(1);
            });
            pm.test(decrypted.toString(CryptoJS.enc.Utf8), function(){
                pm.expect(1).to.eql(1);
            });
        } catch (error) {
            console.log('⚠️ Decryption : failed');

            pm.test("⚠️ Error while decrypting result", function(){
                pm.expect(1).to.eql(1);
            });
        }
    }
    else {
            console.log('⚠️ Decryption : failed');
            
            pm.test("⚠️ Error while decrypting result", function(){
                pm.expect(1).to.eql(1);
            });
    }
}

Ce script est a renseigner dans la partie « Test », cependant nous vous recommandons de l’éditer directement dans la collection Postman pour que celui-ci soit actif sur l’ensemble de vos routes. Pour ce faire, Dans la partie « Collections » survolez le titre de notre collection Postman pour voir apparaitre le symbole d’édition (trois points horizontaux), cliquez dessus et dans le menu qui apparaît, cliquez sur « Edit ». Rendez-vous dans l’onglet « Tests » pour ajouter le code ci-dessus.
En savoir plus sur les Test scripts chez Postman