在信息安全领域,密码技术扮演着至关重要的角色。中国的国密标准(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),其核心参数包括:

  1. 椭圆曲线方程:y² = x³ + ax + b
  2. 有限域参数:大素数p
  3. 基点G:曲线上的生成点
  4. 阶n:基点G的阶

中国商用密码标准中SM2的推荐参数为256位素数域上的椭圆曲线,具体参数可在国家标准文档中找到。

仓颉BigInt的优势

仓颉的BigInt类型具有以下特点,使其非常适合实现SM2:

  1. 任意精度:支持超大整数运算
  2. 丰富的运算符:重载了+, -, *, /, %等运算符
  3. 模运算支持:内置高效的模逆元计算
  4. 类型安全:编译时检查确保运算安全

椭圆曲线运算

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 % p
    return 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 Q
    if (Q == INFINITY) return P
    if (P.x == Q.x && (P.y + Q.y) % p == 0) return INFINITY
    
    let lambda = 
        if (P == Q) {
            // 点倍运算
            let numerator = (P.x.pow(2) * 3 + a) % p
            let denominator = (P.y * 2) % p
            (numerator * inv_mod(denominator, p)) % p
        } else {
            // 点加运算
            let numerator = (Q.y - P.y) % p
            let denominator = (Q.x - P.x) % p
            (numerator * inv_mod(denominator, p)) % p
        }
    
    let x3 = (lambda.pow(2) - P.x - Q.x) % p
    let y3 = (lambda * (P.x - x3) - P.y) % p
    
    return (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 = INFINITY
    var current = P
    var remaining = k
    
    while (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)) {
    // 生成私钥d
    let rand = Random()  // 创建随机数生成器
    let d = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1)  // 生成 [1, SM2_N-1] 范围内的随机 BigInt
    // 计算公钥P
    let 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_P
    let y3 = (lambda * (P[0] - x3) - P[1]) % SM2_P
    return (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 = P
    for (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) + 1
    let rand = Random()  // 创建随机数生成器
    let d = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1)  // 生成 [1, SM2_N-1] 范围内的随机 BigInt
    let 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 = bigInt
    
    var 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] 范围内的随机 BigInt
        let (x1, _) = ec_point_mul(k, (SM2_Gx, SM2_Gy))
        r = (e_int + x1) % SM2_N
        
        if (r == BigInt(0) || r + k == SM2_N) {
            continue
        }
        
        s = (inv_mod(BigInt(1) + priv, SM2_N) * (k - r * priv)) % SM2_N
        if (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) = signature
    if (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 = bigInt
    
    let t = (r + s) % SM2_N
    if (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 = 1
    
    while (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+hashBytes
        derived = 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: BigInt
    var 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) + 1
        let rand = Random()  // 创建随机数生成器
        k = BigInt(false, SM2_N.bitLen, rand: rand) % (SM2_N - BigInt(1)) + BigInt(1)  // 生成 [1, SM2_N-1] 范围内的随机 BigInt
        C1 = 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 + ciphertext
    return 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_P
    let right = (C1[0]**3 + SM2_A * C1[0] + SM2_B) % SM2_P
    if (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算法在仓颉中的实现不仅展示了语言的数学计算能力,也体现了其在密码学应用中的潜力。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐