G8.5 Go語言實(shí)現(xiàn)公鑰私鑰體系的不對(duì)稱加密

我們之前介紹的不對(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)行愤估。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帮辟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子玩焰,更是在濱河造成了極大的恐慌由驹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昔园,死亡現(xiàn)場(chǎng)離奇詭異蔓榄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)默刚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門甥郑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荤西,你說我怎么就攤上這事澜搅。” “怎么了邪锌?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵勉躺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我觅丰,道長(zhǎng)饵溅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任妇萄,我火速辦了婚禮概说,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚣伐。我一直安慰自己糖赔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布轩端。 她就那樣靜靜地躺著放典,像睡著了一般。 火紅的嫁衣襯著肌膚如雪基茵。 梳的紋絲不亂的頭發(fā)上奋构,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音拱层,去河邊找鬼弥臼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛根灯,可吹牛的內(nèi)容都是我干的径缅。 我是一名探鬼主播掺栅,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼纳猪!你這毒婦竟也來了氧卧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤氏堤,失蹤者是張志新(化名)和其女友劉穎沙绝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鼠锈,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡闪檬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了购笆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粗悯。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖由桌,靈堂內(nèi)的尸體忽然破棺而出为黎,到底是詐尸還是另有隱情邮丰,我是刑警寧澤行您,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站剪廉,受9級(jí)特大地震影響娃循,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斗蒋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一捌斧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泉沾,春花似錦捞蚂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俊马,卻和暖如春丁存,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柴我。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工解寝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人艘儒。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓聋伦,卻偏偏與公主長(zhǎng)得像夫偶,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘉抓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容