QT AES对字符串和文件的加密解密

QT AES对字符串和文件的加密解密

在Github和CSDN上找到过很多有关QT实现AES加密文件的博文,但是都没能解决我的问题。在各种踩坑,耗费一个星期之后,总算将这个问题搞清楚。本文将会详细介绍如何使用AES对称加密实现对字符串和文件的加密和解密,最后会奉献出亲测绝对可用的代码。

一、 AES加密算法介绍

很多博文介绍的QT自带的 base64 不能称为加密,它仅仅是一种任意二进制到文本字符串的编码方法。MD5、SHA等属于消息摘要算法,主要用于一致性验证,防止被篡改,像密码的加密是可以采用此类解密算法。它是没有密匙,也是不可逆的加密算法,显然不能用于对文件的加密和解密。

能用于加密和解密的算法分为对称加密和非对称加密两类。对称加密包括AES、DES等算法,非对称加密包含RSA、DSA、ECC等加密算法,本文讨论的AES加密算法就是一种堆成加密算法,其加密和解密的秘钥是相同的。

自己当时捯饬了半天的 base64 和 MD5,最后才发现他们并不能实现自己对文件加密和解密的需求,故特此解释一下。要是想深入了解AES加密的原理,可以查看下方链接。
AES加密算法的详细介绍与实现

二、 用AES加密算法实现对字符串的加密和解密

代码的注释是及其详细的,有不懂的地方可以多看几遍注释

1. 加密方法:OnAesEncrypt(LPVOID InBuffer,DWORD InLength,LPVOID OutBuffer)
//<---加密方法---->

DWORD AesTools::OnAesEncrypt(LPVOID InBuffer,DWORD InLength,LPVOID OutBuffer)
{
    //DWORD占4个字节
    DWORD OutLength=0,ExtraBytes = 0;
    if (m_lpAes==NULL||OutBuffer==NULL)
    {
        return 0;
    }

    //InBuffer是一个char类型的数组,lpCurInBuff指针指向的是InBuffer char数组的第一个元素的地址
    UCHAR *lpCurInBuff=(UCHAR *)InBuffer;
    //加密后的数据往OutBuffer中写入时,从第16个字节开始写。前面空出16个字节,用于存放密文大小和额外加密的字节个数,只用到8个字节,但为了保证字节总数是16的倍数,故预留了16个字节
    UCHAR *lpCurOutBuff=(UCHAR *)OutBuffer+16;
    long blocknum=InLength/16;
    long leftnum=InLength%16;
    for(long i=0;i<blocknum;i++)
    {
        //加密时,传入的数组必须时16个字节的
        m_lpAes->Cipher(lpCurInBuff,lpCurOutBuff);
        //每次加密16个字节,循环直至所有字节均被加密
        lpCurInBuff+=16;
        lpCurOutBuff+=16;
        OutLength+=16;
    }
    //当传入的密文的字节总数不是16的倍数时,会多余出leftnum 个字节。
    //此时,需要添加16-leftnum 个字节到mingwen中。则加密得到的密文,会多出16-leftnum 个字节。
    //这16-leftnum 个字节并不是mingwen里存在的
    if(leftnum)
    {
        UCHAR inbuff[16];
        memset(inbuff,'X',16);
        //经过上面的for循环,此时lpCurInBuff指针指向InBuffer数组的sizeof(InBuffer)-leftnum 位置处
        memcpy(inbuff,lpCurInBuff,leftnum);
        //此次解密,实际上是多加密了16-leftnum 个字节
        m_lpAes->Cipher(inbuff,lpCurOutBuff);
        lpCurOutBuff+=16;
        OutLength+=16;
    }
    UCHAR *bytesSize=(UCHAR *)OutBuffer;
    UCHAR *extraBytesSize = (UCHAR *)(OutBuffer+4);

    //多加密的字节个数
    ExtraBytes = (16-leftnum)%16;
    //将OutLength的地址复制给bytesSize的前4个字节。即将加密后的密文大小存放在miwen的前四个字节中
    memcpy(bytesSize,&OutLength,4);
    //将多加密的字节个数存放在第4-8个字节中
    memcpy(extraBytesSize,&ExtraBytes,4);

    //返回的OutLength只包括密文长度,不包括outBuffer中预留用来存放outBuffer字节个数和额外多加密的字节个数的16字节。
    //即OutLength = sizeof(OutBuffer)-16
    return OutLength;

}

函数名称:OnAesEncrypt
函数描述:用AES加密算法加密数据
调用参数:(具体使用见后面的调用测试代码)

  • LPVOID InBuffer:加密的明文,传参数时可以将char mingwen[]数组强转为LPVOID 类型。
  • DWORD InLength,:明文的实际大小
  • LPVOID OutBuffer:加密之后的密文

返回数值:加密后的数据大小 ,错误返回值 0

2. 解密方法:OnAesUncrypt(LPVOID InBuffer,DWORD InLength,LPVOID OutBuffer)
DWORD AesTools::OnAesUncrypt(LPVOID InBuffer,DWORD InLength,LPVOID OutBuffer)
{
    //传入的InLength大小是加密时返回的OutLength+16,即outBuffer的大小
    DWORD OutLength=0,ExtraBytes=0;
    if (m_lpAes==NULL||OutBuffer==NULL)
    {
        return 0;
    }
    //密文是从第16个字节开始的,故解密时,前16个字节忽略,直接从第16个字节开始解密
    UCHAR *lpCurInBuff=(UCHAR *)InBuffer+16;
    UCHAR *lpCurOutBuff=(UCHAR *)OutBuffer;
    long blocknum=InLength/16;
    long leftnum=InLength%16;
    if(leftnum){//传入的密文大小必须是16的整数倍个字节
                return 0;
    }

    //每次解密16个字节,循环全部解出
    for(long i=0;i<blocknum;i++)
    {
        m_lpAes->InvCipher(lpCurInBuff,lpCurOutBuff);
        lpCurInBuff+=16;
        lpCurOutBuff+=16;
        OutLength+=16;
    }

    //ExtraBytesSize指针指向的是InBuffer的第四个字节处
    UCHAR* ExtraBytesSize =(UCHAR *) InBuffer+4;
    //将InBuffer的第4-8个字节中的内容复制给ExtraBytes。此时ExtraBytes代表的是加密是额外加密的字节数
    memcpy(&ExtraBytes,ExtraBytesSize,4);
    //将额外加密的那部分内容,即ExtraBytes个字节的内容置为0
    memset(lpCurOutBuff-ExtraBytes,0,ExtraBytes);
    return (OutLength);

}

函数名称:OnAesUncrypt
函数描述:用AES解密算法解密数据
调用参数:(具体使用见后面的调用测试代码)

  • LPVOID InBuffer:待解密的密文,传参数时可以将char mingwen[]数组强转为LPVOID 类型。
  • DWORD InLength,:密文的实际大小
  • LPVOID OutBuffer:解密之后的明文

返回数值:加密后的数据大小 ,错误返回值 0

3. 对上述代码做一些说明
  • 代码中的DWORD、LPVOID等类型,是在程序中自己定义的。
typedef unsigned long DWORD;
typedef unsigned char UCHAR,*PUCHAR;
typedef void *PVOID,*LPVOID;
typedef unsigned char byte;
typedef DWORD *PDWORD,*LPDWORD;
  • sizeof()和strllen()方法的区别
    这是自己跳的坑。sizeof()获取到的是数组的大小,而strlen()方法获取到的是有效字符的大小。假如你定义了一个大小为1024字节的字符数组char mingwen[1024] = “abcdefghijk” ,那么 sizeof(mingwen) = 1024 , 而 strlen(minwen) =11 . 我们都知道字符串最后是有一个“\0”字符的,strlen() 方法遇到该字符就会结束,所以当你字符串中间有“\0”字符时候,统计就会出错。

  • char * 和char charArray[] 的区别
    char指针指向的是字符数组的首元素的地址,即 charArray[0] 的地址。使用的时候一定要注意,请一定点击下方链接,仔细研究一下他们的异同点。这对于你理解代码至关重要。

    char* 和 char charArray[]的区别

  • 字符串转char 数组

4. QSring 转 char charArray[]

遇到QString类型明文时候,就涉及到QString类型的数据转换为char数组类型。这里提供一种方法,供参考。

//<---QString 转为 char数组---->

char* aesTool::QString2CharArray(QString str, int size){   
    char* arr;   
    arr = new char[size];    
    QByteArray byteArray=str.toUtf8();   
    memcpy(arr,byteArray.data(),strlen(byteArray)+1);  
    return arr;
}
//因为char 数组不是数据类型,不能作为函数的返回值,故这里函数的返回值是 char*

// 调用函数后,再将char* p 转换为 char charArray[] 。  内存拷贝实现
char * p  = "ahfag"
char charArray [] = "";
memcpy(charArray, p ,strlen(p)+1)



//<--- char数组 转为 QString ---->

QString aesTool::CharArray2QString(char arr[], int size){ 
    QString str(arr) ;  
    return str;
}

三、文件的加密解密

文件的加密和解密是在字符数组加密解密成功的基础上的

1. 打开文件和保存文件

注意: 关于文件的读存,一定要注意以下几个方面。都是自己跳过的坑

  • 直接读取,不要使用QTextStream 每次读取一行的方式。有读入额外换行符的风险
  • 不要将读出的文件内容先存在QString类型变量中,然后再将QString类型的明文转化为char 数组。首先,这样很繁琐。其次,因为涉及到编码方式的不同,中文等问题,字符在转换的过程中,极易出现乱码的问题。
  • 多次跳坑后发现,将文件的读存全部以二进制数组的方式进行。读出的数据直接放到二进制数组里面。存的密文,也存为二进制数组,能够完美避开编码方式,以及中文乱码等问题。
//打开文件,将文件中的内容返回一个QbyteArray的数组
QByteArray AesTools::OpenFile(QString fileName){
    QFile file(fileName);
    file.open(( QIODevice::ReadWrite));
    QByteArray temp = file.read(file.bytesAvailable());
    file.close();
    return temp;
}
//将一个QbyteArray数组写入到指定文件中去。
//使用二进制数组进行文件的读写能够有效避免各种由于编码格式和类型转换造成的问题
void AesTools::WriteFile(QString fileName, QByteArray data){
    QFile file(fileName);
    file.open(( QIODevice::ReadWrite|QIODevice::Truncate));
    file.write(data);
    file.close();
    return ;
}
2. 文件加密(保证加密和解密的秘钥一致)
void AesTools::FileEncryptor(QString inFileName,QString outFileName) {

    QByteArray temp = OpenFile(inFileName);
    qDebug()<<"temp:"<<temp;
    char mingwen[1024] ;
    //将temp字节数组中的所有数据复制给mingwen数组
    memcpy(mingwen,temp.data(),temp.size());
    DWORD size = strlen(mingwen);
    qDebug()<<"size:"<<size;
    char miwen[1024]={0};
    UCHAR key[1024] = "xyz";
    UCHAR *p = key;
    InitializePrivateKey(16, p); //进行初始化

    OnAesEncrypt((LPVOID)mingwen, strlen(mingwen), (LPVOID)miwen); //进行加密
    qDebug()<<miwen;
    QByteArray miwenData;
    DWORD byteSize = 0;
    //将密文的前四个字符复制给bytesize的地址
    memcpy(&byteSize,miwen,4);
    qDebug()<<"bytesize:"<<byteSize;
    miwenData.resize(byteSize+16);
    //将密文的前byteSize+16个字符复制给miwenDate
    memcpy(miwenData.data(),miwen,byteSize+16);

    WriteFile(outFileName,miwenData);

    return ;
}
3. 文件解密(保证加密和解密的秘钥一致)
void AesTools::FileDecryptor(QString inFileName,QString outFileName){

    QByteArray temp = OpenFile(inFileName);

    char miwen[1024]={0};
    char jiemi[1024]={0};
    //将temp字节数组中的所有数据复制给miwen char类型数组
    memcpy(miwen,temp.data(),temp.size());
    DWORD byteSize = 0;
    //miwen的大小存放在miwen的前四个字节中,将miwen大小赋值给byteSize
    memcpy(&byteSize,miwen,4);
    UCHAR key[1024] = "xyz";
    UCHAR *p = key;
    InitializePrivateKey(16, p); //进行初始化
    OnAesUncrypt((LPVOID)miwen, (DWORD)byteSize,(LPVOID)jiemi); //进行解密

    //解密结果写入文件中
    WriteFile(outFileName,jiemi);

    return ;
}
4. 测试代码
#include <QCoreApplication>
#include <aestools.h>
#include <qdebug.h>
#include <iostream>
#include <fstream>
#include <QFile>
#include <cmath>
#include <iomanip>
using namespace std;
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    //创建对象
    AesTools *aes = new AesTools;
    //设置密钥,保证加密和解密的密钥一致
    UCHAR key[1024] = "xyz";
    UCHAR *p = key;
    DWORD keySize = strlen((char *)key);
    //进行密钥初始化
    aes->InitializePrivateKey(keySize, p);


    //文件的加密和解密。给定的是相对路径,也可以直接给绝对路径。
    aes->FileEncryptor("./project/test.txt","./project/test_encrypt.txt");
    aes->FileDecryptor("./project/test_encrypt.txt","./project/test_decrypt.txt");

    //字符数组的加密和解密
    //数组的大小可以自己设定,只要是16的倍数即可,而且要保证mingwen、miwen、jiemi的大小一致
    char mingwen[1024] ="hdgfgghgjf中国文字方式结构化kgkhytytrytryhgjdf";
    char miwen[1024]={0};
    char jiemi[1024]={0};
    //加密,返回值是密文的大小
    DWORD miwenLength = aes->OnAesEncrypt((LPVOID)mingwen, strlen(mingwen), (LPVOID)miwen);
    //解密
    QByteArray miwenData;
    DWORD byteSize = 0;
    memcpy(&byteSize,miwen,4);
    //byteSize即为存储的密文大小,满足byteSize=miwenLength。
    DWORD jiemiLength =aes->OnAesUncrypt((LPVOID)miwen, byteSize, (LPVOID)jiemi);

    /*
        字符数组和文件的加密解密上面代码已经完成,以下代码是为了能够看到密文的打印效果。
        1、直接打印"miwen"到控制台,无法看到密文
        2、使用 strlen(miwen) 方法也无法在控制台得到正确的密文大小
        3、皆是因为密文的前十六个字节中存放的是两个 int 类型的数,其包含有字符"\0"
    */
    DWORD size = strlen(mingwen);
    qDebug()<<"size:"<<size;
    qDebug()<<"miwenLength:"<<miwenLength;
    qDebug()<<"miwen:"<<miwen;
    //打印密文到控制台看不到密文,这是因为密文的前十六个字节存放的是密文的字节个数,其包含有字符"\0",qDebug遇到该字符自动结束打印
    qDebug()<<"miwen.size:"<<strlen(miwen);
    //打印结果为1,因为strlen()方法统计字符个数时,遇到字符"\0"就结束。
    qDebug()<<"byteSize:"<<byteSize;
    miwenData.resize(byteSize+16);
    //打印密文的第16个字节以后的内容
    memcpy(miwenData.data(),miwen+16,byteSize);
    qDebug()<<"miwenData:"<<miwenData;

    qDebug()<<"jiemi:"<<jiemi;
    qDebug()<<"jiemiLength:"<<jiemiLength;

    //对象释放
    free(aes);
    aes = 0;

    return 0;

}



4. 测试结果

运行结果

加密字符数组的测试结果。可以看到

四、Github源码地址

https://github.com/rclhub/QT-AES-EncryptFile

本文介绍的方法比较简单,适合初学者理解。如有问题,欢迎在评论区讨论。

猜你喜欢

转载自blog.csdn.net/Jungle_Rao/article/details/89214397