密码学概述论
原创
©著作权归作者所有:来自51CTO博客作者mb61037a3723f67的原创作品,请联系作者获取转载授权,否则将追究法律责任
密码学基础
一:对称加密:
1.1: 对称加密/解密三要素:
- 1: 对称加密三要素: 明文,加密算法, 秘钥。
- 2: 对称解密三要素:密文,解密算法,秘钥。
1.2:凯撒密码:
- 凯撒密码加密流程: 将小写字母转化成大写字母,然后向右移动指定位数。
- 凯撒密码解密流程:将大写字母,向左移动指定位数,然后转化成小写字母。
1.3:对称加密的流程与特点:
- A使用秘钥将明文加密成密文。
- B使用秘钥将密文解密成明文。
- 加密过程只有一把秘钥。
- 加密效率比非对称加密高。
- 安全性比非对称加密低。
1.4:编码与加密:
- 1Byte = 8bit
- 1K = 1024B
- 1M = 1024K
- 1G = 1024M
- 1T = 1024G
- 1P = 1024T
- 编码: 由字符的形式变成二进制的形式。
- 解码:由二进制的形式变成字符的形式。
- 加密:明文比特—>密文比特。
- 解密:密文比特序列—>明文比特序列。
1.5:分组:
- 1:明文分组:加密之前的分组。
- 2:密文分组:加密之后的分组。
- 3:明文分组和密文分组是等长的。
- 4: 五中分组模式:
- 1: 先对初始向量进行加密处理(密码)
- 2:分组后每组的数据与密码进行异或操作。
- 分组长度取决于加密算法。
- 不断对初始向量的输出进行加密,从而得到数据来源。
- 不需要进行数据填充。
- 加密算法与分组的关系:
- 图示
- 流程:
- 1: 首先使用计数器生成一个8位的随机数,然后看看有几个分组,后面的计数器执行+1操作。
- 2:使用加密算法对计数器进行加密。
- 3:将加密后的密码与明文进行异或操作生成密文。
二:对称加密算法:
2.1:DES算法:
- 将64位比特的明文加密成64位比特密文的对称加密算法。
- 秘钥长度是56比特,少8位的原因是最后一位是校验位。
- 特点:加密的时候先对数据进行分组,每64位 分一组。
- 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
}
src = paddingInfo(src, block.BlockSize())
- 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算法:
- 秘钥长度可以选择:16b, 24b, 32b。
- 分组长度:16b(是DES的一倍)
- 效率高。
- 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生成非对称加密秘钥:
- 切换到存储公钥和私钥的文件夹。
- windows终端中输入:openssl进入 OpenSSL终端。
- 执行命令生成自己的私钥: genrsa -out rsa_private_key.pem
- 执行命令根据私钥生成公钥: rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
四:非对称加密算法:
4.1:RSA生成公钥和私钥:
- 1:对加密的明文字符根据字符对应表转化成数字值。
- 2:对数值依次进行E次方处理。
- 3:对N取模。
- 由E和N组成了公钥。
- E: 根据特定的规则,限定了一个区间, 在这个区间内随意选用的。
- N: 素数的乘积
- 1:对密文进行D次方处理
- 2:对N进行取模
- 3:根据字符表恢复成明文。
- D和N组成我们的私钥:
- D: 如何获取D的值才是最难的,因为只有知道是哪两个大素数才能计算出D。
- N:素数的乘积。
- 生成私钥分析:
/*
需求: 生成并保存公钥私钥和公钥:
生成私钥分析:
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进行编解码:
- 编码规则: 先将传入的字符---->转换成ASCII码---->每个ASCII码是8位---->以6位为一组得到一个数字---->在编码表中查找对应值,得到编码后的数。
- 如果最后不足怎么办?
一般来说是将三个字节转换成四个字节,但是过程中可能出现剩余情况,此时要补零,并且空余字节用等号代替。所以编码最后如果存在剩余,要么一个等号,要么两个等号。
- 存在一个编码表:
- 普通的字符集(A-Z, a-z, 0-9, +,/)
- url专用字符集(A-Z, a-z, 0-9, _,-)
- 标准的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:如果哈希值一直说明没有篡改,则使用对称加密秘钥进行解密。
- 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:对比两个哈希值,可以知道是否被篡改了。
- 无法有效配送秘钥—>数字签名中,不需要协商秘钥,因此没有配送问题。
- 无法进行第三方证明----->任何持有公钥的,都能帮助认证。
- 无法防止发送方否认----->私钥只有发送方有,无法进行抵赖。
4.9:ECC椭圆曲线:
- 使用ECC椭圆曲线生成秘钥特点:
- 1: ECC164位的密钥产生一个安全级,相当于RSA 1024位密钥提供的保密强度 。
- 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:数字证书:
- 非对称加密首先要进行公钥交换,那么我怎么才能确定,我拿到的是你的公钥,而不是被篡改的公钥呢?
- 图示:
4.12:HTTPS流程分析:
- HTTPS = HTTP +SSL
- SSL :
- CA颁发过程:
- 1: 服务器提供者生成公钥和私钥,将公钥发送给CA机构。
- 2:CA机构也会有自己的私钥和公钥,CA使用自己的私钥对服务器的公钥进行数字签名。
- 3:CA机构将数字证书发送给服务器。
- 1:浏览器给服务器发送请求, 服务器将数字证书发送给浏览器。
- 2:客户端的浏览器安装了知名CA机构的根证书,包含了CA机构的公钥, 浏览器对服务器的证书进行验证。
- 3:如果验证成功,则表示网站可信,如果认证失败则提示警告。
- 1:CA认证过程
- 2:如果证书有效,则浏览器将自己支持的加密算法发送给服务器,同时生成一个对称加密秘钥,使用服务器的公钥,对对称加密秘钥进行加密,然后发送给服务器。
- 3:服务器使用自己的私钥,对密文进行解密,得到对称加密秘钥。
- 4:以后双方通信都使用对称加密秘钥进行加密与解密。