我們之前介紹的不對(duì)稱加密算法洲尊,如果從最終密鑰Kenc和Kdec相同這個(gè)角度來看,還不是徹底的不對(duì)稱加密算法奈偏。另外坞嘀,該算法還需要A和B各自保留的一段私有數(shù)據(jù)a與b參與運(yùn)算。因此如果另一個(gè)通信方C想要加入進(jìn)來惊来,在不獲得a或b的情況下是不可能的丽涩。
而現(xiàn)在在加密領(lǐng)域較為流行的公私鑰加密體系較好地解決了上述問題,該體系的主要算法可以描述如下:
假設(shè)A準(zhǔn)備與很多人進(jìn)行加密通信;
A生成一對(duì)密鑰矢渊,分別稱為“公鑰”和“私鑰”继准;
A將公鑰公開,發(fā)送給所有需要與A通信的人矮男;
需要向A發(fā)送數(shù)據(jù)的人移必,在發(fā)送之前將數(shù)據(jù)用公鑰進(jìn)行加密之后再發(fā)送;
A接收到加密的密文后毡鉴,用私鑰進(jìn)行解密即可正確還原出原文崔泵;
同樣,A用私鑰加密后的密文猪瞬,其他人用公鑰也可以正確解密憎瘸。
可以看出,公私鑰體系的加密算法只有一個(gè)公開的公鑰和一個(gè)私有的私鑰陈瘦,并且加解密時(shí)分別就是用這兩個(gè)密鑰作為最終密鑰幌甘,是真正的不對(duì)稱加密算法,而且比之前算法的步驟大大簡(jiǎn)化痊项。下面代碼中演示了一個(gè)在Go語言中實(shí)現(xiàn)的經(jīng)典公私鑰加密算法含潘,這個(gè)算法利用大質(zhì)數(shù)的不相關(guān)性實(shí)現(xiàn)公私鑰對(duì)的生成,由于我們重點(diǎn)不是講解算法线婚,所以有關(guān)該算法的數(shù)學(xué)意義將不進(jìn)行討論遏弱,有興趣的可以自行查閱相關(guān)文獻(xiàn)。
package main
import (
? "bytes"
? "crypto/rand"
? "encoding/gob"
? "math/big"
? mathRand "math/rand"
? "time"
? t "tools"
)
// getRandomPrime 用于獲取一個(gè)隨機(jī)的質(zhì)數(shù)
func getRandomPrime() *big.Int {
? for {
?????? tmpN, errT := rand.Prime(rand.Reader, 10)
?????? if errT != nil {
???????????? // 有錯(cuò)誤發(fā)生則繼續(xù)循環(huán)塞弊,直至正確生成一個(gè)質(zhì)數(shù)為止
???????????? continue
?????? }
?????? return tmpN
? }
}
// calKeys 用于生成一組公鑰漱逸、私鑰
func calKeys() (pubKey *big.Int, privateKey *big.Int, modTotal *big.Int) {
? // 令 p 為一個(gè)隨機(jī)的質(zhì)數(shù)
? p := getRandomPrime()
? // 令 q 為一個(gè)不等于 p 的隨機(jī)質(zhì)數(shù)
? var q *big.Int
? for {
?????? q = getRandomPrime()
?????? if q.Cmp(p) != 0 {
???????????? break
?????? }
? }
? t.Printfln("p: %v, q: %v", p, q)
? // 令 n 為 p 和 q 的乘積(公倍數(shù))
? n := new(big.Int).Mul(p, q)
? t.Printfln("n(模數(shù)): %v", n)
? // bigOneT 相當(dāng)于一個(gè)常數(shù) 1,是 *big.Int 類型的
? bigOneT := big.NewInt(1)
? // 令 m = (p - 1) * (q - 1)
? m := new(big.Int).Mul(new(big.Int).Sub(p, bigOneT), new(big.Int).Sub(q, bigOneT))
? t.Printfln("m: %v", m)
? // 從3開始循環(huán)選擇一個(gè)符合條件的公鑰 e
? e := big.NewInt(3)
? for {
?????? // 每次不符合條件時(shí)游沿,e = e + 1饰抒;
?????? // 實(shí)際上,e = e + 2 會(huì)更快诀黍,因?yàn)榕紨?shù)更可能會(huì)有公約數(shù)
?????? e.Add(e, bigOneT)
?????? // 獲取 e 與 (p - 1) 的最大公約數(shù)
?????? diff1 := new(big.Int).GCD(nil, nil, e, new(big.Int).Sub(p, bigOneT))
?????? // 獲取 e 與 (q - 1) 的最大公約數(shù)
?????? diff2 := new(big.Int).GCD(nil, nil, e, new(big.Int).Sub(q, bigOneT))
?????? // 獲取 e 與 m 的最大公約數(shù)
?????? diff3 := new(big.Int).GCD(nil, nil, e, m)
?????? // 選擇合適的 e 的條件是袋坑,e 與 (p - 1)、(q - 1)眯勾、m 必須都分別互為質(zhì)數(shù)
?????? // 也就是最大公約數(shù)為 1
?????? if diff1.Cmp(bigOneT) == 0 && diff2.Cmp(bigOneT) == 0 && diff3.Cmp(bigOneT) == 0 {
???????????? break
?????? }
? }
? t.Printfln("e(公鑰): %v", e)
? // 計(jì)算私鑰
? d := new(big.Int).ModInverse(e, m)
? t.Printfln("d(私鑰): %v", d)
? return e, d, n
}
func main() {
? // 初始化隨機(jī)數(shù)種子
? mathRand.Seed(time.Now().Unix())
? // 獲取公鑰(pubKeyT)枣宫、私鑰(privateKeyT)和共用的模數(shù)(modTotalT)
? // modTotalT 可以公開
? // 也可以將pubKeyT和modTotalT合起來看做公鑰
? // 將privateKeyT和modTotalT合起來看做私鑰
? pubKeyT, privateKeyT, modTotalT := calKeys()
? // 未加密的文本
? originalText := "我們都很nice。"
? t.Printfln("原文:%#v", originalText)
? // 下面開始加密的過程
? // 用于存放密文的大整數(shù)切片
? cypherSliceT := make([]big.Int, 0)
? // 注意用 range 遍歷 string 時(shí)吃环,其中的 v 都是 rune 類型
? for _, v := range originalText {
?????? // 每個(gè) Unicode 字符將作為數(shù)值用公鑰和模數(shù)進(jìn)行加密
?????? cypherSliceT = append(cypherSliceT, *new(big.Int).Exp(big.NewInt(int64(v)), pubKeyT, modTotalT))
? }
? var cypherBufT bytes.Buffer
? // 用gob包將密文大整數(shù)切片轉(zhuǎn)換為字節(jié)切片以便傳輸或保存
? gob.NewEncoder(&cypherBufT).Encode(cypherSliceT)
? cypherBytesT := cypherBufT.Bytes()
? t.Printfln("密文數(shù)據(jù):%#v", cypherBytesT)
? // 下面開始解密的過程
? // 獲得加密后的密文字節(jié)切片
? decryptBufT := bytes.NewBuffer(cypherBytesT)
? var decryptSliceT []big.Int
? // 用gob包將密文字節(jié)切片轉(zhuǎn)換為對(duì)應(yīng)的密文大整數(shù)切片
? gob.NewDecoder(decryptBufT).Decode(&decryptSliceT)
? // 為了演示也颤,將分別用私鑰和公鑰來解密
? // decryptRunes1T用于存放用私鑰解密的結(jié)果
? // decryptRunes2T用于存放用公鑰解密的結(jié)果
? decryptRunes1T := make([]rune, 0)
? decryptRunes2T := make([]rune, 0)
? // 循環(huán)對(duì)每個(gè)大整數(shù)進(jìn)行解密
? for _, v := range decryptSliceT {
?????? // 注意解密后的大整數(shù)要轉(zhuǎn)換回 rune 格式才符合要求
?????? decryptRunes1T = append(decryptRunes1T, rune((*(new(big.Int))).Exp(&v, privateKeyT, modTotalT).Int64()))
?????? decryptRunes2T = append(decryptRunes2T, rune((*(new(big.Int))).Exp(&v, pubKeyT, modTotalT).Int64()))
? }
? decryptText1T := string(decryptRunes1T)
? t.Printfln("用私鑰解密后還原的文本:%#v", decryptText1T)
? decryptText2T := string(decryptRunes2T)
? t.Printfln("用公鑰解密后還原的文本:%#v", decryptText2T)
}
代碼 14?5 crypto2/crypto2.go
先看看代碼 14?5的多次運(yùn)行結(jié)果:
C:\goprjs\src\test3>go run test3.go
p: 911, q: 809
n(模數(shù)): 736999
m: 735280
e(公鑰): 9
d(私鑰): 408489
原文:"我們都很nice。"
密文數(shù)據(jù):[]byte{0xd, 0xff, 0x83, 0x2, 0x1, 0x2, 0xff, 0x84, 0x0, 0x1, 0xff, 0x82, 0x0, 0x0, 0xf, 0xff, 0x81, 0x5, 0x1, 0x1, 0x3, 0x49, 0x6e, 0x74, 0x1, 0xff, 0x82, 0x0, 0x0, 0x0, 0x30, 0xff, 0x84, 0x0, 0x9, 0x4, 0x2, 0x5, 0x7d, 0xc1, 0x4,
0x2, 0x6, 0x7b, 0xeb, 0x4, 0x2, 0x5, 0x36, 0x2b, 0x4, 0x2, 0x1, 0xd9, 0xf6, 0x4, 0x2, 0xb, 0x1a, 0x81, 0x3, 0x2, 0x66, 0x43, 0x4, 0x2, 0x3, 0x7f, 0x5f, 0x4, 0x2, 0x4, 0x5c, 0x80, 0x4, 0x2, 0x6, 0x2, 0xe}
用私鑰解密后還原的文本:"我們都很nice郁轻。"
用公鑰解密后還原的文本:"\U00057324\U0003aa62\U0005f3d0\U000a2f3a\U00088985?\U000420f8\U0001a4a7\U0006a2bb"
C:\goprjs\src\test3>go run test3.go
p: 937, q: 809
n(模數(shù)): 758033
m: 756288
e(公鑰): 5
d(私鑰): 453773
原文:"我們都很nice翅娶。"
密文數(shù)據(jù):[]byte{0xd, 0xff, 0x83, 0x2, 0x1, 0x2, 0xff, 0x84, 0x0, 0x1, 0xff, 0x82, 0x0, 0x0, 0xf, 0xff, 0x81, 0x5, 0x1, 0x1, 0x3, 0x49, 0x6e, 0x74, 0x1, 0xff, 0x82, 0x0, 0x0, 0x0, 0x31, 0xff, 0x84, 0x0, 0x9, 0x4, 0x2, 0x6, 0xf7, 0xe6, 0x4,
0x2, 0x6, 0xa6, 0x46, 0x4, 0x2, 0x3, 0x73, 0xf6, 0x4, 0x2, 0x7, 0x76, 0x3b, 0x4, 0x2, 0xa, 0x83, 0x13, 0x4, 0x2, 0x8, 0xba, 0x85, 0x4, 0x2, 0x5, 0xbe, 0xc2, 0x4, 0x2, 0xb, 0x27, 0x6d, 0x4, 0x2, 0x6, 0xdb, 0x5a}
用私鑰解密后還原的文本:"我們都很nice文留。"
用公鑰解密后還原的文本:"??\U000ab98a\U00015852\U0008af0c??\U0006c2a7\U0001f26f??\U0009ee7c"
C:\goprjs\src\test3>go run test3.go
p: 911, q: 907
n(模數(shù)): 826277
m: 824460
e(公鑰): 11
d(私鑰): 74951
原文:"我們都很nice。"
密文數(shù)據(jù):[]byte{0xd, 0xff, 0x83, 0x2, 0x1, 0x2, 0xff, 0x84, 0x0, 0x1, 0xff, 0x82, 0x0, 0x0, 0xf, 0xff, 0x81, 0x5, 0x1, 0x1, 0x3, 0x49, 0x6e, 0x74, 0x1, 0xff, 0x82, 0x0, 0x0, 0x0, 0x31, 0xff, 0x84, 0x0, 0x9, 0x4, 0x2, 0x2, 0x9c, 0x0, 0x4, 0x2, 0x2, 0x71, 0x1e, 0x4, 0x2, 0xb, 0xde, 0x42, 0x4, 0x2, 0xa, 0xb6, 0xe4, 0x4,
0x2, 0xa, 0xe3, 0x86, 0x4, 0x2, 0x9, 0x72, 0x10, 0x4, 0x2, 0xa, 0xc6, 0xab, 0x4, 0x2, 0x2, 0xcd, 0x41, 0x4, 0x2, 0x6, 0x78, 0xc5}
用私鑰解密后還原的文本:"我們都很nice竭沫。"
用公鑰解密后還原的文本:"\U0008bb1f\U000992b0\U00060c54\U000c615b\U000a0f37\U000b8b29\U000bc9cd\U000812ec\U000c2517"
可以看出:每次生成的公鑰燥翅、私鑰和模數(shù)都不一樣;但每次用公鑰加密數(shù)據(jù)后蜕提,如果用私鑰進(jìn)行解密總是可以正確地還原原文森书,而用公鑰則無法正確解密(結(jié)果總是亂碼)。這說明這個(gè)公私鑰加密算法是設(shè)計(jì)成功的贯溅。
對(duì)于代碼 14?5,其中已經(jīng)有詳盡的注釋躲查,請(qǐng)一定參看它浅,另外我們?cè)傺a(bǔ)充說明幾點(diǎn):
首先,再次聲明算法的數(shù)學(xué)意義和數(shù)學(xué)計(jì)算過程我們將略過不解釋镣煮;
p姐霍、q、n典唇、m都是生成公鑰私鑰對(duì)的時(shí)候所需的中間過程參數(shù)镊折,其中的n還將被用于作為最后加密和解密算法的模數(shù);
用于生成質(zhì)數(shù)的函數(shù)getRandomPrime中介衔,使用了rand.Prime函數(shù)來產(chǎn)生隨機(jī)質(zhì)數(shù)恨胚,該函數(shù)第二個(gè)參數(shù)是生成質(zhì)數(shù)的最大二進(jìn)制位數(shù),它限制了可能得到的質(zhì)數(shù)的上限范圍炎咖,位數(shù)越多隨機(jī)質(zhì)數(shù)的最大值就越大赃泡,但生成時(shí)間也越長(zhǎng)。真正的公私鑰算法中乘盼,使用的質(zhì)數(shù)一般越大越好升熊,本代碼因僅用作示例,所以只用了10個(gè)二進(jìn)制位來生成質(zhì)數(shù)绸栅;
由于本算法中涉及較大的冪運(yùn)算级野,因此使用了可以表示大數(shù)字的math.big庫,從而一些計(jì)算過程也只能表示的比較復(fù)雜粹胯;
公鑰e理論上可以從符合條件的所有值中任意選擇蓖柔,但一般習(xí)慣從3開始向上挑選;
公鑰e和模數(shù)n都可以公開风纠,私鑰d則不應(yīng)公開渊抽;
本例中使用該算法對(duì)一個(gè)字符串中的所有Unicode字符逐一用公鑰進(jìn)行加密;解密過程中則反向解密议忽;由于用range遍歷字符串時(shí)得到的是一個(gè)一個(gè)rune類型的數(shù)據(jù)懒闷,所以加解密時(shí)要分別做rune到big.Int的類型轉(zhuǎn)換和其反向轉(zhuǎn)換。也可以用索引遍歷字符串的每個(gè)字節(jié),來使加密解密針對(duì)byte類型來進(jìn)行愤估。