需求
需求很简单,如下图所示,要求用sm4加密,最后输出要是base64加密过的。
sm-crypto
sm-crypto是国密算法sm2、sm3和sm4的js实现。用法非常简单。 sm4的加密用法如下:
const sm4 = require('sm-crypto').sm4
const msg = 'hello world! 我是 juneandgreen.' // 可以为 utf8 串或字节数组
const key = '0123456789abcdeffedcba9876543210' // 可以为 16 进制串或字节数组,要求为 128 比特
let encryptData = sm4.encrypt(msg, key) // 加密,默认输出 16 进制字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充)
let encryptData = sm4.encrypt(msg, key, {padding: 'none'}) // 加密,不使用 padding
let encryptData = sm4.encrypt(msg, key, {padding: 'none', output: 'array'}) // 加密,不使用 padding,输出为字节数组
let encryptData = sm4.encrypt(msg, key, {mode: 'cbc', iv: 'fedcba98765432100123456789abcdef'}) // 加密,cbc 模式
用法非常简单。sm4运算模式默认是“ecb”,填充模式默认是“pkcs7”,所以这里都不需要配置。需要做的是直接直接转化为base64即可。
js-base64
js-base64也是一个非常简单且好用的base64加解密的npm包。 加密用法如下:
let latin = 'dankogai';
let utf8 = '小飼弾'
let u8s = new Uint8Array([100,97,110,107,111,103,97,105]);
Base64.encode(latin); // ZGFua29nYWk=
Base64.encode(latin, true); // ZGFua29nYWk skips padding
Base64.encodeURI(latin); // ZGFua29nYWk
Base64.btoa(latin); // ZGFua29nYWk=
Base64.btoa(utf8); // raises exception
Base64.fromUint8Array(u8s); // ZGFua29nYWk=
Base64.fromUint8Array(u8s, true); // ZGFua29nYW which is URI safe
Base64.encode(utf8); // 5bCP6aO85by+
Base64.encode(utf8, true) // 5bCP6aO85by-
Base64.encodeURI(utf8); // 5bCP6aO85by-
我们最经常用的就是直接Base64.encode('string类型'),通常是utf-8编码的。
sm-crypto和js-base64结合实现需求
如果我们使用默认的加密方式sm4.encrypt(msg, key)
那得到的就是一个16进制串,再用js-base64加密的时候就按照了utf8格式的进行加密了,所以,我们需要sm4输出的是字节数组,然后js-base64加密的也是字节数组。
代码如下:
const result = sm4.encrypt(value, publicKey, { output: 'array' });
const base64Res = Base64.fromUint8Array(new Uint8Array(result), false);
补充
ArrayBuffer,二进制数组
基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用。
ArrayBuffer 与 Array 没有任何共同之处:它的长度是固定的,我们无法增加或减少它的长度。
Uint8Array —— Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。
Uint16Array —— 表示 16 位无符号整数,按平台字节顺序排列。如果需要控制字节顺序,请使用 DataView 代替。内容被初始化为 0。建立后,就可以使用对象的方法或使用标准数组索引语法(即使用括号表示法)引用数组中的元素。
Uint32Array —— 表示一个由基于平台字节序的 32 位无符号字节组成的数组。如果需要对字节顺序进行控制,请使用 DataView 代替。数组中每个元素的初始值都是0。一旦创建,你可以用对象的方法引用数组里的元素,或者使用标准的数组索引语法(即,使用中括号)。
16进制串和字节数组的相互转换
16进制串转字节数组
function hexToArray(str) {
const arr = []
for (let i = 0, len = str.length; i < len; i += 2) {
arr.push(parseInt(str.substr(i, 2), 16))
}
return arr
}
字节数组转16进制串
function ArrayToHex(arr) {
return arr.map(item => {
item = item.toString(16)
return item.length === 1 ? '0' + item : item
}).join('')
}
utf8串和字节数组的相互转换
utf8串转字节数组
function utf8ToArray(str) {
const arr = []
for (let i = 0, len = str.length; i < len; i++) {
const point = str.codePointAt(i);
if (point <= 0x007f) {
arr.push(point);
} else if (point <= 0x07ff) {
arr.push(0xc0 | (point >>> 6)); // 110yyyyy(0xc0-0xdf)
arr.push(0x80 | (point & 0x3f)); // 10zzzzzz(0x80-0xbf)
} else if (point <= 0xD7FF || (point >= 0xE000 && point <= 0xFFFF)) {
arr.push(0xe0 | (point >>> 12)); // 1110xxxx(0xe0-0xef)
arr.push(0x80 | ((point >>> 6) & 0x3f)); // 10yyyyyy(0x80-0xbf)
arr.push(0x80 | (point & 0x3f)); // 10zzzzzz(0x80-0xbf)
} else if (point >= 0x010000 && point <= 0x10FFFF) {
i++;
arr.push((0xf0 | (point >>> 18) & 0x1c)); // 11110www(0xf0-0xf7)
arr.push((0x80 | ((point >>> 12) & 0x3f))); // 10xxxxxx(0x80-0xbf)
arr.push((0x80 | ((point >>> 6) & 0x3f))); // 10yyyyyy(0x80-0xbf)
arr.push((0x80 | (point & 0x3f))); // 10zzzzzz(0x80-0xbf)
} else {
arr.push(point);
throw new Error('input is not supported');
}
}
return arr;
}
字节数组转utf8串
function arrayToUtf8(arr) {
const str = [];
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i] >= 0xf0 && arr[i] <= 0xf7) {
str.push(String.fromCodePoint(((arr[i] & 0x07) << 18) + ((arr[i + 1] & 0x3f) << 12) + ((arr[i + 2] & 0x3f) << 6) + (arr[i + 3] & 0x3f)));
i += 3;
} else if (arr[i] >= 0xe0 && arr[i] <= 0xef) {
str.push(String.fromCodePoint(((arr[i] & 0x0f) << 12) + ((arr[i + 1] & 0x3f) << 6) + (arr[i + 2] & 0x3f)));
i += 2;
} else if (arr[i] >= 0xc0 && arr[i] <= 0xdf) {
str.push(String.fromCodePoint(((arr[i] & 0x1f) << 6) + (arr[i + 1] & 0x3f)));
i++;
} else {
str.push(String.fromCodePoint(arr[i]));
}
}
return str.join('');
}