diff --git a/package-lock.json b/package-lock.json index ac706ce..dc895a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,14 @@ { "name": "gmsm-sm2js", - "version": "0.6.1", + "version": "0.6.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gmsm-sm2js", - "version": "0.6.1", + "version": "0.6.4", "license": "Apache-2.0", "dependencies": { - "gmsm-sm3js": "^0.2.0", "jsrsasign": "^11.1.0" }, "devDependencies": { @@ -1663,14 +1662,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gmsm-sm3js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/gmsm-sm3js/-/gmsm-sm3js-0.2.0.tgz", - "integrity": "sha512-z4SZH+xoQtYSIp22qwwZ0Z45Fajaxav+Y2OGlEoAVm2tK+o0fOQHowPeh91FpCCH/+p0jNiKL2G9C4T9EEQJWA==", - "dependencies": { - "long": "^5.2.3" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2385,11 +2376,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/package.json b/package.json index c6b84bb..3597400 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "test": "standard && node src/cryptojs_sm3_test.js && node src/cryptojs_sm4_test.js && node src/sm2_test.js" }, "dependencies": { - "gmsm-sm3js": "^0.2.0", "jsrsasign": "^11.1.0" }, "devDependencies": { diff --git a/src/jsrsasign_patch.js b/src/jsrsasign_patch.js index cfb75e3..618832a 100644 --- a/src/jsrsasign_patch.js +++ b/src/jsrsasign_patch.js @@ -1,6 +1,7 @@ const rs = require('jsrsasign') const KJUR = rs.KJUR const C = rs.CryptoJS +const CEnc = C.enc function parsePBES2 (hP8Prv) { const pASN = rs.ASN1HEX.parse(hP8Prv) @@ -246,10 +247,34 @@ function patchSM4 () { rs.asn1.x509.OID.name2oidList['sm4-CBC'] = '1.2.156.10197.1.104.2' } +function patchEncUint8Array () { + CEnc.Uint8Array = CEnc.Uint8Array || { + stringify: function (wordArray) { + const bytes = [] + const words = wordArray.words + const sigBytes = wordArray.sigBytes + for (let i = 0; i < sigBytes; i++) { + bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff) + } + return new Uint8Array(bytes) + }, + + parse: function (uint8Array) { + const words = [] + const dataLen = uint8Array.length + for (let i = 0; i < uint8Array.length; i++) { + words[i >>> 2] |= uint8Array[i] << (24 - (i % 4) * 8) + } + return C.lib.WordArray.create(words, dataLen) + } + } +} + let patched = false function patch () { if (patched) return patched = true + patchEncUint8Array() require('./cryptojs_sm3') require('./cryptojs_sm4') patchSM3() diff --git a/src/sm2.js b/src/sm2.js index a7fbc45..2832996 100644 --- a/src/sm2.js +++ b/src/sm2.js @@ -1,6 +1,5 @@ const rs = require('jsrsasign') require('./jsrsasign_patch').patch() -const sm3 = require('gmsm-sm3js') const util = require('./util') let crypto @@ -29,6 +28,7 @@ const SM2_CURVE_PARAM_B = '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCB const SM2_CURVE_PARAM_N = 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123' const SM2_CURVE_PARAM_GX = '32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7' const SM2_CURVE_PARAM_GY = 'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0' +const SM2_CURVE_PARAMS_FOR_ZA = 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E9332C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0' const CIPHERTEXT_ENCODING_PLAIN = 0 const CIPHERTEXT_ENCODING_ASN1 = 1 @@ -99,14 +99,14 @@ function adaptSM2 (ecdsa) { } ecdsa.encryptHex = function (dataHex, opts = DEFAULT_SM2_ENCRYPT_OPTIONS) { - return this.encrypt(sm3.fromHex(dataHex), opts) + return this.encrypt(new Uint8Array(Buffer.from(dataHex, 'hex')), opts) } ecdsa.encryptRaw = function (data, Q, opts = DEFAULT_SM2_ENCRYPT_OPTIONS) { if (!opts || !(opts instanceof EncrypterOptions)) { opts = DEFAULT_SM2_ENCRYPT_OPTIONS } - data = sm3.normalizeInput(data) + data = util.normalizeInput(data) const n = this.ecparams.n const G = this.ecparams.G const dataLen = data.length @@ -135,12 +135,12 @@ function adaptSM2 (ecdsa) { md.update(new Uint8Array(util.integerToBytes(point2.getY().toBigInteger(), SM2_BYTE_SIZE))) const hash = md.digestRaw() if (opts.getEncodingFormat() === CIPHERTEXT_ENCODING_PLAIN) { - return sm3.toHex(new Uint8Array(point1.getEncoded(false))) + sm3.toHex(hash) + sm3.toHex(t) + return Buffer.from(point1.getEncoded(false)).toString('hex') + Buffer.from(hash).toString('hex') + Buffer.from(t).toString('hex') } const derX = new rs.asn1.DERInteger({ bigint: point1.getX().toBigInteger() }) const derY = new rs.asn1.DERInteger({ bigint: point1.getY().toBigInteger() }) - const derC3 = new rs.asn1.DEROctetString({ hex: sm3.toHex(hash) }) - const derC2 = new rs.asn1.DEROctetString({ hex: sm3.toHex(t) }) + const derC3 = new rs.asn1.DEROctetString({ hex: Buffer.from(hash).toString('hex') }) + const derC2 = new rs.asn1.DEROctetString({ hex: Buffer.from(t).toString('hex') }) const derSeq = new rs.asn1.DERSequence({ array: [derX, derY, derC3, derC2] }) return derSeq.getEncodedHex() } while (true) @@ -155,11 +155,11 @@ function adaptSM2 (ecdsa) { } ecdsa.decryptHex = function (dataHex) { - return this.decrypt(sm3.fromHex(dataHex)) + return this.decrypt(new Uint8Array(Buffer.from(dataHex, 'hex'))) } ecdsa.decryptRaw = function (data, d) { - data = sm3.normalizeInput(data) + data = util.normalizeInput(data) const dataLen = data.length if (data[0] !== UNCOMPRESSED) { @@ -179,7 +179,7 @@ function adaptSM2 (ecdsa) { for (let i = 0; i < c3.length; i++) { c2[i] ^= t[i] } - return sm3.toHex(c2) + return Buffer.from(c2).toString('hex') } ecdsa.signWithMessageHash = function (hashHex) { @@ -245,7 +245,7 @@ function adaptSM2 (ecdsa) { if (!uid) { uid = DEFAULT_UID } - uid = sm3.normalizeInput(uid) + uid = util.normalizeInput(uid) const uidLen = uid.length if (uidLen >= 0x2000) { throw new Error('SM2: the uid is too long') @@ -254,10 +254,7 @@ function adaptSM2 (ecdsa) { const md = new MessageDigest() md.update(new Uint8Array([0xff & (entla >>> 8), 0xff & entla])) md.update(uid) - md.update(sm3.fromHex(SM2_CURVE_PARAM_A)) // a - md.update(sm3.fromHex(SM2_CURVE_PARAM_B)) // b - md.update(sm3.fromHex(SM2_CURVE_PARAM_GX)) // gx - md.update(sm3.fromHex(SM2_CURVE_PARAM_GY)) // gy + md.update(new Uint8Array(Buffer.from(SM2_CURVE_PARAMS_FOR_ZA, 'hex'))) // a||b||gx||gy let Q if (this.pubKeyHex) { Q = rs.ECPointFp.decodeFromHex(this.ecparams.curve, this.pubKeyHex) @@ -265,6 +262,7 @@ function adaptSM2 (ecdsa) { const d = new rs.BigInteger(this.prvKeyHex, 16) const G = this.ecparams.G Q = G.multiply(d) + this.pubKeyHex = Buffer.from(Q.getEncoded()).toString('hex') } md.update(new Uint8Array(util.integerToBytes(Q.getX().toBigInteger(), SM2_BYTE_SIZE))) // x md.update(new Uint8Array(util.integerToBytes(Q.getY().toBigInteger(), SM2_BYTE_SIZE))) // y @@ -274,7 +272,7 @@ function adaptSM2 (ecdsa) { } function kdf (data, len) { - data = sm3.normalizeInput(data) + data = util.normalizeInput(data) const limit = (len + SM3_SIZE - 1) >>> SM3_SIZE_BIT_SIZE const countBytes = new Uint8Array(4) let ct = 1 @@ -306,20 +304,41 @@ class MessageDigest { if (useNodeSM3) { this.md = crypto.createHash('sm3') } else { - this.md = sm3.create() + this.md = rs.KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME.sm3.create() } } + /** + * Updates the hash content with the given data + * @param {Uint8Array} data The Uint8Array to be hashed + */ update (data) { - this.md.update(data) + if (useNodeSM3) { + this.md.update(data) + } else { + this.md.update(rs.CryptoJS.enc.Uint8Array.parse(data)) + } } + /** + * Updates the hash content with the given hex data + * @param {Uint8Array} hex The hex data + */ updateHex (hex) { - this.md.update(sm3.fromHex(hex)) + if (useNodeSM3) { + this.md.update(new Uint8Array(Buffer.from(hex, 'hex'))) + } else { + this.md.update(rs.CryptoJS.enc.Hex.parse(hex)) + } } digestRaw () { - return useNodeSM3 ? this.md.digest() : this.md.finalize() + if (useNodeSM3) { + return this.md.digest() + } else { + const hash = this.md.finalize() + return rs.CryptoJS.enc.Uint8Array.stringify(hash) + } } digest (data) { @@ -328,8 +347,7 @@ class MessageDigest { } if (!useNodeSM3) { const hash = this.md.finalize() - this.md.reset() - return sm3.toHex(hash) + return hash.toString(rs.CryptoJS.enc.Hex) } else { const h = this.md.digest('hex') this.md = crypto.createHash('sm3') @@ -552,7 +570,7 @@ function encrypt (pubkey, data, opts = DEFAULT_SM2_ENCRYPT_OPTIONS) { * @returns hex ans.1 format ciphertext */ function plainCiphertext2ASN1 (data) { - data = sm3.fromHex(data) + data = new Uint8Array(Buffer.from(data, 'hex')) const dataLen = data.length if (data[0] !== UNCOMPRESSED) { @@ -566,8 +584,8 @@ function plainCiphertext2ASN1 (data) { const c3 = data.subarray(65, 97) const derX = new rs.asn1.DERInteger({ bigint: point1.getX().toBigInteger() }) const derY = new rs.asn1.DERInteger({ bigint: point1.getY().toBigInteger() }) - const derC3 = new rs.asn1.DEROctetString({ hex: sm3.toHex(c3) }) - const derC2 = new rs.asn1.DEROctetString({ hex: sm3.toHex(c2) }) + const derC3 = new rs.asn1.DEROctetString({ hex: Buffer.from(c3).toString('hex') }) + const derC2 = new rs.asn1.DEROctetString({ hex: Buffer.from(c2).toString('hex') }) const derSeq = new rs.asn1.DERSequence({ array: [derX, derY, derC3, derC2] }) return derSeq.getEncodedHex() @@ -613,7 +631,7 @@ function asn1Ciphertext2Plain (hexASN1Data) { const c3 = aValue[2] const c2 = aValue[3] - return sm3.toHex(new Uint8Array(point.getEncoded(false))) + c3 + c2 + return Buffer.from(point.getEncoded(false)).toString('hex') + c3 + c2 } /** @@ -625,7 +643,7 @@ function asn1Ciphertext2Plain (hexASN1Data) { * @returns hex plain format ciphertext */ function encryptHex (pubkey, data, opts = DEFAULT_SM2_ENCRYPT_OPTIONS) { - return encrypt(pubkey, sm3.fromHex(data), opts) + return encrypt(pubkey, new Uint8Array(Buffer.from(data, 'hex')), opts) } /** @@ -667,7 +685,7 @@ function decryptHex (prvKey, data) { if (tag === '30') { data = asn1Ciphertext2Plain(data) } - return decrypt(prvKey, sm3.fromHex(data)) + return decrypt(prvKey, new Uint8Array(Buffer.from(data, 'hex'))) } function getCurveName () { @@ -689,7 +707,7 @@ rs.asn1.csr.CSRUtil.newCSRPEM = function (param) { const hCSRI = (new rs.asn1.csr.CertificationRequestInfo(this.params)).getEncodedHex() const sig = new Signature({ alg: this.params.sigalg }) sig.init(this.params.sbjprvkey) - const sighex = sig.sm2Sign(sm3.fromHex(hCSRI)) + const sighex = sig.sm2Sign(new Uint8Array(Buffer.from(hCSRI, 'hex'))) this.params.sighex = sighex } } @@ -710,7 +728,7 @@ function createX509 () { const sig = new Signature({ alg: algName }) sig.init(pubKey) - return sig.sm2Verify(hSigVal, sm3.fromHex(hTbsCert)) + return sig.sm2Verify(hSigVal, new Uint8Array(Buffer.from(hTbsCert, 'hex'))) } return x } diff --git a/src/sm2_test.js b/src/sm2_test.js index 4574ba1..23c5237 100644 --- a/src/sm2_test.js +++ b/src/sm2_test.js @@ -1,6 +1,5 @@ const test = require('tape') const rs = require('jsrsasign') -const sm3 = require('gmsm-sm3js') const sm2 = require('./sm2') const publicKeyPemFromAliKmsForSign = `-----BEGIN PUBLIC KEY----- @@ -172,7 +171,7 @@ test('SM2 parse public key pem, verify signature, both from ali KMS', function ( test('SM2 calculate ZA', function (t) { const sig = sm2.createSM2Signature() sig.init(publicKeyPemFromAliKmsForSign) - const za = sm3.toHex(sig.pubKey.calculateZA()) + const za = Buffer.from(sig.pubKey.calculateZA()).toString('hex') t.equal(za, '17e7fc071f1418200aeead3c5118a2f18381431d92b808a3bd1ba2d8270c2914') t.end() }) diff --git a/src/util.js b/src/util.js index c2abcc4..3c2b4b2 100644 --- a/src/util.js +++ b/src/util.js @@ -11,6 +11,22 @@ function integerToBytes (i, len) { return bytes } +// For convenience, let people hash a string, not just a Uint8Array +function normalizeInput (input) { + let ret + if (input instanceof Uint8Array) { + ret = input + } else if (input instanceof Buffer) { + ret = new Uint8Array(input) + } else if (typeof input === 'string') { + ret = new Uint8Array(Buffer.from(input, 'utf8')) + } else { + throw new Error('Input must be an string, Buffer or Uint8Array') + } + return ret +} + module.exports = { - integerToBytes + integerToBytes, + normalizeInput }