web-dev-qa-db-fra.com

node.js: crypter des données qui doivent être décryptées?

Nous utilisons bcrypt pour les mots de passe et les données qui n'ont jamais besoin d'être décryptés.

Que doit faire pour protéger les autres informations des utilisateurs qui le font. Pour cet exemple, disons que nous ne voulions pas qu'un vrai nom d'utilisateur soit en texte brut au cas où quelqu'un obtiendrait la base de données.

Il s'agit de données quelque peu sensibles mais qui doivent également être appelées de temps à autre et affichées en texte brut. Existe-t-il un moyen simple de procéder?

49
fancy

Vous pouvez utiliser le module crypto :

var crypto = require('crypto');
var assert = require('assert');

var algorithm = 'aes256'; // or any other algorithm supported by OpenSSL
var key = 'password';
var text = 'I love kittens';

var cipher = crypto.createCipher(algorithm, key);  
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
var decipher = crypto.createDecipher(algorithm, key);
var decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');

assert.equal(decrypted, text);
115
mak

Mise à jour le 30-JUL-2019

Comme la réponse obtient plus de vues et de votes, je pense qu'il convient de mentionner que le code ci-dessous a utilisé une méthode * Sync - crypto.scryptSync. Maintenant, c'est bien si le chiffrement ou le déchiffrement est effectué pendant l'initialisation de l'application. Sinon, envisagez d'utiliser la version asynchrone de la fonction pour éviter de bloquer la boucle d'événements. (Une bibliothèque de promesses comme bluebird est utile).

Mise à jour le 23-JAN-2019

Le bug dans la logique de déchiffrement a été corrigé. Merci @AlexisWilke de l'avoir signalé à juste titre.


La réponse acceptée a 7 ans et ne semble pas sécurisée aujourd'hui. Par conséquent, je réponds:

  1. algorithme de chiffrement: le chiffrement par bloc AES avec une clé de 256 bits est considéré comme suffisamment sécurisé. Pour crypter un message complet, un mode doit être sélectionné. Un chiffrement authentifié (qui assure à la fois la confidentialité et l'intégrité) est recommandé. GCM, CCM et EAX sont les modes de chiffrement authentifiés les plus couramment utilisés. GCM est généralement préféré et il fonctionne bien dans les architectures Intel qui fournissent des instructions dédiées à GCM. Ces trois modes sont tous des modes basés sur CTR (basés sur des compteurs) et n'ont donc pas besoin de remplissage. En conséquence, ils ne sont pas vulnérables aux attaques liées au rembourrage

  2. Un vecteur d'initialisation (IV) est requis pour GCM. Le IV n'est pas un secret. La seule exigence étant qu'elle doit être aléatoire ou imprévisible. Dans NodeJs, crypto.randomBytes() est destiné à produire des nombres pseudo aléatoires cryptographiquement forts.

  3. Le NIST recommande 96 bits IV pour GCM afin de promouvoir l'interopérabilité, l'efficacité et la simplicité de conception

  4. Le destinataire doit connaître l'IV pour pouvoir déchiffrer le texte chiffré. Par conséquent, l'IV doit être transféré avec le texte chiffré. Certaines implémentations envoient l'IV en tant que AD (données associées), ce qui signifie que la balise d'authentification sera calculée à la fois sur le texte chiffré et sur l'IV. Cependant, cela n'est pas obligatoire. Le IV peut être simplement pré-suspendu avec le texte de chiffrement car si le IV est changé pendant la transmission en raison d'une attaque délibérée ou d'une erreur de réseau/système de fichiers, la validation de la balise d'authentification échouera de toute façon

  5. Les chaînes ne doivent pas être utilisées pour contenir le message en texte clair, le mot de passe ou la clé car les chaînes sont immuables, ce qui signifie que nous ne pouvons pas effacer les chaînes après utilisation et qu'elles resteront dans la mémoire. Ainsi, un vidage de mémoire peut révéler les informations sensibles. Pour la même raison, le client appelant ces méthodes de chiffrement ou de déchiffrement doit effacer tous les Buffer contenant le message, la clé ou le mot de passe une fois qu'ils ne sont plus nécessaires à l'aide de bufferVal.fill(0).

  6. Enfin, pour la transmission sur le réseau ou le stockage, le texte chiffré doit être codé à l'aide du codage Base64. buffer.toString('base64'); peut être utilisé pour convertir le Buffer en chaîne encodée en Base64.

  7. Notez que la dérivation de clé scrypt (crypto.scryptSync()) a été utilisée pour dériver une clé d'un mot de passe. Cependant, cette fonction est disponible uniquement dans Node 10. * et versions ultérieures

Le code va ici:

const crypto = require('crypto');

var exports = module.exports = {};

const ALGORITHM = {

    /**
     * GCM is an authenticated encryption mode that
     * not only provides confidentiality but also 
     * provides integrity in a secured way
     * */  
    BLOCK_CIPHER: 'aes-256-gcm',

    /**
     * 128 bit auth tag is recommended for GCM
     */
    AUTH_TAG_BYTE_LEN: 16,

    /**
     * NIST recommends 96 bits or 12 bytes IV for GCM
     * to promote interoperability, efficiency, and
     * simplicity of design
     */
    IV_BYTE_LEN: 12,

    /**
     * Note: 256 (in algorithm name) is key size. 
     * Block size for AES is always 128
     */
    KEY_BYTE_LEN: 32,

    /**
     * To prevent Rainbow table attacks
     * */
    SALT_BYTE_LEN: 16
}

const getIV = () => crypto.randomBytes(ALGORITHM.IV_BYTE_LEN);
exports.getRandomKey = getRandomKey = () => crypto.randomBytes(ALGORITHM.KEY_BYTE_LEN);

/**
 * To prevent Rainbow table attacks
 * */
exports.getSalt = getSalt = () => crypto.randomBytes(ALGORITHM.SALT_BYTE_LEN);

/**
 * 
 * @param {Buffer} password - The password to be used for generating key
 * 
 * To be used when key needs to be generated based on password.
 * The caller of this function has the responsibility to clear 
 * the Buffer after the key generation to prevent the password 
 * from lingering in the memory
 */
exports.getKeyFromPassword = getKeyFromPassword = (password, salt) => {
    return crypto.scryptSync(password, salt, ALGORITHM.KEY_BYTE_LEN);
}

/**
 * 
 * @param {Buffer} messagetext - The clear text message to be encrypted
 * @param {Buffer} key - The key to be used for encryption
 * 
 * The caller of this function has the responsibility to clear 
 * the Buffer after the encryption to prevent the message text 
 * and the key from lingering in the memory
 */
exports.encrypt = encrypt = (messagetext, key) => {
    const iv = getIV();
    const cipher = crypto.createCipheriv(
        ALGORITHM.BLOCK_CIPHER, key, iv, { 'authTagLength': ALGORITHM.AUTH_TAG_BYTE_LEN });
    let encryptedMessage = cipher.update(messagetext);
    encryptedMessage = Buffer.concat([encryptedMessage, cipher.final()]);
    return Buffer.concat([iv, encryptedMessage, cipher.getAuthTag()]);
}

/**
 * 
 * @param {Buffer} ciphertext - Cipher text
 * @param {Buffer} key - The key to be used for decryption
 * 
 * The caller of this function has the responsibility to clear 
 * the Buffer after the decryption to prevent the message text 
 * and the key from lingering in the memory
 */
exports.decrypt = decrypt = (ciphertext, key) => {
    const authTag = ciphertext.slice(-16);
    const iv = ciphertext.slice(0, 12);
    const encryptedMessage = ciphertext.slice(12, -16);
    const decipher = crypto.createDecipheriv(
        ALGORITHM.BLOCK_CIPHER, key, iv, { 'authTagLength': ALGORITHM.AUTH_TAG_BYTE_LEN });
    decipher.setAuthTag(authTag);
    let messagetext = decipher.update(encryptedMessage);
    messagetext = Buffer.concat([messagetext, decipher.final()]);
    return messagetext;
}

Et les tests unitaires sont également fournis ci-dessous:

const assert = require('assert');
const cryptoUtils = require('../lib/crypto_utils');
describe('CryptoUtils', function() {
  describe('decrypt()', function() {
    it('should return the same mesage text after decryption of text encrypted with a randomly generated key', function() {
      let plaintext = 'my message text';
      let key = cryptoUtils.getRandomKey();
      let ciphertext = cryptoUtils.encrypt(plaintext, key);

      let decryptOutput = cryptoUtils.decrypt(ciphertext, key);

      assert.equal(decryptOutput.toString('utf8'), plaintext);
    });

    it('should return the same mesage text after decryption of text excrypted with a key generated from a password', function() {
      let plaintext = 'my message text';
      /**
       * Ideally the password would be read from a file and will be in a Buffer
       */
      let key = cryptoUtils.getKeyFromPassword(Buffer.from('mysecretpassword'), cryptoUtils.getSalt());
      let ciphertext = cryptoUtils.encrypt(plaintext, key);

      let decryptOutput = cryptoUtils.decrypt(ciphertext, key);

      assert.equal(decryptOutput.toString('utf8'), plaintext);
    });
  });
});
13
Saptarshi Basu