公钥,私钥,签名与证书

前言

之前工作中很多初次接触密码学的小伙伴都会纠结于公钥,私钥,证书和签名分别是啥,怎么使用的,这里以golang为范例对这块的相关涉及到的内容进行一定的整理。

概念

  • 私钥
    • 只有自己知道的密钥叫私钥,能解开对应公钥加密的数据
  • 公钥
    • 公开的密钥叫公钥,能解开对应私钥加密的数据
  • 证书
    • 证书中心使用自己的私钥对需要颁布证书的公钥和相关信息一起加密,生成了证书,通过证书可以确定自己的公钥是否是正确的公钥。
  • 签名
    • 将内容生成摘要hash后对这个摘要hash进行加密,这个就是签名,能够通过解开签名和对内容进行hash进行匹配确定内容是否被修改过。

例子

接下来我们就开始在golang中开始玩起来

生成公钥/私钥

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

package main

import (
"crypto/ecdsa"
"crypto/elliptic"
// "crypto/rand"
"crypto/x509"
"encoding/pem"
"flag"
"log"
"os"
"strings"
)

func main() {

key := flag.String("key", "yyyyyyyyyyyttttttttttttttttttttttqqqqq", "generate key")

flag.Parse()

if err := GenRsaKey(*key); err != nil {
log.Fatal("密钥文件生成失败!")
}
log.Println("密钥文件生成成功!")
}

func GenRsaKey(key string) error {
// 生成私钥文件
length := len([]byte(key))

var curve elliptic.Curve

if length >= 521/8+8 {
curve = elliptic.P521()
} else if length >= 384/8+8 {
curve = elliptic.P384()
} else if length >= 256/8+8 {
curve = elliptic.P256()
} else if length >= 224/8+8 {
curve = elliptic.P224()
}

privateKey, err := ecdsa.GenerateKey(curve, strings.NewReader(key))
if err != nil {
log.Println("GenerateKey Error: ", err)
return err
}

bpkey, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
log.Println("MarshalECPrivateKey Error: ", err)
return err
}

block := &pem.Block{
Type: "Private Key",
Bytes: bpkey,
}
file, err := os.Create("private.pem")
if err != nil {
log.Println("priv Create Error: ", err)
return err
}
err = pem.Encode(file, block)
if err != nil {
log.Println("priv Encode Error: ", err)
return err
}

// 生成公钥文件
publicKey := &privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
log.Println("MarshalPKIXPublicKey Error: ", err)
return err
}
block = &pem.Block{
Type: "Public Key",
Bytes: derPkix,
}
file, err = os.Create("public.pem")
if err != nil {
log.Println("pub Create Error: ", err)
return err
}
err = pem.Encode(file, block)
if err != nil {
log.Println("pub Encode Error: ", err)
return err
}
return nil
}

这里我们选用的是椭圆加密算法ECC生成的公/私钥对,并且将其输出到了文件,如下图所示

公钥

公钥

私钥

私钥

公/私钥加密与解密

在golang中对ECC只默认实现了签名算法(ecdsa),因此这里我们很遗憾要使用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
package main

import (
"crypto/rand"
"crypto/rsa"
"log"
)

var bits = 2048

func main() {
// 生成私钥
prvkey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
log.Println("GenerateKey Error: ", err)
return
}

// 生成公钥
pubkey := &prvkey.PublicKey

msg := "lalala"
// 公钥加密

data, err := rsa.EncryptPKCS1v15(rand.Reader, pubkey, []byte(msg))
if err != nil {
log.Println("EncryptPKCS1v15 Error: ", err)
return
}

log.Println("data :", string(data))

// 私钥解密

ans, err := rsa.DecryptPKCS1v15(rand.Reader, prvkey, data)

if err != nil {
log.Println("DecryptPKCS1v15 Error: ", err)
return
}
log.Println("data: ", string(ans))

}

输出结果如下图所示:

加密解密

可以看到加密后各种乱码,解密后完全正常

签名与签名校验

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package main

import (
"bytes"
"compress/gzip"
"crypto/ecdsa"
"crypto/md5"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"io/ioutil"
"log"
"math/big"
"strings"
// "os"
)

var keyw = "yyyyyyyyyyyttttttttttttttttttttttqqqqq"

// 读取pem文件
func ReadPem(path string) (*pem.Block, error) {
// 打开Pem文件

data, err := ioutil.ReadFile(path)

if err != nil {
log.Println("ReadPem Open Error: ", err)
return nil, err
}

pem, next := pem.Decode(data)
log.Println("ReadPem next: ", next)
return pem, nil
}

// 签名
func Sign(text []byte, prikey *ecdsa.PrivateKey, key string) (string, error) {

r, s, err := ecdsa.Sign(strings.NewReader(key), prikey, text)
if err != nil {
log.Println("Sign Sign Error: ", err)
return "", err
}
rt, err := r.MarshalText()
if err != nil {
log.Println("Sign MarshalText Error: ", err)
return "", err
}
st, err := s.MarshalText()
if err != nil {
log.Println("Sign MarshalText Error: ", err)
return "", err
}
var b bytes.Buffer
w := gzip.NewWriter(&b)
defer w.Close()
_, err = w.Write([]byte(string(rt) + "+" + string(st)))
if err != nil {
log.Println("Sign Write Error: ", err)
return "", err
}
w.Flush()
return hex.EncodeToString(b.Bytes()), nil

}

// 从证书中分解,获取数字证书r/s
func GetSign(signature string) (rint, sint big.Int, err error) {
byterun, err := hex.DecodeString(signature)
if err != nil {
log.Println("decrypt error, ", err)
return
}
r, err := gzip.NewReader(bytes.NewBuffer(byterun))
if err != nil {
log.Println("decode error,", err)
return
}
defer r.Close()
buf := make([]byte, 1024)
count, err := r.Read(buf)
if err != nil {
log.Println("decode read error,", err)
return
}
rs := strings.Split(string(buf[:count]), "+")
if len(rs) != 2 {
log.Println("GetSign Split Error: ", err)
return
}
err = rint.UnmarshalText([]byte(rs[0]))
if err != nil {
log.Println("GetSign UnmarshalText Error: ", err)
return
}
err = sint.UnmarshalText([]byte(rs[1]))
if err != nil {
log.Println("decrypt sint fail, ", err)
return
}
return

}

// 制作hash
func Hashtext(text, salt string) []byte {

Md5Inst := md5.New()
Md5Inst.Write([]byte(text))
result := Md5Inst.Sum([]byte(salt))

return result
}

// 校验
func verify(text []byte, signature string, key ecdsa.PublicKey) (bool, error) {

rint, sint, err := GetSign(signature)
if err != nil {
return false, err
}
result := ecdsa.Verify(&key, text, &rint, &sint)
return result, nil

}

func main() {
// 获取公钥
pubpem, err := ReadPem("public.pem")
if err != nil {
log.Println("pubkey ReadPem Error: ", err)
return
}

tpubkey, err := x509.ParsePKIXPublicKey(pubpem.Bytes)
if err != nil {
log.Println("pubkey ParsePKIXPublicKey Error: ", err)
return
}

var pubkey *ecdsa.PublicKey

switch tpubkey.(type) {
case *ecdsa.PublicKey:
pubkey = tpubkey.(*ecdsa.PublicKey)
default:
log.Println("pubkey type Error")
return
}

// 获取私钥

privpem, err := ReadPem("private.pem")
if err != nil {
log.Println("private ReadPem Error: ", err)
return
}

privkey, err := x509.ParseECPrivateKey(privpem.Bytes)
if err != nil {
log.Println("privkey ParseECPrivateKey Error: ", err)
return
}

log.Println("privatekey :", privkey)
log.Println("publickey: ", pubkey)

// 验证开始
data := "yoyoyo"

Edata, err := Sign(Hashtext(data, ""), privkey, keyw)
if err != nil {
log.Println("Sign Error: ", err)
return
}
log.Println("Edata: ", Edata)
tmp, err := verify(Hashtext(data, ""), Edata, *pubkey)
log.Println("ans: ", tmp)
}

这里我们可以看到,我们从之前的pem文件中得到了公钥和私钥,对一个字符串使用了md5摘要,然后对此进行了签名,得到了Edata,最后再根据公钥和md5摘要对这个签名进行了校验,得到了正确的答案

签名与验签

生成证书

这里我们没有CA,所以我们可以采取使用自己私钥自签名生成证书的方式

这里选用的是k8s的自签名生成证书的方式

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
package main

import (
"bytes"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"time"
)

func main() {
ip := []byte("192.168.100.1")
alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost"}
cert, key, _ := GenerateSelfSignedCertKey("10.10.10.10", []net.IP{ip}, alternateDNS)
fmt.Println(string(cert), string(key))

}

func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) {

// 生成私钥
priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
if err != nil {
return nil, nil, err
}

// x509证书初始化
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}

if ip := net.ParseIP(host); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, host)
}

template.IPAddresses = append(template.IPAddresses, alternateIPs...)
template.DNSNames = append(template.DNSNames, alternateDNS...)

derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, nil, err
}

// Generate cert
certBuffer := bytes.Buffer{}
if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return nil, nil, err
}

// Generate key
keyBuffer := bytes.Buffer{}
if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
return nil, nil, err
}

return certBuffer.Bytes(), keyBuffer.Bytes(), nil
}

证书与私钥输出如下所示:

证书

综述

经过这篇文章,相信已经对私钥,公钥,证书,签名这些概念有所了解了,在golang中如何调用也应该比较清楚了