如何使用国密SM2算法进行加解密

如何使用国密SM2算法进行加解密

HarmonyOS
2024-03-18 22:00:11
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
zbw_apple

加密过程

1. 生成ECC密钥(公钥)。

当前SM2密钥生成的参数只支持封装并ASN.1序列化后的数据(公钥91字节,私钥51字节),而很多开发者使用的密钥数据是未封装序列化过的原始裸数据(公钥64字节,私钥32字节)。当前ECC密钥的生成是支持未封装序列化过的原始裸数据作为参数的,可以先使用原始密钥数据生成ECC密钥作为中间对象,然后将ECC密钥的数据作为参数来生成SM2密钥。

/** 
 * 生成ECC密钥对象,为裸密钥数据生成SM2密钥的中间对象 
 * @param key 传入的密钥数据,未序列化的原始裸数据 
 * @returns 返回生成的ECC密钥对象 
 */ 
import { cryptoFramework } from '@kit.CryptoArchitectureKit'; 
 
async function genECCPubKey(key: string) { 
  let mode: number = 1; 
  let pk: cryptoFramework.Point = { 
    x: BigInt(""), 
    y: BigInt("") 
  }; 
  if ((mode & 0x01) != 0 && key != null) { 
    pk = { 
      x: BigInt("0x" + key.substring(0, 64)), 
      y: BigInt("0x" + key.substring(64, 128)) 
    } 
  } 
  let KeyPairGenerator: cryptoFramework.AsyKeyGeneratorBySpec; 
  let pubKeySpec: cryptoFramework.ECCPubKeySpec = { 
    params: genSM2CommonSpec(), 
    pk: pk, 
    algName: "ECC", 
    specType: cryptoFramework.AsyKeySpecType.PUBLIC_KEY_SPEC 
  } 
  KeyPairGenerator = cryptoFramework.createAsyKeyGeneratorBySpec(pubKeySpec); 
  return await KeyPairGenerator.generatePubKey() 
} 
 
/** 
 * 返回SM2密钥参数,加解密通用 
 */ 
export function genSM2CommonSpec(): cryptoFramework.ECCCommonParamsSpec { 
  let fieldFp: cryptoFramework.ECFieldFp = { 
    fieldType: "Fp", 
    p: BigInt("0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") 
  } 
 
  let G: cryptoFramework.Point = { 
    x: BigInt("0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7"), 
    y: BigInt("0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0") 
  } 
 
  let SM2CommonSpec: cryptoFramework.ECCCommonParamsSpec = { 
    algName: "ECC", 
    specType: cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC, 
    field: fieldFp, 
    a: BigInt("0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC"), 
    b: BigInt("0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"), 
    g: G, 
    n: BigInt("0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"), 
    h: 1 
  } 
  return SM2CommonSpec; 
}

2. 生成SM2密钥对象。

完成ECC的密钥对象创建后,下一步就是将ECC的密钥数据传入convertKey中来生成SM2密钥对象。

/** 
 * 生成SM2密钥对象 , 加解密通用 
 * @param pubKey 公钥数据,根据使用场景,允许为空 
 * @param priKey 私钥数据,根据使用场景,允许为空 
 * @returns 返回生成的SM2密钥对象 
 */ 
export async function genSM2Key(pubKey: cryptoFramework.DataBlob | null, priKey: cryptoFramework.DataBlob | null): Promise<cryptoFramework.KeyPair> { 
  let generator = cryptoFramework.createAsyKeyGenerator("SM2_256"); 
  return await generator.convertKey(pubKey, priKey); 
}

3. 使用裸密钥执行SM2加密。

得到SM2密钥后,就可以使用cryptoFramework创建的cipher,执行SM2算法加密。

/** 
 * 使用原始裸密钥执行加密 
 * @param message 待加密明文 
 * @param key 未经序列化的密钥数据 
 * @returns 返回密文数据,为ASN.1序列化后的数据 
 */ 
async function encryptByPrimalKey(message: string, key: string): Promise<string> { 
  // 初始化Base64工具实例 
  let base64Helper = new util.Base64Helper(); 
  // 生成ECC密钥对象,为裸密钥数据生成SM2密钥的中间对象 
  let pubKey = await genECCPubKey(key) 
  // 使用ECC密钥生成SM2密钥 
  let keyPair = await genSM2Key(pubKey.getEncoded(), null) 
  // 创建 Cipher对象 
  let cipher = cryptoFramework.createCipher("SM2_256|SM3"); 
  // 初始化加密模式,指定密钥keyPair.pubKey 
  await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null); 
  // 包装要处理的明文 
  let plainTextBlob: cryptoFramework.DataBlob = { data: new Uint8Array(buffer.from(message, 'utf-8').buffer) }; 
  // 传入明文,获取加密后的数据 
  let encryptBlob = await cipher.doFinal(plainTextBlob); 
  // 返回加密后的字符串,base64编码 
  return base64Helper.encodeToStringSync(encryptBlob.data); 
}

4. 业务层封装调用,执行加密。

其中pubKey是未封装序列化过的原始裸数据公钥。最终返回的是标准SM2密文数据,符合ASN.1编码标准(base64编码)。

/** 
 * 使用非序列化密钥加密 
 */ 
export async function encryptSM2(message: string) { 
  // 公钥字符串 
  let pubKey: string = "5A033A9DBEF84C0784C897D070E6608C5AEED39B806DF82853D64E2A686A3794" + "F9233D20DD878F642D61C2B0344988AE284646226767A1631BBB0DBB6DF40D07"; 
  return encryptByPrimalKey(message, pubKey) 
}

解密过程

1. 将SM2裸密文数据转为标准的ASN.1序列化格式。

部分开发者的云端使用的密文格式为未封装序列化的裸数据,所以需要先根据SM2国标说明,编写代码,将裸密文数据转化为ASN.1序列化格式。

/** 
 * 用于将SM2裸密文数据序列化 
 * @param primal_data SM2裸密文数据,长度为96+明文长度(字节),输入格式为C1C3C2的Hex字符串 
 * @returns 返回序列化后的标准密文数据,输出格式为Hex字符串 
 */ 
function i2d_SM2_Ciphertext(primal_data: string): string { 
  let sm2_sequence = new SM2_SEQUENCE(); 
  sm2_sequence.C1x = primal_data.slice(0, 64); 
  primal_data = primal_data.slice(64, primal_data.length); 
  sm2_sequence.C1y = primal_data.slice(0, 64); 
  primal_data = primal_data.slice(64, primal_data.length); 
  sm2_sequence.C3 = primal_data.slice(0, 64); 
  primal_data = primal_data.slice(64, primal_data.length); 
  sm2_sequence.C2 = primal_data; 
 
  let C1x_title: string = (Number.parseInt("0x" + sm2_sequence.C1x.slice(0, 2)) > 127) ? "022100" : "0220"; 
  let C1y_title: string = (Number.parseInt("0x" + sm2_sequence.C1y.slice(0, 2)) > 127) ? "022100" : "0220"; 
  let C3_title: string = "0420"; 
  let C2_title: string = "04" + genLenHex(sm2_sequence.C2); 
  let sequence_message: string = C1x_title + sm2_sequence.C1x + C1y_title + sm2_sequence.C1y + C3_title + sm2_sequence.C3 + C2_title + sm2_sequence.C2; 
  let sequence_lenHex: string = genLenHex(sequence_message); 
 
  let standard_data = "30" + sequence_lenHex + sequence_message; 
  return standard_data; 
} 
 
// 生成传入内容的长度域 
function genLenHex(content: string): string { 
  let size: number = content.length / 2; 
  let lenHex: string; 
  if (size.toString(16).length % 2 == 1) { 
    lenHex = '0' + size.toString(16); 
  } else { 
    lenHex = size.toString(16); 
  } 
  if (size < 0x80) { 
    return lenHex; 
  } 
  let lenHex_size: number = lenHex.length / 2; 
  return (lenHex_size | 0x80).toString(16) + lenHex; 
} 
 
class SM2_SEQUENCE{ 
  private _C1x: string = ""; 
  private _C1y: string = ""; 
  private _C2: string = ""; 
  private _C3: string = ""; 
 
  public set C1x(value: string) { 
    this._C1x = value; 
  } 
  public get C1x(): string { 
    return this._C1x; 
  } 
  public set C1y(value: string) { 
    this._C1y = value; 
  } 
  public get C1y(): string { 
    return this._C1y; 
  } 
  public set C2(value: string) { 
    this._C2 = value; 
  } 
  public get C2(): string { 
    return this._C2; 
  } 
  public set C3(value: string) { 
    this._C3 = value; 
  } 
  public get C3(): string { 
    return this._C3; 
  } 
  public toString():string{ 
    return JSON.stringify(this); 
  } 
}

2. 生成ECC密钥(私钥)。

与加密过程的生成ECC公钥类似,先使用原始私钥数据生成ECC加密的私钥;其中genSM2CommonSpec与加密时的方法相同。

/** 
/** 
 * 生成ECC密钥对象,为裸密钥数据生成SM2密钥的中间对象 
 * @param key 传入的密钥数据,未序列化的原始裸数据 
 * @returns 返回生成的ECC密钥对象 
 */ 
async function genECCPriKey(key: string) { 
  let mode: number = 2; 
  let sk: bigint = BigInt(""); 
  if ((mode & 0x02) != 0) { 
    sk = BigInt("0x" + key); 
  } 
  let KeyPairGenerator: cryptoFramework.AsyKeyGeneratorBySpec; 
  let priKey: cryptoFramework.ECCPriKeySpec = { 
    params: genSM2CommonSpec(), 
    sk: sk, 
    algName: "ECC", 
    specType: cryptoFramework.AsyKeySpecType.PRIVATE_KEY_SPEC 
  } 
  KeyPairGenerator = cryptoFramework.createAsyKeyGeneratorBySpec(priKey); 
  return await KeyPairGenerator.generatePriKey() 
}

3. 生成SM2密钥对象。

与加密过程相同,使用genSM2Key方法,将ECC的私钥数据传入convertKey中来生成SM2密钥对象。

/** 
 * 生成SM2密钥对象 , 加解密通用 
 * @param pubKey 公钥数据,根据使用场景,允许为空 
 * @param priKey 私钥数据,根据使用场景,允许为空 
 * @returns 返回生成的SM2密钥对象 
 */ 
export async function genSM2Key(pubKey: cryptoFramework.DataBlob | null, priKey: cryptoFramework.DataBlob | null): Promise<cryptoFramework.KeyPair> { 
  let generator = cryptoFramework.createAsyKeyGenerator("SM2_256"); 
  return await generator.convertKey(pubKey, priKey); 
}

4. 使用裸密钥执行SM2解密。

得到SM2密钥后,就可以使用cryptoFramework创建的cipher,执行SM2算法解密。

/** 
 * 使用原始裸密钥执行解密 
 * @param messageArray 待解密密文 
 * @param key 未经序列化的密钥数据 
 * @returns 返回明文数据 
 */ 
async function decryptByPrimalKeyArray(messageArray: Uint8Array, key: string): Promise<string> { 
  // 生成ECC密钥对象,为裸密钥数据生成SM2密钥的中间对象 
  let priKey = await genECCPriKey(key) 
  // 使用ECC密钥生成SM2密钥 
  let keyPair = await genSM2Key(null, priKey.getEncoded()) 
  // 创建 Cipher对象 
  let cipher = cryptoFramework.createCipher("SM2_256|SM3"); 
  // 初始化解密模式,指定密钥keyPair.priKey 
  await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null); 
  // 包装要处理的密文 
  let plainTextBlob: cryptoFramework.DataBlob = { data: messageArray }; 
  // 传入密文,获取解密后的数据 
  let decryptBlob = await cipher.doFinal(plainTextBlob); 
  // 返回解密后的字符串 
  return buffer.from(decryptBlob.data).toString('utf-8'); 
}

5. 业务层封装调用,执行解密。

如下所示,如果需要解密裸密文,执行decryptSM2Array;如果待解密的是base64编码的符合ASN.1序列化的密文,则执行decryptSM2方法。其中priKey是未封装序列化过的原始裸数据私钥。最终返回解密后的字符串。

/** 
 * 使用非序列化密钥解密 
 * @param encryptedStr 待解密密文,裸密文 
 */ 
export async function decryptSM2Array(encryptedStr: string) { 
  // 私钥字符串 
  let priKey: string = "3629EFF03FBC86711F6695CBF5590F0F2FCAAA3C269A1CA9BD64FB4C70DF9C9F"; 
  // 用于将SM2裸密文数据序列化 
  let hexStr = i2d_SM2_Ciphertext(encryptedStr) 
  // hex转Uint8Array 
  let encryptedArray = hexStr2Uint8Array(hexStr) 
  // 执行密钥转换,并进行解密 
  return decryptByPrimalKeyArray(encryptedArray, priKey) 
} 
 
/** 
 * 使用非序列化密钥解密 
 * @param encryptedStr 待解密密文,base64编码的符合ASN.1序列化的密文 
 */ 
export async function decryptSM2(encryptedStr: string) { 
  // 私钥字符串 
  let priKey: string = "3629EFF03FBC86711F6695CBF5590F0F2FCAAA3C269A1CA9BD64FB4C70DF9C9F"; 
  // 初始化Base64工具实例 
  let base64Helper = new util.Base64Helper(); 
  // 将密文进行base64解码 
  let message = base64Helper.decodeSync(encryptedStr); 
  return decryptByPrimalKeyArray(message, priKey) 
} 
 
function  hexStr2Uint8Array(str: string): Uint8Array { 
  let arr = new Uint8Array(str.length / 2); 
  let index = 0; 
  for (let i = 0; i < str.length; i += 2) { 
    let tmp: string = str.slice(i, i + 2); 
    let result = Number.parseInt("0x" + tmp); 
    arr[index++] = result 
  } 
  return arr; 
}

参考链接

SM2算法简介

使用SM2非对称密钥加解密

分享
微博
QQ
微信
回复
2024-03-19 21:49:18
相关问题
DevEco Studio怎么使用真机进行调试
6185浏览 • 1回复 待解决
如何对Serviceabbility进行调试?
995浏览 • 1回复 待解决
如何安装 collectd redis 插件 Amazon Linux2
805浏览 • 1回复 待解决
数据结构与算法分析习题4.33和4.34
1846浏览 • 0回复 待解决