backgroundbackground
SubtleCrypto API 简明教程

SubtleCrypto API 简明教程

SubtleCrypto / Web / JS/TS

教程

2024-11-28 09:55

一、简介

SubtleCrypto API 是 Web Crypto API 的核心组成部分,它为网页应用提供了一套完整的加密功能接口。通过这个 API,开发者可以在浏览器中实现各种密码学操作,包括加密、解密、签名、验证等功能。

为什么叫SubtleCrypto

MDN解释如下:

有些浏览器实现了叫作 Crypto 的接口,但是它缺乏良好的定义,或在密码学上是不健全的。为了避免混乱,这个接口的方法和属性已经被实现 Web Crypto API 的浏览器所移除,并且所有的 Web Crypto API 方法都可以在新的接口中使用:SubtleCrypto。Crypto.subtle 属性可以获取到一个实现了新接口的对象。

简单来说就是有些浏览器已经实现了自己的Crypto,和规范冲突,所以为了避免混乱,而使用SubtleCrypto,可以用crypto.subtle获取到该实例

二、SubtleCrypto API 的核心功能

SubtleCrypto API 主要包含下面这些功能:

密钥管理:

  • generateKey(): 生成新的对称密钥或非对称密钥对。
  • importKey(): 从各种格式(例如 JWK、PKCS#8)导入密钥。
  • exportKey(): 将密钥导出为各种格式。
  • wrapKey(): 使用包装密钥加密敏感密钥,以便在不安全环境中传输和存储。
  • unwrapKey(): 解密并导入已包装的密钥。

加密和解密:

  • encrypt(): 使用指定的算法和密钥加密数据。
  • decrypt(): 使用指定的算法和密钥解密数据。

签名和验证:

  • sign(): 使用指定的算法和密钥对数据进行签名。
  • verify(): 验证数据的签名。

摘要:

  • digest(): 生成数据的哈希值(摘要)。

密钥派生:

  • deriveKey(): 从主密钥派生新的密钥。
  • deriveBits(): 从主密钥派生伪随机位。

三、常用方法示例

1、计算摘要

使用SubtleCryptodigest方法计算数据的摘要(即哈希值)

参数:

algorithm

可以是一个字符串或一个仅有 name 字符串属性的对象。该字符串为使用的哈希函数的名称。支持的算法以及对比如下

算法输出长度(比特)块大小(比特)
SHA-1160512
SHA-256256512
SHA-3843841024
SHA-5125121024

警告:现在,SHA-1 被认为是易受攻击的,不应将其用于加密应用程序。

data

一个包含将计算摘要的数据的对象,支持的类型如下。

  • ArrayBuffer
  • TypedArray
  • DataView

返回值:一个 Promise,会兑现一个包含摘要值的 ArrayBuffer

示例:

/**
 * 支持的哈希算法类型
 */
type HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'

/**
 * 计算给定数据的哈希值
 * @param data - 要计算哈希的数据,支持 ArrayBuffer、string、Blob
 * @param algorithm - 使用的哈希算法,支持 SHA-1、SHA-256、SHA-384、SHA-512,默认为 SHA-256
 * @returns 返回十六进制格式的哈希字符串
 * @example
 * // 计算字符串的 MD5
 * const hash = await calculateHash('hello') // 返回 "5d41402abc4b2a76b9719d911017c592"
 *
 * // 计算二进制数据的 SHA-256
 * const buffer = new ArrayBuffer(8)
 * const hash = await calculateHash(buffer, 'SHA-256')
 *
 * // 计算Blob的哈希
 * const blob = new Blob(['hello'])
 * const hash = await calculateHash(blob)
 */
export async function calculateHash(
  data: ArrayBuffer | string | Blob,
  algorithm: HashAlgorithm = 'SHA-256'
): Promise<string> {
  const cryptoImpl = await getCrypto()
  let buffer: ArrayBuffer
  if (typeof data === 'string') {
    buffer = new TextEncoder().encode(data).buffer
  } else if (data instanceof Blob) {
    buffer = await data.arrayBuffer()
  } else {
    buffer = data
  }
  const hashBuffer = await cryptoImpl.subtle.digest(algorithm, buffer)
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}

2、数据加密

使用SubtleCryptoencrypt方法加密数据

参数:

algorithm

一个对象,用于指定使用的算法,以及需要的任何额外的参数:

  • 使用 RSA-OAEP,则传入 RsaOaepParams 对象。
  • 使用 AES-CTR,则传入 AesCtrParams 对象。
  • 使用 AES-CBC,则传入 AesCbcParams 对象。
  • 使用 AES-GCM,则传入 AesGcmParams 对象。

建议优先用GCM模式,它可以检测密文是否已被攻击者篡改。

key

一个包含了密钥的、用于加密的 CryptoKey 对象。

data

一个包含了待加密的数据(也称为明文)的 ArrayBufferTypedArrayDataView 对象。

返回值

一个 Promise,会兑现一个包含密文的 ArrayBuffer

示例:

/**
 * 使用AES-GCM算法加密数据
 * @param data - 要加密的字符串数据
 * @param keyString - 加密密钥字符串
 * @returns 返回加密后的ArrayBuffer数据
 * @example
 * const encrypted = await encrypt("hello", "secretkey")
 */
export async function encrypt(data: string, keyString: string) {
  const cryptoImpl = await getCrypto()
  const encoder = new TextEncoder()

  // 首先通过 SHA-256 生成固定长度的密钥材料
  const keyHash = await calculateHash(keyString, 'SHA-256')
  const keyBytes = new Uint8Array(32) // 使用 256 位密钥
  for (let i = 0; i < 32; i++) {
    keyBytes[i] = parseInt(keyHash.slice(i * 2, i * 2 + 2), 16)
  }

  const keyMaterial = await cryptoImpl.subtle.importKey('raw', keyBytes, 'AES-GCM', false, [
    'encrypt'
  ])

  const encodedData = encoder.encode(data)

  // 这里的 iv 可以自定义为随机数组,但解密时要保持一致
  const encryptedData = await cryptoImpl.subtle.encrypt(
    { name: 'AES-GCM', iv: new Uint8Array(12) },
    keyMaterial,
    encodedData
  )
  return encryptedData
}

3、数据解密

使用SubtleCryptodecrypt方法加密数据

参数:

algorithm

一个对象,用于指定使用的算法,以及需要的任何额外的参数:

  • 使用 RSA-OAEP,则传入 RsaOaepParams 对象。
  • 使用 AES-CTR,则传入 AesCtrParams 对象。
  • 使用 AES-CBC,则传入 AesCbcParams 对象。
  • 使用 AES-GCM,则传入 AesGcmParams 对象。

建议优先用GCM模式,它可以检测密文是否已被攻击者篡改。

key

一个包含了密钥的 CryptoKey 对象,用于解密。

data

一个包含了待解密的数据(也称为密文)的 ArrayBufferTypedArrayDataView 对象。

返回值

一个 Promise,会兑现一个包含明文的 ArrayBuffer

示例:

/**
 * 使用AES-GCM算法解密数据
 * @param encryptedData - 加密后的ArrayBuffer数据
 * @param keyString - 解密密钥字符串
 * @returns 返回解密后的字符串
 * @example
 * const decrypted = await decrypt(encryptedData, "secretkey")
 */
export async function decrypt(encryptedData: ArrayBuffer, keyString: string) {
  const cryptoImpl = await getCrypto()
  const decoder = new TextDecoder()

  // 使用相同的方法生成密钥
  const keyHash = await calculateHash(keyString, 'SHA-256')
  const keyBytes = new Uint8Array(32)
  for (let i = 0; i < 32; i++) {
    keyBytes[i] = parseInt(keyHash.slice(i * 2, i * 2 + 2), 16)
  }

  const keyMaterial = await cryptoImpl.subtle.importKey('raw', keyBytes, 'AES-GCM', false, [
    'decrypt'
  ])

  // 这里的 iv 和加密用的 iv 保持一致
  const decryptedData = await cryptoImpl.subtle.decrypt(
    { name: 'AES-GCM', iv: new Uint8Array(12) },
    keyMaterial,
    encryptedData
  )
  return decoder.decode(decryptedData)
}

四、附上我封装的crypto工具

该工具提供了哈希计算,数据加解密等操作,以及ArraybufferBase64互转方法

同时兼容nodejs浏览器环境

点击下载:crypto.ts


参考: