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

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

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.

nodejs crypto createDecipheriv throws 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH'

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

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())

crypto.load_certificate / Get public key encryption

How is it possible from crypto.load_certificate to get the public key encryption ? (for example "RSA (2048 Bits").
I can get the public key easily as below :
from OpenSSL import crypto
cert = crypto.load_certificate(crypto.FILETYPE_PEM, open("certificate.crt")).read()
pubKey = cert.get_pubkey()
But I couldn't find anything in the documentation concerning the encryption. Any ideas?
Actually it is really simple :
from OpenSSL import crypto
cert = crypto.load_certificate(crypto.FILETYPE_PEM, open("certificate.crt")).read()
pubKey = cert.get_pubkey()
keySize = pubKey.bits()
if pubKey.type() == crypto.TYPE_RSA:
keyType = 'RSA'
elif pubKey.type() == crypto.TYPE_DSA:
keyType = 'DSA'
print(keyType + "-" + str(keySize))

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