umi4+React18+TS 实现SM4加密

文章介绍了前端使用SM4加密算法来保护用户登录时的账号密码安全,通过在前端加密,然后与后端协商一致的解密方式,确保数据在传输过程中的安全性。提供了TS文件示例,包括加密和解密函数,以及在React或Vue项目中的使用方法。
摘要由CSDN通过智能技术生成

前言

在前端登录需要输入账号密码传到后端,但是我们不能直接明文传输,必须在前端进行加密,所以为了我们的用户账户安全,密码在从前端传输到后端的过程中,加密一下,我这边选用的加密算法是 SM4, 主要有两个原因,一是国产加密算法,二是这个国密算法是对称的,只要加密和解密的 key 和 vi 相同,可以很容易的解密,同时需要匹配 key 和 vi 又兼顾了安全。

我下面会提供前端的 SM4 加密 TS 文件,React或者VUE项目也可以使用,使用加密的方法也要跟后端沟通说明好用哪种加密,协调一致。可实现前端加密传输到后端解密,存到数据库,后端也可以解密传输到前端进行明文的显示。

使用步骤:

1.下载安装包插件

根据自身项目依赖下载使用

npm install --save js-base64
复制代码
pnpm add js-base64
复制代码
yarn add js-base64
复制代码

2.创建 SM4Util.ts 文件

加密源代码网上有很多,但是代码语法大都有些陈旧,使用的语法太旧导致新版本编译不通过(即使不使用 ESLint也不通过),所以我这个在他们的基础上做了修改。直接cv就好

/* eslint-disable no-param-reassign */
// 引入 js-base64 包
import { fromUint8Array, toUint8Array } from "js-base64";

interface SM4Context {
  mode: number;
  isPadding: boolean;
  sk: any;
}

export class SM4_Context implements SM4Context {
  public mode: number;
  public isPadding: boolean;
  public sk: any;

  public constructor(mode = 1, isPadding = true, sk: any = new Array(32)) {
    this.mode = mode;
    this.isPadding = isPadding;
    this.sk = sk;
  }
}

/*
 * 国密SM4加密算法
 * @date 2020-07-14
 */
let SboxTable = [
  0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05, 0x2b, 0x67, 0x9a,
  0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef,
  0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62, 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80,
  0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6, 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19,
  0xe6, 0x85, 0x4f, 0xa8, 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d,
  0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, 0xd4, 0x00,
  0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, 0xea, 0xbf, 0x8a, 0xd2, 0x40,
  0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55,
  0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23,
  0xab, 0x0d, 0x53, 0x4e, 0x6f, 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c,
  0x5b, 0x51, 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, 0x0a,
  0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, 0x89, 0x69, 0x97, 0x4a,
  0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84, 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d,
  0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,
];

let FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];

let CK = [
  0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 0xe0e7eef5,
  0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 0xc0c7ced5, 0xdce3eaf1,
  0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed,
  0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
];

const GET_ULONG_BE = function (b: any, i: number) {
  return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | (b[i + 3] & 0xff & 0xffffffff);
};

const PUT_ULONG_BE = function (n: any, b: any, i: number) {
  let t1 = 0xff & (n >> 24);
  let t2 = 0xff & (n >> 16);
  let t3 = 0xff & (n >> 8);
  let t4 = 0xff & n;
  b[i] = t1 > 128 ? t1 - 256 : t1;
  b[i + 1] = t2 > 128 ? t2 - 256 : t2;
  b[i + 2] = t3 > 128 ? t3 - 256 : t3;
  b[i + 3] = t4 > 128 ? t4 - 256 : t4;
};

const SHL = function (x: any, n: any) {
  return (x & 0xffffffff) << n;
};

const ROTL = function (x: any, n: any) {
  let s = SHL(x, n);
  let ss = x >> (32 - n);
  return SHL(x, n) | (x >> (32 - n));
};

const sm4Lt = function (ka: any) {
  let bb = 0;
  let c = 0;
  let a = new Array(4);
  let b = new Array(4);
  PUT_ULONG_BE(ka, a, 0);
  b[0] = sm4Sbox(a[0]);
  b[1] = sm4Sbox(a[1]);
  b[2] = sm4Sbox(a[2]);
  b[3] = sm4Sbox(a[3]);
  bb = GET_ULONG_BE(b, 0);
  c = bb ^ ROTL(bb, 2) ^ ROTL(bb, 10) ^ ROTL(bb, 18) ^ ROTL(bb, 24);
  return c;
};

const sm4F = function (x0: any, x1: any, x2: any, x3: any, rk: any) {
  return x0 ^ sm4Lt(x1 ^ x2 ^ x3 ^ rk);
};

const sm4CalciRK = function (ka: any) {
  let bb = 0;
  let rk = 0;
  let a = new Array(4);
  let b = new Array(4);
  PUT_ULONG_BE(ka, a, 0);
  b[0] = sm4Sbox(a[0]);
  b[1] = sm4Sbox(a[1]);
  b[2] = sm4Sbox(a[2]);
  b[3] = sm4Sbox(a[3]);
  bb = GET_ULONG_BE(b, 0);
  rk = bb ^ ROTL(bb, 13) ^ ROTL(bb, 23);
  return rk;
};

const sm4Sbox = function (inch: any) {
  let i = inch & 0xff;
  let retVal = SboxTable[i];
  return retVal > 128 ? retVal - 256 : retVal;
};

const sm4_setkey_enc = function (ctx: any, key: any) {
  if (ctx === null) {
    alert("ctx is null!");
    return false;
  }
  if (key === null || key.length < 16) {
    alert("key error!");
    return false;
  }
  ctx.mode = 1;
  sm4_setkey(ctx.sk, key);
};
// 生成解密密钥
const sm4_setkey_dec = function (ctx: any, key: any) {
  if (ctx === null) {
    Error("ctx is null!");
  }

  if (key === null || key.length !== 16) {
    Error("key error!");
  }

  let i = 0;
  ctx.mode = 0;
  sm4_setkey(ctx.sk, key);
  ctx.sk = ctx.sk.reverse();
};

const sm4_setkey = function (SK: any, key: any) {
  let MK = new Array(4);
  let k = new Array(36);
  // let i = 0;
  MK[0] = GET_ULONG_BE(key, 0);
  MK[1] = GET_ULONG_BE(key, 4);
  MK[2] = GET_ULONG_BE(key, 8);
  MK[3] = GET_ULONG_BE(key, 12);
  k[0] = MK[0] ^ FK[0];
  k[1] = MK[1] ^ FK[1];
  k[2] = MK[2] ^ FK[2];
  k[3] = MK[3] ^ FK[3];
  for (let i = 0; i < 32; i++) {
    k[i + 4] = k[i] ^ sm4CalciRK(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ CK[i]);
    SK[i] = k[i + 4];
  }
};
const padding = function (input: any, mode: any) {
  if (input === null) {
    return null;
  }
  let ret = null;
  if (mode === 1) {
    let p = parseInt((16 - (input.length % 16)).toString(), 10);
    ret = input.slice(0);
    for (let i = 0; i < p; i++) {
      ret[input.length + i] = p;
    }
  } else {
    let p = input[input.length - 1];
    ret = input.slice(0, input.length - p);
  }
  return ret;
};

const sm4_one_round = function (sk: any, input: any, output: any) {
  let i = 0;
  let ulbuf = new Array(36);
  ulbuf[0] = GET_ULONG_BE(input, 0);
  ulbuf[1] = GET_ULONG_BE(input, 4);
  ulbuf[2] = GET_ULONG_BE(input, 8);
  ulbuf[3] = GET_ULONG_BE(input, 12);
  while (i < 32) {
    ulbuf[i + 4] = sm4F(ulbuf[i], ulbuf[i + 1], ulbuf[i + 2], ulbuf[i + 3], sk[i]);
    i++;
  }
  PUT_ULONG_BE(ulbuf[35], output, 0);
  PUT_ULONG_BE(ulbuf[34], output, 4);
  PUT_ULONG_BE(ulbuf[33], output, 8);
  PUT_ULONG_BE(ulbuf[32], output, 12);
};

// ECB处理
const sm4_crypt_ecb = function (ctx: any, input: any) {
  if (input === null) {
    alert("input is null!");
  }
  if (ctx.isPadding && ctx.mode === 1) {
    // eslint-disable-next-line no-param-reassign
    input = padding(input, 1);
  }

  let i = 0;
  let length = input.length;
  let bous = new Array();
  for (; length > 0; length -= 16) {
    let out = new Array(16);
    let ins = input.slice(i * 16, 16 * (i + 1));
    sm4_one_round(ctx.sk, ins, out);
    bous = bous.concat(out);
    i++;
  }

  let output = bous;
  if (ctx.isPadding && ctx.mode === 0) {
    output = padding(output, 0);
  }
  for (let ki = 0; ki < output.length; ki++) {
    if (output[ki] < 0) {
      output[ki] = output[ki] + 256;
    }
  }
  return output;
};

// cbc处理
const sm4_crypt_cbc = function (ctx: any, iv: any, input: any) {
  if (iv === null || iv.length !== 16) {
    alert("iv error!");
  }

  if (input === null) {
    alert("input is null!");
  }

  if (ctx.isPadding && ctx.mode === 1) {
    input = padding(input, 1);
  }

  // let i = 0;
  let length = input.length;
  let bous = new Array();
  if (ctx.mode === 1) {
    let k = 0;
    for (; length > 0; length -= 16) {
      let out = new Array(16);
      let out1 = new Array(16);
      let ins = input.slice(k * 16, 16 * (k + 1));

      for (let i = 0; i < 16; i++) {
        out[i] = ins[i] ^ iv[i];
      }
      sm4_one_round(ctx.sk, out, out1);
      iv = out1.slice(0, 16);
      bous = bous.concat(out1);
      k++;
    }
  } else {
    let temp = [];
    let k = 0;
    for (; length > 0; length -= 16) {
      let out = new Array(16);
      let out1 = new Array(16);
      let ins = input.slice(k * 16, 16 * (k + 1));
      temp = ins.slice(0, 16);
      sm4_one_round(ctx.sk, ins, out);
      for (let i = 0; i < 16; i++) {
        out1[i] = out[i] ^ iv[i];
      }
      iv = temp.slice(0, 16);
      bous = bous.concat(out1);
      k++;
    }
  }

  let output = bous;
  if (ctx.isPadding && ctx.mode === 0) {
    output = padding(output, 0);
  }

  for (let i = 0; i < output.length; i++) {
    if (output[i] < 0) {
      output[i] = output[i] + 256;
    }
  }
  return output;
};

// 加密_ECB
const encryptData_ECB = function (plainText: any, secretKey: string) {
  try {
    let ctx = new SM4_Context(1, true);
    let keyBytes = stringToByte(secretKey);
    sm4_setkey_enc(ctx, keyBytes);
    let encrypted = sm4_crypt_ecb(ctx, stringToByte(plainText));
    let cipherText = fromUint8Array(new Uint8Array(encrypted));
    //   let cipherText = Buffer.from(encrypted).toString('base64');
    if (cipherText !== null && cipherText.trim().length > 0) {
      cipherText.replace(/(\s*|\t|\r|\n)/g, "");
    }
    return cipherText;
  } catch (e) {
    // console.error(e);
    return null;
  }
};
// 解密_ECB
const decryptData_ECB = function (cipherText: any, secretKey: string) {
  try {
    let ctx = new SM4_Context();
    ctx.isPadding = true;
    ctx.mode = 0;
    let keyBytes = stringToByte(secretKey);
    sm4_setkey_dec(ctx, keyBytes);
    let decrypted = sm4_crypt_ecb(ctx, toUint8Array(cipherText));
    return byteToString(decrypted);
  } catch (e) {
    // console.error(e);
    return null;
  }
};

// 将string转Byte
const stringToByte = function (str: any) {
  let bytes = new Array();
  let len;
  let c;
  len = str.length;
  for (let i = 0; i < len; i++) {
    c = str.charCodeAt(i);
    if (c >= 0x010000 && c <= 0x10ffff) {
      bytes.push(((c >> 18) & 0x07) | 0xf0);
      bytes.push(((c >> 12) & 0x3f) | 0x80);
      bytes.push(((c >> 6) & 0x3f) | 0x80);
      bytes.push((c & 0x3f) | 0x80);
    } else if (c >= 0x000800 && c <= 0x00ffff) {
      bytes.push(((c >> 12) & 0x0f) | 0xe0);
      bytes.push(((c >> 6) & 0x3f) | 0x80);
      bytes.push((c & 0x3f) | 0x80);
    } else if (c >= 0x000080 && c <= 0x0007ff) {
      bytes.push(((c >> 6) & 0x1f) | 0xc0);
      bytes.push((c & 0x3f) | 0x80);
    } else {
      bytes.push(c & 0xff);
    }
  }
  return bytes;
};

// Byte转string
const byteToString = function (arr: any) {
  if (typeof arr === "string") {
    return arr;
  }
  let str = "";
  let _arr = arr;
  for (let i = 0; i < _arr.length; i++) {
    let one = _arr[i].toString(2);
    let v = one.match(/^1+?(?=0)/);
    if (v && one.length === 8) {
      let bytesLength = v[0].length;
      let store = _arr[i].toString(2).slice(7 - bytesLength);
      for (let st = 1; st < bytesLength; st++) {
        store += _arr[st + i].toString(2).slice(2);
      }
      str += String.fromCharCode(parseInt(store, 2));
      i += bytesLength - 1;
    } else {
      str += String.fromCharCode(_arr[i]);
    }
  }
  return str;
};

export { encryptData_ECB, decryptData_ECB };

复制代码

3.在登录页面引入使用

页面使用 Ant Design 组件 如果你也是 刚好直接可以CV页面使用 哈哈哈


import {
  AlipayCircleOutlined,
  LockOutlined,
  MobileOutlined,
  TaobaoCircleOutlined,
  UserOutlined,
  WeiboCircleOutlined,
} from "@ant-design/icons";
import { LoginForm, ProFormCaptcha, ProFormText, ProConfigProvider } from "@ant-design/pro-components";
import { message, Space, Tabs } from "antd";
import type { CSSProperties } from "react";
import { useState } from "react";
import { history } from "umi";
import "./index.less";
import { request } from "umi";


// 登录接口与登录密钥接口引入
// 登录密钥是后端给的 接口根据各自项目不同 使用起来也会有些不同
import { loginApi, getSaltApi } from "@/global/api";

// 在前端使用ts进行登录加密,用sm4加密
import { encryptData_ECB, decryptData_ECB } from "@/utils/SM4Util";

// 获取登录密钥
const getApi = await getSaltApi();

export default () => {
  const [loginType, setLoginType] = useState<LoginType>("account");
  // 登录前的账号校验
  const rules = {
    username: [
      { required: true, message: "请填写用户名", trigger: "blur" },
      { min: 2, max: 16, message: "长度在 2 到 16 个字符", trigger: "blur" },
    ],
    password: [
      { required: true, message: "请填写密码", trigger: "blur" },
      { min: 6, max: 16, message: "长度在 6 到 16 个字符", trigger: "blur" },
    ],
  };
  // 声明变量
  const items = [
    { label: "账户密码登录", key: "account" },
    { label: "手机号登录", key: "phone" },
  ];
  // 只有通过校验之后才会触发这个方法
  const onSubmit = async (values: { username: any; password: any }) => {
    const { username, password } = values;
    // 在登录页面的表单中添加对密码的加密处理:
    const encryptedUsername = encryptData_ECB(username, getApi.data);
    const encryptPassword = encryptData_ECB(password, getApi.data);
    
    const data = {
      username: encryptedUsername,
      password: encryptPassword,
    };
    // 调登录接口
    await loginApi(data).then((res) => {
      if (res.code === 200) {
        message.success("登录成功");
        history.replace("/");
      } else {
      // 登录失败提示
        message.warning(res.message);
      }
    });
  };
  const onFocus = () => {
    // console.log("变化");
  };
  return (
    <div className="login-form">
      <ProConfigProvider hashed={false}>
        <div>
          <LoginForm
            logo="https://github.githubassets.com/images/modules/logos_page/Octocat.png"
            title="Github"
            subTitle="全球最大的代码托管平台"
            onFinish={onSubmit}
            onFocus={onFocus}
            // 自定义额外的登录功能
            actions={
              <Space>
                其他登录方式
                <AlipayCircleOutlined style={iconStyles} />
                <TaobaoCircleOutlined style={iconStyles} />
                <WeiboCircleOutlined style={iconStyles} />
              </Space>
            }
          >
            <Tabs
              centered
              activeKey={loginType}
              items={items}
              onChange={(activeKey) => setLoginType(activeKey as LoginType)}
            />
            {loginType === "account" && (
              <>
                <ProFormText
                  name="username"
                  rules={rules.username}
                  fieldProps={{
                    size: "large",
                    prefix: <UserOutlined className="prefixIcon" />,
                  }}
                  placeholder="用户名: admin or user"
                />
                <ProFormText.Password
                  name="password"
                  fieldProps={{
                    size: "large",
                    prefix: <LockOutlined className="prefixIcon" />,
                  }}
                  placeholder="密码: ant.design"
                  rules={rules.password}
                />
              </>
            )}
            {loginType === "phone" && (
              <>
                <ProFormText
                  fieldProps={{
                    size: "large",
                    prefix: <MobileOutlined className="prefixIcon" />,
                  }}
                  name="mobile"
                  placeholder="手机号"
                  rules={[
                    {
                      required: true,
                      message: "请输入手机号!",
                    },
                    {
                      pattern: /^1\d{10}$/,
                      message: "手机号格式错误!",
                    },
                  ]}
                />
                <ProFormCaptcha
                  fieldProps={{
                    size: "large",
                    prefix: <LockOutlined className="prefixIcon" />,
                  }}
                  captchaProps={{
                    size: "large",
                  }}
                  placeholder="请输入验证码"
                  captchaTextRender={(timing, count) => {
                    if (timing) {
                      return `${count} ${"获取验证码"}`;
                    }
                    return "获取验证码";
                  }}
                  name="captcha"
                  rules={[
                    {
                      required: true,
                      message: "请输入验证码!",
                    },
                  ]}
                  onGetCaptcha={async () => {
                    message.success("获取验证码成功!验证码为:1234");
                  }}
                />
              </>
            )}
          </LoginForm>
        </div>
      </ProConfigProvider>
    </div>
  );
};

复制代码

总结

这是一个总结,要是帮得到你,可以一键三连哟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值