1. 项目概述与核心价值
在Delphi开发领域,数据安全是一个绕不开的话题。无论是本地配置文件的保护、网络通信的加密,还是用户敏感信息的存储,一套可靠、易用且高效的加密解密库都是项目中的“定海神针”。今天要聊的,就是我在多个商业项目中反复使用、验证过的一个经典选择:DCPcrypt。这不仅仅是一个库,更像是一位沉默而可靠的伙伴,它用纯Pascal写成,无需依赖外部DLL,从Delphi 2到最新的Alexandria版本都能良好兼容,这种跨版本的稳定性在Delphi生态里尤为可贵。
你可能在搜索“Delphi 加密”时见过它的名字,但网络上关于它的系统化资料,尤其是结合现代Delphi版本(如XE系列)和实际项目踩坑经验的深度分析,并不多见。很多文章止步于简单的“Hello World”式演示,对于算法模式选择、密钥管理、性能调优等实战中真正卡脖子的问题,往往语焉不详。我这次分享的目的,就是基于我过去十多年在数据安全模块开发上的经验,带你穿透DCPcrypt的API表面,直抵其设计精髓和实操内核。我们会从如何快速搭建一个可运行的演示程序开始,一步步深入到其源码的关键路径,分析其线程安全模型、内存管理策略,并分享我在处理大文件流加密、与外部系统(如OpenSSL)交互时遇到的“坑”和解决方案。无论你是正在评估加密方案的新手,还是希望优化现有加密模块的老手,相信这些从一线实战中沉淀下来的细节,都能给你带来直接的参考价值。
2. DCPcrypt库的整体架构与设计哲学
2.1 核心组件与算法支持解析
DCPcrypt的设计非常模块化,清晰地将加密学的抽象概念映射为了具体的Pascal类。理解这个架构,是高效使用它的前提。整个库的核心可以看作一个三层结构:最上层是统一的接口类(如
TDCP_cipher
和
TDCP_hash
),它们定义了所有加密算法和哈希算法必须实现的方法,如
Init
,
Encrypt
,
Decrypt
,
Final
等。这为我们提供了编程时的一致性,无论底层用的是AES还是Blowfish,调用的方法名都是一样的。
中间层是具体的算法实现类。这是DCPcrypt的精华所在,它支持的算法相当全面:
- 分组密码(Block Ciphers) :包括经典的AES(Rijndael)、DES、3DES(Triple DES)、Blowfish、Twofish等。对于AES,它支持标准的128、192、256位密钥长度,以及ECB、CBC、CFB、OFB等多种工作模式。这里需要特别注意, 选择算法和模式是安全性的第一道关 。例如,ECB模式因为相同的明文块会产生相同的密文块,在加密图像等数据时会留下明显的模式, 绝对不应用于需要语义安全的场景 。在绝大多数情况下,CBC模式配合一个随机且唯一的初始化向量(IV)是更安全的选择。
- 流密码(Stream Ciphers) :如RC4。流密码通常速度更快,但需要谨慎管理密钥流,RC4在现代应用中已被认为不安全,应避免在新项目中使用。
- 哈希函数(Hash Functions) :包括SHA1、SHA256、SHA512等SHA家族,以及MD5、RIPEMD-160等。需要明确的是,MD5和SHA1已因碰撞攻击被证实不安全, 不能用于密码存储或数字签名验证 ,但在一些非安全关键的校验和数据分片场景中仍可使用。
- 消息认证码(MAC) :如HMAC,用于验证消息的完整性和真实性。
最下层是辅助工具类,例如
TDCP_rijndael
(AES的具体实现)、
TDCP_sha256
等,它们继承自上层接口,包含了算法的具体数学运算和变换逻辑。
这种架构的好处是扩展性极强。如果你需要集成一个DCPcrypt不支持的算法(比如国密SM4),理论上你只需要创建一个新的类,继承
TDCP_cipher
并实现那几个核心虚方法即可,无需改动任何上层调用代码。这种设计哲学体现了Delphi面向对象思想的优雅之处。
2.2 源码目录结构与关键文件导读
下载DCPcrypt的源码包(通常是一个ZIP文件)后,你会看到一系列.pas文件。对于初学者,可能会感到眼花缭乱。我建议按以下优先级来熟悉:
-
核心接口文件 (
DCPcrypt2.pas) :这是整个库的“宪法”。它定义了所有算法类的祖先类TDCP_cipher和TDCP_hash,以及加密上下文、基础类型等。阅读这个文件,你能理解InitStr,EncryptStream这些便捷方法是如何在顶层封装的。 一个关键细节 :TDCP_cipher的Init方法需要密钥和IV,而IV在CBC等模式下至关重要。源码中会检查IV是否被提供,如果没有,在某些模式下会使用全零的IV,这在生产环境中是 极其危险 的,必须由调用者显式提供随机IV。 -
算法实现文件
:如
DCPrijndael.pas(AES),DCPhaval.pas(哈希),DCPmd5.pas等。这些文件包含了算法的核心逻辑。以DCPrijndael.pas为例,你可以看到它如何通过查表(S盒、列混合表)来高效实现AES的SubBytes、ShiftRows和MixColumns步骤。对于性能优化感兴趣的朋友,可以重点关注其中的静态数组和位运算技巧。 -
注册单元 (
DCPreg.pas) :如果你希望将DCPcrypt的组件安装到IDE的工具栏上(就像TButton那样),这个文件负责注册。不过在实际项目开发中,我更倾向于直接代码调用而非拖放组件,因为这样依赖更清晰,更适合版本控制和团队协作。 - 测试与演示文件 :源码包中通常包含一些测试用例和演示程序。这些是学习API用法的绝佳材料。我建议你首先编译并运行这些演示,直观感受加密解密的过程。
在阅读源码时,请特别留意其中的注释和
{$R-}
、
{$Q-}
(关闭范围检查和溢出检查)这样的编译器指令。这些指令通常出现在性能关键循环中,是为了提升速度,但也意味着开发者必须自己确保数组索引和运算不会越界或溢出,这体现了在安全性和性能之间的一种权衡。
3. 从零构建一个健壮的加密解密演示程序
3.1 环境配置与项目搭建实操
假设你使用的是Delphi 10.4 Sydney或更新版本。第一步不是直接写代码,而是妥善管理库文件。我推荐的做法是,不要将DCPcrypt源码直接复制到项目目录下,而是将其放在一个独立的目录中(例如
D:\Libraries\DCPcrypt
),然后将这个目录添加到Delphi的全局库路径(
Tools -> Options -> Language -> Delphi Options -> Library -> Library path
)和当前项目的搜索路径(
Project -> Options -> Delphi Compiler -> Search path
)中。这样做的好处是,所有项目都能共享同一份源码,便于统一升级和维护。
创建一个新的VCL Forms Application。在Form上放置以下组件:几个TEdit用于输入明文、密钥、IV;TMemo用于显示密文(Base64编码后)和解密结果;TButton用于触发加密和解密操作;TComboBox用于选择算法(如AES-128, AES-256, Blowfish);另一个TComboBox用于选择模式(CBC, ECB, CFB);TLabel用于提示。界面布局力求清晰,因为我们的重点是背后的逻辑。
接下来,在单元的
uses
部分,你需要根据要使用的算法添加对应的单元。例如,如果你计划使用AES和SHA256,需要添加
DCPcrypt2, DCPrijndael, DCPsha256
。
一个常见的坑是只添加了
DCPcrypt2
而忘了添加具体的算法单元
,这会导致在运行时提示类找不到(
Class not found
)的错误。
3.2 核心加密/解密流程的代码实现与详解
让我们实现一个使用AES-256-CBC模式加密字符串的函数。这里我会逐行解释关键点:
uses
..., DCPcrypt2, DCPrijndael, DCPbase64;
function AES256_CBC_Encrypt(const PlainText, KeyStr, IVStr: AnsiString): AnsiString;
var
Cipher: TDCP_rijndael; // 使用具体的AES算法类
Key, IV: array[0..31] of Byte; // AES-256密钥为32字节
PlainData, CipherData: TBytes;
i: Integer;
begin
Result := '';
// 1. 参数校验(安全编码的第一步)
if (Length(KeyStr) < 32) or (Length(IVStr) < 16) then
raise Exception.Create('密钥长度必须至少32字符,IV必须至少16字符(CBC模式需要16字节IV)。');
// 2. 准备密钥和IV(字符串到字节数组的转换是关键)
// 注意:这里使用简单的字符串直接拷贝。生产环境中,密钥应从安全存储中获取,
// 且可能需要使用PBKDF2等算法从口令派生。
FillChar(Key, SizeOf(Key), 0);
FillChar(IV, SizeOf(IV), 0);
Move(KeyStr[1], Key[0], Min(Length(KeyStr), SizeOf(Key)));
Move(IVStr[1], IV[0], Min(Length(IVStr), SizeOf(IV)));
// 3. 创建并初始化加密器
Cipher := TDCP_rijndael.Create(nil);
try
Cipher.Init(Key, SizeOf(Key)*8, @IV); // 第三个参数是密钥长度(位),第二个是IV指针
// Init方法内部会根据算法和模式设置好初始状态
// 4. 执行加密
// DCPcrypt要求输入输出缓冲区大小至少为块大小的倍数(AES为16字节)。
// 对于字符串,我们需要处理填充(Padding)。
// 这里我们使用库自带的EncryptString方法,它内部会处理PKCS7填充。
Result := Cipher.EncryptString(PlainText);
// EncryptString返回的是二进制数据,通常我们需要Base64编码以便于显示和传输
Result := Base64EncodeStr(Result);
finally
Cipher.Burn; // 关键!清除内存中的密钥和状态信息,防止内存转储攻击
Cipher.Free;
end;
end;
对应的解密函数
AES256_CBC_Decrypt
与之对称,区别在于调用
Cipher.DecryptString
,并且输入是Base64解码后的字符串。
几个必须强调的实操要点:
-
密钥管理是命门
:绝对不要像演示代码这样把硬编码的密钥放在源码里。在实际项目中,密钥应该来自安全的配置中心、硬件安全模块(HSM)或由密钥派生函数(如PBKDF2、scrypt)从用户口令生成。
DCPcrypt2.pas中提供了TDCP_blockcipher.CalcIV等方法,但更推荐使用专门的密钥派生单元。 -
IV必须随机且唯一
:对于CBC、CFB等模式,每次加密都必须使用一个新的、不可预测的随机IV。可以使用
DCPcrypt2单元中的DCPcrypt2.GenerateRandomKey函数来生成安全的随机IV。 重复使用IV会严重削弱安全性 。 -
Burn方法的重要性 :Burn方法会尝试用随机数据覆盖算法对象内部存储的密钥和缓冲区。尽管在高级语言中无法保证内存立即被回收,但调用它是一个良好的安全习惯,应在Free对象前调用。 -
编码与填充
:网络传输或文本存储时,必须将二进制密文转换为可打印字符,Base64是最常用方案。同时,分组密码需要对不是块大小整倍数的数据进行填充,PKCS7是标准。DCPcrypt的
EncryptString/DecryptString默认处理了填充,但如果你直接操作Encrypt/Decrypt字节流,必须自己处理。
3.3 支持多种算法与模式的动态调度设计
为了让演示程序更灵活,我们可以设计一个通用的加密函数,通过参数动态选择算法和模式。这涉及到一点简单的反射(RTTI)或工厂模式的思想,但DCPcrypt本身提供了一种更直接的方式:使用算法标识符。
uses
..., DCPcrypt2;
function GenericEncrypt(const AlgorithmName, ModeName, PlainText, KeyStr, IVStr: AnsiString): AnsiString;
var
Cipher: TDCP_cipher;
Key, IV: TBytes;
begin
// 1. 根据名称创建算法实例
Cipher := DCPcrypt2.GetCipher(AlgorithmName); // 例如 'RIJNDAEL', 'BLOWFISH'
if Cipher = nil then
raise Exception.CreateFmt('不支持的加密算法: %s', [AlgorithmName]);
try
// 2. 设置工作模式 (通过Cipher对象的属性或Init参数)
// 注意:DCPcrypt中模式通常通过算法类的一个属性(如CipherMode)或Init方法的某个参数来设置。
// 具体需要查阅对应算法单元的源码。例如,TDCP_rijndael有一个CipherMode属性。
if Cipher is TDCP_rijndael then
(Cipher as TDCP_rijndael).CipherMode := StrToCipherMode(ModeName); // 需要自己实现这个转换函数
// 3. 准备密钥和IV(略,同上例)
// ... 将KeyStr, IVStr转换为TBytes ...
// 4. 初始化和加密
Cipher.Init(Key[0], Length(Key)*8, @IV[0]);
Result := Cipher.EncryptString(PlainText);
Result := Base64EncodeStr(Result);
Cipher.Burn;
finally
Cipher.Free;
end;
end;
实现
StrToCipherMode
函数需要你了解DCPcrypt内部定义模式的常量,通常在其算法单元的接口部分,如
cmCBC
,
cmECB
等。这种方式增加了程序的灵活性,但也会引入更多的运行时检查和类型转换,在性能极敏感的场景需斟酌。
4. 深入源码:关键流程分析与性能优化洞见
4.1
TDCP_cipher.Init
与
Encrypt
核心路径剖析
让我们深入
DCPrijndael.pas
,看看一次AES加密调用究竟发生了什么。当你调用
Cipher.Init(Key, 256, @IV)
时:
-
密钥扩展(Key Expansion)
:AES算法并不是直接使用输入的密钥进行每一轮加密,而是通过一个密钥扩展例程(
KeySetup),将初始的密钥扩展成一系列轮密钥(Round Keys)。这个过程在Init中完成,结果存储在一个内部数组(如ctx.RK)中。 性能提示 :对于需要反复使用同一密钥加密大量数据的场景,你应该复用同一个TDCP_rijndael实例,而不是每次加密都创建新的对象并调用Init,因为密钥扩展的计算开销相对较大。 -
初始化向量处理
:对于CBC模式,
Init方法会将传入的IV复制到内部的状态寄存器(如ctx.IV)中。如果未提供IV,某些实现可能会使用全零向量,这就是我之前强调必须显式提供随机IV的原因。 -
加密过程
:当你调用
Encrypt方法(或EncryptString内部调用的Encrypt)时,对于CBC模式,代码大致会执行以下循环:
DCPcrypt的源码为了追求效率,大量使用了展开的循环和内联函数,并将S盒等查找表定义为常量数组。在// 伪代码,示意CBC模式 for i := 0 to (DataSize div BlockSize) - 1 do begin // 1. 当前明文块与上一个密文块(或IV)进行异或 XORBlock(@PlainBlock, @PreviousCipherBlock, BlockSize); // 2. 对异或后的块执行AES加密核心函数(包含SubBytes, ShiftRows, MixColumns, AddRoundKey) AES_EncryptBlock(@State, @RoundKeys); // 3. 输出当前密文块,并作为下一块的“上一个密文块” CopyBlock(@State, @PreviousCipherBlock, BlockSize); OutputBlock(@State); endEncrypt方法中,你会看到类似T0, T1, T2, T3这样预计算的查表操作,这是AES实现中常见的优化手段,用空间换时间,避免了运行时大量的位运算。
4.2 内存管理与线程安全考量
DCPcrypt的算法类通常继承自
TInterfacedObject
或直接是
TObject
,由使用者负责创建和释放。这意味着:
-
内存管理简单
:标准的Delphi对象生命周期管理,
Create和Free。 -
非线程安全
:一个
TDCP_cipher实例在其内部保存了加密状态(如CBC模式下的前一个密文块)。如果多个线程同时调用同一个实例的Encrypt方法,状态会相互干扰,导致加密结果错误甚至程序崩溃。 因此,最佳实践是为每个线程或每次加密会话创建独立的算法实例 。对于高并发服务器应用,可以考虑使用对象池来管理算法实例,以平衡创建开销和线程安全。
在源码中,你很少会看到临界区(Critical Section)或锁(Lock)的身影,这印证了它是为单线程操作设计的。如果你在多线程环境下使用,封装一层线程安全的服务是必要的。
4.3 与外部系统的兼容性实践(以OpenSSL为例)
一个常见需求是:用DCPcrypt加密的数据,需要用其他语言(如Python、Java)或工具(如OpenSSL命令行)解密,反之亦然。这涉及到算法、模式、填充、密钥派生和IV传递的完全一致。
案例:使用AES-256-CBC加密,并与OpenSSL兼容。
-
算法与模式
:双方明确使用
aes-256-cbc。 - 密钥与IV :密钥是32字节的原始二进制数据。IV是16字节的随机数。 必须将IV传递给解密方 。通常的做法是将IV预置在密文前面一起传输或存储。OpenSSL命令行工具在加密时默认就会将IV放在密文文件的开头。
- 填充 :双方必须使用相同的填充方案。DCPcrypt默认使用PKCS7填充,OpenSSL命令行默认也使用PKCS7填充(它称之为PKCS#5 padding,在AES的上下文中与PKCS#7等价)。确保一致。
- 数据格式 :密文(含IV)通常以Base64或原始二进制形式交换。
一个OpenSSL解密DCPcrypt密文的示例命令(假设IV已预置在密文二进制文件中):
# 假设 ciphertext.bin 文件结构为 [16字节IV] + [实际密文]
openssl enc -aes-256-cbc -d -in ciphertext.bin -out plaintext.txt -K $(echo -n "我的32字节密钥" | xxd -p) -iv 00000000000000000000000000000000
# 注意:这里 -iv 参数实际上被忽略了,因为IV已经从文件头读取了。密钥需要是十六进制字符串。
在DCPcrypt端,你需要手动将IV拼接到密文前,或者单独保存IV。解密时,先从数据中分离出IV和密文,再用相同的密钥和IV初始化解密器。
踩坑记录 :我曾遇到一个项目,后端用OpenSSL加密,Delphi客户端用DCPcrypt解密,始终失败。最后排查发现,OpenSSL使用的密钥是字符串的ASCII码值,而Delphi端误将字符串当成了UTF-8编码再进行转换,导致密钥字节序列不一致。 确保密钥的字节表示完全一致是跨平台/语言加密交互中最容易出错的一环。
5. 实战进阶:文件流加密、哈希计算与常见陷阱规避
5.1 大文件流式加密解密的高效实现
加密大文件(如几百MB的视频)时,绝不能将整个文件读入内存。必须使用流(
TStream
)进行分块处理。DCPcrypt的
TDCP_cipher
类提供了
EncryptStream
和
DecryptStream
方法,但它们内部也是循环读取块。我们可以实现一个更可控的版本:
procedure EncryptFileStream(const SourceFile, DestFile: String; Cipher: TDCP_cipher; const Key, IV: TBytes);
var
SrcStream, DstStream: TFileStream;
Buffer: array[0..1024*1024 - 1] of Byte; // 1MB缓冲区
BytesRead: Integer;
begin
Cipher.Init(Key[0], Length(Key)*8, @IV[0]);
SrcStream := TFileStream.Create(SourceFile, fmOpenRead);
try
DstStream := TFileStream.Create(DestFile, fmCreate);
try
// 首先,将IV写入目标文件头部(确保解密时能读取)
DstStream.WriteBuffer(IV[0], Length(IV));
// 然后循环加密数据块
repeat
BytesRead := SrcStream.Read(Buffer, SizeOf(Buffer));
if BytesRead > 0 then
begin
// 注意:最后一块可能需要填充。Encrypt方法要求输入长度是块大小的倍数。
// 这里我们使用EncryptStream,它会自动处理文件结束和填充。
// 但我们需要自己管理流的位置。
// 更精细的做法是,计算需要填充的字节,手动处理最后一块。
// 以下是一个简化版本,假设我们加密整块数据,填充由最终调用处理。
// 实际上,对于文件流,更推荐使用Cipher.EncryptStream(SrcStream, DstStream, SrcStream.Size);
// 但为了展示分块逻辑,我们手动操作:
Cipher.Encrypt(@Buffer, @Buffer, BytesRead);
DstStream.WriteBuffer(Buffer, BytesRead);
end;
until BytesRead = 0;
// 处理可能的最后一块填充(略,具体取决于是否使用流加密模式)
finally
DstStream.Free;
end;
finally
SrcStream.Free;
Cipher.Burn;
end;
end;
关键优化点 :缓冲区大小的选择。太小(如4KB)会导致频繁的I/O和加密调用,开销大;太大(如100MB)则会占用过多内存。通常1MB到10MB是一个在性能和内存占用之间较好的平衡点,具体取决于硬盘速度(尤其是SSD和HDD的差异)和系统内存。
5.2 消息摘要与HMAC的应用场景与代码示例
哈希和HMAC是另一组常用功能。哈希用于验证数据完整性(如文件校验),HMAC用于带密钥的完整性验证(如API请求签名)。
uses DCPsha256;
// 计算文件的SHA256哈希值
function CalcFileSHA256(const FileName: String): String;
var
Hash: TDCP_sha256;
Stream: TFileStream;
Digest: array[0..31] of Byte; // SHA256产生32字节摘要
begin
Hash := TDCP_sha256.Create(nil);
Stream := TFileStream.Create(FileName, fmOpenRead);
try
Hash.Init;
Hash.UpdateStream(Stream, Stream.Size); // 流式更新哈希
Hash.Final(Digest); // 获取最终摘要
// 将二进制摘要转换为十六进制字符串
Result := LowerCase(ByteToHexStr(Digest, SizeOf(Digest)));
finally
Stream.Free;
Hash.Free;
end;
end;
// 计算字符串的HMAC-SHA256
function CalcHMAC_SHA256(const Data, Key: AnsiString): String;
var
Hash: TDCP_sha256;
Digest: array[0..31] of Byte;
begin
Hash := TDCP_sha256.Create(nil);
try
// DCPcrypt的HMAC计算需要手动进行 (Key ^ ipad) + data, 哈希,再 (Key ^ opad) + hash,再哈希
// 这里省略了HMAC的详细实现步骤。实际上,DCPcrypt可能没有直接提供HMAC类。
// 对于HMAC,更常见的做法是使用专门的单元或自己实现RFC 2104。
// 这是一个需要注意的地方:DCPcrypt核心库可能不包含现成的HMAC组件。
// 你可能需要寻找第三方补充单元或自行实现。
raise Exception.Create('DCPcrypt核心库未直接提供HMAC。需使用其他单元或手动实现。');
finally
Hash.Free;
end;
end;
重要提示
:如代码注释所述,标准的DCPcrypt发行版可能不包含一个开箱即用的HMAC组件。你需要检查你使用的版本,或者寻找像
DCPtiger
(如果包含)这样的单元,或者从社区寻找HMAC的Pascal实现。这是一个在实际项目中容易被忽略的兼容性问题。
5.3 开发中的典型“坑”与排查清单
-
“解密后数据末尾有乱码” :
- 原因 :最可能的原因是填充(Padding)不一致。加密端使用了PKCS7填充,但解密端没有移除填充,或者使用了错误的填充方式(如ZeroPadding)。
-
排查
:确认双方(DCPcrypt与另一方)使用的填充方案。对于DCPcrypt,
EncryptString/DecryptString自动处理PKCS7。如果手动使用Encrypt/Decrypt,必须在加密前手动添加填充,解密后手动移除。 -
解决
:统一使用PKCS7填充。解密后,检查最后一个字节的值
PadLen,然后删除末尾的PadLen个字节。
-
“跨语言解密失败” :
- 原因 :99%的问题出在密钥、IV或数据的字节表示上。字符编码(UTF-8 vs ANSI vs Unicode)、字符串到字节数组的转换方式、Hex或Base64编解码的差异都可能导致字节序列不同。
-
排查
:使用十六进制查看工具(如Hex编辑器或编程语言中的
bin2hex),分别在加密端和解密端,打印出密钥、IV和密文的前几个字节,进行逐字节比对。 - 解决 :约定使用明确的编码(如UTF-8 without BOM)进行字符串到字节的转换。对于密钥和IV,优先使用原始的二进制数据(字节数组)进行交换,而非字符串。
-
“加密大文件时内存占用飙升” :
-
原因
:错误地将整个文件加载到
TStringList或TBytes中。 -
解决
:务必使用
TFileStream配合缓冲区进行流式处理,如4.1节所示。监控任务管理器中的内存使用情况。
-
原因
:错误地将整个文件加载到
-
“在多线程中加密结果随机错误” :
-
原因
:多个线程共享了同一个
TDCP_cipher实例。 - 解决 :遵循“一个实例,一个线程”的原则。使用TThread或并行库时,在每个线程的执行函数内部创建和释放加密器对象。
-
原因
:多个线程共享了同一个
-
“升级Delphi版本后编译出错” :
- 原因 :DCPcrypt的某些旧版本代码可能使用了已废弃的编译器指令或函数。
-
排查
:检查错误信息,通常是类型不兼容或函数未定义。例如,
Pointer和PByte的转换,或者AnsiString与RawByteString的混用。 -
解决
:获取最新版本的DCPcrypt源码(很多社区有维护更新版本)。如果自行修改,注意
{$IFDEF}条件编译指令,针对新Delphi版本(如Unicode字符串)进行调整。一个常见的修改点是字符串相关的PChar改为PAnsiChar或PByte。
这份清单是我在多年开发中积累下来的,几乎每一个坑都曾让我花费数小时甚至更长时间去调试。希望它们能成为你的“避坑指南”,让你在Delphi加密解密之路上走得更顺畅。记住,在加密领域,细节决定成败,严谨的测试和跨验证(用不同工具验证同一组数据)是保证最终方案可靠的唯一途径。

472

被折叠的 条评论
为什么被折叠?



