nodejs crypto createDecipheriv throws 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH' - node.js

I'm experimenting with encryption and decryption of mp3 files. I have a python code doing an AES encryption and trying to decrypt the encrypted output with crypto library of node.js. My python code is:
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)
and a javascript code for decryption:
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!');
});
However, I am getting an error while attempting to decrypt with the following message:
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'
}

In the Python code, in the written file, first the size of the plaintext file is stored (on 8 bytes, with little endian), then the 16 bytes IV and finally the ciphertext. This structure is not considered at all in the NodeJS code. Instead, a random IV is used for decryption. Actually, the first 8 bytes and the IV should be read from the encrypted file in the NodeJS code and the rest (i.e. the ciphertext) should be decrypted using the extracted IV.
Additionally, the key must not be Base64 encoded.
Also, different paddings are used in both codes. In the Python code, an unreliable padding with blanks is applied, in the NodeJS code PKCS7 padding.
The most reasonable change would be to switch to PKCS7 padding in the Python code. For this, PyCryptodome provides a padding-module. The advantage is that the padding is automatically removed during decryption. The stored plaintext file size is then not needed.
Alternatively, the padding can be disabled in the NodeJS code and can be removed in an additional step at the end using the plaintext file size.
The following NodeJS code implements the latter approach:
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!');
});

Related

AES encryption error: The input data is not a complete block

I want to encrypt username and password using AES and Python. I want to encrypt username and password with the following details :
key = "qwertyui89765432";
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Mode = CipherMode.ECB; // Noncompliant
aes.Padding = PaddingMode.PKCS7;
code :
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.Padding import unpad
key = 'qwertyui897654321'
username = "Welcome!#34"
cipher = AES.new(key.encode('utf8'), AES.MODE_ECB)
msg =cipher.encrypt(pad(username.encode('utf8'), 16))
print(msg.hex())
I used the above code encrypting username and password and I used the encrypted username and password in the Postman for getting the response.At that time, I got an error like below:
{
"Status": "FAILED",
"EmpCode": "",
"Remarks": "The input data is not a complete block."
}
Is there any mismatch in the encryption code with respect to the above given details?
Can anyone suggest a solution to solve this issue.

AEAD AES-256-GCM in Node.js

How to decrypt data encrypted by other languages with Node.js?
To describe the problem, we write some code in Python:
plain_text = 'some data'
nonce = 'some nonce string'
associated_data = 'some associated data'
key = '--- 32 bytes secret key here ---'
encrypting in python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
aesgcm = AESGCM(key.encode())
encrypted = aesgcm.encrypt(nonce.encode(), plain_text.encode(), associated_data.encode())
print(aesgcm.decrypt(nonce.encode(), encrypted, associated_data.encode()))
ciphertext = base64.b64encode(encrypted)
Decrypting in Node.js, we don't need additional dependencies.
The crypto module has implemented GCM algorithm, but it is different in concept.
const crypto = require('crypto')
encrypted = Buffer.from(ciphertext, 'base64')
let decipher = crypto.createDecipheriv('AES-256-GCM', key, nonce)
decipher.setAuthTag(encrypted.slice(-16))
decipher.setAAD(Buffer.from(associated_data))
let output = Buffer.concat([
decipher.update(encrypted.slice(0, -16)),
decipher.final()
])
console.log(output.toString())

How can we use microphone in google colab?

OSError Traceback (most recent call last)
<ipython-input-21-4159a88154c9> in <module>()
7 response = google_images_download.googleimagesdownload()
8 r = sr.Recognizer()
----> 9 with sr.Microphone() as source:
10 print("Say something!")
11 audio = r.listen(source)
/usr/local/lib/python3.6/dist-packages/speech_recognition/init.py in init(self, device_index, sample_rate, chunk_size)
84 assert 0 <= device_index < count, "Device index out of range ({} devices available; device index should be between 0 and {} inclusive)".format(count, count - 1)
85 if sample_rate is None: # automatically set the sample rate to the hardware's default sample rate if not specified
---> 86 device_info = audio.get_device_inf o_by_index(device_index) if device_index is not None else audio.get_default_input_device_info()
87 assert isinstance(device_info.get("defaultSampleRate"), (float, int)) and device_info["defaultSampleRate"] > 0, "Invalid device info returned from PyAudio: {}".format(device_info)
88 sample_rate = int(device_info["defaultSampleRate"])
Here's an example that shows how to access a user's camera and microphone:
https://colab.research.google.com/notebooks/snippets/advanced_outputs.ipynb#scrollTo=2viqYx97hPMi
The snippet you linked above attempts to access a microphone in Python. That won't work because there's no microphone attached to the virtual machine which executes Python code in Colab.
Instead, you want to access the microphone of the computer running the web browser. Then, capture data there, and pass it back to the virtual machine for processing in Python.
That's what's shown in the snippet linked above.
Here is a simple snippet
from IPython.display import HTML, Audio
from google.colab.output import eval_js
from base64 import b64decode
import numpy as np
from scipy.io.wavfile import read as wav_read
import io
import ffmpeg
AUDIO_HTML = """
<script>
var my_div = document.createElement("DIV");
var my_p = document.createElement("P");
var my_btn = document.createElement("BUTTON");
var t = document.createTextNode("Press to start recording");
my_btn.appendChild(t);
//my_p.appendChild(my_btn);
my_div.appendChild(my_btn);
document.body.appendChild(my_div);
var base64data = 0;
var reader;
var recorder, gumStream;
var recordButton = my_btn;
var handleSuccess = function(stream) {
gumStream = stream;
var options = {
//bitsPerSecond: 8000, //chrome seems to ignore, always 48k
mimeType : 'audio/webm;codecs=opus'
//mimeType : 'audio/webm;codecs=pcm'
};
//recorder = new MediaRecorder(stream, options);
recorder = new MediaRecorder(stream);
recorder.ondataavailable = function(e) {
var url = URL.createObjectURL(e.data);
var preview = document.createElement('audio');
preview.controls = true;
preview.src = url;
document.body.appendChild(preview);
reader = new FileReader();
reader.readAsDataURL(e.data);
reader.onloadend = function() {
base64data = reader.result;
//console.log("Inside FileReader:" + base64data);
}
};
recorder.start();
};
recordButton.innerText = "Recording... press to stop";
navigator.mediaDevices.getUserMedia({audio: true}).then(handleSuccess);
function toggleRecording() {
if (recorder && recorder.state == "recording") {
recorder.stop();
gumStream.getAudioTracks()[0].stop();
recordButton.innerText = "Saving the recording... pls wait!"
}
}
// https://stackoverflow.com/a/951057
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var data = new Promise(resolve=>{
//recordButton.addEventListener("click", toggleRecording);
recordButton.onclick = ()=>{
toggleRecording()
sleep(2000).then(() => {
// wait 2000ms for the data to be available...
// ideally this should use something like await...
//console.log("Inside data:" + base64data)
resolve(base64data.toString())
});
}
});
</script>
"""
def get_audio():
display(HTML(AUDIO_HTML))
data = eval_js("data")
binary = b64decode(data.split(',')[1])
process = (ffmpeg
.input('pipe:0')
.output('pipe:1', format='wav')
.run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True, quiet=True, overwrite_output=True)
)
output, err = process.communicate(input=binary)
riff_chunk_size = len(output) - 8
# Break up the chunk size into four bytes, held in b.
q = riff_chunk_size
b = []
for i in range(4):
q, r = divmod(q, 256)
b.append(r)
# Replace bytes 4:8 in proc.stdout with the actual size of the RIFF chunk.
riff = output[:4] + bytes(b) + output[8:]
sr, audio = wav_read(io.BytesIO(riff))
return audio, sr
then run this
audio, sr = get_audio()
you might need to install this one
!pip install ffmpeg-python

Decrypting cipher with aes-256-cbc with iv using nodejs crypto module

Am porting a python AES-256 Encrypt/Decrypt method to its nodejs equivalent.But am having an error on the nodejs side when decrypting the cipher.
crypto.js:267
this._handle.initiv(cipher, toBuf(key), toBuf(iv));
^
Error: Invalid IV length
at new Decipheriv (crypto.js:267:16)
at Object.createDecipheriv (crypto.js:627:10)
at CryptoUtils.AESDecrypt
The python Encrypt/Decrypt Method:
def AESEncrypt(key, plaintext):
plaintext = AESPad(plaintext)
iv = os.urandom(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(plaintext)
def AESDecrypt(key, ciphertext):
iv = ciphertext[:AES.block_size];
cipher = AES.new(key, AES.MODE_CBC, iv);
plaintext = cipher.decrypt(ciphertext[AES.block_size:]);
return AESUnpad(plaintext);
My nodejs attempt to convert it:
AESEncrypt(key, plaintext) {
const _plaintext = this.AESPad(plaintext)
const iv = crypto.randomBytes(AES_BLOCK_SIZE) //synchronous
const cipher = crypto
.createCipheriv("aes-256-cbc", key, iv)
.update(_plaintext)
return iv + cipher
}
AESDecrypt(key, ciphertext) {
const iv = ciphertext.slice(0, 16)
console.log("iv", iv)
const plaintext = crypto
.createDecipheriv("aes-256-cbc", key, iv)
.update(ciphertext.substring(16))
return this.AESUnpad(plaintext)
What am i doing wrong? My nodejs version is v8.11.2 and Python 2.7.15rc1

Create RSA Token in nodejs

I'm trying to authenticate to a REST API using encryption.
First I need to call the API to get an encryptionKey and a timestamp.
The encryptionKey is in Base 64 format.
Then I need to create a RSAToken using the key and finaly encrypt the password using password + "|" + timestamp.
This is some sample code using python to authenticate to the API
key, timestamp = get_encryption_key()
decoded_key = key.decode('base64')
rsa_key = RSA.importKey(decoded_key)
encrypted = rsa_key.encrypt(password + '|' + str(timestamp), 'x')
encrypted_password = encrypted[0]
and
import base64
from Crypto.PublicKey import RSA
r = requests.get(my_url, headers=headers)
myData = r.json()
decoded = base64.b64decode(myData['encryptionKey'])
key = RSA.importKey(decoded)
enc = key.encrypt(password + '|' + str(myData['timeStamp']), 'x')
encryptedPassword = enc[0]
session = "/session"
my_url = url + session
payload = {"identifier": identifier,
"password": encryptedPassword,
"encryptedPassword": "True"
}
Any hints to achieve this under Node?
You can use crypto.publicEncrypt to encrypt your password. Notice the padding, you might want to use the right padding that is being used in your Python script.
const crypto = require('crypto');
const constants = require('constants');
const decodedKey = Buffer(encriptionKey, 'base64').toString();
const encryptedPassword = crypto.publicEncrypt({
key: decodedKey,
padding : constants.RSA_PKCS1_OAEP_PADDING
} , Buffer(`${password}|${timestamp}`));
Check out this node.js test to find out more examples for different padding.

Resources