密码学基础
一:对称加密:
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: 五中分组模式:
-
ECB : 电子密码本
-
原理:先将原来的数据进行分组,然后对于分组的数据进行加密。如果分组最后一块有缺失,则会进行数据补充。
-
缺点:加密不安全。原因:如果是对于分组进行加密则同一个数据加密的结果是固定的,则如果我们使用计算机对于指定的数据加密,很容易找到规律,进行破解。
-
例如: AAA加密后是CCC, 我们只要得到CCC数据就知道他就是AAA。
-
特点:
- 加密效率高,但是加密不彻底。
- 需要对数据进行分组填充。
- 每个分组独立进行加密,解密。
- 只要有一个分组被破解了,则所有的分组都会被破解了。
- 不使用,GO语言不支持这种分组模式。
-
-
CBC: 密文分组链接模式(常用)
-
与或非概念:
按位操作符号 逻辑操作符号 与 & && 或 | || 非 - ! -
按位操作案例:
A:8,0000,1000
B : 9, 0000, 1001
A & B:(与操作:11才是1,其余全0)
0000, 1000
0000, 1001
&
0000, 1000
-
异或加密:(相同为0,不同为1)
0000, 1000 —>明文
0000, 1001 ---->秘钥
XOR ---->异或操作
0000, 0001 ----->密文
- 对于密文,再次进行异或操作,则可恢复成明文。
-
CBC模式的流程:
- 1:先对于明文进行分组
- 2:使用前一组加密的结果与当前组进行异或操作。
- 3:将异或操作的结果进行加密。
- 4:第一组加密的时候需要提供一个初始化向量(与分组长度相同的)
-
-
CBC加密的特点:
- 数据长度根据算法而定。
- 需要提供初始化向量,要求长度必须和分组长度相同。
- 每一个密文都是下一次加密操作的输入。
- 不能并行加密,但是可以并行解密。
- 加密强度高
- 如果数据不足,需要进行填充。
-
-
CFB:密文反馈模式
- 与CBC差不多,只不过是将异或操作与加密操作的顺序倒置了。
- 由于没有对明文直接进行加密,所以分组时,不需要填充。
-
OFB : 输出反馈模式
-
流程:
- 1: 先对初始向量进行加密处理(密码)
- 2:分组后每组的数据与密码进行异或操作。
-
特点:
- 分组长度取决于加密算法。
- 不断对初始向量的输出进行加密,从而得到数据来源。
- 不需要进行数据填充。
-
图示:
-
-
CTR:计数器模式(常用)
-
加密算法与分组的关系:
-
图示
-
流程:
-
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则截取后七位去掉。
-
实现填充代码:
- 1: 增加这样的一个函数:
// 填充函数,输入明文, 分组长度,输出填充后的数据 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: 在上面的 数据填充位置进行填充数据
// 2: 进行数据填充 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算法:
-
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下载:
- x下载地址:http://slproweb.com/products/Win32OpenSSL.html
- 将bin目录加入到windows全局配置来。
-
使用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:加密过程
- 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位为一组得到一个数字---->在编码表中查找对应值,得到编码后的数。
-
如果最后不足怎么办?
一般来说是将三个字节转换成四个字节,但是过程中可能出现剩余情况,此时要补零,并且空余字节用等号代替。所以编码最后如果存在剩余,要么一个等号,要么两个等号。
-
存在一个编码表:
- 普通的字符集(A-Z, a-z, 0-9, +,/)
- url专用字符集(A-Z, a-z, 0-9, _,-)
-
-
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:如果哈希值一直说明没有篡改,则使用对称加密秘钥进行解密。
图示:
-
消息认证码的用处:
- 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:对比两个哈希值,可以知道是否被篡改了。
-
图示:
- 解决消息认证的问题:
- 无法有效配送秘钥—>数字签名中,不需要协商秘钥,因此没有配送问题。
- 无法进行第三方证明----->任何持有公钥的,都能帮助认证。
- 无法防止发送方否认----->私钥只有发送方有,无法进行抵赖。
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椭圆曲线: 私钥进行签名,公钥进行认证:
- 在GO语言中无法使用ECC进行加解密, 但是支持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("校验成功!!") }
4.11:数字证书:
-
为什么需要数字证书?
-
非对称加密首先要进行公钥交换,那么我怎么才能确定,我拿到的是你的公钥,而不是被篡改的公钥呢?
-
图示:
-
- 解决方案:引入第三方认证机构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证书:
- 过程图示: