RSA加密——go語言版
源起
在做rsa加密驗籤的過程中遇到了一些問題,在對整個rsa體系不夠了解的情況下花了很多的時間去嘗試,但總是各種不通、各種頭疼。
在嘗試和搜索方案的過程中,不斷的發現原來平時對rsa的瞭解是如此的少,rsa的體系是如此的龐大和龐雜。
不知道大家對rsa的瞭解程度如何,作爲一個幾年經驗的後端程序員來說,對加密的大致體系有所瞭解,知道簡單異或、md5、對稱、非對稱、用過幾種對稱加密算法。非對稱加密在做支付對接的時候也有所接觸。對rsa加密的原理有所瞭解,知道如何配置ssh免密、如何配置git的ssh、如何配置nginx的單向、雙向ssl證書。
然而,這些只是外圍知識而已!!!
當然,由於不是密碼學專業,也不是安全方向,這裏不對非對稱加密的體系和算法進行分析,我個人也沒有去學習和分析。這裏僅包含在處理問題和後續梳理過程中涉及的知識要點。
公鑰和私鑰格式關係
這個格式關係很重要。對於理解祕鑰加載的步驟,對於拿到手上的各種樣式的祕鑰的理解能有一個心理準備,有一個總體的可能性判斷。
公鑰
一般來說,我們遇到的公鑰是PEM格式,PEM格式是對DER格式公鑰的一種封裝,封裝的內容包括以下幾塊:
塊名稱 | 塊內容示例 |
---|---|
頭 | -----BEGIN PUBLIC KEY----- |
公鑰 | base64.encode(public_key_string) |
尾 | -----END PUBLIC KEY----- |
以下是一個PEM格式公鑰的示例:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+xGZ/wcz9ugFpP07Nspo6U17l0YhFiFpxxU4pTk3Lifz9R3zsIsu
ERwta7+fWIfxOo208ett/jhskiVodSEt3QBGh4XBipyWopKwZ93HHaDVZAALi/2A
+xTBtWdEo7XGUujKDvC2/aZKukfjpOiUI8AhLAfjmlcD/UZ1QPh0mHsglRNCmpCw
mwSXA9VNmhz+PiB+Dml4WWnKW/VHo2ujTXxq7+efMU4H2fny3Se3KYOsFPFGZ1TN
QSYlFuShWrHPtiLmUdPoP6CV2mML1tk+l7DIIqXrQhLUKDACeM5roMx0kLhUWB8P
+0uj1CNlNN4JRZlC7xFfqiMbFRU9Z4N6YwIDAQAB
-----END RSA PUBLIC KEY-----
HEX是十六進制編碼格式,用的比較少,以下是一個十六進制格式的公鑰示例:
00 00 00 07 73 73 68 2d 72 73 61 00 00 00 01 25 00 00 01 00 7f 9c 09
8e 8d 39 9e cc d5 03 29 8b c4 78 84 5f d9 89 f0 33 df ee 50 6d 5d d0
16 2c 73 cf ed 46 dc 7e 44 68 bb 37 69 54 6e 9e f6 f0 c5 c6 c1 d9 cb
f6 87 78 70 8b 73 93 2f f3 55 d2 d9 13 67 32 70 e6 b5 f3 10 4a f5 c3
96 99 c2 92 d0 0f 05 60 1c 44 41 62 7f ab d6 15 52 06 5b 14 a7 d8 19
a1 90 c6 c1 11 f8 0d 30 fd f5 fc 00 bb a4 ef c9 2d 3f 7d 4a eb d2 dc
42 0c 48 b2 5e eb 37 3c 6c a0 e4 0a 27 f0 88 c4 e1 8c 33 17 33 61 38
84 a0 bb d0 85 aa 45 40 cb 37 14 bf 7a 76 27 4a af f4 1b ad f0 75 59
3e ac df cd fc 48 46 97 7e 06 6f 2d e7 f5 60 1d b1 99 f8 5b 4f d3 97
14 4d c5 5e f8 76 50 f0 5f 37 e7 df 13 b8 a2 6b 24 1f ff 65 d1 fb c8
f8 37 86 d6 df 40 e2 3e d3 90 2c 65 2b 1f 5c b9 5f fa e9 35 93 65 59
6d be 8c 62 31 a9 9b 60 5a 0e e5 4f 2d e6 5f 2e 71 f3 7e 92 8f fe 8b
ssh生成的公鑰,有自己的格式,由3部分組成“ssh-rsa”字符+公鑰+備註。以下是一個ssh公鑰的示例:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3O26p3AlNy5JalOon2E2q62fygTBQZkVv4LJP1v7UxMH8pMNbsP1YG2keMotLtakHiqfHDd/s6eUDQYxU2nVdwruwPmP65mfhD0xTXsFFOdn+B80EVQdkm12h9Mcd9pVzMTplvB9v2j/8RX8UH45KsCgB5GcZJwdexJWys64Q+5eP386xZ7ow3XkKDS+XCEQTZaz+vN97aOQontwp+Dj0lB0VKhpnCWI5cadFRCNVitWaLLK6STRrYKV9CTUWOIuIV+2EC85UGIGmD3M+z6S3RZepoOZPDCRgaXdiNNsAAWa2ULZ9Wep9Qtly0NEsQ9EHrmeHadnDaWQy9w/KLsYj [email protected]
私鑰
私鑰常遇到的是pkcs1和pkcs8兩種標準,有時候還會遇到加密後的私鑰,需要用密碼加載或者去掉祕鑰後再使用。後續會說到格式轉換和去掉私鑰密碼的方法。
go語言生成公私鑰
由於不同語言對rsa公私鑰支持程度不同,所以很可能遇到在java項目用的很好的祕鑰,在go語言沒有辦法用。但是又不確定是祕鑰的問題,還是代碼寫法的問題。這時候如果能夠生成一套所使用語言支持的祕鑰對,對解決問題和建立信心都有很大幫助。
func GenRsaKey(bits int) error {
// 生成私鑰文件
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
derStream := x509.MarshalPKCS1PrivateKey(privateKey)
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derStream,
}
file, err := os.Create("private.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
// 生成公鑰文件
publicKey := &privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return err
}
block = &pem.Block{
Type: "PUBLIC KEY",
Bytes: derPkix,
}
file, err = os.Create("public.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
return nil
}
go加載私鑰
pkcs1
package main
import ("fmt"
"crypto/x509"
"encoding/pem"
"crypto/rsa"
)
func main () {
privateKey := `your key`
blockPri, _ := pem.Decode([]byte(privateKey))
if blockPri == nil {
fmt.Println("err1")
return
}
priKey, err := x509.ParsePKCS1PrivateKey([]byte(blockPri.Bytes))
if err != nil {
fmt.Println(err)
fmt.Println("err2")
return
}
fmt.Println("ok")
fmt.Println(priKey)
}
pkcs8
package main
import ("fmt"
"crypto/x509"
"encoding/pem"
"crypto/rsa"
)
func main () {
privateKey := `your key`
blockPri, _ := pem.Decode([]byte(privateKey))
if blockPri == nil {
fmt.Println("err1")
return
}
prkI, err := x509.ParsePKCS8PrivateKey([]byte(blockPri.Bytes))
if err != nil {
fmt.Println(err)
fmt.Println("err2")
return
}
priKey := prkI.(*rsa.PrivateKey)
fmt.Println("ok")
fmt.Println(priKey)
}
go加載公鑰
blockPub, _ := pem.Decode([]byte(publicKey))
if blockPub == nil {
fmt.Println("err1")
return
}
pubKey, err := x509.ParsePKIXPublicKey(publicKey)
if err != nil {
fmt.Println(err)
fmt.Println("err2")
return
}
fmt.Println("ok")
fmt.Println(pubKey)
go進行rsa加密和驗籤
以下是hash摘要方式的rsa加密驗籤,也可以使用md5摘要方式的rsa加密:
//your_private_key和your_public_key都是上述《go加載rsa公、私鑰》部分創建的結構體類型
func Sign(src []byte, hash crypto.Hash) ([]byte, error) {
h := hash.New()
h.Write(src)
hashed := h.Sum(nil)
return rsa.SignPKCS1v15(rand.Reader, your_private_key, hash, hashed)
}
func Verify(src []byte, sign []byte, hash crypto.Hash) error {
h := hash.New()
h.Write(src)
hashed := h.Sum(nil)
return rsa.VerifyPKCS1v15(your_public_key, hash, hashoed, sign)
}
這裏說一下爲什麼要先摘要,再加密和驗籤,這裏主要考慮的是性能,因爲用rsa加密方式對性能的損耗比較大,如果待加密字串長的話,加密過程對性能的影響更大。所以,一般會先對字符串進行hash或者md5摘要。
openssl生成的公私鑰
//生成私鑰,默認生成的是pkcs1格式私鑰
genrsa -out rsa_private_key.pem 1024
//轉成pkcs8格式
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocrypt
//生成公鑰
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
去掉私鑰的密碼
//執行會後,會要求你輸入私鑰使用的密碼
openssl rsa -in my_private_key.pem -out new_private_key.pem
openssl公鑰和openssh公鑰互轉
兩者的私鑰是同樣的格式,但是兩者的公鑰是不同的格式,所以轉換的基礎就是私鑰。通過私鑰生成另一種格式的公鑰。
比如,如果已經有了openssl生成的私鑰,可以用如下命令生成openssh的公鑰:
ssh-keygen -y -f openssh_private.pem > public_key.pub
參考
本文參考了下列文章,有的代碼片段直接取自文章:
- Golang加密系列之RSA
- GO加密解密RSA番外篇:生成RSA密鑰
- GO加密解密之RSA
- PKCS標準
- openssl數字證書常見格式與協議介紹
- Generating an SSH Key Pair
- Generate Private key with OpenSSL and Public key ssh-keygen for SSH
- Converting keys between openssl and openssh
- How are the files in ~/.ssh related to the theory?
- OpenSSH RSA Public Key format
- openssl 生成公私鑰對
- 去掉ssh key的密碼