scrypt 参数失败后 RSA 私钥突然无法解析:Node 的 OpenSSL 错误队列问题
Node 的 scrypt 参数因内存超限失败后,下一次合法私钥解析可能读取残留的 OpenSSL 错误。按前后调用顺序识别,保留派生强度和签名算法约定。
同一份 RSA 私钥刚才还能解析,调用一次失败的 scryptSync() 后,接下来的 createPrivateKey() 却报 ERR_OSSL_EVP_MEMORY_LIMIT_EXCEEDED。再次解析又正常时,先检查前一条 scrypt 是否因内存参数超限报过 ERR_CRYPTO_INVALID_SCRYPT_PARAMS,不要立即重新生成密钥。
已确认的具体环境是 Windows 11 x64、Node.js 24.13.0 与 OpenSSL 3.5.4。这里讨论的是失败的 scrypt 参数校验影响后续加密调用,不能据此解释所有私钥解析失败,也没有依据把该环境的复现范围扩到全部平台和版本。
把两次错误按先后顺序放回同一条调用链
需要查到的顺序是:同一份密钥正常解析,scrypt 因内存限制被拒绝,随后第一次密钥解析报内存限制,下一次又恢复。经过 JWT 封装后,后一个错误也可能表现为 secretOrPrivateKey must be an asymmetric key when using RS256,看起来像密钥格式忽然变了。
一个会超出256 MiB限制的参数组合是:
const options = {
N: 131072,
r: 32,
p: 1,
maxmem: 256 * 1024 * 1024,
};
这里只列出用于识别故障的配置,不应把它放进业务中反复触发。scrypt 的内存检查大致涉及 128 * N * r,该组合仅这一项就约512 MiB,还未包括额外开销。maxmem 是允许的内存上限,不是为进程预留的内存。
核对真实进程的 process.version、process.versions.openssl 和 scrypt 参数来源,并记录两个错误的时间顺序。日志中保留错误码、算法和配置足够,不要输出私钥、口令或派生密钥。只有后一条私钥错误、没有前面的超限路径,还不能认定属于本文问题。
参数错误处理了,底层错误仍可能留着
这一分支的参数校验会接触 OpenSSL 错误队列。受影响实现读取了校验失败的信息,却没有在返回时清理相应错误,后续无关的密钥解析可能读到残留状态。因此,JavaScript 层捕获了 scrypt 异常,不代表底层队列也已恢复。
并非所有 ERR_CRYPTO_INVALID_SCRYPT_PARAMS 都有同样后果。例如 N 不是2的幂,与内存超限属于不同校验条件,不能只按错误码统一归因。异步 crypto.scrypt() 的参数校验也可能同步抛错,不能仅在回调里处理全部失败。
先阻止无效参数进入加密调用
检查参数是否经过可信的配置入口,是否在更新成本参数时遗漏了 maxmem 和并发预算。固定参数可在服务接收任务前检查;外部传来的参数应受上限约束,不能任意交给 scrypt 消耗资源。
不要为了让校验通过就直接降低已有密码派生强度,也不要无限提高内存上限。保留原安全要求,按单次内存、并发任务数和运行容器限制决定可承受组合;旧口令记录若使用不同参数,还应保留其验证规则,不能全局替换后让历史口令失效。
同样不应把 RSA 签名错误临时改成 HMAC 算法,或关闭密钥类型检查。那会改变认证约定,掩盖真正的故障。增加一次“空解析”来消耗残留错误也不适合作为正式修复:失败可能落到其他加密操作上。
修复已于2026年10月19日合入 Node 源码,为参数校验增加错误队列清理,并覆盖同步与异步接口的回归检查。已核对的24.22.0、26.12.0正式发布均早于这次合并,截至10月20日不能据此宣称安装这些版本已修复。更新时确认实际发行版本包含该改动。
验收应覆盖一次正常派生、一次受控的参数拒绝,以及拒绝后的合法私钥解析与原签名流程;由有权限的人员在隔离环境执行,不使用生产口令。正常配置不再触发超限,只说明避开了触发路径;若要确认运行时修复,还需要检查失败之后的合法加密调用仍能正常完成。


