
简介这份Qt AES加解密资源包为开发者提供了一套基于Crypto库的完整实现示例。由于Qt本身不内置AES对称加密资料恰好补齐了这一短板面向需要在桌面、移动或嵌入式应用中增加数据安全功能的初中级Qt程序员。压缩包含5个文件以2个cpp源码、1个头文件、1个pro工程文件和1个user配置文件组成体积仅10KB轻量而完整。已有1477人学习下载。代码围绕CBC模式展开涵盖密钥与初始化向量的准备、加密器/解密器创建、流式转换处理等环节同时提示了内存管理、错误处理及密钥安全存储等实战要点可按需扩展文件读写与密钥生成能力助力快速落地一套可用的AES加解密方案。1. 用 Qt 写 AES 加密绕不开的几个坑与一条捷径做 Qt 客户端开发的朋友迟早会遇到这么个需求把本地配置文件里的账号密码、接口返回的敏感字段或者要上传的日志文件做一层加密。很多人第一反应是搜“Qt AES 加密”然后被一堆过时的QCryptographicHash文档带偏——那玩意儿只做哈希做不了加解密。真正落地的方案要么引入 OpenSSL 动态库要么用一个叫QAESEncryption的纯 Qt 实现。这个项目就是后者不依赖任何第三方动态库纯 C 加 Qt 原生类实现 AES-128/192/256 的 ECB 和 CBC 模式加解密字符串、文件都能处理。适合谁正在做 Qt 桌面应用、需要给配置文件或本地数据加密的开发者以及想搞懂 AES 在 Qt 里到底怎么组织密钥和初始化向量的人。我拆完这份源码的感受是代码量不大但密钥处理、填充逻辑、ByteArray 转字符串这三处全是血泪点。2. 技术选型与代码结构为什么不用 OpenSSL而选纯 Qt 实现2.1 依赖对比QAESEncryption 和 OpenSSL / QCA 的真实差距在正式拆代码之前先讲清楚这个项目为什么值得用。很多 Qt 项目最终选择 OpenSSL是因为它在服务端和跨语言场景下是事实标准。但在纯 Qt 客户端项目里引入 OpenSSL 意味着你要处理动态库的版本匹配Windows 下是 libcrypto-1_1.dll 和 libssl-1_1.dll 的位数、版本号要处理不同系统下的库搜索路径还要在发布时额外打包。而 QAESEncryption 是单头文件加单源文件的实现全部依赖 Qt 的QByteArray和QString编译出来没有任何额外动态库。对于本地数据加密这种场景它的性能完全够用——加密一个几 MB 的配置文件耗时在毫秒级。从架构上看这个项目的核心是一个QAESEncryption类它把 AES 的密钥扩展、轮加密、填充处理全部封装在内部。外部调用者只需要关心三件事AES 位数128/192/256、分组模式ECB/CBC、以及填充方式。这份源码我看到的实现里填充默认走 PKCS7这是 OpenSSL 默认也是绝大多数在线加密工具使用的填充方式所以你的 Qt 程序加密的数据拿 OpenSSL 命令行的enc工具能直接解反过来也成立。这一点在跨语言联调比如 Qt 客户端加密、Java 服务端解密时特别重要。2.2 目录结构与核心类职责整个项目结构很清爽不是那种动辄几十个文件的工程。核心部分集中在qaesencryption.h和qaesencryption.cpp两个文件里外加一个演示用的窗口类。我拆的时候把源码按职责列了一张表方便你对照着读。文件职责关键内容qaesencryption.h对外接口定义AES_128/192/256枚举、MODE_ECB/CBC枚举、Encode/Decode静态方法qaesencryption.cppAES 算法实现密钥扩展(KeyExpansion)、轮加密(EncryptBlock)、轮解密(DecryptBlock)、填充处理mainwindow.或 demo 窗口演示调用文本框输入、密钥输入、加密结果展示、Base64 输出这个结构最大的好处是算法实现和业务界面完全解耦。你要把它集成到自己项目里只需要把qaesencryption.h和qaesencryption.cpp两个文件拷过去然后在你的类里 include 头文件就能直接用。不像某些项目把算法和界面揉在一起改个 UI 要连带看半天加密逻辑。2.3 从源码看 AES 的实现思路打开qaesencryption.cpp首先能看到的是 S 盒S-Box和逆 S 盒的静态数组这是 AES 算法的根基。KeyExpansion函数负责把用户输入的原始密钥扩展成 11/13/15 轮子密钥分别对应 128/192/256 位。真正核心的EncryptBlock内部执行四步操作SubBytes字节代换查 S 盒、ShiftRows行位移、MixColumns列混合、AddRoundKey轮密钥加。值得留意的实现细节是这个库对输入数据的处理是分块的。AES 固定把数据切成 16 字节一块处理如果你要加密的数据不是 16 的倍数填充逻辑就起作用了。我看了它的填充代码PKCS7 的实现方式是缺几个字节就补几个值为缺失数量的字节。比如数据差 5 个字节满 16就补 5 个0x05。解密的时候根据最后一个字节的值把对应数量的填充字节删掉还原出原始数据。这段逻辑虽然不长但很容易写错——常见的翻车是解密后尾部多出\x01之类的乱码多半是填充没去除干净。3. 核心模块拆解加解密接口的调用链与参数详解3.1 加密一条字符串的完整链路先看最常用的字符串加密场景。以下是基于这个库的典型调用方式我加上了每一步的注释方便你对照理解。#include qaesencryption.h #include QDebug #include QTextCodec // 选择一个 AES-128 位、CBC 模式的加密器 QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::CBC); // 原始明文注意这里必须用 UTF-8 编码拿到字节序列 QString plainText Hello, Qt AES! 你好世界; QByteArray plainArray plainText.toUtf8(); // 密钥一定要保证是 16 字节AES-128 对应 16 字节密钥AES-192 是 24AES-256 是 32 QString keyString mySecretKey12345; // 16 个 ASCII 字符正好 16 字节 QByteArray keyArray keyString.toUtf8(); // IV初始化向量CBC 模式必须提供和密钥长度一致ECB 模式不需要 QByteArray ivArray QByteArray::fromHex(000102030405060708090a0b0c0d0e0f); // 执行加密 QByteArray encryptedArray encryption.encode(plainArray, keyArray, ivArray); // 加密结果是二进制数据通常用 Base64 编码后再传输或存储 QString encryptedBase64 encryptedArray.toBase64(); qDebug() Encrypted (Base64): encryptedBase64;这段代码逻辑很直接先把 QString 转成 UTF-8 字节序列这是处理中文的关键一步然后用指定的密钥和 IV 调用encode最后把二进制结果转成 Base64 字符串。这里encode函数内部做的是字节填充PKCS7→ 分块加密 → 返回密文。你传进去的plainArray并不会被修改返回的是一个新的QByteArray。参数方面有几个容易踩的雷我拆的时候专门验证过第一密钥字节长度必须和 AES 位数严格对应AES-128 用 16 字节密钥AES-192 用 24 字节AES-256 用 32 字节。如果密钥长度不对有的实现会静默截断或补零但这份源码的行为是直接断言报错。第二CBC 模式的 IV 长度必须等于 16 字节一个分组大小不能用密钥长度。第三Base64 编码不是加密的一部分只是为了把二进制变成可打印字符串如果你要直接存二进制文件可以跳过最后一步。3.2 解密操作把密文还原成原始 UTF-8 字符串解密是加密的逆过程但有几个细节和加密不对称特别容易翻车。// 使用与加密完全相同的参数初始化解密器 QAESEncryption decryption(QAESEncryption::AES_128, QAESEncryption::CBC); // 输入刚才得到的 Base64 密文 QString encryptedBase64 oXk6sY8d9zE4VFhqn2LkSA; QByteArray encryptedArray QByteArray::fromBase64(encryptedBase64.toUtf8()); // 密钥和 IV 必须和加密时完全一致 QString keyString mySecretKey12345; QByteArray keyArray keyString.toUtf8(); QByteArray ivArray QByteArray::fromHex(000102030405060708090a0b0c0d0e0f); // 执行解密 QByteArray decryptedArray decryption.decode(encryptedArray, keyArray, ivArray); // 关键一步解密后的字节数组去掉 PKCS7 填充后才是原始 UTF-8 数据 QByteArray originalArray decryption.removePadding(decryptedArray); // 还原成 QString 时必须显式指定 UTF-8 解码否则中文会乱码 QString originalText QString::fromUtf8(originalArray); qDebug() Decrypted: originalText;你在用这个库时最容易漏掉的就是removePadding这一步。我见过不少开发者解密后直接拿QString(decryptedArray)去构造字符串结果发现末尾多出一些奇怪的字符或者中文直接变成乱码。原因是解密出来的原始字节末尾还带着 PKCS7 填充的标记字节必须调用removePadding去掉。另一个细节是fromUtf8的解码方式只要加密前用的是toUtf8解密后就必须用fromUtf8还原不能想当然地用QString::fromLocal8Bit否则在 Windows 中文系统下必现乱码。3.3 文件加密的处理方式和内存边界字符串加解密只是基础实际项目里经常要加密文件比如配置文件、日志文件、或者某个私有格式的资源文件。用这个库做文件加密的思路是读取文件字节 → 加密 → 写回新文件。#include QFile // 读取原始文件 QFile inFile(D:/project/config.ini); if (!inFile.open(QIODevice::ReadOnly)) { qDebug() Failed to open input file; return; } QByteArray fileData inFile.readAll(); inFile.close(); // 加密 QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC); QString keyString my256bitKey....32Bytes; // 32 字节密钥 QByteArray keyArray keyString.toUtf8(); QByteArray ivArray QByteArray::fromHex(000102030405060708090a0b0c0d0e0f); QByteArray encryptedData encryption.encode(fileData, keyArray, ivArray); // 写加密后的文件可以加个自定义后缀标识 QFile outFile(D:/project/config.ini.enc); if (!outFile.open(QIODevice::WriteOnly)) { qDebug() Failed to open output file; return; } outFile.write(encryptedData); outFile.close();这里要提醒一个实际的坑inFile.readAll()会一次性把整个文件读入内存。如果文件只有几 MB没问题但如果加密的是几百 MB 的视频或数据库文件这种写法会直接内存暴涨甚至崩溃。我一般会建议大于 50 MB 的文件别用这个库的整体加密方式改成自定义分段加密——每次读 16 字节的整数倍块逐块调用encode的内部块加密逻辑或者干脆换 OpenSSL 的 EVP 接口做流式加密。这个项目的定位是轻量级本地数据加密不是大文件加密工具这点你得心里有数。4. 运行环境配置与避坑指南从编译到联调的一次完整记录4.1 从源码到跑起来的三个步骤拿到这份源码第一步不是看代码而是先把工程跑起来。我用 Qt 5.15.2 MinGW 64 位环境试过整个编译过程非常顺畅。# 如果你拿到的是 .pro 工程文件直接用 qmake 构建 qmake AESDemo.pro mingw32-make -j8 # 如果是 CMake 工程 cmake -B build -DCMAKE_PREFIX_PATH/path/to/Qt/5.15.2/mingw81_64 cmake --build build --config Release构建完成后运行程序界面上的操作很简单在“明文”输入框输入你要加密的内容在“密钥”输入框输入 16/24/32 字节的密钥选择 AES 位数和模式点“加密”按钮得到 Base64 字符串把密文粘贴到“密文”输入框填好同样的密钥和模式点“解密”按钮还原原文。建议你拿到代码后先跑通这一步对encode和decode的输入输出建立起直观感受再往下读源码。4.2 避坑记录五条真实踩过的坑坑一解密结果尾部出现乱码字符。现象用QString::fromUtf8(decryptedArray)还原后字符串末尾多了几个奇怪的符号或者\x01之类的控制字符。原因decode函数返回的字节数组还带着 PKCS7 填充字节没有调用removePadding清理。解决在decode之后、转 QString 之前必须调用removePadding方法。坑二中文内容加密后解密变成“锟斤拷”或问号。现象明文里有中文加密解密后中文变成???或者锟斤拷。原因QString 转 QByteArray 时用了toLatin1或toLocal8Bit中文字符在这些编码里无法完整表达字节已经丢失。解决统一用toUtf8和fromUtf8成对出现中间不要混用其他编码。坑三密钥长度不对导致程序直接崩溃或断言失败。现象运行到encode或decode调用时程序中断控制台提示assert key length error之类的信息。原因AES-128 要求 16 字节密钥AES-192 要求 24 字节AES-256 要求 32 字节传入的密钥字符串长度不匹配。解决写一个密钥长度校验函数在调用加密前检查keyArray.size()是否等于期望长度。常见做法是如果用户输入的密钥不够长用哈希值扩展如果太长截断。坑四用 ECB 模式加密相同的明文块得到了相同的密文块。现象加密一幅简单的图片或一段重复内容多的文本Base64 结果里能看到重复的模式。原因ECB 模式下每个分组独立加密相同明文产生相同密文这既是特性也是弱点。解决所有实际业务场景改用 CBC 模式如果坚持用 ECB必须在文档里注明风险且不要加密规律性强的数据。我在拆这个项目时看到默认演示用的是 ECB但实际接入时我建议你直接切 CBC。坑五拿 Qt 加密的结果给 Java 或 Python 解密怎么都对不上。现象Qt 端用 AES-128-CBC 加密Java 端用同样参数解密结果是乱码或异常。原因大多数情况下是 IV 和填充方式不一致。Java 默认的 AES/CBC/PKCS5Padding 其实等同于 PKCS7但很多人在 Java 端忘了指定 IV或者 IV 的字节序处理不一致。解决跨语言联调时写清楚密钥的 Hex 字符串、IV 的 Hex 字符串、填充模式这三个参数双方逐字节核对 IV 的十六进制表示。4.3 用 OpenSSL 命令验证你的加解密是否正确这是我最推荐的一个验证手段能快速确认你的 Qt 代码加密出的数据到底标不标准。# 用 OpenSSL 解密 Qt 加密出的 Base64 字符串 # 假设你的 Base64 存在文件 cipher.txt 中 base64 -d cipher.txt cipher.bin openssl enc -d -aes-128-cbc -K 6d795365637265744b65793132333435 \ -iv 000102030405060708090a0b0c0d0e0f \ -in cipher.bin -out plain.txt -nopad # 注意-nopad 表示 OpenSSL 不自动去掉填充因为 Qt 端已经去掉了第一次联调时先用简单的英文短句测试确认整条链路通了之后再上中文和文件。这个命令行的核验方式能帮你把问题和“Qt 代码写错”还是“参数理解错”立刻区分开省去很多查文档的时间。5. 二次开发把硬编码密钥变成可配置密钥管理器项目本身跑通只是第一步要把它用到实际产品里你首先会面对一个问题密钥写死在代码里被反编译怎么办Qt 程序用 windeployqt 打包发布后PE 文件里的字符串常量是可以被 IDA 或 x64dbg 直接扒出来的。这里讲一个我自己常用的改造方向把密钥存储和派生逻辑单独抽出来做成一个可配置的密钥管理模块。5.1 用口令派生密钥而不是直接存密钥与其把 16 字节密钥字符串写在源码里不如让用户输入一个口令然后用哈希算法把口令扩展成固定长度的密钥。一个合格的做法是使用 PBKDF2 或至少是加盐的多次哈希。Qt 自带的QPasswordDigestor类提供了 PBKDF2 支持不需要额外引库。#include QPasswordDigestor #include QCryptographicHash // 用户输入的口令 QString userPassphrase user-input-passphrase; // 盐值可以存到配置里不需要保密 QByteArray salt QByteArray::fromHex(a1b2c3d4e5f60718293a4b5c6d7e8f90); int iterations 10000; // 派生 32 字节密钥对应 AES-256 QByteArray derivedKey QPasswordDigestor::deriveKeyPbkdf2( QCryptographicHash::Sha256, userPassphrase.toUtf8(), salt, iterations, 32 ); // 用派生密钥去做 AES-256-CBC 加解密 QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC); QByteArray encrypted encryption.encode(plainData, derivedKey, ivArray);这样改造之后源码里只保留盐值和迭代次数真正的密钥完全由用户口令决定。即使反编译拿到盐值没有口令也推不出密钥。迭代次数 10000 是基准值如果追求更高安全性可以加到 100000代价是每次加解密会慢几十毫秒对本地配置文件来说体验无感。5.2 把加密抽象成接口支持切换不同算法第二个值得做的改造是抽象加解密接口别让业务代码直接依赖QAESEncryption的静态方法。你可以定义一个ICryptoProvider抽象类让AesCryptoProvider和后续可能加的Sm4CryptoProvider都实现同一个接口。class ICryptoProvider { public: virtual ~ICryptoProvider() {} virtual QByteArray encrypt(const QByteArray plainData, const QByteArray key, const QByteArray iv) 0; virtual QByteArray decrypt(const QByteArray cipherData, const QByteArray key, const QByteArray iv) 0; };业务层只依赖ICryptoProvider指针具体用 AES 还是国密 SM4由配置文件决定。这样做的好处是将来如果客户要求替换加密算法只需要新增一个类业务代码零改动。我自己的项目里就是用这种策略同时支持了 AES-256-CBC 和 SM4-ECB切换只需要改一行配置。5.3 验证改造是否正确的两个习惯改了之后怎么验证我每次改完加密模块都会强制做一遍双向验证先用 Qt 工程里的测试用例跑一遍原文到密文再到原文的回路再把密文用 OpenSSL 命令行解一次确认两边结果一致。第二个习惯是把加解密模块做成独立的命令行工具输入一个文件路径和口令输出加密或解密结果方便集成到 CI 脚本里做自动化回归。从那以后我每次提交加密相关代码之前都强制走一遍 OpenSSL 交叉验证和边界用例测试空字符串、1 字节数据、正好 16 字节、超大文件分段确保没有填充错误和长度问题。加密这种东西错一个字节就是全盘乱码与其上线后查日志不如把验证做在提交前。希望帮到你。本文还有配套的精品资源点击获取