This is the fourth day of my participation in Gwen Challenge

preface



Speaking of front-end VUE, it is really a very useful framework, and VUE is now the mainstream front-end development scheme of Web projects. Therefore, it is necessary to master the IMPLEMENTATION scheme of SM4 encryption based on VUE.

Domestic SM4 encryption and decryption algorithm concept introduction

SMS4 algorithm is an encryption algorithm used in WAPI wireless network standard widely used in China. It is a 32-round iterative non-balanced Feistel structure packet encryption algorithm, and its key length and packet length are 128. The algorithm used in SMS4 encryption and decryption is exactly the same, the only difference is that the decryption key of the algorithm is obtained by the reverse transformation of its encryption key. SMS4 block encryption algorithm is a block encryption algorithm used in China wireless standard. In 2012, it was confirmed as the National cryptography industry standard by The State Commercial Cryptography Administration. The standard number is GM/T 0002-2012 and it was renamed AS SM4 algorithm. SM3 cryptographic hashing algorithm, as a national cryptographic industry standard, plays an extremely important role in China’s cryptographic industry. SMS4 algorithm has 128bit packet length and 128bit key length. Both encryption and decryption algorithms adopt a 32-round non-balanced Feistel iterative structure, which first appears in the key extension algorithm of block cipher LOKI. SMS4 goes through 32 rounds of nonlinear iteration followed by an inverse transformation, so that the decryption algorithm can be consistent with the encryption algorithm as long as the decryption key is the reverse of the encryption key. The structure of SMS4 encryption and decryption algorithm is exactly the same, except that the decryption key is the reverse of the encryption key when the round key is used. S-box is a component of block cipher constructed by nonlinear transformation, which is designed to realize the obfuscation in block cipher. The s-box of SMS4 algorithm is designed in accordance with the design standards of European and American block ciphers. It adopts the affine function inverse mapping complex method which can resist the difference attack well.

Application scenario of SM4 encryption algorithm

SM4 is often used to encrypt data transmission in government systems, such as when we send parameters from the front end to the back end. Encrypts parameter data, decrypts the encrypted data in the background, and stores the encrypted data in the database to prevent data leakage during transmission. The scheme provided not only provides sm4 encryption and decryption, but also provides tamper-proof verification of the integrity of MD5 algorithm.

Vue encryption scheme implementation process

For our front-end VUE, where requests and responses are made through AXIos, there must be a request.js file in our Vue project that encapsulates request and respone. So let’s go through the sm4 utility class. All request requests are intercepted and encrypted. Md5 is used to encrypt parameters. As a tamper-proof representation of parameters, the parameters are stored in the request header for background verification. When we respond, we also intercept the response and parse the encrypted value of the response data. And use MD5 to re-encrypt, so that we get tamper-proof data.

Vue – based SM4 encryption and decryption tool

Let’s first introduce vUE encryption and decryption tool class. Data can be encrypted and decrypted directly by calling the method of the tool class, which can be realized in coordination with Request and Response. We post the following utility class code for you to use directly.

const 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],
];

const FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];
const 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 SM4_ENCRYPT = 1;
const SM4_DECRYPT = 0;

function sm4_context() {
  this.mode = 0;
  this.sk = [];
}


function GET_ULONG_BE(n, b, i) {
  return (b[i] << 24) | (b[i + 1] << 16) | (b[i + 2]) << 8 | (b[i + 3]);
}

function PUT_ULONG_BE(n, b, i) {
  b[i] = n >>> 24;
  b[i + 1] = n >>> 16;
  b[i + 2] = n >>> 8;
  b[i + 3] = n;
}

function ROTL(x, n) {
  const a = (x & 0xFFFFFFFF) << n;
  const b = x >>> (32 - n);

  return a | b;
}


function sm4Sbox(n) {
  const l = n >>> 4;
  const r = n % 16;
  return SboxTable[l][r];
}

function sm4Lt(ka) {
  let bb = 0;
  let c = 0;
  const a = new Uint8Array(4);
  const 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(bb, b, 0);

  c = bb ^ (ROTL(bb, 2)) ^ (ROTL(bb, 10)) ^ (ROTL(bb, 18)) ^ (ROTL(bb, 24));
  return c;
}

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

function sm4CalciRK(ka) {
  let bb = 0;
  let rk = 0;
  const a = new Uint8Array(4);
  const 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(bb, b, 0);

  rk = bb ^ (ROTL(bb, 13)) ^ (ROTL(bb, 23));

  return rk;
}

function sm4_setkey(SK, key) {
  const MK = new Array(4);
  const k = new Array(36);
  let i = 0;
  MK[0] = GET_ULONG_BE(MK[0], key, 0);
  MK[1] = GET_ULONG_BE(MK[1], key, 4);
  MK[2] = GET_ULONG_BE(MK[2], key, 8);
  MK[3] = GET_ULONG_BE(MK[3], 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 (; 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];
  }
}

function sm4_one_round(sk, input, output) {
  let i = 0;
  const ulbuf = new Array(36);

  ulbuf[0] = GET_ULONG_BE(ulbuf[0], input, 0);
  ulbuf[1] = GET_ULONG_BE(ulbuf[1], input, 4);
  ulbuf[2] = GET_ULONG_BE(ulbuf[2], input, 8);
  ulbuf[3] = GET_ULONG_BE(ulbuf[3], 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);
}

function sm4_setkey_enc(ctx, key) {
  ctx.mode = SM4_ENCRYPT;
  sm4_setkey(ctx.sk, key);
}

function sm4_setkey_dec(ctx, key) {
  let i; let j;
  ctx.mode = SM4_ENCRYPT;
  sm4_setkey(ctx.sk, key);
  for (i = 0; i < 16; i++) {
    j = ctx.sk[31 - i];
    ctx.sk[31 - i] = ctx.sk[i];
    ctx.sk[i] = j;
  }
}

function sm4_crypt_ecb(ctx, mode, length, input, output) {
  let index = 0;
  while (length > 0) {
    const oneInput = input.slice(index, index + 16);
    const oneOutput = new Uint8Array(16);
    sm4_one_round(ctx.sk, oneInput, oneOutput);

    for (let i = 0; i < 16; i++) {
      output[index + i] = oneOutput[i];
    }
    index += 16;
    length -= 16;
  }
}

function sm4_crypt_cbc(ctx, mode, length, iv, input, output) {
  let i;
  const temp = new Array(16);
  let index = 0;

  if (mode == SM4_ENCRYPT) {
    while (length > 0) {
      const oneInput = input.slice(index, index + 16);
      const oneOutput = new Array(16);
      for (i = 0; i < 16; i++) {
        oneOutput[i] = oneInput[i] ^ iv[i];
      }

      sm4_one_round(ctx.sk, oneOutput, oneOutput);

      for (i = 0; i < 16; i++) {
        iv[i] = oneOutput[i];
        output[index + i] = oneOutput[i];
      }

      index += 16;
      length -= 16;
    }
  } else /* SM4_DECRYPT */ {
    while (length > 0) {
      const oneInput = input.slice(index, index + 16);
      const oneOutput = new Array(16);
      index += 16;
      for (i = 0; i < 16; i++) {
        temp[i] = oneInput[i];
      }

      sm4_one_round(ctx.sk, oneInput, oneOutput);

      for (i = 0; i < 16; i++) {
        oneOutput[i] = oneOutput[i] ^ iv[i];
        output[index + i] = oneOutput[i];
      }

      for (i = 0; i < 16; i++) {
        iv[i] = temp[i];
      }

      index += 16;
      length -= 16;
    }
  }
}

function strfix(str, len) {
  let length = len - str.length;
  while (length-- > 0) {
    str = `0${str}`;
  }
  return str;
}

function HEXStrXOR(str1, str2) {
  const buf1 = hex2Array(str1);
  const buf2 = hex2Array(str2);

  let result = '';
  for (let i = 0; i < 16; i++) {
    result += strfix((buf1[i] ^ buf2[i]).toString(16).toUpperCase(), 2);
  }

  return result;
}

function hex2Array(str) {
  const len = str.length / 2;
  let substr = '';
  const result = new Array(len);
  for (let i = 0; i < len; i++) {
    substr = str.slice(2 * i, 2 * (i + 1));
    result[i] = parseInt(substr, 16) || 0;
  }
  return result;
}


const stringToByteArray = function (str) {
  const 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;
};

const hexStringToByteArray = function (str) {
  let pos = 0;
  let len = str.length;
  if (len % 2 !== 0) {
    return str;
  }
  len /= 2;
  const arrBytes = new Array();
  for (let i = 0; i < len; i++) {
    const s = str.substr(pos, 2);
    const v = parseInt(s, 16);
    arrBytes.push(v);
    pos += 2;
  }
  return arrBytes;
};
const byteArrayToHexString = function (arr) {
  let str = '';
  for (let i = 0; i < arr.length; i++) {
    let tmp = arr[i].toString(16);
    if (tmp.length == 1) {
      tmp = `0${tmp}`;
    }
    str += tmp;
  }
  return str;
};
const byteArrayToString = function (arr) {
  if (typeof arr === 'string') {
    return arr;
  }
  let str = '';
  const _arr = arr;
  for (let i = 0; i < _arr.length; i++) {
    const one = _arr[i].toString(2);
    const v = one.match(/^1+?(?=0)/);
    if (v && one.length == 8) {
      const 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;
};

function SM4CryptECBWithPKCS7Padding(data, sCryptFlag) {
  const szSM4Key = 'cc9368581322479ebf3e79348a2757d9';
  if (szSM4Key.length !== 32) {
    // console.log("传入密钥[" + szSM4Key + "]长度不为32位");
    return '';
  }
  let szData = null;
  if (sCryptFlag === SM4_ENCRYPT) { // 加密
    szData = stringToByteArray(data);
  } else { // 解密
    szData = hexStringToByteArray(data);
  }
  const len = szData.length;

  if (sCryptFlag === SM4_ENCRYPT) { // 加密,进行填充PKCS7Padding
    const p = 16 - len % 16;
    for (let i = 0; i < p; i++) {
      szData.push(p);
    }
  }

  const ctx = new sm4_context();
  const lpbKey = hex2Array(szSM4Key);
  if (sCryptFlag === SM4_ENCRYPT) {
    sm4_setkey_enc(ctx, lpbKey); // 加密
  } else {
    sm4_setkey_dec(ctx, lpbKey); // 解密
  }
  const pbyCryptResult = new Array(szData.length);
  sm4_crypt_ecb(ctx, sCryptFlag, szData.length, szData, pbyCryptResult);
  if (sCryptFlag === SM4_DECRYPT) { // 解密,去除填充PKCS7Padding
    const p = pbyCryptResult[pbyCryptResult.length - 1];
    for (let i = 0; i < p; i++) {
      pbyCryptResult.pop();
    }
  }
  if (sCryptFlag === SM4_ENCRYPT) { // 加密
    return byteArrayToHexString(pbyCryptResult);
  } // 解密
  return byteArrayToString(pbyCryptResult);
}

export function encrypt(inArray) {
  return SM4CryptECBWithPKCS7Padding(inArray, 1);
}

export function decrypt(inArray) {
  return SM4CryptECBWithPKCS7Padding(inArray, 0);
}
Copy the code

Above is our SM4 encryption and decryption algorithm. One of important methods is SM4CryptECBWithPKCS7Padding, here we are going to pass into two values, the first value is our encryption or decryption, the second is the logo is encryption or decryption. When it is 1, it is encryption, and when it is 0, it is decryption. The following describes the tamper-proof algorithm of MD5 encryption.

Md5 encryption algorithm

Md5 encryption we directly use the module crypto, for our VUE project, we will first install this module.

cnpm install crypto
Copy the code

Once the installation is complete, encrypt it where we need md5 encryption, in this case in request. Here is how to use MD5 encryption.

import crypto from 'crypto';

const md5Hash = crypto.createHash('md5');
const dataJson = JSON.stringify(data);
md5Hash.update(dataJson);
md5Data = md5Hash.digest('hex');
Copy the code

Here we have the MD5 value of the parameter. In the case of the backend, there is a big catch. If the front-end encryption mode starts with a 0, it will not be omitted. If the back-end encryption mode uses bigInteger, it will cause the 0 of the backend to be omitted. The MD5 values are different. Therefore, Integer is recommended for md5 verification at the back end.

public class Md5Utils { public static String getMD5String(String str) { try { MessageDigest md = MessageDigest.getInstance("md5"); md.update(str.getBytes()); byte s[] = md.digest(); String result = ""; for (int i = 0; i < s.length; i++) { result += Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6); } return result; } catch (Exception e) { e.printStackTrace(); return null; }}}Copy the code

Vue based on the actual case code

Configuration of global variables and urls to encrypt

Let’s define two variables like this. The global variable is used to determine whether to encrypt 1 for encryption and 0 for decryption. In encryptUrl, the key value is set to the URL to encrypt, the value is set to the property must encrypt the name property, and the sex property is not encrypted.

const isEncrypt = '1'; Const encryptUrl = {[/test/save '] : ['name'],}; // encryptUrl = {[/test/save '] : ['name'],};Copy the code

Function encapsulation for encryption and decryption

Now that we have the utility classes available, we can recursively encapsulate them.

function encryptValue(pObj, pParamKeys, pDecrypt) { if (pObj) { if (Array.isArray(pObj)) { let i = pObj.length; while (i--) { encryptValue(pObj[i], pParamKeys, pDecrypt); } } else if (typeof (pObj) === 'object') { if (pObj.constructor ! == RegExp) { for (const k in pObj) { if (pObj.hasOwnProperty(k)) { if (typeof (pObj[k]) === 'object') { encryptValue(pObj[k], pParamKeys, pDecrypt); } else if (pParamKeys.indexOf(k) ! = = 1 && pObj [k]) {if (pDecrypt = = = true) {/ / decryption pObj [k] = decrypt (pObj [k]); } else { pObj[k] = encrypt(pObj[k]); } } } } } } } }Copy the code

Request interception mode

We first intercept all requested request places for SM4 encryption and decryption. The code is as follows:

Axios. Interceptors. Request. Use ((config) = > {/ / encryption code -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / access to the current request all the values of const paramData = lodash.cloneDeep(config.data); Const urlAddr = config.url; let md5Data = ''; if (paramData && isEncrypt === '1') { let paramKeys = []; for (const key in encryptUrl) { if (encryptUrl.hasOwnProperty(key)) { if (urlAddr && urlAddr.startsWith(key)) { paramKeys = encryptUrl[key]; break; }}} // Get all the keys to be encrypted. if (paramKeys && paramKeys.length > 0) { let SM4cloneData = paramData; if (typeof (SM4cloneData) === 'string') { try { const dataStr = $.parseJSON(SM4cloneData); encryptValue(dataStr, paramKeys); SM4cloneData = JSON.stringify(dataStr); } catch (e) { console.log(e); } } else { encryptValue(SM4cloneData, paramKeys); } } const md5Hash = crypto.createHash('md5'); const dataJson = JSON.stringify(config.data); md5Hash.update(dataJson); md5Data = md5Hash.digest('hex'); config.data = paramData; } config.headers = { 'md5': md5Data, }; return config; });Copy the code

Response interception mode

For response, we need to decrypt the configured URL.

Axios. Interceptors. Response. Use ((response) = > {/ / decryption / / access to the current request all the values of const cloneData = lodash.cloneDeep(response.data); Const urlAddr = response.url; // paramData is encrypted. if (cloneData && isEncrypt === '1') { let paramKeys = []; for (const key in encryptUrl) { if (encryptUrl.hasOwnProperty(key)) { if (urlAddr && urlAddr.startsWith(key)) { paramKeys = encryptUrl[key]; break; }}} // Get all keys to decrypt. if (paramKeys && paramKeys.length > 0) { let SM4cloneData; if (Array.isArray(cloneData)) { SM4cloneData = cloneData.data; } else if (typeof (cloneData) === 'object') { SM4cloneData = cloneData; } if (typeof (SM4cloneData) === 'string') { try { const dataStr = $.parseJSON(SM4cloneData); encryptValue(dataStr, paramKeys, true); SM4cloneData = JSON.stringify(dataStr); } catch (e) { console.log(e); } } else { encryptValue(SM4cloneData, paramKeys, true); } const md5Hash = crypto.createHash('md5'); const dataJson = JSON.stringify(cloneData); md5Hash.update(dataJson); const md5Data = md5Hash.digest('hex'); If (the response headers. Md5. HasOwnProperty (" md5 ")) {if (md5Data. = = = the response headers. Md5) {alert (" md5 integrity checking error "); return; } } } return response.data; } return response.data; });Copy the code

conclusion

Support vUE – based domestic SM4 encryption and decryption scheme ends here. The article inevitably has the inadequacy place, hoped everybody criticizes correct.