ECC 橢圓曲線加解密算法
一、爲什麼叫橢圓曲線
首先回憶一下直線方程式 y=ax+b ,在座標系中表示一條直線,是一次方程,圓錐曲線可以用二次方程表示。橢圓曲線是用三次方程表示,如下:
其中,a 和 b 的取值不同,橢圓曲線的形狀會有所改變,經典的形狀如下圖所示:
這時有讀者會有疑問了,“上圖中不是一個橢圓的形狀啊,爲什麼叫橢圓曲線啊?”,原因是橢圓曲線的 3 次方程式和橢圓周長公式中的一部分很像,下面是橢圓周長公式:
可以看出積分公式中分母的平方恰恰就是橢圓曲線的公式。
橢圓曲線有以下兩個特點:
- 畫一條直線跟橢圓曲線相交,它們最多有三個交點;
- 關於 X 軸對稱。
二、比特幣使用的橢圓曲線
比特幣中使用的橢圓曲線是 secp256k1,上文圖中的橢圓曲線是連續的,但在像比特幣這些實際應用中,都是有限域上定義的離散曲線。
橢圓曲線三次方程式中參數 a, b,基點 G 的不同,橢圓曲線的加密性能也不一樣。一些常用的使用特定參數的橢圓曲線都有特定的標識,現在世界上比較主流的有:
- 美國國家標準與技術研究院(NIST)和美國國家安全局(NSA)的 secp256r1、secp521r1 等橢圓曲線
- 中國國家密碼局認定的 SM2 國產密碼算法。
- 比特幣和以太坊使用的 secp256k1。
下面來看一下 secp256k1 的所有參數和方程式: y^2 = x^3 + 7 ( a = 0,b = 7)
選擇的基點是:
可以看出方程式和參數並不複雜,反而簡單,那麼有讀者要問了“這麼簡單的方程式和參數,那這個加密算法還安全嗎?”,這個問題非常好,下面會講到它的安全性其實並不是由方程式和參數決定的,而是由橢圓曲線上的運算決定的,請繼續往下看。
三、橢圓曲線運算法則
在橢圓曲線上會定義兩種運算,加法和乘法,但是其實它們和通常意義上的加法和乘法不一樣的,只是爲了方便理解所以這麼叫,怎麼不一樣呢,下面看看具體的定義。
1. 橢圓曲線加法
根據上面介紹的橢圓曲線的特性“畫一條直線跟橢圓曲線相交,它們最多有三個交點”,可以進行以下定義:
- 假設橢圓曲線上有 P、Q 兩個點,經過這兩個點做一條直線和橢圓曲線相交於第三點 R,然後做關於 x 軸的對稱點 -R,-R 即是 R 的逆元,根據阿貝爾羣的定義,-R 也一定在橢圓曲線上。
- 定義 P+Q = -R,也就是說橢圓曲線上任意兩點的和也在橢圓曲線上,同樣可以引申出橢圓曲線上任意三點的和爲 0 即 P+Q+R = 0。如圖:
- 假如 P=Q,則作橢圓曲線在 P 點的切線,與曲線相交於 R,則 R = P+P = 2P
2. 橢圓曲線乘法
根據上面橢圓曲線的加法可以得出下列等式:
- P+P = 2P(過點 P 切線作一條直線)
- P+2P = 3P(過點 P 和 2P 作一條直線)
- P+3P = 4P(過點 P 和 3P 作一條直線)
假設 P 是橢圓曲線上的一個點,正整數 K 乘以 P 可以總結成公式爲:(k-1) * P + P = k * P
如果把 k 看作是兩個數相乘即 k = m * n,則可以得出滿足以下性質(在橢圓曲線密鑰交換中會用到):(m * n) * P = m * (n * P) = (n * m)p = n * (m*P)
四、橢圓曲線的難題
非對稱加密之所以難破解,根本原理就是基於一個數學上的難題,像 RSA 加密就是基於大質數因子分解困難的特性來支撐的,橢圓曲線的難題則是:橢圓曲線上的離散對數問題。
看過 RSA 加密算法原理那篇文章的讀者可能會記得它是基於取模運算,橢圓曲線也是一樣的,滿足下面公式的曲線,其中 p 是質數,x、y、a、b 都是小於 p 的非負整數:
y^2 = x^3 + ax + b (mod p) { (4a^3 + 27b^2!)=0 }
來看一下 y^2 = x^3 - x 這個公式取模後的的圖像(p=71):
可以看出,雖然很散亂,但是仔細看這些點都是關於一條直線對稱的,這條直線就是 y=71/2 這條水平直線,並且原來橢圓曲線上定義的加法和乘法都可用。
假如選擇一個點 P(4,8) 爲基點,按照橢圓曲線的加法去運算 2P、3P… 這樣的話,最後得到一個 k 次加法後的結果 kP(44,44),請問 k 是多少?
這時看一下上面的散點圖,找到 (4,8) 和(44,44)這兩個點,很難找出來通過幾次橢圓曲線加法轉變過去的,更何況這個是在公式中取模的那個質數等於 71 的情況下,如果把這個質數取得很大,難度就更大了,比特幣中使用的 Secp256k1 這條曲線中取模的質數 p 等於:
p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
這樣一個數,要逐一算出可能性取匹配幾乎是不可能的。
總結一下橢圓曲線的數學依據:
K = kG
- G 爲橢圓曲線上的一個點,叫基點;
- k 爲正整數;
- 如果給定小 k 和 G,通過橢圓曲線加法法則去計算 K 很容易;
- 如果給定 K 和 G,求小 k 就非常困難。
一般規定大 K 爲公開密鑰,小 k 爲私鑰。
五、橢圓曲線的加密強度
這裏我們那攻擊分組對稱加密 AES 算法的難度來做對比,如下表所示:
AES RSA ECC 80 1024 163 112 2240 233 128 3072 283
這組數據是國際期刊和一些學術論文中公認的結果,具體的實現步驟這裏就不介紹,重點是要明白 ECC 的強度高,像 AES-128 位的密碼強度和 RSA 的 3072 位密碼強度大約相同,同樣和 ECC 的 283 位密碼強度相同,密碼強度其實就是攻擊的難度,也就是說攻擊 128 位密鑰的 AES 的算法的難度和 ECC283 位密鑰的難度相當。
從圖表中可以觀察到 RSA 的密鑰長度增長的很多,ECC 的密鑰長度增長並不大,考慮到實際使用過程中的性能和未來能持續的安全行,ECC 是更好的選擇。
六、Go語言使用橢圓曲線簽名認證實現
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"math/big"
"fmt"
)
//通過橢圓曲線完成簽名和驗證
func main() {
//聲明明文
message := []byte("hello world")
//生成私鑰
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
//生成公鑰
pub := privateKey.PublicKey
//將明文散列
digest := sha256.Sum256(message)
//簽名
r, s, _ := ecdsa.Sign(rand.Reader, privateKey, digest[:])
//設置私鑰的參數類型爲曲線類型
param := privateKey.Curve.Params()
//獲得私鑰byte長度
curveOrderByteSize := param.P.BitLen() / 8
//獲得簽名返回值的字節
rByte, sByte := r.Bytes(), s.Bytes()
//創建數組
signature := make([]byte, curveOrderByteSize*2)
//通過數組保存了簽名結果的返回值
copy(signature[curveOrderByteSize-len(rByte):], rByte)
copy(signature[curveOrderByteSize*2-len(sByte):], sByte)
//認證
//將明文做hash散列,爲了驗證的內容對比
digest = sha256.Sum256(message)
curveOrderByteSize = pub.Curve.Params().P.BitLen() / 8
//創建兩個整形對象
r, s = new(big.Int), new(big.Int)
//設置證書值
r.SetBytes(signature[:curveOrderByteSize])
s.SetBytes(signature[curveOrderByteSize:])