使用字符串作为加密 API 的输入


【Using strings as inputs to cryptographic APIs】

出于历史原因,Node.js 提供的许多加密 API 接受字符串作为输入,而底层的加密算法实际上是对字节序列进行操作。这些情况包括明文、密文、对称密钥、初始化向量、密码短语、盐、认证标签以及附加认证数据。

【For historical reasons, many cryptographic APIs provided by Node.js accept strings as inputs where the underlying cryptographic algorithm works on byte sequences. These instances include plaintexts, ciphertexts, symmetric keys, initialization vectors, passphrases, salts, authentication tags, and additional authenticated data.】

将字符串传给加密 API 时,请考虑以下因素。

【When passing strings to cryptographic APIs, consider the following factors.】

  • 并非所有字节序列都是有效的 UTF-8 字符串。因此,当一个长度为 n 的字节序列由字符串生成时,其熵通常低于随机或伪随机 n 字节序列的熵。例如,没有任何 UTF-8 字符串会生成字节序列 c0 af。密钥几乎应完全是随机或伪随机的字节序列。

  • 类似地,当将随机或伪随机字节序列转换为 UTF-8 字符串时,那些不表示有效代码点的子序列可能会被替换为 Unicode 替换字符(U+FFFD)。因此,生成的 Unicode 字符串的字节表示可能不等于创建字符串时的原始字节序列。

    const original = [0xc0, 0xaf];
    const bytesAsString = Buffer.from(original).toString('utf8');
    const stringAsBytes = Buffer.from(bytesAsString, 'utf8');
    console.log(stringAsBytes);
    // Prints '<Buffer ef bf bd ef bf bd>'. 

    密码、哈希函数、签名算法和密钥派生函数的输出是伪随机字节序列,不应作为 Unicode 字符串使用。

  • 当字符串来自用户输入时,一些 Unicode 字符可以用多种等效方式表示,这会导致不同的字节序列。例如,将用户密码短语传递给密钥派生函数(如 PBKDF2 或 scrypt)时,密钥派生函数的结果取决于字符串是使用组合字符还是分解字符。Node.js 不会对字符表示进行规范化。开发者应考虑在将用户输入传递给加密 API 之前,对其使用 String.prototype.normalize() 进行处理。