Golang與非對稱加密

語言: CN / TW / HK

1、非對稱加密介紹

非對稱加密和對稱加密不同,主要區別如下

  • 使用公鑰加密,使用私鑰解密

  • 公鑰和私鑰不同

  • 公鑰可以公佈給所有人

  • 私鑰只有自己儲存

  • 相比於對稱加密,運算速度非常慢

加密過程:明文+公鑰——>密文

解密過程:密文+私鑰——>明文

非對稱加密演算法常用於資料加密和身份認證, 常見的非對稱加密演算法如下

DSS
DSA

2、DSA

DSA 是基於整數有限域離散對數難題的,其安全性與 RSA 相比差不多。 DSA 的一個重要特點是兩個素數公開,這樣,當使用別人的 pq 時,即使不知道私鑰,你也能確認它們是否是隨機產生的,還是作了手腳。 RSA 演算法卻做不到,但是其缺點就是隻能用於數字簽名,不能用於加密

3、RSA

1976 年,由於對稱加密演算法已經不能滿足需要, DiffieHellman 發表了一篇叫《密碼學新動向》的文章,介紹了公匙加密的概念,由 RivetShamirAdelman 提出了 RSA 演算法

RSA 是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前為止已知的絕大多數密碼攻擊,已被 ISO 推薦為公鑰資料加密標準

命名:Ron Rivest、Adi Shamir、Leonard Adleman

  • 金鑰越長,越難破解,目前 768 位的金鑰還無法破解(至少沒人公開宣佈),因此可以認為 1024 位的 RSA 金鑰基本安全, 2048 位的金鑰極其安全
  • RSA 的演算法原理主要用到了數論

3.1 RSA的加密過程

1、隨機選擇兩個不相等的質數 pq ,p=61,q=53

2、計算 pq 的乘積,n=3233

3、計算 n 的尤拉函式∅(n) = (p-1)(q-1),∅(n)=3120

4、隨機選擇一個整數 e ,使得1<e<∅(n),且 e∅(n) 互質,e=17

5、計算 e 對於 ∅(n) 的模反元素d,即求解e*d + ∅(n)*y =1,d=2753,y=-15

6、將 ne 封裝成公鑰, nd 封裝成私鑰,公鑰=(3233, 17),私鑰=(3233, 2753)

3.2 呼叫示例

RSA 使用示例程式碼

package main
import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"encoding/pem"
	"fmt"
)
// 使用對方的公鑰的資料, 只有對方的私鑰才能解開
func encrypt(plain string, publicKey string) (cipherByte []byte, err error) {
	msg := []byte(plain)
	// 解碼公鑰
	pubBlock, _ := pem.Decode([]byte(publicKey))
	// 讀取公鑰
	pubKeyValue, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
	if err != nil {
		panic(err)
	}
	pub := pubKeyValue.(*rsa.PublicKey)
	// 加密資料方法: 不用使用EncryptPKCS1v15方法加密,原始碼裡面推薦使用EncryptOAEP, 因此這裡使用安全的方法加密
	encryptOAEP, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, pub, msg, nil)
	if err != nil {
		panic(err)
	}
	cipherByte = encryptOAEP
	return
}
// 使用私鑰解密公鑰加密的資料
func decrypt(cipherByte []byte, privateKey string) (plainText string, err error) {
	// 解析出私鑰
	priBlock, _ := pem.Decode([]byte(privateKey))
	priKey, err := x509.ParsePKCS1PrivateKey(priBlock.Bytes)
	if err != nil {
		panic(err)
	}
	// 解密RSA-OAEP方式加密後的內容
	decryptOAEP, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, priKey, cipherByte, nil)
	if err != nil {
		panic(err)
	}
	plainText = string(decryptOAEP)
	return
}
func test() {
	msg := "Content bo be encrypted!"
	// 獲取公鑰, 生產環境往往是檔案中讀取, 這裡為了測試方便, 直接生成了.
	publicKeyData := `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZsfv1qscqYdy4vY+P4e3cAtmv
ppXQcRvrF1cB4drkv0haU24Y7m5qYtT52Kr539RdbKKdLAM6s20lWy7+5C0Dgacd
wYWd/7PeCELyEipZJL07Vro7Ate8Bfjya+wltGK9+XNUIHiumUKULW4KDx21+1NL
AUeJ6PeW+DAkmJWF6QIDAQAB
-----END PUBLIC KEY-----
`
	// 獲取私鑰
	privateKeyData := `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDZsfv1qscqYdy4vY+P4e3cAtmvppXQcRvrF1cB4drkv0haU24Y
7m5qYtT52Kr539RdbKKdLAM6s20lWy7+5C0DgacdwYWd/7PeCELyEipZJL07Vro7
Ate8Bfjya+wltGK9+XNUIHiumUKULW4KDx21+1NLAUeJ6PeW+DAkmJWF6QIDAQAB
AoGBAJlNxenTQj6OfCl9FMR2jlMJjtMrtQT9InQEE7m3m7bLHeC+MCJOhmNVBjaM
ZpthDORdxIZ6oCuOf6Z2+Dl35lntGFh5J7S34UP2BWzF1IyyQfySCNexGNHKT1G1
XKQtHmtc2gWWthEg+S6ciIyw2IGrrP2Rke81vYHExPrexf0hAkEA9Izb0MiYsMCB
/jemLJB0Lb3Y/B8xjGjQFFBQT7bmwBVjvZWZVpnMnXi9sWGdgUpxsCuAIROXjZ40
IRZ2C9EouwJBAOPjPvV8Sgw4vaseOqlJvSq/C/pIFx6RVznDGlc8bRg7SgTPpjHG
4G+M3mVgpCX1a/EU1mB+fhiJ2LAZ/pTtY6sCQGaW9NwIWu3DRIVGCSMm0mYh/3X9
DAcwLSJoctiODQ1Fq9rreDE5QfpJnaJdJfsIJNtX1F+L3YceeBXtW0Ynz2MCQBI8
9KP274Is5FkWkUFNKnuKUK4WKOuEXEO+LpR+vIhs7k6WQ8nGDd4/mujoJBr5mkrw
DPwqA3N5TMNDQVGv8gMCQQCaKGJgWYgvo3/milFfImbp+m7/Y3vCptarldXrYQWO
AQjxwc71ZGBFDITYvdgJM1MTqc8xQek1FXn1vfpy2c6O
-----END RSA PRIVATE KEY-----
`
	cipherData, err := encrypt(msg, publicKeyData)
	if err != nil {
		panic(err)
	}
	fmt.Printf("encrypt message: %x\n", cipherData)
	plainData, err := decrypt(cipherData, privateKeyData)
	if err != nil {
		panic(err)
	}
	fmt.Printf("decrypt message:%s\n", plainData)
}
func main() {
	test()
}

4、ECC

ECC 又稱橢圓曲線加密

ECC (Elliptic Curve Cryptography)橢圓曲線加密演算法,相比 RSAECC 可以使用更短的金鑰,來實現與 RSA 相當或更高的安全

定義了橢圓曲線上的加法和二倍運算

橢圓曲線依賴的數學難題是: k 為正整數, p 是橢圓曲線上的點(稱為基點),k*p=Q,已知 QP ,很難計算出k

ECC 是建立在基於橢圓曲線的離散對數的難度, 大概過程如下

給定橢圓曲線上的一個點P,一個整數k,求解Q=kP很容易;給定一個點P、Q,知道Q=kP,求整數k確是一個難題。ECDH即建立在此數學難題之上

今天只有短的 RSA 鑰匙才可能被強力方式解破。到 2008 年為止,世界上還沒有任何可靠的攻擊RSA演算法的方式。只要其鑰匙的長度足夠長,用 RSA 加密的資訊實際上是不能被解破的。但在分散式計算和量子計算機理論日趨成熟的今天, RSA 加密安全性受到了挑戰

隨著分解大整數方法的進步及完善、計算機速度的提高以及計算機網路的發展,為了保障資料的安全, RSA 的金鑰需要不斷增加,但是,金鑰長度的增加導致了其加解密的速度大為降低,硬體實現也變得越來越難以忍受,這對使用 RSA 的應用帶來了很重的負擔,因此需要一種新的演算法來代替 RSA

1985N.KoblitzMiller 提出將橢圓曲線用於密碼演算法,根據是有限域上的橢圓曲線上的點群中的離散對數問題 ECDLPECDLP 是比因子分解問題更難的問題,它是指數級的難度

橢圓曲線演算法因引數不同有多種型別, 這個網站列出了現階段那些 ECC 是相對安全的:橢圓曲線演算法安全列表, 而 curve25519 便是其中的佼佼者

Curve25519/Ed25519/X25519 是著名密碼學家 Daniel J. Bernstein2006 年獨立設計的橢圓曲線加密/簽名/金鑰交換演算法, 和現有的任何橢圓曲線演算法都完全獨立

特點是:

  • 完全開放設計: 演算法各引數的選擇直截了當,非常明確,沒有任何可疑之處,相比之下目前廣泛使用的橢圓曲線是NIST系列標準,方程的係數是使用來歷不明的隨機種子 c49d3608 86e70493 6a6678e1 139d26b7 819f7e90 生成的,非常可疑,疑似後門;
  • 高安全性: 一個橢圓曲線加密演算法就算在數學上是安全的,在實用上也並不一定安全,有很大的概率通過快取、時間、惡意輸入摧毀安全性,而25519系列橢圓曲線經過特別設計,儘可能的將出錯的概率降到了最低,可以說是實踐上最安全的加密演算法。例如,任何一個32位隨機數都是一個合法的X25519公鑰,因此通過惡意數值攻擊是不可能的,演算法在設計的時候刻意避免的某些分支操作,這樣在程式設計的時候可以不使用if ,減少了不同if分支程式碼執行時間不同的時序攻擊概率,相反, NIST系列橢圓曲線演算法在實際應用中出錯的可能性非常大,而且對於某些理論攻擊的免疫能力不高, Bernstein 對市面上所有的加密演算法使用12個標準進行了考察, 25519是幾乎唯一滿足這些標準的;
  • 速度快: 25519系列曲線是目前最快的橢圓曲線加密演算法,效能遠遠超過NIST系列,而且具有比P-256更高的安全性;
  • 作者功底深厚: Daniel J. Bernstein是世界著名的密碼學家,他在大學曾經開設過一門 UNIX 系統安全的課程給學生,結果一學期下來,發現了 UNIX 程式中的 91 個安全漏洞;他早年在美國依然禁止出口加密演算法時,曾因為把自己設計的加密演算法釋出到網上遭到了美國政府的起訴,他本人抗爭六年,最後美國政府撤銷所有指控,目前另一個非常火的高效能安全流密碼 ChaCha20 也是出自 Bernstein 之手;
  • 下一代的標準: 25519系列曲線自2006年發表以來,除了學術界無人問津, 2013 年愛德華·斯諾登曝光稜鏡計劃後,該演算法突然大火,大量軟體,如OpenSSH都迅速增加了對25519系列的支援,如今25519已經是大勢所趨,可疑的NIST曲線遲早要退出橢圓曲線的歷史舞臺,目前, RFC增加了SSL/TLS對X25519金鑰交換協議的支援,OpenSSL 1.1也加入支援,是擺脫老大哥的第一步,下一步是將 Ed25519做為可選的TLS證書籤名演算法,徹底擺脫NIST

5、ECC與RSA的比較

ECCRSA 相比,在許多方面都有對絕對的優勢,主要體現在以下方面:

  • 抗攻擊性強。相同的金鑰長度,其抗攻擊性要強很多倍
  • 計算量小,處理速度快。 ECC 總的速度比 RSADSA 要快得多
  • 儲存空間佔用小。 ECC 的金鑰尺寸和系統引數與 RSADSA 相比要小得多,意味著它所佔的存貯空間要小得多。這對於加密演算法在 IC 卡上的應用具有特別重要的意義
  • 頻寬要求低。當對長訊息進行加解密時,三類密碼系統有相同的頻寬要求,但應用於短訊息時 ECC 頻寬要求卻低得多。頻寬要求低使 ECC 在無線網路領域具有廣泛的應用前景

ECC 的這些特點使它必將取代 RSA ,成為通用的公鑰加密演算法。比如 SET 協議的制定者已把它作為下一代 SET 協議中預設的公鑰密碼演算法

6、ECDSA

因為在數字簽名的安全性高, 基於 ECCDSA 更高, 所以非常適合數字簽名使用場景, 在 SSH TLS 有廣泛使用, ECC 把離散對數安全性高很少,所以 ECC 在安全領域會成為下一個標準

golangssh 庫中就是使用這個演算法來簽名的: A 使用自己的私鑰簽名一段資料,然後將公鑰發放出去。使用者拿到公鑰後,驗證資料的簽名,如果通過則證明資料來源是 A ,從而達到身份認證的作用

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/md5"
	"crypto/rand"
	"fmt"
	"hash"
	"io"
	"math/big"
)
// SignData 用於儲存簽名的資料
type SignData struct {
	r         *big.Int
	s         *big.Int
	signhash  *[]byte
	signature *[]byte
}
// 使用私鑰簽名一段資料
func sign(message string, privateKey *ecdsa.PrivateKey) (signData *SignData, err error) {
	// 簽名資料
	var h hash.Hash
	h = md5.New()
	r := big.NewInt(0)
	s := big.NewInt(0)
	io.WriteString(h, message)
	signhash := h.Sum(nil)
	r, s, serr := ecdsa.Sign(rand.Reader, privateKey, signhash)
	if serr != nil {
		return nil, serr
	}
	signature := r.Bytes()
	signature = append(signature, s.Bytes()...)
	signData = &SignData{
		r:         r,
		s:         s,
		signhash:  &signhash,
		signature: &signature,
	}
	return
}
// 校驗數字簽名
func verifySign(signData *SignData, publicKey *ecdsa.PublicKey) (status bool) {
	status = ecdsa.Verify(publicKey, *signData.signhash, signData.r, signData.s)
	return
}
func test() {
	//使用橢圓曲線的P256演算法,現在一共也就實現了4種,我們使用折中一種,具體見http://golang.org/pkg/crypto/elliptic/#P256
	pubkeyCurve := elliptic.P256()
	privateKey := new(ecdsa.PrivateKey)
	// 生成祕鑰對
	privateKey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
	if err != nil {
		panic(err)
	}
	var publicKey ecdsa.PublicKey
	publicKey = privateKey.PublicKey
	// 簽名
	signData, err := sign("This is a message to be signed and verified by ECDSA!", privateKey)
	if err != nil {
		panic(err)
	}
	fmt.Printf("The signhash: %x\nThe signature: %x\n", *signData.signhash, *signData.signature)
	// 驗證
	status := verifySign(signData, &publicKey)
	fmt.Printf("The verify result is: %v\n", status)
}
func main() {
	test()
}

See you ~