使用 OpenSSL 兼容格式在 C# 中加密,在 Poco 中解密
Encrypt in C# using OpenSSL compatible format, decrypt in Poco
我正在尝试使用 OpenSSL 兼容格式在 Win OS 中加密 (aes-128-cbc),并使用 Poco::Crypto 在 Linux OS 上解密是 OpenSSL 的包装器。
我正在使用密码和盐。
深入研究 Stack Overflow、OpenSSL 和 Poco,我发现:
1) 从 Win 端(我使用了 C# 方法)需要创建一个包含 header "Salted__1.....8" 字节的文件,其中 1..8 字节是以随机模式生成盐。总计 header 字节 = 16。事实上,OpenSSL 函数 EVP_BytesToKey(..)
从从 header 中提取的盐中生成密钥。我将所有字节(header + salt + 加密)保存在一个文件中。
我要感谢 Antanas Veiverys 在 https://antanas.veiverys.com/encrypt-data-with-net-decrypt-with-openssl/ 提供的代码。我使用他的 class 如下(片段):
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
);
这样,我将从 header 文件中提取盐,生成密钥并尝试使用此代码解密:
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."
我不明白我哪里错了。感谢任何帮助。
有两个问题。首先,我犯了一个错误。我只需要将没有 header 的字节传递给解密方法:所以我添加了:
toDecrypt = toDectrypt.substr(16);
到代码,否则解密不工作,因为header。
其次,我使用了ENC_BASE64,所以我需要在解密之前对字符串中的字节进行编码。为此,我使用了 Poco::Base64Encoder。
至此……成功了!我使用 C# 中的 AES-128-CBC 在 Windows 上加密,并使用 C++、Poco 和 OpenSSL 格式在 Linux 上解密。我道歉:代码可以更好,但我需要时间来优化它。
#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...
}
}
我正在尝试使用 OpenSSL 兼容格式在 Win OS 中加密 (aes-128-cbc),并使用 Poco::Crypto 在 Linux OS 上解密是 OpenSSL 的包装器。 我正在使用密码和盐。
深入研究 Stack Overflow、OpenSSL 和 Poco,我发现:
1) 从 Win 端(我使用了 C# 方法)需要创建一个包含 header "Salted__1.....8" 字节的文件,其中 1..8 字节是以随机模式生成盐。总计 header 字节 = 16。事实上,OpenSSL 函数 EVP_BytesToKey(..)
从从 header 中提取的盐中生成密钥。我将所有字节(header + salt + 加密)保存在一个文件中。
我要感谢 Antanas Veiverys 在 https://antanas.veiverys.com/encrypt-data-with-net-decrypt-with-openssl/ 提供的代码。我使用他的 class 如下(片段):
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
);
这样,我将从 header 文件中提取盐,生成密钥并尝试使用此代码解密:
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."
我不明白我哪里错了。感谢任何帮助。
有两个问题。首先,我犯了一个错误。我只需要将没有 header 的字节传递给解密方法:所以我添加了:
toDecrypt = toDectrypt.substr(16);
到代码,否则解密不工作,因为header。
其次,我使用了ENC_BASE64,所以我需要在解密之前对字符串中的字节进行编码。为此,我使用了 Poco::Base64Encoder。
至此……成功了!我使用 C# 中的 AES-128-CBC 在 Windows 上加密,并使用 C++、Poco 和 OpenSSL 格式在 Linux 上解密。我道歉:代码可以更好,但我需要时间来优化它。
#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...
}
}