Подтвердить что ты не робот

Node.js: шифрование данных, которые необходимо расшифровать?

Мы используем bcrypt для паролей и данных, которые никогда не нуждаются в расшифровке.

Что делать, чтобы защитить другую информацию пользователя, которая делает. В этом примере можно сказать, что мы не хотели, чтобы настоящее имя пользователя было в обычном тексте, если кто-то должен был получить db.

Это несколько конфиденциальные данные, но также их нужно время от времени вызывать и отображать в виде обычного текста. Есть ли простой способ сделать это?

4b9b3361

Ответ 1

Вы можете использовать модуль 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);

Ответ 2

Обновление от 30 июля 2019 года

Поскольку ответом становится все больше просмотров и голосов, я думаю, стоит упомянуть, что в приведенном ниже коде использовался метод * Sync - crypto.scryptSync. Теперь это нормально, если шифрование или дешифрование выполняется во время инициализации приложения. В противном случае рассмотрите возможность использования асинхронной версии функции, чтобы избежать блокировки цикла событий. (Библиотека обещаний, такая как bluebird, полезна).

Обновление от 23 января 2019 года

Ошибка в логике расшифровки была исправлена. Спасибо @AlexisWilke за правильное указание на это.


Принятый ответ - 7 лет, и сегодня он не выглядит защищенным. Следовательно, я отвечаю на это:

  1. Алгоритм шифрования: блочный шифр AES с 256-битным ключом считается достаточно безопасным. Чтобы зашифровать полное сообщение, необходимо выбрать режим. Рекомендуется использовать аутентифицированное шифрование (которое обеспечивает как конфиденциальность, так и целостность). GCM, CCM и EAX являются наиболее часто используемыми аутентифицированными режимами шифрования. GCM обычно предпочтительнее, и он хорошо работает в архитектурах Intel, которые предоставляют специальные инструкции для GCM. Все эти три режима являются режимами на основе CTR (счетчика) и, следовательно, они не требуют заполнения. В результате они не подвержены атакам, связанным с отступами

  2. Вектор инициализации (IV) требуется для GCM. IV не секрет. Единственное требование - быть случайным или непредсказуемым. В NodeJs crypto.randomBytes() предназначен для создания криптографически сильных псевдослучайных чисел.

  3. NIST рекомендует 96-битный IV для GCM для обеспечения функциональной совместимости, эффективности и простоты конструкции

  4. Получатель должен знать IV, чтобы иметь возможность расшифровать зашифрованный текст. Поэтому IV необходимо передать вместе с зашифрованным текстом. Некоторые реализации отправляют IV как AD (связанные данные), что означает, что тег аутентификации будет рассчитываться как для текста шифра, так и для IV. Однако это не обязательно. IV можно просто предварительно добавить к зашифрованному тексту, потому что, если IV изменяется во время передачи из-за преднамеренной атаки или ошибки сети/файловой системы, проверка тега аутентификации все равно не удастся

  5. Строки не должны использоваться для хранения открытого текстового сообщения, пароля или ключа, поскольку строки являются неизменяемыми, что означает, что мы не можем очистить строки после использования, и они будут задерживаться в памяти. Таким образом, дамп памяти может раскрыть конфиденциальную информацию. По той же причине клиент, вызывающий эти методы шифрования или дешифрования, должен очистить все Buffer, содержащие сообщение, ключ или пароль, после того как они больше не нужны, используя bufferVal.fill(0).

  6. Наконец, для передачи по сети или хранилищу зашифрованный текст должен быть закодирован с использованием кодировки Base64. buffer.toString('base64'); можно использовать для преобразования Buffer в строку в кодировке Base64.

  7. Обратите внимание, что сценарий получения ключа (crypto.scryptSync()) использовался для получения ключа из пароля. Однако эта функция доступна только в Узле 10. * и более поздних версиях

Код идет здесь:

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;
}

И модульные тесты также представлены ниже:

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);
    });
  });
});