密码学基础



一:对称加密:

1.1: 对称加密/解密三要素:


  • 1: 对称加密三要素: 明文,加密算法, 秘钥。
  • 2: 对称解密三要素:密文,解密算法,秘钥。

1.2:凯撒密码:


  • 凯撒密码加密流程: 将小写字母转化成大写字母,然后向右移动指定位数。
  • 凯撒密码解密流程:将大写字母,向左移动指定位数,然后转化成小写字母。

密码学概述论_非对称加密

1.3:对称加密的流程与特点:


  • 流程:

  • A使用秘钥将明文加密成密文。
  • B使用秘钥将密文解密成明文。

  • 特点:

  • 加密过程只有一把秘钥。
  • 加密效率比非对称加密高。
  • 安全性比非对称加密低。


1.4:编码与加密:


  • 1: 最小单位: bit

  • 1Byte = 8bit
  • 1K = 1024B
  • 1M = 1024K
  • 1G = 1024M
  • 1T = 1024G
  • 1P = 1024T

  • 编码和解码:

  • 编码: 由字符的形式变成二进制的形式。
  • 解码:由二进制的形式变成字符的形式。

  • 加密与解密:

  • 加密:明文比特—>密文比特。
  • 解密:密文比特序列—>明文比特序列。


1.5:分组:


  • 1:明文分组:加密之前的分组。
  • 2:密文分组:加密之后的分组。
  • 3:明文分组和密文分组是等长的。
  • 4: 五中分组模式:

  • OFB : 输出反馈模式

  • 流程:

  • 1: 先对初始向量进行加密处理(密码)
  • 2:分组后每组的数据与密码进行异或操作。

  • 特点:

  • 分组长度取决于加密算法。
  • 不断对初始向量的输出进行加密,从而得到数据来源。
  • 不需要进行数据填充。

  • 图示:

  • CTR:计数器模式(常用)

  • 加密算法与分组的关系:
    密码学概述论_对称加密_02
  • 图示
    密码学概述论_对称加密_03
  • 流程:

  • 1: 首先使用计数器生成一个8位的随机数,然后看看有几个分组,后面的计数器执行+1操作。
  • 2:使用加密算法对计数器进行加密。
  • 3:将加密后的密码与明文进行异或操作生成密文。

  • CTR与OFB的区别:



二:对称加密算法:

2.1:DES算法:


  • DES算法介绍:

  • 将64位比特的明文加密成64位比特密文的对称加密算法。
  • 秘钥长度是56比特,少8位的原因是最后一位是校验位。
  • 特点:加密的时候先对数据进行分组,每64位 分一组。

  • 代码:使用DES算法+CBC分组模式:

  • DES算法要求:秘钥:8bytes, 分组长度:8bytes
  • CBC分组模式要求: 初始化向量,长度与密码长度相同:8bytes
  • 实现加密函数的代码:
    ​ package main import ( "bytes" "crypto/cipher" "crypto/des" "fmt" ) /* 需求:使用DES算法和CBC分组模式进行加密 DES算法:秘钥8bytes,分组长度:8bytes CBC分组:提供初始化向量:长度与分组长度相同。 */ // 加密分析: /* 1: 创建一个实现DES算法的cipher.Block接口, 这个接口返回一个cipter.Block - func NewCipter(key []byte)(cipter.Block, error) - 包名: des - 参数: 秘钥, 8bytes - 返回值: type Bolck interface { // 返回加密字节快的大小 BlockSize() int // 加密src的第一块数据并写入dst, src和dst是指向同一块内存地址的。 Encrypt(dst, src []byte) // 解密src的第一块数据写入dst, src和dst是指向同一块内存地址的。 Decrypt(dst, src []byte) } 2: 进行数据填充: 3: 引入CBC模式接口,而这个接口返回一个密码分组链接模式的, 底层使用b加密的BlockMode接口,初始化向量iv的长度必须等于b的块尺寸。 func NewCBCEncrypter(b Block, iv []byte) BlockMode - 包名: cipter - 参数1: cipter.Block - 参数2: 初始化向量: initialize vector - 返回值:分组模式, 里面提供加密解密算法。 type BolckMode interface { // 返回加密字节快的大小 BlockSize() int // 加密或者解密数据块, src和dst是指向同一块内存地址的。 CryptBlocks(dst, src []byte) } */ // 输入明文和秘钥, 输出密文 func desCBCEncrypt(src , key []byte) []byte{ fmt.Printf("加密开始, 输入的数据是: %s\n", src) // 1: 创建并返回一个使用DES进行加密的cipter.Block接口 block, err := des.NewCipher(key) if err != nil{ panic(err) } // 2: 进行数据填充 src = paddingInfo(src, block.BlockSize()) // 3: 引入CBC模式: //iv := []byte("12345678") iv := bytes.Repeat([]byte("1"),block.BlockSize()) blockMode := cipher.NewCBCEncrypter(block, iv) // 4: 加密操作: blockMode.CryptBlocks(src/* 加密后的密文*/, src/* 明文*/) fmt.Printf("加密结束, 加密之后的密文是:%x\n",src) return src } ​
  • 实现填充:填充逻辑:

  • 加密:假设分组位数是8位,则如果剩余一位,则填充7个7, 如果剩余两位,则填充6个6, 如果没有剩余,则填充8个8。
  • 解密:首先判断最后一位是多少,如果是8则截取8后八位去掉,如果是7则截取后七位去掉。

  • 实现填充代码:
  • ​// 填充函数,输入明文, 分组长度,输出填充后的数据 func paddingInfo(src []byte , blockSize int) []byte{ // 1: 得到明文长度 length := len(src) // 2: 需要填充的数量 remains := length % blockSize paddingNumber := blockSize - remains // 3:把填充的数值转换成字符 s1 := byte(paddingNumber) // 4: 把字符拼接成数组 s2 := bytes.Repeat([]byte{s1}, paddingNumber) // 5: 把拼接的数组追加到src的后面 srcNew := append(src, s2...) // 6: 返回新的数组 return srcNew } ​
  • // 2: 进行数据填充
    src = paddingInfo(src, block.BlockSize())
  • 2: 在上面的 数据填充位置进行填充数据
  • 解密函数:

  • 1:解密函数编写:
    ​func desCBCDecrypt(cipherData, key []byte) []byte { // 1: 创建并返回一个使用DES进行加密的cipter.Block接口 block, err := des.NewCipher(key) if err != nil{ panic(err) } // 2: 引入CBC模式: //iv := []byte("12345678") iv := bytes.Repeat([]byte("1"),block.BlockSize()) blockMode := cipher.NewCBCDecrypter(block, iv) // 3: 进行解密操作: blockMode.CryptBlocks(cipherData/* 解密后的明文*/, cipherData/* 解密前的密文*/) fmt.Printf("解密结束, 解密之后的明文是:%x\n",cipherData) // 4: 去掉最后面的几位 cipherData = unpaddingInfo(cipherData) return cipherData } ​
  • 2:去掉最后的多余数据:
    ​func unpaddingInfo(plainText []byte) []byte{ // 1: 获取长度 length := len(plainText) if length == 0{ return []byte{} } // 2: 获取最后一个字符: lastByte := plainText[length - 1] // 3: 将字符串转换成数字 unpaddingNumber := int(lastByte) // 4: 切片获取想要的数据 return plainText[ : length - unpaddingNumber] } ​

  • 整体的DES-CBC加密与解密:
    ​package main import ( "bytes" "crypto/cipher" "crypto/des" "fmt" ) /* 需求:使用DES算法和CBC分组模式进行加密 DES算法:秘钥8bytes,分组长度:8bytes CBC分组:提供初始化向量:长度与分组长度相同。 */ // 加密分析: /* 1: 创建一个实现DES算法的cipher.Block接口, 这个接口返回一个cipter.Block - func NewCipter(key []byte)(cipter.Block, error) - 包名: des - 参数: 秘钥, 8bytes - 返回值: type Bolck interface { // 返回加密字节快的大小 BlockSize() int // 加密src的第一块数据并写入dst, src和dst是指向同一块内存地址的。 Encrypt(dst, src []byte) // 解密src的第一块数据写入dst, src和dst是指向同一块内存地址的。 Decrypt(dst, src []byte) } 2: 进行数据填充: 3: 引入CBC模式接口,而这个接口返回一个密码分组链接模式的, 底层使用b加密的BlockMode接口,初始化向量iv的长度必须等于b的块尺寸。 func NewCBCEncrypter(b Block, iv []byte) BlockMode - 包名: cipter - 参数1: cipter.Block - 参数2: 初始化向量: initialize vector - 返回值:分组模式, 里面提供加密解密算法。 type BolckMode interface { // 返回加密字节快的大小 BlockSize() int // 加密或者解密数据块, src和dst是指向同一块内存地址的。 CryptBlocks(dst, src []byte) } */ // 输入明文和秘钥, 输出密文 func desCBCEncrypt(src , key []byte) []byte{ fmt.Printf("加密开始, 输入的数据是: %s\n", src) // 1: 创建并返回一个使用DES进行加密的cipter.Block接口 block, err := des.NewCipher(key) if err != nil{ panic(err) } // 2: 进行数据填充 src = paddingInfo(src, block.BlockSize()) // 3: 引入CBC模式: //iv := []byte("12345678") iv := bytes.Repeat([]byte("1"),block.BlockSize()) blockMode := cipher.NewCBCEncrypter(block, iv) // 4: 加密操作: blockMode.CryptBlocks(src/* 加密后的密文*/, src/* 明文*/) fmt.Printf("加密结束, 加密之后的密文是:%x\n",src) return src } // 填充函数,输入明文, 分组长度,输出填充后的数据 func paddingInfo(src []byte , blockSize int) []byte{ // 1: 得到明文长度 length := len(src) // 2: 需要填充的数量 remains := length % blockSize paddingNumber := blockSize - remains // 3:把填充的数值转换成字符 s1 := byte(paddingNumber) // 4: 把字符拼接成数组 s2 := bytes.Repeat([]byte{s1}, paddingNumber) // 5: 把拼接的数组追加到src的后面 srcNew := append(src, s2...) // 6: 返回新的数组 return srcNew } /* 解密分析: 1: 创建一个实现DES算法的cipher.Block接口, 这个接口返回一个cipter.Block 2: 引入CBC模式接口 3: 进行解密操作 4: 去除填充 */ func desCBCDecrypt(cipherData, key []byte) []byte { // 1: 创建并返回一个使用DES进行加密的cipter.Block接口 block, err := des.NewCipher(key) if err != nil{ panic(err) } // 2: 引入CBC模式: //iv := []byte("12345678") iv := bytes.Repeat([]byte("1"),block.BlockSize()) blockMode := cipher.NewCBCDecrypter(block, iv) // 3: 进行解密操作: blockMode.CryptBlocks(cipherData/* 解密后的明文*/, cipherData/* 解密前的密文*/) fmt.Printf("解密结束, 解密之后的明文是:%x\n",cipherData) // 4: 去掉最后面的几位 cipherData = unpaddingInfo(cipherData) return cipherData } func unpaddingInfo(plainText []byte) []byte{ // 1: 获取长度 length := len(plainText) if length == 0{ return []byte{} } // 2: 获取最后一个字符: lastByte := plainText[length - 1] // 3: 将字符串转换成数字 unpaddingNumber := int(lastByte) // 4: 切片获取想要的数据 return plainText[ : length - unpaddingNumber] } func main(){ src := []byte("12345678asdadsa") key := []byte("12345678") cipherData := desCBCEncrypt(src, key) fmt.Printf("cipherData: %x\n", cipherData) plainText := desCBCDecrypt(cipherData, key) fmt.Printf("解密后的数据是:%s\n", plainText) } ​


2.2:3DES算法:


  • 1: DES后来被破解的时间越来越短,因此有一种思路就是进行三次加密,提高被破解的时间。
  • 2:加密与解密过程:

  • 加密:明文—>加密—>解密—>加密—>密文。

  • 中间使用解密的原因是为了兼容之前的DES。
  • 加密过程是以解密的方式进行加密,整体还是三次加密。
  • 秘钥长度:8B * 3 = 24B = 24 * 8 = 192b
  • 秘钥的分组:与DES算法相同。

  • 解密:密文—>解密—>加密—>解密—>明文。
  • 3个秘钥:

  • 如果秘钥1等于秘钥2,或者秘钥2等于秘钥三,则3DES其实就是DES。
  • 如果秘钥1与秘钥3相同,相当于两个秘钥。专业名词:3des-EDE2
  • 如果三个秘钥都不相同,则专业名词:3des-EDE3。



2.3:AES算法:


  • 3DES存在的问题:
  • 加密效率很低。
  • AES特点:

  • 秘钥长度可以选择:16b, 24b, 32b。
  • 分组长度:16b(是DES的一倍)
  • 效率高。

  • AES-CTR加密代码:

  • 加密解密分析:

  • AES秘钥可以选择长度,我们选择16,则秘钥长度也是16。
  • CTR不需要提供向量,但是需要提供数字。

  • 加密代码:
    ​package main import ( "bytes" "crypto/aes" "crypto/cipher" "fmt" ) /* 加密分析: 1: 创建一个cipter.Block接口 2: 选择一个分组模式: CTR 3: 加密 4: 返回加密数据 */ func aesCTREncrypt(src, key []byte) []byte { // 1: 创建一个cipter.Block接口 block, err := aes.NewCipher(key) if err != nil{ panic(err) } // 2: 选择分组模式: iv := bytes.Repeat([]byte("1"), block.BlockSize()) stream := cipher.NewCTR(block, iv) // 3: 进行加密操作: stream.XORKeyStream(src/*密文*/, src/*明文*/) // 4: 返回加密的数据 return src } func main(){ // 1: 准备加密数据 src := []byte("任稻草") // 2: 准备加密秘钥: 秘钥必须16 key := []byte("1234567812345678") // 3: 调用加密函数,得到密文 cipherData := aesCTREncrypt(src, key) fmt.Printf("加密后的数据是:%x\n", cipherData) } ​
  • 解密代码:
    ​func aesCTRDecrypt(cipherData, key []byte) []byte { // 1: 创建一个cipter.Block接口 block, err := aes.NewCipher(key) if err != nil{ panic(err) } // 2: 选择分组模式: iv := bytes.Repeat([]byte("1"), block.BlockSize()) stream := cipher.NewCTR(block, iv) // 3: 进行解密操作: stream.XORKeyStream(cipherData/*明文*/, cipherData/*密文*/) // 4: 返回加密的数据 return cipherData } func main(){ // 1: 准备加密数据 src := []byte("任稻草") // 2: 准备加密秘钥: 秘钥必须16 key := []byte("1234567812345678") // 3: 调用加密函数,得到密文 cipherData := aesCTREncrypt(src, key) fmt.Printf("加密后的数据是:%x\n", cipherData) // 4: 调用解密函数进行解密 plainText := aesCTRDecrypt(cipherData, key) fmt.Printf("解密后的数据是:%s\n", plainText) } ​


三:非对称加密:

3.1:非对称加密流程与特点:


  • 流程:

  • A,B分别产生自己的公钥和私钥。
  • A, B双方将自己的公钥发送给对方。
  • A与B进行通信:A使用B的公钥进行加密,B使用自己的私钥进行解密。
  • B与A进行通信:B使用A的公钥进行加密,A使用自己的私钥进行解密。

  • 特点:

  • 通信双方,都有自己的公钥,私钥。
  • 安全性高。
  • 加密效率低。


3.2: 对称加密存在的问题:


  • 1: 秘钥管理困难:
    对于一个对称加密:A要跟100个分进行通信,则需要管理100个秘钥。我要找到哪个才是咱俩进行通信的才能加解密。而对于非对称加密,我保存的是我自己的私钥和你传给我的公钥,无论我要跟谁发送消息,我只需要使用自己私钥进行加密,而谁跟我交流,我用对应人的公钥解密就可以了。
  • 2: 秘钥分发困难:
    对于对称加密,我们要保证传输过程中,秘钥不被窃取。而非对称加密,只有窃取双方的公钥,第三个人才只是拥有看信息的权利,也无法进行交流。只有将双方的私钥还要拿到,才能进行加密,伪造信息。

3.3: 非对称加密的使用场景:


  • 1:通信加密
  • 2:https
  • 3:网银的U盾
  • 4:github ssh登录
  • 5: 签名(哈希 + 非对称加密)

3.4:OpenSSL生成非对称加密秘钥:


  • openssl下载:
  • 将bin目录加入到windows全局配置来。
  • 使用openssl生成公钥和私钥:

  • 切换到存储公钥和私钥的文件夹。
  • windows终端中输入:openssl进入 OpenSSL终端。
  • 执行命令生成自己的私钥: genrsa -out rsa_private_key.pem
  • 执行命令根据私钥生成公钥: rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem


密码学概述论_对称加密_04

四:非对称加密算法:

4.1:RSA生成公钥和私钥:


  • 1:加密过程

  • 1:对加密的明文字符根据字符对应表转化成数字值。
  • 2:对数值依次进行E次方处理。
  • 3:对N取模。
  • 由E和N组成了公钥。

  • E: 根据特定的规则,限定了一个区间, 在这个区间内随意选用的。
  • N: 素数的乘积


  • 2: 解密过程:

  • 1:对密文进行D次方处理
  • 2:对N进行取模
  • 3:根据字符表恢复成明文。
  • D和N组成我们的私钥:

  • D: 如何获取D的值才是最难的,因为只有知道是哪两个大素数才能计算出D。
  • N:素数的乘积。


  • 3:RSA安全的原因:
  • 基于对大素数进行因式分解(世界公认难题)
  • 4:Go创建RSA公钥和私钥

  • 生成私钥分析:
    ​/* 需求: 生成并保存公钥私钥和公钥: 生成私钥分析: 1:使用GenerateKey函数使用随机数生成器random生成一对具有指定字位数的RSA秘钥 func GenerateKey(random io.Reader, bits int)(priv *PrivateKey, err error) - 参数1: 随机数 - 参数2: 秘钥长度 - 返回值: 私钥 2:对于生成的私钥进行编码处理, x509规则,按照这个规则,进行序列化处理, 生成der编码的数据。 MarshalPKIXPublicKey将公钥序列化成PKIX格式DER编码。 func MarshalPKCSPublicKey(pub interface{})([]byte, error) 3:创建一个Block结构,并填入私钥。(目的是生成上面虚线,下面虚线的格式) type Block struct { Type string Headers map[string]string Bytes []byte } 4: 将Pem Block格式写入到磁盘文件中。 */ ​
  • 生成公钥分析:
    ​/* 1: 通过私钥获取公钥 2:对生成的公钥进行der 3:创建Block结构,将Block结构加入到der编码中 4:将Pem Block数据写入到磁盘文件中。 */ ​
  • 生成私钥和公钥代码:
    ​package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "os" ) const PrivateKeyFile = "./privateKey.pem" const PublicKeyFile = "./PublicKey.pem" func generateKeyPair(bits int) error{ // 1: GenerateKey函数使用随机数生成器random生成RSA私钥 privateKey, err := rsa.GenerateKey(rand.Reader, bits) // 注意这里导包需要导入:"crypto/rand", 不能导入math/rand if err != nil{ return err } // 2: 对于生成的私钥进行编码处理,生成der编码后的数据 priDerText := x509.MarshalPKCS1PrivateKey(privateKey) // 3: 创建一个Block, 把der编码后的数据加入到Block中 block := pem.Block{ Type: "RSA PRIVATE KEY", Headers: nil, Bytes: priDerText, } // 4: 将Block写入到磁盘文件中 filehandler1, err := os.Create(PrivateKeyFile) defer filehandler1.Close() err = pem.Encode(filehandler1, &block) if err != nil{ return err } /* 1: 通过私钥获取公钥 2:对生成的公钥进行der 3:创建Block结构,将Block结构加入到der编码中 4:将Pem Block数据写入到磁盘文件中。 */ // 1: 通过私钥获取公钥,得到的是对象 pubKey := privateKey.PublicKey // 2: 对公钥进行der编码 pubKeyDerText := x509.MarshalPKCS1PublicKey(&pubKey) // 3: 将der后的结构,放入到Block中 block1 := pem.Block{ Type: "RSA Public Key", Headers: nil, Bytes: pubKeyDerText, } // 4: 写入磁盘: filehandler2, err := os.Create(PublicKeyFile) err = pem.Encode(filehandler2, &block1) defer filehandler2.Close() if err != nil{ return err } return nil } func main(){ // 1: 生成私钥 fmt.Printf("生成的私钥") err := generateKeyPair(1024) if err != nil{ fmt.Printf("生成私钥出错了!!") }else{ fmt.Printf("生成私钥成功了!!") } } ​


4.2:RSA进行加密和解密


  • 公钥加密流程:
    ​package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "io/ioutil" ) const PrivateKeyFile = "./privateKey.pem" const PublicKeyFile = "./PublicKey.pem" func rsaPubEncrypt(filename string, plainText []byte) (error, []byte) { // 1: 通过公钥文件读取公钥信息 info , err := ioutil.ReadFile(filename) if err != nil{ return err, []byte{} } // 2: 在读到的Block中获取中间的内容 block , _ := pem.Decode(info) // 返回值1:pem.Block // 返回值2:rest参加是没有没有解码完的数据,存储在这里 // 3: 解码der, 得到公钥 derText := block.Bytes publicKey, err := x509.ParsePKCS1PublicKey(derText) if err != nil{ return err, []byte{} } // 4: 使用公钥进行加密 // func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) cipherData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) if err != nil{ return err, []byte{} } return nil, cipherData } func main(){ src := []byte("稻草任") err, cipherData := rsaPubEncrypt(PublicKeyFile, src) if err != nil{ fmt.Println("公钥加密失败") } fmt.Printf("公钥加密结果是:%x\n", cipherData) } ​
  • 私钥解密流程:
    ​func rsaPriKeyDecrypt(filename string, cipherData[]byte) (error, []byte) { // 1: 通过私钥文件读取私钥信息 info , err := ioutil.ReadFile(filename) if err != nil{ return err, []byte{} } // 2: 在读到的Block中获取中间的内容 block , _ := pem.Decode(info) // 返回值1:pem.Block // 返回值2:rest参加是没有没有解码完的数据,存储在这里 // 3: 解码der, 得到私钥 derText := block.Bytes privateKey, err := x509.ParsePKCS1PrivateKey(derText) if err != nil{ return err, []byte{} } // 4: 使用私钥进行解密 // func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) plainText, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherData) if err != nil{ return err, []byte{} } return nil, plainText } func main(){ src := []byte("稻草任") err, cipherData := rsaPubEncrypt(PublicKeyFile, src) if err != nil{ fmt.Println("公钥加密失败") } fmt.Printf("公钥加密结果是:%x\n", cipherData) err, plainText := rsaPriKeyDecrypt(PrivateKeyFile, cipherData) if err != nil{ fmt.Println("私钥解密失败") } fmt.Printf("私钥解密结果是:%s\n", plainText) } ​

4.3: Base64进行编解码:

  • Base64编码原理:

  • 编码规则: 先将传入的字符---->转换成ASCII码---->每个ASCII码是8位---->以6位为一组得到一个数字---->在编码表中查找对应值,得到编码后的数。
  • 如果最后不足怎么办?
    一般来说是将三个字节转换成四个字节,但是过程中可能出现剩余情况,此时要补零,并且空余字节用等号代替。所以编码最后如果存在剩余,要么一个等号,要么两个等号。
    密码学概述论_非对称加密_05
  • 存在一个编码表:

  • 普通的字符集(A-Z, a-z, 0-9, +,/)
  • url专用字符集(A-Z, a-z, 0-9, _,-)


密码学概述论_对称加密_06

  • GO语言实现Base64编解码:

  • 标准的Base64编码:
  • Url的Base64编码:
    ​package main import ( "encoding/base64" "fmt" ) func main(){ info := []byte("我是一夜奈何梁山") // 1: 进行Base64编码 encodeInfo := base64.StdEncoding.EncodeToString(info) fmt.Printf("使用Base64进行编码后的结果是:%s\n",encodeInfo ) // 进行解码: decode_info , err := base64.StdEncoding.DecodeString(encodeInfo) if err != nil{ panic(err) } fmt.Printf("使用Base64进行解码后的结果是:%s\n", decode_info) // 2: 使用Base64url方式进行编码 info2 := []byte("https://www.bilibili.com/video/BV1e4411H7vq?p=49") encodeInfo2 := base64.URLEncoding.EncodeToString(info2) fmt.Printf("使用Base64进行Url编码的结果是:%s\n", encodeInfo2) // 进行解码 decode_info2 , err := base64.URLEncoding.DecodeString(encodeInfo2) fmt.Printf("使用Base64进行Url解码的结果是:%s\n", decode_info2) } ​

4.4:哈希(单向散列函数):


  • 哈希: 可以对输入的内容生成一个唯一的数值。

  • 输入的内容不变,输出的内容不变。
  • 输入的内容改变一点,输出的内容千差万别。
  • 无论输入的内容多大,生成的哈希长度是相等的。
  • 哈希运算是对输入的内容进行摘要(指纹), 无法根据哈希值推断出原文。

  • 哈希的应用场景:

  • 1: 检测软件是否篡改。
    对于一个软件的下载,我们官方一般提供一个哈希值,当我们下载完成后,可以对安装的exe文件进行哈希运算,得到的值如果跟官网的一致,则传输过程中没有被篡改。
  • 2:消息认证码:
  • 3:伪随机数生成器
  • 4:一次性口令:
  • 5:密码存储:
    为了方式密码被盗,数据库一般存储密文(哈希加密后的值)
  • 6:数字签名:


4.5:MD5加密(哈希):


  • 数据量多的时候进行哈希处理:
    ​package main import ( "crypto/md5" "fmt" "io" ) func main(){ // 对少量数据进行哈希运算 // 1: 创建一个哈希器 hasher := md5.New() // 2:向哈希中加入数据 io.WriteString(hasher, "hello") io.WriteString(hasher, "word") // 2: 执行SUm操作,得到哈希值 hash := hasher.Sum(nil) fmt.Printf("生成的哈希值是:%x\n", hash) //生成的哈希值是:59284aa85709ddaf3bd246030060f6a2 } 如果 hasher.Sum(nil),中的nil是其他的字节数据例如0x,则会加入到哈希值的前面,根本不影响哈希值。 ​
  • 数据量少的时候:
    ​package main import ( "crypto/md5" "fmt" ) func main(){ // 1:准备数据 src := []byte("我是一页奈何梁山") // 2:进行哈希运算:返回的是数组类型 hash := md5.Sum(src) fmt.Printf("MD5加密后的哈希值是:%x\n", hash) // MD5加密后的哈希值是:61d6b87120ce5fd3ee27f746ee1a4c90 } ​

4.6:sha运算(哈希):


  • sha类的哈希运算分为两类: sha1, sah2
  • 最常用的是sha256(比特币,以太坊)

4.7:消息认证码(MAC):


  • 场景分析: A与B进行通信,假设A向B发送了一个消息,B如何才能判断是A发送过来的,并且如何判断A发送过来的信息没有被篡改呢?
  • 思考: 使用对称加密可以吗?
    A明文----->加密---->密文----->发送给B---->B解密---->明文
    假设发送的是“你好”, 没有篡改解密是“你好”, 假设篡改后解密是“**XX()”乱码,因为是乱码,我们判断被篡改了。但是如果发送的本身就是乱码,解密出来,我如何判断是不是被篡改了呢?我无法进行判断。因此这就需要消息认证(哈希)
  • 消息认证码如何判断没有消息没有被篡改?
  • 图示:

  • 1: A与B之间协商消息认证码秘钥。
  • 2:A将消息经过对称加密秘钥加密,得到密文。
  • 3: A将密文经过消息认证码秘钥计算出哈希值
  • 4:A将这个哈希值与密文同时发送给B。
  • 5:B拿到密文,使用消息验证码秘钥对密文进行哈希得到哈希值,
  • 6:对比两个哈希值是否相同,判断信息是否被篡改。
  • 7:如果哈希值一直说明没有篡改,则使用对称加密秘钥进行解密。


密码学概述论_数据_07


  • 消息认证码的用处:

  • SWIFT:
  • HTTPS:(重点)
  • IPSec:

  • HMAC函数(哈希MAC)
    ​package main import ( "crypto/hmac" "crypto/sha512" "fmt" ) // 生成hmac(消息认证码的函数) // 传入字节类型的数据--->返回哈希处理后的结果 func generateHMAC(src []byte, key []byte) []byte { // 1: 创建哈希器 hasher := hmac.New(sha512.New, key) // 2: 将数据加入哈希器中 hasher.Write(src) // 2: 生成哈希值 mac := hasher.Sum(nil) return mac } // 消息认证码的认证: func verifyHMC(src , key, mac1 []byte) bool { // 1: B接收到的数据---src // 2: B接收到的mac1 ---mac1 // 3: 对B到的数据进行哈希处理,得到mac2 mac2 := generateHMAC(src, key) // 4: 对比两个mac值是否相同。 return hmac.Equal(mac1, mac2) } func main(){ // 1: 生成明文和秘钥 src := []byte("我是一夜奈何梁山") key := []byte("1234567890") // 2: 生成mac1 mac1 := generateHMAC(src, key) // 3: 判断消息认证码是否相同 isEqual := verifyHMC(src, key, mac1) if isEqual{ fmt.Println("验证通过,没有被修改") }else{ fmt.Println("验证失败,中间有人被修改了") } } ​

4.8:数字签名:


  • 消息认证存在的问题:

  • 1:消息认证码的秘钥配送问题。
  • 2:无法进行第三方证明。
  • 3:无法防止发送方否认。

  • 数字签名的流程:

  • 1:对原文进行哈希运算得到一个哈希值。
  • 2:使用私钥对这个哈希值进行处理,得到一个签名。
  • 3:将原文和签名一起发送给对方。

  • 数字签名的认证流程:

  • 1:接收到原文,对原文进行哈希处理,得到一个哈希值。
  • 2:使用公钥对签名进行解密,得到另外一个哈希值。
  • 3:对比两个哈希值,可以知道是否被篡改了。

  • 图示:

密码学概述论_对称加密_08

  • 解决消息认证的问题:

  • 无法有效配送秘钥—>数字签名中,不需要协商秘钥,因此没有配送问题。
  • 无法进行第三方证明----->任何持有公钥的,都能帮助认证。
  • 无法防止发送方否认----->私钥只有发送方有,无法进行抵赖。

4.9:ECC椭圆曲线:


  • 使用ECC椭圆曲线生成秘钥特点:
  • 1: ECC164位的密钥产生一个安全级,相当于RSA 1024位密钥提供的保密强度 。
  • 2: 计算量较小,处理速度更快,存储空间和传输带宽占用较少。
  • ECC椭圆曲线生成秘钥的用途:

  • 1: 居民二代身份证。
  • 2:比特币

  • 使用ECC椭圆曲线生成公钥和私钥:
    ​package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "os" ) const EccPrivateKeyFile = "./EccPrivateKey.pem" const EccPublicKeyFile = "./EccPublicKey.pem" func generateEccKeypair(){ // 1: 选择一个椭圆曲线 curve := elliptic.P256() // 2: 使用ecdsa包,创建私钥 privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil{ panic(err) } // 3:使用x509进行编码,序列化成DER编码 derText , err:= x509.MarshalECPrivateKey(privateKey) if err != nil{ panic(err) } // 4: 将编码后的DER编码写入Block中 block1 := pem.Block{ Type: "ECC PRIVATE KEY", Headers: nil, Bytes: derText, } // 5:将私钥写入到文件中 fileHander, err := os.Create(EccPrivateKeyFile) // 填充数据 if err != nil{ panic(err) } // 关闭上下文 defer fileHander.Close() // 写入到文件中 err = pem.Encode(fileHander, &block1) if err != nil{ panic(err) } // 1: 根据私钥生成公钥 publicKey := privateKey.PublicKey // 2:进行编码 derText1, err := x509.MarshalPKIXPublicKey(&publicKey) if err != nil{ panic(err) } // 4: 将编码后的DER编码写入Block中 block2 := pem.Block{ Type: "ECC Pubclic KEY", Headers: nil, Bytes: derText1, } // 5:将私钥写入到文件中 fileHander2, err := os.Create(EccPublicKeyFile) // 填充数据 if err != nil{ panic(err) } // 关闭上下文 defer fileHander2.Close() // 写入到文件中 err = pem.Encode(fileHander2, &block2) if err != nil{ panic(err) } } func main(){ generateEccKeypair() } ​
  • 使用ECC椭圆曲线: 私钥进行签名,公钥进行认证:
  • ​package main import ( "crypto/ecdsa" "crypto/rand" "crypto/sha256" "crypto/x509" "encoding/pem" "errors" "fmt" "io/ioutil" "math/big" ) const EccPrivateKeyFile = "./EccPrivateKey.pem" const EccPublicKeyFile = "./EccPublicKey.pem" /* 使用私钥进行签名 1:读取私钥,解码 2: 对原文进行哈希,得到哈希值 3:对哈希值使用私钥进行签名 */ // 自定义签名结构: type Signature struct { r *big.Int s *big.Int } // 私钥签名: func eccSignData(filename string, src []byte) (Signature, error) { // 1: 读取私钥,解码 info , err := ioutil.ReadFile(filename) if err != nil{ return Signature{} , err } block, _ := pem.Decode(info) derText := block.Bytes privateKey , err := x509.ParseECPrivateKey(derText) if err != nil { return Signature{} , err } // 2: 对原文进行哈希,得到哈希值 hash := sha256.Sum256(src) // 3: 使用私钥对哈希值进行签名 r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash[:]) if err != nil{ return Signature{}, err } sig := Signature{r, s} return sig, nil } /* 1: 读取公钥,解码 2:对原文生成哈希 3:使用公钥进行验证 */ // 公钥认证 func eccVerifySig(filename string, src []byte, sig Signature) error{ // 1:读取公钥 info , err := ioutil.ReadFile(filename) if err != nil { return nil } // 2: 在block中获取被der编码的数据 block , _ := pem.Decode(info) // 3: 解码der,拿到公钥 derText := block.Bytes publicKeyInterface , err := x509.ParsePKIXPublicKey(derText) if err != nil{ return err } publicKey, ok := publicKeyInterface.(*ecdsa.PublicKey) if !ok { return errors.New("断言失败,不是ECDS公钥") } // 4: 对原文进行哈希处理 hash := sha256.Sum256(src) // 5: 使用公钥验证哈希值和两个大数r,s,返回签名是否合法。 isValid := ecdsa.Verify(publicKey, hash[:], sig.r, sig.s) if !isValid{ return errors.New("校验失败") }else{ return nil } } func main(){ // 1: 调用私钥签名函数返回签名 src := []byte("我是一夜奈何梁山") sig , err := eccSignData(EccPrivateKeyFile, src) if err != nil{ panic(err) } fmt.Println("私钥进行的签名是:", sig.r, sig.s) // 2: 进行校验 err = eccVerifySig(EccPublicKeyFile, src, sig) if err != nil{ fmt.Println(err) return } fmt.Println("校验成功!!") } ​
  • 在GO语言中无法使用ECC进行加解密, 但是支持ECC进行签名。

4.11:数字证书:

  • 为什么需要数字证书?

  • 非对称加密首先要进行公钥交换,那么我怎么才能确定,我拿到的是你的公钥,而不是被篡改的公钥呢?
  • 图示:

密码学概述论_对称加密_09

  • 解决方案:引入第三方认证机构CA

4.12:HTTPS流程分析:


  • HTTPS = HTTP +SSL
  • SSL :
  • CA颁发过程:

  • 1: 服务器提供者生成公钥和私钥,将公钥发送给CA机构。
  • 2:CA机构也会有自己的私钥和公钥,CA使用自己的私钥对服务器的公钥进行数字签名。
  • 3:CA机构将数字证书发送给服务器。

  • CA认证过程:

  • 1:浏览器给服务器发送请求, 服务器将数字证书发送给浏览器。
  • 2:客户端的浏览器安装了知名CA机构的根证书,包含了CA机构的公钥, 浏览器对服务器的证书进行验证。
  • 3:如果验证成功,则表示网站可信,如果认证失败则提示警告。

  • HTTPS通信的过程:

  • 1:CA认证过程
  • 2:如果证书有效,则浏览器将自己支持的加密算法发送给服务器,同时生成一个对称加密秘钥,使用服务器的公钥,对对称加密秘钥进行加密,然后发送给服务器。
  • 3:服务器使用自己的私钥,对密文进行解密,得到对称加密秘钥。
  • 4:以后双方通信都使用对称加密秘钥进行加密与解密。

  • windows下查看CA证书:
  • 过程图示:

密码学概述论_对称加密_10

密码学概述论_对称加密_11