ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RSA加密填充机制详解:PKCS#1 OAEP原理与跨语言实践

RSA加密填充机制详解:PKCS#1 OAEP原理与跨语言实践 第一次在项目里正儿八经地用RSA是在一个支付回调的联调任务里。我把 PKCS#1 OAEP 当成普通的填充参数从老代码里复制过来改了改就往上贴。结果Java侧加密出来的一长串密文Python侧怎么都解不开日志里密钥对明明是同一份但decryption error就像复读机一样刷屏。折腾了两天才翻明白PKCS#1规范也才意识到OAEP这三个字母背后不是简单的补位而是一套跟安全模型绑定的编码协议。这篇内容主要讲清楚三件事为什么RSA在加密时必须做填充、OAEP这套填充机制内部到底怎么运作、以及到了实际项目里参数配置和密钥格式最容易在哪些地方翻车。适合刚上手RSA加密的新人也适合被跨语言解密失败折磨过、想搞清楚原理的工程师。看完你可以直接照着后面几张配置表去对齐两端参数。1. 裸RSA的翻车现场为什么非要有填充这一步如果只看数学教材RSA加密就一句话把明文m转成整数算 c m^e mod n解密就是 m c^d mod n。很多新手第一次实现时就是这么干的然后就会撞上一连串莫名其妙的问题。1.1 三个看起来没问题却解不开的场景第一个场景是明文长度稍微长一点就失败。比如2048位密钥n是256字节你拿一个200字节的字符串去加密pow函数跑完解密得到的往往是乱码。因为m一旦接近或超过n模运算就把整数卷过去了密文对应的整数就不再是原来的那个。很多人以为只要小于256字节就行其实不是RSA加密要求明文整数必须严格小于n而且为了保证可逆性最好与n互质。第二个场景是同一个明文加密两次密文完全一样。这个特性看起来无害但在真实协议里很致命。比如你在登录请求里把密码用RSA公钥加密后发给服务器攻击者只要截获两段密文发现长度相等、内容相同就能判断是同一用户、同一密码。更麻烦的是加密结果的确定性和明文空间有限这两个条件叠加给了攻击者离线穷举的空间他可以把常用口令全部加密成密文然后跟你发的密文比对一次匹配就泄露了明文。第三个场景容易被忽略但真的会出事裸RSA是同态的。也就是说攻击者拿到你的密文c不用知道私钥就可以构造出另一个密文c它解密后对应的明文等于原明文乘上某个攻击者选择的因子。在一些需要防篡改的协议里这种性质会被利用来操纵消息内容。填充机制的核心目的之一就是把这些数学结构给打散掉。1.2 裸RSA的数学层面硬伤从形式化安全模型的角度讲裸RSA连最基础的选择明文攻击下的不可区分性IND-CPA都满足不了。简单说就是攻击者选出两条明文m0和m1挑战者只加密其中一条给你攻击者只要把两条都自己加密一遍跟挑战密文一比立刻知道是哪条。这种可以猜的加密方式在真实系统里是没法直接当加密方案用的。另外还有一个细节如果明文m太小比如只有几个字节那么m^e甚至可能小于n这时密文就是明文的e次方攻击者直接开e次方就能还原明文。这种情况听起来极端但当你真去处理一些短字段、短口令时确实存在。所以RSA需要一层把明文变长、变随机、变不可预测的处理。这层处理就是我们常说的填充padding。从PKCS#1 v1.5开始官方规范就要求做填充到了v2.0OAEP作为新的填充方案被正式引入也就是标题里的 PKCS#1 OAEP。1.3 填充到底在填什么填充不是单纯地把密文对齐到固定长度。它要解决的核心问题有两个一是让同一明文在不同加密中产生不同密文让攻击者没有判断依据二是消除明文与被加密整数之间的直接对应关系让任何微小改动都会引起整个密文雪崩式变化。OAEP正是围绕这两个目标在用RSA模幂运算之前给明文做了一次精心设计的混淆。2. OAEP的核心思想把确定性加密变成掷骰子加密2.1 从哪里来Bellare-Rogaway 1994OAEP的全称是Optimal Asymmetric Encryption Padding中文一般译作最优非对称加密填充。它由Mihir Bellare和Phillip Rogaway在1994年提出随后被纳入PKCS#1标准在v2.0里正式成为RSA推荐的加密填充方案对应的标准名称是RSAES-OAEP。所谓最优是指在随机预言模型下这个填充方案能让RSA加密获得可证明的选择密文攻击下不可区分IND-CCA安全属性。这也是它从1994年提出后至今依然是主流选择的原因。2.2 三个关键词哈希、掩码、Feistel网络OAEP的构造不复杂但第一次看规范的人通常会被一大串符号劝退。把它拆开来看核心只有三个东西一个哈希函数H通常用SHA-1或SHA-256。它在整个方案里承担随机化和完整性校验两个职责。一个掩码生成函数MGFPKCS#1标准里定义的是MGF1。它的作用是把任意长度的种子扩展成任意长度的伪随机字节串就像把一小撮盐均匀撒进一大锅汤。一个Feistel网络结构把明文和随机种子反复进行异或交叉的变换。这也是DES等分组密码里用过的经典结构优点是混淆扩散效果好任何一个输入比特变化都能扩散到输出的一大片比特。可以这样理解OAEP先掷一个骰子生成随机种子再用这个骰子对明文做两次打码最后才把打码后的数据交给RSA做模幂。整个过程是可逆的解密端只要拿到密文就能反推出打码前的种子和明文。但攻击者看不到种子也无法在不改动整个打码结果的情况下对密文做手脚。2.3 为什么加一个随机种子就能打破确定性RSA模幂本身是确定性的函数同样输入必然得到同样输出。但加密协议要求的是同一明文加密后每次的密文都不同。这个随机性没法从模幂里来只能从进入模幂之前的输入里来。OAEP的做法是每次加密时生成一个全新的随机种子seedseed和明文一起参与变换最终被掩盖在编码块里。所以即使明文完全一样两次编码出来的EM也可能完全不同。经过RSA模幂后两个密文看起来也没有任何统计规律上的关联。这一步就解决了裸RSA能猜、能比对的硬伤。有些新人会问随机种子不也一起加密了吗解密端怎么区分哪部分是种子、哪部分是数据答案是不需要区分。解密端不是去找种子而是做逆推先从编码块尾部还原出掩码后的DB再通过DB和种子之间的交叉关系反解出种子然后重新生成掩码把明文从DB里挖出来。整个过程靠的就是Feistel结构的可逆性。这个逆向流程在下一章里我会一步步拆开讲。2.4 MGF1到底是怎么扩展的掩码生成函数MGF1是基于普通哈希函数构造的。它接收一个种子seed和一个期望的输出长度maskLen然后循环执行第一次调用哈希输入是seed拼接上4字节的大端计数器0把哈希输出拼到结果里第二次换计数器1再拼一次如此往复直到结果长度达到maskLen最后按需截断。换句话说如果你要生成4000字节的掩码MGF1会以seed0、seed1、seed2这种方式调用哈希函数上百次再把输出拼接起来。这样任何一位种子发生变化最终掩码的所有字节都会面目全非。真实标准里还会检查计数器的上界但日常使用中我们几乎不会触及上限知道它是基于seed扩展出长随机串就够了。3. OAEP编码与解码的完整推演从数据块到密文这一章我按PKCS#1 v2.2RFC 8017的RSAES-OAEP定义来讲把编码和解码的每个步骤都摆出来。3.1 编码前的长度检查为什么2048位RSA只能塞190字节OAEP有明确的明文长度上限。先约定几个符号kRSA模数n的字节长度。2048位RSA对应k256。hLen哈希函数输出长度。SHA-256是32字节SHA-1是20字节。mLen要加密的明文长度。加密前必须满足mLen ≤ k - 2×hLen - 2。原因在于编码块EM的布局是这样的1字节的0x00开头中间是hLen长度的随机种子maskedSeed剩下的全部是maskedDB而maskedDB里还要装hLen的lHash、可选的零字节串PS、1字节的0x01分隔符、以及真正的明文M。减掉这些固定开销剩下的才是明文可以用的空间。以2048位RSAk256搭配SHA-256hLen32为例明文上限 256 - 2×32 - 2 190字节。如果你换成SHA-1上限会提升到214字节。很多项目第一次报Message too long就是因为没算这个上限拿RSA去直接加密几百字节的JSON。超过了上限怎么办规范做法是分层先用AES等对称加密算法加密整个消息再用RSA-OAEP加密这个AES会话密钥。这也是TLS等协议的标准做法。3.2 编码五步走从DB到EM编码过程可以分成组织数据块和两次掩码混淆两个阶段。第一步组织数据块DB如果使用了标签Llabel先对L求哈希得到lHash如果没使用标签L就是空字符串于是lHash Hash()。现实中绝大多数实现都不设置标签默认就是空字符串的哈希。根据明文长度和模数长度计算填充段PS。PS由若干个0x00字节组成长度是 k - mLen - 2×hLen - 2。DB lHash || PS || 0x01 || M。这串字节就是待打码的数据。它保证了解密端能通过固定位置的lHash校验数据的完整性通过0x01分隔符定位明文起点。第二步生成随机种子seed长度hLen。这一步的随机性来源必须是系统安全随机源比如Python的secrets、Java的SecureRandom、操作系统的/dev/urandom不能用时间戳、进程ID这类可预测的东西。第三步计算 dbMask MGF(seed, k - hLen - 1)然后用 maskedDB DB XOR dbMask。这一步是把数据块用随机种子扩展出的掩码罩住。第四步计算 seedMask MGF(maskedDB, hLen)然后用 maskedSeed seed XOR seedMask。注意这里MGF的输入变成了maskedDB输出长度只有hLen。这一步把随机种子也罩住了。第五步拼接得到最终编码块EM 0x00 || maskedSeed || maskedDBEM的总长度恰好是k字节。最后把EM当作一个大整数做 c EM^e mod n得到密文。这里有个容易忽略但值得提一句的细节EM最前面的0x00字节是硬编码的。它的作用不只是对齐长度更重要的是保证EM这个整数一定小于模数n因为对于一个k字节的RSA模数其最高字节的最高有效位通常是10x00开头能把EM的数值范围压到2^(8×(k-1))以下从而避免加密出来的整数大于等于n这类边界问题。3.3 解码与校验每个0x01、每个哈希都要较真解密端拿到密文c后先做私钥模幂 mRaw c^d mod n得到一个大整数。如果它的字节数不足k要在前面补0x00补齐到k字节如果它大于等于n直接判定错误。补位后的EM就是加密端EM的还原版。接下来检查EM[0]是否等于0x00不等于就返回decryption error。取出maskedSeed EM[1 : 1hLen]maskedDB EM[1hLen : k]。计算 seedMask MGF(maskedDB, hLen)还原 seed maskedSeed XOR seedMask。计算 dbMask MGF(seed, k - hLen - 1)还原 DB maskedDB XOR dbMask。从DB里取出前hLen字节用它和Hash(标签)做比较。要注意PKCS#1标准要求这种比较是恒定时间的不能写成一个遇到第一个不相等字节就提前退出的循环否则会给攻击者留出时序侧信道。从lHash之后开始检查在遇到第一个非零字节之前所有字节都必须为0x00第一个非零字节必须是0x010x01之后剩下的才是明文M。如果0x01的位置不对或者提前碰到了别的字节都要返回同样的decryption error。为什么要同时检查这么多地方因为每一个结构性的约束都在压缩攻击者猜测的空间。假如解密端能区分这里错了和那里错了攻击者就能利用这种区分逐渐逼近正确结果。所以任何异常都必须统一成一个错误不露任何口风。3.4 一个教学版的OAEP编码实现理论讲再多不如一行能跑的代码直观。下面是一个用Python写的教学简化版OAEP编码器不依赖任何加密库只为了展示核心流程import os import hashlib def i2osp(x, xlen): return x.to_bytes(xlen, big) def mgf1(seed, mask_len): h_len hashlib.sha256(b).digest_size full b counter 0 while len(full) mask_len: full hashlib.sha256(seed i2osp(counter, 4)).digest() counter 1 return full[:mask_len] def oaep_encode(message, k, labelb): h_len hashlib.sha256(b).digest_size m_len len(message) if m_len k - 2 * h_len - 2: raise ValueError(message too long) l_hash hashlib.sha256(label).digest() ps b\x00 * (k - m_len - 2 * h_len - 2) db l_hash ps b\x01 message seed os.urandom(h_len) db_mask mgf1(seed, k - h_len - 1) masked_db bytes(a ^ b for a, b in zip(db, db_mask)) seed_mask mgf1(masked_db, h_len) masked_seed bytes(a ^ b for a, b in zip(seed, seed_mask)) em b\x00 masked_seed masked_db return em这段代码里的db_mask、masked_db、seed_mask、masked_seed完全对应上面五步。真正用到生产环境时推荐直接用cryptography或OpenSSL这类成熟实现不要在业务代码里自己维护这套算法。教学版的意义是帮你建立每一步在干什么的心智模型以后排错的时候能快速定位数据流的哪一环出了问题。4. 从Bleichenbacher攻击看OAEP为什么更安全4.1 1998年的百万富翁攻击是怎么打穿PKCS#1 v1.5的在OAEP出现之前PKCS#1 v1.5填充通常写作RSAES-PKCS1-v1_5是RSA加密的主流。它的填充格式比较简单0x00 0x02开头跟一串非零随机字节再加0x00分隔符最后是明文。解密时服务器检查格式如果格式不对就返回padding error。1998年Daniel Bleichenbacher观察到当服务器对填充格式是否正确给出不同响应时攻击者可以把截获的密文反复改写并重新提交给服务器根据服务器是否解密成功判断自己的猜测是否正确。每次成功的查询都会让候选的明文区间缩小一半左右几十万到百万次查询之后攻击者就能恢复出完整的明文。这个攻击因此被称为Million Message Attack百万富翁攻击。它在当时能够实际攻破SSL 3.0等早期协议里采用PKCS#1 v1.5的RSA加密。这个案例给整个行业最重要的教训是加密方案的安全性不只取决于数学难题还取决于填充方案在错误响应下的信息泄露路径。你以为Oracles只在数据库里实际上一个返回不同错误码的解密接口就是一个Oracle。4.2 OAEP的随机化与不可延展性如何堵住这条路OAEP之所以能对抗上述攻击有几个层面解码端对EM、lHash、PS、0x01分隔符做全套校验任何一个环节不满足都统一返回decryption error。攻击者得不到哪一步错了的细粒度反馈。随机种子改变了整个编码块。攻击者改写密文的任何一个bit解码后EM分布就会完全随机化几乎不可能恰好构造出一个能通过全部校验的密文。OAEP的设计目标正是不可延展性攻击者无法在不知道明文的情况下通过修改密文得到另一个合法密文。这直接消灭了Bleichenbacher式攻击赖以存在的条件。4.3 一个容易忽略的点错误消息和时序照样能形成padding oracle这里有个极其重要的工程细节即使你用了OAEP如果在实现里把解密失败和填充校验失败区分开返回或者错误日志打印得过于详细攻击者还是可以利用响应差异做类似攻击。更隐蔽的是时序如果lHash比较不是恒定时间的或者不同错误分支的返回速度不一样攻击者可以通过统计响应时间来判断内部状态。所以在生产代码里我通常要求三件事解密失败统一抛同一个错误日志里不记录具体的填充失败原因所有敏感比较尽量使用恒定时间实现。这个习惯比换一个更强的填充算法更能决定系统真实的安全等级。5. 跨语言对齐指南Java、Python、OpenSSL、PB里的OAEP理论部分结束回到最实际的问题为什么同一个RSA密钥对Java和Python互相解密总是失败5.1 参数不对的解密失败长什么样OAEP实际使用时有三个参数必须两端完全一致哈希算法OAEP Digest掩码生成函数MGF1所用的哈希算法MGF1 Digest标签Label通常为空字符串但必须明确设置这三个参数一旦有一项不一致解密端就会在某个校验步骤挂掉报错往往是Decryption Error或者oaep decoding error。最坑的是报错信息完全看不出是哪个参数错了只能靠试。所以规范的做法是在项目文档或配置里把这三个参数明确写死做成常量两端共用同一份文档。5.2 Java从Cipher.getInstance到OAEPParameterSpecJava标准库处理OAEP最常用的写法是Cipher cipher Cipher.getInstance(RSA/ECB/OAEPWithSHA-256AndMGF1Padding); OAEPParameterSpec spec new OAEPParameterSpec( SHA-256, // 主哈希算法 MGF1, // 掩码生成函数固定为MGF1 MGF1ParameterSpec.SHA256, // MGF1内部哈希算法 PSource.PSpecified.DEFAULT // 标签默认空字符串 ); cipher.init(Cipher.ENCRYPT_MODE, publicKey, spec); byte[] encrypted cipher.doFinal(plainBytes);注意这里有个很经典的坑Cipher.getInstance字符串写作RSA/ECB/OAEPWithSHA-256AndMGF1Padding它只指定了主哈希是SHA-256没有指定MGF1用什么哈希。在新旧JDK版本里MGF1的默认值可能不同如果你没显式传OAEPParameterSpec很可能出现Java自己加密自己解密没问题但和Python端对不上的情况。所以只要涉及跨端联调就把OAEPParameterSpec写全不要依赖任何默认值。5.3 Pythoncryptography库的正确打开方式Python生态里最推荐的是cryptography库它的OAEP参数和Java完全对标from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes encrypted public_key.encrypt( bhello, padding.OAEP( mgfpadding.MGF1(algorithmhashes.SHA256()), algorithmhashes.SHA256(), labelNone ) )这个写法里外层OAEP用SHA-256MGF1也用SHA-256label为空和上面Java的OAEPParameterSpec完全一致。如果两端都用这套配置跨语言解密就能一次通过。如果你用的是pycryptodome的PKCS1_OAEP也要留意它默认的hashAlgo是SHA-1需要显式改成SHA-256并注意它的mgfunc参数默认依赖hashAlgo。5.4 OpenSSL命令行里的默认值警告用OpenSSL命令行做OAEP加解密时容易踩的坑是默认参数。命令大概是# 加密 openssl pkeyutl -encrypt -pubin -inkey public.pem -in plain.txt -out cipher.bin \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256 \ -pkeyopt rsa_mgf1_md:sha256 # 解密 openssl pkeyutl -decrypt -inkey private.pem -in cipher.bin -out plain.txt \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256 \ -pkeyopt rsa_mgf1_md:sha256如果你不显式写rsa_oaep_md和rsa_mgf1_mdOpenSSL在不同版本里的默认行为可能不同低版本常用SHA-1。当你用OpenSSL生成的密文交给Java端解密若两端的摘要参数不一致就会看到那个令人头疼的oaep decoding error。5.5 老平台接RSAPB这类环境的可行路线搜索热词里有pb调用rsa加密算法这里顺带聊一下PowerBuilder这类老平台。PB本身没有内置RSA实现我见过的可行方案大致有用PB调用封装好的C动态库底层用OpenSSL编译DLLPB通过外部函数声明调用。老项目里这个方案很常见但要注意二进制数据的字节数组传参、内存释放、异常处理维护成本比较高。用PB的.NET桥接PB 12.5直接调用System.Security.Cryptography中的RSA类。好处是不用额外造轮子坏处是部署环境需要.NET运行时。把RSA加解密独立成一个小服务PB只用HTTP调接口把公钥加密的需求转发出去。这是我现在最推荐的做法因为密钥、算法、参数都收敛在服务端PB端的改动最小也不会把私钥暴露给客户端。无论走哪条路OAEP的三个参数依然要有一份统一的定义别让PB端拿着一个只有RSA字样的简化封装去对接否则迟早会踩参数不一致的坑。5.6 顺手记住明文长度上限混合加密才是长文本归宿再强调一次长度上限2048位RSA SHA-256时OAEP最多加密190字节。如果你要加密的对象是几百上千字节的JSON、XML、表单数据千万别硬来。正确姿势是先随机生成一个AES密钥用AES-GCM加密业务数据再用RSA-OAEP加密这个AES密钥。这不仅是长度限制决定的也是性能要求的必然选择——RSA模幂运算比对称加密慢几个数量级。6. 那些年我们追过的报错密钥格式与公钥找不到排查链路6.1 rsa public key not find出现的几种真实场景rsa public key not find这个报错听起来像是算法库找不到公钥对象实际上绝大多数情况是公钥文件或公钥字节流没有被正确解析。我遇到过的主要有三类公钥文件是二进制DER格式但代码用文本方式读取读进来是一堆乱码字符解析必然失败。PEM文件头是BEGIN RSA PUBLIC KEYPKCS#1格式但代码或配置期望的是BEGIN PUBLIC KEYPKCS#8格式格式不匹配解析器抛异常。从KeyStore或配置中心取公钥时别名写错了或者键值对取出来是null算法层拿不到PublicKey对象。这类问题的坑点在于报错通常出现在很后面的RSA初始化环节让你误以为是算法参数问题但其实从头到尾是密钥加载问题。6.2 PEM、DER、PKCS#1、PKCS#8看完就不糊涂这几个词经常被混用简单梳理如下格式PEM头说明PKCS#1公钥-----BEGIN RSA PUBLIC KEY-----只包含RSA两个大整数n和ePKCS#8/SPKI公钥-----BEGIN PUBLIC KEY-----还包含算法标识现代系统默认PKCS#1私钥-----BEGIN RSA PRIVATE KEY-----传统格式部分库不直接支持PKCS#8私钥-----BEGIN PRIVATE KEY-----标准推荐格式可加密存储DER是二进制编码实际存储时就是一段字节流。PEM是先对DER做Base64编码再加一行-----BEGIN XXX-----头、一行-----END XXX-----尾的文本格式。PKCS#1格式的公钥只包含RSA的两个大整数n和ePKCS#8格式准确说公钥叫SubjectPublicKeyInfo出自X.509除了n和e还包含算法编号等元信息这也是现代系统默认使用的格式。大部分现代库和框架都推荐用PKCS#8格式的公钥、PKCS#8格式的私钥。如果你的代码里写的是BEGIN RSA PUBLIC KEY而对方给的是BEGIN PUBLIC KEY直接解析会失败需要在拿到密钥时先做一次格式转换。OpenSSL转换公钥格式的命令很直接# 从PKCS#1格式转成PKCS#8/SPKI格式 openssl rsa -pubin -in rsa_pub_pkcs1.pem -RSAPublicKey_out -out rsa_pub_pkcs8.pem # 查看公钥详细信息确认格式和N、E openssl pkey -pubin -in public.pem -text -noout6.3 排查公钥解析问题的标准步骤我自己的排查顺序是这样基本每次都能用上先看文件头。打开PEM文件确认头是BEGIN PUBLIC KEY还是BEGIN RSA PUBLIC KEY再根据目标语言支持的类型决定是否需要转换。如果是DER二进制确认读取模式是二进制方式别在Python里用open(path, r)读DER否则一定乱码。用OpenSSL的pkey命令验证公钥能不能正常解析、能不能打印出模数和指数。如果命令行都解析不了问题就在密钥文件本身。检查Base64解码后的字节长度。2048位RSA的PKCS#1公钥DER编码通常在270字节左右SPKI格式通常在292字节左右。如果长度差得离谱说明文件根本不是公钥或者Base64字符串本身有问题。再看业务代码。Java里用X509EncodedKeySpec对应SPKI公钥用RSAPublicKeySpec是需要你手动提供n和e的。你不想把n和e拆出来的话就用X509EncodedKeySpec前提是公钥是SPKI格式。把上面五步走完绝大多数rsa public key not find都能定位到根因。它和OAEP参数是两个维度的坑密钥格式坑在于文件根本加载不出来OAEP参数坑在于文件加载出来了但解密对不上。建议排查时先过密钥格式这一关再去看填充参数。7. 从填充往外看RSA加密项目里的安全实践清单7.1 公因子攻击看似遥远的真实威胁搜索热词里有rsa公因子的网络攻击案例这里专门拎出来说一下。所谓公因子攻击是指如果两个不同的RSA模数N1和N2共享了一个素数因子p那么求两者最大公约数gcd(N1, N2) p然后就能分别分解这两个模数把两个私钥都还原出来。2012年有学术团队扫描了互联网上的大量公共证书结果真的发现了不少共享素因子的模数。问题根源不在RSA算法本身而在部分老旧设备的随机数生成器熵不足它们生成的素数集中在一个很小的集合里碰撞概率远高于预期。也就是说这些设备在掷骰子的时候掷出来的点数高度重复。这对我们的启示是填充解决的是加密过程中的语义安全问题公因子攻击解决的是密钥生成过程中的随机源问题。两者都要管。在代码里使用操作系统级安全随机源比如Python的secrets模块、Java的SecureRandom不要用自研的伪随机算法去生成密钥这是底线。有条件的话可以在密钥或证书入库前做一次检测检查是否存在与其他库存密钥共享因子的情况。7.2 密钥长度与哈希算法的搭配关于RSA密钥长度当前的主流建议是至少2048位新系统可以直接上3072或4096。更长的密钥带来更高的计算开销在服务端高频解密时差异非常明显所以具体选多少位要结合性能预算来权衡。OAEP里哈希算法的选择SHA-256是下限不要再用SHA-1。虽然SHA-1在OAEP中并没有直接被找到碰撞利用但SHA-1本身已经进入出局倒计时没必要给审计人员留话柄。同时要记得换了哈希算法明文长度上限也要跟着重算。从SHA-1换成SHA-2562048位密钥下最大明文从214字节掉到190字节这个变化在系统设计时很容易被忽略。7.3 我自己的RSA加密项目检查单最后把我这些年踩坑总结出的检查单放出来新项目直接照着过一遍能省不少联调时间私钥只放在服务端客户端只分发公钥。公钥统一转成PKCS#8/PEM格式私钥统一用PKCS#8加密存储。OAEP参数在项目文档里写死主哈希、MGF1哈希、label三个都写明跨端统一。长数据处理采用AES-GCM加密业务数据 RSA-OAEP加密会话密钥的混合方案。解密失败统一抛decryption error日志不记录具体校验失败的原因。敏感比较使用恒定时间函数避免时序侧信道。密钥生成使用系统安全随机源不使用时间戳、进程ID等弱种子。定期检查库存公钥是否存在重复素因子。如果用了PB等老平台优先把RSA逻辑收敛到服务端避免客户端持有完整密钥材料。这份清单里的每一条背后都有真实项目踩坑的影子。OAEP本身只是RSA加密体系里的一小块但它牵涉到的参数一致性问题、密钥格式问题、随机源问题、错误处理问题才是实际交付时真正决定系统安全性的部分。最后说点个人体会。刚开始做RSA对接的时候我也以为填充只是加几个零补齐长度直到有一天盯着解密耗时的日志发现某些错误分支的响应明显快几十毫秒才意识到这些细节在安全模型里有多大的分量。从那以后每次做加密方案评审我都会把填充参数和错误处理当成正式议题而不是粘贴一段现成代码就收工。加密这里的坑往往不在数学题本身而在那些看着能用的默认值里。
返回列表