Day2——SM4加密算法的实现(半成品)

7 阅读5分钟

之前的无用功:

  1. 我用C++复现了别人开源的SM4,但是发现只能加密16进制数字,不合我的想法(我想可以加解密中文,怎么都有我不知道怎么解决的报错,然后太麻烦了(我不是很想研究透SM4,因为我只是为了完成课程作业,所以我知识了解了SM4的数学实现过程)于是我就想换一个更契合我想法的开源代码学习。
  2. 然后折腾了很久后,我突然意识到我不一定要用C++呀,我就去实验了最简单的python,只要调用就可以了,但是我觉得实在是太简单了,怕老师不满意☹️。
  3. 于是我想起我正在学习go!于是我正在用go来实现, 给大家看一下我的第一版(半成品,解密那里出bug了,明天考完试再debug
/*
SM4加密的方式和原理的简要说明:
    1、密钥扩展:SM4使用128位的密钥,首先对密钥进行扩展,生成32个子密钥,用于后续的加密轮操作。
    2、初始轮:将明文分为4个字节的分组,与第一个子密钥进行异或操作。
    3、加密轮:SM4加密算法共进行32轮加密操作。每轮操作包括以下步骤:
       字节替换:使用S盒进行字节替换。
       行移位:对每个分组进行行移位操作。
       列混淆:对每个分组进行列混淆操作。
       轮密钥加:将当前轮的子密钥与分组进行异或操作。
    4、最终轮:在最后一轮加密操作中,不进行列混淆操作,只进行字节替换、行移位和轮密钥加操作。
    5、输出:经过32轮加密操作后,得到加密后的密文。
*/

package main

import (
    "bufio"
    "bytes"                     // 提供字节切片操作函数,用于实现PKCS#7填充和去填充
    "crypto/cipher"             // 提供加密块模式的实现,用于构建完整的加密过程
    "encoding/hex"              // 提供十六进制编解码功能,用于密钥、IV的输入输出
    "fmt"                       // 提供格式化输入输出功能,用于调试和结果展示
    "github.com/tjfoc/gmsm/sm4" // 提供SM4算法的具体实现,包括加密块生成、密钥扩展等核心功能
    "os"
    "strings"
)

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
}

// SM4Decrypt 传入string 输出string
func 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] // 解密报错:plainText 为空,length-1 就是 -1,索引越界错误

    //2.将最后一个数据转换为整数
    number := int(lastChar)
    return plainText[:length-number]
}

func main() {
    // 创建一个缓冲读取器,用于读取标准输入
    reader := bufio.NewReader(os.Stdin)
    var plainText string
    var decrypt string

    // 选择模式[加密/解密/退出]
    for {
       fmt.Print("请输入命令 [加密/解密/退出]: ")
       // 允许用户输入包含空格的内容。(从标准输入读取一整行内容,直到遇到 \n)
       command, err := reader.ReadString('\n')
       if err != nil {
          fmt.Println("读取输入失败:", err)
       }
       // 删除首尾的空白字符,确保后续判断准确。
       command = strings.TrimSpace(command)

       switch command {
       case "加密":
          //待加密的数据 手动输入
          fmt.Printf("请输入要加密的内容:")
          _, err := fmt.Scanln(&plainText)
          if err != nil {
             return
          }
          //SM4加密
          decrypt, err := SM4Encrypt(plainText)
          if err != nil {
             return
          }
          fmt.Printf("要加密的明文是:%s\n", plainText)
          fmt.Printf("SM4加密结果:%s\n", decrypt)
          //cipherString := hex.EncodeToString(cipherText)
          //fmt.Printf("sm4加密结果转成字符串:%s\n", cipherString)
       case "解密":
          //SM4解密
          sm4Decrypt, err := SM4Decrypt(decrypt)
          if err != nil {
             return
          }
          fmt.Printf("SM4解密结果:%s\n", sm4Decrypt)
          flag := plainText == sm4Decrypt
          fmt.Println("解密是否成功:", flag)
       case "退出":
          fmt.Println("程序已退出")
          return
       default:
          fmt.Println("无效命令,请重新输入")
       }
    }
}

报错信息

panic: runtime error: index out of range [-1]

goroutine 1 [running]:
main.unPaddingLastGroup(...)
	/Users/*/sm4-demo/main.go:98
main.SM4Decrypt({0x0, 0x0})
	/Users/*/main.go:76 +0x134
main.main()
	/Users/*/main.go:141 +0x308

错误原因应该是unPaddingLastGrouplastChar := plainText[length-1]在明文不存在时会发生数组越界的情况。

  1. 但突发通知,我代表 党**** 为学院的党纪知识竞赛出了决赛的部分题目,出完题没多久图书馆就响铃,迫不得已结束学习,明天有重要的课和国家体测呜呜呜