Skip to content

Commit

Permalink
feat(sm2): support asn1 der encoded encryption/decryption
Browse files Browse the repository at this point in the history
  • Loading branch information
Cubelrti committed Dec 15, 2023
1 parent cc781d9 commit f08b9fd
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 15 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ verifyResult = sm2.verifyPublicKey(compressedPublicKey) // 验证公钥
```js
import { sm2 } from 'sm-crypto-v2'
const cipherMode = 1 // 1 - C1C3C2,0 - C1C2C3,默认为1
// 支持使用 asn1 对加密结果进行编码,在 options 参数中传入 { asn1: true } 即可,默认不开启
let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode, { asn1: false }) // 加密结果

let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) // 加密结果
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果
// 支持使用 asn1 对密文进行解码再解密,在 options 参数中传入 { asn1: true } 即可,默认不开启
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode, { asn1: false }) // 解密结果

encryptData = sm2.doEncrypt(msgArray, publicKey, cipherMode) // 加密结果,输入数组
decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode, {output: 'array'}) // 解密结果,输出数组
Expand Down
51 changes: 50 additions & 1 deletion src/sm2/asn1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as utils from '@noble/curves/abstract/utils';
import { ONE } from './bn';
import { utf8ToHex } from './utils';

export function bigintToValue(bigint: bigint) {
let h = bigint.toString(16)
Expand Down Expand Up @@ -81,6 +82,20 @@ class DERInteger extends ASN1Object {
}
}

class DEROctetString extends ASN1Object {
public hV: string = ''
constructor(public s: string) {
super()

this.t = '04' // octstr 标签说明
if (s) this.v = s.toLowerCase()
}

getValue() {
return this.v
}
}

class DERSequence extends ASN1Object {
public t = '30'
constructor(public asn1Array: ASN1Object[]) {
Expand Down Expand Up @@ -134,6 +149,14 @@ export function encodeDer(r: bigint, s: bigint) {
return derSeq.getEncodedHex()
}

export function encodeEnc(x: bigint, y: bigint, hash: string, cipher: string) {
const derX = new DERInteger(x)
const derY = new DERInteger(y)
const derHash = new DEROctetString(hash)
const derCipher = new DEROctetString(cipher)
const derSeq = new DERSequence([derX, derY, derHash, derCipher])
return derSeq.getEncodedHex()
}
/**
* 解析 ASN.1 der,针对 sm2 验签
*/
Expand All @@ -145,7 +168,7 @@ export function decodeDer(input: string) {

const vIndexR = getStartOfV(input, start)
const lR = getL(input, start)
const vR = input.substr(vIndexR, lR * 2)
const vR = input.substring(vIndexR, vIndexR +lR * 2)

const nextStart = vIndexR + vR.length
const vIndexS = getStartOfV(input, nextStart)
Expand All @@ -159,3 +182,29 @@ export function decodeDer(input: string) {

return { r, s }
}

/**
* 解析 ASN.1 der,针对 sm2 加密
*/
export function decodeEnc(input: string) {
// Extracts a sequence from the input based on the current start index.
function extractSequence(input: string, start: number): { value: string; nextStart: number } {
const vIndex = getStartOfV(input, start);
const length = getL(input, start);
const value = input.substring(vIndex, vIndex + length * 2);
const nextStart = vIndex + value.length;
return { value, nextStart };
}

const start = getStartOfV(input, 0);

const { value: vR, nextStart: startS } = extractSequence(input, start);
const { value: vS, nextStart: startHash } = extractSequence(input, startS);
const { value: hash, nextStart: startCipher } = extractSequence(input, startHash);
const { value: cipher } = extractSequence(input, startCipher);

const x = utils.hexToNumber(vR);
const y = utils.hexToNumber(vS);

return { x, y, hash, cipher };
}
52 changes: 40 additions & 12 deletions src/sm2/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-use-before-define */
import { encodeDer, decodeDer } from './asn1'
import { encodeDer, decodeDer, encodeEnc, decodeEnc } from './asn1'
import { arrayToHex, arrayToUtf8, generateKeyPairHex, hexToArray, leftPad, utf8ToHex } from './utils'
import { sm3 } from './sm3'
import * as utils from '@noble/curves/abstract/utils';
Expand All @@ -18,7 +18,9 @@ export const EmptyArray = new Uint8Array()
/**
* 加密
*/
export function doEncrypt(msg: string | Uint8Array, publicKey: string | ProjPointType<bigint>, cipherMode = 1) {
export function doEncrypt(msg: string | Uint8Array, publicKey: string | ProjPointType<bigint>, cipherMode = 1, options?: {
asn1?: boolean // 使用 ASN.1 对 C1 编码
}) {

const msgArr = typeof msg === 'string' ? hexToArray(utf8ToHex(msg)) : Uint8Array.from(msg)
const publicKeyPoint = typeof publicKey === 'string' ? sm2Curve.ProjectivePoint.fromHex(publicKey) :
Expand All @@ -29,6 +31,7 @@ export function doEncrypt(msg: string | Uint8Array, publicKey: string | ProjPoin

// c1 = k * G
let c1 = keypair.publicKey

if (c1.length > 128) c1 = c1.substring(c1.length - 128)
const p = publicKeyPoint.multiply(k)

Expand All @@ -41,7 +44,13 @@ export function doEncrypt(msg: string | Uint8Array, publicKey: string | ProjPoin

xorCipherStream(x2, y2, msgArr)
const c2 = bytesToHex(msgArr)

if (options?.asn1) {
const point = sm2Curve.ProjectivePoint.fromHex(keypair.publicKey)
const encode = cipherMode === C1C2C3 ?
encodeEnc(point.x, point.y, c2, c3) :
encodeEnc(point.x, point.y, c3, c2)
return encode
}
return cipherMode === C1C2C3 ? c1 + c2 + c3 : c1 + c3 + c2
}

Expand Down Expand Up @@ -78,34 +87,53 @@ function xorCipherStream(x2: Uint8Array, y2: Uint8Array, msg: Uint8Array) {
*/
export function doDecrypt(encryptData: string, privateKey: string, cipherMode?: number, options?: {
output: 'array'
asn1?: boolean
}): Uint8Array
export function doDecrypt(encryptData: string, privateKey: string, cipherMode?: number, options?: {
output: 'string'
output: 'string',
asn1?: boolean
}): string
export function doDecrypt(encryptData: string, privateKey: string, cipherMode = 1, {
output = 'string',
asn1 = false,
} = {}) {
const privateKeyInteger = utils.hexToNumber(privateKey)

let c3 = encryptData.substring(128, 128 + 64)
let c2 = encryptData.substring(128 + 64)
let c1: ProjPointType<bigint>
let c2: string
let c3: string


if (cipherMode === C1C2C3) {
c3 = encryptData.substring(encryptData.length - 64)
c2 = encryptData.substring(128, encryptData.length - 64)
if (asn1) {
const {x, y, cipher, hash} = decodeEnc(encryptData)
c1 = sm2Curve.ProjectivePoint.fromAffine({ x, y })
c3 = hash
c2 = cipher
if (cipherMode === C1C2C3) {
[c2, c3] = [c3, c2]
}
} else {
// c1c3c2
c1 = sm2Curve.ProjectivePoint.fromHex('04' + encryptData.substring(0, 128))!
c3 = encryptData.substring(128, 128 + 64)
c2 = encryptData.substring(128 + 64)

if (cipherMode === C1C2C3) {
c3 = encryptData.substring(encryptData.length - 64)
c2 = encryptData.substring(128, encryptData.length - 64)
}
}


const msg = hexToArray(c2)
const c1 = sm2Curve.ProjectivePoint.fromHex('04' + encryptData.substring(0, 128))!

const p = c1.multiply(privateKeyInteger)
const x2 = hexToArray(leftPad(utils.numberToHexUnpadded(p.x), 64))
const y2 = hexToArray(leftPad(utils.numberToHexUnpadded(p.y), 64))

xorCipherStream(x2, y2, msg)
// c3 = hash(x2 || msg || y2)
const checkC3 = arrayToHex(Array.from(sm3(utils.concatBytes(x2, msg, y2))))

if (checkC3 === c3.toLowerCase()) {
return output === 'array' ? msg : arrayToUtf8(msg)
} else {
Expand Down
31 changes: 31 additions & 0 deletions test/sm2.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sm2 } from '@/index'
import { arrayToHex, hexToArray } from '@/sm2'
import { expect, it, describe, beforeEach } from 'vitest'

const cipherMode = 1 // 1 - C1C3C2,0 - C1C2C3
Expand Down Expand Up @@ -93,6 +94,36 @@ describe('sm2: encrypt and decrypt data', () => {
});
})


describe('sm2: encrypt and decrypt data using asn1 encoding', () => {
it('decrypted data should be correct', () => {
const input = 'aabbccdd'
const res = sm2.doEncrypt(hexToArray(input), '049812a275eca335e85998eb4030a6cc9e88a098010bdbfc134b26e29c43253439d3821ef18e69e0813bcc55eee7dc9163f1edb81ad2032b20cbdf1408897faaac', 1, {
asn1: true,
})
// console.log('res', res)
const dec = sm2.doDecrypt(res, '75b25a5d6101013e9be25816f81cf1f64bf78ea8383b32d61f5b26e6f1429e70', 1, {
output: 'array',
asn1: true,
})
expect(arrayToHex([...dec])).toBe(input)
})
it('decrypted data should be correct: c1c2c3', () => {
const input = 'aabbccdd'
const res = sm2.doEncrypt(hexToArray(input), '049812a275eca335e85998eb4030a6cc9e88a098010bdbfc134b26e29c43253439d3821ef18e69e0813bcc55eee7dc9163f1edb81ad2032b20cbdf1408897faaac', 0, {
asn1: true,
})
// console.log('res', res)
const dec = sm2.doDecrypt(res, '75b25a5d6101013e9be25816f81cf1f64bf78ea8383b32d61f5b26e6f1429e70', 0, {
output: 'array',
asn1: true,
})
expect(arrayToHex([...dec])).toBe(input)
})

})


describe('sm2: sign data and verify sign', () => {
it('signature and generate ec point', ({ unCompressedPublicKey, compressedPublicKey, privateKey }) => {
for (const publicKey of [unCompressedPublicKey, compressedPublicKey]) {
Expand Down

0 comments on commit f08b9fd

Please sign in to comment.