简介DCPcrypt v2.0 是一套面向 Delphi 开发者的开源加密算法控件集合由 David Barton 编写并采用 MIT 许可发布适合需要在 Pascal 项目中集成密码学能力的初中级工程师与安全方向学习者。它把多种对称加密、分组密码与哈希算法封装成可直接引用的组件省去从零实现底层算法的成本常用于数据加密传输、文件校验与安全模块开发等场景。压缩包共 178 个文件约 652KB以 pas 源码、ppu 编译单元、o 目标文件与 bak 备份为主另含 inc 包含文件、res/dcr 资源、dpk 与 lpk 安装包及少量 html 说明结构完整便于二次编译与集成。目前已有 222 人学习下载。借助其中的源码与备份文件读者可快速理解各算法实现细节按需裁剪或扩展并参考安装包完成组件注册为 Delphi 项目搭建可复用的加密基础。1. 加密算法控件DCPcrypt2Delphi老项目里绕不开的那块密码学拼图接手一个 Delphi 维护项目翻到uses里躺着DCPcrypt2、DCPrijndael、DCPsha256这几行心里大概就有数了——这项目要做数据加密而且多半是十几年前就定下的技术选型。DCPcrypt2 是一套面向 Delphi 和 CBuilder 的密码学控件库把 AES、DES、Blowfish、Twofish、SHA、MD5、HMAC 这些算法封装成统一的接口调用方式在 VCL 和 FireMonkey 下基本一致。它适合两类人一类是维护老 Delphi 系统、需要读懂或替换加密逻辑的开发者另一类是想在 Object Pascal 项目里快速接入对称加密和哈希、又不想自己从零实现算法细节的人。源码包里通常包含核心单元、算法实现单元和示例工程拿到手就能编译进现有项目。2. 架构拆解DCPcrypt2 的单元划分与算法注册机制2.1 核心抽象层TDCP_cipher 与 TDCP_hash 两条继承链DCPcrypt2 的设计思路很清晰它把「加密」和「哈希」拆成两条独立的继承链。加密类的根是TDCP_cipher哈希类的根是TDCP_hash两者都继承自TDCP_base。这种分层的好处是上层业务代码只需要面向基类编程换算法时改动量极小。// 加密基类的关键方法签名简化示意 TDCP_cipher class(TDCP_base) public procedure Init(const Key; Size: longword; InitVector: Pointer); virtual; procedure Encrypt(const Indata; var Outdata; Size: longword); virtual; procedure Decrypt(const Indata; var Outdata; Size: longword); virtual; function EncryptString(const Str: string): string; function DecryptString(const Str: string): string; end;Init负责设置密钥和初始化向量Encrypt/Decrypt处理原始内存块EncryptString/DecryptString则是面向字符串的便捷封装。参数Size在加密方法里指的是字节数不是字符数这个区别在中文环境下特别容易翻车——一个汉字在 UTF-8 下占 3 字节在 GBK 下占 2 字节传错了就会截断或越界。哈希链的根TDCP_hash提供Init、Update、Final三段式接口也提供HashString这种一步到位的封装。三段式的意义在于可以流式处理大文件不用一次性把整个文件读进内存。2.2 算法单元的组织方式与条件编译源码包里每个算法基本对应一个独立单元命名规则是DCP加算法名DCPrijndael是 AESRijndael 算法DCPsha256是 SHA-256DCPblowfish是 BlowfishDCPtwofish是 TwofishDCPmd5是 MD5。这种一个算法一个单元的做法好处是编译器只会链接你实际uses的单元不会把没用到的算法代码打进可执行文件。单元内部大量使用条件编译指令来适配不同版本的 Delphi 和不同平台{$IFDEF FPC} {$MODE DELPHI} {$ENDIF} {$IFDEF DELPHI2009_UP} // 使用 UnicodeString 相关处理 {$ELSE} // 使用 AnsiString 相关处理 {$ENDIF}这意味着同一份源码可以在 Delphi 7 到较新版本、以及 Free Pascal 下编译。但要注意Unicode 和非 Unicode 版本下字符串处理逻辑不同如果你从老项目迁移到新版本 Delphi字符串加密结果可能对不上原因往往就在这里。2.3 上下文对象与算法注册为什么不能直接 new 一个 CipherDCPcrypt2 推荐通过TDCP_cipher的子类实例来操作而不是直接调用类方法。典型用法是声明一个TDCP_rijndael变量然后Create出来设置密钥再调用加密方法。这里有个容易被忽略的点Create的时候可以传入一个TDCP_cipher类型的参数作为「算法上下文」但大多数场景下直接Create(nil)就行。var Cipher: TDCP_rijndael; Key: array[0..31] of Byte; // 256 位密钥 IV: array[0..15] of Byte; // 128 位初始化向量 PlainText, CipherText: string; begin Cipher : TDCP_rijndael.Create(nil); try // 填充密钥和 IV实际项目中应从安全来源获取 FillChar(Key, SizeOf(Key), $00); FillChar(IV, SizeOf(IV), $00); Cipher.Init(Key, SizeOf(Key) * 8, IV); // 注意这里 Size 是位 PlainText : 需要加密的业务数据; CipherText : Cipher.EncryptString(PlainText); // CipherText 是 Base64 编码后的字符串 finally Cipher.Free; end; end;注意Init的第二个参数是密钥长度单位是位bit不是字节。256 位密钥要传 256不是 32。这个参数传错轻则加密结果不对重则直接抛异常。EncryptString返回的是 Base64 编码的字符串方便存储和传输但如果你需要原始二进制密文得用Encrypt方法操作内存块。3. 跑起来从零编译一个 DCPcrypt2 加密 Demo3.1 环境准备与源码包结构确认拿到源码包后先确认目录结构。典型的 DCPcrypt2 包会包含Source目录核心单元、Examples目录示例工程、以及可能的Packages目录设计期包。如果你用的是较新版本的 Delphi建议直接把Source目录下的.pas文件添加到项目搜索路径里而不是安装设计期包——设计期包在老版本和新版本之间的兼容性经常出问题。在 Delphi IDE 里打开Project→Options→Delphi Compiler→Search Path把Source目录的完整路径加进去。如果你用的是 Free Pascal 或 Lazarus在项目选项的Other Unit Files里加同样的路径。3.2 最小可运行示例AES 加密一个字符串新建一个 VCL Forms Application在窗体上放一个Memo用于输入明文一个Button触发加密再放一个Memo显示密文。然后在uses里加上DCPrijndael、DCPsha256、DCPbase64如果包里有独立的 Base64 单元。uses DCPrijndael, DCPsha256; procedure TForm1.btnEncryptClick(Sender: TObject); var Cipher: TDCP_rijndael; Key: TSHA256Digest; // 用 SHA-256 从口令派生密钥 Hash: TDCP_sha256; IV: array[0..15] of Byte; Encrypted: string; begin // 用 SHA-256 把用户口令转成 256 位密钥 Hash : TDCP_sha256.Create(nil); try Hash.Init; Hash.UpdateStr(edtPassword.Text); Hash.Final(Key); finally Hash.Free; end; // IV 在实际项目中应随机生成并随密文一起存储 FillChar(IV, SizeOf(IV), $00); Cipher : TDCP_rijndael.Create(nil); try Cipher.Init(Key, 256, IV); Encrypted : Cipher.EncryptString(memoPlain.Text); memoCipher.Text : Encrypted; finally Cipher.Free; end; end;这段代码做了三件事用 SHA-256 从口令派生固定长度的密钥、用 AES-256 加密明文、把 Base64 编码后的密文显示出来。Hash.UpdateStr接受字符串输入Hash.Final输出固定长度的摘要。注意TSHA256Digest是 DCPcrypt2 定义的类型别名长度是 32 字节。3.3 解密验证与常见编译错误处理解密流程和加密对称但要注意Init时的密钥和 IV 必须完全一致procedure TForm1.btnDecryptClick(Sender: TObject); var Cipher: TDCP_rijndael; Key: TSHA256Digest; Hash: TDCP_sha256; IV: array[0..15] of Byte; Decrypted: string; begin Hash : TDCP_sha256.Create(nil); try Hash.Init; Hash.UpdateStr(edtPassword.Text); Hash.Final(Key); finally Hash.Free; end; FillChar(IV, SizeOf(IV), $00); Cipher : TDCP_rijndael.Create(nil); try Cipher.Init(Key, 256, IV); Decrypted : Cipher.DecryptString(memoCipher.Text); memoPlain.Text : Decrypted; finally Cipher.Free; end; end;编译时最常见的错误是找不到单元检查搜索路径是否包含Source目录。另一个常见问题是TDCP_rijndael未声明确认uses里加了DCPrijndael。如果编译通过但运行时报「Invalid key size」检查Init的第二个参数是不是传了字节数而不是位数。提示DCPcrypt2 的EncryptString和DecryptString内部使用 Base64 编码所以密文是文本安全的可以直接存数据库的 varchar 字段。但如果你的数据库字段长度不够Base64 后的密文会比原文长约 33%记得留足空间。4. 避坑与排查DCPcrypt2 实际使用中的五个血泪教训4.1 现象加密结果每次都不一样解密却正常原因如果你用了随机 IV每次加密的 IV 不同密文自然不同。但解密时如果 IV 没跟着密文一起存就会解出乱码。DCPcrypt2 不会自动帮你管理 IV。解决把 IV 和密文拼在一起存储格式可以是Base64(IV) : Base64(密文)。解密时先拆分再用同样的 IV 初始化。4.2 现象老项目迁移到新 Delphi 后加密结果对不上原因Delphi 2009 之后字符串默认是 UnicodeString而老版本是 AnsiString。DCPcrypt2 的EncryptString在不同版本下对字符串的字节处理方式不同。解决统一用TEncoding.UTF8.GetBytes把字符串转成字节数组再用Encrypt方法操作字节数组避免依赖EncryptString的版本相关行为。4.3 现象加密大文件时内存暴涨原因一次性把整个文件读进内存再加密文件大了自然扛不住。解决用流式处理。DCPcrypt2 的TDCP_hash支持Update多次调用加密类虽然没有直接的流式接口但可以分块调用Encrypt每次处理固定大小的块比如 64KB最后一块注意填充。4.4 现象MD5 和 SHA1 被安全扫描标记为弱算法原因DCPcrypt2 包含 MD5 和 SHA1 的实现如果项目里还在用这些算法做完整性校验或口令哈希安全扫描会报警。解决新项目直接用 SHA-256 或 SHA-512。老项目如果只是做文件校验可以逐步替换如果是口令存储必须换成带盐的 SHA-256 或更专业的口令哈希方案。4.5 现象编译时报「Unit DCPcrypt2 was compiled with a different version of ...」原因源码包里的单元和项目里已有的同名单元版本不一致或者 Delphi 的 DCU 缓存混乱。解决清理项目的 DCU 输出目录重新编译。如果还不行检查是否有多个版本的 DCPcrypt2 源码混用确保搜索路径里只有一个Source目录。5. 二次开发给 DCPcrypt2 加一层业务友好的封装5.1 封装思路把算法细节挡在业务代码之外直接在业务代码里Create加密对象、管理密钥和 IV时间长了会散落各处改起来痛苦。我一般会写一个TCryptoHelper类把常用操作封装成静态方法type TCryptoHelper class public class function AESEncrypt(const PlainText, Password: string): string; class function AESDecrypt(const CipherText, Password: string): string; class function SHA256Hash(const Input: string): string; end; class function TCryptoHelper.AESEncrypt(const PlainText, Password: string): string; var Cipher: TDCP_rijndael; Hash: TDCP_sha256; Key: TSHA256Digest; IV: array[0..15] of Byte; IVBase64, CipherBase64: string; begin // 从口令派生密钥 Hash : TDCP_sha256.Create(nil); try Hash.Init; Hash.UpdateStr(Password); Hash.Final(Key); finally Hash.Free; end; // 生成随机 IV Randomize; // 实际项目中应使用密码学安全的随机源 // 这里用简单随机数示意 // ... Cipher : TDCP_rijndael.Create(nil); try Cipher.Init(Key, 256, IV); CipherBase64 : Cipher.EncryptString(PlainText); IVBase64 : EncodeBase64(IV, SizeOf(IV)); Result : IVBase64 : CipherBase64; finally Cipher.Free; end; end;封装的关键点有三个密钥派生统一走 SHA-256IV 随机生成并和密文一起返回返回格式用IV:密文的字符串形式。这样业务代码只需要调TCryptoHelper.AESEncrypt(明文, 口令)不用关心底层细节。5.2 验证方法写一个往返测试用例封装完之后必须写测试验证加密解密往返一致。在 Delphi 里可以用 DUnit 或者简单的控制台程序procedure TestRoundTrip; var Original, Encrypted, Decrypted: string; begin Original : 测试数据 123 ABC; Encrypted : TCryptoHelper.AESEncrypt(Original, my_password); Decrypted : TCryptoHelper.AESDecrypt(Encrypted, my_password); Assert(Original Decrypted, 往返测试失败); end;这个测试要覆盖空字符串、纯中文、纯英文、中英混合、超长字符串几种情况。特别是空字符串DCPcrypt2 对空输入的处理在不同版本下可能有差异提前测出来比上线后炸了好。5.3 改造技巧用条件编译兼容新旧 Delphi如果你的代码需要在 Delphi 7 和 Delphi 11 之间共用字符串处理部分必须用条件编译隔开function StringToBytes(const S: string): TBytes; begin {$IFDEF DELPHI2009_UP} Result : TEncoding.UTF8.GetBytes(S); {$ELSE} SetLength(Result, Length(S)); if Length(S) 0 then Move(S[1], Result[0], Length(S)); {$ENDIF} end;这样无论编译环境是哪个版本字符串转字节数组的行为都一致加密结果也就一致了。这个技巧在维护跨版本项目时特别有用我一般会把它放在一个独立的CryptoUtils.pas单元里所有加密相关操作都走这个单元业务代码不直接引用 DCPcrypt2 的单元。从那以后我每次接手老 Delphi 项目的加密模块都强制先跑一遍往返测试和跨版本编译检查确认密钥派生、IV 管理和字符串编码这三块没有暗坑再动业务代码。希望帮到你。本文还有配套的精品资源点击获取