【问题标题】:Encrypt in C# using OpenSSL compatible format, decrypt in Poco使用 OpenSSL 兼容格式在 C# 中加密,在 Poco 中解密
【发布时间】:2015-03-25 22:30:31
【问题描述】:

我正在尝试使用 OpenSSL 兼容格式在 Win OS 中加密 (aes-128-cbc),并在 Linux OS 上使用 Poco::Crypto 进行解密,Poco::Crypto 是 OpenSSL 的包装器。 我正在使用密码和盐。

挖掘 Stack Overflow、OpenSSL 和 Poco 我发现:

1) 需要从 Win 端(我使用 C# 方法)创建一个标题为 "Salted__1.....8" 字节的文件,其中 1..8 字节是以随机模式生成盐。标头字节总数 = 16。事实上 OpenSSL 函数 EVP_BytesToKey(..) 从标头中提取的盐生成密钥。我将所有字节(头 + 盐 + 加密)保存在一个文件中。

我要感谢 Antanas Veiverys 在https://antanas.veiverys.com/encrypt-data-with-net-decrypt-with-openssl/ 上提供的代码。我使用他的类如下(sn-p):

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)此时,要在 Poco 中创建正确的键,我必须将 ItererationCount = 1 而不是 2000 设置为默认值:

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

这样,我将从头文件中提取盐,生成密钥并尝试使用以下代码解密:

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

我检查了 salt 和 key 是否正常:它们与我在 C# 代码中使用的相同。

最后一行产生错误:"EVP_DecryptFinal_ex: wrong final block length."

我不明白我错在哪里。任何帮助表示赞赏。

【问题讨论】:

    标签: c# c++ cryptography openssl poco


    【解决方案1】:

    有两个问题。首先,我犯了一个错误。我只需要将没有标头的字节传递给解密方法:所以我添加了:

    toDecrypt = toDectrypt.substr(16);
    

    到代码,否则解密不起作用,因为header。

    其次,我使用了ENC_BASE64,所以我需要在解密之前对字符串中的字节进行编码。为此,我使用了 Poco::Base64Encoder。

    此时……它起作用了!我在 Windows 上使用 C# 中的 AES-128-CBC 进行加密,并在 Linux 上使用 C++、Poco 和 OpenSSL 格式进行解密。我很抱歉:代码可能会更好,但我需要时间来优化它。

    #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...
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-07-19
      • 1970-01-01
      • 2018-11-28
      • 2018-10-27
      • 2013-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-15
      相关资源
      最近更新 更多