Skip to content

Decryption js side fails with error:1C800064:Provider routines::bad decrypt #4940

@sysdev-jay

Description

@sysdev-jay

Describe the bug
Decryption fails on the Node.js side and throws the following exception:
error:1C800064:Provider routines::bad decrypt

To Reproduce
Encrypt a string on the C++ side using POCO, then attempt to decrypt the encrypted string on the JavaScript side.

CODE:

Cpp side:

#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/Context.h>
#include <Poco/Net/SSLManager.h>
#include <Poco/StreamCopier.h>
#include <Poco/Path.h>
#include <Poco/URI.h>
#include <Poco/Crypto/Cipher.h>
#include <Poco/Crypto/CipherFactory.h>
#include <Poco/Crypto/CipherKey.h>
#include <Poco/DigestEngine.h>
#include <Poco/MD5Engine.h>
#include <Poco/Base64Decoder.h>
#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>

using namespace Poco;
using namespace Poco::Net;
using namespace Poco::Crypto;
using namespace std;

// Global encryption parameters
const std::string g_encMethod = "aes-256-cbc";
const std::string g_key = "01234567890123456789012345678901"; // 32 bytes
const std::string g_iv = "1234567890123456"; // 16 bytes

void printHex(const std::string& data) {
    std::ostringstream oss;
    for (unsigned char c : data) {
        oss << std::hex << std::setw(2) << std::setfill('0') << (int)c << "";
    }
    std::cout << "[DEBUG] Base64 Decoded (Hex) : [" << oss.str() << "]" << std::endl;
    std::cout << "[DEBUG] Base64 Decoded Length: " << oss.str().length() << "]" << std::endl;
}

std::string base64Decode(const std::string& base64Str) {
    std::istringstream istr(base64Str);
    Poco::Base64Decoder decoder(istr);
    std::ostringstream out;
    Poco::StreamCopier::copyStream(decoder, out);
    return out.str(); // decoded binary string
}

// In CppClient2.cpp
std::string encryptData(const std::string& data) {
    // Create cipher with explicit parameters
    CipherKey cipherKey(g_encMethod, g_key, g_iv);
    unique_ptr<Cipher> pCipher(CipherFactory::defaultFactory().createCipher(cipherKey));

    // Use base64 encoding
    std::string encryptedData = pCipher->encryptString(data, Cipher::ENC_BASE64); 

    return encryptedData;
}
std::string decryptData(const std::string& encryptedBase64) {
    // Create cipher with explicit parameters
    CipherKey cipherKey(g_encMethod, g_key, g_iv);
    unique_ptr<Cipher> pCipher(CipherFactory::defaultFactory().createCipher(cipherKey));

    try {
        // Decrypt from base64
        std::string decryptedText = pCipher->decryptString(encryptedBase64, Cipher::ENC_BASE64); 
        return decryptedText;
    }
    catch (Exception& ex) {
        std::cerr << "Decryption error: " << ex.displayText() << std::endl;
        throw;
    }
}

std::string calculateMD5(const std::string& data) {
    MD5Engine md5;
    md5.update(data);
    return DigestEngine::digestToHex(md5.digest());
}


int mainUsingPayloadEncryption() {
    try {

        std::cout << "Using key: "; printHex(g_key); std::cout << std::endl;
        std::cout << "Using IV: "; printHex(g_iv); std::cout << std::endl;
        std::cout << "Using method: " << g_encMethod << std::endl << std::endl;

        // First test local encryption/decryption
        std::string jsonData = "{\"name\":\"John\",\"message\":\"Hello from C++\"}";
        std::cout << "Original JSON: [" << jsonData << "]" << std::endl;
        std::cout << "Original JSON length: [" << jsonData.length() << "]" << std::endl;

        // Test encryption
        std::string encrypted = encryptData(jsonData);
        std::cout << "Encrypted data (base64): [" << encrypted << "]" << std::endl;
        std::cout << "Encrypted data length: [" << encrypted.length() << "]" << std::endl;

        std::string decoded = base64Decode(encrypted);
        std::cout << "decoded data (from base64): [" << decoded << "]" << std::endl;
        std::cout << "decoded data (from base64): ["; printHex(decoded); cout << "]" << std::endl;
        std::cout << "decoded data length: [" << decoded.length() << "]" << std::endl; 



        // Test decryption
        std::string decrypted = decryptData(encrypted);
        std::cout << "Decrypted Data: [" << decrypted << "]" << std::endl;
        std::cout << "Decrypted Data length: [" << decrypted.length() << "]" << std::endl;

        // Verify the round trip was successful 
        if (jsonData == decrypted) {
            std::cout << "✓ Local encryption/decryption test PASSED!" << std::endl;
        }
        else {
            std::cout << "✗ Local encryption/decryption test FAILED!" << std::endl;
            return 1;
        }

        // Now proceed with the HTTPS request
        const string certPath = "certs/";
        const string clientCert = certPath + "client.crt";
        const string clientKey = certPath + "client.key";
        const string caFile = certPath + "rootCA.crt";

        Context::Ptr context = new Context(
            Context::CLIENT_USE,
            clientKey,
            clientCert,
            caFile,
            Context::VERIFY_STRICT,
            9,
            false,
            "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
        );

        SSLManager::instance().initializeClient(0, 0, context);

        HTTPSClientSession session("localhost", 8090, context);
        session.setTimeout(Timespan(10, 0));

        // Calculate MD5 hash for integrity check
        Poco::MD5Engine md5;
        md5.update(encrypted);
        std::string md5Hash = Poco::DigestEngine::digestToHex(md5.digest());

        // Prepare request body
        std::string requestBody = "{\"encrypted_payload\":\"" + encrypted +
            "\",\"integrity_hash\":\"" + md5Hash + "\"}";

        HTTPRequest request(HTTPRequest::HTTP_POST, "/send", HTTPMessage::HTTP_1_1);
        request.setContentType("application/json");
        request.setContentLength(requestBody.length());

        std::ostream& os = session.sendRequest(request);
        os << requestBody;

        HTTPResponse response;
        std::istream& rs = session.receiveResponse(response);
        std::string responseBody;
        StreamCopier::copyToString(rs, responseBody);

        cout << "Response: " << response.getStatus() << " " << response.getReason() << endl;
        cout << "Body: " << responseBody << endl;

        return 0;
    }
    catch (Exception& ex) {
        cerr << "Error: " << ex.displayText() << endl;
        return 1;
    }
}

JavaScript code:

const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
const https = require('https');

const app = express();
app.use(express.json());

// Global encryption parameters - must match C++ client
const g_encMethod = 'aes-256-cbc';
const g_key = Buffer.from('01234567890123456789012345678901', 'utf8'); // 32 bytes
const g_iv = Buffer.from('1234567890123456', 'utf8'); // 16 bytes


/**
 * Encrypts text using AES-256-CBC.
 * 
 * @param {string} plaintext - The text to encrypt.
 * @param {string} algorithm - The encryption algorithm, e.g., 'aes-256-cbc'.
 * @param {Buffer} key - The 32-byte key.
 * @param {Buffer} iv - The 16-byte initialization vector.
 * @returns {Buffer} The encrypted data.
 */
function encrypt(plaintext, algorithm, key, iv) {
    try {
        console.log('[DEBUG] Plaintext:', plaintext);
        console.log('[DEBUG] Key:', key.toString('hex'));
        console.log('[DEBUG] IV:', iv.toString('hex'));

        const cipher = crypto.createCipheriv(algorithm, key, iv);
        let encrypted = cipher.update(plaintext, 'utf8');
        encrypted = Buffer.concat([encrypted, cipher.final()]);

        console.log('[DEBUG] Encrypted buffer (hex):', encrypted.toString('hex'));
        console.log('[DEBUG] Encrypted buffer (base64):', encrypted.toString('base64'));
        return encrypted;
    } catch (error) {
        console.error('[DEBUG] Encryption error details:', error.message);
        throw error;
    }
}

/**
 * Decrypts AES-256-CBC encrypted data.
 * 
 * @param {Buffer} encryptedBuffer - The encrypted data buffer.
 * @param {string} algorithm - The encryption algorithm, e.g., 'aes-256-cbc'.
 * @param {Buffer} key - The 32-byte key.
 * @param {Buffer} iv - The 16-byte initialization vector.
 * @returns {string} The decrypted plaintext.
 */
function decrypt(encryptedBuffer, algorithm, key, iv) {
    try {
        console.log('[DEBUG] Encrypted buffer:', encryptedBuffer.toString('hex'));
        console.log('[DEBUG] Encrypted buffer length:', encryptedBuffer.length);
        console.log('[DEBUG] Key:', key.toString('hex'));
        console.log('[DEBUG] IV:', iv.toString('hex'));

        const decipher = crypto.createDecipheriv(algorithm, key, iv);
        let decrypted = decipher.update(encryptedBuffer);
        decrypted = Buffer.concat([decrypted, decipher.final()]);

        const result = decrypted.toString('utf8');
        console.log('[DEBUG] Decrypted text:', result);
        return result;
    } catch (error) {
        console.error('[DEBUG] Decryption error details:', error.message);
        throw error;
    }
}

// Middleware to verify data integrity
function verifyIntegrity(req, res, next) {
    const { encrypted_payload, integrity_hash } = req.body;

    if (!encrypted_payload || !integrity_hash) {
        return res.status(400).json({ error: 'Missing required fields' });
    }

    console.log('[DEBUG] Received encrypted_payload:', encrypted_payload);
    console.log('[DEBUG] Received integrity_hash:', integrity_hash);

    // Calculate MD5 hash of encrypted payload
    const calculatedHash = crypto.createHash('md5')
        .update(encrypted_payload)
        .digest('hex');

    console.log('[DEBUG] Calculated MD5:', calculatedHash);

    // Verify hash matches
    if (calculatedHash !== integrity_hash) {
        return res.status(400).json({ error: 'Data integrity check failed' });
    }

    next();
}

// Endpoint to receive encrypted data
app.post('/send', verifyIntegrity, (req, res) => {
    const { encrypted_payload } = req.body;

    console.log('[DEBUG] Key hex: [' + g_key.toString('hex') + "]");
    console.log('[DEBUG] IV hex: [' + g_iv.toString('hex') + "]");
    console.log('[DEBUG] Method: [' + g_encMethod + "]");

    try {
        console.log('[DEBUG] Ciphertext (base64): [' + encrypted_payload + "]");
        console.log('[DEBUG] Ciphertext length: [' + encrypted_payload.length + "]");

        // Decrypt using the global parameters
        const decryptedStr = decrypt(encrypted_payload, g_encMethod, g_key, g_iv);
        console.log('[DEBUG] Decrypted: [' + decryptedStr + "]");

        const data = JSON.parse(decryptedStr);
        console.log('[DEBUG] Parsed data: [' + data + "]");

        // Send success response
        res.json({
            status: 'success',
            message: `Hello ${data.name || 'User'}, your message was received securely`
        });
    } catch (error) {
        console.error('[ERROR] Processing error:', error.message);
        res.status(500).json({ error: 'Failed to process data: ' + error.message });
    }


});

// Default route
app.get('/', (req, res) => {
    res.send('Secure server is running');
});

// HTTPS server options
const options = {
    key: fs.readFileSync('../certs/server.key'),
    cert: fs.readFileSync('../certs/server.crt'),
    ca: fs.readFileSync('../certs/rootCA.crt'),
    requestCert: true,
    rejectUnauthorized: true
};

const server = https.createServer(options, app);
server.listen(8090, () => {
    console.log('Secure server listening on port 8090');
});

Expected behavior
At js side, Decrypted string should be same as original string before encryption from c++ side.

Logs

Screenshots
Image

Please add relevant environment information:

  • OS Type and Version: 10
  • POCO Version: 1.14.0
  • Third-party product (eg. database or library) type and version: NA

Additional context
I'm attempting to encrypt a JSON object on the C++ side using POCO and then send the encrypted data (along with the payload) to the server. On the JavaScript side, both the base64-encoded string and the raw encrypted data match the output from C++, which confirms the data is transmitted correctly.

However, when I try to decrypt the data in JavaScript, I get the following error:
error:1C800064:Provider routines::bad decrypt

I'm unsure why the decryption fails in JavaScript. For context:

Encryption and decryption on the C++ side work perfectly.

Similarly, both encryption and decryption in JavaScript also work as expected.

The issue only occurs when encrypting in C++ and attempting to decrypt that encrypted data in JavaScript.

Note: I am using the same key, iv and encryption AES like 256.

I'd greatly appreciate any guidance on how to resolve this issue.

Thanks a lot in advance to this amazing community!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions