写点什么

保护数据隐私:深入探索 Golang 中的 SM4 加密解密算法

作者:王中阳Go
  • 2023-06-25
    北京
  • 本文字数:5583 字

    阅读完需:约 18 分钟

保护数据隐私:深入探索Golang中的SM4加密解密算法

前言

最近做的项目对安全性要求比较高,特别强调:系统不能涉及 MD5、SHA1、RSA1024、DES 高风险算法。



那用什么嘞?甲方:建议用国产密码算法 SM4。



擅长敏捷开发(CV 大法)的我,先去 GitHub 找了开源项目、又去网络上找了一些教程,但是或多或少都有些问题:


  1. 比如golang.org/x/crypto/sm4无法安装编译

  2. 比如 C 站烂大街的 SM4 教程,不能解决数据填充的问题,超过 16 位就解密失败了

  3. 比如如何封装成通用的方法,供系统进行调用

  4. 更多就是复制粘贴了 SM4 的定义,很抽象。


于是我花了 2 天时间研究 SM4 的原理和应用,解决了上面这些问题,整理这篇文章分享给大家,让大家能少踩坑。



我会按照下面的顺序分享这篇文章,方便大家更好的理解,如果你就是喜欢拿来主义(敏捷开发),可以直接 copy 底部的示例代码,快速上手使用即可。

文章目录

  1. SM4 的优势

  2. IV 是什么?

  3. SM4 加密的方式和原理

  4. SM4 的各种工作模式对比

  5. 直接可用的「代码示例」

  6. 核心方法的源码解析

  7. 总结回顾

1. SM4 的优势

相比于其他加密算法,SM4 加密算法具有以下几个优势:


  1. 高安全性:SM4 是一种对称加密算法,采用 128 位密钥长度,具有较高的安全性和抗攻击性。它经过了广泛的安全性分析和评估,并通过了多个密码学标准的验证。

  2. 高效性:SM4 算法的加密和解密速度较快,适用于对大量数据进行加密和解密的场景。它在硬件和软件实现上都具有高效性能。

  3. 简单性:SM4 算法的实现相对简单,代码量较小,易于理解和使用。它的设计目标之一是提供一种易于实现和部署的加密算法。

  4. 标准化SM4 算法是中国国家密码管理局发布的密码算法标准,得到了广泛的应用和认可。它已成为国际上公认的密码算法之一。

  5. 广泛支持:SM4 算法在各种平台和编程语言中都有支持和实现,包括 Go、Java、C/C++等。它可以在不同的系统和环境中进行跨平台的应用和部署。

  6. 可扩展性:SM4 算法支持不同的工作模式和填充方式,可以根据具体需求进行灵活配置。它可以与其他密码算法结合使用,提供更高级别的安全保护。


小小的总结一下:SM4 加密算法在安全性、高效性、简单性、标准化和广泛支持等方面具有优势,适用于各种数据保护和加密应用场景。它是一种可靠的加密算法选择。

2.IV 是什么?

我在学习的时候看到 IV 就蒙了,所以有必要先说清楚 IV 的概念:


Initialization Vector(IV)是一种在密码学中使用的初始值。它是一个固定长度的随机数或者随机生成的值,用于在加密算法中初始化密码算法的状态。


在加密过程中,IV 的作用是引入随机性和唯一性,以增加加密的安全性。 它与密钥一起用于初始化密码算法的内部状态,确保每次加密操作都产生不同的输出,即使相同的明文使用相同的密钥进行加密。


IV 的长度和使用方式取决于具体的加密算法和应用场景。在使用加密算法时,IV 通常需要与密文一起传输给解密方,以便解密方能够正确还原明文。


需要注意的是:IV 本身不需要保密,可以与密文一起传输。然而,为了确保加密的安全性,IV 应该是随机生成的,并且每次加密操作都应该使用不同的 IV。这样可以防止密码分析者通过观察加密结果的模式来破解密钥或者明文。


3. SM4 加密的方式和原理

SM4 加密算法是一种对称加密算法,采用分组密码的方式对数据进行加密。


下面是 SM4 加密的方式和原理的简要说明:


  1. 密钥扩展:SM4 使用 128 位的密钥,首先对密钥进行扩展,生成 32 个子密钥,用于后续的加密轮操作。

  2. 初始轮:将明文分为 4 个字节的分组,与第一个子密钥进行异或操作。

  3. 加密轮:SM4 加密算法共进行 32 轮加密操作。每轮操作包括以下步骤:

  4. 字节替换:使用 S 盒进行字节替换。

  5. 行移位:对每个分组进行行移位操作。

  6. 列混淆:对每个分组进行列混淆操作。

  7. 轮密钥加:将当前轮的子密钥与分组进行异或操作。

  8. 最终轮:在最后一轮加密操作中,不进行列混淆操作,只进行字节替换、行移位和轮密钥加操作。

  9. 输出:经过 32 轮加密操作后,得到加密后的密文。


SM4 加密算法的安全性和强度主要来自于其复杂的轮函数和密钥扩展过程。它具有较高的安全性和抗攻击性,并且在实际应用中得到了广泛的应用和认可。


需要注意的是:SM4 加密算法的安全性还依赖于密钥的保密性和随机性。在使用 SM4 进行加密时,应确保使用足够强度的密钥,并采取适当的密钥管理和保护措施。

4.SM4 的各种工作模式对比

SM4 加密算法可以使用不同的工作模式,其中包括 CBC(Cipher Block Chaining)模式。


我使用的是 CBC 模式,下面和大家分享一下 CBC 模式与其他模式的对比:


  1. CBC 模式(Cipher Block Chaining):


  • 特点:每个明文块与前一个密文块进行异或操作,然后再进行加密。初始块使用初始化向量(IV)。

  • 优点:具有较好的安全性,能够隐藏明文的模式和重复性。

  • 缺点:加密过程是串行的,不适合并行处理。


  1. ECB 模式(Electronic Codebook):


  • 特点:将每个明文块独立加密,相同的明文块会得到相同的密文块。

  • 优点:简单、并行处理效率高。

  • 缺点:不能隐藏明文的模式和重复性,不适合加密大量重复的数据。


  1. CFB 模式(Cipher Feedback):


  • 特点:将前一个密文块作为输入来加密当前的明文块,可以实现流密码的功能。

  • 优点:能够处理不定长的数据流,适用于实时加密和流式传输。

  • 缺点:加密过程是串行的,不适合并行处理。


  1. OFB 模式(Output Feedback):


  • 特点:将前一个密文块作为输入来生成密钥流,然后与明文块进行异或操作,可以实现流密码的功能。

  • 优点:能够处理不定长的数据流,适用于实时加密和流式传输。

  • 缺点:加密过程是串行的,不适合并行处理。


  1. CTR 模式(Counter):


  • 特点:使用一个计数器来生成密钥流,然后与明文块进行异或操作,可以实现流密码的功能。

  • 优点:能够处理不定长的数据流,适用于实时加密和流式传输。并行处理效率高,适合硬件实现。

  • 缺点:需要保证计数器的唯一性,否则会导致密钥流的重复。

对比总结:

  • CBC 模式和 ECB 模式相比,CBC 模式具有更好的安全性,能够隐藏明文的模式和重复性,而 ECB 模式无法隐藏这些信息。

  • CFB 模式、OFB 模式和 CTR 模式都是流密码模式,适用于不定长的数据流加密,能够实现实时加密和流式传输。它们的主要区别在于密钥流的生成方式和加密过程的并行性。

  • CFB 模式和 OFB 模式的加密过程是串行的,不适合并行处理,而 CTR 模式的加密过程可以并行处理,适合硬件实现。


总的来说:CBC 模式在安全性方面较好,能够隐藏明文的模式和重复性。而流密码模式(CFB、OFB 和 CTR)适用于不定长数据流的加密,能够实现实时加密和流式传输,其中 CTR 模式具有较好的并行处理性能。选择合适的加密模式取决于具体的应用需求和安全性要求。


5. 直接可用的「代码示例」

我一直认为可以通过复制粘贴,直接跑通的示例代码才是好代码。


没错,我的代码示例就是这样,并且关键代码都写好了注释:


package main
import ( "bytes" "crypto/cipher" "encoding/hex" "fmt" "github.com/tjfoc/gmsm/sm4")
// SM4加密func SM4Encrypt(data string) (result string, err error) { //字符串转byte切片 plainText := []byte(data) //建议从配置文件中读取秘钥,进行统一管理 SM4Key := "Uv6tkf2M3xYSRuFv" //todo 注意:iv需要是随机的,进一步保证加密的安全性,将iv的值和加密后的数据一起返回给外部 SM4Iv := "04TzMuvkHm_EZnHm" iv := []byte(SM4Iv) key := []byte(SM4Key) //实例化sm4加密对象 block, err := sm4.NewCipher(key) if err != nil { panic(err) } //明文数据填充 paddingData := paddingLastGroup(plainText, block.BlockSize()) //声明SM4的加密工作模式 blockMode := cipher.NewCBCEncrypter(block, iv) //为填充后的数据进行加密处理 cipherText := make([]byte, len(paddingData)) //使用CryptBlocks这个核心方法,将paddingData进行加密处理,将加密处理后的值赋值到cipherText中 blockMode.CryptBlocks(cipherText, paddingData) //加密结果使用hex转成字符串,方便外部调用 cipherString := hex.EncodeToString(cipherText) return cipherString, nil}
// SM4解密 传入string 输出stringfunc SM4Decrypt(data string) (res string, err error) { //秘钥 SM4Key := "Uv6tkf2M3xYSRuFv" //iv是Initialization Vector,初始向量, SM4Iv := "04TzMuvkHm_EZnHm" iv := []byte(SM4Iv) key := []byte(SM4Key) block, err := sm4.NewCipher(key) if err != nil { panic(err) } //使用hex解码 decodeString, err := hex.DecodeString(data) if err != nil { return "", err } //CBC模式 优点:具有较好的安全性,能够隐藏明文的模式和重复性。 缺点:加密过程是串行的,不适合并行处理。 blockMode := cipher.NewCBCDecrypter(block, iv) //下文有详解这段代码的含义 blockMode.CryptBlocks(decodeString, decodeString) //去掉明文后面的填充数据 plainText := unPaddingLastGroup(decodeString) //直接返回字符串类型,方便外部调用 return string(plainText), nil}
// 明文数据填充func paddingLastGroup(plainText []byte, blockSize int) []byte { //1.计算最后一个分组中明文后需要填充的字节数 padNum := blockSize - len(plainText)%blockSize //2.将字节数转换为byte类型 char := []byte{byte(padNum)} //3.创建切片并初始化 newPlain := bytes.Repeat(char, padNum) //4.将填充数据追加到原始数据后 newText := append(plainText, newPlain...) return newText}
// 去掉明文后面的填充数据func unPaddingLastGroup(plainText []byte) []byte { //1.拿到切片中的最后一个字节 length := len(plainText) lastChar := plainText[length-1] //2.将最后一个数据转换为整数 number := int(lastChar) return plainText[:length-number]}
func main() { //待加密的数据 模拟18位的身份证号 plainText := "131229199907097219" //SM4加密 decrypt, err := SM4Encrypt(plainText) if err != nil { return } fmt.Printf("sm4加密结果:%s\n", decrypt) //cipherString := hex.EncodeToString(cipherText) //fmt.Printf("sm4加密结果转成字符串:%s\n", cipherString)
//SM4解密 sm4Decrypt, err := SM4Decrypt(decrypt) if err != nil { return } fmt.Printf("plainText:%s\n", sm4Decrypt) flag := plainText == sm4Decrypt fmt.Println("解密是否成功:", flag)}
复制代码


运行结果如下:



6. 核心方法的源码解析

细心的小伙伴应该又发现,(或者通过你真实的敲代码一定能发现。


在加密和解密部分有一个 CryptBlocks()方法,我们来解析一下这段源码:


  // CryptBlocks encrypts or decrypts a number of blocks. The length of  // src must be a multiple of the block size. Dst and src must overlap  // entirely or not at all.  //  // If len(dst) < len(src), CryptBlocks should panic. It is acceptable  // to pass a dst bigger than src, and in that case, CryptBlocks will  // only update dst[:len(src)] and will not touch the rest of dst.  //  // Multiple calls to CryptBlocks behave as if the concatenation of  // the src buffers was passed in a single run. That is, BlockMode  // maintains state and does not reset at each CryptBlocks call.  CryptBlocks(dst, src []byte)
复制代码

翻译翻译

CryptBlocks 方法用于加密或解密多个数据块。src 的长度必须是块大小的倍数。dst 和 src 必须完全重叠或完全不重叠。


如果 len(dst) < len(src),CryptBlocks 方法应该引发 panic。允许传递比 src 更大的 dst,此时 CryptBlocks 只会更新 dst[:len(src)],不会触及 dst 的其余部分。


在这段代码注释中,dst 表示目标缓冲区,用于存储加密或解密后的结果。src 表示源缓冲区,包含要加密或解密的数据。这两个缓冲区可以是相同的内存区域,也可以是不同的内存区域。CryptBlocks 方法会将 src 中的数据进行加密或解密,并将结果存储在 dst 中。


需要注意的是,dst 和 src 的长度必须是块大小的倍数,否则 CryptBlocks 方法可能会引发 panic。如果 dst 的长度小于 src 的长度,CryptBlocks 方法只会更新 dst 的前 len(src)个字节,并不会修改 dst 的其余部分。


此外,CryptBlocks 方法可以多次调用,多次调用的效果相当于将所有 src 缓冲区的数据连接在一起,然后进行加密或解密。这意味着 BlockMode 会保持状态,并且不会在每次 CryptBlocks 调用时重置。


如果你看注释翻译理解起来还是比较抽象的话,我换个方式介绍一下:

用我的话来说

在 SM4 加密中,CryptBlocks()方法是用于加密或解密多个数据块的方法。它是 SM4 算法中的一个核心函数。


具体来说,CryptBlocks()方法接受一个源数据缓冲区(src)和一个目标数据缓冲区(dst),并对源数据进行加密或解密操作,将结果存储在目标数据缓冲区中。


在加密过程中,CryptBlocks()方法会将源数据分成多个数据块,然后对每个数据块进行加密操作,并将结果存储在目标数据缓冲区中。加密过程中使用的密钥和其他参数由 SM4 算法的实现确定。


在解密过程中,CryptBlocks()方法会对源数据缓冲区中的数据块进行解密操作,并将解密后的结果存储在目标数据缓冲区中。


需要注意的是:CryptBlocks()方法要求源数据缓冲区和目标数据缓冲区的长度必须是 SM4 算法的块大小的倍数。否则,可能会引发错误或产生不可预测的结果。


CryptBlocks()方法是 SM4 加密算法中用于加密或解密多个数据块的关键方法,它实现了 SM4 算法的核心功能。


7. 总结回顾

我之前也写过一篇解密解密的文章,欢迎大家阅读指教:保障网络请求数据传输的安全性、一致性和防篡改:对称加密与非对称加密的结合


相信你读了这篇文章能对 SM4 加密有个整体理解,通过我在文章中提供的示例代码可以快速跑通加密和解密流程。我还带着你分析了 CryptBlocks()源码的作用。


欢迎大家收藏、点赞、转发。你的关注,是我更文的最大动力!


欢迎关注 ❤

我的微信:wangzhongyang1993


视频号:王中阳Go


公众号:程序员升职加薪之旅

发布于: 13 小时前阅读数: 4
用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论

发布
暂无评论
保护数据隐私:深入探索Golang中的SM4加密解密算法_Go_王中阳Go_InfoQ写作社区