由于C#的反编译太过容易,所以之前自己客户端里调用的加密Dll被反编译之后,可以很容易的看到一些比较重要的信息,所以这次制作一个C++的Dll,调用C++的加密库Crypto++并且把密钥写在自己的C++Dll中,C#程序直接调用,这样可以把密钥隐藏起来。(但是有什么用呢?反正别人可以直接拷贝走C++Dll,这样不知道密钥只要知道输入输出就能解密,不得不说之前用C#制作这种需要安全性的软件简直就是一个大坑。但是在这里我们先假装它有用吧。)
本文使用的是window10系统下的VS2015,生产环境则是win7 x64的机器。
#pragma once
#include
using namespace std;
extern "C" __declspec(dllexport) int EncryptStr(char* plainText);
extern "C" __declspec(dllexport) int DecryptStr(char* plainText);
在.h头文件中申明需要导出的函数:EncryptStr和DecryptStr。
其中,extern "C"是为了解决命名问题,如果不加的话最后DLL的导出函数名字可能会和自己写的函数名不一致。__declspec(dllexport)是声明导出函数。当然也可以写成这样:
#pragma once
#include
using namespace std;
int EncryptStr(char* plainText);
int DecryptStr(char* plainText);
一会在.def(模块定义文件)中声明导出函数。
具体可以参考这篇博客,讲的比较详细。
这里比较值得注意的一点是我之前直接使用string类型的返回值,结果最后函数传递参数的时候不知道为什么出现了错误,可能是因为我使用了__stdcall关键字。
#include "MyDLL.h"
#pragma comment(lib, "cryptlib.lib" )
#pragma warning( disable : 4996)
#include
#include // StreamTransformationFilter
#include // CFB_Mode
#include // std:cerr
#include // std::stringstream
#include
using namespace std;
using namespace CryptoPP;
std::string ECB_AESEncryptStr(std::string sKey, const char *plainText)
{
std::string outstr;
//填key
SecByteBlock key(AES::MAX_KEYLENGTH);
memset(key, 0x30, key.size());
sKey.size() <= AES::MAX_KEYLENGTH ? memcpy(key, sKey.c_str(), sKey.size()) : memcpy(key, sKey.c_str(), AES::MAX_KEYLENGTH);
AES::Encryption aesEncryption((byte *)key, AES::MAX_KEYLENGTH);
ECB_Mode_ExternalCipher::Encryption ecbEncryption(aesEncryption);
StreamTransformationFilter ecbEncryptor(ecbEncryption, new HexEncoder(new StringSink(outstr)));
ecbEncryptor.Put((byte *)plainText, strlen(plainText));
ecbEncryptor.MessageEnd();
return outstr;
}
std::string ECB_AESDecryptStr(std::string sKey, const char *cipherText)
{
std::string outstr;
//填key
SecByteBlock key(AES::MAX_KEYLENGTH);
memset(key, 0x30, key.size());
sKey.size() <= AES::MAX_KEYLENGTH ? memcpy(key, sKey.c_str(), sKey.size()) : memcpy(key, sKey.c_str(), AES::MAX_KEYLENGTH);
ECB_Mode<AES >::Decryption ecbDecryption((byte *)key, AES::MAX_KEYLENGTH);
HexDecoder decryptor(new StreamTransformationFilter(ecbDecryption, new StringSink(outstr)));
decryptor.Put((byte *)cipherText, strlen(cipherText));
decryptor.MessageEnd();
return outstr;
}
int EncryptStr(char* plainText)
{
string s = ECB_AESEncryptStr("1234", plainText);
strcpy(plainText, s.data());
return 1;
}
int DecryptStr(char* plainText)
{
string s = ECB_AESDecryptStr("1234", plainText);
strcpy(plainText, s.data());
return 1;
}
在这里写了具体的函数和一些需要用的内容。如何调用Crypto++可以参考这篇文章。
值得注意的点是,在配置属性页的时候一定要注意上面的平台。
可以自行在右侧的配置管理器中进行配置。
LIBRARY "MyDLL"
EXPORTS
EncryptStr @1
DecryptStr @2
这里同样声明了导出函数,最前面的LIBRARY是自己的库名,后面的EncryptStr @1是指第一个函数导出为EncryptStr,当然也可以不写@1,会按照默认顺序进行导出函数。
右键解决方案,然后新建一个C#控制台程序,这边就不再赘述了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace MyDLLTest
{
class Program
{
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int EncryptStr(StringBuilder a);
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DecryptStr(StringBuilder a);
static void Main(string[] args)
{
int outdata;
StringBuilder str1 = new StringBuilder(1024);
str1.Append("test");
StringBuilder str2 = new StringBuilder(1024);
outdata = EncryptStr(str1);
Console.WriteLine(str1.ToString());
str2.Append(str1.ToString());
outdata = DecryptStr(str2);
Console.WriteLine(str2.ToString());
Console.Read();
}
}
}
使用DllImport引用DLL文件,值得注意的是如果要往C++的语言中传入char*,则在C#中需要使用StringBuilder。
虽然感觉C#通过调用C++来实现加密解密并没有什么卵用,因为C#自己就带有一个非常好用的加密解密库,所以感觉这篇文章有点多此一举的感觉。
但是话又说回来,在制作C++DLL的时候还是有非常多的坑的,这篇文章主要说明了几个我之前卡了很久的点,包括一开始制作了一个AnyCPU的结果一到x64程序里就报错了(因为我的项目是x64的),还包括C#和C++之间相互传递字符串,最后选择了传入char*,返回string,之前传入传出都使用string的话不知道为什么会读不到字符串。
总结一下,还是有非常多的问题没有仔细研究,希望之后再学习之中会慢慢的明白其中的原理。
下一篇文章应该是JAVA后台和C#程序建立一个长连接。