前端加密·国密算法

本文讨论国密算法,只涉及简单介绍和使用,不研究算法实现。

国密

国密,即“国家商用密码”,主要包括以下算法:

  • GM/T 0001 ZUC 祖冲之序列密码算法(对应 GB/T 33133 标准)
  • GM/T 0002 SM4 分组密码算法(对应 GB/T 32907 标准)
  • GM/T 0003 SM2 椭圆曲线公钥密码算法(对应 GB/T 32918 标准)
  • GM/T 0004 SM3 密码杂凑算法(对应 GB/T 32905 标准)
  • GM/T 0044 SM9 标识密码算法(对应 GB/T 38635 标准)

它们由国密局和密标委制定发布,属于中华人民共和国密码行业标准。

  • 国家密码管理局,简称“国密局”,英文名称 State Cryptography Administration Office of Security Commercial Code Administration (OSCCA)
  • 密码行业标准化技术委员会,简称“密标委”,英文名称 Cryptography Standardization Technical Committee (CSTC)

GmSSL JavaScript API

GmSSL 是 OpenSSL 的一个分支项目,支持 SM2/SM3/SM4/SM9/ZUC 等国密算法,不仅实现了类似 OpenSSL 的算法库和命令行工具,还内置了 JS 实现,见 https://github.com/guanzhi/GmSSL/tree/master/js。JS API 比较 low-level,没有封装为 ES 或 CommonJS 模块。

为了方便下面只演示 sm-cryptogm-crypto 的使用(互相验证算法正确性),生产环境推荐使用 API 设计更为良好的 gm-crypto

sm-crypto

yarn add sm-crypto

API:

  • sm2
    • sm2.generateKeyPairHex() 生成密钥对
    • sm2.doEncrypt() 加密
    • sm2.doDecrypt() 解密
    • sm2.doSignature() 签名
    • sm2.doVerifySignature() 验签
    • sm2.getPoint() 获取椭圆曲线点
  • sm3
    • sm3() 杂凑
  • sm4
    • sm4.encrypt() 加密
    • sm4.decrypt() 解密

详细文档见 https://www.npmjs.com/package/sm-crypto

gm-crypto

yarn add gm-crypto

API:

  • SM2
    • SM2.generateKeyPair() 生成密钥对
    • SM2.encrypt(data, key[, options]) 加密
    • SM2.decrypt(data, key[, options]) 解密
  • SM3
    • SM3.digest(data[, inputEncoding][, outputEncoding]) 摘要
  • SM4
    • SM4.encrypt(data, key[, options]) 加密
    • SM4.decrypt(data, key[, options]) 解密

详细文档见 https://www.npmjs.com/package/gm-crypto

SM2

SM2 是非对称加密算法,在加解密前需要先生成公钥和私钥,可以使用 GmSSL 提供的命令行工具,或者通过 JS 库。

使用 sm-crypto 生成密钥:

const { writeFile } = require('fs');
const util = require('util');
const { sm2 } = require('sm-crypto');
const writeFileAsync = util.promisify(writeFile);

async function gensm2() {
  const { publicKey, privateKey } = sm2.generateKeyPairHex();
  await writeFileAsync(`sm2pk.key`, publicKey, 'utf8');
  await writeFileAsync(`sm2sk.key`, privateKey, 'utf8');
  console.log(`publicKey: ${publicKey}`);
  console.log(`privateKey: ${privateKey}`);
}

gensm2();

或者 gm-crypto

const { writeFile } = require('fs');
const util = require('util');
const { SM2 } = require('gm-crypto');
const writeFileAsync = util.promisify(writeFile);

async function gensm2() {
  const { publicKey, privateKey } = SM2.generateKeyPair();
  await writeFileAsync(`sm2pk.key`, publicKey, 'utf8');
  await writeFileAsync(`sm2sk.key`, privateKey, 'utf8');
  console.log(`publicKey: ${publicKey}`);
  console.log(`privateKey: ${privateKey}`);
}

gensm2();

两者的 API 基本一致。

使用 sm-crypto 进行 SM2 加解密:

const { sm2 } = require('sm-crypto');
const publicKey = `047a0c3901d445b5b21a498a2ec148b3aedff9bad42ef2228ee8afd6a389ab92a083cd5dae47381be4e4ff5e26d0eacd3aa716ba137a7dcfac27795b319abf270f`;
const privateKey = `84aa47c525a596264f42e1c6785978f1cc62f8fe6b02768f3f8467b8c840d0c7`;
const plaintext = `keqingrong@outlook.com`;
/* 拼接模式 */
const mode = {
  C1C3C2: 1,
  C1C2C3: 0
};

const encrypted = sm2.doEncrypt(plaintext, publicKey, mode.C1C3C2); // 加密结果为 16进制格式字符串
const decrypted = sm2.doDecrypt(encrypted, privateKey, mode.C1C3C2);
console.log(decrypted === plaintext); // true

使用 gm-crypto 进行 SM2 加解密:

const { SM2 } = require('gm-crypto');
const publicKey = `047a0c3901d445b5b21a498a2ec148b3aedff9bad42ef2228ee8afd6a389ab92a083cd5dae47381be4e4ff5e26d0eacd3aa716ba137a7dcfac27795b319abf270f`;
const privateKey = `84aa47c525a596264f42e1c6785978f1cc62f8fe6b02768f3f8467b8c840d0c7`;
const plaintext = `keqingrong@outlook.com`;

const encrypted = SM2.encrypt(plaintext, publicKey, {
  mode: SM2.constants.C1C3C2,
  inputEncoding: 'utf8',
  outputEncoding: 'hex' // 支持 hex/base64 等格式
});
const decrypted = SM2.decrypt(encrypted, privateKey, {
  mode: SM2.constants.C1C3C2,
  inputEncoding: 'hex', // 支持 hex/base64 等格式
  outputEncoding: 'utf8'
});
console.log(decrypted === plaintext); // true

SM3

SM3 是消息摘要算法,国家标准中称为“杂凑算法”,其实就是 Hash 算法。

const { sm3 } = require('sm-crypto');
const { SM3 } = require('gm-crypto');

console.log(sm3('keqingrong@outlook.com')); // a78144537f946f03c7eb6164a66a09dddbeddc64481a4e962639dd2c388c3b94
console.log(SM3.digest('keqingrong@outlook.com', 'utf8', 'hex')); // a78144537f946f03c7eb6164a66a09dddbeddc64481a4e962639dd2c388c3b94

SM4

SM4 是对称加密算法,加解密使用同一个密钥。

使用 sm-crypto 进行 SM4 加解密:

const { sm4 } = require('sm-crypto');
/* 密钥,32位十六进制数字 */
const key = '0123456789abcdeffedcba9876543210';
const plaintext = `keqingrong@outlook.com`;

// 默认 ECB 模式
const encrypted = sm4.encrypt(plaintext, key);
const decrypted = sm4.decrypt(encrypted, key);
console.log(encrypted); // 19cfe4742a6b961882f7d7ed1ae29429eafc43c785acaf6ba7a53f49af084308
console.log(decrypted === plaintext); // true

// CBC 模式
/* 初始化向量,32位十六进制数字 */
const iv = '0123456789abcdeffedcba9876543210';
const encrypted2 = sm4.encrypt(plaintext, key, {
  iv,
  mode: 'cbc'
});
const decrypted2 = sm4.decrypt(encrypted2, key, {
  iv,
  mode: 'cbc'
});
console.log(encrypted2); // dfca4853b818858ab3dc0ac6875baf41c9cf963c37ad36549611c5c3013410aa
console.log(decrypted2 === plaintext); // true

使用 gm-crypto 进行 SM4 加解密:

const { SM4 } = require('gm-crypto');
/* 密钥,32位十六进制数字 */
const key = '0123456789abcdeffedcba9876543210';
const plaintext = `keqingrong@outlook.com`;

// ECB 模式
const encrypted = SM4.encrypt(plaintext, key, {
  mode: SM4.constants.ECB,
  inputEncoding: 'utf8',
  outputEncoding: 'hex', // 支持 hex/base64 等格式
});
const decrypted = SM4.decrypt(encrypted, key, {
  mode: SM4.constants.ECB,
  inputEncoding: 'hex', // 支持 hex/base64 等格式
  outputEncoding: 'utf8',
});
console.log(encrypted); // 19cfe4742a6b961882f7d7ed1ae29429eafc43c785acaf6ba7a53f49af084308
console.log(decrypted === plaintext); // true

// CBC 模式
const iv = '0123456789abcdeffedcba9876543210';
const encrypted2 = SM4.encrypt(plaintext, key, {
  iv,
  mode: SM4.constants.CBC,
  inputEncoding: 'utf8',
  outputEncoding: 'hex',
});
const decrypted2 = SM4.decrypt(encrypted2, key, {
  iv,
  mode: SM4.constants.CBC,
  inputEncoding: 'hex',
  outputEncoding: 'utf8',
});
console.log(encrypted2); // dfca4853b818858ab3dc0ac6875baf41c9cf963c37ad36549611c5c3013410aa
console.log(decrypted2 === plaintext); // true

相关链接