├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── UPDATE.md ├── gulpfile.js ├── package.json ├── src ├── index.js ├── sm2 │ ├── asn1.js │ ├── ec.js │ ├── index.js │ ├── sm3.js │ └── utils.js ├── sm3 │ └── index.js └── sm4 │ └── index.js ├── test ├── sm2.test.js ├── sm3.test.js ├── sm4.test.js └── test.jpg └── tools ├── build.js ├── checkcomponents.js ├── config.js ├── demo ├── app.js ├── app.json ├── app.wxss ├── package.json ├── pages │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss └── project.config.json ├── test └── helper.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["module-resolver", { 4 | "root": ["./src"], 5 | "alias": {} 6 | }] 7 | ], 8 | "presets": [ 9 | ["env", {"loose": true, "modules": "commonjs"}] 10 | ] 11 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'airbnb-base', 4 | 'plugin:promise/recommended' 5 | ], 6 | 'parserOptions': { 7 | 'ecmaVersion': 9, 8 | 'ecmaFeatures': { 9 | 'jsx': false 10 | }, 11 | 'sourceType': 'module' 12 | }, 13 | 'env': { 14 | 'es6': true, 15 | 'node': true, 16 | 'jest': true 17 | }, 18 | 'plugins': [ 19 | 'import', 20 | 'node', 21 | 'promise' 22 | ], 23 | 'rules': { 24 | 'arrow-parens': 'off', 25 | 'comma-dangle': [ 26 | 'error', 27 | 'only-multiline' 28 | ], 29 | 'complexity': ['error', 20], 30 | 'func-names': 'off', 31 | 'global-require': 'off', 32 | 'handle-callback-err': [ 33 | 'error', 34 | '^(err|error)$' 35 | ], 36 | 'import/no-unresolved': [ 37 | 'error', 38 | { 39 | 'caseSensitive': true, 40 | 'commonjs': true, 41 | 'ignore': ['^[^.]'] 42 | } 43 | ], 44 | 'import/prefer-default-export': 'off', 45 | 'linebreak-style': 'off', 46 | 'no-catch-shadow': 'error', 47 | 'no-continue': 'off', 48 | 'no-div-regex': 'warn', 49 | 'no-else-return': 'off', 50 | 'no-param-reassign': 'off', 51 | 'no-plusplus': 'off', 52 | 'no-shadow': 'off', 53 | 'no-multi-assign': 'off', 54 | 'no-underscore-dangle': 'off', 55 | 'node/no-deprecated-api': 'error', 56 | 'node/process-exit-as-throw': 'error', 57 | 'object-curly-spacing': [ 58 | 'error', 59 | 'never' 60 | ], 61 | 'operator-linebreak': [ 62 | 'error', 63 | 'after', 64 | { 65 | 'overrides': { 66 | ':': 'before', 67 | '?': 'before' 68 | } 69 | } 70 | ], 71 | 'prefer-arrow-callback': 'off', 72 | 'prefer-destructuring': 'off', 73 | 'prefer-template': 'off', 74 | 'quote-props': [ 75 | 1, 76 | 'as-needed', 77 | { 78 | 'unnecessary': true 79 | } 80 | ], 81 | 'semi': [ 82 | 'error', 83 | 'never' 84 | ], 85 | 'max-len': 'off', 86 | 'no-bitwise': 'off', 87 | 'no-mixed-operators': 'off', 88 | }, 89 | 'globals': { 90 | 'window': true, 91 | 'document': true, 92 | 'App': true, 93 | 'Page': true, 94 | 'Component': true, 95 | 'Behavior': true 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | package-lock.json 4 | 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | miniprogram_dist 12 | miniprogram_dev 13 | node_modules 14 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | package-lock.json 4 | 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | test 12 | tools 13 | docs 14 | miniprogram_dev 15 | node_modules 16 | coverage -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sm-crypto 2 | 3 | 小程序 js 库。国密算法 sm2、sm3 和 sm4 的实现。 4 | 5 | > 使用此组件需要依赖小程序基础库 2.2.1 以上版本,同时依赖开发者工具的 npm 构建。具体详情可查阅[官方 npm 文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)。 6 | 7 | ## 安装 8 | 9 | ```bash 10 | npm install --save miniprogram-sm-crypto 11 | ``` 12 | 13 | ## sm2 14 | 15 | ### 获取密钥对 16 | 17 | ```js 18 | const sm2 = require('miniprogram-sm-crypto').sm2 19 | 20 | let keypair = sm2.generateKeyPairHex() 21 | 22 | publicKey = keypair.publicKey // 公钥 23 | privateKey = keypair.privateKey // 私钥 24 | 25 | // 默认生成公钥 130 位太长,可以压缩公钥到 66 位 26 | const compressedPublicKey = sm2.compressPublicKeyHex(publicKey) // compressedPublicKey 和 publicKey 等价 27 | sm2.comparePublicKeyHex(publicKey, compressedPublicKey) // 判断公钥是否等价 28 | 29 | // 自定义随机数,参数会直接透传给 jsbn 库的 BigInteger 构造器 30 | // 注意:开发者使用自定义随机数,需要自行确保传入的随机数符合密码学安全 31 | let keypair2 = sm2.generateKeyPairHex('123123123123123') 32 | let keypair3 = sm2.generateKeyPairHex(256, SecureRandom) 33 | 34 | let verifyResult = sm2.verifyPublicKey(publicKey) // 验证公钥 35 | verifyResult = sm2.verifyPublicKey(compressedPublicKey) // 验证公钥 36 | ``` 37 | 38 | ### 加密解密 39 | 40 | ```js 41 | const sm2 = require('miniprogram-sm-crypto').sm2 42 | const cipherMode = 1 // 1 - C1C3C2,0 - C1C2C3,默认为1 43 | 44 | let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) // 加密结果 45 | let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果 46 | 47 | encryptData = sm2.doEncrypt(msgArray, publicKey, cipherMode) // 加密结果,输入数组 48 | decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode, {output: 'array'}) // 解密结果,输出数组 49 | ``` 50 | 51 | > ps:密文会在解密时自动补充 `04`,如遇到其他工具补充的 `04` 需手动去除再传入。 52 | 53 | ### 签名验签 54 | 55 | > ps:理论上来说,只做纯签名是最快的。 56 | 57 | ```js 58 | const sm2 = require('miniprogram-sm-crypto').sm2 59 | 60 | // 纯签名 + 生成椭圆曲线点 61 | let sigValueHex = sm2.doSignature(msg, privateKey) // 签名 62 | let verifyResult = sm2.doVerifySignature(msg, sigValueHex, publicKey) // 验签结果 63 | 64 | // 纯签名 65 | let sigValueHex2 = sm2.doSignature(msg, privateKey, { 66 | pointPool: [sm2.getPoint(), sm2.getPoint(), sm2.getPoint(), sm2.getPoint()], // 传入事先已生成好的椭圆曲线点,可加快签名速度 67 | }) // 签名 68 | let verifyResult2 = sm2.doVerifySignature(msg, sigValueHex2, publicKey) // 验签结果 69 | 70 | // 纯签名 + 生成椭圆曲线点 + der编解码 71 | let sigValueHex3 = sm2.doSignature(msg, privateKey, { 72 | der: true, 73 | }) // 签名 74 | let verifyResult3 = sm2.doVerifySignature(msg, sigValueHex3, publicKey, { 75 | der: true, 76 | }) // 验签结果 77 | 78 | // 纯签名 + 生成椭圆曲线点 + sm3杂凑 79 | let sigValueHex4 = sm2.doSignature(msg, privateKey, { 80 | hash: true, 81 | }) // 签名 82 | let verifyResult4 = sm2.doVerifySignature(msg, sigValueHex4, publicKey, { 83 | hash: true, 84 | }) // 验签结果 85 | 86 | // 纯签名 + 生成椭圆曲线点 + sm3杂凑(不做公钥推导) 87 | let sigValueHex5 = sm2.doSignature(msg, privateKey, { 88 | hash: true, 89 | publicKey, // 传入公钥的话,可以去掉sm3杂凑中推导公钥的过程,速度会比纯签名 + 生成椭圆曲线点 + sm3杂凑快 90 | }) 91 | let verifyResult5 = sm2.doVerifySignature(msg, sigValueHex5, publicKey, { 92 | hash: true, 93 | publicKey, 94 | }) 95 | 96 | // 纯签名 + 生成椭圆曲线点 + sm3杂凑 + 不做公钥推 + 添加 userId(长度小于 8192) 97 | // 默认 userId 值为 1234567812345678 98 | let sigValueHex6 = sm2.doSignature(msgString, privateKey, { 99 | hash: true, 100 | publicKey, 101 | userId: 'testUserId', 102 | }) 103 | let verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 104 | hash: true, 105 | userId: 'testUserId', 106 | }) 107 | ``` 108 | 109 | ### 获取椭圆曲线点 110 | 111 | ```js 112 | const sm2 = require('miniprogram-sm-crypto').sm2 113 | 114 | let point = sm2.getPoint() // 获取一个椭圆曲线点,可在sm2签名时传入 115 | ``` 116 | 117 | ### 根据私钥获取公钥 118 | 119 | ```js 120 | const sm2 = require('sm-crypto).sm2 121 | 122 | let publicKey = sm2.getPublicKeyFromPrivateKey(privateKey) 123 | ``` 124 | 125 | ## sm3 126 | 127 | ```js 128 | const sm3 = require('miniprogram-sm-crypto').sm3 129 | 130 | let hashData = sm3('abc') // 杂凑 131 | 132 | // hmac 133 | hashData = sm3('abc', { 134 | key: 'daac25c1512fe50f79b0e4526b93f5c0e1460cef40b6dd44af13caec62e8c60e0d885f3c6d6fb51e530889e6fd4ac743a6d332e68a0f2a3923f42585dceb93e9', // 要求为 16 进制串或字节数组 135 | }) 136 | ``` 137 | 138 | ## sm4 139 | 140 | ### 加密 141 | 142 | ```js 143 | const sm4 = require('miniprogram-sm-crypto').sm4 144 | const msg = 'hello world! 我是 juneandgreen.' // 可以为 utf8 串或字节数组 145 | const key = '0123456789abcdeffedcba9876543210' // 可以为 16 进制串或字节数组,要求为 128 比特 146 | 147 | let encryptData = sm4.encrypt(msg, key) // 加密,默认输出 16 进制字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充) 148 | let encryptData = sm4.encrypt(msg, key, {padding: 'none'}) // 加密,不使用 padding 149 | let encryptData = sm4.encrypt(msg, key, {padding: 'none', output: 'array'}) // 加密,不使用 padding,输出为字节数组 150 | let encryptData = sm4.encrypt(msg, key, {mode: 'cbc', iv: 'fedcba98765432100123456789abcdef'}) // 加密,cbc 模式 151 | ``` 152 | 153 | ### 解密 154 | 155 | ```js 156 | const sm4 = require('miniprogram-sm-crypto').sm4 157 | const encryptData = '0e395deb10f6e8a17e17823e1fd9bd98a1bff1df508b5b8a1efb79ec633d1bb129432ac1b74972dbe97bab04f024e89c' // 可以为 16 进制串或字节数组 158 | const key = '0123456789abcdeffedcba9876543210' // 可以为 16 进制串或字节数组,要求为 128 比特 159 | 160 | let decryptData = sm4.decrypt(encryptData, key) // 解密,默认输出 utf8 字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充) 161 | let decryptData = sm4.decrypt(encryptData, key, {padding: 'none'}) // 解密,不使用 padding 162 | let decryptData = sm4.decrypt(encryptData, key, {padding: 'none', output: 'array'}) // 解密,不使用 padding,输出为字节数组 163 | let decryptData = sm4.decrypt(encryptData, key, {mode: 'cbc', iv: 'fedcba98765432100123456789abcdef'}) // 解密,cbc 模式 164 | ``` 165 | 166 | ## 协议 167 | 168 | MIT 169 | -------------------------------------------------------------------------------- /UPDATE.md: -------------------------------------------------------------------------------- 1 | ## 0.3.13 2 | 3 | * 支持根据私钥获取公钥 4 | 5 | ## 0.3.12 6 | 7 | * 优化 sm3 运行性能 8 | 9 | ## 0.3.11 10 | 11 | * sm2 支持压缩公钥 12 | 13 | ## 0.3.10 14 | 15 | * 支持 sm3 hmac 模式 16 | 17 | ## 0.3.9 18 | 19 | * 补充 sm4 解密时的 padding 判断 20 | 21 | ## 0.3.8 22 | 23 | * sm2 解密时兼容密文可能是大写的情况 24 | 25 | ## 0.3.7 26 | 27 | * 默认填充改为 pkcs#7,如传入 pkcs#5 也转到 pkcs#7 逻辑 28 | 29 | ## 0.3.6 30 | 31 | * sm2 加解密支持二进制数据 32 | 33 | ## 0.3.5 34 | 35 | * sm2.generateKeyPairHex 支持完整的 BigInteger 入参 36 | 37 | ## 0.3.4 38 | 39 | * sm2 支持验证公钥接口 40 | * sm2 生成密钥时支持自定义随机数 41 | 42 | ## 0.3.2 43 | 44 | * 修复 sm2 在 userId 长度大于 31 时新旧版本验签不通过的问题 45 | 46 | ## 0.3.0 47 | 48 | * sm2、sm3 重构 49 | * sm4 支持 cbc 模式 50 | 51 | ## 0.2.7 52 | 53 | * 优化 sm3 性能 54 | 55 | ## 0.2.5 56 | 57 | * sm3 支持字节数组输入 58 | 59 | ## 0.2.4 60 | 61 | * 修复 sm4 四字节字符输出编码 62 | 63 | ## 0.2.3 64 | 65 | * sm3/sm4 支持输入四字节字符 66 | 67 | ## 0.2.2 68 | 69 | * sm3 支持中文输入 70 | 71 | ## 0.2.0 72 | 73 | * 修复 sm2 点 16 进制串可能不满 64 位的问题 74 | * sm4 默认支持 pkcs#5 填充方式 75 | * sm4 支持输入输出为字符串 76 | 77 | ## 0.1.3 78 | 79 | * 支持用户传入 userId 80 | 81 | ## 0.1.0 82 | 83 | * 修复 sm2 中 z 值数组可能包含 256 的问题。 84 | 85 | ## 0.0.6 86 | 87 | * 修复加解密偶现失败的情况。 88 | 89 | ## 0.0.5 90 | 91 | * 修复签名时如果使用 hash,会执行两次 utf8 转 hex 的问题。 92 | * 修复 sm2/utils/js 中当传入 hex 为奇数位串会导致出错的问题。 93 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const clean = require('gulp-clean'); 3 | 4 | const config = require('./tools/config'); 5 | const BuildTask = require('./tools/build'); 6 | const id = require('./package.json').name || 'miniprogram-custom-component'; 7 | 8 | // build task instance 9 | new BuildTask(id, config.entry); 10 | 11 | // clean the generated folders and files 12 | gulp.task('clean', gulp.series(() => { 13 | return gulp.src(config.distPath, { read: false, allowEmpty: true }) 14 | .pipe(clean()) 15 | }, done => { 16 | if (config.isDev) { 17 | return gulp.src(config.demoDist, { read: false, allowEmpty: true }) 18 | .pipe(clean()); 19 | } 20 | 21 | done(); 22 | })); 23 | // watch files and build 24 | gulp.task('watch', gulp.series(`${id}-watch`)); 25 | // build for develop 26 | gulp.task('dev', gulp.series(`${id}-dev`)); 27 | // build for publish 28 | gulp.task('default', gulp.series(`${id}-default`)); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprogram-sm-crypto", 3 | "version": "0.3.13", 4 | "description": "miniprogram js library", 5 | "main": "miniprogram_dist/index.js", 6 | "scripts": { 7 | "prepublish": "npm run build", 8 | "dev": "gulp dev --develop", 9 | "watch": "gulp watch --develop --watch", 10 | "build": "gulp", 11 | "dist": "npm run build", 12 | "clean-dev": "gulp clean --develop", 13 | "clean": "gulp clean", 14 | "test": "jest ./test/* --silent --bail", 15 | "coverage": "jest ./test/* --coverage --bail", 16 | "lint": "eslint \"src/**/*.js\" --fix", 17 | "lint-tools": "eslint \"tools/**/*.js\" --rule \"import/no-extraneous-dependencies: false\"" 18 | }, 19 | "miniprogram": "miniprogram_dist", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/wechat-miniprogram/sm-crypto.git" 23 | }, 24 | "author": "wechat-miniprogram", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "babel-core": "^6.26.3", 28 | "babel-loader": "^7.1.5", 29 | "babel-plugin-module-resolver": "^3.1.1", 30 | "babel-preset-env": "^1.7.0", 31 | "colors": "^1.3.1", 32 | "eslint": "^5.3.0", 33 | "eslint-loader": "^2.1.0", 34 | "gulp": "^4.0.0", 35 | "gulp-clean": "^0.4.0", 36 | "gulp-if": "^2.0.2", 37 | "gulp-install": "^1.1.0", 38 | "gulp-less": "^3.5.0", 39 | "gulp-rename": "^1.4.0", 40 | "gulp-sourcemaps": "^2.6.4", 41 | "j-component": "git+https://github.com/JuneAndGreen/j-component.git", 42 | "jest": "^23.5.0", 43 | "through2": "^2.0.3", 44 | "webpack": "^4.16.5", 45 | "webpack-node-externals": "^1.7.2", 46 | "eslint-config-airbnb-base": "13.1.0", 47 | "eslint-plugin-import": "^2.14.0", 48 | "eslint-plugin-node": "^7.0.1", 49 | "eslint-plugin-promise": "^3.8.0" 50 | }, 51 | "dependencies": { 52 | "jsbn": "^1.1.0" 53 | }, 54 | "jest": { 55 | "testEnvironment": "jsdom", 56 | "testURL": "https://jest.test", 57 | "collectCoverageFrom": [ 58 | "src/**/*.js" 59 | ], 60 | "moduleDirectories": [ 61 | "node_modules", 62 | "src" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sm2: require('./sm2/index'), 3 | sm3: require('./sm3/index'), 4 | sm4: require('./sm4/index'), 5 | } 6 | -------------------------------------------------------------------------------- /src/sm2/asn1.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | const {BigInteger} = require('jsbn') 3 | 4 | function bigintToValue(bigint) { 5 | let h = bigint.toString(16) 6 | if (h[0] !== '-') { 7 | // 正数 8 | if (h.length % 2 === 1) h = '0' + h // 补齐到整字节 9 | else if (!h.match(/^[0-7]/)) h = '00' + h // 非0开头,则补一个全0字节 10 | } else { 11 | // 负数 12 | h = h.substr(1) 13 | 14 | let len = h.length 15 | if (len % 2 === 1) len += 1 // 补齐到整字节 16 | else if (!h.match(/^[0-7]/)) len += 2 // 非0开头,则补一个全0字节 17 | 18 | let mask = '' 19 | for (let i = 0; i < len; i++) mask += 'f' 20 | mask = new BigInteger(mask, 16) 21 | 22 | // 对绝对值取反,加1 23 | h = mask.xor(bigint).add(BigInteger.ONE) 24 | h = h.toString(16).replace(/^-/, '') 25 | } 26 | return h 27 | } 28 | 29 | class ASN1Object { 30 | constructor() { 31 | this.tlv = null 32 | this.t = '00' 33 | this.l = '00' 34 | this.v = '' 35 | } 36 | 37 | /** 38 | * 获取 der 编码比特流16进制串 39 | */ 40 | getEncodedHex() { 41 | if (!this.tlv) { 42 | this.v = this.getValue() 43 | this.l = this.getLength() 44 | this.tlv = this.t + this.l + this.v 45 | } 46 | return this.tlv 47 | } 48 | 49 | getLength() { 50 | const n = this.v.length / 2 // 字节数 51 | let nHex = n.toString(16) 52 | if (nHex.length % 2 === 1) nHex = '0' + nHex // 补齐到整字节 53 | 54 | if (n < 128) { 55 | // 短格式,以 0 开头 56 | return nHex 57 | } else { 58 | // 长格式,以 1 开头 59 | const head = 128 + nHex.length / 2 // 1(1位) + 真正的长度占用字节数(7位) + 真正的长度 60 | return head.toString(16) + nHex 61 | } 62 | } 63 | 64 | getValue() { 65 | return '' 66 | } 67 | } 68 | 69 | class DERInteger extends ASN1Object { 70 | constructor(bigint) { 71 | super() 72 | 73 | this.t = '02' // 整型标签说明 74 | if (bigint) this.v = bigintToValue(bigint) 75 | } 76 | 77 | getValue() { 78 | return this.v 79 | } 80 | } 81 | 82 | class DERSequence extends ASN1Object { 83 | constructor(asn1Array) { 84 | super() 85 | 86 | this.t = '30' // 序列标签说明 87 | this.asn1Array = asn1Array 88 | } 89 | 90 | getValue() { 91 | this.v = this.asn1Array.map(asn1Object => asn1Object.getEncodedHex()).join('') 92 | return this.v 93 | } 94 | } 95 | 96 | /** 97 | * 获取 l 占用字节数 98 | */ 99 | function getLenOfL(str, start) { 100 | if (+str[start + 2] < 8) return 1 // l 以0开头,则表示短格式,只占一个字节 101 | return +str.substr(start + 2, 2) & 0x7f + 1 // 长格式,取第一个字节后7位作为长度真正占用字节数,再加上本身 102 | } 103 | 104 | /** 105 | * 获取 l 106 | */ 107 | function getL(str, start) { 108 | // 获取 l 109 | const len = getLenOfL(str, start) 110 | const l = str.substr(start + 2, len * 2) 111 | 112 | if (!l) return -1 113 | const bigint = +l[0] < 8 ? new BigInteger(l, 16) : new BigInteger(l.substr(2), 16) 114 | 115 | return bigint.intValue() 116 | } 117 | 118 | /** 119 | * 获取 v 的位置 120 | */ 121 | function getStartOfV(str, start) { 122 | const len = getLenOfL(str, start) 123 | return start + (len + 1) * 2 124 | } 125 | 126 | module.exports = { 127 | /** 128 | * ASN.1 der 编码,针对 sm2 签名 129 | */ 130 | encodeDer(r, s) { 131 | const derR = new DERInteger(r) 132 | const derS = new DERInteger(s) 133 | const derSeq = new DERSequence([derR, derS]) 134 | 135 | return derSeq.getEncodedHex() 136 | }, 137 | 138 | /** 139 | * 解析 ASN.1 der,针对 sm2 验签 140 | */ 141 | decodeDer(input) { 142 | // 结构: 143 | // input = | tSeq | lSeq | vSeq | 144 | // vSeq = | tR | lR | vR | tS | lS | vS | 145 | const start = getStartOfV(input, 0) 146 | 147 | const vIndexR = getStartOfV(input, start) 148 | const lR = getL(input, start) 149 | const vR = input.substr(vIndexR, lR * 2) 150 | 151 | const nextStart = vIndexR + vR.length 152 | const vIndexS = getStartOfV(input, nextStart) 153 | const lS = getL(input, nextStart) 154 | const vS = input.substr(vIndexS, lS * 2) 155 | 156 | const r = new BigInteger(vR, 16) 157 | const s = new BigInteger(vS, 16) 158 | 159 | return {r, s} 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/sm2/ec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-case-declarations, max-len */ 2 | const {BigInteger} = require('jsbn') 3 | 4 | /** 5 | * thanks for Tom Wu : http://www-cs-students.stanford.edu/~tjw/jsbn/ 6 | * 7 | * Basic Javascript Elliptic Curve implementation 8 | * Ported loosely from BouncyCastle's Java EC code 9 | * Only Fp curves implemented for now 10 | */ 11 | 12 | const TWO = new BigInteger('2') 13 | const THREE = new BigInteger('3') 14 | 15 | /** 16 | * 椭圆曲线域元素 17 | */ 18 | class ECFieldElementFp { 19 | constructor(q, x) { 20 | this.x = x 21 | this.q = q 22 | // TODO if (x.compareTo(q) >= 0) error 23 | } 24 | 25 | /** 26 | * 判断相等 27 | */ 28 | equals(other) { 29 | if (other === this) return true 30 | return (this.q.equals(other.q) && this.x.equals(other.x)) 31 | } 32 | 33 | /** 34 | * 返回具体数值 35 | */ 36 | toBigInteger() { 37 | return this.x 38 | } 39 | 40 | /** 41 | * 取反 42 | */ 43 | negate() { 44 | return new ECFieldElementFp(this.q, this.x.negate().mod(this.q)) 45 | } 46 | 47 | /** 48 | * 相加 49 | */ 50 | add(b) { 51 | return new ECFieldElementFp(this.q, this.x.add(b.toBigInteger()).mod(this.q)) 52 | } 53 | 54 | /** 55 | * 相减 56 | */ 57 | subtract(b) { 58 | return new ECFieldElementFp(this.q, this.x.subtract(b.toBigInteger()).mod(this.q)) 59 | } 60 | 61 | /** 62 | * 相乘 63 | */ 64 | multiply(b) { 65 | return new ECFieldElementFp(this.q, this.x.multiply(b.toBigInteger()).mod(this.q)) 66 | } 67 | 68 | /** 69 | * 相除 70 | */ 71 | divide(b) { 72 | return new ECFieldElementFp(this.q, this.x.multiply(b.toBigInteger().modInverse(this.q)).mod(this.q)) 73 | } 74 | 75 | /** 76 | * 平方 77 | */ 78 | square() { 79 | return new ECFieldElementFp(this.q, this.x.square().mod(this.q)) 80 | } 81 | } 82 | 83 | class ECPointFp { 84 | constructor(curve, x, y, z) { 85 | this.curve = curve 86 | this.x = x 87 | this.y = y 88 | // 标准射影坐标系:zinv == null 或 z * zinv == 1 89 | this.z = z == null ? BigInteger.ONE : z 90 | this.zinv = null 91 | // TODO: compression flag 92 | } 93 | 94 | getX() { 95 | if (this.zinv === null) this.zinv = this.z.modInverse(this.curve.q) 96 | 97 | return this.curve.fromBigInteger(this.x.toBigInteger().multiply(this.zinv).mod(this.curve.q)) 98 | } 99 | 100 | getY() { 101 | if (this.zinv === null) this.zinv = this.z.modInverse(this.curve.q) 102 | 103 | return this.curve.fromBigInteger(this.y.toBigInteger().multiply(this.zinv).mod(this.curve.q)) 104 | } 105 | 106 | /** 107 | * 判断相等 108 | */ 109 | equals(other) { 110 | if (other === this) return true 111 | if (this.isInfinity()) return other.isInfinity() 112 | if (other.isInfinity()) return this.isInfinity() 113 | 114 | // u = y2 * z1 - y1 * z2 115 | const u = other.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(other.z)).mod(this.curve.q) 116 | if (!u.equals(BigInteger.ZERO)) return false 117 | 118 | // v = x2 * z1 - x1 * z2 119 | const v = other.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(other.z)).mod(this.curve.q) 120 | return v.equals(BigInteger.ZERO) 121 | } 122 | 123 | /** 124 | * 是否是无穷远点 125 | */ 126 | isInfinity() { 127 | if ((this.x === null) && (this.y === null)) return true 128 | return this.z.equals(BigInteger.ZERO) && !this.y.toBigInteger().equals(BigInteger.ZERO) 129 | } 130 | 131 | /** 132 | * 取反,x 轴对称点 133 | */ 134 | negate() { 135 | return new ECPointFp(this.curve, this.x, this.y.negate(), this.z) 136 | } 137 | 138 | /** 139 | * 相加 140 | * 141 | * 标准射影坐标系: 142 | * 143 | * λ1 = x1 * z2 144 | * λ2 = x2 * z1 145 | * λ3 = λ1 − λ2 146 | * λ4 = y1 * z2 147 | * λ5 = y2 * z1 148 | * λ6 = λ4 − λ5 149 | * λ7 = λ1 + λ2 150 | * λ8 = z1 * z2 151 | * λ9 = λ3^2 152 | * λ10 = λ3 * λ9 153 | * λ11 = λ8 * λ6^2 − λ7 * λ9 154 | * x3 = λ3 * λ11 155 | * y3 = λ6 * (λ9 * λ1 − λ11) − λ4 * λ10 156 | * z3 = λ10 * λ8 157 | */ 158 | add(b) { 159 | if (this.isInfinity()) return b 160 | if (b.isInfinity()) return this 161 | 162 | const x1 = this.x.toBigInteger() 163 | const y1 = this.y.toBigInteger() 164 | const z1 = this.z 165 | const x2 = b.x.toBigInteger() 166 | const y2 = b.y.toBigInteger() 167 | const z2 = b.z 168 | const q = this.curve.q 169 | 170 | const w1 = x1.multiply(z2).mod(q) 171 | const w2 = x2.multiply(z1).mod(q) 172 | const w3 = w1.subtract(w2) 173 | const w4 = y1.multiply(z2).mod(q) 174 | const w5 = y2.multiply(z1).mod(q) 175 | const w6 = w4.subtract(w5) 176 | 177 | if (BigInteger.ZERO.equals(w3)) { 178 | if (BigInteger.ZERO.equals(w6)) { 179 | return this.twice() // this == b,计算自加 180 | } 181 | return this.curve.infinity // this == -b,则返回无穷远点 182 | } 183 | 184 | const w7 = w1.add(w2) 185 | const w8 = z1.multiply(z2).mod(q) 186 | const w9 = w3.square().mod(q) 187 | const w10 = w3.multiply(w9).mod(q) 188 | const w11 = w8.multiply(w6.square()).subtract(w7.multiply(w9)).mod(q) 189 | 190 | const x3 = w3.multiply(w11).mod(q) 191 | const y3 = w6.multiply(w9.multiply(w1).subtract(w11)).subtract(w4.multiply(w10)).mod(q) 192 | const z3 = w10.multiply(w8).mod(q) 193 | 194 | return new ECPointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3) 195 | } 196 | 197 | /** 198 | * 自加 199 | * 200 | * 标准射影坐标系: 201 | * 202 | * λ1 = 3 * x1^2 + a * z1^2 203 | * λ2 = 2 * y1 * z1 204 | * λ3 = y1^2 205 | * λ4 = λ3 * x1 * z1 206 | * λ5 = λ2^2 207 | * λ6 = λ1^2 − 8 * λ4 208 | * x3 = λ2 * λ6 209 | * y3 = λ1 * (4 * λ4 − λ6) − 2 * λ5 * λ3 210 | * z3 = λ2 * λ5 211 | */ 212 | twice() { 213 | if (this.isInfinity()) return this 214 | if (!this.y.toBigInteger().signum()) return this.curve.infinity 215 | 216 | const x1 = this.x.toBigInteger() 217 | const y1 = this.y.toBigInteger() 218 | const z1 = this.z 219 | const q = this.curve.q 220 | const a = this.curve.a.toBigInteger() 221 | 222 | const w1 = x1.square().multiply(THREE).add(a.multiply(z1.square())).mod(q) 223 | const w2 = y1.shiftLeft(1).multiply(z1).mod(q) 224 | const w3 = y1.square().mod(q) 225 | const w4 = w3.multiply(x1).multiply(z1).mod(q) 226 | const w5 = w2.square().mod(q) 227 | const w6 = w1.square().subtract(w4.shiftLeft(3)).mod(q) 228 | 229 | const x3 = w2.multiply(w6).mod(q) 230 | const y3 = w1.multiply(w4.shiftLeft(2).subtract(w6)).subtract(w5.shiftLeft(1).multiply(w3)).mod(q) 231 | const z3 = w2.multiply(w5).mod(q) 232 | 233 | return new ECPointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3) 234 | } 235 | 236 | /** 237 | * 倍点计算 238 | */ 239 | multiply(k) { 240 | if (this.isInfinity()) return this 241 | if (!k.signum()) return this.curve.infinity 242 | 243 | // 使用加减法 244 | const k3 = k.multiply(THREE) 245 | const neg = this.negate() 246 | let Q = this 247 | 248 | for (let i = k3.bitLength() - 2; i > 0; i--) { 249 | Q = Q.twice() 250 | 251 | const k3Bit = k3.testBit(i) 252 | const kBit = k.testBit(i) 253 | 254 | if (k3Bit !== kBit) { 255 | Q = Q.add(k3Bit ? this : neg) 256 | } 257 | } 258 | 259 | return Q 260 | } 261 | } 262 | 263 | /** 264 | * 椭圆曲线 y^2 = x^3 + ax + b 265 | */ 266 | class ECCurveFp { 267 | constructor(q, a, b) { 268 | this.q = q 269 | this.a = this.fromBigInteger(a) 270 | this.b = this.fromBigInteger(b) 271 | this.infinity = new ECPointFp(this, null, null) // 无穷远点 272 | } 273 | 274 | /** 275 | * 判断两个椭圆曲线是否相等 276 | */ 277 | equals(other) { 278 | if (other === this) return true 279 | return (this.q.equals(other.q) && this.a.equals(other.a) && this.b.equals(other.b)) 280 | } 281 | 282 | /** 283 | * 生成椭圆曲线域元素 284 | */ 285 | fromBigInteger(x) { 286 | return new ECFieldElementFp(this.q, x) 287 | } 288 | 289 | /** 290 | * 解析 16 进制串为椭圆曲线点 291 | */ 292 | decodePointHex(s) { 293 | switch (parseInt(s.substr(0, 2), 16)) { 294 | // 第一个字节 295 | case 0: 296 | return this.infinity 297 | case 2: 298 | case 3: 299 | // 压缩 300 | const x = this.fromBigInteger(new BigInteger(s.substr(2), 16)) 301 | // 对 p ≡ 3 (mod4),即存在正整数 u,使得 p = 4u + 3 302 | // 计算 y = (√ (x^3 + ax + b) % p)^(u + 1) modp 303 | let y = this.fromBigInteger(x.multiply(x.square()).add( 304 | x.multiply(this.a) 305 | ).add(this.b).toBigInteger() 306 | .modPow( 307 | this.q.divide(new BigInteger('4')).add(BigInteger.ONE), this.q 308 | )) 309 | // 算出结果 2 进制最后 1 位不等于第 1 个字节减 2 则取反 310 | if (!y.toBigInteger().mod(TWO).equals(new BigInteger(s.substr(0, 2), 16).subtract(TWO))) { 311 | y = y.negate() 312 | } 313 | return new ECPointFp(this, x, y) 314 | case 4: 315 | case 6: 316 | case 7: 317 | const len = (s.length - 2) / 2 318 | const xHex = s.substr(2, len) 319 | const yHex = s.substr(len + 2, len) 320 | 321 | return new ECPointFp(this, this.fromBigInteger(new BigInteger(xHex, 16)), this.fromBigInteger(new BigInteger(yHex, 16))) 322 | default: 323 | // 不支持 324 | return null 325 | } 326 | } 327 | } 328 | 329 | module.exports = { 330 | ECPointFp, 331 | ECCurveFp, 332 | } 333 | -------------------------------------------------------------------------------- /src/sm2/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | const {BigInteger} = require('jsbn') 3 | const {encodeDer, decodeDer} = require('./asn1') 4 | const _ = require('./utils') 5 | const sm3 = require('./sm3').sm3 6 | 7 | const {G, curve, n} = _.generateEcparam() 8 | const C1C2C3 = 0 9 | 10 | /** 11 | * 加密 12 | */ 13 | function doEncrypt(msg, publicKey, cipherMode = 1) { 14 | msg = typeof msg === 'string' ? _.hexToArray(_.utf8ToHex(msg)) : Array.prototype.slice.call(msg) 15 | publicKey = _.getGlobalCurve().decodePointHex(publicKey) // 先将公钥转成点 16 | 17 | const keypair = _.generateKeyPairHex() 18 | const k = new BigInteger(keypair.privateKey, 16) // 随机数 k 19 | 20 | // c1 = k * G 21 | let c1 = keypair.publicKey 22 | if (c1.length > 128) c1 = c1.substr(c1.length - 128) 23 | 24 | // (x2, y2) = k * publicKey 25 | const p = publicKey.multiply(k) 26 | const x2 = _.hexToArray(_.leftPad(p.getX().toBigInteger().toRadix(16), 64)) 27 | const y2 = _.hexToArray(_.leftPad(p.getY().toBigInteger().toRadix(16), 64)) 28 | 29 | // c3 = hash(x2 || msg || y2) 30 | const c3 = _.arrayToHex(sm3([].concat(x2, msg, y2))) 31 | 32 | let ct = 1 33 | let offset = 0 34 | let t = [] // 256 位 35 | const z = [].concat(x2, y2) 36 | const nextT = () => { 37 | // (1) Hai = hash(z || ct) 38 | // (2) ct++ 39 | t = sm3([...z, ct >> 24 & 0x00ff, ct >> 16 & 0x00ff, ct >> 8 & 0x00ff, ct & 0x00ff]) 40 | ct++ 41 | offset = 0 42 | } 43 | nextT() // 先生成 Ha1 44 | 45 | for (let i = 0, len = msg.length; i < len; i++) { 46 | // t = Ha1 || Ha2 || Ha3 || Ha4 47 | if (offset === t.length) nextT() 48 | 49 | // c2 = msg ^ t 50 | msg[i] ^= t[offset++] & 0xff 51 | } 52 | const c2 = _.arrayToHex(msg) 53 | 54 | return cipherMode === C1C2C3 ? c1 + c2 + c3 : c1 + c3 + c2 55 | } 56 | 57 | /** 58 | * 解密 59 | */ 60 | function doDecrypt(encryptData, privateKey, cipherMode = 1, { 61 | output = 'string', 62 | } = {}) { 63 | privateKey = new BigInteger(privateKey, 16) 64 | 65 | let c3 = encryptData.substr(128, 64) 66 | let c2 = encryptData.substr(128 + 64) 67 | 68 | if (cipherMode === C1C2C3) { 69 | c3 = encryptData.substr(encryptData.length - 64) 70 | c2 = encryptData.substr(128, encryptData.length - 128 - 64) 71 | } 72 | 73 | const msg = _.hexToArray(c2) 74 | const c1 = _.getGlobalCurve().decodePointHex('04' + encryptData.substr(0, 128)) 75 | 76 | const p = c1.multiply(privateKey) 77 | const x2 = _.hexToArray(_.leftPad(p.getX().toBigInteger().toRadix(16), 64)) 78 | const y2 = _.hexToArray(_.leftPad(p.getY().toBigInteger().toRadix(16), 64)) 79 | 80 | let ct = 1 81 | let offset = 0 82 | let t = [] // 256 位 83 | const z = [].concat(x2, y2) 84 | const nextT = () => { 85 | // (1) Hai = hash(z || ct) 86 | // (2) ct++ 87 | t = sm3([...z, ct >> 24 & 0x00ff, ct >> 16 & 0x00ff, ct >> 8 & 0x00ff, ct & 0x00ff]) 88 | ct++ 89 | offset = 0 90 | } 91 | nextT() // 先生成 Ha1 92 | 93 | for (let i = 0, len = msg.length; i < len; i++) { 94 | // t = Ha1 || Ha2 || Ha3 || Ha4 95 | if (offset === t.length) nextT() 96 | 97 | // c2 = msg ^ t 98 | msg[i] ^= t[offset++] & 0xff 99 | } 100 | 101 | // c3 = hash(x2 || msg || y2) 102 | const checkC3 = _.arrayToHex(sm3([].concat(x2, msg, y2))) 103 | 104 | if (checkC3 === c3.toLowerCase()) { 105 | return output === 'array' ? msg : _.arrayToUtf8(msg) 106 | } else { 107 | return output === 'array' ? [] : '' 108 | } 109 | } 110 | 111 | /** 112 | * 签名 113 | */ 114 | function doSignature(msg, privateKey, { 115 | pointPool, der, hash, publicKey, userId 116 | } = {}) { 117 | let hashHex = typeof msg === 'string' ? _.utf8ToHex(msg) : _.arrayToHex(msg) 118 | 119 | if (hash) { 120 | // sm3杂凑 121 | publicKey = publicKey || getPublicKeyFromPrivateKey(privateKey) 122 | hashHex = getHash(hashHex, publicKey, userId) 123 | } 124 | 125 | const dA = new BigInteger(privateKey, 16) 126 | const e = new BigInteger(hashHex, 16) 127 | 128 | // k 129 | let k = null 130 | let r = null 131 | let s = null 132 | 133 | do { 134 | do { 135 | let point 136 | if (pointPool && pointPool.length) { 137 | point = pointPool.pop() 138 | } else { 139 | point = getPoint() 140 | } 141 | k = point.k 142 | 143 | // r = (e + x1) mod n 144 | r = e.add(point.x1).mod(n) 145 | } while (r.equals(BigInteger.ZERO) || r.add(k).equals(n)) 146 | 147 | // s = ((1 + dA)^-1 * (k - r * dA)) mod n 148 | s = dA.add(BigInteger.ONE).modInverse(n).multiply(k.subtract(r.multiply(dA))).mod(n) 149 | } while (s.equals(BigInteger.ZERO)) 150 | 151 | if (der) return encodeDer(r, s) // asn.1 der 编码 152 | 153 | return _.leftPad(r.toString(16), 64) + _.leftPad(s.toString(16), 64) 154 | } 155 | 156 | /** 157 | * 验签 158 | */ 159 | function doVerifySignature(msg, signHex, publicKey, {der, hash, userId} = {}) { 160 | let hashHex = typeof msg === 'string' ? _.utf8ToHex(msg) : _.arrayToHex(msg) 161 | 162 | if (hash) { 163 | // sm3杂凑 164 | hashHex = getHash(hashHex, publicKey, userId) 165 | } 166 | 167 | let r; let 168 | s 169 | if (der) { 170 | const decodeDerObj = decodeDer(signHex) // asn.1 der 解码 171 | r = decodeDerObj.r 172 | s = decodeDerObj.s 173 | } else { 174 | r = new BigInteger(signHex.substring(0, 64), 16) 175 | s = new BigInteger(signHex.substring(64), 16) 176 | } 177 | 178 | const PA = curve.decodePointHex(publicKey) 179 | const e = new BigInteger(hashHex, 16) 180 | 181 | // t = (r + s) mod n 182 | const t = r.add(s).mod(n) 183 | 184 | if (t.equals(BigInteger.ZERO)) return false 185 | 186 | // x1y1 = s * G + t * PA 187 | const x1y1 = G.multiply(s).add(PA.multiply(t)) 188 | 189 | // R = (e + x1) mod n 190 | const R = e.add(x1y1.getX().toBigInteger()).mod(n) 191 | 192 | return r.equals(R) 193 | } 194 | 195 | /** 196 | * sm3杂凑算法 197 | */ 198 | function getHash(hashHex, publicKey, userId = '1234567812345678') { 199 | // z = hash(entl || userId || a || b || gx || gy || px || py) 200 | userId = _.utf8ToHex(userId) 201 | const a = _.leftPad(G.curve.a.toBigInteger().toRadix(16), 64) 202 | const b = _.leftPad(G.curve.b.toBigInteger().toRadix(16), 64) 203 | const gx = _.leftPad(G.getX().toBigInteger().toRadix(16), 64) 204 | const gy = _.leftPad(G.getY().toBigInteger().toRadix(16), 64) 205 | let px 206 | let py 207 | if (publicKey.length === 128) { 208 | px = publicKey.substr(0, 64) 209 | py = publicKey.substr(64, 64) 210 | } else { 211 | const point = G.curve.decodePointHex(publicKey) 212 | px = _.leftPad(point.getX().toBigInteger().toRadix(16), 64) 213 | py = _.leftPad(point.getY().toBigInteger().toRadix(16), 64) 214 | } 215 | const data = _.hexToArray(userId + a + b + gx + gy + px + py) 216 | 217 | const entl = userId.length * 4 218 | data.unshift(entl & 0x00ff) 219 | data.unshift(entl >> 8 & 0x00ff) 220 | 221 | const z = sm3(data) 222 | 223 | // e = hash(z || msg) 224 | return _.arrayToHex(sm3(z.concat(_.hexToArray(hashHex)))) 225 | } 226 | 227 | /** 228 | * 计算公钥 229 | */ 230 | function getPublicKeyFromPrivateKey(privateKey) { 231 | const PA = G.multiply(new BigInteger(privateKey, 16)) 232 | const x = _.leftPad(PA.getX().toBigInteger().toString(16), 64) 233 | const y = _.leftPad(PA.getY().toBigInteger().toString(16), 64) 234 | return '04' + x + y 235 | } 236 | 237 | /** 238 | * 获取椭圆曲线点 239 | */ 240 | function getPoint() { 241 | const keypair = _.generateKeyPairHex() 242 | const PA = curve.decodePointHex(keypair.publicKey) 243 | 244 | keypair.k = new BigInteger(keypair.privateKey, 16) 245 | keypair.x1 = PA.getX().toBigInteger() 246 | 247 | return keypair 248 | } 249 | 250 | module.exports = { 251 | generateKeyPairHex: _.generateKeyPairHex, 252 | compressPublicKeyHex: _.compressPublicKeyHex, 253 | comparePublicKeyHex: _.comparePublicKeyHex, 254 | doEncrypt, 255 | doDecrypt, 256 | doSignature, 257 | doVerifySignature, 258 | getPublicKeyFromPrivateKey, 259 | getPoint, 260 | verifyPublicKey: _.verifyPublicKey, 261 | } 262 | -------------------------------------------------------------------------------- /src/sm2/sm3.js: -------------------------------------------------------------------------------- 1 | // 消息扩展 2 | const W = new Uint32Array(68) 3 | const M = new Uint32Array(64) // W' 4 | 5 | /** 6 | * 循环左移 7 | */ 8 | function rotl(x, n) { 9 | const s = n & 31 10 | return (x << s) | (x >>> (32 - s)) 11 | } 12 | 13 | /** 14 | * 二进制异或运算 15 | */ 16 | function xor(x, y) { 17 | const result = [] 18 | for (let i = x.length - 1; i >= 0; i--) result[i] = (x[i] ^ y[i]) & 0xff 19 | return result 20 | } 21 | 22 | /** 23 | * 压缩函数中的置换函数 P0(X) = X xor (X <<< 9) xor (X <<< 17) 24 | */ 25 | function P0(X) { 26 | return (X ^ rotl(X, 9)) ^ rotl(X, 17) 27 | } 28 | 29 | /** 30 | * 消息扩展中的置换函数 P1(X) = X xor (X <<< 15) xor (X <<< 23) 31 | */ 32 | function P1(X) { 33 | return (X ^ rotl(X, 15)) ^ rotl(X, 23) 34 | } 35 | 36 | /** 37 | * sm3 本体 38 | */ 39 | function sm3(array) { 40 | let len = array.length * 8 41 | 42 | // k 是满足 len + 1 + k = 448mod512 的最小的非负整数 43 | let k = len % 512 44 | // 如果 448 <= (512 % len) < 512,需要多补充 (len % 448) 比特'0'以满足总比特长度为512的倍数 45 | k = k >= 448 ? 512 - (k % 448) - 1 : 448 - k - 1 46 | 47 | // 填充 48 | const kArr = new Array((k - 7) / 8) 49 | const lenArr = new Array(8) 50 | for (let i = 0, len = kArr.length; i < len; i++) kArr[i] = 0 51 | for (let i = 0, len = lenArr.length; i < len; i++) lenArr[i] = 0 52 | len = len.toString(2) 53 | for (let i = 7; i >= 0; i--) { 54 | if (len.length > 8) { 55 | const start = len.length - 8 56 | lenArr[i] = parseInt(len.substr(start), 2) 57 | len = len.substr(0, start) 58 | } else if (len.length > 0) { 59 | lenArr[i] = parseInt(len, 2) 60 | len = '' 61 | } 62 | } 63 | const m = new Uint8Array([...array, 0x80, ...kArr, ...lenArr]) 64 | const dataView = new DataView(m.buffer, 0) 65 | 66 | // 迭代压缩 67 | const n = m.length / 64 68 | const V = new Uint32Array([0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e]) 69 | for (let i = 0; i < n; i++) { 70 | W.fill(0) 71 | M.fill(0) 72 | 73 | // 将消息分组B划分为 16 个字 W0, W1,……,W15 74 | const start = 16 * i 75 | for (let j = 0; j < 16; j++) { 76 | W[j] = dataView.getUint32((start + j) * 4, false) 77 | } 78 | 79 | // W16 ~ W67:W[j] <- P1(W[j−16] xor W[j−9] xor (W[j−3] <<< 15)) xor (W[j−13] <<< 7) xor W[j−6] 80 | for (let j = 16; j < 68; j++) { 81 | W[j] = (P1((W[j - 16] ^ W[j - 9]) ^ rotl(W[j - 3], 15)) ^ rotl(W[j - 13], 7)) ^ W[j - 6] 82 | } 83 | 84 | // W′0 ~ W′63:W′[j] = W[j] xor W[j+4] 85 | for (let j = 0; j < 64; j++) { 86 | M[j] = W[j] ^ W[j + 4] 87 | } 88 | 89 | // 压缩 90 | const T1 = 0x79cc4519 91 | const T2 = 0x7a879d8a 92 | // 字寄存器 93 | let A = V[0] 94 | let B = V[1] 95 | let C = V[2] 96 | let D = V[3] 97 | let E = V[4] 98 | let F = V[5] 99 | let G = V[6] 100 | let H = V[7] 101 | // 中间变量 102 | let SS1 103 | let SS2 104 | let TT1 105 | let TT2 106 | let T 107 | for (let j = 0; j < 64; j++) { 108 | T = j >= 0 && j <= 15 ? T1 : T2 109 | SS1 = rotl(rotl(A, 12) + E + rotl(T, j), 7) 110 | SS2 = SS1 ^ rotl(A, 12) 111 | 112 | TT1 = (j >= 0 && j <= 15 ? ((A ^ B) ^ C) : (((A & B) | (A & C)) | (B & C))) + D + SS2 + M[j] 113 | TT2 = (j >= 0 && j <= 15 ? ((E ^ F) ^ G) : ((E & F) | ((~E) & G))) + H + SS1 + W[j] 114 | 115 | D = C 116 | C = rotl(B, 9) 117 | B = A 118 | A = TT1 119 | H = G 120 | G = rotl(F, 19) 121 | F = E 122 | E = P0(TT2) 123 | } 124 | 125 | V[0] ^= A 126 | V[1] ^= B 127 | V[2] ^= C 128 | V[3] ^= D 129 | V[4] ^= E 130 | V[5] ^= F 131 | V[6] ^= G 132 | V[7] ^= H 133 | } 134 | 135 | // 转回 uint8 136 | const result = [] 137 | for (let i = 0, len = V.length; i < len; i++) { 138 | const word = V[i] 139 | result.push((word & 0xff000000) >>> 24, (word & 0xff0000) >>> 16, (word & 0xff00) >>> 8, word & 0xff) 140 | } 141 | 142 | return result 143 | } 144 | 145 | /** 146 | * hmac 实现 147 | */ 148 | const blockLen = 64 149 | const iPad = new Uint8Array(blockLen) 150 | const oPad = new Uint8Array(blockLen) 151 | for (let i = 0; i < blockLen; i++) { 152 | iPad[i] = 0x36 153 | oPad[i] = 0x5c 154 | } 155 | function hmac(input, key) { 156 | // 密钥填充 157 | if (key.length > blockLen) key = sm3(key) 158 | while (key.length < blockLen) key.push(0) 159 | 160 | const iPadKey = xor(key, iPad) 161 | const oPadKey = xor(key, oPad) 162 | 163 | const hash = sm3([...iPadKey, ...input]) 164 | return sm3([...oPadKey, ...hash]) 165 | } 166 | 167 | module.exports = { 168 | sm3, 169 | hmac, 170 | } 171 | -------------------------------------------------------------------------------- /src/sm2/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise, no-mixed-operators, no-use-before-define, max-len */ 2 | const {BigInteger, SecureRandom} = require('jsbn') 3 | const {ECCurveFp} = require('./ec') 4 | 5 | const rng = new SecureRandom() 6 | const {curve, G, n} = generateEcparam() 7 | 8 | /** 9 | * 获取公共椭圆曲线 10 | */ 11 | function getGlobalCurve() { 12 | return curve 13 | } 14 | 15 | /** 16 | * 生成ecparam 17 | */ 18 | function generateEcparam() { 19 | // 椭圆曲线 20 | const p = new BigInteger('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF', 16) 21 | const a = new BigInteger('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC', 16) 22 | const b = new BigInteger('28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93', 16) 23 | const curve = new ECCurveFp(p, a, b) 24 | 25 | // 基点 26 | const gxHex = '32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7' 27 | const gyHex = 'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0' 28 | const G = curve.decodePointHex('04' + gxHex + gyHex) 29 | 30 | const n = new BigInteger('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123', 16) 31 | 32 | return {curve, G, n} 33 | } 34 | 35 | /** 36 | * 生成密钥对:publicKey = privateKey * G 37 | */ 38 | function generateKeyPairHex(a, b, c) { 39 | const random = a ? new BigInteger(a, b, c) : new BigInteger(n.bitLength(), rng) 40 | const d = random.mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE) // 随机数 41 | const privateKey = leftPad(d.toString(16), 64) 42 | 43 | const P = G.multiply(d) // P = dG,p 为公钥,d 为私钥 44 | const Px = leftPad(P.getX().toBigInteger().toString(16), 64) 45 | const Py = leftPad(P.getY().toBigInteger().toString(16), 64) 46 | const publicKey = '04' + Px + Py 47 | 48 | return {privateKey, publicKey} 49 | } 50 | 51 | /** 52 | * 生成压缩公钥 53 | */ 54 | function compressPublicKeyHex(s) { 55 | if (s.length !== 130) throw new Error('Invalid public key to compress') 56 | 57 | const len = (s.length - 2) / 2 58 | const xHex = s.substr(2, len) 59 | const y = new BigInteger(s.substr(len + 2, len), 16) 60 | 61 | let prefix = '03' 62 | if (y.mod(new BigInteger('2')).equals(BigInteger.ZERO)) prefix = '02' 63 | 64 | return prefix + xHex 65 | } 66 | 67 | /** 68 | * utf8串转16进制串 69 | */ 70 | function utf8ToHex(input) { 71 | input = unescape(encodeURIComponent(input)) 72 | 73 | const length = input.length 74 | 75 | // 转换到字数组 76 | const words = [] 77 | for (let i = 0; i < length; i++) { 78 | words[i >>> 2] |= (input.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8) 79 | } 80 | 81 | // 转换到16进制 82 | const hexChars = [] 83 | for (let i = 0; i < length; i++) { 84 | const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff 85 | hexChars.push((bite >>> 4).toString(16)) 86 | hexChars.push((bite & 0x0f).toString(16)) 87 | } 88 | 89 | return hexChars.join('') 90 | } 91 | 92 | /** 93 | * 补全16进制字符串 94 | */ 95 | function leftPad(input, num) { 96 | if (input.length >= num) return input 97 | 98 | return (new Array(num - input.length + 1)).join('0') + input 99 | } 100 | 101 | /** 102 | * 转成16进制串 103 | */ 104 | function arrayToHex(arr) { 105 | return arr.map(item => { 106 | item = item.toString(16) 107 | return item.length === 1 ? '0' + item : item 108 | }).join('') 109 | } 110 | 111 | /** 112 | * 转成utf8串 113 | */ 114 | function arrayToUtf8(arr) { 115 | const words = [] 116 | let j = 0 117 | for (let i = 0; i < arr.length * 2; i += 2) { 118 | words[i >>> 3] |= parseInt(arr[j], 10) << (24 - (i % 8) * 4) 119 | j++ 120 | } 121 | 122 | try { 123 | const latin1Chars = [] 124 | 125 | for (let i = 0; i < arr.length; i++) { 126 | const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff 127 | latin1Chars.push(String.fromCharCode(bite)) 128 | } 129 | 130 | return decodeURIComponent(escape(latin1Chars.join(''))) 131 | } catch (e) { 132 | throw new Error('Malformed UTF-8 data') 133 | } 134 | } 135 | 136 | /** 137 | * 转成字节数组 138 | */ 139 | function hexToArray(hexStr) { 140 | const words = [] 141 | let hexStrLength = hexStr.length 142 | 143 | if (hexStrLength % 2 !== 0) { 144 | hexStr = leftPad(hexStr, hexStrLength + 1) 145 | } 146 | 147 | hexStrLength = hexStr.length 148 | 149 | for (let i = 0; i < hexStrLength; i += 2) { 150 | words.push(parseInt(hexStr.substr(i, 2), 16)) 151 | } 152 | return words 153 | } 154 | 155 | /** 156 | * 验证公钥是否为椭圆曲线上的点 157 | */ 158 | function verifyPublicKey(publicKey) { 159 | const point = curve.decodePointHex(publicKey) 160 | if (!point) return false 161 | 162 | const x = point.getX() 163 | const y = point.getY() 164 | 165 | // 验证 y^2 是否等于 x^3 + ax + b 166 | return y.square().equals(x.multiply(x.square()).add(x.multiply(curve.a)).add(curve.b)) 167 | } 168 | 169 | /** 170 | * 验证公钥是否等价,等价返回true 171 | */ 172 | function comparePublicKeyHex(publicKey1, publicKey2) { 173 | const point1 = curve.decodePointHex(publicKey1) 174 | if (!point1) return false 175 | 176 | const point2 = curve.decodePointHex(publicKey2) 177 | if (!point2) return false 178 | 179 | return point1.equals(point2) 180 | } 181 | 182 | module.exports = { 183 | getGlobalCurve, 184 | generateEcparam, 185 | generateKeyPairHex, 186 | compressPublicKeyHex, 187 | utf8ToHex, 188 | leftPad, 189 | arrayToHex, 190 | arrayToUtf8, 191 | hexToArray, 192 | verifyPublicKey, 193 | comparePublicKeyHex, 194 | } 195 | -------------------------------------------------------------------------------- /src/sm3/index.js: -------------------------------------------------------------------------------- 1 | const {sm3, hmac} = require('../sm2/sm3') 2 | 3 | /** 4 | * 补全16进制字符串 5 | */ 6 | function leftPad(input, num) { 7 | if (input.length >= num) return input 8 | 9 | return (new Array(num - input.length + 1)).join('0') + input 10 | } 11 | 12 | /** 13 | * 字节数组转 16 进制串 14 | */ 15 | function ArrayToHex(arr) { 16 | return arr.map(item => { 17 | item = item.toString(16) 18 | return item.length === 1 ? '0' + item : item 19 | }).join('') 20 | } 21 | 22 | /** 23 | * 转成字节数组 24 | */ 25 | function hexToArray(hexStr) { 26 | const words = [] 27 | let hexStrLength = hexStr.length 28 | 29 | if (hexStrLength % 2 !== 0) { 30 | hexStr = leftPad(hexStr, hexStrLength + 1) 31 | } 32 | 33 | hexStrLength = hexStr.length 34 | 35 | for (let i = 0; i < hexStrLength; i += 2) { 36 | words.push(parseInt(hexStr.substr(i, 2), 16)) 37 | } 38 | return words 39 | } 40 | 41 | /** 42 | * utf8 串转字节数组 43 | */ 44 | function utf8ToArray(str) { 45 | const arr = [] 46 | 47 | for (let i = 0, len = str.length; i < len; i++) { 48 | const point = str.codePointAt(i) 49 | 50 | if (point <= 0x007f) { 51 | // 单字节,标量值:00000000 00000000 0zzzzzzz 52 | arr.push(point) 53 | } else if (point <= 0x07ff) { 54 | // 双字节,标量值:00000000 00000yyy yyzzzzzz 55 | arr.push(0xc0 | (point >>> 6)) // 110yyyyy(0xc0-0xdf) 56 | arr.push(0x80 | (point & 0x3f)) // 10zzzzzz(0x80-0xbf) 57 | } else if (point <= 0xD7FF || (point >= 0xE000 && point <= 0xFFFF)) { 58 | // 三字节:标量值:00000000 xxxxyyyy yyzzzzzz 59 | arr.push(0xe0 | (point >>> 12)) // 1110xxxx(0xe0-0xef) 60 | arr.push(0x80 | ((point >>> 6) & 0x3f)) // 10yyyyyy(0x80-0xbf) 61 | arr.push(0x80 | (point & 0x3f)) // 10zzzzzz(0x80-0xbf) 62 | } else if (point >= 0x010000 && point <= 0x10FFFF) { 63 | // 四字节:标量值:000wwwxx xxxxyyyy yyzzzzzz 64 | i++ 65 | arr.push((0xf0 | (point >>> 18) & 0x1c)) // 11110www(0xf0-0xf7) 66 | arr.push((0x80 | ((point >>> 12) & 0x3f))) // 10xxxxxx(0x80-0xbf) 67 | arr.push((0x80 | ((point >>> 6) & 0x3f))) // 10yyyyyy(0x80-0xbf) 68 | arr.push((0x80 | (point & 0x3f))) // 10zzzzzz(0x80-0xbf) 69 | } else { 70 | // 五、六字节,暂时不支持 71 | arr.push(point) 72 | throw new Error('input is not supported') 73 | } 74 | } 75 | 76 | return arr 77 | } 78 | 79 | module.exports = function (input, options) { 80 | input = typeof input === 'string' ? utf8ToArray(input) : Array.prototype.slice.call(input) 81 | 82 | if (options) { 83 | const mode = options.mode || 'hmac' 84 | if (mode !== 'hmac') throw new Error('invalid mode') 85 | 86 | let key = options.key 87 | if (!key) throw new Error('invalid key') 88 | 89 | key = typeof key === 'string' ? hexToArray(key) : Array.prototype.slice.call(key) 90 | return ArrayToHex(hmac(input, key)) 91 | } 92 | 93 | return ArrayToHex(sm3(input)) 94 | } 95 | -------------------------------------------------------------------------------- /src/sm4/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise, no-mixed-operators, complexity */ 2 | const DECRYPT = 0 3 | const ROUND = 32 4 | const BLOCK = 16 5 | 6 | const Sbox = [ 7 | 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05, 8 | 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, 9 | 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62, 10 | 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6, 11 | 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8, 12 | 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35, 13 | 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, 14 | 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, 15 | 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, 16 | 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, 17 | 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f, 18 | 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, 19 | 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, 20 | 0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, 21 | 0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84, 22 | 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48 23 | ] 24 | 25 | const CK = [ 26 | 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 27 | 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 28 | 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 29 | 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 30 | 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 31 | 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 32 | 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 33 | 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 34 | ] 35 | 36 | /** 37 | * 16 进制串转字节数组 38 | */ 39 | function hexToArray(str) { 40 | const arr = [] 41 | for (let i = 0, len = str.length; i < len; i += 2) { 42 | arr.push(parseInt(str.substr(i, 2), 16)) 43 | } 44 | return arr 45 | } 46 | 47 | /** 48 | * 字节数组转 16 进制串 49 | */ 50 | function ArrayToHex(arr) { 51 | return arr.map(item => { 52 | item = item.toString(16) 53 | return item.length === 1 ? '0' + item : item 54 | }).join('') 55 | } 56 | 57 | /** 58 | * utf8 串转字节数组 59 | */ 60 | function utf8ToArray(str) { 61 | const arr = [] 62 | 63 | for (let i = 0, len = str.length; i < len; i++) { 64 | const point = str.codePointAt(i) 65 | 66 | if (point <= 0x007f) { 67 | // 单字节,标量值:00000000 00000000 0zzzzzzz 68 | arr.push(point) 69 | } else if (point <= 0x07ff) { 70 | // 双字节,标量值:00000000 00000yyy yyzzzzzz 71 | arr.push(0xc0 | (point >>> 6)) // 110yyyyy(0xc0-0xdf) 72 | arr.push(0x80 | (point & 0x3f)) // 10zzzzzz(0x80-0xbf) 73 | } else if (point <= 0xD7FF || (point >= 0xE000 && point <= 0xFFFF)) { 74 | // 三字节:标量值:00000000 xxxxyyyy yyzzzzzz 75 | arr.push(0xe0 | (point >>> 12)) // 1110xxxx(0xe0-0xef) 76 | arr.push(0x80 | ((point >>> 6) & 0x3f)) // 10yyyyyy(0x80-0xbf) 77 | arr.push(0x80 | (point & 0x3f)) // 10zzzzzz(0x80-0xbf) 78 | } else if (point >= 0x010000 && point <= 0x10FFFF) { 79 | // 四字节:标量值:000wwwxx xxxxyyyy yyzzzzzz 80 | i++ 81 | arr.push((0xf0 | (point >>> 18) & 0x1c)) // 11110www(0xf0-0xf7) 82 | arr.push((0x80 | ((point >>> 12) & 0x3f))) // 10xxxxxx(0x80-0xbf) 83 | arr.push((0x80 | ((point >>> 6) & 0x3f))) // 10yyyyyy(0x80-0xbf) 84 | arr.push((0x80 | (point & 0x3f))) // 10zzzzzz(0x80-0xbf) 85 | } else { 86 | // 五、六字节,暂时不支持 87 | arr.push(point) 88 | throw new Error('input is not supported') 89 | } 90 | } 91 | 92 | return arr 93 | } 94 | 95 | /** 96 | * 字节数组转 utf8 串 97 | */ 98 | function arrayToUtf8(arr) { 99 | const str = [] 100 | for (let i = 0, len = arr.length; i < len; i++) { 101 | if (arr[i] >= 0xf0 && arr[i] <= 0xf7) { 102 | // 四字节 103 | str.push(String.fromCodePoint(((arr[i] & 0x07) << 18) + ((arr[i + 1] & 0x3f) << 12) + ((arr[i + 2] & 0x3f) << 6) + (arr[i + 3] & 0x3f))) 104 | i += 3 105 | } else if (arr[i] >= 0xe0 && arr[i] <= 0xef) { 106 | // 三字节 107 | str.push(String.fromCodePoint(((arr[i] & 0x0f) << 12) + ((arr[i + 1] & 0x3f) << 6) + (arr[i + 2] & 0x3f))) 108 | i += 2 109 | } else if (arr[i] >= 0xc0 && arr[i] <= 0xdf) { 110 | // 双字节 111 | str.push(String.fromCodePoint(((arr[i] & 0x1f) << 6) + (arr[i + 1] & 0x3f))) 112 | i++ 113 | } else { 114 | // 单字节 115 | str.push(String.fromCodePoint(arr[i])) 116 | } 117 | } 118 | 119 | return str.join('') 120 | } 121 | 122 | /** 123 | * 32 比特循环左移 124 | */ 125 | function rotl(x, n) { 126 | const s = n & 31 127 | return (x << s) | (x >>> (32 - s)) 128 | } 129 | 130 | /** 131 | * 非线性变换 132 | */ 133 | function byteSub(a) { 134 | return (Sbox[a >>> 24 & 0xFF] & 0xFF) << 24 | 135 | (Sbox[a >>> 16 & 0xFF] & 0xFF) << 16 | 136 | (Sbox[a >>> 8 & 0xFF] & 0xFF) << 8 | 137 | (Sbox[a & 0xFF] & 0xFF) 138 | } 139 | 140 | /** 141 | * 线性变换,加密/解密用 142 | */ 143 | function l1(b) { 144 | return b ^ rotl(b, 2) ^ rotl(b, 10) ^ rotl(b, 18) ^ rotl(b, 24) 145 | } 146 | 147 | /** 148 | * 线性变换,生成轮密钥用 149 | */ 150 | function l2(b) { 151 | return b ^ rotl(b, 13) ^ rotl(b, 23) 152 | } 153 | 154 | /** 155 | * 以一组 128 比特进行加密/解密操作 156 | */ 157 | function sms4Crypt(input, output, roundKey) { 158 | const x = new Array(4) 159 | 160 | // 字节数组转成字数组(此处 1 字 = 32 比特) 161 | const tmp = new Array(4) 162 | for (let i = 0; i < 4; i++) { 163 | tmp[0] = input[4 * i] & 0xff 164 | tmp[1] = input[4 * i + 1] & 0xff 165 | tmp[2] = input[4 * i + 2] & 0xff 166 | tmp[3] = input[4 * i + 3] & 0xff 167 | x[i] = tmp[0] << 24 | tmp[1] << 16 | tmp[2] << 8 | tmp[3] 168 | } 169 | 170 | // x[i + 4] = x[i] ^ l1(byteSub(x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ roundKey[i])) 171 | for (let r = 0, mid; r < 32; r += 4) { 172 | mid = x[1] ^ x[2] ^ x[3] ^ roundKey[r + 0] 173 | x[0] ^= l1(byteSub(mid)) // x[4] 174 | 175 | mid = x[2] ^ x[3] ^ x[0] ^ roundKey[r + 1] 176 | x[1] ^= l1(byteSub(mid)) // x[5] 177 | 178 | mid = x[3] ^ x[0] ^ x[1] ^ roundKey[r + 2] 179 | x[2] ^= l1(byteSub(mid)) // x[6] 180 | 181 | mid = x[0] ^ x[1] ^ x[2] ^ roundKey[r + 3] 182 | x[3] ^= l1(byteSub(mid)) // x[7] 183 | } 184 | 185 | // 反序变换 186 | for (let j = 0; j < 16; j += 4) { 187 | output[j] = x[3 - j / 4] >>> 24 & 0xff 188 | output[j + 1] = x[3 - j / 4] >>> 16 & 0xff 189 | output[j + 2] = x[3 - j / 4] >>> 8 & 0xff 190 | output[j + 3] = x[3 - j / 4] & 0xff 191 | } 192 | } 193 | 194 | /** 195 | * 密钥扩展算法 196 | */ 197 | function sms4KeyExt(key, roundKey, cryptFlag) { 198 | const x = new Array(4) 199 | 200 | // 字节数组转成字数组(此处 1 字 = 32 比特) 201 | const tmp = new Array(4) 202 | for (let i = 0; i < 4; i++) { 203 | tmp[0] = key[0 + 4 * i] & 0xff 204 | tmp[1] = key[1 + 4 * i] & 0xff 205 | tmp[2] = key[2 + 4 * i] & 0xff 206 | tmp[3] = key[3 + 4 * i] & 0xff 207 | x[i] = tmp[0] << 24 | tmp[1] << 16 | tmp[2] << 8 | tmp[3] 208 | } 209 | 210 | // 与系统参数做异或 211 | x[0] ^= 0xa3b1bac6 212 | x[1] ^= 0x56aa3350 213 | x[2] ^= 0x677d9197 214 | x[3] ^= 0xb27022dc 215 | 216 | // roundKey[i] = x[i + 4] = x[i] ^ l2(byteSub(x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ CK[i])) 217 | for (let r = 0, mid; r < 32; r += 4) { 218 | mid = x[1] ^ x[2] ^ x[3] ^ CK[r + 0] 219 | roundKey[r + 0] = x[0] ^= l2(byteSub(mid)) // x[4] 220 | 221 | mid = x[2] ^ x[3] ^ x[0] ^ CK[r + 1] 222 | roundKey[r + 1] = x[1] ^= l2(byteSub(mid)) // x[5] 223 | 224 | mid = x[3] ^ x[0] ^ x[1] ^ CK[r + 2] 225 | roundKey[r + 2] = x[2] ^= l2(byteSub(mid)) // x[6] 226 | 227 | mid = x[0] ^ x[1] ^ x[2] ^ CK[r + 3] 228 | roundKey[r + 3] = x[3] ^= l2(byteSub(mid)) // x[7] 229 | } 230 | 231 | // 解密时使用反序的轮密钥 232 | if (cryptFlag === DECRYPT) { 233 | for (let r = 0, mid; r < 16; r++) { 234 | mid = roundKey[r] 235 | roundKey[r] = roundKey[31 - r] 236 | roundKey[31 - r] = mid 237 | } 238 | } 239 | } 240 | 241 | function sm4(inArray, key, cryptFlag, { 242 | padding = 'pkcs#7', mode, iv = [], output = 'string' 243 | } = {}) { 244 | if (mode === 'cbc') { 245 | // CBC 模式,默认走 ECB 模式 246 | if (typeof iv === 'string') iv = hexToArray(iv) 247 | if (iv.length !== (128 / 8)) { 248 | // iv 不是 128 比特 249 | throw new Error('iv is invalid') 250 | } 251 | } 252 | 253 | // 检查 key 254 | if (typeof key === 'string') key = hexToArray(key) 255 | if (key.length !== (128 / 8)) { 256 | // key 不是 128 比特 257 | throw new Error('key is invalid') 258 | } 259 | 260 | // 检查输入 261 | if (typeof inArray === 'string') { 262 | if (cryptFlag !== DECRYPT) { 263 | // 加密,输入为 utf8 串 264 | inArray = utf8ToArray(inArray) 265 | } else { 266 | // 解密,输入为 16 进制串 267 | inArray = hexToArray(inArray) 268 | } 269 | } else { 270 | inArray = [...inArray] 271 | } 272 | 273 | // 新增填充,sm4 是 16 个字节一个分组,所以统一走到 pkcs#7 274 | if ((padding === 'pkcs#5' || padding === 'pkcs#7') && cryptFlag !== DECRYPT) { 275 | const paddingCount = BLOCK - inArray.length % BLOCK 276 | for (let i = 0; i < paddingCount; i++) inArray.push(paddingCount) 277 | } 278 | 279 | // 生成轮密钥 280 | const roundKey = new Array(ROUND) 281 | sms4KeyExt(key, roundKey, cryptFlag) 282 | 283 | const outArray = [] 284 | let lastVector = iv 285 | let restLen = inArray.length 286 | let point = 0 287 | while (restLen >= BLOCK) { 288 | const input = inArray.slice(point, point + 16) 289 | const output = new Array(16) 290 | 291 | if (mode === 'cbc') { 292 | for (let i = 0; i < BLOCK; i++) { 293 | if (cryptFlag !== DECRYPT) { 294 | // 加密过程在组加密前进行异或 295 | input[i] ^= lastVector[i] 296 | } 297 | } 298 | } 299 | 300 | sms4Crypt(input, output, roundKey) 301 | 302 | 303 | for (let i = 0; i < BLOCK; i++) { 304 | if (mode === 'cbc') { 305 | if (cryptFlag === DECRYPT) { 306 | // 解密过程在组解密后进行异或 307 | output[i] ^= lastVector[i] 308 | } 309 | } 310 | 311 | outArray[point + i] = output[i] 312 | } 313 | 314 | if (mode === 'cbc') { 315 | if (cryptFlag !== DECRYPT) { 316 | // 使用上一次输出作为加密向量 317 | lastVector = output 318 | } else { 319 | // 使用上一次输入作为解密向量 320 | lastVector = input 321 | } 322 | } 323 | 324 | restLen -= BLOCK 325 | point += BLOCK 326 | } 327 | 328 | // 去除填充,sm4 是 16 个字节一个分组,所以统一走到 pkcs#7 329 | if ((padding === 'pkcs#5' || padding === 'pkcs#7') && cryptFlag === DECRYPT) { 330 | const len = outArray.length 331 | const paddingCount = outArray[len - 1] 332 | for (let i = 1; i <= paddingCount; i++) { 333 | if (outArray[len - i] !== paddingCount) throw new Error('padding is invalid') 334 | } 335 | outArray.splice(len - paddingCount, paddingCount) 336 | } 337 | 338 | // 调整输出 339 | if (output !== 'array') { 340 | if (cryptFlag !== DECRYPT) { 341 | // 加密,输出转 16 进制串 342 | return ArrayToHex(outArray) 343 | } else { 344 | // 解密,输出转 utf8 串 345 | return arrayToUtf8(outArray) 346 | } 347 | } else { 348 | return outArray 349 | } 350 | } 351 | 352 | module.exports = { 353 | encrypt(inArray, key, options) { 354 | return sm4(inArray, key, 1, options) 355 | }, 356 | decrypt(inArray, key, options) { 357 | return sm4(inArray, key, 0, options) 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /test/sm2.test.js: -------------------------------------------------------------------------------- 1 | const sm2 = require('../src/index').sm2 2 | 3 | const cipherMode = 1 // 1 - C1C3C2,0 - C1C2C3 4 | 5 | // const msgString = 'abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCDabcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCDabcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCDabcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCDabcdefghABCDEFGH'; 6 | const msgString = 'absasdagfadgadsfdfdsf' 7 | 8 | let unCompressedPublicKey 9 | let compressedPublicKey 10 | let privateKey 11 | 12 | beforeAll(() => { 13 | // 生成密钥对 14 | let keypair = sm2.generateKeyPairHex() 15 | 16 | unCompressedPublicKey = keypair.publicKey 17 | privateKey = keypair.privateKey 18 | 19 | compressedPublicKey = sm2.compressPublicKeyHex(unCompressedPublicKey) 20 | }) 21 | 22 | test('sm2: generate keypair', () => { 23 | expect(unCompressedPublicKey.length).toBe(130) 24 | expect(privateKey.length).toBe(64) 25 | expect(compressedPublicKey.length).toBe(66) 26 | expect(sm2.verifyPublicKey(unCompressedPublicKey)).toBe(true) 27 | expect(sm2.verifyPublicKey(compressedPublicKey)).toBe(true) 28 | expect(sm2.comparePublicKeyHex(unCompressedPublicKey, compressedPublicKey)).toBe(true) 29 | expect(sm2.comparePublicKeyHex(unCompressedPublicKey, unCompressedPublicKey)).toBe(true) 30 | expect(sm2.comparePublicKeyHex(compressedPublicKey, compressedPublicKey)).toBe(true) 31 | 32 | // 自定义随机数 33 | const random = [] 34 | for (let i = 0; i < 20; i++) random.push(~~(Math.random() * 10)) 35 | const keypair2 = sm2.generateKeyPairHex(random.join('')) 36 | expect(keypair2.publicKey.length).toBe(130) 37 | expect(keypair2.privateKey.length).toBe(64) 38 | const compressedPublicKey2 = sm2.compressPublicKeyHex(keypair2.publicKey) 39 | expect(compressedPublicKey2.length).toBe(66) 40 | expect(sm2.verifyPublicKey(keypair2.publicKey)).toBe(true) 41 | expect(sm2.verifyPublicKey(compressedPublicKey2)).toBe(true) 42 | expect(sm2.comparePublicKeyHex(keypair2.publicKey, compressedPublicKey2)).toBe(true) 43 | }) 44 | 45 | test('sm2: encrypt and decrypt data', () => { 46 | for (const publicKey of [unCompressedPublicKey, compressedPublicKey]) { 47 | let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) 48 | let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) 49 | expect(decryptData).toBe(msgString) 50 | 51 | for (let i = 0; i < 100; i++) { 52 | encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) 53 | decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) 54 | expect(decryptData).toBe(msgString) 55 | } 56 | 57 | encryptData = sm2.doEncrypt([0x61, 0x62, 0x73, 0x61, 0x73, 0x64, 0x61, 0x67, 0x66, 0x61, 0x64, 0x67, 0x61, 0x64, 0x73, 0x66, 0x64, 0x66, 0x64, 0x73, 0x66], publicKey, cipherMode) 58 | decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) 59 | expect(decryptData).toBe(msgString) 60 | encryptData = sm2.doEncrypt(Uint8Array.from([0x61, 0x62, 0x73, 0x61, 0x73, 0x64, 0x61, 0x67, 0x66, 0x61, 0x64, 0x67, 0x61, 0x64, 0x73, 0x66, 0x64, 0x66, 0x64, 0x73, 0x66]), publicKey, cipherMode) 61 | decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) 62 | expect(decryptData).toBe(msgString) 63 | decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode, {output: 'array'}) 64 | expect(decryptData).toEqual([0x61, 0x62, 0x73, 0x61, 0x73, 0x64, 0x61, 0x67, 0x66, 0x61, 0x64, 0x67, 0x61, 0x64, 0x73, 0x66, 0x64, 0x66, 0x64, 0x73, 0x66]) 65 | } 66 | }) 67 | 68 | test('sm2: sign data and verify sign', () => { 69 | for (const publicKey of [unCompressedPublicKey, compressedPublicKey]) { 70 | // 纯签名 + 生成椭圆曲线点 71 | let sigValueHex = sm2.doSignature(msgString, privateKey) 72 | let verifyResult = sm2.doVerifySignature(msgString, sigValueHex, publicKey) 73 | expect(verifyResult).toBe(true) 74 | 75 | // 纯签名 76 | let sigValueHex2 = sm2.doSignature(msgString, privateKey, { 77 | pointPool: [sm2.getPoint(), sm2.getPoint(), sm2.getPoint(), sm2.getPoint()], 78 | }) 79 | let verifyResult2 = sm2.doVerifySignature(msgString, sigValueHex2, publicKey) 80 | expect(verifyResult2).toBe(true) 81 | 82 | // 纯签名 + 生成椭圆曲线点 + der编解码 83 | let sigValueHex3 = sm2.doSignature(msgString, privateKey, { 84 | der: true, 85 | }) 86 | let verifyResult3 = sm2.doVerifySignature(msgString, sigValueHex3, publicKey, { 87 | der: true, 88 | }) 89 | expect(verifyResult3).toBe(true) 90 | 91 | // 纯签名 + 生成椭圆曲线点 + sm3杂凑 92 | let sigValueHex4 = sm2.doSignature(msgString, privateKey, { 93 | hash: true, 94 | }) 95 | let verifyResult4 = sm2.doVerifySignature(msgString, sigValueHex4, publicKey, { 96 | hash: true, 97 | }) 98 | expect(verifyResult4).toBe(true) 99 | 100 | for (let i = 0; i < 100; i++) { 101 | sigValueHex4 = sm2.doSignature(msgString, privateKey, { 102 | hash: true, 103 | }) 104 | verifyResult4 = sm2.doVerifySignature(msgString, sigValueHex4, publicKey, { 105 | hash: true, 106 | }) 107 | expect(verifyResult4).toBe(true) 108 | } 109 | 110 | // 纯签名 + 生成椭圆曲线点 + sm3杂凑(不做公钥推导) 111 | let sigValueHex5 = sm2.doSignature(msgString, privateKey, { 112 | hash: true, 113 | publicKey, 114 | }) 115 | let verifyResult5 = sm2.doVerifySignature(msgString, sigValueHex5, publicKey, { 116 | hash: true, 117 | publicKey, 118 | }) 119 | expect(verifyResult5).toBe(true) 120 | 121 | // 纯签名 + 生成椭圆曲线点 + sm3杂凑 + 不做公钥推 + 添加 userId 122 | let sigValueHex6 = sm2.doSignature(msgString, privateKey, { 123 | hash: true, 124 | publicKey, 125 | userId: 'testUserId', 126 | }) 127 | let verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 128 | hash: true, 129 | userId: 'testUserId', 130 | }) 131 | expect(verifyResult6).toBe(true) 132 | verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 133 | hash: true, 134 | userId: 'wrongTestUserId', 135 | }) 136 | expect(verifyResult6).toBe(false) 137 | sigValueHex6 = sm2.doSignature(msgString, privateKey, { 138 | hash: true, 139 | publicKey, 140 | userId: '', 141 | }) 142 | verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 143 | hash: true, 144 | userId: '', 145 | }) 146 | expect(verifyResult6).toBe(true) 147 | verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 148 | hash: true, 149 | }) 150 | expect(verifyResult6).toBe(false) 151 | sigValueHex6 = sm2.doSignature(msgString, privateKey, { 152 | hash: true, 153 | publicKey, 154 | }) 155 | verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 156 | hash: true, 157 | }) 158 | expect(verifyResult6).toBe(true) 159 | verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 160 | hash: true, 161 | userId: '', 162 | }) 163 | expect(verifyResult6).toBe(false) 164 | verifyResult6 = sm2.doVerifySignature(msgString, sigValueHex6, publicKey, { 165 | hash: true, 166 | userId: '1234567812345678' 167 | }) 168 | expect(verifyResult6).toBe(true) 169 | } 170 | }) 171 | 172 | test('sm2: getPublicKeyFromPrivateKey', () => { 173 | for (let i = 0; i < 10; i++) { 174 | const { privateKey, publicKey } = sm2.generateKeyPairHex() 175 | expect(sm2.getPublicKeyFromPrivateKey(privateKey)).toEqual(publicKey) 176 | } 177 | }) 178 | -------------------------------------------------------------------------------- /test/sm3.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const sm3 = require('../src/index').sm3 4 | 5 | test('sm3: must match the result', () => { 6 | // 单字节 7 | expect(sm3('abc')).toBe('66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0') 8 | expect(sm3('abcdefghABCDEFGH12345678')).toBe('d670c7f027fd5f9f0c163f4bfe98f9003fe597d3f52dbab0885ec2ca8dd23e9b') 9 | expect(sm3('abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefgh')).toBe('1cf3bafec325d7d9102cd67ba46b09195af4e613b6c2b898122363d810308b11') 10 | expect(sm3('abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCD')).toBe('b8ac4203969bde27434ce667b0adbf3439ee97e416e73cb96f4431f478a531fe') 11 | expect(sm3('abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCDEFGH')).toBe('5ef0cdbe0d54426eea7f5c8b44385bb1003548735feaa59137c3dfe608aa9567') 12 | expect(sm3('abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd')).toBe('debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732') 13 | 14 | // 双字节 15 | expect(sm3('ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĀāẢảȀȁȂȃẠạẶặẬậḀḁȺⱥᶏẚɐɑᶐɒᴀÆæǼǽǢǣᴂᴁ')).toBe('175c329c05aa9e2ce3ad49551d404f670bc7fd59dfa51c748871ffd1a6b179a6') 16 | expect(sm3('ḂḃḄḅḆḇɃƀƁɓƂƃᵬᶀʙᴃĆćĈĉČčC̱c̱ĊċÇçḈḉȻȼƇƈɕↃↄᴄĎďḊḋḐḑḌḍḒḓḎḏÐðĐđƉɖƊɗᶑƋƌᵭȸȡᴅᴆƍʣʥʤÉéÈèĔĕÊêẾếỀềỄễỂểĚěËëẼẽĖėȨȩḜḝĘęĒēḖḗḔḕẺẻȄȅȆȇẸẹỆệḘḙḚḛɆɇᶒƎǝƏəᶕɚƐɛᶓᴈɘɜᶔɝɞʚɤᴇḞḟ')).toBe('42da89d385fa8b62bb531cccffe5a4115d3069cd3ad5f9f42f86fa8875308d2a') 17 | expect(sm3('ƑƒᵮᶂⅎʩǴǵĞğĜĝǦǧĠġĢģḠḡǤǥƓɠɡᶃᵹɢʛᵷƔɣƢƣĤĥȞȟḦḧḢḣḨḩḤḥḪḫH̠ẖĦħⱧⱨǶƕⱵⱶʜɦɧÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨIJijɪƗᵻᶖİıƖɩᵼᴉɿĴĵɈɉJ̌ǰȷʝɟʄᴊḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄĸʞᴋ')).toBe('323d957e0426bb388bdf83149aa99f5b80447adb27fb4514d1ffffd9c2e9f1f2') 18 | 19 | // 中文 20 | expect(sm3('你好')).toBe('78e5c78c5322ca174089e58dc7790acf8ce9d542bee6ae4a5a0797d5e356be61') 21 | expect(sm3('今天天气真是不错')).toBe('fff6e05118c782f5a2cea8bc2efec8819d0dc6d7d09cb9aa5c4ef14e673fa043') 22 | expect(sm3('今天天气真是糟透了')).toBe('f2e417f09f99ee7a08fa6c8fd75f87b7969b20a60e80b04154a5aae7220c87d8') 23 | 24 | // 四字节 25 | expect(sm3('🇨🇳𠮷😀😃😄😁😆😅')).toBe('b6d217a24511ddb7593f13b74519038618cbb5b947fee20da3f0cd5503152c23') 26 | 27 | // 字节数组 28 | expect(sm3([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x20, 0xe6, 0x88, 0x91, 0xe6, 0x98, 0xaf, 0x20, 0x6a, 0x75, 0x6e, 0x65, 0x61, 0x6e, 0x64, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x2e])).toBe('ef1cc8e36012c1f1bc18034ab778ef800e8bb1b40c7a4c7177186f6fd521110e') 29 | expect(sm3('hello world! 我是 juneandgreen.')).toBe('ef1cc8e36012c1f1bc18034ab778ef800e8bb1b40c7a4c7177186f6fd521110e') 30 | expect(sm3([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x20, 0xe6, 0x88, 0x91, 0xe6, 0x98, 0xaf, 0x20, 0x6a, 0x75, 0x6e, 0x65, 0x61, 0x6e, 0x64, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x2e])).toBe(sm3('hello world! 我是 juneandgreen.')) 31 | 32 | // 碰撞 case 33 | expect(sm3('丧丙上䠉䰋不亐乑')).toBe('382f78a3065187c40152d2f5ca283f8f4bf148909c763cfbdfa7efb943016552') 34 | expect(sm3('鱏8fpT肙腳荧HNQ')).toBe('b67a77a89564b7be13fccf456e0ec39ad564eb46f4c54c6946b1d548efc4078d') 35 | expect(sm3('丧丙上䠉䰋不亐乑')).not.toBe(sm3('鱏8fpT肙腳荧HNQ')) 36 | }) 37 | 38 | test('sm3: long text', () => { 39 | const input = fs.readFileSync(path.join(__dirname, './test.jpg')) 40 | expect(sm3(input)).toBe('585084878e61b6b1bed61207142ea0313fa3c0c8211e44871bdaa637632ccff0') 41 | }) 42 | 43 | test('sm3: hmac', () => { 44 | expect(sm3('abc', { 45 | key: 'daac25c1512fe50f79b0e4526b93f5c0e1460cef40b6dd44af13caec62e8c60e0d885f3c6d6fb51e530889e6fd4ac743a6d332e68a0f2a3923f42585dceb93e9', 46 | })).toBe('5c690e2b822a514017f1ccb9a61b6738714dbd17dbd6fdbc2fa662d122b6885d') 47 | 48 | expect(sm3([0x31, 0x32, 0x33, 0x34, 0x35, 0x36], { 49 | key: '31323334353637383930', 50 | })).toBe('bc1f71eef901223ae7a9718e3ae1dbf97353c81acb429b491bbdbefd2195b95e') 51 | 52 | const bytes8 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] 53 | 54 | // 32 字节 55 | const bytes32 = [].concat(bytes8, bytes8, bytes8, bytes8) 56 | expect(sm3(bytes32, { 57 | key: bytes32, 58 | })).toBe('41e6589cde89b4f8c810a820c2fb6f0ad86bf2c136a19cfb3a5c0835f598e07b') 59 | 60 | // 64 字节 61 | const bytes64 = [].concat(bytes32, bytes32) 62 | expect(sm3(bytes64, { 63 | key: bytes64, 64 | })).toBe('d6fb17c240930a21996373aa9fc0b1092931b016640809297911cd3f8cc9dcdd') 65 | 66 | // 128 字节 67 | const bytes128 = [].concat(bytes64, bytes64) 68 | expect(sm3(bytes128, { 69 | key: bytes128, 70 | })).toBe('d374f8adb0e9d1f12de94c1406fe8b2d53f84129e033f0d269400de8e8e7ca1a') 71 | }) -------------------------------------------------------------------------------- /test/sm4.test.js: -------------------------------------------------------------------------------- 1 | const sm4 = require('../src/index').sm4 2 | const msg = 'hello world! 我是 juneandgreen.' 3 | const input = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10] 4 | const output_1 = [0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46, 0x00, 0x2a, 0x8a, 0x4e, 0xfa, 0x86, 0x3c, 0xca, 0xd0, 0x24, 0xac, 0x03, 0x00, 0xbb, 0x40, 0xd2] 5 | const output_2 = [0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46] 6 | const output_3 = [0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46, 0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46, 0x00, 0x2a, 0x8a, 0x4e, 0xfa, 0x86, 0x3c, 0xca, 0xd0, 0x24, 0xac, 0x03, 0x00, 0xbb, 0x40, 0xd2] 7 | const outputHexStr_1 = '681edf34d206965e86b3e94f536e4246002a8a4efa863ccad024ac0300bb40d2' 8 | const outputHexStr_2 = '681edf34d206965e86b3e94f536e4246' 9 | const outputHexStr_3 = '681edf34d206965e86b3e94f536e4246681edf34d206965e86b3e94f536e4246002a8a4efa863ccad024ac0300bb40d2' 10 | const input2 = [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x20, 0xe6, 0x88, 0x91, 0xe6, 0x98, 0xaf, 0x20, 0x6a, 0x75, 0x6e, 0x65, 0x61, 0x6e, 0x64, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x2e] 11 | const output2HexStr = '0e395deb10f6e8a17e17823e1fd9bd98a1bff1df508b5b8a1efb79ec633d1bb129432ac1b74972dbe97bab04f024e89c' 12 | const output3HexStr = '0d6cfa73c823b2ac0d6a92c564171892000fbea90be7a4d440bc58a9044fcb5f3d1615d91a6dbfb4dfb0c6915071527b' 13 | const output2 = [0x0e, 0x39, 0x5d, 0xeb, 0x10, 0xf6, 0xe8, 0xa1, 0x7e, 0x17, 0x82, 0x3e, 0x1f, 0xd9, 0xbd, 0x98, 0xa1, 0xbf, 0xf1, 0xdf, 0x50, 0x8b, 0x5b, 0x8a, 0x1e, 0xfb, 0x79, 0xec, 0x63, 0x3d, 0x1b, 0xb1, 0x29, 0x43, 0x2a, 0xc1, 0xb7, 0x49, 0x72, 0xdb, 0xe9, 0x7b, 0xab, 0x04, 0xf0, 0x24, 0xe8, 0x9c] 14 | const iv = [0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef] 15 | const ivHexStr = 'fedcba98765432100123456789abcdef' 16 | const key = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10] 17 | const keyHexStr = '0123456789abcdeffedcba9876543210' 18 | 19 | test('sm4: encrypt a group', () => { 20 | expect(sm4.encrypt(input, key)).toBe(outputHexStr_1) 21 | expect(sm4.encrypt(input, key, {output: 'array'})).toEqual(output_1) 22 | expect(sm4.encrypt(input, key, {padding: 'pkcs#5'})).toBe(outputHexStr_1) 23 | expect(sm4.encrypt(input, key, {padding: 'pkcs#5', output: 'array'})).toEqual(output_1) 24 | expect(sm4.encrypt(input, key, {padding: 'none'})).toBe(outputHexStr_2) 25 | expect(sm4.encrypt(input, key, {padding: 'none', output: 'array'})).toEqual(output_2) 26 | expect(sm4.encrypt(msg, keyHexStr)).toBe(output2HexStr) 27 | expect(sm4.encrypt(msg, keyHexStr, {output: 'array'})).toEqual(output2) 28 | expect(sm4.encrypt(msg, keyHexStr, {mode: 'cbc', iv})).toBe(output3HexStr) 29 | expect(sm4.encrypt(msg, keyHexStr, {mode: 'cbc', iv: ivHexStr})).toBe(output3HexStr) 30 | expect(sm4.encrypt(input2, keyHexStr)).toBe(output2HexStr) 31 | expect(sm4.encrypt(input2, keyHexStr, {output: 'array'})).toEqual(output2) 32 | }) 33 | 34 | test('sm4: decrypt a group', () => { 35 | expect(sm4.decrypt(outputHexStr_1, key, {output: 'array'})).toEqual(input) 36 | expect(sm4.decrypt(output_1, key, {output: 'array'})).toEqual(input) 37 | expect(sm4.decrypt(outputHexStr_2, key, {padding: 'none', output: 'array'})).toEqual(input) 38 | expect(sm4.decrypt(output_2, key, {padding: 'none', output: 'array'})).toEqual(input) 39 | expect(sm4.decrypt(output2HexStr, keyHexStr)).toBe(msg) 40 | expect(sm4.decrypt(output2, keyHexStr)).toBe(msg) 41 | expect(sm4.decrypt(output2HexStr, keyHexStr, {output: 'array'})).toEqual(input2) 42 | expect(sm4.decrypt(output3HexStr, keyHexStr, {mode: 'cbc', iv})).toBe(msg) 43 | expect(sm4.decrypt(output3HexStr, keyHexStr, {mode: 'cbc', iv: ivHexStr})).toBe(msg) 44 | expect(sm4.decrypt(output2, keyHexStr, {output: 'array'})).toEqual(input2) 45 | }) 46 | 47 | test('sm4: encrypt several groups', () => { 48 | expect(sm4.encrypt([...input, ...input], key)).toBe(outputHexStr_3) 49 | expect(sm4.encrypt([...input, ...input], key, {output: 'array'})).toEqual(output_3) 50 | expect(sm4.encrypt([...input, ...input], key, {padding: 'none'})).toBe(outputHexStr_2 + outputHexStr_2) 51 | expect(sm4.encrypt([...input, ...input], key, {padding: 'none', output: 'array'})).toEqual([...output_2, ...output_2]) 52 | }) 53 | 54 | test('sm4: decrypt several groups', () => { 55 | expect(sm4.decrypt(outputHexStr_3, key, {output: 'array'})).toEqual([...input, ...input]) 56 | expect(sm4.decrypt(output_3, key, {output: 'array'})).toEqual([...input, ...input]) 57 | expect(sm4.decrypt(outputHexStr_2 + outputHexStr_2, key, {padding: 'none', output: 'array'})).toEqual([...input, ...input]) 58 | expect(sm4.decrypt([...output_2, ...output_2], key, {padding: 'none', output: 'array'})).toEqual([...input, ...input]) 59 | }) 60 | 61 | test('sm4: encrypt unicode string', () => { 62 | expect(sm4.encrypt('🇨🇳𠮷😀😃😄😁😆😅', keyHexStr)).toBe('a0b1aac2e6db928ddfc8a081a6661d0452b44e5720db106714ffc8cbee29bcf7d96b4d64bffd07553e6a2ee096523b7f') 63 | expect(sm4.decrypt('a0b1aac2e6db928ddfc8a081a6661d0452b44e5720db106714ffc8cbee29bcf7d96b4d64bffd07553e6a2ee096523b7f', keyHexStr)).toBe('🇨🇳𠮷😀😃😄😁😆😅') 64 | }) 65 | 66 | test('sm4: encrypt a group whit 1000000 times', () => { 67 | let temp = input 68 | for (let i = 0; i < 1000000; i++) { 69 | temp = sm4.encrypt(temp, key, {padding: 'none', output: 'array'}) 70 | } 71 | expect(temp).toEqual([0x59, 0x52, 0x98, 0xc7, 0xc6, 0xfd, 0x27, 0x1f, 0x04, 0x02, 0xf8, 0x04, 0xc3, 0x3d, 0x3f, 0x66]) 72 | }) 73 | 74 | test('sm4: invalid padding', () => { 75 | expect(() => sm4.decrypt('a0b1aac2e6db928ddfc8a081a6661d0452b44e5720db106714ffc8cbee29bcf7d96b4d64bffd07553e6a2ee096523b7a', keyHexStr)).toThrow('padding is invalid') 76 | expect(() => sm4.decrypt('a0b1aac2e6db928ddfc8a081a6661d0452b44e5720db106714ffc8cbee29bcf7d96b4d64bffd07553e6a2ee096523b7f', ivHexStr)).toThrow('padding is invalid') 77 | }) -------------------------------------------------------------------------------- /test/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/sm-crypto/43bbfc859c8b9516e53419b49e978f1a21b82d80/test/test.jpg -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const gulp = require('gulp') 4 | const clean = require('gulp-clean') 5 | const less = require('gulp-less') 6 | const rename = require('gulp-rename') 7 | const gulpif = require('gulp-if') 8 | const sourcemaps = require('gulp-sourcemaps') 9 | const webpack = require('webpack') 10 | const gulpInstall = require('gulp-install') 11 | 12 | const config = require('./config') 13 | const checkComponents = require('./checkcomponents') 14 | const _ = require('./utils') 15 | 16 | const wxssConfig = config.wxss || {} 17 | const srcPath = config.srcPath 18 | const distPath = config.distPath 19 | 20 | /** 21 | * get wxss stream 22 | */ 23 | function wxss(wxssFileList) { 24 | if (!wxssFileList.length) return false 25 | 26 | return gulp.src(wxssFileList, {cwd: srcPath, base: srcPath}) 27 | .pipe(gulpif(wxssConfig.less && wxssConfig.sourcemap, sourcemaps.init())) 28 | .pipe(gulpif(wxssConfig.less, less({paths: [srcPath]}))) 29 | .pipe(rename({extname: '.wxss'})) 30 | .pipe(gulpif(wxssConfig.less && wxssConfig.sourcemap, sourcemaps.write('./'))) 31 | .pipe(_.logger(wxssConfig.less ? 'generate' : undefined)) 32 | .pipe(gulp.dest(distPath)) 33 | } 34 | 35 | /** 36 | * get js stream 37 | */ 38 | function js(jsFileMap, scope) { 39 | const webpackConfig = config.webpack 40 | const webpackCallback = (err, stats) => { 41 | if (!err) { 42 | // eslint-disable-next-line no-console 43 | console.log(stats.toString({ 44 | assets: true, 45 | cached: false, 46 | colors: true, 47 | children: false, 48 | errors: true, 49 | warnings: true, 50 | version: true, 51 | modules: false, 52 | publicPath: true, 53 | })) 54 | } else { 55 | // eslint-disable-next-line no-console 56 | console.log(err) 57 | } 58 | } 59 | 60 | webpackConfig.entry = jsFileMap 61 | webpackConfig.output.path = distPath 62 | 63 | if (scope.webpackWatcher) { 64 | scope.webpackWatcher.close() 65 | scope.webpackWatcher = null 66 | } 67 | 68 | if (config.isWatch) { 69 | scope.webpackWatcher = webpack(webpackConfig).watch({ 70 | ignored: /node_modules/, 71 | }, webpackCallback) 72 | } else { 73 | webpack(webpackConfig).run(webpackCallback) 74 | } 75 | } 76 | 77 | /** 78 | * copy file 79 | */ 80 | function copy(copyFileList) { 81 | if (!copyFileList.length) return false 82 | 83 | return gulp.src(copyFileList, {cwd: srcPath, base: srcPath}) 84 | .pipe(_.logger()) 85 | .pipe(gulp.dest(distPath)) 86 | } 87 | 88 | /** 89 | * install packages 90 | */ 91 | function install() { 92 | return gulp.series(async () => { 93 | const demoDist = config.demoDist 94 | const demoPackageJsonPath = path.join(demoDist, 'package.json') 95 | const packageJson = _.readJson(path.resolve(__dirname, '../package.json')) 96 | const dependencies = packageJson.dependencies || {} 97 | 98 | await _.writeFile(demoPackageJsonPath, JSON.stringify({dependencies}, null, '\t')) // write dev demo's package.json 99 | }, () => { 100 | const demoDist = config.demoDist 101 | const demoPackageJsonPath = path.join(demoDist, 'package.json') 102 | 103 | return gulp.src(demoPackageJsonPath) 104 | .pipe(gulpInstall({production: true})) 105 | }) 106 | } 107 | 108 | class BuildTask { 109 | constructor(id, entry) { 110 | if (!entry) return 111 | 112 | this.id = id 113 | this.entries = Array.isArray(config.entry) ? config.entry : [config.entry] 114 | this.copyList = Array.isArray(config.copy) ? config.copy : [] 115 | this.componentListMap = {} 116 | this.cachedComponentListMap = {} 117 | 118 | this.init() 119 | } 120 | 121 | init() { 122 | const id = this.id 123 | 124 | /** 125 | * clean the dist folder 126 | */ 127 | gulp.task(`${id}-clean-dist`, () => gulp.src(distPath, {read: false, allowEmpty: true}).pipe(clean())) 128 | 129 | /** 130 | * copy demo to the dev folder 131 | */ 132 | let isDemoExists = false 133 | gulp.task(`${id}-demo`, gulp.series(async () => { 134 | const demoDist = config.demoDist 135 | 136 | isDemoExists = await _.checkFileExists(path.join(demoDist, 'project.config.json')) 137 | }, done => { 138 | if (!isDemoExists) { 139 | const demoSrc = config.demoSrc 140 | const demoDist = config.demoDist 141 | 142 | return gulp.src('**/*', {cwd: demoSrc, base: demoSrc}) 143 | .pipe(gulp.dest(demoDist)) 144 | } 145 | 146 | return done() 147 | })) 148 | 149 | /** 150 | * install packages for dev 151 | */ 152 | gulp.task(`${id}-install`, install()) 153 | 154 | /** 155 | * check custom components 156 | */ 157 | gulp.task(`${id}-component-check`, async () => { 158 | const entries = this.entries 159 | const mergeComponentListMap = {} 160 | for (let i = 0, len = entries.length; i < len; i++) { 161 | let entry = entries[i] 162 | entry = path.join(srcPath, `${entry}.json`) 163 | // eslint-disable-next-line no-await-in-loop 164 | const newComponentListMap = await checkComponents(entry) 165 | 166 | _.merge(mergeComponentListMap, newComponentListMap) 167 | } 168 | 169 | this.cachedComponentListMap = this.componentListMap 170 | this.componentListMap = mergeComponentListMap 171 | }) 172 | 173 | /** 174 | * write json to the dist folder 175 | */ 176 | gulp.task(`${id}-component-json`, done => { 177 | const jsonFileList = this.componentListMap.jsonFileList 178 | 179 | if (jsonFileList && jsonFileList.length) { 180 | return copy(this.componentListMap.jsonFileList) 181 | } 182 | 183 | return done() 184 | }) 185 | 186 | /** 187 | * copy wxml to the dist folder 188 | */ 189 | gulp.task(`${id}-component-wxml`, done => { 190 | const wxmlFileList = this.componentListMap.wxmlFileList 191 | 192 | if (wxmlFileList && 193 | wxmlFileList.length && 194 | !_.compareArray(this.cachedComponentListMap.wxmlFileList, wxmlFileList)) { 195 | return copy(wxmlFileList) 196 | } 197 | 198 | return done() 199 | }) 200 | 201 | /** 202 | * generate wxss to the dist folder 203 | */ 204 | gulp.task(`${id}-component-wxss`, done => { 205 | const wxssFileList = this.componentListMap.wxssFileList 206 | 207 | if (wxssFileList && 208 | wxssFileList.length && 209 | !_.compareArray(this.cachedComponentListMap.wxssFileList, wxssFileList)) { 210 | return wxss(wxssFileList, srcPath, distPath) 211 | } 212 | 213 | return done() 214 | }) 215 | 216 | /** 217 | * generate js to the dist folder 218 | */ 219 | gulp.task(`${id}-component-js`, done => { 220 | const jsFileList = this.componentListMap.jsFileList 221 | 222 | if (jsFileList && 223 | jsFileList.length && 224 | !_.compareArray(this.cachedComponentListMap.jsFileList, jsFileList)) { 225 | js(this.componentListMap.jsFileMap, this) 226 | } 227 | 228 | return done() 229 | }) 230 | 231 | /** 232 | * copy resources to dist folder 233 | */ 234 | gulp.task(`${id}-copy`, gulp.parallel(done => { 235 | const copyList = this.copyList 236 | const copyFileList = copyList.map(dir => path.join(dir, '**/*.!(wxss)')) 237 | 238 | if (copyFileList.length) return copy(copyFileList) 239 | 240 | return done() 241 | }, done => { 242 | const copyList = this.copyList 243 | const copyFileList = copyList.map(dir => path.join(dir, '**/*.wxss')) 244 | 245 | if (copyFileList.length) return wxss(copyFileList, srcPath, distPath) 246 | 247 | return done() 248 | })) 249 | 250 | /** 251 | * watch json 252 | */ 253 | gulp.task(`${id}-watch-json`, () => gulp.watch(this.componentListMap.jsonFileList, {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-check`, gulp.parallel(`${id}-component-wxml`, `${id}-component-wxss`, `${id}-component-js`, `${id}-component-json`)))) 254 | 255 | /** 256 | * watch wxml 257 | */ 258 | gulp.task(`${id}-watch-wxml`, () => { 259 | this.cachedComponentListMap.wxmlFileList = null 260 | return gulp.watch(this.componentListMap.wxmlFileList, {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-wxml`)) 261 | }) 262 | 263 | /** 264 | * watch wxss 265 | */ 266 | gulp.task(`${id}-watch-wxss`, () => { 267 | this.cachedComponentListMap.wxssFileList = null 268 | return gulp.watch('**/*.wxss', {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-wxss`)) 269 | }) 270 | 271 | /** 272 | * watch resources 273 | */ 274 | gulp.task(`${id}-watch-copy`, () => { 275 | const copyList = this.copyList 276 | const copyFileList = copyList.map(dir => path.join(dir, '**/*')) 277 | const watchCallback = filePath => copy([filePath]) 278 | 279 | return gulp.watch(copyFileList, {cwd: srcPath, base: srcPath}) 280 | .on('change', watchCallback) 281 | .on('add', watchCallback) 282 | .on('unlink', watchCallback) 283 | }) 284 | 285 | /** 286 | * watch demo 287 | */ 288 | gulp.task(`${id}-watch-demo`, () => { 289 | const demoSrc = config.demoSrc 290 | const demoDist = config.demoDist 291 | const watchCallback = filePath => gulp.src(filePath, {cwd: demoSrc, base: demoSrc}) 292 | .pipe(gulp.dest(demoDist)) 293 | 294 | return gulp.watch('**/*', {cwd: demoSrc, base: demoSrc}) 295 | .on('change', watchCallback) 296 | .on('add', watchCallback) 297 | .on('unlink', watchCallback) 298 | }) 299 | 300 | /** 301 | * watch installed packages 302 | */ 303 | gulp.task(`${id}-watch-install`, () => gulp.watch(path.resolve(__dirname, '../package.json'), install())) 304 | 305 | /** 306 | * build custom component 307 | */ 308 | gulp.task(`${id}-build`, gulp.series(`${id}-clean-dist`, `${id}-component-check`, gulp.parallel(`${id}-component-wxml`, `${id}-component-wxss`, `${id}-component-js`, `${id}-component-json`, `${id}-copy`))) 309 | 310 | gulp.task(`${id}-watch`, gulp.series(`${id}-build`, `${id}-demo`, `${id}-install`, gulp.parallel(`${id}-watch-wxml`, `${id}-watch-wxss`, `${id}-watch-json`, `${id}-watch-copy`, `${id}-watch-install`, `${id}-watch-demo`))) 311 | 312 | gulp.task(`${id}-dev`, gulp.series(`${id}-build`, `${id}-demo`, `${id}-install`)) 313 | 314 | gulp.task(`${id}-default`, gulp.series(`${id}-build`)) 315 | } 316 | } 317 | 318 | module.exports = BuildTask 319 | -------------------------------------------------------------------------------- /tools/checkcomponents.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const _ = require('./utils') 4 | const config = require('./config') 5 | 6 | const srcPath = config.srcPath 7 | 8 | /** 9 | * get json path's info 10 | */ 11 | function getJsonPathInfo(jsonPath) { 12 | const dirPath = path.dirname(jsonPath) 13 | const fileName = path.basename(jsonPath, '.json') 14 | const relative = path.relative(srcPath, dirPath) 15 | const fileBase = path.join(relative, fileName) 16 | 17 | return { 18 | dirPath, fileName, relative, fileBase 19 | } 20 | } 21 | 22 | /** 23 | * check included components 24 | */ 25 | const checkProps = ['usingComponents', 'componentGenerics'] 26 | async function checkIncludedComponents(jsonPath, componentListMap) { 27 | const json = _.readJson(jsonPath) 28 | if (!json) throw new Error(`json is not valid: "${jsonPath}"`) 29 | 30 | const {dirPath, fileName, fileBase} = getJsonPathInfo(jsonPath) 31 | 32 | for (let i = 0, len = checkProps.length; i < len; i++) { 33 | const checkProp = checkProps[i] 34 | const checkPropValue = json[checkProp] || {} 35 | const keys = Object.keys(checkPropValue) 36 | 37 | for (let j = 0, jlen = keys.length; j < jlen; j++) { 38 | const key = keys[j] 39 | let value = typeof checkPropValue[key] === 'object' ? checkPropValue[key].default : checkPropValue[key] 40 | if (!value) continue 41 | 42 | value = _.transformPath(value, path.sep) 43 | 44 | // check relative path 45 | const componentPath = `${path.join(dirPath, value)}.json` 46 | // eslint-disable-next-line no-await-in-loop 47 | const isExists = await _.checkFileExists(componentPath) 48 | if (isExists) { 49 | // eslint-disable-next-line no-await-in-loop 50 | await checkIncludedComponents(componentPath, componentListMap) 51 | } 52 | } 53 | } 54 | 55 | // checked 56 | componentListMap.wxmlFileList.push(`${fileBase}.wxml`) 57 | componentListMap.wxssFileList.push(`${fileBase}.wxss`) 58 | componentListMap.jsonFileList.push(`${fileBase}.json`) 59 | componentListMap.jsFileList.push(`${fileBase}.js`) 60 | 61 | componentListMap.jsFileMap[fileBase] = `${path.join(dirPath, fileName)}.js` 62 | } 63 | 64 | module.exports = async function (entry) { 65 | const componentListMap = { 66 | wxmlFileList: [], 67 | wxssFileList: [], 68 | jsonFileList: [], 69 | jsFileList: [], 70 | 71 | jsFileMap: {}, // for webpack entry 72 | } 73 | 74 | const isExists = await _.checkFileExists(entry) 75 | if (!isExists) { 76 | const {dirPath, fileName, fileBase} = getJsonPathInfo(entry) 77 | 78 | componentListMap.jsFileList.push(`${fileBase}.js`) 79 | componentListMap.jsFileMap[fileBase] = `${path.join(dirPath, fileName)}.js` 80 | 81 | return componentListMap 82 | } 83 | 84 | await checkIncludedComponents(entry, componentListMap) 85 | 86 | return componentListMap 87 | } 88 | -------------------------------------------------------------------------------- /tools/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const webpack = require('webpack') 4 | const nodeExternals = require('webpack-node-externals') 5 | 6 | const isDev = process.argv.indexOf('--develop') >= 0 7 | const isWatch = process.argv.indexOf('--watch') >= 0 8 | const demoSrc = path.resolve(__dirname, './demo') 9 | const demoDist = path.resolve(__dirname, '../miniprogram_dev') 10 | const src = path.resolve(__dirname, '../src') 11 | const dev = path.join(demoDist, 'components') 12 | const dist = path.resolve(__dirname, '../miniprogram_dist') 13 | 14 | module.exports = { 15 | entry: ['index'], 16 | 17 | isDev, 18 | isWatch, 19 | srcPath: src, 20 | distPath: isDev ? dev : dist, 21 | 22 | demoSrc, 23 | demoDist, 24 | 25 | wxss: { 26 | less: false, // compile wxss with less 27 | sourcemap: false, // source map for less 28 | }, 29 | 30 | webpack: { 31 | mode: 'production', 32 | output: { 33 | filename: '[name].js', 34 | libraryTarget: 'commonjs2', 35 | }, 36 | target: 'node', 37 | externals: [nodeExternals()], // ignore node_modules 38 | module: { 39 | rules: [{ 40 | test: /\.js$/i, 41 | use: [ 42 | 'babel-loader', 43 | 'eslint-loader' 44 | ], 45 | exclude: /node_modules/ 46 | }], 47 | }, 48 | resolve: { 49 | modules: [src, 'node_modules'], 50 | extensions: ['.js', '.json'], 51 | }, 52 | plugins: [ 53 | new webpack.DefinePlugin({}), 54 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}), 55 | ], 56 | optimization: { 57 | minimize: false, 58 | }, 59 | // devtool: 'nosources-source-map', // source map for js 60 | performance: { 61 | hints: 'warning', 62 | assetFilter: assetFilename => assetFilename.endsWith('.js') 63 | } 64 | }, 65 | copy: ['./wxml', './wxss', './wxs', './images'], 66 | } 67 | -------------------------------------------------------------------------------- /tools/demo/app.js: -------------------------------------------------------------------------------- 1 | App({}); 2 | -------------------------------------------------------------------------------- /tools/demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index" 4 | ], 5 | "window":{ 6 | "backgroundTextStyle":"light", 7 | "navigationBarBackgroundColor": "#000", 8 | "navigationBarTitleText": "WeChat", 9 | "navigationBarTextStyle":"white" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tools/demo/app.wxss: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: space-between; 7 | padding: 200rpx 0; 8 | box-sizing: border-box; 9 | } -------------------------------------------------------------------------------- /tools/demo/package.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tools/demo/pages/index/index.js: -------------------------------------------------------------------------------- 1 | const sm = require('../../components/index') 2 | 3 | function compareArray(a, b) { 4 | if (a.length !== b.length) return false 5 | 6 | for (let i = 0, len = a.length; i < a; i++) { 7 | if (a[i] !== b[i]) return false; 8 | } 9 | 10 | return true; 11 | } 12 | 13 | Page({ 14 | onLoad() { 15 | this.sm2() 16 | this.sm3() 17 | this.sm4() 18 | }, 19 | 20 | sm2() { 21 | let sm2 = sm.sm2 22 | let msgString = 'absasdagfadgadsfdfdsf' 23 | let cipherMode = 1 24 | 25 | let keypair = sm2.generateKeyPairHex() 26 | let publicKey = keypair.publicKey 27 | let privateKey = keypair.privateKey 28 | console.log('sm2 --> generate keypair', publicKey.length === 130 && privateKey.length === 64) 29 | 30 | let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) 31 | let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) 32 | console.log('sm2 --> encrypt and decrypt data', decryptData === msgString) 33 | }, 34 | 35 | sm3() { 36 | let sm3 = sm.sm3 37 | 38 | console.log('sm3 --> test1: ', sm3('abc') === '66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0') 39 | console.log('sm3 --> test2: ', sm3('abcdefghABCDEFGH12345678') === 'd670c7f027fd5f9f0c163f4bfe98f9003fe597d3f52dbab0885ec2ca8dd23e9b') 40 | console.log('sm3 --> test3: ', sm3('abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefgh') === '1cf3bafec325d7d9102cd67ba46b09195af4e613b6c2b898122363d810308b11') 41 | console.log('sm3 --> test4: ', sm3('abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCD') === 'b8ac4203969bde27434ce667b0adbf3439ee97e416e73cb96f4431f478a531fe') 42 | console.log('sm3 --> test5: ', sm3('abcdefghABCDEFGH12345678abcdefghABCDEFGH12345678abcdefghABCDEFGH') === '5ef0cdbe0d54426eea7f5c8b44385bb1003548735feaa59137c3dfe608aa9567') 43 | console.log('sm3 --> test6: ', sm3('abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd') === 'debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732') 44 | }, 45 | 46 | sm4() { 47 | let key = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10] 48 | let sm4 = sm.sm4 49 | 50 | console.log('sm4 --> encrypt a group: ', compareArray(sm4.encrypt([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10], key), [0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46])) 51 | 52 | console.log('sm4 --> decrypt a group: ', compareArray(sm4.decrypt([0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46], key), [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10])) 53 | 54 | }, 55 | }) 56 | -------------------------------------------------------------------------------- /tools/demo/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tools/demo/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 请看 console 2 | -------------------------------------------------------------------------------- /tools/demo/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .slide{ 2 | } 3 | .l { 4 | background-color: blue; 5 | height: 100rpx; 6 | width: 320rpx; 7 | } 8 | .r { 9 | height: 100rpx; 10 | width: 200rpx; 11 | display: flex; 12 | direction: row; 13 | } -------------------------------------------------------------------------------- /tools/demo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true, 12 | "nodeModules": true 13 | }, 14 | "compileType": "miniprogram", 15 | "libVersion": "2.2.3", 16 | "appid": "", 17 | "projectname": "miniprogram-sm-crypto", 18 | "isGameTourist": false, 19 | "condition": { 20 | "search": { 21 | "current": -1, 22 | "list": [] 23 | }, 24 | "conversation": { 25 | "current": -1, 26 | "list": [] 27 | }, 28 | "game": { 29 | "currentL": -1, 30 | "list": [] 31 | }, 32 | "miniprogram": { 33 | "current": -1, 34 | "list": [] 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tools/test/helper.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const jComponent = require('j-component') 4 | 5 | const config = require('../config') 6 | const _ = require('../utils') 7 | 8 | const srcPath = config.srcPath 9 | const componentMap = {} 10 | let nowLoad = null 11 | 12 | /** 13 | * register custom component 14 | */ 15 | global.Component = options => { 16 | const component = nowLoad 17 | const definition = Object.assign({ 18 | template: component.wxml, 19 | usingComponents: component.json.usingComponents, 20 | }, options) 21 | 22 | component.id = jComponent.register(definition) 23 | } 24 | 25 | /** 26 | * register behavior 27 | */ 28 | global.Behavior = options => jComponent.behavior(options) 29 | 30 | /** 31 | * register global components 32 | */ 33 | // eslint-disable-next-line semi-style 34 | ;[ 35 | 'view', 'scroll-view', 'swiper', 'movable-view', 'cover-view', 'cover-view', 36 | 'icon', 'text', 'rich-text', 'progress', 37 | 'button', 'checkbox', 'form', 'input', 'label', 'picker', 'picker', 'picker-view', 'radio', 'slider', 'switch', 'textarea', 38 | 'navigator', 'function-page-navigator', 39 | 'audio', 'image', 'video', 'camera', 'live-player', 'live-pusher', 40 | 'map', 41 | 'canvas', 42 | 'open-data', 'web-view', 'ad' 43 | ].forEach(name => { 44 | jComponent.register({ 45 | id: name, 46 | tagName: `wx-${name}`, 47 | template: '' 48 | }) 49 | }) 50 | 51 | /** 52 | * Touch polyfill 53 | */ 54 | class Touch { 55 | constructor(options = {}) { 56 | this.clientX = 0 57 | this.clientY = 0 58 | this.identifier = 0 59 | this.pageX = 0 60 | this.pageY = 0 61 | this.screenX = 0 62 | this.screenY = 0 63 | this.target = null 64 | 65 | Object.keys(options).forEach(key => { 66 | this[key] = options[key] 67 | }) 68 | } 69 | } 70 | global.Touch = window.Touch = Touch 71 | 72 | /** 73 | * load component 74 | */ 75 | async function load(componentPath) { 76 | const wholePath = path.join(srcPath, componentPath) 77 | 78 | const oldLoad = nowLoad 79 | const component = nowLoad = {} 80 | 81 | component.wxml = await _.readFile(`${wholePath}.wxml`) 82 | component.wxss = await _.readFile(`${wholePath}.wxss`) 83 | component.json = _.readJson(`${wholePath}.json`) 84 | 85 | if (!component.json) { 86 | throw new Error(`invalid component: ${wholePath}`) 87 | } 88 | 89 | // preload using components 90 | const usingComponents = component.json.usingComponents || {} 91 | const usingComponentKeys = Object.keys(usingComponents) 92 | for (let i = 0, len = usingComponentKeys.length; i < len; i++) { 93 | const key = usingComponentKeys[i] 94 | const usingPath = path.join(path.dirname(componentPath), usingComponents[key]) 95 | // eslint-disable-next-line no-await-in-loop 96 | const id = await load(usingPath) 97 | 98 | usingComponents[key] = id 99 | } 100 | 101 | // require js 102 | // eslint-disable-next-line import/no-dynamic-require 103 | require(wholePath) 104 | 105 | nowLoad = oldLoad 106 | componentMap[wholePath] = component 107 | 108 | return component.id 109 | } 110 | 111 | /** 112 | * render component 113 | */ 114 | function render(componentId, properties) { 115 | if (!componentId) throw new Error('you need to pass the componentId') 116 | 117 | return jComponent.create(componentId, properties) 118 | } 119 | 120 | /** 121 | * test a dom is similar to the html 122 | */ 123 | function match(dom, html) { 124 | if (!(dom instanceof window.Element) || !html || typeof html !== 'string') return false 125 | 126 | // remove some 127 | html = html.trim() 128 | .replace(/(>)[\n\r\s\t]+(<)/g, '$1$2') 129 | 130 | const a = dom.cloneNode() 131 | const b = dom.cloneNode() 132 | 133 | a.innerHTML = dom.innerHTML 134 | b.innerHTML = html 135 | 136 | return a.isEqualNode(b) 137 | } 138 | 139 | /** 140 | * wait for some time 141 | */ 142 | function sleep(time = 0) { 143 | return new Promise(resolve => { 144 | setTimeout(() => { 145 | resolve() 146 | }, time) 147 | }) 148 | } 149 | 150 | module.exports = { 151 | load, 152 | render, 153 | match, 154 | sleep, 155 | } 156 | -------------------------------------------------------------------------------- /tools/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | // eslint-disable-next-line no-unused-vars 5 | const colors = require('colors') 6 | const through = require('through2') 7 | 8 | /** 9 | * async function wrapper 10 | */ 11 | function wrap(func, scope) { 12 | return function (...args) { 13 | if (args.length) { 14 | const temp = args.pop() 15 | if (typeof temp !== 'function') { 16 | args.push(temp) 17 | } 18 | } 19 | 20 | return new Promise(function (resolve, reject) { 21 | args.push(function (err, data) { 22 | if (err) reject(err) 23 | else resolve(data) 24 | }) 25 | 26 | func.apply((scope || null), args) 27 | }) 28 | } 29 | } 30 | 31 | const accessSync = wrap(fs.access) 32 | const statSync = wrap(fs.stat) 33 | const renameSync = wrap(fs.rename) 34 | const mkdirSync = wrap(fs.mkdir) 35 | const readFileSync = wrap(fs.readFile) 36 | const writeFileSync = wrap(fs.writeFile) 37 | 38 | /** 39 | * transform path segment separator 40 | */ 41 | function transformPath(filePath, sep = '/') { 42 | return filePath.replace(/[\\/]/g, sep) 43 | } 44 | 45 | /** 46 | * check file exists 47 | */ 48 | async function checkFileExists(filePath) { 49 | try { 50 | await accessSync(filePath) 51 | return true 52 | } catch (err) { 53 | return false 54 | } 55 | } 56 | 57 | /** 58 | * create folder 59 | */ 60 | async function recursiveMkdir(dirPath) { 61 | const prevDirPath = path.dirname(dirPath) 62 | try { 63 | await accessSync(prevDirPath) 64 | } catch (err) { 65 | // prevDirPath is not exist 66 | await recursiveMkdir(prevDirPath) 67 | } 68 | 69 | try { 70 | await accessSync(dirPath) 71 | 72 | const stat = await statSync(dirPath) 73 | if (stat && !stat.isDirectory()) { 74 | // dirPath already exists but is not a directory 75 | await renameSync(dirPath, `${dirPath}.bak`) // rename to a file with the suffix ending in '.bak' 76 | await mkdirSync(dirPath) 77 | } 78 | } catch (err) { 79 | // dirPath is not exist 80 | await mkdirSync(dirPath) 81 | } 82 | } 83 | 84 | /** 85 | * read json 86 | */ 87 | function readJson(filePath) { 88 | try { 89 | // eslint-disable-next-line import/no-dynamic-require 90 | const content = require(filePath) 91 | delete require.cache[require.resolve(filePath)] 92 | return content 93 | } catch (err) { 94 | return null 95 | } 96 | } 97 | 98 | /** 99 | * read file 100 | */ 101 | async function readFile(filePath) { 102 | try { 103 | return await readFileSync(filePath, 'utf8') 104 | } catch (err) { 105 | // eslint-disable-next-line no-console 106 | return console.error(err) 107 | } 108 | } 109 | 110 | /** 111 | * write file 112 | */ 113 | async function writeFile(filePath, data) { 114 | try { 115 | await recursiveMkdir(path.dirname(filePath)) 116 | return await writeFileSync(filePath, data, 'utf8') 117 | } catch (err) { 118 | // eslint-disable-next-line no-console 119 | return console.error(err) 120 | } 121 | } 122 | 123 | /** 124 | * time format 125 | */ 126 | function format(time, reg) { 127 | const date = typeof time === 'string' ? new Date(time) : time 128 | const map = {} 129 | map.yyyy = date.getFullYear() 130 | map.yy = ('' + map.yyyy).substr(2) 131 | map.M = date.getMonth() + 1 132 | map.MM = (map.M < 10 ? '0' : '') + map.M 133 | map.d = date.getDate() 134 | map.dd = (map.d < 10 ? '0' : '') + map.d 135 | map.H = date.getHours() 136 | map.HH = (map.H < 10 ? '0' : '') + map.H 137 | map.m = date.getMinutes() 138 | map.mm = (map.m < 10 ? '0' : '') + map.m 139 | map.s = date.getSeconds() 140 | map.ss = (map.s < 10 ? '0' : '') + map.s 141 | 142 | return reg.replace(/\byyyy|yy|MM|M|dd|d|HH|H|mm|m|ss|s\b/g, $1 => map[$1]) 143 | } 144 | 145 | /** 146 | * logger plugin 147 | */ 148 | function logger(action = 'copy') { 149 | return through.obj(function (file, enc, cb) { 150 | const type = path.extname(file.path).slice(1).toLowerCase() 151 | 152 | // eslint-disable-next-line no-console 153 | console.log(`[${format(new Date(), 'yyyy-MM-dd HH:mm:ss').grey}] [${action.green} ${type.green}] ${'=>'.cyan} ${file.path}`) 154 | 155 | this.push(file) 156 | cb() 157 | }) 158 | } 159 | 160 | /** 161 | * compare arrays 162 | */ 163 | function compareArray(arr1, arr2) { 164 | if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false 165 | if (arr1.length !== arr2.length) return false 166 | 167 | for (let i = 0, len = arr1.length; i < len; i++) { 168 | if (arr1[i] !== arr2[i]) return false 169 | } 170 | 171 | return true 172 | } 173 | 174 | /** 175 | * merge two object 176 | */ 177 | function merge(obj1, obj2) { 178 | Object.keys(obj2).forEach(key => { 179 | if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) { 180 | obj1[key] = obj1[key].concat(obj2[key]) 181 | } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') { 182 | obj1[key] = Object.assign(obj1[key], obj2[key]) 183 | } else { 184 | obj1[key] = obj2[key] 185 | } 186 | }) 187 | 188 | return obj1 189 | } 190 | 191 | /** 192 | * get random id 193 | */ 194 | let seed = +new Date() 195 | function getId() { 196 | return ++seed 197 | } 198 | 199 | module.exports = { 200 | wrap, 201 | transformPath, 202 | 203 | checkFileExists, 204 | readJson, 205 | readFile, 206 | writeFile, 207 | 208 | logger, 209 | format, 210 | compareArray, 211 | merge, 212 | getId, 213 | } 214 | --------------------------------------------------------------------------------