seckit/src/utils/algorithm/sm4.ts
2026-04-15 20:02:36 +08:00

251 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as sjcl from 'sjcl-with-all'
import { bindSM4 } from 'gmsm-sm4js'
import { SM4 } from 'gm-crypto'
// 绑定 SM4 到 sjcl
bindSM4(sjcl)
// 初始化 sjcl 的加密模式(这些模式在 sjcl.beware 中定义)
if (sjcl.beware) {
// 触发模式的初始化
Object.keys(sjcl.beware).forEach(key => {
if (typeof sjcl.beware[key] === 'function') {
sjcl.beware[key]()
}
})
}
// 验证是否为有效的 hex 字符串
const isValidHex = (hex: string): boolean => {
return /^[0-9a-fA-F]*$/.test(hex) && hex.length % 2 === 0
}
// Hex 字符串转字节数组
const hexToBytes = (hex: string): number[] => {
const bytes: number[] = []
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16))
}
return bytes
}
// 字节数组转 hex 字符串
const bytesToHex = (bytes: number[]): string => {
return bytes.map(byte => byte.toString(16).padStart(2, '0')).join('')
}
// PKCS7 填充
const pkcs7Pad = (data: number[], blockSize: number): number[] => {
const padLength = blockSize - (data.length % blockSize)
const padded = [...data]
for (let i = 0; i < padLength; i++) {
padded.push(padLength)
}
return padded
}
// PKCS7 去填充
const pkcs7Unpad = (data: number[]): number[] => {
if (data.length === 0) {
return data
}
const padLength = data[data.length - 1]
if (padLength > data.length) {
return data
}
// 验证填充是否正确
for (let i = data.length - padLength; i < data.length; i++) {
if (data[i] !== padLength) {
return data
}
}
return data.slice(0, data.length - padLength)
}
// SM4 加密
export const sm4Encrypt = (message: string, key: string, mode: string, iv?: string, paddingMode: 'pkcs7' | 'none' = 'pkcs7'): string => {
// 验证消息是否为有效的 hex 字符串
if (!isValidHex(message)) {
throw new Error('Invalid message: must be hex string')
}
// 验证密钥是否为有效的 hex 字符串
if (!isValidHex(key)) {
throw new Error('Invalid key: must be hex string')
}
// 验证 IV 是否为有效的 hex 字符串(如果提供)
if (iv && !isValidHex(iv)) {
throw new Error('Invalid IV: must be hex string')
}
// 根据模式选择加密方式
const modeLower = mode.toLowerCase()
if (modeLower === 'ecb') {
// ECB 模式使用 gm-crypto
return SM4.encrypt(message, key, {
mode: 'ecb',
inputEncoding: 'hex',
outputEncoding: 'hex',
padding: paddingMode === 'pkcs7' ? 'pkcs7' : 'none'
})
} else if (modeLower === 'cbc') {
// CBC 模式使用 gmsm-sm4js
if (!iv) {
throw new Error('IV is required for CBC mode')
}
// 转换为字节数组
const messageBytes = hexToBytes(message)
const keyBytes = hexToBytes(key)
// 处理填充
let dataToEncrypt = messageBytes
if (paddingMode === 'pkcs7') {
dataToEncrypt = pkcs7Pad(messageBytes, 16)
}
// 创建 SM4 密钥
const sm4Key = new sjcl.cipher.sm4(sjcl.codec.bytes.toBits(keyBytes))
const ivBytes = hexToBytes(iv)
const ivBits = sjcl.codec.bytes.toBits(ivBytes)
const dataBits = sjcl.codec.bytes.toBits(dataToEncrypt)
const encryptedBits = sjcl.mode.cbc.encrypt(sm4Key, dataBits, ivBits)
const encryptedBytes = sjcl.codec.bytes.fromBits(encryptedBits)
return bytesToHex(encryptedBytes)
} else if (modeLower === 'gcm') {
// GCM 模式使用 gmsm-sm4js
if (!iv) {
throw new Error('IV is required for GCM mode')
}
// 转换为字节数组
const messageBytes = hexToBytes(message)
const keyBytes = hexToBytes(key)
// 处理填充
let dataToEncrypt = messageBytes
if (paddingMode === 'pkcs7') {
dataToEncrypt = pkcs7Pad(messageBytes, 16)
}
// 创建 SM4 密钥
const sm4Key = new sjcl.cipher.sm4(sjcl.codec.bytes.toBits(keyBytes))
const ivBytes = hexToBytes(iv)
const ivBits = sjcl.codec.bytes.toBits(ivBytes)
const dataBits = sjcl.codec.bytes.toBits(dataToEncrypt)
// 传递标签长度参数128 位)
const encryptedResult = sjcl.mode.gcm.encrypt(sm4Key, dataBits, ivBits, sjcl.codec.bytes.toBits([]), 128)
// GCM 模式返回的数据包含 tag
if (!encryptedResult) {
throw new Error('GCM encryption failed: result is undefined')
}
// 检查 encryptedResult 的类型
if (Array.isArray(encryptedResult)) {
// 如果返回的是数组,直接使用(包含了 tag
const encryptedBytes = sjcl.codec.bytes.fromBits(encryptedResult)
return bytesToHex(encryptedBytes)
} else if (encryptedResult.data) {
// 如果返回的是对象,使用 data 属性(可能已经包含了 tag
const encryptedBits = encryptedResult.data
const encryptedBytes = sjcl.codec.bytes.fromBits(encryptedBits)
return bytesToHex(encryptedBytes)
} else {
throw new Error('GCM encryption failed: unexpected result format')
}
} else {
throw new Error(`Unsupported mode: ${mode}`)
}
}
// SM4 解密
export const sm4Decrypt = (encrypted: string, key: string, mode: string, iv?: string, paddingMode: 'pkcs7' | 'none' = 'pkcs7'): string => {
// 验证加密数据是否为有效的 hex 字符串
if (!isValidHex(encrypted)) {
throw new Error('Invalid encrypted data: must be hex string')
}
// 验证密钥是否为有效的 hex 字符串
if (!isValidHex(key)) {
throw new Error('Invalid key: must be hex string')
}
// 验证 IV 是否为有效的 hex 字符串(如果提供)
if (iv && !isValidHex(iv)) {
throw new Error('Invalid IV: must be hex string')
}
// 根据模式选择解密方式
const modeLower = mode.toLowerCase()
if (modeLower === 'ecb') {
// ECB 模式使用 gm-crypto
return SM4.decrypt(encrypted, key, {
mode: 'ecb',
inputEncoding: 'hex',
outputEncoding: 'hex',
padding: paddingMode === 'pkcs7' ? 'pkcs7' : 'none'
})
} else if (modeLower === 'cbc') {
// CBC 模式使用 gmsm-sm4js
if (!iv) {
throw new Error('IV is required for CBC mode')
}
// 转换为字节数组
const encryptedBytes = hexToBytes(encrypted)
const keyBytes = hexToBytes(key)
// 创建 SM4 密钥
const sm4Key = new sjcl.cipher.sm4(sjcl.codec.bytes.toBits(keyBytes))
const ivBytes = hexToBytes(iv)
const ivBits = sjcl.codec.bytes.toBits(ivBytes)
const encryptedBits = sjcl.codec.bytes.toBits(encryptedBytes)
const decryptedBits = sjcl.mode.cbc.decrypt(sm4Key, encryptedBits, ivBits)
const decryptedBytes = sjcl.codec.bytes.fromBits(decryptedBits)
// 处理去填充
if (paddingMode === 'pkcs7') {
return bytesToHex(pkcs7Unpad(decryptedBytes))
}
return bytesToHex(decryptedBytes)
} else if (modeLower === 'gcm') {
// GCM 模式使用 gmsm-sm4js
if (!iv) {
throw new Error('IV is required for GCM mode')
}
// 转换为字节数组
const encryptedBytes = hexToBytes(encrypted)
const keyBytes = hexToBytes(key)
// 创建 SM4 密钥
const sm4Key = new sjcl.cipher.sm4(sjcl.codec.bytes.toBits(keyBytes))
const ivBytes = hexToBytes(iv)
const ivBits = sjcl.codec.bytes.toBits(ivBytes)
const encryptedBits = sjcl.codec.bytes.toBits(encryptedBytes)
// 传递标签长度参数128 位)
const decryptedResult = sjcl.mode.gcm.decrypt(sm4Key, encryptedBits, ivBits, sjcl.codec.bytes.toBits([]), 128)
// 检查 decryptedResult 的类型
if (!decryptedResult) {
throw new Error('GCM decryption failed: result is undefined')
}
if (Array.isArray(decryptedResult)) {
// 如果返回的是数组,直接使用
const decryptedBytes = sjcl.codec.bytes.fromBits(decryptedResult)
// 处理去填充
if (paddingMode === 'pkcs7') {
return bytesToHex(pkcs7Unpad(decryptedBytes))
}
return bytesToHex(decryptedBytes)
} else if (decryptedResult.data) {
// 如果返回的是对象,使用 data 属性
const decryptedBits = decryptedResult.data
const decryptedBytes = sjcl.codec.bytes.fromBits(decryptedBits)
// 处理去填充
if (paddingMode === 'pkcs7') {
return bytesToHex(pkcs7Unpad(decryptedBytes))
}
return bytesToHex(decryptedBytes)
} else {
throw new Error('GCM decryption failed: unexpected result format')
}
} else {
throw new Error(`Unsupported mode: ${mode}`)
}
}