Chris
Chris

Reputation: 459

Encrypt in C# using OpenSSL compatible format, decrypt in Poco

I'm trying to encrypt (aes-128-cbc) in Win OS using a OpenSSL compatible format and decrypt on Linux OS using Poco::Crypto that is a wrapper of OpenSSL. I'm using a pwd and salt.

Digging in the Stack Overflow, OpenSSL and Poco I found that:

1) from the Win end (I used a C# method) is needed to create a file with the header "Salted__1.....8" bytes where 1..8 bytes are the generated salt in random mode. Total of header byte = 16. Infact OpenSSL function EVP_BytesToKey(..) generate the key from salt extracted from the header. I saved all the bytes (header + salt + encrypted) in a file.

I would say thank you to Antanas Veiverys for his code at https://antanas.veiverys.com/encrypt-data-with-net-decrypt-with-openssl/. I used his class as below (snippet):

public static class AESEncryption
{
    private static byte[] randomBytes(int size)
    {
        byte[] array = new byte[size];
        new Random().NextBytes(array);
        return array;
    }

    /// Encrypt a string
    public static string Encrypt(string plainText, string password)
    {
        byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        byte[] encryptedBytes = Encrypt(plainTextBytes, password);
        return Convert.ToBase64String(encryptedBytes);
    }

    public static byte[] Encrypt(byte[] plainTextBytes, string password)
    {
        byte[] salt = randomBytes(8);
        // if salt is same during every encryption, same key is used. May be useful for testing, must not be used in production code
        //salt = new byte[8]; //set {0,0...}

        byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

        MD5 md5 = MD5.Create();

        int preKeyLength = password.Length + salt.Length;
        byte[] preKey = new byte[preKeyLength];

        Buffer.BlockCopy(passwordBytes, 0, preKey, 0, passwordBytes.Length);
        Buffer.BlockCopy(salt, 0, preKey, passwordBytes.Length, salt.Length);

        byte[] key = md5.ComputeHash(preKey);

        int preIVLength = key.Length + preKeyLength;
        byte[] preIV = new byte[preIVLength];

        Buffer.BlockCopy(key, 0, preIV, 0, key.Length);
        Buffer.BlockCopy(preKey, 0, preIV, key.Length, preKey.Length);

        byte[] iv = md5.ComputeHash(preIV);

        md5.Clear();
        md5 = null;

        //debug
        Console.WriteLine("Key:");
        foreach(byte b in key){
            Console.WriteLine("Hex: {0:X}", b);
        }
        Console.WriteLine("--------------------");
        AesManaged aes = new AesManaged();
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;
        aes.KeySize = 128;
        aes.BlockSize = 128;
        aes.Key = key;
        aes.IV = iv;

        byte[] encrypted = null;

        using (ICryptoTransform Encryptor = aes.CreateEncryptor())
        {
            using (MemoryStream MemStream = new MemoryStream())
            {
                using (CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write))
                {
                    CryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                    CryptoStream.FlushFinalBlock();

                    encrypted = MemStream.ToArray();
                    CryptoStream.Close();
                }
                MemStream.Close();
            }
        }
        aes.Clear();

        int resultLength = encrypted.Length + 8 + 8;
        byte[] salted = Encoding.UTF8.GetBytes("Salted__");
        byte[] result = new byte[resultLength];

        Buffer.BlockCopy(salted, 0, result, 0, salted.Length);
        Buffer.BlockCopy(salt, 0, result, 8, salt.Length);
        Buffer.BlockCopy(encrypted, 0, result, 16, encrypted.Length);

        return result;
    }

2) at this point, to create the right key in Poco, I must set the ItererationCount = 1 and not 2000 as the default value in:

CipherKeyImpl
{
    const std::string & name,
    const std::string & passphrase,
    const std::string & salt,
    int iterationCount = 2000
);

In this way, I'm going to extract the salt from the header file, generate the key and try to decrypt using this code:

ifstream myfile(FILE_TO_DECRYPT, ios::binary);
//get the length
myfile.seekg(0, myfile.end);
int length = myfile.tellg();
myfile.seekg(0, myfile.beg);

//read the encrypted file
char buffer = new char[lenght]();
myfile.read(buffer, lenght);

 //get the salt from the header
string toDecrypt(buffer);
string salt = toDecrypt.substr(8, 8); //the salt is 8 bytes start from 8th in header in OpenSSL

//decode
CipherFactory& factory = CipherFactory::defaultFactory();
Chipher key("aes-128-cbc", PASSWORD, salt, 1);
//key is well generated!

unique_ptr<Cipher> uptrCipher(factory.createCipher(key));
string decrypted = uptrChiper->decryptString(toDecrypt, Cipher::ENC_BASE64);

I checked that salt and key are ok: they are the same that I used in C# code.

The last line generate an error: "EVP_DecryptFinal_ex: wrong final block length."

I don't undestand where I wrong. Any help is appreciated.

Upvotes: 3

Views: 1831

Answers (1)

Chris
Chris

Reputation: 459

There were two issues. First, I made a mistake. I need to pass to decrypt method ONLY the bytes without the header: so I added:

toDecrypt = toDectrypt.substr(16);

to the code, otherwise the decryption doesn't work because the header.

Secondly, I used the ENC_BASE64, so I need to encode the bytes in the string before decrypting. For this purpose I used the Poco::Base64Encoder.

At this point...it works! I encrypted on Windows using AES-128-CBC in C# and decrypted on Linux using C++, Poco and OpenSSL format. I apologize: the code could be better, but I need time to optimize it.

#include <iostream>
#include <memory>
#include <fstream>
#include <sstream>
#include "Poco/Crypto/OpenSSLInitializer.h"
#include "Poco/Crypto/Cipher.h"
#include "Poco/Crypto/CipherKey.h"
#include "Poco/Crypto/CipherFactory.h"
#include "Poco/Base64Encoder.h"

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

const string FILE_TO_DECRYPT = {"/home/myuser/Documents/CryptoTest/EncryptedText.txt"};

void decrypt() {
    OpenSSLInitializer();
    string PASSWORD = "1234567890123456"; //example! I know that is FORBIDDEN to embed the pwd :-)

    try {
        const size_t len = 17;

        std::ifstream fileImage(FILE_TO_DECRYPT);
        if(!fileImage.good())
            return;

        //get lenght of file
        fileImage.seekg(0, fileImage.end);
        int length = fileImage.tellg();
        fileImage.seekg(0, fileImage.beg);

        char* buffer = new char[length];
        fileImage.read(buffer, length);

        ostringstream ostringstr;
        Base64Encoder base64 (ostringstr);

        string toDecrypt(buffer, length);
        string salt = toDecrypt.substr(8, 8);
        cout<<endl<<"salt length = "<<salt.length()<<endl;

        //Debug 
        for(unsigned k = 0; k < salt.length(); k++)
            cout<<endl<<"["<<k<<"]"<<hex<<(unsigned)(unsigned char)salt[k]<<flush;

        CipherFactory& factory = CipherFactory::defaultFactory();
        CipherKey key("aes-128-cbc", PASSWORD, salt, 1);

        auto k = key.getKey();
        //Debug
        cout<<endl<<"KEY---";
        for(auto i : k)
            cout<<endl<<std::hex<<(unsigned)i<<std::dec<<flush;

        unique_ptr<Cipher> uptrCipher(factory.createCipher(key));

        toDecrypt=toDecrypt.substr(16);

        base64<<toDecrypt;
        base64.close();
        string toDecryptEncoded = ostringstr.str();

        string decrypted = uptrCipher->decryptString(toDecryptEncoded, Cipher::ENC_BASE64); //Cipher::ENC_BASE64);
        cout<<endl<<"Decrypted= "<<decrypted<<endl;

        delete []buffer;
    }
    catch(const Poco::Exception& exc)
    {
        cerr << endl<< "Decryption error: " << exc.message() << endl;
        delete []buffer; //in case of error...
    }
}

Upvotes: 1

Related Questions