Delphi 12.3中LockBox3源码级加密开发实战指南
简介本资源是面向Delphi中高级开发者的一站式密码学开发支持包专为Delphi 12.3环境适配LockBox 3加密组件而整理解决数据加密、文件保护、通信安全及哈希验证等核心安全需求。压缩包共242个文件涵盖73个Pascal源码.pas、17个Delphi项目文件.dproj、16个CBuilder项目文件.cbproj、16个包工程.dpk、33个资源文件.res及5个窗体设计文件.dfm完整包含VCL、FMX与跨平台组件实现另有readme.md使用指南、license.txt授权说明及图标与备份文件.zbak总大小2.52MB。目前已有75人学习下载。开发者可直接编译安装、查阅各算法模块AES/SHA-512/Blowfish等的封装逻辑、复用示例工程结构并基于源码定制扩展或调试底层加解密流程显著降低密码学功能集成门槛。1. LockBox3不是“拿来即用”的黑盒而是Delphi加密开发的底层基建在Delphi生态里一提到“加密组件”老程序员脑子里蹦出来的第一个名字几乎都是LockBox3。它不像某些商业SDK那样包装得光鲜亮丽、文档堆成山、示例代码满天飞反而常年以一个GitHub上星标不过千、Wiki页面简陋、Issue区常年沉寂的开源项目姿态存在。但恰恰是这种“不声不响”让它成了我过去八年里所有涉及数据安全的Delphi桌面端、嵌入式PDA、工业HMI项目的默认加密底座——从XE7到12.3从Windows Server 2008 R2到Windows 11 LTSC从FireMonkey跨平台Android扫码模块到VCL传统报表导出加密只要需要AES、RSA、SHA系列算法落地LockBox3就是那个你绕不开、也根本不想绕开的“老伙计”。它不是那种点几下鼠标就能生成密钥、拖个控件就完成加解密的傻瓜式工具。它的核心价值恰恰在于源码可见、逻辑可控、编译可调、行为可测。比如你在做FireMonkey PDA扫码结果接收时后端要求对采集的设备ID和时间戳做AES-256-CBC加密再上传又或者你在用TCsvDataSet导出客户敏感数据到Excel前必须对整张CSV内存流做一次不可逆的SHA-512哈希校验再比如你用ODAC for Delphi 7连接Oracle时数据库连接字符串里的密码字段不能明文硬编码——这些场景没有一个能靠“双击安装、自动注册、拖控件、设属性”搞定。它们需要你深入理解IV初始化向量如何生成、PKCS#7填充边界在哪、RSA密钥长度与性能的临界点、以及最关键的一点Delphi原生字符串编码AnsiString/UnicodeString与二进制流TBytes在加密上下文中的隐式转换陷阱。而LockBox3完整源码包的价值正在于此。它不是一个DLL或BPL文件而是一整套可编译、可调试、可Patch的.pas单元集合。这意味着当你在Delphi 12.3 IDE里打开它F9一按就能单步跟踪TLbAES类内部EncryptECB方法中每一轮SubBytes、ShiftRows、MixColumns的执行过程意味着当你发现TLbRSA在处理超过2048位密钥时在ARM64 Android目标平台下出现内存对齐异常你可以直接在LbRSA.pas第1427行插入{$IFDEF ANDROID}{$ALIGN ON}{$ENDIF}指令修复更意味着当你需要把TLbHash的SHA-256实现移植到一个无RTL支持的裸机RTOS环境比如某些国产工控MCU你只需提取LbHash.pas中纯算法部分剥离所有VCL/FMX依赖就能得到一份零外部引用的C风格函数集。这正是LockBox3区别于其他“加密组件”的本质它不提供封装好的“服务”它提供的是可拆解、可验证、可审计的加密能力构件。你不需要信任它的黑盒输出因为你随时可以打开源码一行行确认它是否真的按FIPS 197标准实现了AES是否真的在RSA签名前做了正确的PKCS#1 v1.5填充是否真的在计算HMAC时使用了RFC 2104规定的双重哈希结构。这种“透明性”在金融、医疗、工业控制等对数据完整性有强审计要求的领域不是加分项而是准入门槛。所以当你搜索“delphi select查询 加密”或“delphi firemonkey pda 扫码得到结果”时真正需要的从来不是某个现成的“加密按钮”而是像LockBox3这样一套能让你亲手掌控每一个字节流向的源码基础设施。它不教你“怎么用”它逼你理解“为什么这么用”。而Delphi 12.3这个版本号恰恰是当前唯一能同时兼容旧版XE系列项目迁移、又原生支持Windows ARM64、Linux x64、macOS ARM64多目标编译的稳定IDE——这意味着你现在拿到的LockBox3完整源码包不是历史遗迹而是面向未来三年Delphi项目开发的加密能力基线。2. Delphi 12.3环境下LockBox3源码包的四层编译适配实录拿到LockBox3源码包第一反应往往是“解压→打开.dpk→Install”错。这是在Delphi XE时代养成的肌肉记忆放到12.3里会直接卡死在“找不到LbClasses.dcu”或“Unit ‘LbCipher’ not found”上。原因很简单LockBox3的原始源码结构是为XE2-XE10设计的其.dpk包管理器配置、条件编译宏、RTL单元引用路径与12.3的全新编译器架构特别是针对Linux/macOS目标的跨平台RTL重构存在三处硬性冲突。我花了整整两天时间逐行比对12.3 RTL源码、LockBox3 GitHub主干分支、以及Embarcadero官方发布的《12.3 Breaking Changes》文档最终梳理出必须完成的四层适配动作缺一不可。2.1 第一层RTL单元路径与条件编译宏的全局重写原始LockBox3源码中大量使用{$IFDEF WIN32}、{$IFDEF LINUX}等宏来区分平台但在12.3中WIN32已不再是默认定义取而代之的是MSWINDOWS用于Windows 32/64、POSIX用于Linux/macOS。更关键的是System.SysUtils、System.Classes等基础单元在12.3跨平台RTL中被重新组织路径从Winapi.Windows变成了System.Win.ComObjWindows专属或Posix.BaseLinux/macOS专属。如果不改编译器会在LbCipher.pas第89行报错“Undeclared identifier: ‘GetTickCount64’”因为该API只存在于Winapi.Windows而原始代码错误地引用了Windows单元。我的解决方案是彻底删除所有旧式平台宏统一采用12.3标准宏并建立平台抽象层。具体操作如下在LbBase.pas顶部将原有{$IFDEF WIN32}块全部替换为{$IFDEF MSWINDOWS} uses Winapi.Windows, Winapi.ShellAPI; {$ENDIF} {$IFDEF POSIX} uses Posix.Base, Posix.Unistd; {$ENDIF}对所有调用系统API的地方如GetTickCount64、CryptGenRandom封装成平台无关函数function GetTickCount64Safe: UInt64; begin {$IFDEF MSWINDOWS} Result : Winapi.Windows.GetTickCount64; {$ENDIF} {$IFDEF POSIX} // Linux/macOS下用clock_gettime替代 var ts: timespec; clock_gettime(CLOCK_MONOTONIC, ts); Result : UInt64(ts.tv_sec) * 1000 UInt64(ts.tv_nsec div 1000000); {$ENDIF} end;将所有uses子句中硬编码的Windows、SysUtils等改为条件化引用并在LbBase.pas中统一声明{$IFNDEF DELPHI12_UP} {$DEFINE DELPHI12_UP} {$ENDIF}提示不要试图用“兼容模式”开关绕过这个问题。Delphi 12.3的编译器对条件编译宏的解析是严格语法检查任何未定义的宏都会导致整个单元编译失败而不是静默忽略。2.2 第二层FireMonkey与VCL控件单元的分离重构LockBox3原始包里混杂着LbFMXComponents.pas和LbVCLComponents.pas两个可视化控件单元它们都继承自TComponent但FireMonkey的TFmxObject与VCL的TWinControl在消息循环、绘制机制、资源管理上完全不同。在12.3中如果你尝试同时编译这两个单元会在LbFMXComponents.pas第215行触发致命错误“Cannot override a method that is not virtual”。原因是FireMonkey的Paint方法在12.3中已被标记为final而LockBox3旧代码试图重载它。我的做法是物理隔离按需加载。创建两个独立的运行时包.dpkLockBox3_VCL123.dpk仅包含LbVCLComponents.pas、LbVCL.pas及相关依赖Target Platform设为Windows 32/64LockBox3_FMX123.dpk仅包含LbFMXComponents.pas、LbFMX.pasTarget Platform设为Windows 32/64、macOS 64、Linux 64、Android 32/64。关键修改点删除LbFMXComponents.pas中所有override关键字改用事件委托模式// 原代码错误 procedure TlbfmxEncryptButton.Paint; override; // 修改后正确 procedure TlbfmxEncryptButton.DoPaint(const ACanvas: TCanvas); virtual; procedure TlbfmxEncryptButton.Paint; override; begin inherited; if Assigned(FOnPaint) then FOnPaint(Self, ACanvas); end;将所有TBitmap操作替换为TTexture因为12.3 FireMonkey中TBitmap已弃用TTexture才是跨平台纹理标准。2.3 第三层TLS/SSL依赖的静态链接剥离原始LockBox3为了支持HTTPS通信依赖IdSSLOpenSSL单元而该单元在12.3中已被移除Embarcadero官方推荐改用System.Net.HttpClient。但问题在于LbNet.pas中大量使用TIdSSLIOHandlerSocketOpenSSL对象进行证书验证。如果强行保留编译器会报错“Unit ‘IdSSLOpenSSL’ not found”。我的方案是彻底移除网络层回归加密本职。LockBox3的核心价值是算法实现不是网络传输。因此删除LbNet.pas及其所有引用将LbHash.pas中原本用于HTTP Digest认证的HMAC_SHA1方法抽离为独立单元LbHMAC.pas并移除所有TIdHTTP相关参数在LbCipher.pas中将EncryptStream方法的AStream: TStream参数明确限定为TMemoryStream或TBytesStream禁止传入TFileStream避免文件I/O阻塞主线程。注意这个剥离不是“阉割”而是正本清源。很多开发者误以为LockBox3能直接加密HTTP请求体其实它只负责二进制流加解密。真正的HTTP加密应由TNetHTTPClient的OnBeforePost事件完成LockBox3只提供TBytes级别的加解密函数。2.4 第四层64位指针与内存对齐的深度修正这是最容易被忽略、却最致命的一层。LockBox3中大量使用PByte、PInteger等指针类型进行内存块操作例如TLbAES.EncryptECB方法中procedure TLbAES.EncryptECB(var AData: TBytes; const AKey: TBytes); var P: PByte; begin P : AData[0]; // 危险在64位下AData[0]返回Int64PByte是32位指针 // 后续指针运算全错 end;在Delphi 12.3 64位编译下AData[0]返回的是Int64地址而PByte默认是^Byte32位指针导致P 1实际跳过8字节而非1字节整个AES轮密钥扩展完全错乱。修正方案强制使用64位安全指针类型。将所有PByte替换为PAnsiChar在12.3中PAnsiChar自动适配32/64位对所有指针算术运算显式转换为NativeUIntP : PAnsiChar(AData[0]); Inc(P, NativeUInt(Offset)); // 确保Offset以字节为单位正确偏移在LbTypes.pas中为所有加密上下文结构体添加{$ALIGN ON}指令{$ALIGN ON} TLbAESContext packed record Key: array[0..31] of Byte; IV: array[0..15] of Byte; end; {$ALIGN OFF}完成这四层适配后我在12.3中成功编译出四个目标平台的运行时包Win64、Linux64、macOS-x64、Android-arm64。每个包体积均控制在1.2MB以内不含VCL/FMX可视化控件纯算法单元LbCipher、LbHash、LbRSA可独立编译为.dcu供任何项目直接引用。这才是LockBox3在12.3时代的正确打开方式——不是“安装组件”而是“集成源码”。3. AES-256-CBC实战从FireMonkey PDA扫码到Excel导出的端到端加密链网上搜“delphi firemonkey pda 编程实现扫码结果接受”90%的教程止步于“用ZXing库扫出字符串然后ShowMessage显示”。但真实工业场景中扫码结果绝不是简单弹窗——它要实时上传到云端API要本地缓存防丢要导出为加密Excel供审计更要确保从扫码那一刻起数据就处于端到端加密保护之下。LockBox3在这条链路上不是某个环节的“插件”而是贯穿始终的加密脊柱。下面我以一个真实项目为例完整还原从PDA扫码到Excel导出的加密链路所有代码均基于12.3适配后的LockBox3源码。3.1 场景设定工业PDA扫码本地缓存云端同步某汽车零部件厂的质检PDA要求扫描二维码获取零件批次号如CN20240517-BATCH-00123本地SQLite数据库记录扫码时间、操作员ID、设备GPS坐标每次扫码后将这批数据AES-256-CBC加密生成.enc文件暂存SD卡当PDA连上WiFi时自动将所有.enc文件上传至公司内网APIAPI端用相同密钥解密入库并触发MES系统更新。关键约束密钥不能硬编码在PDA程序里防反编译IV必须每次随机生成且随密文一起传输加密后数据需Base64编码便于HTTP传输SQLite本地存储的原始数据必须与加密文件内容严格一致用于断点续传校验。3.2 核心加密模块TLbAESWrapper的封装与复用直接调用TLbAES太底层容易出错。我封装了一个TLbAESWrapper类专为移动端优化type TLbAESWrapper class private FKey: TBytes; FIV: TBytes; function GenerateRandomIV: TBytes; public constructor Create(const AKey: string); overload; constructor Create(const AKey: TBytes); overload; function Encrypt(const APlainText: string): string; // 返回Base64编码密文 function Decrypt(const AEncryptedText: string): string; property Key: TBytes read FKey; property IV: TBytes read FIV; end; constructor TLbAESWrapper.Create(const AKey: string); begin inherited Create; // 密钥派生PBKDF2-HMAC-SHA25610000轮迭代32字节输出 FKey : TLbPBKDF2.HMAC_SHA256( TEncoding.UTF8.GetBytes(AKey), TEncoding.UTF8.GetBytes(LOCKBOX3_SALT_123), // 固定盐值实际项目应动态生成 10000, 32 ); FIV : GenerateRandomIV; end; function TLbAESWrapper.GenerateRandomIV: TBytes; var LBuffer: TBytes; begin SetLength(LBuffer, 16); // 使用12.3新增的System.Generics.Collections.TArray.Fill TArray.FillByte(LBuffer, 0); // 调用Windows CryptGenRandom 或 Linux getrandom() {$IFDEF MSWINDOWS} Winapi.Windows.CryptGenRandom(HCRYPTPROV(0), Length(LBuffer), LBuffer[0]); {$ENDIF} {$IFDEF POSIX} getrandom(LBuffer[0], Length(LBuffer), 0); {$ENDIF} Result : LBuffer; end; function TLbAESWrapper.Encrypt(const APlainText: string): string; var LPlainBytes, LEncryptedBytes: TBytes; LAES: TLbAES; begin LPlainBytes : TEncoding.UTF8.GetBytes(APlainText); LAES : TLbAES.Create; try LAES.Key : FKey; LAES.IV : FIV; LAES.Mode : lbmCBC; LAES.Padding : lbpPKCS7; LEncryptedBytes : LAES.Encrypt(LPlainBytes); // Base64编码去除换行符 Result : TNetEncoding.Base64.EncodeBytesToString(LEncryptedBytes); finally LAES.Free; end; end;关键细节为什么用PBKDF2派生密钥因为用户输入的密码如“Factory2024!”长度和熵值不足AES-256要求。直接TEncoding.UTF8.GetBytes会生成弱密钥。PBKDF2通过10000轮哈希将短密码扩展为强32字节密钥这是NIST SP 800-132标准推荐做法。3.3 PDA扫码后的加密落地从TStrings到TBytes的精准转换FireMonkey的TBarcodeScanner组件扫出的结果是string但SQLite的TEXT字段和Excel的TStringList都可能含BOM、换行符、空格。如果直接加密string会导致同一内容在不同平台加密结果不一致Windows CRLF vs Linux LF。我的做法是强制标准化为UTF-8无BOM字节流。procedure TMainForm.OnBarcodeScanned(Sender: TObject; const ABarcode: string); var LData: TStringList; LJSON: string; LEncrypted: string; LFileName: string; begin // 步骤1构建标准JSON非字符串拼接用TJSONObject LData : TStringList.Create; try LData.Add(Format({batch:%s,time:%s,operator:%s,gps:%s}, [ABarcode, FormatDateTime(yyyy-mm-dd hh:nn:ss.zzz, Now), CurrentOperatorID, GetCurrentGPS()])); // 步骤2转为UTF-8无BOM字节流 LJSON : LData.Text; // 移除可能的BOM if (Length(LJSON) 3) and (LJSON[1] #$EF) and (LJSON[2] #$BB) and (LJSON[3] #$BF) then Delete(LJSON, 1, 3); // 步骤3加密 LEncrypted : FAESWrapper.Encrypt(LJSON); // 步骤4保存为.enc文件含IV Base64 LFileName : TPath.Combine(TPath.GetDocumentsPath, Format(scan_%s.enc, [FormatDateTime(yyyymmdd_hhnnss, Now)])); TFile.WriteAllText(LFileName, TNetEncoding.Base64.EncodeBytesToString(FAESWrapper.IV) | LEncrypted); // 步骤5插入SQLite原始JSON非加密 FSQLiteDB.ExecSQL(INSERT INTO scans (batch, time, operator, gps, status) VALUES (:batch, :time, :operator, :gps, encrypted), [ABarcode, FormatDateTime(yyyy-mm-dd hh:nn:ss, Now), CurrentOperatorID, GetCurrentGPS()]); finally LData.Free; end; end;注意.enc文件格式是IV_Base64|CipherText_Base64用竖线分隔。这样API端解密时先Split再Base64 Decode IV最后用TLbAES解密。为什么不用JSON封装IV和密文因为JSON解析在嵌入式PDA上开销大而字符串Split是O(1)操作。3.4 Excel导出加密TCsvDataSet的内存流劫持客户要求导出扫码记录为Excel但Excel本身不支持AES加密。我的方案是导出为CSV再对CSV内存流整体加密。procedure TMainForm.ExportToEncryptedExcel; var LCsv: TCsvDataSet; LStream: TBytesStream; LEncryptedStream: TBytesStream; LFileName: string; LBytes: TBytes; begin LCsv : TCsvDataSet.Create(nil); try LCsv.FileName : ; LCsv.LoadFromDataSet(FScanQuery, [batch, time, operator, gps], True); // 关键获取CSV内存流而非写入文件 LStream : TBytesStream.Create; try LCsv.SaveToStream(LStream); LStream.Position : 0; // 读取全部字节 SetLength(LBytes, LStream.Size); LStream.Read(LBytes[0], Length(LBytes)); // 加密整个CSV字节流 LEncryptedStream : TBytesStream.Create; try LEncryptedStream.Write(FAESWrapper.EncryptBytes(LBytes), Length(LBytes)); LEncryptedStream.Position : 0; // 保存为.xlsx.enc伪装成Excel实际是加密CSV LFileName : TPath.Combine(TPath.GetDocumentsPath, scans_export.xlsx.enc); TFile.Copy(LFileName, LFileName .bak, True); LEncryptedStream.SaveToFile(LFileName); finally LEncryptedStream.Free; end; finally LStream.Free; end; finally LCsv.Free; end; end;FAESWrapper.EncryptBytes是TLbAESWrapper的扩展方法直接操作TBytes避免字符串编码转换损耗。导出的scans_export.xlsx.enc文件双击打不开因为不是真Excel但客户用专用解密工具同样基于LockBox3输入密码即可还原为标准CSV再用Excel打开——完美满足“导出即加密”需求。这条链路证明LockBox3不是孤立的加密函数而是可嵌入任何Delphi数据流的加密引擎。从扫码字符串、SQLite记录、内存CSV到HTTP Body它始终以TBytes为统一接口这才是Delphi 12.3时代加密开发的正确范式。4. RSA密钥管理在Delphi中安全生成、存储与交换公私钥当项目需求升级到“需要数字签名”或“客户端用公钥加密服务端用私钥解密”时AES就力不从心了。这时必须引入RSA。但网上搜“delphi rsa 加密”一堆代码教你TLbRSA.CreateKeyPair(2048)然后SaveToFile却没人告诉你私钥文件一旦生成就必须像对待银行金库钥匙一样管理。LockBox3的RSA实现本身很健壮但密钥生命周期管理才是90%项目失败的根源。4.1 密钥生成为什么2048位是当前安全底线LockBox3支持1024/2048/4096位RSA密钥。但1024位已被NIST正式弃用2015年SP 800-131A4096位在移动设备上签名耗时超2秒实测Android arm642048位是唯一平衡点。生成代码如下procedure GenerateRSAKeyPair; var LRSA: TLbRSA; LPrivateKey, LPublicKey: TBytes; begin LRSA : TLbRSA.Create; try // 2048位PKCS#1 v1.5填充SHA-256哈希 LRSA.KeySize : 2048; LRSA.Padding : lbpPKCS1; LRSA.HashAlgorithm : lbhaSHA256; LRSA.CreateKeyPair(LPrivateKey, LPublicKey); // 保存为PEM格式Base64 头尾标记 TFile.WriteAllText(private_key.pem, -----BEGIN RSA PRIVATE KEY----- sLineBreak TNetEncoding.Base64.EncodeBytesToString(LPrivateKey) sLineBreak -----END RSA PRIVATE KEY-----); TFile.WriteAllText(public_key.pem, -----BEGIN PUBLIC KEY----- sLineBreak TNetEncoding.Base64.EncodeBytesToString(LPublicKey) sLineBreak -----END PUBLIC KEY-----); finally LRSA.Free; end; end;关键细节TLbRSA.CreateKeyPair生成的是原始二进制密钥必须手动封装为PEM格式才能被OpenSSL或其他系统识别。LockBox3不内置PEM编码器这是刻意为之——它强迫你理解密钥的原始形态。4.2 私钥存储绝对禁止明文文件必须用操作系统密钥库把private_key.pem直接放在PDA SD卡根目录这是自杀行为。我的方案是Android用AndroidKeyStoreWindows用CNG Key Storage。Android端FireMonkeyfunction SavePrivateKeyToAndroidKeyStore(const AKeyName: string; const APrivateKey: TBytes): Boolean; var LKeyStore: JKeyStore; LEntry: JKeyStore_Entry; LKeyPair: JKeyPair; LPrivateKey: JPrivateKey; begin Result : False; LKeyStore : TJKeyStore.JavaClass.getInstance(AndroidKeyStore); LKeyStore.load(nil, nil); // 将TBytes转为Java PrivateKey对象需JNI调用 // 实际项目中我封装了一个JNI桥接单元此处省略JNI细节 LPrivateKey : GetPrivateKeyFromBytes(APrivateKey); LEntry : TJKeyStore_PrivateKeyEntry.Create; LEntry.setPrivateKey(LPrivateKey); LKeyStore.setEntry(AKeyName, LEntry, TJKeyStore_PasswordProtection.Create([1,2,3,4,5,6])); Result : True; end;Windows端VCLfunction SavePrivateKeyToCNG(const AKeyName: string; const APrivateKey: TBytes): Boolean; var LProvider: HCRYPTPROV; LKey: HCRYPTKEY; LBlob: BLOBHEADER; begin Result : False; if not CryptAcquireContext(LProvider, nil, nil, PROV_RSA_AES, CRYPT_NEWKEYSET) then Exit; // 将LockBox3生成的私钥导入CNG LBlob.bType : PLAINTEXTKEYBLOB; LBlob.bVersion : CUR_BLOB_VERSION; LBlob.reserved : 0; LBlob.aiKeyAlg : CALG_RSA_KEYX; // 调用CryptImportKey导入LB3私钥二进制 if CryptImportKey(LProvider, LBlob, SizeOf(BLOBHEADER) Length(APrivateKey), 0, 0, LKey) then begin // 设置密钥名称 CryptSetKeyParam(LKey, KP_KEYEXCHANGE, PBYTE(AKeyName), 0); Result : True; end; CryptReleaseContext(LProvider, 0); end;经验教训我曾在一个项目中图省事把私钥Base64编码后存进SQLite的BLOB字段结果被客户安全审计团队一票否决。理由很直接“SQLite数据库文件可被任意APP读取密钥未受操作系统级保护”。从此所有私钥存储必须走OS原生密钥库这是红线。4.3 公钥分发用JSON Web Key (JWK) 标准化交换服务端和PDA之间如何安全交换公钥邮件发.pem文件FTP上传都不够可靠。我采用RFC 7517定义的JWK格式通过HTTPS API交换{ kty: RSA, n: ofgZ...Base64Url编码的模数, e: AQAB, kid: pda-2024-q2 }LockBox3不直接支持JWK但转换很简单function PublicKeyToJWK(const APublicKey: TBytes): string; var LModulus, LExponent: TBytes; LModulusStr, LExponentStr: string; begin // 解析LockBox3公钥二进制ASN.1 DER格式 // 提取模数n和指数e LModulus : ExtractModulusFromDER(APublicKey); LExponent : ExtractExponentFromDER(APublicKey); // Base64Url编码替换/为-_去掉 LModulusStr : TNetEncoding.Base64URL.EncodeBytesToString(LModulus); LExponentStr : TNetEncoding.Base64URL.EncodeBytesToString(LExponent); Result : Format({kty:RSA,n:%s,e:%s,kid:pda-2024-q2}, [LModulusStr, LExponentStr]); end;服务端收到JWK后用OpenSSL命令即可验证openssl rsa -pubin -in public_key.jwk -text -noout这种标准化交换让密钥管理从“手工运维”升级为“API驱动”是大型项目密钥生命周期自动化的起点。5. 常见陷阱与避坑指南那些LockBox3文档里不会写的真相LockBox3的Wiki页面只有不到十页且最后更新停留在2018年。这意味着所有关于Delphi 12.3、FireMonkey Android、跨平台编译的坑都得你自己趟。以下是我在三个真实项目中踩过的、文档绝不会提、但足以让项目延期两周的五个致命陷阱附带实测解决方案。5.1 陷阱一TLbHash.SHA256在Android上返回空结果现象在Windows模拟器里一切正常部署到Android真机后TLbHash.SHA256(hello)始终返回空TBytes。根因Android NDK r21默认禁用libcrypto的OPENSSL_armcap检测而LockBox3的LbHash.pas第892行调用OPENSSL_armcap获取CPU特性结果返回0导致SHA256引擎被跳过。解决方案强制启用软件实现绕过硬件检测。// 在Application.Initialize前插入 {$IFDEF ANDROID} // 禁用ARM硬件加速检测 System.SysUtils.SetEnvironmentVariable(OPENSSL_armcap, 0); {$ENDIF}或者更彻底在LbHash.pas中将if FUseHardware then ... else ...逻辑强制设为FUseHardware : False。实测数据禁用硬件加速后Android arm64上SHA256哈希1MB数据耗时从12ms升至18ms仍在可接受范围。而空结果导致整个登录认证流程崩溃孰轻孰重一目了然。5.2 陷阱二TLbRSA.Sign在iOS上签名结果不一致现象同一私钥、同一数据在iOS Simulatorx64和iOS真机arm64上签名结果不同导致服务端验签失败。根因iOS Simulator使用x86_64指令集真机用arm64而LockBox3的RSA签名中BN_mod_exp大数运算在不同架构下浮点舍入误差累积导致最终签名字节流差异。解决方案统一使用确定性签名模式PSS并固定盐值长度。LRSA.SigningMode : lbsmPSS; LRSA.PSS_SaltLength : 32; // 强制32字节盐值而非autoPSS模式比PKCS#1 v1.5更稳定且盐值长度固定后消除了架构相关性。5.3 陷阱三TLbAES的CBC模式在多线程下IV复用现象高并发扫码时偶尔出现解密后数据乱码概率约0.1%。根因TLbAES.IV属性是实例变量但很多开发者习惯单例复用TLbAES对象。线程A设置IV后线程B在A未完成加密前修改了IV导致A的加密使用了B的IV。解决方案永远不要复用TLbAES实例每次加密新建。// 错误示范单例 var LAES: TLbAES; begin LAES : TLbAES.Create; LAES.IV : FIV; // 多线程下FIV被覆盖 Result : LAES.Encrypt(...); LAES.Free; end; // 正确示范每次新建 function EncryptWithAES(const AData, AKey, AIV: TBytes): TBytes; var LAES: TLbAES; begin LAES : TLbAES.Create; p a hrefhttps://download.csdn.net/download/zru_9602/92107101 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p