Мартен покрывает большинство основных моментов, которые я хотел сделать, так что это всего лишь некоторая проработка и пример этого в Node.
Изменения в вашем коде:
- Кодировать в Base64, а не в Hex. Это намного экономичнее. Было бы даже лучше просто использовать Buffers и вообще не создавать строку; тогда мы могли бы иметь 9-байтовый одноразовый номер, а не 5-байтовый одноразовый номер.
- Избавиться от символа-разделителя между IV / nonce и зашифрованным текстом. Мы знаем, как долго IV / nonce; нам не нужен разделитель.
- Используйте режим CTR, а не режим CBC. Это делает длину вывода равной длине ввода.
- Используйте одноразовый номер, а не IV. Случайно выберите одноразовый номер из пространства 2 ^ 40. (Случайный выбор одноразового номера CTR в целом очень опасен. См. Ниже о том, почему может быть приемлемым в вашем случае использования; он по-прежнему никогда не рекомендуется.)
- Исправлена генерация пароля путем добавления PBKDF2 (вы также можете просто использовать randomBytes). Ваш пароль крайне ненадежен. Это строка ASCII, что означает, что она представляет крошечную часть пространства ключей AES-256. На порядок 0,000000000002% от пространства клавиш. Это намного менее безопасно, чем AES-256.
Как отмечает Мартен, в режиме CTR очень опасно дублировать пару Key + Nonce. Если кто-то делает это, он может узнать XOR двух оригинальных сообщений. При этом у них есть хороший шанс расшифровать оба сообщения. Например, если вы дублировали свой ключ + nonce на два сообщения, и злоумышленник использовал это, чтобы обнаружить, что их XOR равен 3, и знал, что зашифрованный текст был заглавной буквой, они знали бы, что эти два сообщения должны быть одним из них:
[('A', 'B'), ('D', 'G'), ('E', 'F'), ('H', 'K'), ('I', 'J'), ('L', 'O'),
('M', 'N'), ('P', 'S'), ('Q', 'R'), ('T', 'W'), ('U', 'V')]
Такая информация разрушительна для структурированных данных, таких как человеческий язык или компьютерные протоколы. Его можно очень быстро использовать для расшифровки всего сообщения. Использование ключа + nonce - это то, как WEP был сломан . (Когда вы делаете это вручную, это в основном идентично решению загадки криптограммы, которую вы найдете в газете.) Это менее мощно, чем более случайны зашифрованные данные, и тем меньше контекста они предоставляют.
При случайном 5-байтовом одноразовом числе вероятность столкновения составляет около 50% после 1,3 млн шифрования. При случайном 8-байтовом одноразовом коде вероятность коллизии составляет 50% после примерно 5,3 Б шифрования. sqrt (pi / 2 * 2 ^ бит)
В терминах криптографии это полностью нарушено. Это может или не может быть достаточно для ваших целей. Чтобы сделать это правильно (что я не делаю ниже), как отмечает Мартен, вы должны следить за своим счетчиком и увеличивать его для каждого шифрования, а не использовать случайное. После 2 ^ 40 шифрований (~ 1T) вы меняете свой ключ.
Предполагая, что утечка информации о двух сообщениях на миллион приемлема, именно так вы бы это реализовали.
const crypto = require('crypto');
const ENCRYPTION_KEY = 'Must256bytes(32characters)secret';
const SALT = 'somethingrandom';
const IV_LENGTH = 16;
const NONCE_LENGTH = 5; // Gives us 8-character Base64 output. The higher this number, the better
function encrypt(key, text) {
let nonce = crypto.randomBytes(NONCE_LENGTH);
let iv = Buffer.alloc(IV_LENGTH)
nonce.copy(iv)
let cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
let encrypted = cipher.update(text.toString());
message = Buffer.concat([nonce, encrypted, cipher.final()]);
return message.toString('base64')
}
function decrypt(key, text) {
let message = Buffer.from(text, 'base64')
let iv = Buffer.alloc(IV_LENGTH)
message.copy(iv, 0, 0, NONCE_LENGTH)
let encryptedText = message.slice(NONCE_LENGTH)
let decipher = crypto.createDecipheriv('aes-256-ctr', key, iv);
let decrypted = decipher.update(encryptedText);
try{
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}catch(Err){
return 'NULL';
}
}
// You could do this one time and record the result. Or you could just
// generate a random 32-byte key and record that. But you should never
// pass an ASCII string to the encryption function.
let key = crypto.pbkdf2Sync(ENCRYPTION_KEY, SALT, 10000, 32, 'sha512')
let encrypted = encrypt(key, "X")
console.log(encrypted + " : " + encrypted.length)
let decrypted = decrypt(key, encrypted)
console.log(decrypted)