angular实现国密算法sm2、sm3和sm4的ts版,基于sm-crypto库实现,前后端实现

ts版,js的话直接根据npm文档调用就可以了!

ts提供的方法有问题,所以还换了个思路来实现!而且因为不是nodeJs环境所以const sm4 = require('sm-crypto').sm4这个在ts里是报错的导致无法实现,如果是使用的是electron那么使用const sm4 = window.require('sm-crypto').sm4就可以来调用js里的方法了。

npm文档:sm-crypto

前提:方法入口出口都是通过ts的Buffer来传递的也就是字节数组Uint8Array,并且前端加解密后端可以对应加解密

导入库

# js库
npm install sm-crypto --save
#下载ts实现的一个文件index.d.ts
npm install @types/sm-crypto --save
index.d.ts文件内容如下:
遇到的问题1:sm4算法直接使用的话会遇到问题,前端加密后去后端解密解出来的不一致,它实现的时候目前猜测的是传入的参数options与后端不一致,因为不可以修改所以放弃,直接去调用底层js来实现。

在这里插入图片描述

这是js提供出来的方法,以及实现加解密的方法,所以我们可以看到options是能自己修改的,我们就来自定义实现。
module.exports = {
  encrypt(inArray, key, options) {
    return sm4(inArray, key, 1, options)
  },
  decrypt(inArray, key, options) {
    return sm4(inArray, key, 0, options)
  }
}
function sm4(inArray, key, cryptFlag, {padding = 'pkcs#5', mode, output = 'string'} = {}) {
  if (mode === CBC) {
    // @TODO,CBC 模式,默认走 ECB 模式
  }

  // 检查 key
  if (typeof key === 'string') key = hexToArray(key)
  if (key.length !== (128 / 8)) {
    // key 不是 128 比特
    throw new Error('key is invalid')
  }

  // 检查输入
  if (typeof inArray === 'string') {
    if (cryptFlag !== DECRYPT) {
      // 加密,输入为 utf8 串
      inArray = utf8ToArray(inArray)
    } else {
      // 解密,输入为 16 进制串
      inArray = hexToArray(inArray)
    }
  } else {
    inArray = [...inArray]
  }

  // 新增填充
  if (padding === 'pkcs#5' && cryptFlag !== DECRYPT) {
    const paddingCount = BLOCK - inArray.length % BLOCK
    for (let i = 0; i < paddingCount; i++) inArray.push(paddingCount)
  }

  // 生成轮密钥
  const roundKey = new Array(ROUND)
  sms4KeyExt(key, roundKey, cryptFlag)

  const outArray = []
  let restLen = inArray.length
  let point = 0
  while (restLen >= BLOCK) {
    const input = inArray.slice(point, point + 16)
    const output = new Array(16)
    sms4Crypt(input, output, roundKey)

    for (let i = 0; i < BLOCK; i++) {
      outArray[point + i] = output[i]
    }

    restLen -= BLOCK
    point += BLOCK
  }

  // 去除填充
  if (padding === 'pkcs#5' && cryptFlag === DECRYPT) {
    const paddingCount = outArray[outArray.length - 1]
    outArray.splice(outArray.length - paddingCount, paddingCount)
  }

  // 调整输出
  if (output !== 'array') {
    if (cryptFlag !== DECRYPT) {
      // 加密,输出转 16 进制串
      return ArrayToHex(outArray)
    } else {
      // 解密,输出转 utf8 串
      return arrayToUtf8(outArray)
    }
  } else {
    return outArray
  }
}

一:实现SM4

这里我是根据他js提供出来方法的思路来实现的,因为我对js也不熟悉,没怎么写过,一开始是打算用类似工具类的思路来实现,发现那样实现不能使用const sm4 = require('sm-crypto').sm4;,所以只能换思路了。
下面一定要用module.exports,一开始我用的exports会报错的。底层js里也是使用的module.exports,目前区别可以百度下。
options:{padding: ‘pkcs#5’, output: ‘array’} 我传递的这个padding和mode可以和后端来协调不一致可以修改,mode默认ECB,这里ECB和CBC无区别js给过滤了可以看源码,输出都是array,因为我需要的是字节数组,如果想要string也可以改成string,具体可以自己看下源码里面的实现,很简单!
const sm4 = require('sm-crypto').sm4;

function encodeBySM4(buf, key) {
  return sm4.encrypt(buf, key, {padding: 'pkcs#5', output: 'array'});
}

function decodeBySM4(buf, key) {
  return sm4.decrypt(buf, key, {padding: 'pkcs#5', output: 'array'});
}

module.exports = {
  encodeBySM4, decodeBySM4
};

接下来使用ts调用它

import {Buffer} from 'buffer';
//引入js
import * as sm4 from 'src/assets/lib/SM4Execport';
export class SM4Utils {
  /**
   * @param buf 字节数组
   * @param key key
   *
   * 传入字节数组和key,返回加密后的字节数组
   */
  public static encrypt(buf: Buffer, key: string): Buffer {
    return sm4.encodeBySM4(buf, key);
  }

  public static decrypt(buf: Buffer, key: string): Buffer {
    return sm4.decodeBySM4(buf, key);
  }
}

测试
<button style="background-color: yellow;" (click)="enTest()">加密解密</button>
  enTest() {
    let buffer = SM4Utils.encrypt(new Buffer([96,22]),"0123456789abcdeffedcba9876543210");
    console.log(buffer);
    let buffer1 = SM4Utils.decrypt(buffer,"0123456789abcdeffedcba9876543210");
    console.log(buffer1);
  }

在这里插入图片描述

二:实现SM2

这里SM2不管是ts还是js输入输出都是string,加密输入utf8的String,输出hex字符串,解密输入hex字符串,输出utf8字符串,这样的话就是只能加密字符流,如果是字节流的话就转换出错了,例如[22,33,44,55],对他转为utf8去加密,会转成3字节一个所以是两个字符,可能是两个乱码,也可能是一个中文一个乱码。解密后会转成6字节。所以在加密前加了一层base64或者加hex也可以。解密后再解一层base64。如果只对字符串操作直接加解密就可以。
这里面还有个带不带04的问题,后端的库里和前端的库有点区别,后面再说!
import {sm2} from 'sm-crypto';
import {Buffer} from "buffer";

export class SM2Utils {
  public static getKeysPairHex(){
    let keysPairHex = sm2.generateKeyPairHex();
    let publicKey = keysPairHex.publicKey;
    let privateKey = keysPairHex.privateKey;
    return {publicKey, privateKey};
  }

  //原始数据 ==> base64 ==> 对base字符串sm2加密后的buffer
  public static encrypt(buffer: Buffer,publicKey:string): Buffer {
    let hexStr = buffer.toString("base64");  //将buffer转成base进制
    return new Buffer("04"+sm2.doEncrypt(hexStr, publicKey, 1),'hex');//加密出来是hex,将它转为byte[]
  }

  //buffer ⇒  hexstr ⇒ base64 ⇒ 原始数据
  public static decrypt(buffer: Buffer,privateKey:string): Buffer {
    let strHex = buffer.toString('hex');
    strHex = strHex.substr(2);
    let str = sm2.doDecrypt(strHex, privateKey, 1);
    return new Buffer(str,'base64');
  }

}

04问题

后端加密出来的数据头部相比前端多了04,因为前端底层将04去掉了

在这里插入图片描述

这里进行substr了,后端加密出来的数据是130位,前端128位,比前端多了个04

在这里插入图片描述

前端sm2加解密分别处理了这个问题:
"04"+sm2.doEncrypt(hexStr, publicKey, 1)//加密后的hex加上04
strHex = strHex.substr(2);//解密钱去掉04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值