本文章使用上一篇《C#调用C++类库例子》的项目代码作为Demo。本文中,C#将调用C++的Crypto++库,实现AES的ECB和CBC加解密。
一、下载Crypto
1、进入Crypto的官网下载openssl。网址是: https://www.cryptopp.com/。
2、点击“DownLoad”,选择最新的可下载的版本即可。此时我下载的是cryptopp820.zip,如下图所示的。
3、解压 cryptopp820.zip。
4、打开cryptopp820文件夹中的cryptest.sln,点击“重定解决方案目标”。
5、重新生成解决方案。
二、建立自己使用的Crypto++ Library
由于从官方网下载的Crypto++库是开源的,只有源文件和几个可以生成lib、dll的工程,以及一个使用的例子工程,因此希望生成自己建的工程能使用的SDK。
1. 编译链接生成cryptlib.lib
根据当前项目系统设置平台(我是设置为x64),分别在Debug模式和Release模式下编译链接cryptlib工程,成功后会在cryptopp820\x64\Output\Debug和cryptopp820\x64\Output\Release下生成cryptlib.lib文件。
2. 建立Crypto++ SDK
在解决方案下新建一文件夹,取名“CryptoPP”,里面新建文件夹“include”、“lib”,在“lib”中新建文件夹“debug”、“release”。将Crypto++库中的所有头文件复制到“include”文件夹中,再将上面生成的两个cryptlib.lib分别复制到“debug”和“release”中。
三、设置工程属性
在EncryptBese项目--右键--属性:
(1)“配置属性”→“C/C++” →“常规”,右边的“附加包含目录”设置为上面建好的Crypto++ SDK的Include文件夹,“E:\Project\ArticleProject\AuthorizationTest\CryptoPP\include”;
(2) “配置属性”→“Linker” →“链接器”,右边的“附加库目录”设置为上面建好的Crypto++ SDK的Lib\Debug文件夹,“E:\Project\ArticleProject\AuthorizationTest\CryptoPP\lib\debug”(Release模式下对应着Release文件夹);
(3) “配置属性”→“C/C++” →“代码生成”,右边的“运行库”设置为“Multi-threaded Debug (/MTd)”(Release模式下对应着“Multi-threaded (/MT)”)
四、为EncryptBase项目添加代码
1、在EncryptBase.h文件添加以下代码
#ifndef _ENCRYPTBASE_H //定义_ENRYPTBASE_H宏,是为了防止头文件的重复引用 #define _ENCRYPTBASE_H #ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, extern "C" { //因为cpp文件默认定义了该宏),则采用C语言方式进行编译 #endif #ifdef DLL_EXPORTS #define DLL_EXPORTS __declspec(dllexport) #else #define DLL_EXPORTS __declspec(dllimport) #endif enum AESMode { ECB = 0x00, CBC, }; enum BlockPaddingScheme { NO_PADDING, ZEROS_PADDING, PKCS_PADDING, ONE_AND_ZEROS_PADDING, W3C_PADDING, DEFAULT_PADDING }; DLL_EXPORTS int AesEncrypt(AESMode mode, BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int ivlen, const BYTE *plainText, int txtlen, BYTE *out, int outLen); DLL_EXPORTS int AesDecrypt(AESMode mode, BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int inlen, const BYTE *cipherText, int txtlen, BYTE *out, int outLen); int AesEcbEncrypt(BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int inlen, const BYTE *plainText, int txtlen, BYTE *out, int outLen); int AesEcbDecrypt(BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int inlen, const BYTE *cipherText, int txtlen, BYTE *out, int outLen); int AesCbcEncrypt(BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int inlen, const BYTE *plainText, int txtlen, BYTE *out, int outLen); int AesCbcDecrypt(BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int inlen, const BYTE *cipherText, int txtlen, BYTE *out, int outLen); #ifdef __cplusplus } #endif #endif // !_ENCRYPTBASE_H
2、EncryptBase.cpp添加相关代码,如下图。
在EncryptBase.cpp修改为以下代码:
// EncryptBase.cpp: 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" #include "EncryptBase.h" #include "base64.h" #include#include #include // StreamTransformationFilter using namespace std; using namespace CryptoPP; #pragma comment(lib, "cryptlib.lib") //加密 int AesEncrypt(AESMode mode, BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int ivlen, const BYTE *plainText, int txtlen, BYTE *out, int outLen) { int rtnRs = 0; switch (mode) { case AESMode::ECB: rtnRs = AesEcbEncrypt(scheme, key, keylen, iv, ivlen, plainText, txtlen, out, outLen); break; case AESMode::CBC: rtnRs = AesCbcEncrypt(scheme, key, keylen, iv, ivlen, plainText, txtlen, out, outLen); break; } return rtnRs; } //解密 int AesDecrypt(AESMode mode, BlockPaddingScheme scheme, const BYTE *key, int keylen, const BYTE *iv, int ivlen, const BYTE *cipherText, int txtlen, BYTE *out, int outLen) { int rtnRs = 0; switch (mode) { case AESMode::ECB: rtnRs = AesEcbDecrypt(scheme, key, keylen, iv, ivlen, cipherText, txtlen, out, outLen); break; case AESMode::CBC: rtnRs = AesCbcDecrypt(scheme, key, keylen, iv, ivlen, cipherText, txtlen, out, outLen); break; } return rtnRs; } //AES_ECB加密 int AesEcbEncrypt(BlockPaddingScheme scheme, const BYTE *skey, int keylen, const BYTE *iv, int ivlen, const BYTE *plainText, int txtlen, BYTE *out, int outLen) { int rtnLength = -1; try { //填key SecByteBlock key(AES::MIN_KEYLENGTH); memset(key, 0x00, key.size()); if (keylen <= AES::MIN_KEYLENGTH) { memcpy(key, skey, keylen); } else { memcpy(key, skey, AES::MIN_KEYLENGTH); } AES::Encryption aesEncryption(key, AES::MIN_KEYLENGTH); ECB_Mode_ExternalCipher::Encryption ecbEncryption(aesEncryption); vector encrypted; StreamTransformationFilter stfEncryptor( ecbEncryption, new VectorSink(encrypted), (CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme)scheme); stfEncryptor.Put(plainText, txtlen); //this is where it crashes stfEncryptor.MessageEnd(); rtnLength = encrypted.size(); memcpy(out, encrypted.data(), rtnLength); } catch (exception e) { cout << e.what() << endl; //TODO:记录日志 rtnLength = -1; } return rtnLength; } //AES_ECB解密 int AesEcbDecrypt(BlockPaddingScheme scheme, const BYTE *skey, int keylen, const BYTE *siv, int ivlen, const BYTE *cipherText, int txtlen, BYTE *out, int outLen) { int rtnLength = -1; try { //填key SecByteBlock key(AES::MIN_KEYLENGTH); memset(key, 0x00, key.size()); if (keylen <= AES::MIN_KEYLENGTH) { memcpy(key, skey, keylen); } else { memcpy(key, skey, AES::MIN_KEYLENGTH); } ECB_Mode ::Decryption ecbDecryption(key, AES::MIN_KEYLENGTH); vector decrypted; StreamTransformationFilter stfDecryptor( ecbDecryption, new VectorSink(decrypted), (CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme)scheme); stfDecryptor.Put(cipherText, txtlen); stfDecryptor.MessageEnd(); rtnLength = decrypted.size(); memcpy(out, decrypted.data(), rtnLength); } catch (exception e) { cout << e.what() << endl; //TODO:记录日志 } return rtnLength; } //AES_CBC加密 int AesCbcEncrypt(BlockPaddingScheme scheme, const BYTE *skey, int keylen, const BYTE *siv, int ivlen, const BYTE *plainText, int txtlen, BYTE *out, int outLen) { int rtnLength = -1; try { //填key SecByteBlock key(AES::MIN_KEYLENGTH); memset(key, 0x00, key.size()); if (keylen <= AES::MIN_KEYLENGTH) { memcpy(key, skey, keylen); } else { memcpy(key, skey, AES::MIN_KEYLENGTH); } //填iv byte iv[AES::BLOCKSIZE]; memset(iv, 0x00, AES::BLOCKSIZE); if (ivlen <= AES::BLOCKSIZE) { memcpy(iv, siv, ivlen); } else { memcpy(iv, siv, AES::BLOCKSIZE); } AES::Encryption aesEncryption((byte *)key, AES::MIN_KEYLENGTH); CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv); vector decrypted; StreamTransformationFilter stfDecryptor( cbcEncryption, new VectorSink(decrypted), (CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme)scheme); stfDecryptor.Put(plainText, txtlen); stfDecryptor.MessageEnd(); rtnLength = decrypted.size(); memcpy(out, decrypted.data(), rtnLength); } catch (exception e) { cout << e.what() << endl; //TODO:记录日志 rtnLength = -1; } return rtnLength; } //AES_CBC解密 int AesCbcDecrypt(BlockPaddingScheme scheme, const BYTE *skey, int keylen, const BYTE *siv, int ivlen, const BYTE *cipherText, int txtlen, BYTE *out, int outLen) { int rtnLength = -1; try { //填key SecByteBlock key(AES::MIN_KEYLENGTH); memset(key, 0x00, key.size()); if (keylen <= AES::MIN_KEYLENGTH) { memcpy(key, skey, keylen); } else { memcpy(key, skey, AES::MIN_KEYLENGTH); } //填iv byte iv[AES::BLOCKSIZE]; memset(iv, 0x00, AES::BLOCKSIZE); if (ivlen <= AES::BLOCKSIZE) { memcpy(iv, siv, ivlen); } else { memcpy(iv, siv, AES::BLOCKSIZE); } CBC_Mode ::Decryption cbcDecryption(key, AES::MIN_KEYLENGTH, iv); vector decrypted; StreamTransformationFilter stfDecryptor( cbcDecryption, new VectorSink(decrypted), (CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme)scheme); stfDecryptor.Put(cipherText, txtlen); stfDecryptor.MessageEnd(); rtnLength = decrypted.size(); memcpy(out, decrypted.data(), rtnLength); } catch (exception e) { cout << e.what() << endl; //TODO:记录日志 } return rtnLength; }
五、编写测试代码
1、在在FrameworkConsoleTest项目的Program编写以下代码。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace FrameworkConsoleTest { class Program { [DllImport("EncryptBase.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int AesEncrypt(AESMode mode, BlockPaddingScheme scheme, byte[] key, int keylen, byte[] iv, int ivlen, byte[] plainTextPtr, int txtlen, byte[] outBytes, int outLen); [DllImport("EncryptBase.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int AesDecrypt(AESMode mode, BlockPaddingScheme scheme, byte[] key, int keylen, byte[] iv, int ivlen, byte[] plainTextPtr, int txtlen, byte[] outBytes, int outLen); static void Main(string[] args) { try { string plainText = "123456"; byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); //AesKey密匙 string key = "abc123456_+,./@$"; byte[] keyBytes = Encoding.UTF8.GetBytes(key); //IV向量 byte[] ivBytes = new byte[16]; for (int i = 0; i < 16; i++) { ivBytes[i] = 0; } string strEncrypt = string.Empty; string strDecrypt = string.Empty; byte[] encryptOutBytes = new byte[100]; int execRs = -1; execRs = ToAesEncrypt(AESMode.ECB, BlockPaddingScheme.PKCS_PADDING, keyBytes, ivBytes, plainTextBytes, ref encryptOutBytes); if (execRs > 0) { strEncrypt = Convert.ToBase64String(encryptOutBytes, 0, execRs); } Console.WriteLine("Aes Ecb Encrypt,明文:{0},密文:{1}", plainText, strEncrypt); if (execRs > 0) { byte[] decryptBytes = new byte[execRs]; for (int i = 0; i0) { strDecrypt = System.Text.Encoding.UTF8.GetString(decryptOutBytes, 0, execRs); } Console.WriteLine("Aes Ecb Decrypt,解密后明文:{0}", strDecrypt); } byte[] encryptOutBytes2 = new byte[100]; execRs = ToAesEncrypt(AESMode.CBC, BlockPaddingScheme.PKCS_PADDING, keyBytes, ivBytes, plainTextBytes, ref encryptOutBytes2); if (execRs > 0) { strEncrypt = Convert.ToBase64String(encryptOutBytes2, 0, execRs); } Console.WriteLine("Aes Cbc Encrypt,明文:{0},密文:{1}", plainText, strEncrypt); if (execRs > 0) { byte[] decryptBytes2 = new byte[execRs]; for (int i = 0; i 0) { strDecrypt = System.Text.Encoding.UTF8.GetString(decryptOutBytes2, 0, execRs); } Console.WriteLine("Aes Cbc Decrypt,解密后明文:{0}", strDecrypt); } } catch (Exception ex) { Console.WriteLine("Main,ex:{0}", ex); } Console.ReadKey(); } private static int ToAesEncrypt(AESMode mode, BlockPaddingScheme scheme, byte[] keyBytes, byte[] ivBytes, byte[] encryptBytes, ref byte[] outBytes) { int rtnRs = -1; if (encryptBytes == null || encryptBytes.Count() == 0) { rtnRs = 0; goto TOEND; } try { int keyBytesLen = keyBytes.Length; int ivBytesLen = ivBytes.Length; int txtBytesLen = encryptBytes.Length; int outLen = outBytes.Length; //C#数组有长度,C++指针没长度,所以函数应该定义两个参数,一个数据BYTE*,一个长度int rtnRs = AesEncrypt(mode, scheme, keyBytes, keyBytesLen, ivBytes, ivBytesLen, encryptBytes, txtBytesLen, outBytes, outLen); } catch (Exception ex) { Console.WriteLine("AesEncrypt,ex:{0}", ex); } TOEND: ; return rtnRs; } private static int ToAesDecrypt(AESMode mode, BlockPaddingScheme scheme, byte[] keyBytes, byte[] ivBytes, byte[] decryptBytes, ref byte[] outBytes) { int rtnRs = -1; if (decryptBytes == null || decryptBytes.Count() == 0) { rtnRs = 0; goto TOEND; } try { //因为 C++ 返回的是 char* ,是指针,所以c# 要用 IntPtr 来接收 int txtBytesLen = decryptBytes.Length; int keyBytesLen = keyBytes.Length; int ivBytesLen = ivBytes.Length; int outLen = outBytes.Length; //C#数组有长度,C++指针没长度,所以函数应该定义两个参数,一个数据char*,一个长度int rtnRs = AesDecrypt(mode, scheme, keyBytes, keyBytesLen, ivBytes, ivBytesLen, decryptBytes, txtBytesLen, outBytes, outLen); } catch (Exception ex) { Console.WriteLine("AesDecrypt,ex:{0}", ex); } TOEND: ; return rtnRs; } } }
2、添加Enum.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FrameworkConsoleTest { enum AESMode { ECB = 0x00, CBC, }; enum BlockPaddingScheme { NO_PADDING, ZEROS_PADDING, PKCS_PADDING, ONE_AND_ZEROS_PADDING, W3C_PADDING, DEFAULT_PADDING }; }
六、整体结构
七、结果测试
1、运行结果
2、通过和其它平台测试比较结果,我是在http://tool.chacuo.net/cryptaes进行结果比对的。
八、下载地址
https://download.csdn.net/download/suterfo/12155867
九、参考资料:
https://www.cnblogs.com/cxun/archive/2008/07/30/743541.html
https://blog.csdn.net/liang19890820/article/details/51659452
https://stackoverflow.com/questions/59323973/aes-ctr-decryption-stops-unexpectedly-using-crypto?r=SearchResults
https://www.cryptopp.com/wiki/Filter