You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Node.js RSA加密不同填充模式下数据大小不匹配问题排查及动态适配方案咨询

Node.js RSA加密不同填充模式下数据大小不匹配问题排查及动态适配方案咨询

我来帮你梳理下这个问题的核心原因,以及给出适合动态场景的解决方案。

遇到的错误详情

1. 使用crypto.constants.RSA_NO_PADDING时的报错

node:internal/crypto/cipher:79
return method(data, format, type, passphrase, buffer, padding, oaepHash,
^
Error: error:0200007A:rsa routines::data too small for key size
at Object.publicEncrypt (node:internal/crypto/cipher:79:12)
at Object._encryptFile (C:\Users\GB\Documents\projects\customprojects\numericalarrays\pickler\node_modules\hasher-apis\src\hasher.js:260:28)
at Object.<anonymous> (C:\Users\GB\Documents\projects\customprojects\numericalarrays\pickler\demos\demos.jscertificate.pickle.js:44:49)
at Module._compile (node:internal/modules/cjs/loader:1149:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1203:10)
at Module.load (node:internal/modules/cjs/loader:1027:32)
at Module._load (node:internal/modules/cjs/loader:868:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:23:47 {
library: 'rsa routines',
reason: 'data too small for key size',
code: 'ERR_OSSL_RSA_DATA_TOO_SMALL_FOR_KEY_SIZE'
}
Node.js v18.10.0

2. 使用crypto.constants.RSA_PKCS1_OAEP_PADDING时的报错

node:internal/crypto/cipher:79
return method(data, format, type, passphrase, buffer, padding, oaepHash,
^
Error: error:0200006E:rsa routines::data too large for key size
at Object.publicEncrypt (node:internal/crypto/cipher:79:12)
at Object._encryptFile (C:\Users\GB\Documents\projects\customprojects\numericalarrays\pickler\node_modules\hasher-apis\src\hasher.js:260:28)
at Object.<anonymous> (C:\Users\GB\Documents\projects\customprojects\numericalarrays\pickler\demos\demos.jscertificate.pickle.js:44:49)
at Module._compile (node:internal/modules/cjs/loader:1149:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1203:10)
at Module.load (node:internal/modules/cjs/loader:1027:32)
at Module._load (node:internal/modules/cjs/loader:868:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:23:47 {
library: 'rsa routines',
reason: 'data too large for key size',
code: 'ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE'
}
Node.js v18.10.0

你的相关代码实现

加密文件函数 _encryptFile

function _encryptFile(remotePath, remoteDestPath, algorithm = "sha256", keyAlgorithm = "rsa", digest = "base64", keyOptions = { modulusLength: 2048 }, options = { modulusLength: 2048 }) {
const crypto = require('crypto');
let data = fs.readFileSync(remotePath, { encoding: options.encoding ? options.encoding : "utf-8", flag: "r" });
algorithm = algorithm || "sha256";
keyAlgorithm = keyAlgorithm || "rsa";
digest = digest || "base64";
keyOptions = keyOptions || { modulusLength: 2048 };
options = options || { modulusLength: 2048 };
const { privateKey, publicKey } = _genKeyPair(keyAlgorithm, keyOptions);
let encrypted = crypto.publicEncrypt({
key:  publicKey,
padding: crypto.constants.RSA_PKCS1_PADDING,
oaepHash: algorithm
},
Buffer.from(data)
).toString(digest);
fs.writeFileSync(remoteDestPath, encrypted);
return {
privateKey: privateKey,
publicKey: publicKey,
encrypted: encrypted
}
}

解密文件函数 _decryptFile

function _decryptFile(remotePath, remoteDestPath, privateKey, algorithm = "sha256", keyAlgorithm = "rsa", digest = "base64", options = { modulusLength: 2048 }) {
const crypto = require('crypto');
let hashdata = fs.readFileSync(remotePath, { encoding: options.encoding ? options.encoding : "utf-8", flag: "r" });
algorithm = algorithm || "sha256";
keyAlgorithm = keyAlgorithm || "rsa";
digest = digest || "base64";
options = options || { modulusLength: 2048 };
let decrypted = crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING,
oaepHash: algorithm
},
Buffer.from(hashdata, digest)
);
fs.writeFileSync(remoteDestPath, decrypted);
return {
decrypted: decrypted.toString("utf-8")
}
}

密钥生成函数 _genKeyPair

function _genKeyPair(keyGenType = "rsa", options = { modulusLength: 2048 }) {
const crypto = require('crypto');
const { privateKey, publicKey } = crypto.generateKeyPairSync(keyGenType, options);
return { privateKey, publicKey }
}

函数调用示例

// Hash P3 File to P3
let { privateKey, publicKey, encrypted } = hash.encrypt(p1, p1, "sha256", "rsa", "base64", { modulusLength: 2048 }, { modulusLength: 2048 });
// let { privateKey, publicKey, encrypted } = hash.encrypt(p3, p1);
console.log("[hasher._fileHash]: File hashed");
console.log(encrypted);
// DeHash P3 File hashed content to P3
let r3 = hash.decrypt(p1, p1, privateKey, "sha256", "rsa", "base64", { modulusLength: 2048 });
// let r3 = hash.decrypt(p1, p1, privateKey);
console.log("[hasher._fileHash]: File dehashed");
console.log(r3);

测试文件内容

触发“数据过大”错误的文件内容

  1. C
  2. C++
  3. Java

This is a file containing a collection of interpreted programming languages.

  1. Python
  2. Nodejs
  3. Ruby

触发“数据过小”错误的文件内容

  1. C
  2. C++
  3. Java

问题核心原因

本质上是不同RSA填充模式对明文数据的长度要求完全不同,这是你遇到问题的关键:

  • RSA_NO_PADDING:这种模式完全不添加任何填充内容,要求明文的字节长度必须严格等于RSA密钥的模长(比如你用的2048位密钥,对应256字节)。你的第二个测试文件内容远小于256字节,所以直接触发data too small for key size错误。而且这种模式几乎不会在实际场景中使用,完全没有安全性保障,强烈不推荐。
  • RSA_PKCS1_PADDING:这是传统的PKCS#1 v1.5填充,它会给明文添加固定长度的填充内容。对于2048位密钥,最大允许的明文长度是 密钥模长字节数 - 11(也就是256-11=245字节)。你的测试文件内容刚好在这个范围内,所以能正常工作。
  • RSA_PKCS1_OAEP_PADDING:这是更安全的OAEP填充,它的填充长度更长,依赖于你指定的哈希算法(比如SHA256)。对于2048位密钥+SHA256,最大明文长度是 密钥模长字节数 - 2*哈希长度 - 2(256 - 2*32 -2 = 190字节)。你的第一个测试文件内容超过了这个长度,所以触发data too large for key size错误。

另外要注意:RSA本身是非对称加密算法,天生适合加密小数据(比如对称密钥、哈希值),直接用它加密任意大小的文件本身就是不合理的用法,效率极低还容易触发这类长度问题。

动态场景的解决方案:混合加密

正确的做法是使用混合加密(RSA+对称加密),既保证安全性,又能处理任意大小的文件,步骤如下:

  1. 生成一个随机的对称加密密钥(比如AES-256-GCM)
  2. 用对称密钥加密大文件内容
  3. 用RSA公钥加密这个对称密钥
  4. 解密时,先用RSA私钥解密出对称密钥,再用对称密钥解密文件内容

改进后的加密函数示例

function _encryptFile(remotePath, remoteDestPath, algorithm = "sha256", keyAlgorithm = "rsa", digest = "base64", keyOptions = { modulusLength: 2048 }, options = {}) {
    const crypto = require('crypto');
    const fs = require('fs');

    // 读取文件为Buffer,兼容二进制文件
    const fileBuffer = fs.readFileSync(remotePath, { encoding: null });

    // 生成AES-256密钥和GCM模式的IV(推荐12字节)
    const aesKey = crypto.randomBytes(32);
    const iv = crypto.randomBytes(12);

    // 用AES加密文件内容
    const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
    const encryptedFileData = Buffer.concat([cipher.update(fileBuffer), cipher.final()]);
    const authTag = cipher.getAuthTag(); // GCM模式需要保存认证标签用于解密验证

    // 生成RSA密钥对并加密AES密钥
    const { privateKey, publicKey } = _genKeyPair(keyAlgorithm, keyOptions);
    const encryptedAesKey = crypto.publicEncrypt({
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: algorithm
    }, aesKey).toString(digest);

    // 拼接所有加密后的数据:IV(12) + 认证标签(16) + 加密的AES密钥(256) + 加密的文件内容
    const finalEncryptedBuffer = Buffer.concat([
        iv,
        authTag,
        Buffer.from(encryptedAesKey, digest),
        encryptedFileData
    ]);

    fs.writeFileSync(remoteDestPath, finalEncryptedBuffer);

    return {
        privateKey,
        publicKey,
        encryptedAesKey
    };
}

改进后的解密函数示例

function _decryptFile(remotePath, remoteDestPath, privateKey, algorithm = "sha256", keyAlgorithm = "rsa", digest = "base64", options = {}) {
    const crypto = require('crypto');
    const fs = require('fs');

    const encryptedBuffer = fs.readFileSync(remotePath);

    // 拆分各部分数据
    const iv = encryptedBuffer.slice(0, 12);
    const authTag = encryptedBuffer.slice(12, 28);
    const encryptedAesKeyBuffer = encryptedBuffer.slice(28, 28 + 256);
    const encryptedFileData = encryptedBuffer.slice(28 + 256);

    // 解密AES密钥
    const aesKey = crypto.privateDecrypt({
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: algorithm
    }, encryptedAesKeyBuffer);

    // 用AES解密文件内容
    const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
    decipher.setAuthTag(authTag);
    const decryptedFileData = Buffer.concat([decipher.update(encryptedFileData), decipher.final()]);

    // 保存解密后的文件,支持指定编码
    fs.writeFileSync(remoteDestPath, decryptedFileData, { encoding: options.encoding || "utf-8" });

    return {
        decrypted: decryptedFileData.toString(options.encoding || "utf-8")
    };
}

额外注意事项

  • 如果你非要坚持只用RSA加密(非常不推荐),需要根据填充模式计算允许的最大明文长度,然后将文件分块加密再拼接,但这种方式效率极低,安全性也不如混合加密。
  • 永远不要使用RSA_NO_PADDING,它没有任何抗攻击能力,属于危险用法。
  • 对于RSA密钥长度,2048位是当前的基本安全标准,推荐使用3072位或更高的密钥长度来提升安全性。

备注:内容来源于stack exchange,提问作者Gary

火山引擎 最新活动