3 votes

Cryptage de chaîne WebCrypto à l'aide d'un mot de passe soumis par l'utilisateur

En utilisant JavaScript et l'API WebCrypto (sans aucune bibliothèque externe), quelle est la meilleure façon de chiffrer une chaîne de caractères en utilisant une clé dérivée d'un mot de passe soumis par l'utilisateur ?

Voici un code dans lequel la clé n'est pas dérivée mais simplement générée par la fonction generatekey() fonction. Le but est de crypter la chaîne de caractères, puis de la décrypter pour vérifier que l'on récupère la chaîne originale :

var secretmessage = "";
var password = "";
var key_object = null; 
var promise_key = null;
var encrypted_data = null;
var encrypt_promise = null;
var vector = window.crypto.getRandomValues(new Uint8Array(16));
var decrypt_promise = null;
var decrypted_data = null;

function encryptThenDecrypt() {
    secretmessage = document.getElementById("secretmessageField").value; // some string to encrypt

    promise_key = window.crypto.subtle.generateKey(
        {
            name: "AES-GCM",
            length: 128
        },
        false,
        ["encrypt", "decrypt"]
    );
    promise_key.then(function(key) {
        key_object = key;
        encrypt_data();
    });
    promise_key.catch = function(e) {
        alert("Error while generating key: " + e.message);
    }
}

function encrypt_data() {
    encrypt_promise = window.crypto.subtle.encrypt({name: "AES-GCM", iv: vector}, key_object, convertStringToArrayBuffer(secretmessage));
    encrypt_promise.then(
        function(result) {
            encrypted_data = new Uint8Array(result);
            decrypt_data();
        }, 
        function(e) {
            alert("Error while encrypting data: " + e.message);
        }
    );
}

function decrypt_data() {
    decrypt_promise = window.crypto.subtle.decrypt({name: "AES-GCM", iv: vector}, key_object, encrypted_data);

    decrypt_promise.then(
        function(result){
            decrypted_data = new Uint8Array(result);
            alert("Decrypted data: " + convertArrayBuffertoString(decrypted_data));
        },
        function(e) {
            alert("Error while decrypting data: " + e.message);
        }
    );
}

function convertStringToArrayBuffer(str) {
    var encoder = new TextEncoder("utf-8");
    return encoder.encode(str);
}   
function convertArrayBuffertoString(buffer) {
    var decoder = new TextDecoder("utf-8");
    return decoder.decode(buffer);
}

Il fonctionne dans tous les navigateurs récents.

Maintenant, j'essaie de modifier le encryptThenDecrypt() afin de dériver la clé à partir d'un mot de passe soumis par l'utilisateur :

function encryptThenDecrypt() {
    secretmessage = document.getElementById("secretmessageField").value; // some string to encrypt
    password = document.getElementById("passwordField").value; // some user-chosen password

    promise_key = window.crypto.subtle.importKey(
        "raw",
        convertStringToArrayBuffer(password),
        {"name": "PBKDF2"},
        false,
        ["deriveKey"]
    );
    promise_key.then(function(importedPassword) {
        return window.crypto.subtle.deriveKey(
            {
                "name": "PBKDF2",
                "salt": convertStringToArrayBuffer("the salt is this random string"),
                "iterations": 500,
                "hash": "SHA-256"
            },
            importedPassword,
            {
                "name": "AES-GCM",
                "length": 128
            },
            false,
            ["encrypt", "decrypt"]
        );
    });
    promise_key.then(function(key) {
        key_object = key;
        encrypt_data();
    });
    promise_key.catch = function(e) {
        alert("Error while importing key: " + e.message);
    }
}

Il échoue. Les messages d'erreur sont :

  • Safari 11 : CryptoKey doesn't match AlgorithmIdentifier
  • Firefox 54 : A parameter or an operation is not supported by the underlying object
  • Chrome 61 : key.algorithm does not match that of operation

Il doit y avoir quelque chose de simple à réparer, mais je ne vois pas quoi. Toute aide sera grandement appréciée.

3voto

dsprenkels Points 826

Vous avez un petit bug dans votre code. Rien à voir avec la cryptographie, juste des promesses.

Les promesses ne mettent pas à jour leur état lorsque leur .then() est appelée, ils renvoient une nouvelle promesse à la place. Vous voyez que dans votre code, vous écartez la promesse résultant de l'appel à la fonction de dérivation de clé. Ensuite, lors du cryptage des données, vous réutilisez la promesse du mot de passe, pas la clé.

Vous devez soit enregistrer la promesse résultant de la dérivation de la clé dans une nouvelle variable :

let promise_derived_key = promise_key.then(function(importedPassword) {
    return window.crypto.subtle.deriveKey(
        // [...]
    );
});
promise_derived_key.then(function(key) {
    // [...]

ou enchaîner les appels à .then() :

promise_key = window.crypto.subtle.importKey(
    // [...]
).then(function(importedPassword) {
    return window.crypto.subtle.deriveKey(
        // [...]
    );
});
promise_key.then(function(key) {
    // [...]

[exemple de travail sur JSFiddle]

Au fait, vous voudrez utiliser beaucoup plus d'itérations PBKDF2 que 500. ( info )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X