node.js - Crypto.decipher.final for 'aes-256-cbc' algorithm with invalid key fails with bad decrypt
问题描述
I am able to use use node.js Crypto module to encrypt and decrypt a message using Cipher and Decipher classes with 'aes-256-cbc' algorithm like so:
var crypto = require('crypto');
var cipherKey = crypto.randomBytes(32); // aes-256 => key length is 256 bits => 32 bytes
var cipherIV = crypto.randomBytes(16); // aes block size = initialization vector size = 128 bits => 16 bytes
var cipher = crypto.createCipheriv('aes-256-cbc', cipherKey, cipherIV);
var message = 'Hello world';
var encrypted = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
console.log('Encrypted \'' + message + '\' as \'' + encrypted + '\' with key \''+ cipherKey.toString('hex') + '\' and IV \'' + cipherIV.toString('hex') + '\'');
// Outputs: Encrypted 'Hello world' as '2b8559ce4227c3c3c200ea126cb50957' with key '50f7a656cfa3c4f90796a972b2f6eedf41b589da705fdec95b9d25c180c16cf0' and IV '6b28c13d63af14cf05059a2a2caf370c'
var decipher = crypto.createDecipheriv('aes-256-cbc', cipherKey, cipherIV);
var decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
console.log('Decrypted \'' + encrypted + '\' as \'' + decrypted + '\' with key \''+ cipherKey.toString('hex') + '\' and IV \'' + cipherIV.toString('hex') + '\'');
// Outputs: Decrypted '2b8559ce4227c3c3c200ea126cb50957' as 'Hello world' with key '50f7a656cfa3c4f90796a972b2f6eedf41b589da705fdec95b9d25c180c16cf0' and IV '6b28c13d63af14cf05059a2a2caf370c'
However when I try to decrypt the message using a wrong key to, perhaps naively, demonstrate an attacker will not be able decrypt the message unless the key is known, I get Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt at Decipheriv.final (internal/crypto/cipher.js:164:28)
:
var differentCipherKey = crypto.randomBytes(32);
var decipherDifferentKey = crypto.createDecipheriv('aes-256-cbc', differentCipherKey, cipherIV);
decrypted = decipherDifferentKey.update(encrypted, 'hex', 'utf8') + decipherDifferentKey.final('utf8');
What was I was hoping to get is unintelligible text. bad decrypt
was featured in other SO questions either regarding openssl version mismatch between encrypting and decrypting or too-short initialization vector in the same case but I believe my case is a different scenario. Does AES somehow known that encrypted text was generated with a different key?
Tested on node v12.13.0 on Windows 10 and also in repl.it running v10.16.0.
EDIT: As suggested in the answers the issue was with default padding, in order to see unintelligible output one needs to disable auto-padding on both cipher and deciphers and pad manually:
var requirePadding = 16 - Buffer.byteLength(message, 'utf8');
var paddedMessage = Buffer.alloc(requirePadding, 0).toString('utf8') + message;
cipher.setAutoPadding(false)
解决方案
The CBC mode requires padding, you did not define one, but the library applied one for you as default. The default is PKCS7Padding which supports from 1 to up to 256 bytes of the block size.
Each padding has a specific format so that it can be uniquely removed from the decrypted text without ambiguity. For example, if the plaintext missing two characters to match the block size, 16-byte in AES, then the PKCS7 padding adds 0202
(in hex) indicating that 2 characters are added and each has value as the number of added characters. If 5 missing 0505050505
, etc. In the below xy
is a byte.
xyxyxyxyxyxyxyxyxyxyxyxyxyxyxy01
xyxyxyxyxyxyxyxyxyxyxyxyxyxy0202
xyxyxyxyxyxyxyxyxyxyxyxyxy030303
...
xyxy0E0E0E0E0E0E0E0E0E0E0E0E0E0E
xy0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F
and if the last block is a full block, a new block completely filled with padding
xyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxy 10101010101010101010101010101010
After the decryption, firstly the padding is checked. if the padding has not a correct format as specified in rfc 2315, then one can say that there is a padding error.
In this case, while decrypting the library checks the padding and warns you about this. To prevent the padding oracle attacks you don't get an incorrect padding warning. You get a bad decrypt.
The library knows that the key results with a valid padding or not, nothing more. There may be more than one key that results with valid padding even with a negligible probability where integrity is helpful.
In modern Cryptography, we don't use CBC mode anymore. We prefer Authenticated Encryption (AE) modes like AES-GCM or ChaCha20-Poly1305. AE modes provide confidentiality, integrity, and authentication in a bundle.
THE Galois Counter Mode (GCM), internally uses CTR mode in which there is no padding therefore they are free from padding oracle attacks.
推荐阅读
- python - Python:用于 NumPy 数组的 struct.pack 和 struct.unpack
- android - Android Studio 到 React Native
- php - 如何从发送给 WooCommerce 客户的电子邮件中删除发货国家/地区?
- python - 在python中绘制特征值
- wordpress - 如何在循环之前更改查询结果:wordpress
- leaflet - 如何在传单面板组中一次只激活一项?
- exception - 如何调试无法解释的异常 VS 2019 Windows Xamarin Forms?
- angularjs - 使用时刻 js 的日期验证不起作用
- css - 如果输入字段高于其余输入
- tensorflow - Keras 实验性 RandomFlip 和 RandomRotation 不适用于地图