GO语言密码学

推荐网站:https://studygolang.com/pkgdoc

对称加密和非对称加密

对称加密

  1. 秘钥数量:1个
  2. 特点:
    1. 加密效率高
    2. 双方使用的秘钥相同
  3. 安全性:相对于非对称加密安全性低
  4. 使用情况:主流的加密方式

非对称加密

  1. 秘钥数量:2个
    1. 公钥:任何人都可以使用,用于加密
    2. 私钥:只有自己持有,用于签名,可以证明私钥持有人发送的数据
  2. 特点:
    1. 公钥加密只有自己的私钥能解
    2. 加解密效率低,一般不做大量数据使用
  3. 安全性:安全性高
  4. 使用情况:
    1. 配合对称加密一起使用
    2. 建立连接之初,先使用非对称加密协商对称加密的算法和秘钥
    3. 然后使用对称加密进行后续的加解密

编码和解码

  1. 程序在计算机内部的存储形式
  2. 由字符转成二进制比特流的过程叫做编码
  3. 由比特流转成可读字符的过程叫做解码
  4. 加解密就是对比特流进行编码解码

常见的编码方式

  1. gob包->go内置的编解码包
  2. html编码
  3. json编码
  4. binary包->go内置的编解码包

对称加密算法

存在的问题

  1. 当通信对象很多时会面临众多秘钥的有效管理问题

    1. 对于一个新的数据通信对象,秘钥怎样进行传输的问题

解决方法:

​ 引入非对称加密

1. DES(Data Encryption Standard)

特点:

  1. 不安全,不建议使用
  2. 秘钥:8字节,64bit,但由于每7个bit就会设置一个校验位,所以实际长度位56bit
  3. 加密时,会对明文进行分组,分组长度是8bytes,得到的密文也是8bytes为一组

2. 3DES(Triple Data Encryption Standard)

特点:

  1. 将DES重复三次得到的一种密码算法,但不是单纯的重复三次,加密过程是加密->解密->加密,解密时为了兼容以前的DES,以解密的形式进行加密。解密过程是解密->加密->解密

  2. 秘钥:8bytes*3=24bytes,如果秘钥1与秘钥2相同或者秘钥2与秘钥3相同,相当于DES,若秘钥1与秘钥3相同,是3DES-EDE2

    若3个秘钥都不相同,是3DES-EDE3

  3. 加密时,会对明文进行分组,分组长度是8bytes,得到的密文也是8bytes为一组,与DES相同

  4. 加密效率低

3. AES(Advance Encryption Standard)

特点:

1. 秘钥长度可在128、192、256bit三种进行选择
2. 分组:16bytes
3. 加密效率高

五种分组模式

加密算法可以和任何分组模式进行连接,只有对明文进行加密,才需要填充

1. ECB,电子密码本模式:不使用,淘汰

特点:

  1. 明文消息被分成固定大小的分组,块大小由加密算法决定,并且每个块被使用相同的方法单独加解密
  2. 一旦有一个块被破解,那么所有的块都能使用相同的方法进行破解
  3. 安全性差,适用于数据较小的情形,加密前需要把明文数据填充到块大小的整倍数
  4. 加密效率高,加密不彻底,go语言不支持着这种模式

2.CBC,密文分组链接模式:常用,推荐使用

特点:

1. 先异或,再加密,模式中的每一个分组都要先和前一个分组加密后的数据进行异或操作,然后再加密,第一个数据块进行加密之前需要用初始化向量进行异或操作,初始化向量长度必须与分组长度相同
2. 连续加密,无法并行处理,加密前需要把明文数据填充到块大小的整倍数
3. 加密强度高

3.CFB,密文反馈模式:偶尔使用

特点:

  1. 先加密,再异或,前一个分组的密文加密后和当前分组的明文异或操作生成当前分组的密文,同样需要初始化向量,初始化向量长度必须与分组长度相同
  2. 连续加密,无法并行处理,没有直接对明文进行加密,所以不需要填充
  3. 解密的时候,是对初始向量进行加密操作,这样才能得到同样的数据

4.OFB,输出反馈模式:偶尔使用

特点:

  1. 通过明文分组和密码算法的输出来进行异或的,是对初始向量的结果进行不断的加密,作为下一次的数据来源
  2. 不需要进行数据填充
  3. 解密的时候,是对初始向量进行不断的加密与密文分组异或得到明文分组
  4. 不支持并行操作

5. CTR,计数器模式:建议使用

特点:

  1. 通过将逐次累加的计数器进行加密来生产密钥流的流密码
  2. 流密码与明文分组进行异或得到密文分组
  3. 解密的时候,是对计数器进行加密与密文分组异或得到明文分组
  4. 当分组为16字节时,计数器前8个字节为随机数,这个值在加密的时候都是不同的,后8个字节为分组序号,会逐次累加
  5. 加密解密并行操作,不需要字节填充,推荐使用

DES-CBC加密实现

推荐网站:https://studygolang.com/pkgdoc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//输入明文,秘钥、输出密文
func desCBCEncrypt(src,key []byte) []byte{
fmt.Printf("加密开始,输入的数据为:%s\n",src)
//创建并返回一个使用DES算法的cipher.Block接口。
block,err:=des.NewCipher(key)

if err!=nil{
log.Panic(err)
}
//进行数据填充
src = paddingInfo(src,block.BlockSize())
//引入CBC模式,返回一个密码分组链接模式的、底层用b加密的BlockMode接口
//把一个元素的byte重复BlockSize次数
iv:=bytes.Repeat([]byte("1"),block.BlockSize())
blockMode:=cipher.NewCBCEncrypter(block,iv)

//加密操作
blockMode.CryptBlocks(src/*加密后的密文*/,src/*明文*/)
fmt.Printf("加密结束,加密数据为:%x\n",src)
return src
}

//填充函数,输入明文长度,分组长度,输出填充后的数据
func paddingInfo(src []byte,blockSize int) []byte {
//1. 得到明文长度
length:=len(src)
//2. 计算需要填充的数量
paddingNum:=blockSize-length%blockSize
//3. 把填充的数值转换为字符
s1:=byte(paddingNum)
//4. 把字符拼成数组
s2:=bytes.Repeat([]byte{s1},paddingNum)//[]byte{"5","5","5","5","5"}
//5. 把数组追加到src后面返回新的数组
src=append(src,s2...)
return src
}


func main(){
src:=[]byte("123456789")
key:=[]byte("12345678")
desCBCEncrypt(src,key)
}

/*
输出:
加密开始,输入的数据为:123456789
加密结束,加密数据为:6e8b7929826faede31c279ed4bc114cf
*/

DES-CBC解密实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//输入密文、秘钥,得到明文
func desCBCDecrypt(cipherData,key []byte) []byte {
fmt.Printf("解密开始,需要解密的数据为:%x\n",cipherData)
block,err:=des.NewCipher(key)

if err!=nil{
log.Panic(err)
}

iv:=bytes.Repeat([]byte("1"),block.BlockSize())
blockMode:=cipher.NewCBCDecrypter(block,iv)
blockMode.CryptBlocks(cipherData,cipherData)

//解除填充
cipherData=unpaddingInfo(cipherData)

fmt.Printf("解密结束,解密的数据为:%s\n",cipherData)
return cipherData


}

//解除填充函数
func unpaddingInfo(plainText []byte) []byte {
//1. 获取长度
length:=len(plainText)
//2. 获取最后一个字符
if length ==0{
return []byte{}
}
lastByte:=plainText[length-1]
//3. 将字符转换成数字
unpaddingNum:=int(lastByte)
//4.切片需要的数据
return plainText[:length-unpaddingNum]
}

/*
输出:
解密开始,需要解密的数据为:7bc99fdee71314ac971211e0b3b8fc71
解密结束,解密的数据为:夜行书生
*/

AES-CTR加解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
func aesCTREncrypt(src,key []byte) []byte {
fmt.Printf("加密开始,需要加密的数据为:%s\n",src)

block,err:=aes.NewCipher(key)

if err!=nil{
log.Panic(err)
}

iv:=bytes.Repeat([]byte("1"),block.BlockSize())
stream:=cipher.NewCTR(block,iv)

stream.XORKeyStream(src,src)

fmt.Printf("加密结束,加密数据为:%x\n",src)
return src
}

func aesCTRDecrypt(cipherData,key []byte) []byte {
fmt.Printf("解密开始,需要解密的数据为:%x\n",cipherData)
block,err:=aes.NewCipher(key)

if err!=nil{
log.Panic(err)
}

iv:=bytes.Repeat([]byte("1"),block.BlockSize())
stream:=cipher.NewCTR(block,iv)

stream.XORKeyStream(cipherData,cipherData)

fmt.Printf("加密结束,解密数据为:%s\n",cipherData)
return cipherData

}

func main() {
src:=[]byte("夜行书生")
key:=[]byte("1234567812345678")
cipherData:=aesCTREncrypt(src,key)
aesCTRDecrypt(cipherData,key)
}

//加解密操作完全一致

/*输出:
加密开始,需要加密的数据为:夜行书生
加密结束,加密数据为:70547d09ab14352da487c0f6
解密开始,需要解密的数据为:70547d09ab14352da487c0f6
加密结束,解密数据为:夜行书生
*/

非对称加密算法

存在的问题

  1. 直接传递公钥,容易被截取
  2. 放到固定的位置,容易被替换

解决的办法:

​ 引入第三方认证机构,CA,它是一系列具有社会公信力的机构的总称,它们负责为厂商提供数字证书

常见使用场景

  1. 加密通信:私钥加密,公钥解密
  2. https:验证服务器,数字证书,使用ca认证公钥
  3. 签名:防止篡改,哈希+非对称加密
  4. 网银U盾:验证client,U盾相当于私钥,公钥在服务器
  5. github ssh(secure shell)登录
    • ssh是一种网络协议,主要用于计算机之间的加密登录与数据传递
    • ssh登录的时候没有ca认证,需要用户自己确认登录主机的指纹,点击yes后把远程主机的指纹存放到本地的know_hosts中,后续登录会跳过警告
    • ssh-keygen -t rsa 演示

非对称加密使用公钥加密,使用私钥解密

私钥:使用随机数按照一定的规则生成的,只有自己持有

公钥:由私钥推导而来,任何人可以持有,公钥加密的数据只能被配套私钥解开

RSA生成私钥、公钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
const privateKeyFile = "./privateRSAKey.pem"
const publicKeyFile = "./publicRSAKey.pem"
//分析需求:生成并保存私钥、公钥

func generateKeyPair(bits int) error {
//生成私钥:
//1. 使用RSA包
// //GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥。
// func GenerateKey(random io.Reader, bits int) (priv *PrivateKey, err error)//会自己生成两个素数
// -参数1:随机数
// -参数2:秘钥长度
// -返回值:私钥
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}

//2. 对生成的私钥进行编码处理,X509,按照规则进行序列化处理
//MarshalPKCS1PrivateKey将rsa私钥序列化为ASN.1 PKCS#1 DER编码。

priDerText:=x509.MarshalPKCS1PrivateKey(privateKey)

//3. 创建一个block结构,并填入BER编码数据
// type Block struct {
// Type string // 得自前言的类型(如"RSA PRIVATE KEY")
// Headers map[string]string // 可选的头项
// Bytes []byte // 内容解码后的数据,一般是DER编码的ASN.1结构
// }

block:=pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: priDerText,
}

//4. 将PEM格式Block数据写入磁盘文件
filehandler1,err:=os.Create(privateKeyFile)
if err!=nil{
return nil
}
defer filehandler1.Close()

// func Encode(out io.Writer, b *Block) error
err=pem.Encode(filehandler1,&block)
if err!=nil{
return nil
}
fmt.Printf("私钥写入成功!\n")

//生成公钥:
//1. 获取公钥,通过私钥获取
pubKey:=privateKey.PublicKey
//2. 对生成的公钥进行编码处理,X509,按照规则进行序列化处理
pubKeyDerText:=x509.MarshalPKCS1PublicKey(&pubKey)
//3. 创建一个block结构,并填入BER编码数据
block=pem.Block{
Type: "RSA PUBLIC KEY",
Headers: nil,
Bytes: pubKeyDerText,
}
//4. 将PEM格式Block数据写入磁盘文件
filehandler2,err:=os.Create(publicKeyFile)
if err!=nil{
return nil
}
defer filehandler2.Close()

err=pem.Encode(filehandler2,&block)
if err!=nil{
return nil
}
fmt.Printf("公钥写入成功!\n")
return nil
}

RSA加解密

可以使用openssl生成公钥私钥

1
2
openssl genrsa -out rsa_private_key.pem  1024	#生成私钥,1024时秘钥长度,不指定长度默认2048位
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥

加解密过程:

1
2
密文=明文^E mod N #加密过程
明文=密文^D mod N #解密过程
  1. 加密的数据都是明文对应的数字值
  2. 对数值一次进行E或D次方处理
  3. 对N取模

加密时:公钥(E,N),由E和N组成公钥,E是encrypt(根据特定规则限定一个区间,在区间内随意选择),N是两个素数的乘积

解密时:私钥(D,N),由D和N组成私钥,D时Decrypt,只有知道了时哪两个大素数,才能推出D

算法描述:转载自:http://bank.hexun.com/2009-06-24/118958531.html

  1. 选择一对不同的、足够大的素数p,q。
  2. 计算n=p*q。
  3. 计算f(n)=(p-1)(q-1),同时对p, q严加保密,不让任何人知道。
  4. 找一个与f(n)互质的数e,且1<e<f(n)。
  5. 计算d,使得d ≡e-1 mod f(n),两边的同余运算相同,或者相当于(d*****e)%f(n)=1
  6. 公钥KU=(e,n),私钥KR=(d,n)。

1.公钥加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const publicKeyFile = "./publicRSAKey.pem"

func rsaPubEncrypt(fileName string,src []byte) (error ,[]byte) {
//1. 通过公钥文件,读取公钥信息
info,err:=ioutil.ReadFile(fileName)
if err!=nil{
return err,nil
}
//2. pem.Decode,得到block中的der编码数据
block,_:=pem.Decode(info) //返回值2rest是未解码完的数据
//3. 解码der,得到公钥
derText:=block.Bytes
pubKey,err:=x509.ParsePKCS1PublicKey(derText)
if err!=nil{
return err,nil
}
//4. 公钥加密
//EncryptPKCS1v15使用PKCS#1 v1.5规定的填充方案和RSA算法加密msg。信息不能超过((公共模数的长度)-11)字节。
//func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) (out []byte, err error)
cipherData,err:=rsa.EncryptPKCS1v15(rand.Reader,pubKey,src)
if err!=nil{
return err,nil
}
return nil,cipherData
}

2.私钥解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func rsaPriKeyDecrypt(fileName string,cipherData []byte) (error,[]byte) {
//读取私钥文件
info,err:=ioutil.ReadFile(fileName)
if err!=nil{
return err,nil
}
block,_:=pem.Decode(info)

derText:=block.Bytes

privateKey,err:=x509.ParsePKCS1PrivateKey(derText)

if err!=nil{
return err,nil
}

//私钥解密
//DecryptPKCS1v15使用PKCS#1 v1.5规定的填充方案和RSA算法解密密文。
//func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (out []byte, err error)

plainText,err:=rsa.DecryptPKCS1v15(rand.Reader,privateKey,cipherData)
if err!=nil{
return err,nil
}
return nil,plainText
}

3.输出

1
2
加密成功,加密后的信息为 : 68590b1ce53a7a6958603ae3df5ee965f2d13fae0616a36b5c72def9e867c3a20d9b00a3ca33f8de82800448a642356b25791f9f6807281fc6b30f23b272502e5550799a12d683de8386d6dc69994b7d1fa70382d6e787199bc3600170c897c3e97c66b0ce0322e21baabeeaa242bad9cdf15163af27db8f42e2adafd951ed57
解密成功,解密后的信息为 : 夜行书生

ECC椭圆曲线

golang中不支持ECC加解密,支持ECC签名

-私钥:16(不是16G点)

-公钥:G点和16G点组成

ECC公私钥创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const PrivateKeyFile = "./ECC/privateECCKey.pem"
const PublicKeyFile = "./ECC/publicECCKey.pem"

//生成公钥私钥
func generateEccKeypair() {
//1. 选择一个椭圆曲线(elliptic包)
curve := elliptic.P256()
//2. 使用ecdsa包(椭圆曲线数字签名),创建私钥
//GenerateKey函数生成一对公私钥/密钥对。
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic(err)
}
//3. 使用x509进行编码
//MarshalECPrivateKey将ecdsa私钥序列化为ASN.1 DER编码。
//func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)
derText1, err := x509.MarshalECPrivateKey(privateKey)
//4. 写入pem.Block中
block1 := pem.Block{
Type: "ECC PRIVATE KEY",
Headers: nil,
Bytes: derText1,
}
//5. pem.Encode
filehandler1, err := os.Create(PrivateKeyFile)
if err != nil {
log.Panic(err)
}
defer filehandler1.Close()
pem.Encode(filehandler1, &block1)

//获取公钥
publicKey := privateKey.PublicKey
//MarshalPKIXPublicKey将公钥序列化为PKIX格式DER编码。
//func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)
derText2, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
log.Panic(err)
}
block2 := pem.Block{
Type: "ECC PUBLIC KEY",
Headers: nil,
Bytes: derText2,
}
filehandler2, err := os.Create(PublicKeyFile)
if err != nil {
log.Panic(err)
}
defer filehandler2.Close()
pem.Encode(filehandler2, &block2)

Hash

可以对输入的内容生产一个唯一的数值

  1. 输入内容不变,输出内容不变(散列值)
  2. 输入内容有一点点改变,输出也会千差万别
  3. 无论输入的内容大小是多少,生产的哈希长度相同
  4. 哈希运算时对输入内容做指纹摘要,无法推回原文

Base64

Base64编码,是我们程序开发中经常使用到的编码方式,因为Base64编码的字符串更适合不同的平台,不同语言的传输。它是一种基于用64个可打印字符来表示二进制数据的表示方法,它通常用作存储、传输一些二进制数据编码方法。就是将二进制数据文本化(就是转成ASCLL码)

作用:

  1. 由于某些系统中只能使用ASCLL字符,Base64就是用来将非ASCLL字符的数据转换成ASCLL字符的一种方法
  2. 对二进制文件进行文本化后的输出
  3. 前后台交互时,经常使用base64,可以避免特殊字符传输错误

单向散列函数MD5

也是一个hash算法,不同于sha256的256位,md5是128位的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//使用go包,有两种hash运算方式
//方式一:
func md5Test1(info []byte) []byte {
//对多量数据进行MD5哈希运算
//1. 创建一个哈希器
hasher:=md5.New()
io.WriteString(hasher,string(info))//类型断言
//2. 执行Sum操作,得到哈希值
hash:=hasher.Sum(nil)
return hash
}
//方式二:
func md5Test2(info []byte) []byte {
hash:=md5.Sum(info)
return hash[:]
}

SHA256

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const fileName = "D:/GoCode/go语言密码学/SHA256.go"

//直接传入内容
func sha256Test1(info []byte) []byte {
hash:=sha256.Sum256(info)
return hash[:]
}

//使用打开文件方式获取哈希
func sha256Test2() []byte {
//1. open文件
file,err:=os.Open(fileName)
if err!=nil{
log.Panic(err)
}
//2. 创建hash
hasher:=sha256.New()
//3. copy句柄
_,err=io.Copy(hasher,file)
if err!=nil{
log.Panic(err)
}
//4. hash sum操作
hash:=hasher.Sum(nil)
return hash[:]
}

消息认证码

如果接受方接收到了乱码,无法判断是否是原本的数据信息,需要使用消息认证码,它是一种确认完整性并进行认证的技术,是一种与密钥相关联的单向散列函数,取三个单词的首字母,简称为MAC

  • 保证数据未被篡改
  • 保证数据的来源

认证步骤

  1. 发送者事先与接收者共享密钥
  2. 发送至根据请求信息和密钥计算MAC值
  3. 发送者将请求信息和MAC值发送给接收者
  4. 接收者根据请求信息和密钥计算MAC值
  5. 两者的MAC值进行对比,如果一致则认证成功

使用场景

  1. SWIFT(环球银行金融电信协会),曾经使用了消息认证码
  2. https,握手协议使用了消息认证码
  3. IPSec,IP协议的增强版,使用了消息认证码

HMAC

是一种使用单向散列函数来构造消息认证码的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//New函数返回一个采用hash.Hash作为底层hash接口、key作为密钥的HMAC算法的hash接口。
//func New(h func() hash.Hash, key []byte) hash.Hash
//-参数1:自己指定的哈希算法
//-参数2:密钥
//-返回值:哈希对象

//比较两个MAC是否相同,而不会泄露对比时间信息。
//func Equal(mac1, mac2 []byte) bool

//生成mac
func generateHMAC(src []byte,key []byte) []byte {

//1. 创建哈希器
hasher:=hmac.New(sha256.New,key)
//2. 生成哈希值
hasher.Write(src)//写入
mac:=hasher.Sum(nil)//填充
return mac
}

//认证mac
func verifyHMAC(src []byte,mac1 []byte,key []byte) bool {
//1. 对端接收到的原信息
//2. 对端接收到的mac
//3. 对端计算本地mac值

hasher:=hmac.New(sha256.New,key)

hasher.Write(src)//写入
mac2:=hasher.Sum(nil)//填充

//4. 对比两个mac值

return hmac.Equal(mac1,mac2)
}

存在问题

  1. 无法有效的配送密钥
  2. 无法进行第三方证明
  3. 无法防止发送方否认

解决办法: 非对称加密的数字签名

数字签名

签名流程

  1. 发送方对原文做哈希运算得到哈希值1

  2. 发送方用私钥对哈希值1进行加密签名

  3. 发送方将原文和私钥签名发送给接收方

  4. 接收方对接收到的原文进行哈希运算得到哈希值2

  5. 接收方用公钥解密签名得到哈希值1

  6. 接收方对哈希值1和哈希值2进行比较

因此:数字签名解决了消息认证码的问题

  1. 不需要协商密钥
  2. 任何人都持有公钥,都可以帮忙验证
  3. 私钥只有发送方持有,无法否认

通过RSA进行数字签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//私钥签名:
func rsaSignData(fileName string,src []byte) (error, []byte) {

//1. 提供私钥文件,解析出私钥的内容

//读取私钥文件
info,err:=ioutil.ReadFile(fileName)
if err!=nil{
return err,nil
}
block,_:=pem.Decode(info)
derText:=block.Bytes
privateKey,err:=x509.ParsePKCS1PrivateKey(derText)
if err!=nil{
return err,nil
}
//2. 使用私钥进行数字签名
//SignPKCS1v15使用RSA PKCS#1 v1.5规定的RSASSA-PKCS1-V1_5-SIGN签名方案计算签名。
//func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) (s []byte, err error)
//获取原文的哈希值
hash:=sha256.Sum256(src)
//执行签名操作
signature,err:=rsa.SignPKCS1v15(rand.Reader,privateKey,crypto.SHA256,hash[:])
if err!=nil{
return err,nil
}
return nil,signature

}

//公钥认证:
func rsaVerifySignature(fileName string,src []byte,signature []byte) error {

//1. 通过公钥文件,读取公钥信息
info,err:=ioutil.ReadFile(fileName)
if err!=nil{
return err
}
//2. pem.Decode,得到block中的der编码数据
block,_:=pem.Decode(info) //返回值2:rest是未解码完的数据
//3. 解码der,得到公钥
derText:=block.Bytes
pubKey,err:=x509.ParsePKCS1PublicKey(derText)
if err!=nil{
return err
}

//2. 使用公钥进行数字签名认证
//VerifyPKCS1v15认证RSA PKCS#1 v1.5签名。hashed是使用提供的hash参数对(要签名的)原始数据进行hash的结果。
//func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error)
//获取原文的哈希值
hash:=sha256.Sum256(src)
err=rsa.VerifyPKCS1v15(pubKey,crypto.SHA256,hash[:],signature)

return err
}

通过ECC进行数字签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
const PrivateKeyFile = "./ECC/privateECCKey.pem"
const PublicKeyFile = "./ECC/publicECCKey.pem"

//自定义签名结构
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{
log.Panic(err)
}
block,_:=pem.Decode(info)
derText:=block.Bytes
privateKey,err:=x509.ParseECPrivateKey(derText)

if err!=nil{
log.Panic(err)
}

//2. 对原文生成哈希值
hash:=sha256.Sum256(src)

//3. 使用私钥签名
//使用私钥对任意长度的hash值(必须是较大信息的hash结果)进行签名,返回签名结果(一对大整数)。
//func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)
r,s,err:=ecdsa.Sign(rand.Reader,privateKey,hash[:])
if err!=nil{
log.Panic(err)
}

sig:=Signature{
r: r,
s: s,
}

return sig,nil

}



//使用公钥验证
func eccVerifySign(filename string,src []byte,sig Signature) bool {
//1. 读取公钥解码
info,err:=ioutil.ReadFile(filename)
if err!=nil{
log.Panic(err)
}
block,_:=pem.Decode(info)
derText:=block.Bytes
publicKeyInterface,err:=x509.ParsePKIXPublicKey(derText)
publicKey,ok:=publicKeyInterface.(*ecdsa.PublicKey)//断言
if !ok{
fmt.Println("断言失败,非ecdsa公钥")
os.Exit(1)
}
//2. 对原文生成哈希值
hash:=sha256.Sum256(src)
//3. 使用公钥验证
//使用公钥验证hash值和两个大整数r、s构成的签名,并返回签名是否合法。
//func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
return ecdsa.Verify(publicKey,hash[:],sig.r,sig.s)
}

数字证书

对公钥进行数字签名

推荐网站:https://blog.csdn.net/suchahaerkang/article/details/84849093

PKI(公钥基础设施)和CA(证书颁发机构)

PKI:用来实现基于公钥密码体制的密钥和证书的产生、管理、存储、分发和撤销等功能。

PKI的组成元素主要有三个:

  1. 用户:使用PKI的人
  2. 认证机构:办法证书的人
  3. 仓库:保存证书的数据库

CA:负责发放和管理数字证书,作为电子商务交易中受信任的第三方

证书使用

所有的网站都转成https,https就等于http加ssl,ssl是一个通讯协议,在通讯过程中,使用了数字证书

https通信

  • 所有的通信不再传输公钥,而是传输数字证书
  • 证书里面包含公钥,由CA机构认证
  1. 网站提供者会自己在本地生成公钥私钥,也可以不自己生成,全部由CA处理
  2. 服务器将公钥发送给CA机构
  3. CA机构也有自己的私钥公钥
  4. CA使用自己的私钥对服务器的公钥进行签名
  5. CA向服务器颁发一个数字证书
  6. 当用户访问服务器时,服务器会将CA证书发送给用户
  7. 在客户的浏览器中,已经随着操作系统,预装了知名CA机构的根证书,这里面包含了CA机构的公钥,浏览器会对服务器的证书进行验证
  8. 验证成功则服务器可靠,即可正常通信,否则显示Warning
  9. 证书有效时,浏览器会将自己支持的对称加密算法,生成一个随机秘钥,使用服务器的公钥进行加密,发送给服务器
  10. 服务器选择一个加密算法,使用对称秘钥加密信息,发送给客户端
  11. 双方达成一致,接下来的通信转换为对称加密

SSL/TLS

安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性。

windows下查看数字证书

1
2
win+R输入certmgr.msc导出数字证书
终端输入 openssl x509 -in 证书名称 -inform der -text

证书信任链

通过一个证书证明另一个证书是真实可信的,根证书是最安全的证书,不需要验证

生成自签名证书

  • 生成私钥文件
  • 生成数字证书(包含公钥)

方法一:分步生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#启动openssl 
openssl
#生成一个RSA私钥,执行后输入密码并确认
genrsa -des3 -out server.key 2048 #des3是对私钥进行加密
#生成CSR(证书签名请求)
req -new -key server.key -out server.csr
#查看csr文件细节
openssl req -in server.csr -noout -text
#删除私钥中的密码
rsa -in server.key -out server.key
#生成自签名证书
x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
#查看crt文件细节
x509 -in server.crt -text -noout

方法二:一步生成

1
2
3
4
#不需要生成csr,直接生成证书
req -x509 -newkey rsa:4096 -keyout server2.key -out cert.crt -days 365 -nodes #nodes不设置密码
#查看证书细节
x509 -in cert.crt -text -noout

常见证书格式

  1. pem格式

    使用openssl生成的都是pem格式

    1
    openssl x509 -in cert.crt -text
  2. der格式

    1
    openssl x509 -in cert.der -inform der -text

单向认证

客户端单向认证服务器,服务器不认证客户端,服务器证书使用openssl自签名

创建证书:

1
req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes

代码流程:

推荐网站:http://www.youkud.com/m/view.php?id=1487

创建http服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
//1. 创建http server
server:=http.Server{
Addr: ":433",//监听端口
Handler: nil,//处理函数,nil时会使用默认处理函数
TLSConfig: nil,//单向认证填nil
}

//编写处理逻辑
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Printf("HandleFunc called\n")
writer.Write([]byte("hello world"))
})

//2. 启动http server,启动时加载自己的证书,使用TLS
err:=server.ListenAndServeTLS("server.crt","server.key")
if err!=nil{
log.Fatal(err)
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func main() {
//读取CA证书
caCertInfo,err:=ioutil.ReadFile("server.crt")
if err!=nil{
log.Fatal(err)
}
//创建CA池
certPool:=x509.NewCertPool()
//把CA证书添加到CA池中
certPool.AppendCertsFromPEM(caCertInfo)
//将CA池配置给tls
cfg:=tls.Config{
RootCAs: certPool,
//如果设置验证证书的话(TLSClientConfig: &tls.Config{InsecureSkipVerify: false}),那么需要验证两个东西,一个是上证书用的CN的名称一致。
//另一个是证书是否权威签发,否则就是上面的unknown authority了如果设置成true,则无所谓了,不校验证书,当然不一致也无所谓了。否则会报错: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
InsecureSkipVerify: true,
}
//创建http client
client:=http.Client{Transport: &http.Transport{TLSClientConfig: &cfg}}
//5. client发起请求
response,err:=client.Get("https://localhost:433")
if err!=nil {
log.Fatal(err)
}
//6. 打印返回值
bodyInfo,err:=ioutil.ReadAll(response.Body)
if err!=nil {
log.Fatal(err)
}

response.Body.Close()
fmt.Printf("body : %s\n",bodyInfo)
fmt.Printf("response : %s\n",response.Status)
}

双向认证

客户端认证服务器,服务器认证客户端,服务器和客户端证书都使用openssl自签名证书

创建证书:

1
2
req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes
req -x509 -newkey rsa:4096 -keyout client.key -out client.crt -days 365 -nodes

服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func main() {
//读取client证书
caInfo,err:=ioutil.ReadFile("client.crt")
if err!=nil{
log.Fatal(err)
}
//创建CA池
certPool:=x509.NewCertPool()
//把CA证书添加到CA池中
certPool.AppendCertsFromPEM(caInfo)
//将CA池配置给tls
cfg:=tls.Config{
ClientCAs: certPool,//客户端的CA池填充
ClientAuth: tls.RequireAndVerifyClientCert,//设置服务器认证客户端
InsecureSkipVerify: true,
}
//创建http server
server:=http.Server{
Addr:":433",
Handler: nil,
TLSConfig: &cfg,
}

//编写处理逻辑
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Printf("ServerHttp called\n")
writer.Write([]byte("夜行书生"))
})

//启动http server
err = server.ListenAndServeTLS("server.crt","server.key")
if err!=nil{
log.Fatal(err)
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func main() {
//读取server证书
caInfo,err:=ioutil.ReadFile("server.crt")
if err!=nil{
log.Fatal(err)
}
//创建CA池
certPool:=x509.NewCertPool()
//把CA证书添加到CA池中
certPool.AppendCertsFromPEM(caInfo)

//加载客户端的证书和密钥
clientCert,err:=tls.LoadX509KeyPair("client.crt","client.key")
if err!=nil{
log.Fatal(err)
}
//配置tls
cfg:=tls.Config{
//CA池
RootCAs: certPool,
//客户端证书
Certificates: []tls.Certificate{clientCert},
InsecureSkipVerify: true,
}
//创建http client
client:=http.Client{Transport: &http.Transport{TLSClientConfig: &cfg}}
//client发起请求
response,err:=client.Get("https://localhost:433")
if err!=nil{
log.Fatal(err)
}
//打印返回值
bodyInfo,err:=ioutil.ReadAll(response.Body)
if err!=nil {
log.Fatal(err)
}
defer response.Body.Close()
fmt.Printf("body : %s\n",bodyInfo)
fmt.Printf("response : %s\n",response.Status)
}