← console.debug() 方法 crypto.setFips() 方法 →

Node.js crypto.getCiphers()方法详解:支持算法列表查询、平台差异与安全选型指南

著
原创 2026-10-16 Node.js 已有人查阅

getCiphers()的方法定位

crypto.getCiphers()是Node.jscrypto模块的静态方法,返回当前运行环境所支持的全部密码算法名称数组。该方法无需参数,调用后直接返回字符串列表,例如['aes-128-cbc','aes-192-ccm','aes-256-gcm',...]。

这个方法的实用价值不在于“列出所有算法”本身,而在于运行时的算法可用性探测。当应用需要在不同Node.js版本、不同操作系统、不同编译选项(如Electron使用BoringSSL替代OpenSSL)之间保持行为一致时,直接假设某个算法一定存在是不稳妥的。先调用getCiphers()确认目标算法在列表中,再执行createCipheriv,可以避免运行时的ERR_CRYPTO_INVALID_ARGUMENT异常。

返回值特征与版本演进

在Node.jsv0.9.3中,getCiphers()被正式加入crypto模块。早期版本(v0.10.2之前)返回的列表包含了SSL/TLS的密码套件,后续更新将其修正为仅返回非SSL的密码算法。

在OpenSSL1.1.1之前,算法名称可能以大写和小写两种形式同时出现;从OpenSSL1.1.1开始,只返回小写形式。这一行为变化影响了依赖精确字符串匹配的代码。如果应用中存在ciphers.includes('AES-256-CBC')这类判断,在较新的Node.js环境中会得到false。

基本用法

const crypto = require('crypto');

const ciphers = crypto.getCiphers();
console.log(`代码号学习编程 - 当前环境支持 ${ciphers.length} 种密码算法`);

// 检查特定算法是否可用
const target = 'aes-256-gcm';
if (ciphers.includes(target)) {
  console.log(`${target} 可用`);
} else {
  console.error(`${target} 在当前环境不可用`);
}

四、权威来源引用

Node.js官方文档明确将getCiphers()定义为返回“支持的密码算法名称数组”的静态方法。Deno和Electron的兼容性文档也确认了该方法的存在,但算法列表因底层加密库不同而有差异。PHP官方的openssl_get_cipher_methods()提供了语义对等的能力,其文档同样标注了OpenSSL1.1.1版本的大小写行为变化。

PHP环境下的对照差异

PHP通过openssl_get_cipher_methods(bool$aliases=false)获取可用密码方法列表。与Node.js的getCiphers()存在几处关键差异:

参数差异:PHP方法接受一个可选布尔参数,传入true时会返回包含别名的列表;Node.js的getCiphers()不接受任何参数。

返回值格式:PHP返回的数组在OpenSSL1.1.1之前同时包含大小写形式,之后仅返回小写。Node.js同样在OpenSSL1.1.1之后统一为小写。

环境依赖性:PHP的列表取决于编译时链接的OpenSSL版本和配置。同一PHP版本在不同Linux发行版上可能返回不同的算法。Node.js同样如此,但Node.js官方构建通常捆绑特定版本的OpenSSL,行为一致性相对更高。

如果项目需要PHP与Node.js之间的加密互操作,不能依赖“两边都列出了aes-256-gcm就一定能互通”。还需要确认密钥派生方式、IV长度、认证标签处理策略对齐。

单元测试与边界测试

const assert = require('assert');
const crypto = require('crypto');

// 返回值为字符串数组
const ciphers = crypto.getCiphers();
assert.ok(Array.isArray(ciphers));
assert.ok(ciphers.length > 0);
assert.ok(ciphers.every(c => typeof c === 'string'));

// 常见的 AES 算法应当存在(在标准 Node.js 构建中)
const expectedAes = ['aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc'];
expectedAes.forEach(algo => {
  assert.ok(ciphers.includes(algo), `${algo} 应存在于标准构建中`);
});

// 无参数调用:传入参数不会抛错但会被忽略
assert.deepStrictEqual(crypto.getCiphers('ignored'), ciphers);

// XTS 算法的特殊约束:密钥必须为双倍块大小
if (ciphers.includes('aes-256-xts')) {
  // XTS 要求密钥长度是块大小的两倍(AES-256-XTS 需要 64 字节密钥)
  const key = crypto.randomBytes(64);
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-xts', key, iv);
  const out = Buffer.concat([cipher.update(Buffer.alloc(32)), cipher.final()]);
  assert.ok(out.length > 0);
}

常见错误与异常处理

getCiphers()本身不会抛出异常。真正的风险在于基于返回列表选择算法后的后续调用。

算法存在但参数不匹配:aes-256-cbc-hmac-sha1在列表中可见,但直接用于createDecipheriv时可能触发TypeError:error:00000000:lib(0):func(0):reason(0)。这类“算法名在列表中但OpenSSL内部不支持该操作组合”的情况,属于底层实现的限制,无法仅通过getCiphers()预判。

XTS密钥长度约束:aes-256-xts要求密钥长度为64字节(两个AES-256密钥),传入32字节密钥会抛出ERR_CRYPTO_INVALID_KEYLEN。

废弃算法的隐性风险:getCiphers()返回的列表中可能包含RC4、DES等已被认为不安全的算法。列表的存在不代表这些算法适合用于新项目。

与相邻函数的对比

函数 职责 返回值
crypto.getCiphers() 列出密码算法 算法名称字符串数组
crypto.getHashes() 列出摘要算法 摘要名称字符串数组,如'sha256'
crypto.getCurves() 列出椭圆曲线 曲线名称数组,如'prime256v1'

三者共享相同的方法签名设计:无参数、返回字符串数组、反映当前OpenSSL/BoringSSL环境的能力。在需要完整展示平台加密能力的场景中,可以依次调用三者构建能力清单。

算法选型的结构化映射

getCiphers()返回的是扁平字符串列表,在项目中通常需要按模式归类以便选型:

function categorizeCiphers(ciphers) {
  const groups = { gcm: [], ccm: [], cbc: [], xts: [], other: [] };
  for (const name of ciphers) {
    if (name.endsWith('-gcm')) groups.gcm.push(name);
    else if (name.endsWith('-ccm')) groups.ccm.push(name);
    else if (name.endsWith('-cbc')) groups.cbc.push(name);
    else if (name.endsWith('-xts')) groups.xts.push(name);
    else groups.other.push(name);
  }
  return groups;
}

const groups = categorizeCiphers(crypto.getCiphers());
console.log('GCM 算法:', groups.gcm); // ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm']

这种分类在需要根据安全策略动态选择算法时特别有用:优先选择GCM模式(认证加密),降级时选择CBC配合独立的HMAC。

性能测试差异

getCiphers()的执行开销主要取决于底层加密库返回算法列表的速度,通常极为轻量。真正的性能差异体现在不同算法在加解密操作中的表现。

function benchmarkCipher(algo, iterations = 10000) {
  const keyLen = algo.includes('256') ? 32 : 16;
  const key = crypto.randomBytes(keyLen);
  const iv = crypto.randomBytes(16);
  const data = Buffer.from('代码号学习编程 - 性能测试负载');

  const start = process.hrtime.bigint();
  for (let i = 0; i < iterations; i++) {
    const cipher = crypto.createCipheriv(algo, key, iv);
    cipher.update(data);
    cipher.final();
  }
  return Number(process.hrtime.bigint() - start) / 1e6;
}

// 对比 AES-256-GCM 与 AES-256-CBC
const ciphers = crypto.getCiphers();
if (ciphers.includes('aes-256-gcm') && ciphers.includes('aes-256-cbc')) {
  console.log('aes-256-gcm 耗时:', benchmarkCipher('aes-256-gcm').toFixed(2), 'ms');
  console.log('aes-256-cbc 耗时:', benchmarkCipher('aes-256-cbc').toFixed(2), 'ms');
}

在多数现在CPU上,AES-GCM因硬件加速指令(AES-NI)的支持而具有竞争力。CBC模式在相同的硬件条件下通常略慢,但差异幅度取决于具体的负载大小和迭代次数。

职责边界

getCiphers()的职责是运行时能力枚举。它不负责算法安全性评估(RC4和AES-256-GCM会同时出现在列表中),不负责密钥管理(密钥派生和存储由应用层决定),不负责算法选型策略(“优先GCM”是工程决策而非API职责)。将getCiphers()视为“环境支持什么”的事实查询工具,而非“应该用什么”的建议来源,是正确使用该方法的前提。

常见误区速查

误区一:认为列表中的算法都适合生产使用。RC4、DES、Blowfish等算法仍可能出现在某些构建的返回列表中,但它们不符合现在安全标准。

误区二:跨平台假设算法一致性。Electron应用使用BoringSSL,其算法列表与Node.js官方构建的OpenSSL列表存在差异。在Electron中测试通过的算法选择逻辑,部署到Node.js服务端可能失效。

误区三:忽略大小写变化。OpenSSL1.1.1之前的返回值可能同时包含'AES-256-CBC'和'aes-256-cbc',精确匹配需要做规范化处理。

误区四:用getCiphers()替代算法安全评估。该方法只回答“是否支持”,不回答“是否安全”。

专业级教程补充内容

练习思考题

  1. 编写一个函数,接收getCiphers()的返回列表和一个算法名,判断该算法是否可用,同时判断其是否属于AEAD(认证加密)模式。AEAD模式的特征后缀有哪些?

  2. 如果需要在Electron主进程和Node.js服务端之间传输加密数据,如何设计算法协商机制,使两端都能使用各自环境支持的算法?

  3. 假设getCiphers()返回了'aes-256-cbc-hmac-sha1',但实际调用createCipheriv时抛出TypeError。这说明getCiphers()的语义边界在哪里?

现在框架中的惯用写法

在NestJS的加密模块配置中,通常会在启动时进行一次算法可用性检查:

// crypto-capability.provider.ts
export const CryptoCapabilityProvider = {
  provide: 'CRYPTO_CAPABILITIES',
  useFactory: () => {
    const ciphers = crypto.getCiphers();
    return {
      hasGcm: ciphers.some(c => c.endsWith('-gcm')),
      hasCcm: ciphers.some(c => c.endsWith('-ccm')),
      preferred: ciphers.includes('aes-256-gcm')
        ? 'aes-256-gcm'
        : ciphers.includes('aes-256-cbc')
          ? 'aes-256-cbc'
          : null
    };
  }
};

这种模式将算法可用性探测的结果注入到依赖注入容器中,业务代码无需反复调用getCiphers()。

项目反思

在一个跨平台桌面应用中,主进程使用Electron(BoringSSL),渲染进程通过IPC调用主进程的加密服务,同时存在一个CLI工具使用标准Node.js(OpenSSL)处理同一批加密文件。

问题出现在:CLI工具使用aes-256-ocb加密的文件,Electron端无法解密。排查发现BoringSSL不支持OCB模式,crypto.getCiphers()在Electron中根本不返回任何-ocb算法。

教训:如果应用跨越多个加密运行时,算法选型必须取交集,或者通过显式的算法协商机制确定双方都支持的算法。getCiphers()是发现差异的工具,但差异的解决需要架构层面的设计。

类型安全封装

TypeScript项目中可以为getCiphers()添加语义化的返回类型,但字符串数组本身的类型表达力有限。更实用的做法是在编译期维护一个“已知安全算法”的常量集,运行时与该集做交集:

const RECOMMENDED_AEAD = ['aes-256-gcm', 'aes-192-gcm', 'aes-128-gcm'] as const;
type RecommendedAead = typeof RECOMMENDED_AEAD[number];

function selectAeadAlgorithm(available: string[]): RecommendedAead | null {
  for (const algo of RECOMMENDED_AEAD) {
    if (available.includes(algo)) return algo;
  }
  return null;
}

const algo = selectAeadAlgorithm(crypto.getCiphers());
if (!algo) {
  throw new Error('当前环境缺少推荐的 AEAD 算法');
}

延伸阅读与参考文献

Node.js官方文档—Crypto
https://nodejs.org/api/crypto.html
crypto模块的完整API参考,getCiphers()的定义位于“crypto.getCiphers()”章节。

Node.jsv0.9.3发布说明
https://nodejs.org/en/blog/release/v0.9.3
getCiphers()和getHashes()首次加入crypto模块的版本说明。

Node.jsv0.10.2发布说明
https://nodejs.org/en/blog/release/v0.10.2
修复getCiphers()使其仅返回非SSL密码算法的变更记录。

PHPopenssl_get_cipher_methods手册
https://www.php.net/manual/en/function.openssl-get-cipher-methods.php
PHP对等方法的官方文档,包含OpenSSL1.1.1大小写行为变化的说明。

GitHubIssue#7583—aes-256-cbc-hmac-sha1TypeError
https://github.com/nodejs/node-v0.x-archive/issues/7583
算法出现在列表中但实际调用失败的典型案例,揭示了getCiphers()返回值的语义边界。

NISTSP800-131A—加密算法过渡指南
https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
算法强度和过渡期的权威参考,解释了为什么RC4和DES不应继续用于新项目。

← console.debug() 方法 crypto.setFips() 方法 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号