Node.js v18.18.2 文档


目录

WEB加密 API#

稳定性:1 - 实验性

Node.js 提供了标准Web Crypto API的实现。

使用require('node:crypto').webcrypto访问此模块。

const { subtle } = require('node:crypto').webcrypto;

(async function() {

  const key = await subtle.generateKey({
    name: 'HMAC',
    hash: 'SHA-256',
    length: 256,
  }, true, ['sign', 'verify']);

  const enc = new TextEncoder();
  const message = enc.encode('I love cupcakes');

  const digest = await subtle.sign({
    name: 'HMAC',
  }, key, message);

})(); 

例子#

生成密钥#

<SubtleCrypto>类可用于生成对称(秘密)密钥或非对称密钥对(公钥和私钥)

AES 密钥#
const { subtle } = require('node:crypto').webcrypto;

async function generateAesKey(length = 256) {
  const key = await subtle.generateKey({
    name: 'AES-CBC',
    length,
  }, true, ['encrypt', 'decrypt']);

  return key;
} 
ECDSA 密钥对#
const { subtle } = require('node:crypto').webcrypto;

async function generateEcKey(namedCurve = 'P-521') {
  const {
    publicKey,
    privateKey,
  } = await subtle.generateKey({
    name: 'ECDSA',
    namedCurve,
  }, true, ['sign', 'verify']);

  return { publicKey, privateKey };
} 
Ed25519/Ed448/X25519/X448 密钥对#

稳定性:1 - 实验性

const { subtle } = require('node:crypto').webcrypto;

async function generateEd25519Key() {
  return subtle.generateKey({
    name: 'Ed25519',
  }, true, ['sign', 'verify']);
}

async function generateX25519Key() {
  return subtle.generateKey({
    name: 'X25519',
  }, true, ['deriveKey']);
} 
HMAC 密钥#
const { subtle } = require('node:crypto').webcrypto;

async function generateHmacKey(hash = 'SHA-256') {
  const key = await subtle.generateKey({
    name: 'HMAC',
    hash,
  }, true, ['sign', 'verify']);

  return key;
} 
RSA 密钥对#
const { subtle } = require('node:crypto').webcrypto;
const publicExponent = new Uint8Array([1, 0, 1]);

async function generateRsaKey(modulusLength = 2048, hash = 'SHA-256') {
  const {
    publicKey,
    privateKey,
  } = await subtle.generateKey({
    name: 'RSASSA-PKCS1-v1_5',
    modulusLength,
    publicExponent,
    hash,
  }, true, ['sign', 'verify']);

  return { publicKey, privateKey };
} 

加密与解密#

const crypto = require('node:crypto').webcrypto;

async function aesEncrypt(plaintext) {
  const ec = new TextEncoder();
  const key = await generateAesKey();
  const iv = crypto.getRandomValues(new Uint8Array(16));

  const ciphertext = await crypto.subtle.encrypt({
    name: 'AES-CBC',
    iv,
  }, key, ec.encode(plaintext));

  return {
    key,
    iv,
    ciphertext,
  };
}

async function aesDecrypt(ciphertext, key, iv) {
  const dec = new TextDecoder();
  const plaintext = await crypto.subtle.decrypt({
    name: 'AES-CBC',
    iv,
  }, key, ciphertext);

  return dec.decode(plaintext);
} 

导出和导入密钥#

const { subtle } = require('node:crypto').webcrypto;

async function generateAndExportHmacKey(format = 'jwk', hash = 'SHA-512') {
  const key = await subtle.generateKey({
    name: 'HMAC',
    hash,
  }, true, ['sign', 'verify']);

  return subtle.exportKey(format, key);
}

async function importHmacKey(keyData, format = 'jwk', hash = 'SHA-512') {
  const key = await subtle.importKey(format, keyData, {
    name: 'HMAC',
    hash,
  }, true, ['sign', 'verify']);

  return key;
} 

包装和解开密钥#

const { subtle } = require('node:crypto').webcrypto;

async function generateAndWrapHmacKey(format = 'jwk', hash = 'SHA-512') {
  const [
    key,
    wrappingKey,
  ] = await Promise.all([
    subtle.generateKey({
      name: 'HMAC', hash,
    }, true, ['sign', 'verify']),
    subtle.generateKey({
      name: 'AES-KW',
      length: 256,
    }, true, ['wrapKey', 'unwrapKey']),
  ]);

  const wrappedKey = await subtle.wrapKey(format, key, wrappingKey, 'AES-KW');

  return { wrappedKey, wrappingKey };
}

async function unwrapHmacKey(
  wrappedKey,
  wrappingKey,
  format = 'jwk',
  hash = 'SHA-512') {

  const key = await subtle.unwrapKey(
    format,
    wrappedKey,
    wrappingKey,
    'AES-KW',
    { name: 'HMAC', hash },
    true,
    ['sign', 'verify']);

  return key;
} 

签名并验证#

const { subtle } = require('node:crypto').webcrypto;

async function sign(key, data) {
  const ec = new TextEncoder();
  const signature =
    await subtle.sign('RSASSA-PKCS1-v1_5', key, ec.encode(data));
  return signature;
}

async function verify(key, signature, data) {
  const ec = new TextEncoder();
  const verified =
    await subtle.verify(
      'RSASSA-PKCS1-v1_5',
      key,
      signature,
      ec.encode(data));
  return verified;
} 

派生位和密钥#

const { subtle } = require('node:crypto').webcrypto;

async function pbkdf2(pass, salt, iterations = 1000, length = 256) {
  const ec = new TextEncoder();
  const key = await subtle.importKey(
    'raw',
    ec.encode(pass),
    'PBKDF2',
    false,
    ['deriveBits']);
  const bits = await subtle.deriveBits({
    name: 'PBKDF2',
    hash: 'SHA-512',
    salt: ec.encode(salt),
    iterations,
  }, key, length);
  return bits;
}

async function pbkdf2Key(pass, salt, iterations = 1000, length = 256) {
  const ec = new TextEncoder();
  const keyMaterial = await subtle.importKey(
    'raw',
    ec.encode(pass),
    'PBKDF2',
    false,
    ['deriveKey']);
  const key = await subtle.deriveKey({
    name: 'PBKDF2',
    hash: 'SHA-512',
    salt: ec.encode(salt),
    iterations,
  }, keyMaterial, {
    name: 'AES-GCM',
    length: 256,
  }, true, ['encrypt', 'decrypt']);
  return key;
} 

摘要#

const { subtle } = require('node:crypto').webcrypto;

async function digest(data, algorithm = 'SHA-512') {
  const ec = new TextEncoder();
  const digest = await subtle.digest(algorithm, ec.encode(data));
  return digest;
} 

算法矩阵#

该表详细介绍了 Node.js Web Crypto API 实现支持的算法以及每个算法支持的 API:

算法generateKeyexportKeyimportKeyencryptdecryptwrapKeyunwrapKeyderiveBitsderiveKeysignverifydigest
'RSASSA-PKCS1-v1_5'
'RSA-PSS'
'RSA-OAEP'
'ECDSA'
'Ed25519' 1
'Ed448' 1
'ECDH'
'X25519' 1
'X448' 1
'AES-CTR'
'AES-CBC'
'AES-GCM'
'AES-KW'
'HMAC'
'HKDF'
'PBKDF2'
'SHA-1'
'SHA-256'
'SHA-384'
'SHA-512'

类:Crypto#

调用require('node:crypto').webcrypto返回Crypto类的实例 。Crypto是一个单例,提供对加密 API 其余部分的访问。

crypto.subtle#

提供对SubtleCrypto API的访问。

crypto.getRandomValues(typedArray)#

生成加密的强随机值。给定的typedArray用随机值填充,并返回对typedArray的引用。

给定的typedArray必须是<TypedArray>基于整数的实例,即不接受Float32ArrayFloat64Array

如果给定的typedArray大于 65,536 字节,则会抛出错误。

crypto.randomUUID()#

生成随机RFC 4122版本 4 UUID。UUID 是使用加密伪随机数生成器生成的。

类:CryptoKey#

cryptoKey.algorithm#

详细说明算法的对象,可以将密钥与其他特定于算法的参数一起使用。

只读。

cryptoKey.extractable#

true时,可以使用 subtleCrypto.exportKey()subtleCrypto.wrapKey()提取<CryptoKey>

只读。

cryptoKey.type#

  • 类型:<string> 'secret''private''public'之一。

一个字符串,用于标识密钥是对称密钥 ( 'secret' ) 还是非对称密钥 ( 'private''public' )。

cryptoKey.usages#

标识可以使用该密钥的操作的字符串数组。

可能的用途是:

  • 'encrypt' - 密钥可用于加密数据。
  • 'decrypt' - 密钥可用于解密数据。
  • 'sign' - 该密钥可用于生成数字签名。
  • 'verify' - 密钥可用于验证数字签名。
  • 'deriveKey' - 该密钥可用于派生新密钥。
  • 'deriveBits' - 密钥可用于导出位。
  • 'wrapKey' - 该密钥可用于包装另一个密钥。
  • 'unwrapKey' - 该密钥可用于解开另一个密钥。

有效的密钥用法取决于密钥算法(由 cryptokey.algorithm.name标识)。

钥匙类型'encrypt''decrypt''sign''verify''deriveKey''deriveBits''wrapKey''unwrapKey'
'AES-CBC'
'AES-CTR'
'AES-GCM'
'AES-KW'
'ECDH'
'X25519' 1
'X448' 1
'ECDSA'
'Ed25519' 1
'Ed448' 1
'HDKF'
'HMAC'
'PBKDF2'
'RSA-OAEP'
'RSA-PSS'
'RSASSA-PKCS1-v1_5'

类:CryptoKeyPair#

CryptoKeyPair是一个简单的字典对象,具有publicKeyprivateKey属性,表示非对称密钥对。

cryptoKeyPair.privateKey#

cryptoKeyPair.publicKey#

类:SubtleCrypto#

subtle.decrypt(algorithm, key, data)#

使用algorithm中指定的方法和参数以及key提供的密钥材料,subtle.decrypt()尝试解密提供的data。如果成功,返回的 Promise 将使用包含明文结果的<ArrayBuffer>进行解析。

目前支持的算法包括:

  • 'RSA-OAEP'
  • 'AES-CTR'
  • 'AES-CBC'
  • 'AES-GCM '

subtle.deriveBits(algorithm, baseKey, length)#

使用algorithm中指定的方法和参数以及baseKey提供的密钥材料,subtle.deriveBits()尝试生成 length位。

Node.js 实现要求,当length是数字时,它必须是8的倍数。

lengthnull时,将生成给定算法的最大位数。这对于'ECDH''X25519''X448'算法是允许的 。

如果成功,返回的 Promise 将使用 包含生成数据的<ArrayBuffer>进行解析。

目前支持的算法包括:

  • 'ECDH'
  • 'X25519' 1
  • 'X448' 1
  • 'HKDF'
  • 'PBKDF2'

subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)#

使用algorithm中指定的方法和参数以及baseKey提供的密钥材料,subtle.deriveKey()尝试根据该方法和参数生成新的<CryptoKey>derivedKeyAlgorithm中。

调用subtle.deriveKey()相当于调用subtle.deriveBits()生成原始密钥材料,然后使用 deriveKeyAlgorithm将结果传递到 subtle.importKey()方法,extractablekeyUsages参数作为输入。

目前支持的算法包括:

  • 'ECDH'
  • 'X25519' 1
  • 'X448' 1
  • 'HKDF'
  • 'PBKDF2'

subtle.digest(algorithm, data)#

使用algorithm标识的方法,subtle.digest()尝试生成data的摘要。如果成功,返回的 Promise 将使用包含计算摘要的<ArrayBuffer>进行解析。

如果algorithm作为<string>提供,则它必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果algorithm作为<Object>提供,则它必须具有一个name属性,其值为上述之一。

subtle.encrypt(algorithm, key, data)#

使用algorithm指定的方法和参数以及key提供的密钥材料,subtle.encrypt()尝试加密data。如果成功,返回的 Promise 将使用 包含加密结果的<ArrayBuffer>进行解析。

目前支持的算法包括:

  • 'RSA-OAEP'
  • 'AES-CTR'
  • 'AES-CBC'
  • 'AES-GCM '

subtle.exportKey(format, key)#

将给定密钥导出为指定格式(如果支持)。

如果<CryptoKey>不可提取,则返回的 Promise 将被拒绝。

format'pkcs8''spki'且导出成功时,返回的 Promise 将使用包含导出的键数据的<ArrayBuffer>进行解析。

format'jwk'并且导出成功时,返回的 Promise 将使用符合JSON Web Key 规范的 JavaScript 对象进行解析。

钥匙类型'spki''pkcs8''jwk''raw'
'AES-CBC'
'AES-CTR'
'AES-GCM'
'AES-KW'
'ECDH'
'ECDSA'
'Ed25519' 1
'Ed448' 1
'HDKF'
'HMAC'
'PBKDF2'
'RSA-OAEP'
'RSA-PSS'
'RSASSA-PKCS1-v1_5'

subtle.generateKey(algorithm, extractable, keyUsages)#

使用algorithm中提供的方法和参数,subtle.generateKey() 尝试生成新的密钥材料。根据所使用的方法,该方法可能生成单个<CryptoKey><CryptoKeyPair>

支持的 <CryptoKeyPair>(公钥和私钥)生成算法包括:

  • 'RSASSA-PKCS1-v1_5'
  • 'RSA-PSS'
  • 'RSA-OAEP'
  • 'ECDSA'
  • 'Ed25519' 1
  • 'Ed448' 1
  • 'ECDH'
  • 'X25519' 1
  • 'X448' 1

支持的<CryptoKey>(秘密密钥)生成算法包括:

  • 'HMAC'
  • 'AES-CTR'
  • 'AES-CBC'
  • 'AES-GCM'
  • 'AES-KW'

subtle.importKey(format, keyData, algorithm, extractable, keyUsages)#

subtle.importKey()方法尝试将提供的keyData解释 为给定的format ,以使用提供的 algorithm创建<CryptoKey>实例,extractablekeyUsages参数。如果导入成功,返回的 Promise 将使用创建的<CryptoKey>进行解析。

如果导入'PBKDF2'键,则extractable必须为false

目前支持的算法包括:

钥匙类型'spki''pkcs8''jwk''raw'
'AES-CBC'
'AES-CTR'
'AES-GCM'
'AES-KW'
'ECDH'
'X25519' 1
'X448' 1
'ECDSA'
'Ed25519' 1
'Ed448' 1
'HDKF'
'HMAC'
'PBKDF2'
'RSA-OAEP'
'RSA-PSS'
'RSASSA-PKCS1-v1_5'

subtle.sign(algorithm, key, data)#

使用algorithm给出的方法和参数以及key提供的密钥材料,subtle.sign()尝试生成data的加密签名。如果成功,返回的 Promise 将使用包含生成签名的<ArrayBuffer>进行解析。

目前支持的算法包括:

  • 'RSASSA-PKCS1-v1_5'
  • 'RSA-PSS'
  • 'ECDSA'
  • 'Ed25519' 1
  • 'Ed448' 1
  • 'HMAC'

subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)#

在密码学中,“包装密钥”是指导出然后加密密钥材料。subtle.unwrapKey()方法尝试解密包装的密钥并创建<CryptoKey>实例。它相当于 首先对加密的密钥数据调用subtle.decrypt() (使用wrappedKeyunwrapAlgounwrappingKey参数作为输入),然后使用unwrappedKeyAlgoextractablekeyUsages参数作为输入将结果传递给subtle.importKey()方法。如果成功,返回的 Promise 将通过<CryptoKey>对象解析。

目前支持的环绕算法包括:

  • 'RSA-OAEP'
  • 'AES-CTR'
  • 'AES-CBC'
  • 'AES-GCM'
  • 'AES-KW'

支持的解包关键算法包括:

  • 'RSASSA-PKCS1-v1_5'
  • 'RSA-PSS'
  • 'RSA-OAEP'
  • 'ECDSA'
  • 'Ed25519' 1
  • 'Ed448' 1
  • 'ECDH'
  • 'X25519' 1
  • 'X448' 1
  • 'HMAC'
  • 'AES-CTR'
  • 'AES-CBC'
  • 'AES-GCM'
  • 'AES-KW'

subtle.verify(algorithm, key, signature, data)#

使用algorithm中给出的方法和参数以及key提供的密钥材料,subtle.verify()尝试验证signature是否是有效的加密data的签名。返回的 Promise 通过truefalse解析。

目前支持的算法包括:

  • 'RSASSA-PKCS1-v1_5'
  • 'RSA-PSS'
  • 'ECDSA'
  • 'Ed25519' 1
  • 'Ed448' 1
  • 'HMAC'

subtle.wrapKey(format, key, wrappingKey, wrapAlgo)#

在密码学中,“包装密钥”是指导出然后加密密钥材料。subtle.wrapKey()方法将密钥材料导出为format标识的格式,然后使用wrapAlgo指定的方法和参数以及wrappingKey提供的密钥材料对其 进行加密。它相当于使用 formatkey作为参数调用 subtle.exportKey(),然后使用 wrappingKey 将结果传递给 subtle.encrypt()方法和wrapAlgo作为输入。如果成功,返回的 Promise 将使用 包含加密密钥数据的<ArrayBuffer>进行解析。

目前支持的环绕算法包括:

  • 'RSA-OAEP'
  • 'AES-CTR'
  • 'AES-CBC'
  • 'AES-GCM'
  • 'AES-KW'

算法参数#

算法参数对象定义了各种<SubtleCrypto>方法使用的方法和参数。虽然这里被描述为“类”,但它们是简单的 JavaScript 字典对象。

类:AlgorithmIdentifier#

algorithmIdentifier.name#

类:AesCbcParams#

aesCbcParams.iv#

提供初始化向量。它的长度必须正好是 16 字节,并且应该是不可预测的且加密随机的。

aesCbcParams.name#
  • 类型:<string>必须是'AES-CBC'

类:AesCtrParams#

aesCtrParams.counter#

计数器块的初始值。该长度必须正好是 16 个字节。

AES-CTR方法使用块的最右边的length位作为计数器,其余位作为随机数。

aesCtrParams.length#
  • 类型:<number> aesCtrParams.counter中用作计数器的位数。
aesCtrParams.name#
  • 类型:<string>必须是'AES-CTR'

类:AesGcmParams#

aesGcmParams.additionalData#

对于 AES-GCM 方法,additionalData是未加密的额外输入,但包含在数据的身份验证中。additionalData的使用 是可选的。

aesGcmParams.iv#

对于使用给定密钥的每个加密操作,初始化向量必须是唯一的。

理想情况下,这是一个确定性的 12 字节值,其计算方式确保它在使用相同密钥的所有调用中是唯一的。或者,初始化向量可以由至少12个加密随机字节组成。有关构建 AES-GCM 初始化向量的更多信息,请参阅NIST SP 800-38D第 8 节。

aesGcmParams.name#
  • 类型:<string>必须是'AES-GCM'
aesGcmParams.tagLength#
  • 类型:<number>生成的身份验证标记的大小(以位为单位)。该值必须是以下值之一:326496104112120128默认值: 128

类:AesKeyGenParams#

aesKeyGenParams.length#

要生成的 AES 密钥的长度。这必须是128192256

aesKeyGenParams.name#
  • 类型:<string>必须是'AES-CBC''AES-CTR''AES-GCM''AES-KW'之一

类:EcdhKeyDeriveParams#

ecdhKeyDeriveParams.name#
  • 类型:<string>必须为'ECDH''X25519''X448'
ecdhKeyDeriveParams.public#

ECDH 密钥派生通过将一方私钥和另一方公钥作为输入进行操作——使用两者生成共同的共享秘密。ecdhKeyDeriveParams.public属性设置为其他方公钥。

类:EcdsaParams#

ecdsaParams.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

ecdsaParams.name#

类:EcKeyGenParams#

ecKeyGenParams.name#
ecKeyGenParams.namedCurve#

类:EcKeyImportParams#

ecKeyImportParams.name#
ecKeyImportParams.namedCurve#

类:Ed448Params#

ed448Params.name#
ed448Params.context#

context成员表示与消息关联的可选上下文数据。Node.js Web Crypto API 实现仅支持零长度上下文,这相当于根本不提供上下文。

类:HkdfParams#

hkdfParams.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

hkdfParams.info#

为 HKDF 算法提供特定于应用程序的上下文输入。该长度可以为零,但必须提供。

hkdfParams.name#
hkdfParams.salt#

salt值显着提高了HKDF算法的强度。它应该是随机或伪随机的,并且应该与摘要函数的输出长度相同(例如,如果使用'SHA-256'作为摘要,则盐应该是 256 位随机数据​​)。

类:HmacImportParams#

hmacImportParams.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

hmacImportParams.length#

HMAC 密钥中的可选位数。这是可选的,在大多数情况下应省略。

hmacImportParams.name#

类:HmacKeyGenParams#

hmacKeyGenParams.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

hmacKeyGenParams.length#

为 HMAC 密钥生成的位数。如果省略,长度将由使用的哈希算法确定。这是可选的,在大多数情况下应省略。

hmacKeyGenParams.name#

类:Pbkdf2Params#

pbkdb2Params.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

pbkdf2Params.iterations#

PBKDF2 算法在导出位时应进行的迭代次数。

pbkdf2Params.name#
pbkdf2Params.salt#

应至少为 16 个随机或伪随机字节。

类:RsaHashedImportParams#

rsaHashedImportParams.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

rsaHashedImportParams.name#

类:RsaHashedKeyGenParams#

rsaHashedKeyGenParams.hash#

如果表示为<string>,则该值必须是以下之一:

  • 'SHA-1'
  • 'SHA-256'
  • 'SHA-384'
  • 'SHA-512'

如果表示为<Object>,则该对象必须具有name属性,其值是上面列出的值之一。

rsaHashedKeyGenParams.modulusLength#

RSA 模数的长度(以位为单位)。作为最佳实践,这应该至少为2048

rsaHashedKeyGenParams.name#
rsaHashedKeyGenParams.publicExponent#

RSA 公共索引。这必须是一个<Uint8Array>,其中包含必须适合 32 位的大端无符号整数。<Uint8Array>可以包含任意数量前导零位。该值必须是素数。除非有理由使用不同的值,否则请使用new Uint8Array([1, 0, 1]) (65537) 作为公共索引。

类:RsaOaepParams#

rsaOaepParams.label#

不会加密的附加字节集合,但将绑定到生成的密文。

rsaOaepParams.label参数是可选的。

rsaOaepParams.name#
  • 类型:<string>必须为'RSA-OAEP'

类:RsaPssParams#

rsaPssParams.name#
  • 类型:<string>必须为'RSA-PSS'
rsaPssParams.saltLength#

要使用的随机盐的长度(以字节为单位)。

脚注

  1. 自 2022 年 5 月 5 日起,在 Web 加密 API 中实验性实施 安全曲线 23456789101112131415161718↩19↩20↩21↩22↩23↩24↩25↩26↩27↩28↩29↩30 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

NodeJS中文文档为Read dev Docs平台提供托管,中文NodeJS文档均由英文版NodeJS文档翻译,版权属于nodejs.org