最近做了一个项目,里面有一个小需求就是对处理过的文件进行加密,加密之后无法打开。我最先想到的是异或加密,因为需要速度,并且对加密的安全性要求不高。
1、异或加密原理异或密码(simple XOR cipher)是密码学中一种简单的加密算法,是指对信息进行异或操作来达到加密和解密目的。按这种逻辑,文本串行的每个字符可以通过与给定的密钥进行按位异或运算来加密。如果要解密,只需要将加密后的结果与密钥再次进行按位异或运算即可。
说的通俗一点,就是一个字符异或一个密钥字符进行加密,解密的时候,则用加密后的字符再次异或密钥字符则能够还原。
2、文件加密流程异或加密的原理就是使用了异或运算符的运算特点!例如异或密钥key是10101010,待加密的字符s1的二进制表示为01010101,则进行加密处理(s1 ^ key)后的结果s2为 11111111,如果要解密还原,则使用密钥key再次异或处理(s2 ^ key)就能够还原为s1。
对文件进行加密的流程就比较简单了,步骤如下:
- 用二进制方式读取文件
- 将文件读到缓冲区
- 每次从缓冲区读取一个字符
- 对读取的字符进行异或加密
- 加密结果保存到结果缓冲区
- 读取下一个字符进行加密(循环执行)
- 加密结束后保存到加密文件
那么,解密的流程呢?
其实解密和加密的流程一模一样,因为加密和解密的密钥是一样的!
3、C++实现这里有一些地方是可以进行优化改进的,例如:
- 大文件的情况:大文件可能导致超过内存大小
- 效率问题:每个字符都进行了加密
- 安全问题:单个字符作为密钥比较容易破解
- 无法区分文件是否已经加密
以上问题咱们放在第4部分再进行讨论!
实现部分用标准C++,支持跨平台的使用。这里我封装了一个处理类,大家直接看代码吧!
注意:代码中的方法是对pdf文件进行加解密操作,并不表示无法对其他文件进行操作。因为,所有的文件(不管pdf还是其他的文件)都是保存为二进制的,所以使用我提供的方法一样可以进行加解密操作!
所以这个加密方案就是一个通用万能的加解密方案!
h文件:
#ifndef __MasterEncoder_H__ #define __MasterEncoder_H__ #include#include using namespace std; class MasterEncoder { public: static MasterEncoder* getInstance(); MasterEncoder(); ~MasterEncoder(); void setSignAndKey(char* sign, int signLen, unsigned char key); void decode(unsigned char* data, long size); void encode(unsigned char* data, long size); void writePDF(const string& filePath, unsigned char* data, long size); unsigned char* readPDF(const string& filepath, long& size); public: void encodePDF(const string& pdfPath, const string& savePath); void decodePDF(const string& pdfPath, const string& savePath); unsigned char* decode(const string& pdfPath, long& size); private: static MasterEncoder* _instance; static mutex _mtx; unsigned char _codeKey; char* _sign; int _signLen; }; #endif // !MasterEncoder
cpp文件:
#include "MasterEncoder.h" #include#include #include #include #include using namespace std; MasterEncoder* MasterEncoder::_instance = nullptr; mutex MasterEncoder::_mtx; MasterEncoder* MasterEncoder::getInstance() { if (_instance == nullptr) { _mtx.lock(); if (_instance == nullptr) _instance = new MasterEncoder(); _mtx.unlock(); } return _instance; } MasterEncoder::MasterEncoder() : _codeKey{100} , _sign{nullptr} , _signLen{0} { } MasterEncoder::~MasterEncoder() { } void MasterEncoder::setSignAndKey(char* sign, int signLen, unsigned char key) { if (sign && signLen) { _signLen = signLen; _sign = new char[signLen]; if (_sign) { memcpy(_sign, sign, signLen); } else { cout << "setSignAndKey Failed!n"; } } _codeKey = key; } void MasterEncoder::encode(unsigned char* data, long size) { for (long i = 0; i < size; i++) { data[i] ^= _codeKey; } } void MasterEncoder::decode(unsigned char* data, long size) { for (long i = 0; i < size; i++) { data[i] ^= _codeKey; } } unsigned char* MasterEncoder::readPDF(const string& filepath, long& size) { ifstream ifs; ifs.open(filepath, ios::in | ios::binary); if (!ifs.is_open()) { cout << "文件打开失败" << endl; return nullptr; } ifs.seekg(0, std::ios_base::end); size = ifs.tellg(); ifs.seekg(0, std::ios_base::beg); unsigned char* data = new unsigned char[size]; long count = 0; while (count < size) { ifs.read((char*)&data[count], sizeof(unsigned char)); //ifs >> data[count];//字符串模式 //printf("%c", data[count]); count++; } cout << "read PDF size = " << size << endl; ifs.close(); return data; } void MasterEncoder::writePDF(const string& filePath, unsigned char* data, long size) { ofstream ofs; ofs.open(filePath, ios::out | ios::trunc | ios::binary); if (!ofs.is_open()) { cout << "文件打开失败" << endl; return; } long count = 0; while (count < size) { //ofs << data[count]; ofs.write((char*)&data[count], sizeof(unsigned char)); count++; } cout << "save PDF size = " << size << endl; ofs.close(); } void MasterEncoder::encodePDF(const string& pdfPath, const string& savePath) { long size = 0; unsigned char* data = readPDF(pdfPath, size); //判断是否已经加密过 if (memcmp(data, _sign, _signLen) == 0) { cout << "加密过了, 直接保存!" << endl; writePDF(savePath, data, size); } else { encode(data, size); if (_signLen > 0) { unsigned char* p = new unsigned char[size + _signLen]; memcpy(p, _sign, _signLen); memcpy(p + _signLen, data, size); writePDF(savePath, p, size + _signLen); delete[] p; } else { writePDF(savePath, data, size); } } delete[] data; } void MasterEncoder::decodePDF(const string& pdfPath, const string& savePath) { long size = 0; unsigned char* data = readPDF(pdfPath, size); if (_signLen == 0) { decode(data, size); writePDF(savePath, data, size); } else if (memcmp(data, _sign, _signLen) == 0) { unsigned char* p = (data + _signLen); decode(p, size - _signLen); writePDF(savePath, p, size - _signLen); } else { cout << "解密失败!" << endl; } delete[] data; } unsigned char* MasterEncoder::decodePDF(const string& pdfPath, long& size) { unsigned char* data = readPDF(pdfPath, size); if (_signLen == 0) { decode(data, size); return data; } else if (memcmp(data, _sign, _signLen) == 0) { size -= _signLen; unsigned char* p = new unsigned char[size]; memcpy(p, data+_signLen, size); decode(p, size); delete[] data; return p; } else { return data; } }
代码中已经对多线程进行了处理,并且已使用sign来标识加密和解密文件。
使用的方式就非常简单了,主要是两个方法:
- encodePDF :MS::getInstance()->encodePDF(input, output);
- decodePDF :MS::getInstance()->decodePDF(input, output);
使用的示例如下:(提供了命令行的方式来操作,可以直接拿走使用)
#include4、方案改进#include #include "MasterEncoder.h" using namespace std; using MS = MasterEncoder; int main(int argc, char* argv[]) { system("COLOR 0A"); const char* sign = "master"; MS::getInstance()->setSignAndKey((char*)sign, strlen(sign), 100); char* input = nullptr; char* output = nullptr; printf("argc = %dn", argc); if (argc >= 2) { if (strcmp(argv[1], "-help") == 0) { printf(" -encode 加密n"); printf(" -decode 解密n"); printf(" -i 输入文件n"); printf(" -o 输出文件n"); printf(" example: main.exe -encode -i test.pdf -o test1.pdf n"); } if (strcmp(argv[1], "-encode") == 0) {//加密 if (strcmp(argv[2], "-i") == 0) input = argv[3]; if (strcmp(argv[4], "-o") == 0) output = argv[5]; if (input && output) MS::getInstance()->encodePDF(input, output); } else if (strcmp(argv[1], "-decode") == 0) {//解密 if (strcmp(argv[2], "-i") == 0) input = argv[3]; if (strcmp(argv[4], "-o") == 0) output = argv[5]; if(input && output) MS::getInstance()->decodePDF(input, output); } else { printf("请输入操作类型!"); } if (input == nullptr || output == nullptr) printf("请输入正确参数!"); } system("pause"); return 0; }
这里的方案我只针对我自己需要处理的PDF文件,所以很多东西没有考虑。例如文件大小等等,下面来一一解答。也欢迎大家留言讨论其他情况!
4.1 大文件的情况大文件(好几G的文件)可能导致超过内存大小,一次读到内存中可能会撑爆内存!
4.2 效率问题:解决方案:
每次读取若干个字节(例如1000个)进行加密处理,然后将处理完的结果保存到输出文件中,如此循环下去,直到所有文件处理结束!
上面的方案中,我对每个字符都进行了加密。如果文件过大或者对效率要求特别高,怎么提高效率呢?
其实这个没有标准答案,异或处理是非常非常简单的加密逻辑了,效率也是非常非常高的!如果你还行提升加密性能,那只能够在处理流程上下手!
解决方案:
隔若干个字符做一次加密,其他字符不进行加密处理。
例如每隔50个字符加密一个字符,当然,这个间隔字符的数量可以自行设定!甚至你可以设计一个取值公式来替代固定的间隔值。
此外,还有一个方案就是使用 《4.3节》 中的逻辑,同样可以提升效率!
这里我只是让PDF文件无法再被pdf阅读器打开,所以这个方案是可行的!
也就是咱们再讨论具体方案的时候一定要结合实际需求,抛开需求谈方案往往是不切实际的,甚至可能讨论出的方案最终无法满足需求!
4.3 安全问题:单个字符作为密钥比较容易破解,密钥最多只有2^8种,很容易通过枚举来破解!
4.4 无法区分文件是否已经加密解决方案:
使用多个字节来作为密钥,每次加密的时候一次加密多个字节即可。
例如密钥使用8个字节,这样会2^64种情况,每次加密处理的时候对8个字节进行加密操作!这样不仅更加安全,效率也会更高!
加密后的文件无法区分是否已经加密,这样你在进行解密的时候可能做的是加密操作,而加密操作可能是解密操作!
例如:加密的时候传入了已经加过密的文件,则输出的是解密文件,因为异或操作2次就能还原,所以加密和解密的逻辑是一模一样的。
所以,我们要明确知道你的文件是否已经加过密,否则,你做的操作可能是相反的!
这个标识逻辑我在代码中已经实现了,大家看看代码就能明白!
5、总结方案在设计的时候要考虑实际需求,例如我这里的需求只要求pdf文件加密后无法再打开。
还有一点就是加密的安全性的问题,例如我这里的加密密钥(_codeKey)是存在代码中的,这个是很容易被反编译破解的。
那么有没有必要再对加密逻辑这一块进行加固呢?这就完全取决于你的需求!这也不再本文讨论范围内,这里只是做一个提示!
如果您觉得文章有帮助到你,欢迎打赏!
您的支持是我不断创作的动力



