der c1c3c2 转换工具
This commit is contained in:
parent
f4228370d9
commit
3459a389ac
4
docs/design.md
Normal file
4
docs/design.md
Normal file
@ -0,0 +1,4 @@
|
||||
设计规范
|
||||
|
||||
- 所有输入框,双击后弹出提示框,确认后可以清空内容。移动端为长按弹出提示框,确认后可以清空内容。
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { sm2GenerateKeyPair, sm2Encrypt, sm2Decrypt } from '../../utils/algorithm/sm2'
|
||||
import { sm2GenerateKeyPair, sm2Encrypt, sm2Decrypt, sm2C1C3C2ToDer, sm2DerToC1C3C2 } from '../../utils/algorithm/sm2'
|
||||
|
||||
// UTF8 转 Hex
|
||||
const utf8ToHex = (utf8: string): string => {
|
||||
@ -37,4 +37,48 @@ describe('SM2 算法测试', () => {
|
||||
// console.log('Decrypted Message:', decryptedMessage)
|
||||
// expect(decryptedMessage).toBe(originalMessage)
|
||||
})
|
||||
|
||||
test('C1C3C2 转 DER 格式', () => {
|
||||
const { publicKey } = sm2GenerateKeyPair()
|
||||
const originalMessage = '测试消息'
|
||||
const messageHex = utf8ToHex(originalMessage)
|
||||
|
||||
const encrypted = sm2Encrypt(messageHex, publicKey)
|
||||
console.log('C1C3C2 Format:', encrypted)
|
||||
|
||||
const derFormat = sm2C1C3C2ToDer(encrypted)
|
||||
console.log('DER Format:', derFormat)
|
||||
expect(derFormat).toBeTruthy()
|
||||
expect(derFormat.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('DER 转 C1C3C2 格式', () => {
|
||||
const { publicKey } = sm2GenerateKeyPair()
|
||||
const originalMessage = '测试消息'
|
||||
const messageHex = utf8ToHex(originalMessage)
|
||||
|
||||
const encrypted = sm2Encrypt(messageHex, publicKey)
|
||||
const derFormat = sm2C1C3C2ToDer(encrypted)
|
||||
|
||||
const c1c3c2Format = sm2DerToC1C3C2(derFormat)
|
||||
console.log('Converted C1C3C2 Format:', c1c3c2Format)
|
||||
expect(c1c3c2Format).toBeTruthy()
|
||||
expect(c1c3c2Format.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('C1C3C2 与 DER 格式互转一致性', () => {
|
||||
const { publicKey } = sm2GenerateKeyPair()
|
||||
const originalMessage = '测试消息'
|
||||
const messageHex = utf8ToHex(originalMessage)
|
||||
|
||||
const originalC1C3C2 = sm2Encrypt(messageHex, publicKey)
|
||||
const derFormat = sm2C1C3C2ToDer(originalC1C3C2)
|
||||
const convertedC1C3C2 = sm2DerToC1C3C2(derFormat)
|
||||
|
||||
console.log('Original C1C3C2:', originalC1C3C2)
|
||||
console.log('Converted C1C3C2:', convertedC1C3C2)
|
||||
|
||||
// 验证互转后的数据是否一致
|
||||
expect(convertedC1C3C2).toBe(originalC1C3C2)
|
||||
})
|
||||
})
|
||||
|
||||
4
src/types/sm-crypto.d.ts
vendored
4
src/types/sm-crypto.d.ts
vendored
@ -6,8 +6,8 @@ declare module 'sm-crypto' {
|
||||
|
||||
export const sm2: {
|
||||
generateKeyPairHex: (privateKey?: string) => SM2KeyPair;
|
||||
doEncrypt: (message: string, publicKey: string) => string;
|
||||
doDecrypt: (ciphertext: string, privateKey: string) => string;
|
||||
doEncrypt: (message: string, publicKey: string, cipherMode?: number) => string;
|
||||
doDecrypt: (ciphertext: string, privateKey: string, cipherMode?: number, options?: { output?: string }) => string | Uint8Array;
|
||||
doSignature: (message: string | Uint8Array, privateKey: string, options?: { hash?: boolean, der?: boolean, userId?: string, publicKey?: string, pointPool?: any[] }) => string;
|
||||
doVerifySignature: (message: string | Uint8Array, signature: string, publicKey: string, options?: { hash?: boolean, der?: boolean, userId?: string }) => boolean;
|
||||
getPublicKeyFromPrivateKey: (privateKey: string) => string;
|
||||
|
||||
@ -3,6 +3,9 @@ import { isValidHex, hexToArrayBuffer } from '../convert/hex'
|
||||
// 导入 sm-crypto 库,它在浏览器环境中可用
|
||||
import { sm2 } from 'sm-crypto'
|
||||
|
||||
// 类型断言,确保 sm2 对象具有我们需要的方法
|
||||
const sm2Extended = sm2 as any
|
||||
|
||||
// SM2 密钥对生成
|
||||
export const sm2GenerateKeyPair = (): { privateKey: string; publicKey: string } => {
|
||||
const keypair = sm2.generateKeyPairHex()
|
||||
@ -27,7 +30,7 @@ export const sm2GetPublicKeyFromPrivateKey = (privateKey: string): string => {
|
||||
}
|
||||
|
||||
// SM2 加密
|
||||
export const sm2Encrypt = (message: string, publicKey: string): string => {
|
||||
export const sm2Encrypt = (message: string, publicKey: string, cipherMode: number = 1): string => {
|
||||
// 验证消息是否为有效的 hex 字符串
|
||||
if (!isValidHex(message)) {
|
||||
throw new Error('Invalid message: must be hex string')
|
||||
@ -43,12 +46,12 @@ export const sm2Encrypt = (message: string, publicKey: string): string => {
|
||||
const messageString = new TextDecoder().decode(messageBytes)
|
||||
|
||||
// 加密
|
||||
const encrypted = sm2.doEncrypt(messageString, publicKey)
|
||||
const encrypted = sm2Extended.doEncrypt(messageString, publicKey, cipherMode)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// SM2 解密
|
||||
export const sm2Decrypt = (ciphertext: string, privateKey: string): string => {
|
||||
export const sm2Decrypt = (ciphertext: string, privateKey: string, cipherMode: number = 1): string => {
|
||||
// 验证密文是否为有效的 hex 字符串
|
||||
if (!isValidHex(ciphertext)) {
|
||||
throw new Error('Invalid ciphertext: must be hex string')
|
||||
@ -59,7 +62,7 @@ export const sm2Decrypt = (ciphertext: string, privateKey: string): string => {
|
||||
}
|
||||
|
||||
// 解密
|
||||
const decrypted = sm2.doDecrypt(ciphertext, privateKey)
|
||||
const decrypted = sm2Extended.doDecrypt(ciphertext, privateKey, cipherMode)
|
||||
// 将解密结果转换为 hex 字符串
|
||||
const decryptedBytes = new TextEncoder().encode(decrypted)
|
||||
return Array.from(decryptedBytes)
|
||||
@ -106,3 +109,199 @@ export const sm2Verify = (message: string, signature: string, publicKey: string)
|
||||
const result = sm2.doVerifySignature(messageBytes, signature, publicKey, { hash: false })
|
||||
return result
|
||||
}
|
||||
|
||||
// 辅助函数:将十六进制字符串转换为字节数组
|
||||
const hexToBytes = (hex: string): Uint8Array => {
|
||||
const bytes = new Uint8Array(hex.length / 2)
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
// 辅助函数:将字节数组转换为十六进制字符串
|
||||
const bytesToHex = (bytes: Uint8Array): string => {
|
||||
return Array.from(bytes).map(byte => byte.toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
|
||||
// 辅助函数:ASN.1 DER编码长度
|
||||
const encodeLength = (length: number): Uint8Array => {
|
||||
if (length < 128) {
|
||||
return new Uint8Array([length])
|
||||
} else if (length < 256) {
|
||||
return new Uint8Array([0x81, length])
|
||||
} else if (length < 65536) {
|
||||
return new Uint8Array([0x82, length >> 8, length & 0xff])
|
||||
} else {
|
||||
throw new Error('Length too large')
|
||||
}
|
||||
}
|
||||
|
||||
// SM2 密文 C1C3C2 格式转 DER 格式
|
||||
export const sm2C1C3C2ToDer = (c1c3c2: string): string => {
|
||||
// 验证输入是否为有效的 hex 字符串
|
||||
if (!isValidHex(c1c3c2)) {
|
||||
throw new Error('Invalid C1C3C2: must be hex string')
|
||||
}
|
||||
|
||||
// C1C3C2 格式结构:C1(65字节) + C3(32字节) + C2(可变长度)
|
||||
// C1: 04 + x(32字节) + y(32字节)
|
||||
const c1HexLength = 130; // 65字节 * 2 (hex)
|
||||
const c3HexLength = 64; // 32字节 * 2 (hex)
|
||||
|
||||
if (c1c3c2.length < c1HexLength + c3HexLength) {
|
||||
throw new Error('Invalid C1C3C2: length too short')
|
||||
}
|
||||
|
||||
const c1 = c1c3c2.substring(0, c1HexLength)
|
||||
const c3 = c1c3c2.substring(c1HexLength, c1HexLength + c3HexLength)
|
||||
const c2 = c1c3c2.substring(c1HexLength + c3HexLength)
|
||||
|
||||
// 转换为字节数组
|
||||
const c1Bytes = hexToBytes(c1)
|
||||
const c2Bytes = hexToBytes(c2)
|
||||
const c3Bytes = hexToBytes(c3)
|
||||
|
||||
// ASN.1 DER 编码
|
||||
// 1. 开始 SEQUENCE
|
||||
const sequenceTag = new Uint8Array([0x30])
|
||||
|
||||
// 2. 编码 C1 (BIT STRING)
|
||||
const c1Tag = new Uint8Array([0x03])
|
||||
const c1BitString = new Uint8Array([0x00, ...c1Bytes]) // BIT STRING 前加 0x00
|
||||
const c1DerLength = encodeLength(c1BitString.length)
|
||||
|
||||
// 3. 编码 C2 (OCTET STRING)
|
||||
const c2Tag = new Uint8Array([0x04])
|
||||
const c2DerLength = encodeLength(c2Bytes.length)
|
||||
|
||||
// 4. 编码 C3 (OCTET STRING)
|
||||
const c3Tag = new Uint8Array([0x04])
|
||||
const c3DerLength = encodeLength(c3Bytes.length)
|
||||
|
||||
// 计算总长度
|
||||
const totalLength = c1Tag.length + c1DerLength.length + c1BitString.length +
|
||||
c2Tag.length + c2DerLength.length + c2Bytes.length +
|
||||
c3Tag.length + c3DerLength.length + c3Bytes.length
|
||||
|
||||
const sequenceLength = encodeLength(totalLength)
|
||||
|
||||
// 组合所有部分
|
||||
const derBytes = new Uint8Array(
|
||||
sequenceTag.length + sequenceLength.length + totalLength
|
||||
)
|
||||
|
||||
let offset = 0
|
||||
|
||||
// 写入 SEQUENCE
|
||||
derBytes.set(sequenceTag, offset)
|
||||
offset += sequenceTag.length
|
||||
derBytes.set(sequenceLength, offset)
|
||||
offset += sequenceLength.length
|
||||
|
||||
// 写入 C1
|
||||
derBytes.set(c1Tag, offset)
|
||||
offset += c1Tag.length
|
||||
derBytes.set(c1DerLength, offset)
|
||||
offset += c1DerLength.length
|
||||
derBytes.set(c1BitString, offset)
|
||||
offset += c1BitString.length
|
||||
|
||||
// 写入 C2
|
||||
derBytes.set(c2Tag, offset)
|
||||
offset += c2Tag.length
|
||||
derBytes.set(c2DerLength, offset)
|
||||
offset += c2DerLength.length
|
||||
derBytes.set(c2Bytes, offset)
|
||||
offset += c2Bytes.length
|
||||
|
||||
// 写入 C3
|
||||
derBytes.set(c3Tag, offset)
|
||||
offset += c3Tag.length
|
||||
derBytes.set(c3DerLength, offset)
|
||||
offset += c3DerLength.length
|
||||
derBytes.set(c3Bytes, offset)
|
||||
|
||||
return bytesToHex(derBytes)
|
||||
}
|
||||
|
||||
// 辅助函数:ASN.1 DER解码长度
|
||||
const decodeLength = (bytes: Uint8Array, offset: number): { length: number, newOffset: number } => {
|
||||
const firstByte = bytes[offset]
|
||||
if (firstByte < 128) {
|
||||
return { length: firstByte, newOffset: offset + 1 }
|
||||
} else if (firstByte === 0x81) {
|
||||
return { length: bytes[offset + 1], newOffset: offset + 2 }
|
||||
} else if (firstByte === 0x82) {
|
||||
return { length: (bytes[offset + 1] << 8) | bytes[offset + 2], newOffset: offset + 3 }
|
||||
} else {
|
||||
throw new Error('Invalid length encoding')
|
||||
}
|
||||
}
|
||||
|
||||
// SM2 密文 DER 格式转 C1C3C2 格式
|
||||
export const sm2DerToC1C3C2 = (der: string): string => {
|
||||
// 验证输入是否为有效的 hex 字符串
|
||||
if (!isValidHex(der)) {
|
||||
throw new Error('Invalid DER: must be hex string')
|
||||
}
|
||||
|
||||
const derBytes = hexToBytes(der)
|
||||
let offset = 0
|
||||
|
||||
// 检查是否为 SEQUENCE
|
||||
if (derBytes[offset] !== 0x30) {
|
||||
throw new Error('Invalid DER: not a SEQUENCE')
|
||||
}
|
||||
offset += 1
|
||||
|
||||
// 解码 SEQUENCE 长度
|
||||
const { newOffset: seqOffset } = decodeLength(derBytes, offset)
|
||||
offset = seqOffset
|
||||
|
||||
// 解码 C1 (BIT STRING)
|
||||
if (derBytes[offset] !== 0x03) {
|
||||
throw new Error('Invalid DER: C1 is not a BIT STRING')
|
||||
}
|
||||
offset += 1
|
||||
|
||||
const { length: c1Length, newOffset: c1Offset } = decodeLength(derBytes, offset)
|
||||
offset = c1Offset
|
||||
|
||||
// 跳过 BIT STRING 的 0x00 前缀
|
||||
if (derBytes[offset] !== 0x00) {
|
||||
throw new Error('Invalid DER: BIT STRING missing leading 0x00')
|
||||
}
|
||||
offset += 1
|
||||
|
||||
const c1Bytes = derBytes.slice(offset, offset + c1Length - 1) // -1 因为跳过了 0x00
|
||||
offset += c1Length - 1
|
||||
|
||||
// 解码 C2 (OCTET STRING)
|
||||
if (derBytes[offset] !== 0x04) {
|
||||
throw new Error('Invalid DER: C2 is not an OCTET STRING')
|
||||
}
|
||||
offset += 1
|
||||
|
||||
const { length: c2Length, newOffset: c2Offset } = decodeLength(derBytes, offset)
|
||||
offset = c2Offset
|
||||
|
||||
const c2Bytes = derBytes.slice(offset, offset + c2Length)
|
||||
offset += c2Length
|
||||
|
||||
// 解码 C3 (OCTET STRING)
|
||||
if (derBytes[offset] !== 0x04) {
|
||||
throw new Error('Invalid DER: C3 is not an OCTET STRING')
|
||||
}
|
||||
offset += 1
|
||||
|
||||
const { length: c3Length, newOffset: c3Offset } = decodeLength(derBytes, offset)
|
||||
offset = c3Offset
|
||||
|
||||
const c3Bytes = derBytes.slice(offset, offset + c3Length)
|
||||
|
||||
// 组合成 C1C3C2 格式
|
||||
const c1c3c2 = bytesToHex(c1Bytes) + bytesToHex(c3Bytes) + bytesToHex(c2Bytes)
|
||||
|
||||
return c1c3c2
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
export const VERSION = "V1.0-20260418075022";
|
||||
export const VERSION = "V1.0-20260423021405";
|
||||
Loading…
Reference in New Issue
Block a user