• SM4 研究与实现


    SM4

    标准号:GB/T 32907-2016

    SM4 分组对称加密算法,分组长度128位, 密钥长度128位; 实现参考论文,讲解的非常详细;以下列举几个易错点

    • 基于密钥扩展轮密钥,在传入密钥时就可以确定
    • 解密和加密都是一样流程,区别在于密钥;解密轮密钥是加密轮密钥的逆序
    • < < < 符号的含义 <<< 符号的含义 <<<符号的含义,该符号表示32位循环左移; 代码表示如下
    func rol32(x uint32, n int) uint32 {
    	return (x << n) | ((x & 0xffffffff) >> (32 - n))
    }
    
    • 1
    • 2
    • 3

    实现

    SM4和AES很类似,保持在golang语言实现的统一性; 可参考golang aes 实现结构和逻辑; https://github.com/golang/go/blob/go1.17.13/src/crypto/aes/cipher.go#L32

    目录结构

    cryptox/sm4/     
                  |-------const.go                     常量定义和常量公式定义      
                  |-------sm4.go                       SM4实现源文件            
                  |-------sm4_test.go               SM4单元测试             
                  |-------block.go                      SM4数字签名生成逻辑
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码实现

    const.go
     package sm4
    
    // GB/T 32907-2016
    // http://c.gb688.cn/bzgk/gb/showGb?type=online&hcno=7803DE42D3BC5E80B0C3E5D8E873D56A
    
    const BlockSize = 16
    
    // GB/T 32907-2016 SBox
    var sbox = [256]byte{
    	0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
    	0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
    	0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
    	0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
    	0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
    	0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
    	0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
    	0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
    	0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
    	0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
    	0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
    	0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
    	0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
    	0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
    	0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
    	0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,
    }
    
    var (
    	fk0 = uint32(0xa3b1bac6)
    	fk1 = uint32(0x56aa3350)
    	fk2 = uint32(0x677d9197)
    	fk3 = uint32(0xb27022dc)
    )
    
    var ck = [32]uint32{
    	0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
    	0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
    	0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
    	0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
    	0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
    	0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
    	0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
    	0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
    }
    
    func delta(a uint32) uint32 {
    	a = (uint32(sbox[byte(a>>24)]) << 24) | (a & 0x00ffffff)
    	a = (uint32(sbox[byte(a>>16)]) << 16) | (a & 0xff00ffff)
    	a = (uint32(sbox[byte(a>>8)]) << 8) | (a & 0xffff00ff)
    	a = (uint32(sbox[byte(a)])) | (a & 0xffffff00)
    	return a
    }
    
    func rol32(x uint32, n int) uint32 {
    	return (x << n) | ((x & 0xffffffff) >> (32 - n))
    }
    
    func L1(a uint32) uint32 {
    	return a ^ rol32(a, 2) ^ rol32(a, 10) ^ rol32(a, 18) ^ rol32(a, 24)
    }
    
    func L2(a uint32) uint32 {
    	return a ^ rol32(a, 13) ^ rol32(a, 23)
    }
    
    func T1(a uint32) uint32 {
    	return L1(delta(a))
    }
    
    func T2(a uint32) uint32 {
    	return L2(delta(a))
    }
    
    func F(a, b, c, d, k uint32) uint32 {
    	return a ^ T1(b^c^d^k)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    block.go
    package sm4
    
    import (
    	"encoding/binary"
    	"unsafe"
    )
    
    // GB/T 32907-2016
    // http://c.gb688.cn/bzgk/gb/showGb?type=online&hcno=7803DE42D3BC5E80B0C3E5D8E873D56A
    func encryptBlockGo(xk []uint32, dst, src []byte) {
    	_ = src[15] // early bounds check
    	s0 := binary.BigEndian.Uint32(src[0:4])
    	s1 := binary.BigEndian.Uint32(src[4:8])
    	s2 := binary.BigEndian.Uint32(src[8:12])
    	s3 := binary.BigEndian.Uint32(src[12:16])
    
    	for i := 0; i < 32; i++ {
    
    		si := F(s0, s1, s2, s3, xk[i])
    
    		s0 = s1
    		s1 = s2
    		s2 = s3
    		s3 = si
    	}
    
    	_ = dst[15] // early bounds check
    	binary.BigEndian.PutUint32(dst[0:4], s3)
    	binary.BigEndian.PutUint32(dst[4:8], s2)
    	binary.BigEndian.PutUint32(dst[8:12], s1)
    	binary.BigEndian.PutUint32(dst[12:16], s0)
    }
    
    func expandEncKeyGo(key []byte) []uint32 {
    	_ = key[15] // early bounds check
    	s0 := binary.BigEndian.Uint32(key[0:4])
    	s1 := binary.BigEndian.Uint32(key[4:8])
    	s2 := binary.BigEndian.Uint32(key[8:12])
    	s3 := binary.BigEndian.Uint32(key[12:16])
    
    	k0 := s0 ^ fk0
    	k1 := s1 ^ fk1
    	k2 := s2 ^ fk2
    	k3 := s3 ^ fk3
    
    	rk := make([]uint32, 32)
    
    	for i := 0; i < 32; i++ {
    		x := k0 ^ T2(k1^k2^k3^ck[i])
    
    		k0 = k1
    		k1 = k2
    		k2 = k3
    		k3 = x
    
    		rk[i] = x
    	}
    	return rk
    }
    
    func expandDecKeyGo(key []byte) []uint32 {
    	_ = key[15] // early bounds check
    	s0 := binary.BigEndian.Uint32(key[0:4])
    	s1 := binary.BigEndian.Uint32(key[4:8])
    	s2 := binary.BigEndian.Uint32(key[8:12])
    	s3 := binary.BigEndian.Uint32(key[12:16])
    
    	k0 := s0 ^ fk0
    	k1 := s1 ^ fk1
    	k2 := s2 ^ fk2
    	k3 := s3 ^ fk3
    
    	rk := make([]uint32, 32)
    
    	for i := 0; i < 32; i++ {
    		x := k0 ^ T2(k1^k2^k3^ck[i])
    
    		k0 = k1
    		k1 = k2
    		k2 = k3
    		k3 = x
    
    		rk[31-i] = x
    	}
    
    	return rk
    }
    
    // copy from https://github.com/golang/go/blob/15da892a4950a4caac987ee72c632436329f62d5/src/crypto/internal/subtle/aliasing.go#L30
    func inexactOverlap(x, y []byte) bool {
    	if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] {
    		return false
    	}
    	return anyOverlap(x, y)
    }
    
    func anyOverlap(x, y []byte) bool {
    	return len(x) > 0 && len(y) > 0 &&
    		uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) &&
    		uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1]))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    sm4.go
    package sm4
    
    import (
    	"crypto/cipher"
    	"strconv"
    )
    
    type sm4 struct {
    	enc []uint32
    	dec []uint32
    }
    
    type KeySizeError int
    
    func (k KeySizeError) Error() string {
    	return "cryptox/sm4: invalid key size " + strconv.Itoa(int(k))
    }
    
    //GB/T 32907-2016; SM4-128
    func NewCipher(key []byte) (cipher.Block, error) {
    	k := len(key)
    	switch k {
    	default:
    		return nil, KeySizeError(k)
    	case 16:
    		break
    	}
    	return newCipher(key)
    }
    
    func newCipher(key []byte) (cipher.Block, error) {
    	c := sm4{}
    	c.enc = expandEncKeyGo(key)
    	c.dec = expandDecKeyGo(key)
    	return &c, nil
    }
    
    func (c *sm4) BlockSize() int { return BlockSize }
    
    func (c *sm4) Encrypt(dst, src []byte) {
    	if len(src) < BlockSize {
    		panic("crypto/sm4: input not full block")
    	}
    	if len(dst) < BlockSize {
    		panic("crypto/sm4: output not full block")
    	}
    	if inexactOverlap(dst[:BlockSize], src[:BlockSize]) {
    		panic("crypto/sm4: invalid buffer overlap")
    	}
    	encryptBlockGo(c.enc, dst, src)
    }
    
    func (c *sm4) Decrypt(dst, src []byte) {
    	if len(src) < BlockSize {
    		panic("crypto/sm4: input not full block")
    	}
    	if len(dst) < BlockSize {
    		panic("crypto/sm4: output not full block")
    	}
    	if inexactOverlap(dst[:BlockSize], src[:BlockSize]) {
    		panic("crypto/sm4: invalid buffer overlap")
    	}
    	encryptBlockGo(c.dec, dst, src)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    遗留问题

    SM4 是128位加密算法,但是对于加密很多数据时,需要填充数据,使其长度是128位的整数倍; 常见的填充方式;

    • NoPadding
    • PKCS5Padding
    • PKCS7Padding
    • ISO10126Padding
    • ISO7816-4Padding
    • ZeroBytePadding
    • X923Padding
    • TBCPadding(Trailing-Bit-Compliment)
    • PKCS1Padding

    AES五种加密模式(CBC、ECB、CTR、OCF、CFB),那么SM4 也应该有该五种加密模式;

    • 电码本模式(Electronic Codebook Book (ECB))
    • 密码分组链接模式(Cipher Block Chaining (CBC))
    • 计算器模式(Counter (CTR));
    • 密码反馈模式(Cipher FeedBack (CFB))
    • 输出反馈模式(Output FeedBack (OFB))

    待续。。。。

  • 相关阅读:
    USB转串口设备如何固定串口号
    北邮《计算机网络》网络层笔记
    LuatOS-SOC接口文档(air780E)--os - os操作
    基础的正则表达式
    macOS - 使用VLC
    U盘重装系统,踩了很多坑后的总结
    CobalStrike(CS)流量分析
    jbase仪器接口设计
    C# ref用法,实现引用传递(地址传递)
    openeuler上安装polarismesh集群
  • 原文地址:https://blog.csdn.net/cugriver/article/details/127639703