├── .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 |
--------------------------------------------------------------------------------