Skip to content

Instantly share code, notes, and snippets.

@emmansun
Last active March 14, 2024 03:02
Show Gist options
  • Save emmansun/2eb37257cfe6ed561d1668f720f51030 to your computer and use it in GitHub Desktop.
Save emmansun/2eb37257cfe6ed561d1668f720f51030 to your computer and use it in GitHub Desktop.
目前nodejs尚未提供sm4-gcm,这里借用nodejs的sm4-ecb和sjcl的gcm实现,来实现sm4-gcm。当然这个只是临时方案,最终还是要用nodejs(openssl)实现的sm4-gcm。
const crypto = require('crypto');
const sjcl = require('sjcl');
const Sm4Cipher = function (key) {
this.cipher = crypto.createCipheriv('sm4-ecb', key, null);
};
Sm4Cipher.bufferFromBits = function (arr, padding, paddingCount) {
padding = padding === undefined ? true : padding;
paddingCount = paddingCount || 8;
if (arr.length === 0) {
return new ArrayBuffer(0);
}
let ol = sjcl.bitArray.bitLength(arr) / 8;
// check to make sure the bitLength is divisible by 8, if it isn't
// we can't do anything since arraybuffers work with bytes, not bits
if (sjcl.bitArray.bitLength(arr) % 8 !== 0) {
throw new Error('Invalid bit size, must be divisble by 8 to fit in an arraybuffer correctly');
}
if (padding && ol % paddingCount !== 0) {
ol += paddingCount - (ol % paddingCount);
}
// padded temp for easy copying
const tmp = new DataView(new ArrayBuffer(arr.length * 4));
for (let i = 0; i < arr.length; i++) {
tmp.setUint32(i * 4, arr[i] << 32); // get rid of the higher bits
}
// now copy the final message if we are not going to 0 pad
const out = new DataView(new ArrayBuffer(ol));
// save a step when the tmp and out bytelength are ===
if (out.byteLength === tmp.byteLength) {
return Buffer.from(tmp.buffer);
}
const smallest = tmp.byteLength < out.byteLength ? tmp.byteLength : out.byteLength;
for (let i = 0; i < smallest; i++) {
out.setUint8(i, tmp.getUint8(i));
}
return Buffer.from(out.buffer);
};
Sm4Cipher.bufferToBits = function (buffer) {
const out = [];
if (buffer.byteLength === 0) {
return [];
}
const inView = new DataView(buffer);
const len = inView.byteLength - (inView.byteLength % 4);
for (let i = 0; i < len; i += 4) {
out.push(inView.getUint32(i));
}
if (inView.byteLength % 4 !== 0) {
const tmp = new DataView(new ArrayBuffer(4));
for (let i = 0, l = inView.byteLength % 4; i < l; i++) {
// we want the data to the right, because partial slices off the starting bits
tmp.setUint8(i + 4 - l, inView.getUint8(len + i)); // big-endian,
}
out.push(sjcl.bitArray.partial((inView.byteLength % 4) * 8, tmp.getUint32(0)));
}
return out;
};
Sm4Cipher.prototype.encrypt = function (data) {
const enc = this.cipher.update(Sm4Cipher.bufferFromBits(data));
return Sm4Cipher.bufferToBits(enc.buffer);
};
function testSm4GcmHex(key, nonce, plaintext, ad, result) {
const got = sjcl.mode.gcm.encrypt(
new Sm4Cipher(Buffer.from(key, 'hex')),
sjcl.codec.hex.toBits(plaintext),
sjcl.codec.hex.toBits(nonce),
ad ? sjcl.codec.hex.toBits(ad) : undefined,
128
);
if (sjcl.codec.hex.fromBits(got) !== result) {
console.error(`encrypt failed! expected ${result}, got ${sjcl.codec.hex.fromBits(got)}`);
}
const gotPlaintext = sjcl.mode.gcm.decrypt(
new Sm4Cipher(Buffer.from(key, 'hex')),
got,
sjcl.codec.hex.toBits(nonce),
ad ? sjcl.codec.hex.toBits(ad) : undefined,
128
);
if (sjcl.codec.hex.fromBits(gotPlaintext) !== plaintext) {
console.error(`decrypt failed! expected ${plaintext}, got ${sjcl.codec.hex.fromBits(gotPlaintext)}`);
}
}
console.log(JSON.stringify(crypto.getCiphers()));
console.log(crypto.getCipherInfo('sm4-ecb'));
testSm4GcmHex(
'00000000000000000000000000000000',
'000000000000000000000000',
'00000000000000000000000000000000',
undefined,
'7de2aa7f1110188218063be1bfeb6d89b851b5f39493752be508f1bb4482c557'
);
testSm4GcmHex(
'7fddb57453c241d03efbed3ac44e371c',
'ee283a3fc75575e33efd4887',
'd5de42b461646c255c87bd2962d3b9a2',
undefined,
'15e29a2a64bfc2974286e0cb84cfc7fa6c5ed60f77e0832fbbd81f07958f3934'
);
testSm4GcmHex(
'fe47fcce5fc32665d2ae399e4eec72ba',
'5adb9609dbaeb58cbd6e7275',
'7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1b840382c4bccaf3bafb4ca8429bea063',
'88319d6e1d3ffa5f987199166c8a9b56c2aeba5a',
'2276da0e9a4ccaa2a5934c96ba1dc6b0a52b3430ca011b4db4bf6e298b3a58425402952806350fdda7ac20bc38838d7124ee7c333e395b9a94c508b6bf0ce6b2d10d61'
);
testSm4GcmHex(
'ec0c2ba17aa95cd6afffe949da9cc3a8',
'296bce5b50b7d66096d627ef',
'b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987b764b9611f6c0f8641843d5d58f3a242',
'f8d00f05d22bf68599bcdeb131292ad6e2df5d14',
'3175cd3cb772af34490e4f5203b6a5743cd9b3798c387b7bda2708ff82d520c35d3022767b2d0fe4addff59fb25ead69ca3dd4d73ce1b4cb53a7c4cdc6a4c1fb06c316'
);
testSm4GcmHex(
'2c1f21cf0f6fb3661943155c3e3d8492',
'23cb5ff362e22426984d1907',
'42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d68b5615ba7c1220ff6510e259f06655d8',
'5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f4488f33cfb5e979e42b6e1cfc0a60238982a7aec',
'9db299bb7f9d6914c4a13589cf41ab014445e4914c1571745d50508bf0f6adeaa41aa4b081a444ee82fed6769da92f5e727d004b21791f961e212a69bfe80af14e7adf'
);
testSm4GcmHex(
'9a4fea86a621a91ab371e492457796c0',
'75',
'ca6131faf0ff210e4e693d6c31c109fc5b6f54224eb120f37de31dc59ec669b6',
'4f6e2585c161f05a9ae1f2f894e9f0ab52b45d0f',
'b86d6055e7e07a664801ccce38172bf7d91dc20babf2c0662d635cc9111ffefb308ee64ce01afe544b6ee1a65b803cb9'
);
// https://tools.ietf.org/html/rfc8998 A.1. SM4-GCM Test Vectors
testSm4GcmHex(
'0123456789abcdeffedcba9876543210',
'00001234567800000000abcd',
'aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccddddddddddddddddeeeeeeeeeeeeeeeeffffffffffffffffeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaa',
'feedfacedeadbeeffeedfacedeadbeefabaddad2',
'17f399f08c67d5ee19d0dc9969c4bb7d5fd46fd3756489069157b282bb200735d82710ca5c22f0ccfa7cbf93d496ac15a56834cbcf98c397b4024a2691233b8d83de3541e4c2b58177e065a9bf7b62ec'
);
@emmansun
Copy link
Author

这里SM4的BlockMode实现借助nodejs的SM4-ECB,需要注意的是,update的outputEncoding不能选择base64,否则需要调用final才能得到完整内容,但是一旦调用final,这个cipher就不能再用了。以下方法都能用,根据内存占用选用。

Sm4Cipher.prototype.encrypt = function (data) {
  const enc = this.cipher.update(sjcl.codec.base64.fromBits(data), 'base64');
  return this.toBits(enc.buffer);
};

Sm4Cipher.prototype.encrypt = function (data) {
  const enc = this.cipher.update(sjcl.codec.base64.fromBits(data), 'base64', 'hex');
  return sjcl.codec.hex.toBits(enc);
};

Sm4Cipher.prototype.encrypt = function (data) {
  const enc = this.cipher.update(sjcl.codec.base64.fromBits(data), 'base64');
  return sjcl.codec.base64.toBits(enc.toString('base64'));
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment