nodejs 加密 createDecipheriv 抛出 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH'
nodejs crypto createDecipheriv throws 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH'
我正在试验 mp3 文件的加密和解密。我有一个 python 代码执行 AES 加密并尝试使用 node.js 的加密库解密加密输出。我的 python 代码是:
from Crypto.Cipher import AES
import hashlib
# code from https://eli.thegreenplace.net/2010/06/25/
# aes-encryption-of-files-in-python-with-pycrypto
def encrypt_file(key, in_filename, out_filename=None, chunksize=64*1024):
if not out_filename:
out_filename = in_filename + '.enc'
#iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
iv = os.urandom(16)
encryptor = AES.new(key, AES.MODE_CBC, iv)
filesize = os.path.getsize(in_filename)
with open(in_filename, 'rb') as infile:
with open(out_filename, 'wb') as outfile:
outfile.write(struct.pack('<Q', filesize))
outfile.write(iv)
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += (' ' * (16 - len(chunk) % 16)).encode('ascii')
outfile.write(encryptor.encrypt(chunk))
if __name__ == '__main__':
password = 'helloWorld!'
enc_key = hashlib.sha256(password.encode(encoding='utf-8',errors='strict')).digest()
print(base64.b64encode(enc_key).decode('ascii'))
audio_file_name = "GetachewMekuryaSaxphone_IBSA8Sz.mp3"
enc_output = audio_file_name + ".enc"
encrypt_file(enc_key, audio_file_name, enc_output)
和一个javascript解密代码:
var fs = require('fs');
var crypto = require('crypto');
var password = 'helloWorld!'
const enc_key = crypto.createHash('sha256').update(String(password)).digest('base64').substr(0, 32);
console.log(enc_key);
let iv = crypto.randomBytes(16);
var cipher = crypto.createCipheriv('aes-256-cbc', enc_key, iv);
var decipher = crypto.createDecipheriv('aes-256-cbc',enc_key, iv);
var input = fs.createReadStream('GetachewMekuryaSaxphone_IBSA8Sz.mp3.enc');
var output = fs.createWriteStream('GetachewMekuryaSaxphone_IBSA8Sz_DEC.mp3');
input.pipe(decipher).pipe(output);
output.on('finish', function() {
console.log('Encrypted file written to disk!');
});
但是,我在尝试解密时收到错误消息:
events.js:292
throw er; // Unhandled 'error' event
^
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
at Decipheriv._flush (internal/crypto/cipher.js:141:29)
at Decipheriv.prefinish (_stream_transform.js:142:10)
at Decipheriv.emit (events.js:315:20)
at prefinish (_stream_writable.js:619:14)
at finishMaybe (_stream_writable.js:627:5)
at Decipheriv.Writable.end (_stream_writable.js:571:5)
at ReadStream.onend (_stream_readable.js:676:10)
at Object.onceWrapper (events.js:421:28)
at ReadStream.emit (events.js:327:22)
at endReadableNT (_stream_readable.js:1223:12)
Emitted 'error' event on Decipheriv instance at:
at emitErrorNT (internal/streams/destroy.js:100:8)
at emitErrorCloseNT (internal/streams/destroy.js:68:3)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
library: 'digital envelope routines',
function: 'EVP_DecryptFinal_ex',
reason: 'wrong final block length',
code: 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH'
}
在Python代码中,在写入的文件中,首先存储明文文件的大小(8字节,小端),然后是16字节的IV,最后是密文。 NodeJS 代码中根本没有考虑这种结构。相反,随机 IV 用于解密。实际上,前 8 个字节和 IV 应该从 NodeJS 代码中的加密文件中读取,其余(即密文)应该使用提取的 IV 解密。
此外,密钥不得使用 Base64 编码。
此外,两种代码中使用了不同的填充。在 Python 代码中,应用了带有空格的不可靠填充,在 NodeJS 代码中 PKCS7 填充。
最合理的更改是在 Python 代码中切换到 PKCS7 填充。为此,PyCryptodome 提供了 padding-module。优点是在解密过程中会自动删除填充。然后不需要存储的明文文件大小。
或者,可以在 NodeJS 代码中禁用填充,并可以在最后使用纯文本文件大小的附加步骤中将其删除。
以下 NodeJS 代码实现了后一种方法:
var fs = require('fs');
var crypto = require('crypto');
var password = 'helloWorld!'
var pathEncryptedFile = '<path to .mp3.enc input file>';
var pathDecryptedFile = '<path to .dec.mp3 output file>';
// Derive key
var enc_key = crypto.createHash('sha256').update(password).digest();
// Read IV and size
// Remeber: Encrypted file structure: plain file length (8 bytes) | iv (16 bytes) | ciphertext
var fd = fs.openSync(pathEncryptedFile, 'r');
var size = Buffer.alloc(8);
fs.readSync(fd, size, 0, 8, 0) // Read plaintext file size
var size = size.readUIntLE(0, 6)
var iv = Buffer.alloc(16);
fs.readSync(fd, iv, 0, 16, 8) // Read iv
fs.closeSync(fd)
// Decrypt (ignore the first 8 + 16 bytes)
var input = fs.createReadStream(pathEncryptedFile, { start: 24 });
var output = fs.createWriteStream(pathDecryptedFile);
var decipher = crypto.createDecipheriv('aes-256-cbc', enc_key, iv);
decipher.setAutoPadding(false); // Disable unpadding
input.pipe(decipher).pipe(output);
output.on('finish', function () {
// Unpad manually using the plaintext file size
var fd = fs.openSync(pathDecryptedFile, 'r+');
fs.ftruncateSync(fd, size);
fs.closeSync(fd)
console.log('Encrypted file written to disk!');
});
我正在试验 mp3 文件的加密和解密。我有一个 python 代码执行 AES 加密并尝试使用 node.js 的加密库解密加密输出。我的 python 代码是:
from Crypto.Cipher import AES
import hashlib
# code from https://eli.thegreenplace.net/2010/06/25/
# aes-encryption-of-files-in-python-with-pycrypto
def encrypt_file(key, in_filename, out_filename=None, chunksize=64*1024):
if not out_filename:
out_filename = in_filename + '.enc'
#iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
iv = os.urandom(16)
encryptor = AES.new(key, AES.MODE_CBC, iv)
filesize = os.path.getsize(in_filename)
with open(in_filename, 'rb') as infile:
with open(out_filename, 'wb') as outfile:
outfile.write(struct.pack('<Q', filesize))
outfile.write(iv)
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += (' ' * (16 - len(chunk) % 16)).encode('ascii')
outfile.write(encryptor.encrypt(chunk))
if __name__ == '__main__':
password = 'helloWorld!'
enc_key = hashlib.sha256(password.encode(encoding='utf-8',errors='strict')).digest()
print(base64.b64encode(enc_key).decode('ascii'))
audio_file_name = "GetachewMekuryaSaxphone_IBSA8Sz.mp3"
enc_output = audio_file_name + ".enc"
encrypt_file(enc_key, audio_file_name, enc_output)
和一个javascript解密代码:
var fs = require('fs');
var crypto = require('crypto');
var password = 'helloWorld!'
const enc_key = crypto.createHash('sha256').update(String(password)).digest('base64').substr(0, 32);
console.log(enc_key);
let iv = crypto.randomBytes(16);
var cipher = crypto.createCipheriv('aes-256-cbc', enc_key, iv);
var decipher = crypto.createDecipheriv('aes-256-cbc',enc_key, iv);
var input = fs.createReadStream('GetachewMekuryaSaxphone_IBSA8Sz.mp3.enc');
var output = fs.createWriteStream('GetachewMekuryaSaxphone_IBSA8Sz_DEC.mp3');
input.pipe(decipher).pipe(output);
output.on('finish', function() {
console.log('Encrypted file written to disk!');
});
但是,我在尝试解密时收到错误消息:
events.js:292
throw er; // Unhandled 'error' event
^
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
at Decipheriv._flush (internal/crypto/cipher.js:141:29)
at Decipheriv.prefinish (_stream_transform.js:142:10)
at Decipheriv.emit (events.js:315:20)
at prefinish (_stream_writable.js:619:14)
at finishMaybe (_stream_writable.js:627:5)
at Decipheriv.Writable.end (_stream_writable.js:571:5)
at ReadStream.onend (_stream_readable.js:676:10)
at Object.onceWrapper (events.js:421:28)
at ReadStream.emit (events.js:327:22)
at endReadableNT (_stream_readable.js:1223:12)
Emitted 'error' event on Decipheriv instance at:
at emitErrorNT (internal/streams/destroy.js:100:8)
at emitErrorCloseNT (internal/streams/destroy.js:68:3)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
library: 'digital envelope routines',
function: 'EVP_DecryptFinal_ex',
reason: 'wrong final block length',
code: 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH'
}
在Python代码中,在写入的文件中,首先存储明文文件的大小(8字节,小端),然后是16字节的IV,最后是密文。 NodeJS 代码中根本没有考虑这种结构。相反,随机 IV 用于解密。实际上,前 8 个字节和 IV 应该从 NodeJS 代码中的加密文件中读取,其余(即密文)应该使用提取的 IV 解密。
此外,密钥不得使用 Base64 编码。
此外,两种代码中使用了不同的填充。在 Python 代码中,应用了带有空格的不可靠填充,在 NodeJS 代码中 PKCS7 填充。
最合理的更改是在 Python 代码中切换到 PKCS7 填充。为此,PyCryptodome 提供了 padding-module。优点是在解密过程中会自动删除填充。然后不需要存储的明文文件大小。
或者,可以在 NodeJS 代码中禁用填充,并可以在最后使用纯文本文件大小的附加步骤中将其删除。
以下 NodeJS 代码实现了后一种方法:
var fs = require('fs');
var crypto = require('crypto');
var password = 'helloWorld!'
var pathEncryptedFile = '<path to .mp3.enc input file>';
var pathDecryptedFile = '<path to .dec.mp3 output file>';
// Derive key
var enc_key = crypto.createHash('sha256').update(password).digest();
// Read IV and size
// Remeber: Encrypted file structure: plain file length (8 bytes) | iv (16 bytes) | ciphertext
var fd = fs.openSync(pathEncryptedFile, 'r');
var size = Buffer.alloc(8);
fs.readSync(fd, size, 0, 8, 0) // Read plaintext file size
var size = size.readUIntLE(0, 6)
var iv = Buffer.alloc(16);
fs.readSync(fd, iv, 0, 16, 8) // Read iv
fs.closeSync(fd)
// Decrypt (ignore the first 8 + 16 bytes)
var input = fs.createReadStream(pathEncryptedFile, { start: 24 });
var output = fs.createWriteStream(pathDecryptedFile);
var decipher = crypto.createDecipheriv('aes-256-cbc', enc_key, iv);
decipher.setAutoPadding(false); // Disable unpadding
input.pipe(decipher).pipe(output);
output.on('finish', function () {
// Unpad manually using the plaintext file size
var fd = fs.openSync(pathDecryptedFile, 'r+');
fs.ftruncateSync(fd, size);
fs.closeSync(fd)
console.log('Encrypted file written to disk!');
});