Delphi XE10.2集成TMS Cryptography Pack:AES/RSA加密实战指南

Delphi XE10.2集成TMS Cryptography Pack:AES/RSA加密实战指南 简介这是一套面向Delphi开发者的加密组件库版本3.4.0.1针对XE10.2编译器优化适合需要在桌面、移动端项目中集成数据加密与安全通信的开发者。组件覆盖AES、DES、3DES、Blowfish、Twofish、RC4、RSA等对称与非对称算法同时提供MD5、SHA系列哈希、HMAC消息认证码、数字签名、X.509证书管理以及PKCS#7兼容接口基本满足常见的信息安全需求。资源包共210个文件体积约71.54MB以编译单元dcu/o、静态库a、组件包dcp/bpl、位图与资源文件bmp/res为主另含PDF说明文档与演示程序便于查阅和上手。目前已吸引267人下载学习。对希望在Delphi XE10.2环境中快速获得可靠加密方案的开发者来说这套组件能显著缩短功能实现周期并可通过清晰的包结构和示例资源快速集成到现有项目。 做Delphi开发的朋友应该都有过这种体会项目里一旦涉及数据加密事情就变得不那么让人省心。Delphi自带的加密能力零散不说跨平台支持也让人头疼Win32上能跑的代码到了Android或iOS上各种不兼容。所以当我在XE10.2RAD Studio 10.2 Tokyo上拿到TMS_Cryptography_Pack_3.4.0.1这个组件包时第一反应是总算能把加解密这件事做成一个统一方案了。TMS Cryptography Pack是一个专门面向Delphi和C Builder的加密组件库目标就是用一套API搞定常见的对称加密、非对称加密、哈希计算、数字签名、随机数生成这些活儿。它特别适合在Win32、Win64以及移动端Android/iOS上做统一加密逻辑的项目尤其是那些需要给C/S架构、文件传输、本地配置文件做安全处理的朋友。这篇文章我尽量把安装、集成、实战和踩坑都写透照着做就能在XE10.2里把加密能力真正跑起来。1. 为什么项目里要专门引入一个加密组件包1.1 Delphi自带的加密能力远远不够用先说个现实问题如果你直接在XE10.2里用System.NetEncoding或者调用Windows CryptAPI这些底层接口确实能实现一部分加密需求但离“开箱即用”差得很远。自带的方案往往只覆盖某类算法或者在不同平台上行为不一致。比如在Windows上能用DPAPI保护的密钥换到Android上就完全不存在再比如Win32下用ANSI字符串做哈希到了移动端由于默认字符集不同结果直接对不上。项目一旦上了规模这些问题就会集中爆发。你不可能为每个平台写一套加密逻辑那样维护成本太高了。TMS Cryptography Pack这种第三方库的价值在于它能把这些算法封装成统一的Delphi接口底层不管是调用操作系统的加密API还是自己实现对外暴露的逻辑都是一样的换平台不用改业务代码。1.2 组件包装了哪些核心能力从功能覆盖上看这个包基本满足了一个业务系统90%的加密需求对称加密AES、DES、3DES、Blowfish常用的模式ECB、CBC、CFB等和填充方式基本都有。非对称加密RSA密钥生成、加密、解密、签名、验签。哈希算法MD5、SHA-1、SHA-256、SHA-512等不管做文件校验还是口令哈希都够用。辅助能力随机数生成、Base64编解码以及在Delphi对象和字节数组之间的转换工具。这些功能在项目里最常见的使用场景有几个。第一是接口通信前后端交互的关键字段用AES加密后用Base64传输第二是文件防篡改发布文件时附带一个SHA-256摘要客户端下载后对比一下就知道有没有被动过第三是口令安全用户注册登录时不做明文存储而是加盐哈希之后入库就算数据库泄露了原始口令也不会直接暴露。1.3 为什么建议选3.4.0.1这个版本版本选择上我多说一句。TMS系列组件更新频率其实不低但并不是越新越好。比如TMS Cryptography Pack后续版本的API有过调整如果项目已经稳定运行升级组件比升级业务代码还麻烦。3.4.0.1配XE10.2是一套比较成熟的组合网上能找到的案例多出了问题也容易排查。如果项目还在早期也可以考虑新版但一定要在测试环境里把旧代码全部回归一遍。2. 安装与集成在XE10.2上把包跑起来2.1 安装前先看包内结构别急着点安装按钮解压安装包之后很多朋友习惯直接找dpk文件一顿点。我建议先花五分钟把目录结构看清楚。通常你会看到packages目录存放各版本Delphi对应的包工程文件文件名里一般会带版本号后缀比如d10_2代表支持10.2。lib目录编译好的DCU文件或者需要你自己编译的源文件。source目录核心源码建议保留调试时能进去看实现细节。docs目录帮助文档和API说明别忽略它查参数比看博客快多了。看清楚结构之后再确认一个关键点这个包是要手动编译安装还是直接引入预编译的DCU。预编译版本省事但一旦你换了Delphi补丁版本DCU可能不匹配必须重新编译。2.2 在IDE中安装组件的标准步骤我以手动编译安装的典型流程为例。打开XE10.2后执行以下操作点击File Open Project打开packages里对应的运行时包工程例如TMS_Crypto_D10_2.dpk先Compile然后Install。安装完运行时包再打开设计期包例如TMS_Crypto_D10_2_Dsgn.dpk同样执行编译和安装。安装完成后打开Tools Options Environment Options Delphi Options Library Library Path把组件的lib目录和source目录添加进去同时确认Win32和Win64平台都配置了对应的路径。关闭IDE重新打开新建一个VCL或FMX项目在组件面板上如果能看到TMS Crypto相关的组件说明安装成功。需要特别注意的是如果项目要用到Win64编译必须把64位平台下的Library Path也配置好否则编译时找不到DCU。这个问题特别容易在第一次切换平台时踩到编译报错信息往往是Unit not found。2.3 安装失败最常见的原因和处理办法我从身边的同事和网上的反馈里总结了一下安装失败多半是这几个原因症状原因解决办法提示某个dcu文件无法找到Library Path没有配置全把编译后的DCU目录按平台分别添加到Library Path提示Cannot load package设计期包和运行时包版本不匹配两个包必须都重新编译然后同时安装提示某个单元名冲突其他组件包里也有同名文件调整Library Path顺序让TMS的目录排在前面安装时编译报错Delphi版本和包文件不匹配确认你打开的是d10_2对应的dpk工程而不是其他版本的如果实在装不上还有一个办法不注册设计期组件只引用运行期DCU。直接在项目的Search Path里指向lib目录代码里uses需要的单元就能正常编译。这种方式虽然组件面板上看不到控件但加密功能本身全部可用适合不需要可视化拖拽的场景。3. 实战一用AES把敏感数据加密成密文3.1 对称加密选型AES-256-CBC是稳妥起点对称加密里我优先推荐AES。算法本身经过充分验证性能也不错。密钥长度选256位模式选CBC填充方式选PKCS7这套组合基本是业务系统里最常见的配置。为什么要特别强调模式和填充方式因为很多初学者只关心密钥忽略模式、IV和填充最终导致“我在Windows上加密的数据Linux上解不开”这种问题。CBC模式需要初始向量IVIV不需要保密但每次加密都应该随机生成否则相同明文会得到相同密文很容易被攻击者做模式分析。填充方式要确保明文长度不是块大小AES是16字节的整数倍时能补齐到最后一块。我把一个适合落地使用的字符串AES加解密函数写在这里。接口名称在不同版本的包里可能略有差异但调用逻辑是一致的uses System.SysUtils, System.NetEncoding, System.Types, TMS.Crypto.Core, TMS.Crypto.AES; // 加密返回 Base64(IV 密文) function AESEncryptString(const PlainText, Password: string): string; var Cipher: TAESCipher; Key, IV, PlainBytes, CipherBytes: TBytes; begin Cipher : TAESCipher.Create; try // 口令通过SHA-256转换成32字节的AES-256密钥 Key : THashSHA256.GetBytes(TEncoding.UTF8.GetBytes(Password)); // 每次随机生成16字节IV IV : TRandom.GenerateBytes(16); PlainBytes : TEncoding.UTF8.GetBytes(PlainText); CipherBytes : Cipher.EncryptBytes(PlainBytes, Key, cmCBC, pmPKCS7, IV); Result : TNetEncoding.Base64.EncodeBytesToString( TEncoding.UTF8.GetBytes( TEncoding.UTF8.GetString(IV) TEncoding.UTF8.GetString(CipherBytes) ) ); finally Cipher.Free; end; end;这里一个很容易犯错的地方把IV和密文拼接时不要用字符串拼接而是直接用字节数组拼接。上面的写法是先用Base64统一编码了IV和密文虽然可用但更规范的做法是把IV CipherBytes拼成字节数组再Base64。我在项目里就是这么做的function AESEncryptString(const PlainText, Password: string): string; var Cipher: TAESCipher; Key, IV, PlainBytes, CipherBytes, Combined: TBytes; begin Cipher : TAESCipher.Create; try Key : THashSHA256.GetBytes(TEncoding.UTF8.GetBytes(Password)); IV : TRandom.GenerateBytes(16); PlainBytes : TEncoding.UTF8.GetBytes(PlainText); CipherBytes : Cipher.EncryptBytes(PlainBytes, Key, cmCBC, pmPKCS7, IV); SetLength(Combined, Length(IV) Length(CipherBytes)); Move(IV[0], Combined[0], Length(IV)); Move(CipherBytes[0], Combined[Length(IV)], Length(CipherBytes)); Result : TNetEncoding.Base64.EncodeBytesToString(Combined); finally Cipher.Free; end; end;解密就是逆过程先Base64解码拿到组合字节前16字节是IV后面是密文再用相同的Key去解。如果你需要把加密结果持久化存储建议在密文字符串头部加一个版本号比如AES256V1:前缀。这样以后算法升级了老数据还能根据版本号走老逻辑解密不至于做一次性迁移。3.2 加解密过程中的几个关键细节第一string与TBytes之间的转换务必统一用UTF-8。Delphi 10.2默认的string是Unicode直接TEncoding.Default转字节在中文Windows上可能变成GBK换一台英文系统机器解密就乱码。用UTF-8能保证跨平台、跨机器结果一致。第二CBC模式一定要传IV而且解密时的IV必须和加密时用同一个。所以我习惯把IV拼到密文里一起返回而不是单独存一个字段少一次数据库存储也少一个出错的环节。第三口令本身长度不够AES-256密钥长度怎么办最简单的方案就是把口令做一次SHA-256哈希得到32字节作为密钥。这样用户不管输入什么长度口令最终密钥都是256位安全性也更均衡。第四性能上不要对一个超大字符串一次性加密。AES加密是分块处理的一次性加载几百MB数据进内存很容易把进程内存打爆。大文件用分块流式处理每读1MB数据就加密一次然后写入输出流内存占用基本恒定。4. 实战二哈希完整性与RSA签名/验签4.1 用SHA-256给文件算摘要校验文件是否被改过哈希算法适合做完整性校验。一个常见场景客户端从服务端下载安装包如何知道下载过程中文件没有被篡改服务端把安装包的SHA-256值发布出来客户端下载完成后重新计算一次SHA-256两个值一致就说明文件是完整的。在TMS Cryptography Pack里做文件哈希典型代码如下function ComputeFileSHA256(const FileName: string): string; var Stream: TFileStream; Hasher: TSHA256Hasher; Buffer: TBytes; ReadCount: Integer; Digest: TBytes; begin Stream : TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone); try Hasher : TSHA256Hasher.Create; try SetLength(Buffer, 1024 * 1024); while Stream.Position Stream.Size do begin ReadCount : Stream.Read(Buffer, Length(Buffer)); Hasher.Update(Buffer, ReadCount); end; Digest : Hasher.Final; Result : BytesToHex(Digest); finally Hasher.Free; end; finally Stream.Free; end; end;关于哈希算法我有几个建议。MD5和SHA-1目前的碰撞风险已经不是理论问题了凡是涉及安全校验的场景不要再用。SHA-256是目前性价比最高的选择速度和安全性平衡得很好。哈希结果统一转成小写十六进制字符串比较时一律转小写再比避免大小写不一致导致误判。4.2 大数据量场景分块哈希避免一次性载入内存上面代码里我用了1MB的缓冲区这是从实操角度出发的选择。文件小的时候感觉不到区别但如果你处理的是几个GB的镜像文件一次性把整个文件读进内存再算哈希内存占用直接上GB级别非常危险。分块Update不影响哈希结果的正确性——SHA-256本身就是分块压缩结构逐块更新和一次性输入得到的摘要完全一致。所以只要文件读取逻辑正确可以放心用。另外在Windows上打开文件时建议带上fmShareDenyNone否则计算哈希时如果另一个程序正在读同一个文件可能会因为文件被独占而报错。这个参数的含义是允许其他进程同时读取该文件不至于因为权限冲突中断操作。4.3 RSA签名身份验证与防抵赖对称加密适合大批量数据但两个互不信任的节点之间交换密钥是个麻烦事。RSA的作用就是解决这个问题用私钥签名用公钥验签公钥可以公开分发私钥自己保管这样接收方就能确认消息确实来自持有私钥的一方。在TMS Cryptography Pack里RSA核心操作长这样// 生成RSA-2048密钥对 RSA.GenerateKeyPair(2048, PublicKey, PrivateKey); // 私钥对数据签名摘要算法用SHA-256 Signature : RSA.SignBytes(DataBytes, PrivateKey, sha256); // 公钥验证签名 Valid : RSA.VerifyBytes(DataBytes, Signature, PublicKey);这里有个重要认知RSA本身对加密数据长度有限制2048位密钥一次最多加密245字节左右不能直接拿RSA去加密大文件。实际项目里更常见的做法是混合加密先用AES随机生成密钥加密业务数据再用RSA公钥加密这个AES密钥。TMS这个包也支持这种混合流程原理就是两层密钥分开用。签名场景里私钥的安全保管特别重要。Windows环境下可以考虑用系统DPAPI保护私钥文件移动端则需要借助系统提供的密钥库。不要把私钥硬编码在代码里逆向工程一旦拿到硬编码私钥整个安全体系就形同虚设。5. 常见坑与排查技巧实录5.1 字符串编码不一致导致“加密成功解密乱码”这个问题出现的频率最高症状是加解密函数单独跑都正常但把密文通过网络传到另一台机器上解密出来的内容就是乱码。根因几乎都是字符串与字节数组转换时用的编码不一致。Delphi下的string是Unicode和字节数组打交道时一定要明确用什么编码。我在项目里统一了口径入参字符串一律TEncoding.UTF8.GetBytes转成字节数组出参字节数组一律TEncoding.UTF8.GetString转回字符串这样无论在哪台机器上运行结果都一致。5.2 密钥迁移与版本升级的兼容性问题上线半年后如果要换密钥或者升级算法参数老数据怎么处理这是个很容易被忽视的问题。如果在设计阶段就给密文加了版本前缀比如AES256V1:和AES256V2:程序解密时先解析版本号再走对应的解密逻辑那么新旧数据可以平滑共存不需要停机一次性迁移。别觉得这是小题大做我见过太多项目因为忘了这步升级时只能把几万条历史数据全部重新加密一遍不仅耗时还增加了出错概率。5.3 移动端路径、内存和随机源的特殊性如果项目要跑在移动端有几个注意事项要提前考虑。一是文件路径不要写死Android下外部存储目录和iOS下沙盒目录完全不同最好通过TPath.GetDocumentsPath这类系统API获取。二是移动端内存更紧张大文件哈希或者加密更是要用分块方式不要在手机上一次读几百MB数据。三是随机数生成器的问题有些自研算法在移动端拿不到足够熵源组件的随机数函数如果封装了系统的安全随机源就可以放心用但你自己用Random函数去生成密钥或IV是绝对不行的伪随机密钥等于没有密钥。5.4 排查工具小步验证逐步分块系统联调阶段遇到加解密不一致我先不管业务代码直接写一个最小测试程序固定密钥、固定IV、固定明文跑一遍加解密看结果能不能对上。如果这一步都不对就是算法参数的问题如果这一步对了再逐步带入业务逻辑定位传输、存储、编码哪个环节出了问题。这个思路能帮你省下大量排查时间。写在最后一点个人体会从安装组件到几个核心功能跑通我用这个包做过的项目覆盖了PC端工具、服务端中间件和移动App的数据保护模块。最大的体会是选对一个成熟的加密组件库只是开始真正决定安全上限的是使用的人有没有理解算法背后的参数含义比如IV的随机性、密钥的长度、哈希的用途、私钥的保护方式。TMS Cryptography Pack把实现细节藏得很深让你调用起来很舒服但也容易让人忽略底层原理一旦换平台或者换版本就不知道怎么调了。最后分享一个我自己的习惯每次搭好加密模块我会在项目里放一个自检单元里面固定写几个测试向量——明文、密钥、IV、期望密文都是写死的跑一遍能对上就说明当前环境的组件版本和代码行为是一致的。这样以后不管是升级Delphi、换机器还是升级组件包跑一下自检就能迅速确认加密模块有没有受到影响。保存好这份自检代码等于给加密部分加了安全气囊。本文还有配套的精品资源点击获取