318 lines
10 KiB
JavaScript
318 lines
10 KiB
JavaScript
|
|
/**
|
|||
|
|
* 功能:加解密工具类第三方系统及对外接口专用版
|
|||
|
|
* 说明:使用国密SM4算法
|
|||
|
|
* JavaScript版本实现
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const sm4 = require('sm-crypto').sm4;
|
|||
|
|
const crypto = require('crypto');
|
|||
|
|
|
|||
|
|
// 密文前缀常量
|
|||
|
|
const CIPHERTEXT_PREFIX_THIRD_AES128 = "|$|";
|
|||
|
|
const CIPHERTEXT_PREFIX_THIRD_AES128_REGEX = /\|\$\|/;
|
|||
|
|
const CIPHERTEXT_PREFIX_THIRD_SM4ECB = "|$4|";
|
|||
|
|
const CIPHERTEXT_PREFIX_THIRD_SM4ECB_REGEX = /\|\$4\|/;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 判断字符串是否为空
|
|||
|
|
* @param {string} str - 待判断的字符串
|
|||
|
|
* @returns {boolean} 是否为空
|
|||
|
|
*/
|
|||
|
|
function isBlank(str) {
|
|||
|
|
return !str || str.length === 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将字节数组转换为Base64字符串
|
|||
|
|
* @param {Buffer} binaryData - 字节数组
|
|||
|
|
* @returns {string} Base64字符串
|
|||
|
|
*/
|
|||
|
|
function base64StringFromByteArray(binaryData) {
|
|||
|
|
try {
|
|||
|
|
const base64String = binaryData.toString('base64');
|
|||
|
|
return base64String
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - base64StringFromByteArray() | binaryData :', binaryData);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将Base64字符串转换为字节数组
|
|||
|
|
* @param {string} base64String - Base64字符串
|
|||
|
|
* @returns {Buffer} 字节数组
|
|||
|
|
*/
|
|||
|
|
function byteArrayFromBase64String(base64String) {
|
|||
|
|
try {
|
|||
|
|
return Buffer.from(base64String, 'base64');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - byteArrayFromBase64String() | base64String :', base64String);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* SM4 ECB模式加密(带PKCS5Padding)
|
|||
|
|
* @param {Buffer} content - 待加密内容
|
|||
|
|
* @param {Buffer} key - SM4密钥(16字节)
|
|||
|
|
* @returns {Buffer} 加密后的字节数组
|
|||
|
|
*/
|
|||
|
|
function encryptECBPadding(content, key) {
|
|||
|
|
try {
|
|||
|
|
// sm-crypto 的 sm4.encrypt 使用 ECB 模式,自动处理 PKCS5Padding
|
|||
|
|
// 密钥需要转换为十六进制字符串,内容需要转换为UTF-8字符串
|
|||
|
|
const keyHex = key.toString('hex');
|
|||
|
|
const contentStr = content.toString('utf8');
|
|||
|
|
// sm4.encrypt(data, key, mode, outputEncoding)
|
|||
|
|
// mode: 1=ECB, 2=CBC; outputEncoding: 'base64' 或 'hex',默认为 'base64'
|
|||
|
|
// 第一个参数应该是UTF-8字符串,而不是hex字符串
|
|||
|
|
const encryptedHex = sm4.encrypt(contentStr, keyHex, 1, 'hex');
|
|||
|
|
return Buffer.from(encryptedHex, 'hex');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception-encryptECBPadding()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* SM4 ECB模式解密(带PKCS5Padding)
|
|||
|
|
* @param {Buffer} cipherText - 待解密内容
|
|||
|
|
* @param {Buffer} key - SM4密钥(16字节)
|
|||
|
|
* @returns {Buffer} 解密后的字节数组
|
|||
|
|
*/
|
|||
|
|
function decryptECBPadding(cipherText, key) {
|
|||
|
|
try {
|
|||
|
|
// sm-crypto 的 sm4.decrypt 使用 ECB 模式,自动处理 PKCS5Padding
|
|||
|
|
const keyHex = key.toString('hex');
|
|||
|
|
const cipherTextHex = cipherText.toString('hex');
|
|||
|
|
// sm4.decrypt(data, key, mode, outputEncoding)
|
|||
|
|
// mode: 1=ECB, 2=CBC; outputEncoding: 'base64' 或 'hex',默认为 'base64'
|
|||
|
|
// 第一个参数应该是hex字符串(密文的hex表示)
|
|||
|
|
const decryptedHex = sm4.decrypt(cipherTextHex, keyHex, 1, 'hex');
|
|||
|
|
return Buffer.from(decryptedHex, 'hex');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception-decryptECBPadding()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* SM4 ECB模式加密(Base64输入输出)
|
|||
|
|
* @param {string} content - 待加密内容
|
|||
|
|
* @param {string} key - Base64编码的SM4密钥
|
|||
|
|
* @returns {string} Base64编码的加密结果
|
|||
|
|
*/
|
|||
|
|
function encryptECBPaddingBase64(content, key) {
|
|||
|
|
if (isBlank(content) || isBlank(key)) {
|
|||
|
|
console.error('【警告】encryptECBPaddingBase64():[IN]content or [IN]key is null!|content=' + content);
|
|||
|
|
return content;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const contentBuffer = Buffer.from(content, 'utf8');
|
|||
|
|
const keyBuffer = byteArrayFromBase64String(key);
|
|||
|
|
if (!keyBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const encryptedBuffer = encryptECBPadding(contentBuffer, keyBuffer);
|
|||
|
|
if (!encryptedBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return base64StringFromByteArray(encryptedBuffer);
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception-encryptECBPaddingBase64()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* SM4 ECB模式解密(Base64输入输出)
|
|||
|
|
* @param {string} cipherText - Base64编码的密文
|
|||
|
|
* @param {string} key - Base64编码的SM4密钥
|
|||
|
|
* @returns {string} 解密后的明文
|
|||
|
|
*/
|
|||
|
|
function decryptECBPaddingBase64(cipherText, key) {
|
|||
|
|
if (isBlank(cipherText) || isBlank(key)) {
|
|||
|
|
console.error('【警告】decryptECBPaddingBase64():[IN]cipherText or [IN]key is null!|cipherText=' + cipherText);
|
|||
|
|
return cipherText;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const cipherTextBuffer = byteArrayFromBase64String(cipherText);
|
|||
|
|
const keyBuffer = byteArrayFromBase64String(key);
|
|||
|
|
if (!cipherTextBuffer || !keyBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const decryptedBuffer = decryptECBPadding(cipherTextBuffer, keyBuffer);
|
|||
|
|
if (!decryptedBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return decryptedBuffer.toString('utf8');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception-decryptECBPaddingBase64()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* AES128加密
|
|||
|
|
* @param {string} content - 待加密内容
|
|||
|
|
* @param {Buffer} byteAESKey - AES密钥(16字节)
|
|||
|
|
* @returns {Buffer} 加密后的字节数组
|
|||
|
|
*/
|
|||
|
|
function encryptAES(content, byteAESKey) {
|
|||
|
|
try {
|
|||
|
|
const cipher = crypto.createCipheriv('aes-128-ecb', byteAESKey, null);
|
|||
|
|
cipher.setAutoPadding(true);
|
|||
|
|
let encrypted = cipher.update(content, 'utf8');
|
|||
|
|
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|||
|
|
return encrypted;
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - encryptAES()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* AES128解密
|
|||
|
|
* @param {Buffer} content - 待解密内容
|
|||
|
|
* @param {Buffer} byteAESKey - AES密钥(16字节)
|
|||
|
|
* @returns {Buffer} 解密后的字节数组
|
|||
|
|
*/
|
|||
|
|
function decryptAES(content, byteAESKey) {
|
|||
|
|
try {
|
|||
|
|
const decipher = crypto.createDecipheriv('aes-128-ecb', byteAESKey, null);
|
|||
|
|
decipher.setAutoPadding(true);
|
|||
|
|
let decrypted = decipher.update(content);
|
|||
|
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|||
|
|
return decrypted;
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - decryptAES()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* AES128加密(Base64输入输出)
|
|||
|
|
* @param {string} content - 待加密内容
|
|||
|
|
* @param {string} base64AESKey - Base64编码的AES密钥
|
|||
|
|
* @returns {string} Base64编码的加密结果
|
|||
|
|
*/
|
|||
|
|
function encryptBase64(content, base64AESKey) {
|
|||
|
|
try {
|
|||
|
|
const keyBuffer = byteArrayFromBase64String(base64AESKey);
|
|||
|
|
if (!keyBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const encryptedBuffer = encryptAES(content, keyBuffer);
|
|||
|
|
if (!encryptedBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return base64StringFromByteArray(encryptedBuffer);
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - encryptBase64()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* AES128解密(Base64输入输出)
|
|||
|
|
* @param {string} base64Content - Base64编码的密文
|
|||
|
|
* @param {Buffer} byteAESKey - AES密钥(16字节)
|
|||
|
|
* @returns {Buffer} 解密后的字节数组
|
|||
|
|
*/
|
|||
|
|
function decryptBase64FromBuffer(base64Content, byteAESKey) {
|
|||
|
|
try {
|
|||
|
|
const contentBuffer = byteArrayFromBase64String(base64Content);
|
|||
|
|
if (!contentBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return decryptAES(contentBuffer, byteAESKey);
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - decryptBase64FromBuffer()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* AES128解密(Base64输入输出,返回字符串)
|
|||
|
|
* @param {string} base64Content - Base64编码的密文
|
|||
|
|
* @param {string} base64AESKey - Base64编码的AES密钥
|
|||
|
|
* @returns {string} 解密后的明文
|
|||
|
|
*/
|
|||
|
|
function decryptBase64(base64Content, base64AESKey) {
|
|||
|
|
try {
|
|||
|
|
const keyBuffer = byteArrayFromBase64String(base64AESKey);
|
|||
|
|
if (!keyBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const decryptedBuffer = decryptBase64FromBuffer(base64Content, keyBuffer);
|
|||
|
|
if (!decryptedBuffer) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return decryptedBuffer.toString('utf8');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('Exception - decryptBase64()', e.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 功能:国密商用SM4加密
|
|||
|
|
* 说明:用于加密其它敏感类数据,带密钥参数
|
|||
|
|
* @param {string} plaintext - 明文
|
|||
|
|
* @param {string} key - Base64编码的SM4密钥
|
|||
|
|
* @returns {string} 加密后的密文(带前缀 |$4|)
|
|||
|
|
*/
|
|||
|
|
function sm4Encrypt(plaintext, key) {
|
|||
|
|
if (isBlank(plaintext) || isBlank(key)
|
|||
|
|
|| plaintext.startsWith(CIPHERTEXT_PREFIX_THIRD_AES128)
|
|||
|
|
|| plaintext.startsWith(CIPHERTEXT_PREFIX_THIRD_SM4ECB)
|
|||
|
|
) {
|
|||
|
|
return plaintext;
|
|||
|
|
} else {
|
|||
|
|
const tmp = encryptECBPaddingBase64(plaintext, key);
|
|||
|
|
if (tmp == null) {
|
|||
|
|
console.error('ERROR-sm4Encrypt():plaintext=' + plaintext + ',encryptRes=' + tmp);
|
|||
|
|
}
|
|||
|
|
return tmp != null ? (CIPHERTEXT_PREFIX_THIRD_SM4ECB + tmp) : null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 功能:国密商用SM4解密
|
|||
|
|
* 说明:用于解密其它敏感类数据,带密钥参数,兼容AES128算法
|
|||
|
|
* @param {string} ciphertext - 密文
|
|||
|
|
* @param {string} key - Base64编码的SM4密钥
|
|||
|
|
* @returns {string} 解密后的明文
|
|||
|
|
*/
|
|||
|
|
function sm4Decrypt(ciphertext, key) {
|
|||
|
|
if (isBlank(ciphertext) || isBlank(key)) {
|
|||
|
|
return ciphertext;
|
|||
|
|
}
|
|||
|
|
if (ciphertext.startsWith(CIPHERTEXT_PREFIX_THIRD_SM4ECB)) {
|
|||
|
|
const tmp = decryptECBPaddingBase64(ciphertext.replace(CIPHERTEXT_PREFIX_THIRD_SM4ECB_REGEX, ''), key);
|
|||
|
|
if (tmp == null) {
|
|||
|
|
console.error('ERROR-sm4Decrypt():ciphertext=' + ciphertext + ',decryptRes=' + tmp);
|
|||
|
|
}
|
|||
|
|
return tmp;
|
|||
|
|
} else if (ciphertext.startsWith(CIPHERTEXT_PREFIX_THIRD_AES128)) {
|
|||
|
|
const tmp = decryptBase64(ciphertext.replace(CIPHERTEXT_PREFIX_THIRD_AES128_REGEX, ''), key);
|
|||
|
|
if (tmp == null) {
|
|||
|
|
console.error('ERROR-sm4Decrypt():ciphertext=' + ciphertext + ',decryptRes=' + tmp);
|
|||
|
|
}
|
|||
|
|
return tmp;
|
|||
|
|
} else {
|
|||
|
|
return ciphertext;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出函数
|
|||
|
|
module.exports = {
|
|||
|
|
sm4Encrypt,
|
|||
|
|
sm4Decrypt,
|
|||
|
|
encryptECBPaddingBase64,
|
|||
|
|
decryptECBPaddingBase64,
|
|||
|
|
encryptBase64,
|
|||
|
|
decryptBase64
|
|||
|
|
};
|
|||
|
|
|