基于仓颉语言BigInt库实现SM2国密算法
在信息安全领域,密码技术扮演着至关重要的角色。中国的国密标准(SM系列)为保障国家安全提供了有力的技术支持。本文将介绍基于仓颉语言的SM2国密算法实现,包括椭圆曲线运算、密钥生成、签名与验签、加密与解密等功能。
在仓颉语言中有BigInt大数库,因此实现sm2算法变得简单了,这里介绍下实现原理及过程。
BigInt大数库在标准库的std.math.numeric.*包下面。
文档地址:https://developer.huawei.com/consumer/cn/doc/cangjie-guides-V5/bigint_basic_arithmetic-V5
BigInt 定义为任意精度(二进制)的有符号整数。仓颉的 struct BigInt 用于任意精度有符号整数的计算,类型转换等。
BigInt的使用示例:
import std.math.numeric.*main() {let int1: BigInt = BigInt("123456789")let int2: BigInt = BigInt("987654321")println("${int1} + ${int2} = ${int1 + int2}")println("${int1} - ${int2} = ${int1 - int2}")println("${int1} * ${int2} = ${int1 * int2}")println("${int1} / ${int2} = ${int1 / int2}")let (quo, mod) = int1.divAndMod(int2)println("${int1} / ${int2} = ${quo} .. ${mod}")return 0
}
SM2国密算法简介
SM2是一种基于椭圆曲线公钥密码算法(Elliptic Curve Public Key Cryptography, ECC),它使用了中国自主定义的椭圆曲线参数,能够提供安全的加密和签名功能。SM2定义在GB/T 32918-2016标准中,该标准由中国国家标准化管理委员会发布。
SM2算法基于椭圆曲线密码学(ECC),其核心参数包括:
- 椭圆曲线方程:y² = x³ + ax + b
- 有限域参数:大素数p
- 基点G:曲线上的生成点
- 阶n:基点G的阶
中国商用密码标准中SM2的推荐参数为256位素数域上的椭圆曲线,具体参数可在国家标准文档中找到。
仓颉BigInt的优势
仓颉的BigInt类型具有以下特点,使其非常适合实现SM2:
- 任意精度:支持超大整数运算
- 丰富的运算符:重载了+, -, *, /, %等运算符
- 模运算支持:内置高效的模逆元计算
- 类型安全:编译时检查确保运算安全
椭圆曲线运算
SM2算法的核心是椭圆曲线运算。实现椭圆曲线运算前,需要定义椭圆曲线的参数。本文中,我们使用的是SM2的曲线参数:
P = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
A = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
B = 28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E933
N = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
Gx = 32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
Gy = BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0// 定义无穷远点
let INFINITY = (x: BigInt(0), y: BigInt(0))
计算模逆元
在椭圆曲线运算中,计算模逆元是一个关键步骤。以下为计算模逆元的函数实现:
func inv_mod(a: BigInt, p: BigInt): BigInt {if (p <= 1) throw ArithmeticException("Modulus must be > 1")if (a == 0) throw ArithmeticException("Cannot invert 0")var (old_r, r) = (a, p)var (old_s, s) = (BigInt(1), BigInt(0))while (r != BigInt(0)) {let quotient = old_r / r(old_r, r) = (r, old_r - quotient * r)(old_s, s) = (s, old_s - quotient * s)}if (old_r != BigInt(1)) {throw ArithmeticException("No inverse")}let result = old_s % preturn if (result < 0) result + p else result
}
椭圆曲线点加
椭圆曲线点加是椭圆曲线加密算法的基础。通过特定的公式可以实现两个椭圆曲线点的相加。
func point_add(P: (x: BigInt, y: BigInt), Q: (x: BigInt, y: BigInt)): (x: BigInt, y: BigInt) {if (P == INFINITY) return Qif (Q == INFINITY) return Pif (P.x == Q.x && (P.y + Q.y) % p == 0) return INFINITYlet lambda = if (P == Q) {// 点倍运算let numerator = (P.x.pow(2) * 3 + a) % plet denominator = (P.y * 2) % p(numerator * inv_mod(denominator, p)) % p} else {// 点加运算let numerator = (Q.y - P.y) % plet denominator = (Q.x - P.x) % p(numerator * inv_mod(denominator, p)) % p}let x3 = (lambda.pow(2) - P.x - Q.x) % plet y3 = (lambda * (P.x - x3) - P.y) % preturn (x: if (x3 < 0) x3 + p else x3, y: if (y3 < 0) y3 + p else y3)
}
椭圆曲线点乘
椭圆曲线点乘是椭圆曲线加密算法中另一个重要的运算,它用于生成公钥和签名密钥。
func point_mul(k: BigInt, P: (x: BigInt, y: BigInt)): (x: BigInt, y: BigInt) {var result = INFINITYvar current = Pvar remaining = kwhile (remaining > BigInt(0)) {if (remaining % BigInt(2) == BigInt(1)) {result = point_add(result, current)}current = point_add(current, current)remaining = remaining / BigInt(2)}return result
}
密钥生成
SM2算法的密钥对包括私钥和公钥。私钥是一个随机数,而公钥则是根据私钥和椭圆曲线的基点G计算得到的。
func generate_keypair(): (priv: BigInt, pub: (x: BigInt, y: BigInt)) {// 生成私钥dlet rand = Random() // 创建随机数生成器let d = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1) // 生成 [1, SM2_N-1] 范围内的随机 BigInt// 计算公钥Plet P = ec_point_mul(d, (SM2_Gx, SM2_Gy))return (d, P)
}
签名/验签
签名和验签是公钥密码算法的重要应用。签名过程包括哈希计算、随机数生成、椭圆曲线运算等步骤。验签过程则是签名过程的逆操作,用于验证签名的真伪。
计算ZA
compute_za
函数用于计算Za值,这是签名过程中的一个重要参数。
func compute_za(pub: (x: BigInt, y: BigInt), id!: Array<UInt8> = []): Array<UInt8> {// ...return sm3.finish()
}
签名生成
sm2_sign
函数用于生成签名。
func sm2_sign(priv: BigInt, msg: Array<UInt8>, id!: Array<UInt8> = []): (r: BigInt, s: BigInt) {// ...return (r, s)
}
签名验证
sm2_verify
函数用于验证签名。
func sm2_verify(pub: (x: BigInt, y: BigInt), msg: Array<UInt8>, signature: (r: BigInt, s: BigInt), id!: Array<UInt8> = []): Bool {// ...return (e_int + x1) % SM2_N == r
}
加密/解密
SM2算法还可以用于加密和解密。加密过程包括随机数生成、椭圆曲线运算、密钥派生、异或加密等步骤。解密过程则是加密过程的逆操作,用于恢复原始数据。
密钥派生
kdf
函数用于密钥派生。
func kdf(z: Array<UInt8>, keyLen: Int): Array<UInt8> {// ...return derived[0..keyLen-1]
}
加密实现
sm2_encrypt
函数用于加密数据。
func sm2_encrypt(pub: (x: BigInt, y: BigInt), plaintext: Array<UInt8>): Array<UInt8> {// ...return C1[0].toBytes().concat(C1[1].toBytes()).concat(C3).concat(ciphertext)
}
解密实现
sm2_decrypt
函数用于解密数据。
func sm2_decrypt(priv: BigInt, ciphertext: Array<UInt8>): Array<UInt8> {// ...return plaintext
}
示例用法
下面是一个完整的示例,展示了如何使用上述函数进行密钥生成、签名、验签、加密和解密操作。
main() {// 密钥生成let (priv, pub) = generate_keypair()println("私钥: ${toHexString(priv.toBytes())}")println("公钥: (${toHexString(pub[0].toBytes())}, ${toHexString(pub[1].toBytes())})")// 签名验签let msg = "仓颉SM2测试消息".toArray()let (r, s) = sm2_sign(priv, msg)println("签名: r=${toHexString(r.toBytes())}, s=${toHexString(s.toBytes())}")println("验签结果: ${sm2_verify(pub, msg, (r, s))}")// 加密解密let plaintext = "加密测试数据".toArray()let ciphertext = sm2_encrypt(pub, plaintext)println("密文: ${toHexString(ciphertext)}")println("解密: ${toHexString(sm2_decrypt(priv, ciphertext))}")// 错误测试try {sm2_decrypt(priv, ciphertext[0..95])println("错误:未检测到短密文")} catch (e: Exception) {println("成功捕获异常: ${e.message}")}
}
完整实现代码
package testcj
import std.random.*
import std.binary.*
import stdx.crypto.digest.*
import std.math.numeric.*
import stdx.encoding.hex.*
//import std.exception.*// ==================== 国密SM2参数 ====================
let SM2_P = BigInt.parse("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", radix: 16)
let SM2_A = BigInt.parse("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", radix: 16)
let SM2_B = BigInt.parse("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", radix: 16)
let SM2_N = BigInt.parse("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", radix: 16)
let SM2_Gx = BigInt.parse("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", radix: 16)
let SM2_Gy = BigInt.parse("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", radix: 16)// ==================== 椭圆曲线运算 ====================
func inv_mod(a: BigInt, p: BigInt): BigInt {if (p <= BigInt(1)) {throw ArithmeticException("Modulus must be > 1")}if (a == BigInt(0)) {throw ArithmeticException("Cannot invert 0")}var (old_r, r) = (a, p)var (old_s, s) = (BigInt(1), BigInt(0))while (r != BigInt(0)) {let quotient = old_r / r(old_r, r) = (r, old_r - quotient * r)(old_s, s) = (s, old_s - quotient * s)}//println("old_r:${toHexString(old_r.toBytes())}" )if (old_r != BigInt(1)) { throw ArithmeticException("No inverse") } else { return old_s%p }
}func ec_point_add(P: (x: BigInt, y: BigInt), Q: (x: BigInt, y: BigInt)): (x: BigInt, y: BigInt) {// 参数名本身不能作为变量使用或用于访问元组中元素if (P[0]== BigInt(0) && P[1]== BigInt(0)) { return Q }if (Q[0] == BigInt(0) && Q[1] == BigInt(0)) { return P }if (P[0] == Q[0] && (P[1] != Q[1] || P[1] == BigInt(0))) { return (BigInt(0), BigInt(0)) }let lambda = if (P == Q) {(BigInt(3) * P[0]**2 + SM2_A) * inv_mod(BigInt(2) * P[1], SM2_P) % SM2_P} else {(Q[1] - P[1]) * inv_mod(Q[0] - P[0], SM2_P) % SM2_P}let x3 = (lambda**2 - P[0] - Q[0]) % SM2_Plet y3 = (lambda * (P[0] - x3) - P[1]) % SM2_Preturn (x3, y3)
}func ec_point_mul(k: BigInt, P: (x: BigInt, y: BigInt)): (x: BigInt, y: BigInt) {var result = (BigInt(0), BigInt(0))var temp = Pfor (i in 0..k.bitLen) {if (k.testBit(i)) {result = ec_point_add(result, temp)}temp = ec_point_add(temp, temp)}return result
}// ==================== 密钥生成 ====================
func generate_keypair(): (priv: BigInt, pub: (x: BigInt, y: BigInt)) {//let d = BigInt.random(SM2_N - 1) + 1let rand = Random() // 创建随机数生成器let d = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1) // 生成 [1, SM2_N-1] 范围内的随机 BigIntlet P = ec_point_mul(d, (SM2_Gx, SM2_Gy))return (d, P)
}// ==================== 签名/验签 ====================
func compute_za(pub: (x: BigInt, y: BigInt), id!: Array<UInt8> = []): Array<UInt8> {let sm3 = SM3()//sm3.write(UInt16(id.size * 8).toBytes())let buffer = Array<Byte>(4, repeat: 0)UInt16(id.size * 8).writeBigEndian(buffer)sm3.write(buffer)sm3.write(id)sm3.write(SM2_A.toBytes())sm3.write(SM2_B.toBytes())sm3.write(SM2_Gx.toBytes())sm3.write(SM2_Gy.toBytes())sm3.write(pub[0].toBytes())sm3.write(pub[1].toBytes())return sm3.finish()
}func sm2_sign(priv: BigInt, msg: Array<UInt8>, id!: Array<UInt8> = []): (r: BigInt, s: BigInt) {let pub = ec_point_mul(priv, (SM2_Gx, SM2_Gy))let za = compute_za(pub, id:id)let sm3 = SM3()sm3.write(za)sm3.write(msg)let hashBytes = sm3.finish()//let hexStr = hashBytes.map({b => b.toHexString()}).join("")let hexStrArray = hashBytes.map({ b: Byte => toHexString(b) })let hexStr = String.join(hexStrArray, delimiter: "") let bigInt = BigInt.parse(hexStr, radix: 16)//let e_int = BigInt.fromBytes(sm3.finish())let e_int = bigIntvar r = BigInt(0)var s = BigInt(0)var k = BigInt(0)while (true) {let rand = Random() // 创建随机数生成器k = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1) // 生成 [1, SM2_N-1] 范围内的随机 BigIntlet (x1, _) = ec_point_mul(k, (SM2_Gx, SM2_Gy))r = (e_int + x1) % SM2_Nif (r == BigInt(0) || r + k == SM2_N) {continue}s = (inv_mod(BigInt(1) + priv, SM2_N) * (k - r * priv)) % SM2_Nif (s != BigInt(0)) {break}}return (r, s)
}func sm2_verify(pub: (x: BigInt, y: BigInt), msg: Array<UInt8>, signature: (r: BigInt, s: BigInt), id!: Array<UInt8> = []): Bool {let (r, s) = signatureif (r <= BigInt(0) || r >= SM2_N || s <= BigInt(0) || s >= SM2_N) {return false}let za = compute_za(pub, id:id)let sm3 = SM3()sm3.write(za)sm3.write(msg)//let e_int = BigInt.fromBytes(sm3.finish())let hashBytes = sm3.finish()//let hexStr = hashBytes.map({b => b.toHexString()}).join("")let hexStrArray = hashBytes.map({ b: Byte => toHexString(b) })let hexStr = String.join(hexStrArray, delimiter: "") let bigInt = BigInt.parse(hexStr, radix: 16)let e_int = bigIntlet t = (r + s) % SM2_Nif (t == BigInt(0)) {return false}let sG = ec_point_mul(s, (SM2_Gx, SM2_Gy))let tP = ec_point_mul(t, pub)let (x1, _) = ec_point_add(sG, tP)return (e_int + x1) % SM2_N == r
}// ==================== 加密/解密 ====================
func kdf(z: Array<UInt8>, keyLen: Int): Array<UInt8> {var derived: Array<UInt8> = []var counter: UInt32 = 1while (derived.size < keyLen) {let sm3 = SM3()sm3.write(z)//sm3.write(counter.toBytes())let buffer = Array<Byte>(4, repeat: 0)UInt32(counter).writeBigEndian(buffer)sm3.write(buffer)let hashBytes = sm3.finish()//derived = derived+hashBytesderived = derived.concat(hashBytes)counter += 1}return derived[0..keyLen-1]
}func sm2_encrypt(pub: (x: BigInt, y: BigInt), plaintext: Array<UInt8>): Array<UInt8> {var k: BigIntvar C1: (x: BigInt, y: BigInt) = (BigInt(0), BigInt(0))var S: (x: BigInt, y: BigInt) = (BigInt(0), BigInt(0))while (true) {//k = BigInt.random(SM2_N - 1) + 1let rand = Random() // 创建随机数生成器k = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1) // 生成 [1, SM2_N-1] 范围内的随机 BigIntC1 = ec_point_mul(k, (SM2_Gx, SM2_Gy))S = ec_point_mul(k, pub)if (!(S[0] == BigInt(0) && S[1] == BigInt(0))) {break}}let x2 = S[0].toBytes()let y2 = S[1].toBytes()let con = x2.concat(y2) // 不支持 x2 + y2这种let kdfKey = kdf(con, plaintext.size)var ciphertext = plaintext.clone()for (i in 0..plaintext.size) {ciphertext[i] ^= kdfKey[i]}let sm3 = SM3()sm3.write(x2)sm3.write(plaintext)sm3.write(y2)let C3 = sm3.finish()//return C1[0].toBytes() + C1[1].toBytes() + C3 + ciphertextreturn C1[0].toBytes().concat(C1[1].toBytes()).concat(C3).concat(ciphertext)
}func sm2_decrypt(priv: BigInt, ciphertext: Array<UInt8>): Array<UInt8> {if (ciphertext.size < 96) {throw IllegalArgumentException("Invalid ciphertext length")}let hexStr1 = toHexString(ciphertext[0..32])let hexStr2 = toHexString(ciphertext[32..64])let bigInt1 = BigInt.parse(hexStr1, radix: 16)let bigInt2 = BigInt.parse(hexStr2, radix: 16)let C1: (x: BigInt, y: BigInt) = (bigInt1,bigInt2)let C3 = ciphertext[64..96]let C2 = ciphertext[96..]// 验证曲线点let left = (C1[1]**2) % SM2_Plet right = (C1[0]**3 + SM2_A * C1[0] + SM2_B) % SM2_Pif (left != right) {throw IllegalArgumentException("Invalid curve point")}let S = ec_point_mul(priv, C1)if (S[0] == BigInt(0) && S[1] == BigInt(0)) {throw IllegalArgumentException("Invalid private key")}let x2 = S[0].toBytes()let y2 = S[1].toBytes()let con = x2.concat(y2) // 不支持 x2 + y2这种let kdfKey = kdf(con, C2.size)var plaintext = C2.clone()for (i in 0..C2.size) {plaintext[i] ^= kdfKey[i]}let sm3 = SM3()sm3.write(x2)sm3.write(plaintext)sm3.write(y2)let u = sm3.finish()if (u != C3) {throw IllegalArgumentException("MAC verification failed")}return plaintext
}// ==================== 示例用法 ====================
main() {// 密钥生成let (priv, pub) = generate_keypair()println("私钥: ${toHexString(priv.toBytes())}")println("公钥: (${toHexString(pub[0].toBytes())}, ${toHexString(pub[1].toBytes())})")// 签名验签let msg = "仓颉SM2测试消息".toArray()let (r, s) = sm2_sign(priv, msg)println("签名: r=${toHexString(r.toBytes())}, s=${toHexString(s.toBytes())}")println("验签结果: ${sm2_verify(pub, msg, (r, s))}")// 加密解密let plaintext = "加密测试数据".toArray()let ciphertext = sm2_encrypt(pub, plaintext)println("密文: ${toHexString(ciphertext)}")println("解密: ${toHexString(sm2_decrypt(priv, ciphertext))}")// 错误测试try {sm2_decrypt(priv, ciphertext[0..95])println("错误:未检测到短密文")} catch (e: Exception) {println("成功捕获异常: ${e.message}")}
}
结论
本文详细介绍了基于仓颉语言的SM2国密算法实现,涵盖了从椭圆曲线运算到加密解密的核心功能。通过这个实现,可以方便地在仓颉语言环境中使用SM2算法进行安全的加密和签名操作。
仓颉语言的BigInt类型为SM2算法的实现提供了坚实的基础,使得开发者能够以简洁、安全的方式实现国密标准算法。开发者可根据实际需求进行扩展和优化。SM2算法在仓颉中的实现不仅展示了语言的数学计算能力,也体现了其在密码学应用中的潜力。