ECC
橢圓曲線密碼學(xué)(英語:Elliptic Curve Cryptography眯分,縮寫:ECC)是一種基于橢圓曲線數(shù)學(xué)的公開密鑰加密演算法勘畔。
ECC的主要優(yōu)勢(shì)是它相比RSA加密演算法使用較小的密鑰長度并提供相當(dāng)?shù)燃?jí)的安全性慰枕。
安全性高
- 有研究表示160位的橢圓密鑰與1024位的RSA密鑰安全性相同魔熏。
處理速度快
- 在私鑰的加密解密速度上三圆,ecc算法比RSA、DSA速度更快污呼。
- 存儲(chǔ)空間占用小裕坊。
- 帶寬要求低。
ECC被廣泛認(rèn)為是在給定密鑰長度的情況下燕酷,最強(qiáng)大的非對(duì)稱算法籍凝,因此在對(duì)帶寬要求十分緊的連接中會(huì)十分有用。
基于這個(gè)秘密值苗缩,用來對(duì)Alice和Bob之間的報(bào)文進(jìn)行加密的實(shí)際方法是適應(yīng)以前的饵蒂,最初是在其他組中描述使用的離散對(duì)數(shù)密碼系統(tǒng)。這些系統(tǒng)包括:
- Diffie-Hellman 橢圓曲線迪菲-赫爾曼密鑰交換 —ECDH
- MQV(英語:Menezes–Qu–Vanstone)—ECMQV
- ElGamal discrete log cryptosystem ElGamal離散對(duì)數(shù)密碼體制— ECElGamal
- 數(shù)字簽名算法—ECDSA
ECDSA
每個(gè)人可能都聽說過ECDSA算法酱讶。當(dāng)我提到數(shù)字簽名的時(shí)候退盯,有些人能夠很好地識(shí)別出它岂嗓,有些人卻不知道我說的是什么攻礼。
我曾經(jīng)試圖去理解ECDSA是如何工作的读虏,但是大部分的在線參考資料都是有缺失的芹枷,不足以讓我能夠很好地理解它铲汪。他們要么太基礎(chǔ)了──只是解釋算法的基礎(chǔ)然后留給你“它是如何工作的吊洼?”的疑問──要么就是太高階了玻蝌,完全略過那些你本該知道但它卻假設(shè)你已經(jīng)知道的基礎(chǔ)知識(shí)万俗。因此膏萧,我始終在“它是如何工作的”和“我們?nèi)绾尾拍軌蚶斫馑墓ぷ髟碇g”徘徊漓骚。
概念
橢圓曲線數(shù)字簽名算法(ECDSA)是使用橢圓曲線密碼(ECC)對(duì)數(shù)字簽名算法(DSA)的模擬。
ECDSA的全名是Elliptic Curve DSA榛泛,即橢圓曲線DSA蝌蹂。它是Digital Signature Algorithm (DSA)應(yīng)用了橢圓曲線加密算法的變種。橢圓曲線算法的原理很復(fù)雜曹锨,但是具有很好的公開密鑰算法特性孤个,通過公鑰無法逆向獲得私鑰。
主要用于對(duì)數(shù)據(jù)(比如一個(gè)文件)創(chuàng)建數(shù)字簽名沛简,以便于你在不破壞它的安全性的前提下對(duì)它的真實(shí)性進(jìn)行驗(yàn)證齐鲤〕夥希可以將它想象成一個(gè)實(shí)際的簽名,你可以識(shí)別部分人的簽名给郊,但是你無法在別人不知道的情況下偽造它牡肉。而ECDSA簽名和真實(shí)簽名的區(qū)別在于,偽造ECDSA簽名是根本不可能的淆九。
你不應(yīng)該將ECDSA與用來對(duì)數(shù)據(jù)進(jìn)行加密的AES(高級(jí)加密標(biāo)準(zhǔn))相混淆统锤。ECDSA不會(huì)對(duì)數(shù)據(jù)進(jìn)行加密、或阻止別人看到或訪問你的數(shù)據(jù)炭庙,它可以防止的是確保數(shù)據(jù)沒有被篡改饲窿。
ECDSA當(dāng)中有兩個(gè)詞需要注意:Curve(曲線)和Algorithm(算法),這意味著ECDSA基本上是基于數(shù)學(xué)的焕蹄。
基礎(chǔ)支撐
原理非常簡單逾雄,有一個(gè)數(shù)學(xué)方程,在圖上畫了一條曲線腻脏,然后你在這條曲線上面隨機(jī)選取了一個(gè)點(diǎn)作為你的原點(diǎn)(point of origin)嘲驾。接著你產(chǎn)生了一個(gè)隨機(jī)數(shù),作為你的私鑰(Private key)迹卢,最后你用上面的隨機(jī)數(shù)和原點(diǎn)通過一些復(fù)雜的魔法數(shù)學(xué)方程得到該條曲線上面的第二個(gè)點(diǎn)辽故,這是你的公鑰(Public key)。
當(dāng)你想要對(duì)一個(gè)文件進(jìn)行簽名的時(shí)候腐碱,你會(huì)用這個(gè)私鑰(隨機(jī)數(shù))和文件的哈希(一串獨(dú)一無二的代表該文件的數(shù))組成一個(gè)魔法數(shù)學(xué)方程誊垢,這將給出你的簽名。簽名本身將被分成兩部分症见,稱為R和S喂走。為了驗(yàn)證簽名的正確性,你只需要公鑰(用私鑰在曲線上面產(chǎn)生的點(diǎn))并將公鑰和簽名的一部分S一起代入另外一個(gè)方程谋作,如果這個(gè)簽名是由私鑰正確簽名過的數(shù)字簽名芋肠,那么它將給出簽名的另外一部分R。簡單來說遵蚜,一個(gè)數(shù)字簽名包含兩個(gè)數(shù)字帖池,R和S,然后你使用一個(gè)私鑰來產(chǎn)生R和S吭净,如果將公鑰和S代入被選定的魔法數(shù)學(xué)方程給出R的話睡汹,這個(gè)簽名就是有效的。僅僅知道公鑰是無法知道私鑰或者創(chuàng)建出數(shù)字簽名寂殉。
ECDSA只使用整數(shù)數(shù)學(xué)囚巴,沒有浮點(diǎn)數(shù),這意味著可能的數(shù)值是1,2彤叉,3庶柿,1.5,2.5則是不被允許的秽浇,并且浮庐,整數(shù)的范圍由簽名當(dāng)中所采用的位數(shù)決定,更多的位數(shù)意味著更大的數(shù)字范圍兼呵,更高的安全性能,因?yàn)檫@使得“猜”到方程當(dāng)中所采用的具體數(shù)字變得更難腊敲。正如大家所應(yīng)該知道的击喂,計(jì)算機(jī)采用比特來表示數(shù)據(jù),一個(gè)比特是二進(jìn)制當(dāng)中的一位碰辅,八個(gè)比特表示一個(gè)字節(jié)懂昂。每次你增加一個(gè)比特,可表示的最大整數(shù)就可以翻一倍没宾,使用4個(gè)比特凌彬,你可以表示0~15,一共16個(gè)數(shù)字循衰,6個(gè)比特铲敛,可以表示64個(gè)數(shù)字。一個(gè)字節(jié)会钝,可以表示256個(gè)數(shù)字伐蒋,32比特,可以表示4294967296個(gè)數(shù)字迁酸。通常ECDSA會(huì)總共使用160比特先鱼,它可以表示相當(dāng)大的數(shù),可以由49個(gè)數(shù)字在里面奸鬓。
用途
除了顯而易見的“我需要對(duì)一份文件/合同進(jìn)行簽名”焙畔,還有一個(gè)非常流行的應(yīng)用場(chǎng)景:讓我們以一個(gè)不想自己的數(shù)據(jù)被用戶修改或者破壞的應(yīng)用程序?yàn)槔热缫粋€(gè)只允許你載入官方地圖和不可修改的模塊的游戲串远,或者一部只允許你安裝官方應(yīng)用程序的手機(jī)或其它設(shè)備宏多。
在這些案例當(dāng)中,相關(guān)文件(應(yīng)用程序澡罚、游戲地圖绷落、數(shù)據(jù)等)會(huì)用ECDSA進(jìn)行簽名,公鑰會(huì)隨應(yīng)用程序/游戲/設(shè)備一起捆綁并用來驗(yàn)證簽名來確保數(shù)據(jù)沒有被修改始苇,而私鑰在本地一個(gè)私密的地方進(jìn)行保存砌烁。由于你可以用公鑰對(duì)簽名進(jìn)行驗(yàn)證,但是不能用它創(chuàng)建或者偽造新的簽名,你可以無所顧忌地將公鑰隨應(yīng)用程序/游戲/設(shè)備一起分發(fā)函喉。
這與AES相比避归,區(qū)別是顯而易見的。AES加密系統(tǒng)允許你對(duì)數(shù)據(jù)進(jìn)行加密管呵,但是你需要用密鑰來解密梳毙,這就要求你將密鑰與應(yīng)用程序一起捆綁,破壞了對(duì)數(shù)據(jù)進(jìn)行保護(hù)防止數(shù)據(jù)被用戶修改的目的捐下。
ECDSA簽名過程
大家知道加密算法可以分為對(duì)稱加密和非對(duì)稱加密兩大類账锹,對(duì)稱加密就是說加密和解密用的是同一個(gè)秘鑰(所以秘鑰不能公開),非對(duì)稱加密就是用公鑰加密坷襟,只有私鑰能解密(注意奸柬,跟數(shù)字簽名相反,數(shù)字簽名是用私鑰簽婴程,用公鑰驗(yàn)證)廓奕。
非對(duì)稱加密算法的其中一種就是用橢圓曲線,就是說我們先規(guī)定一條曲線档叔,它大致長這樣(不同的曲線可以有不同的參數(shù)):
這條曲線上桌粉,每個(gè)點(diǎn)就可以用(x, y)坐標(biāo)來表示。我們規(guī)定x和y是固定大小的整數(shù)(通常是256位的整數(shù)衙四,32字節(jié))铃肯。這條曲線上的點(diǎn)就可以用兩個(gè)256位的整數(shù)來表示。
重點(diǎn)來了:橢圓曲線上的一個(gè)點(diǎn)可以乘以一個(gè)整數(shù)來得到曲線上的另一個(gè)點(diǎn)(相當(dāng)于移動(dòng)點(diǎn)然后取模)传蹈,這個(gè)乘的操作非吃笛Γ快,而反過來要把兩個(gè)點(diǎn)相除反推出這個(gè)乘數(shù)就非常慢卡睦,被認(rèn)為是不可行宴胧。所以我們就可以選一個(gè)秘密的整數(shù)作為私鑰,去乘以曲線上的一個(gè)固定的“生成點(diǎn)“來得到公鑰(曲線上的另一個(gè)點(diǎn))表锻,這樣一來其他人是無法從公鑰算出我們私鑰的恕齐。
再說一遍,私鑰是一個(gè)整數(shù)瞬逊,公鑰是曲線上的一個(gè)點(diǎn)显歧。
由于公鑰是一個(gè)點(diǎn),有兩個(gè)坐標(biāo)x, y确镊,每個(gè)坐標(biāo)256位士骤,這樣總共是64字節(jié),但是我們可以把公鑰壓縮到33字節(jié)蕾域,具體方法是取出y的正負(fù)號(hào)加上2形成一字節(jié)拷肌,然后照抄x的32字節(jié)到旦。因?yàn)閺膞可以推出y的絕對(duì)值,所以可以這樣壓縮巨缘。
我們有了私鑰添忘,我們就可以去生成簽名。我們先把需要簽的信息(可以是一次轉(zhuǎn)賬內(nèi)容若锁,或者是證書)哈希一下搁骑,得到哈希值。如果是用ECDSA-SHA256那么哈希值就是32字節(jié)又固。
這里需要注意仲器,一般情況下ECDSA生成的簽名帶有隨機(jī)性,也就是說我同一個(gè)私鑰仰冠,簽同一份信息乏冀,簽很多次會(huì)得到很多份不同的簽名,但每一份都是有效的沪停,可驗(yàn)證的煤辨。這個(gè)隨機(jī)性來自于裳涛,生成簽名的時(shí)候木张,你可以給ECDSA一個(gè)隨機(jī)數(shù)k。ECDSA先用k乘以生成點(diǎn)G得到一個(gè)隨機(jī)點(diǎn)R端三,它的x坐標(biāo)就是簽名的第一部分r舷礼。簽名的第二部分s,是用 (哈希值 + r*私鑰)/ k算出來郊闯。r和s拼在一起就是數(shù)字簽名妻献。
那你可能要問了,如果我不希望我的簽名帶有隨機(jī)性团赁,比如說我想做測(cè)試育拨,需要每次測(cè)試得到一樣的結(jié)果,怎么辦呢欢摄?rfc6979 規(guī)定熬丧,在需要deterministic ECDSA的時(shí)候,可以把哈希值和私鑰拼接在一起用作hmac-drbg的種子怀挠,生成一個(gè)偽隨機(jī)數(shù)來作為k析蝴。因?yàn)榉N子是確定的,所以k, R, s都是確定的绿淋。
驗(yàn)證簽名的時(shí)候闷畸,需要先用信息哈希值、s和簽發(fā)者的公鑰來算出R吞滞,如果R的x坐標(biāo)確實(shí)是簽名里的r佑菩,就說明簽名確實(shí)是簽發(fā)者的私鑰生成的。
小工具:https://javacardos.com/tools/ecdsa-sign-verify
JDK中對(duì)于ECDSA的實(shí)現(xiàn)
特別注意的是:ECDSA簽名算法,只是在JDK1.7之后才有實(shí)現(xiàn)倘待,最常見的場(chǎng)景是在微軟的產(chǎn)品的安裝的產(chǎn)品密鑰的設(shè)計(jì)
1疮跑、KeyPairGenerator
KeyPairGenerator 類用于生成公鑰和私鑰對(duì)。密鑰對(duì)生成器是使用 getInstance 工廠方法(返回一個(gè)給定類的實(shí)例的靜態(tài)方法)構(gòu)造的凸舵。
特定算法的密鑰對(duì)生成器可以創(chuàng)建能夠與此算法一起使用的公鑰/私鑰對(duì)祖娘。它還可以將特定于算法的參數(shù)與每個(gè)生成的密鑰關(guān)聯(lián)。
有兩種生成密鑰對(duì)的方式:與算法無關(guān)的方式和特定于算法的方式啊奄。
下面我們將按照指定ECDSA算法去生成秘鑰KeyPairGenerator.getInstance("EC");
2渐苏、ECDSAPublicKey
ECDSA公用密鑰的接口
3、ECDSAPublicKey
ECDSA 專用密鑰的接口
4菇夸、PKCS8EncodedKeySpec
PKCS8EncodedKeySpec類繼承EncodedKeySpec類琼富,以編碼格式來表示私鑰。
PKCS8EncodedKeySpec類使用PKCS#8標(biāo)準(zhǔn)作為密鑰規(guī)范管理的編碼格式
5庄新、Signature
Signature 類用來為應(yīng)用程序提供數(shù)字簽名算法功能鞠眉。數(shù)字簽名用于確保數(shù)字?jǐn)?shù)據(jù)的驗(yàn)證和完整性。
在所有算法當(dāng)中择诈,數(shù)字簽名可以是 NIST 標(biāo)準(zhǔn)的 ECDSA械蹋,它使用 ECDSA 和 SHA-1⌒呱郑可以將使用 SHA-1 消息摘要算法的 ECDSA 算法指定為SHA1withECDSA哗戈。
其他摘要算法和比較如下圖:
在Java中,簽名和校驗(yàn)荷科,都是通過: Signature 類來實(shí)現(xiàn)的唯咬。
該類的主要方法如下:
getInstance(String algorithm)
工廠方法,獲取Signature實(shí)例畏浆,而參數(shù):algorithm就是簽名算法的名稱胆胰,這里我們使用的是:SHA256withECDSA
更具體的參數(shù),請(qǐng)瀏覽:https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Signature
initSign(PrivateKey privateKey)
initVerify(PublicKey publicKey)
初始化成簽名或者校驗(yàn)刻获,Signature類的實(shí)例蜀涨,在同一個(gè)階段只能完成簽名或者校驗(yàn)之一的任務(wù),因此調(diào)用initSign或者initVeify讓Signature進(jìn)入相應(yīng)的狀態(tài)将鸵。update(byte[] data)
無論是計(jì)算簽名勉盅,還是校驗(yàn)數(shù)據(jù),都需要傳入被簽名或者被校驗(yàn)的數(shù)據(jù)顶掉,調(diào)用該方法進(jìn)行計(jì)算草娜。注意,該方法可以調(diào)用多次痒筒,通常數(shù)據(jù)都可能非常大宰闰,不可能一次性讀入內(nèi)存(從磁盤上或者網(wǎng)絡(luò))茬贵,因此我們可以對(duì)數(shù)據(jù)進(jìn)行分塊,一次性一KB或者合適的塊進(jìn)行多次調(diào)用sign()
獲取簽名移袍,當(dāng)所有的數(shù)據(jù)都調(diào)用update計(jì)算后解藻,就可以獲取簽名了,返回的簽名是一個(gè)byte數(shù)組verify(byte[] signature)
校驗(yàn)簽名葡盗,當(dāng)所有的數(shù)據(jù)都調(diào)用update計(jì)算之后螟左,調(diào)用該方法,傳遞簽名數(shù)據(jù)觅够,如果簽名正確胶背,那么返回true,否則返回false喘先,說明數(shù)據(jù)可能被篡改钳吟、偽造,或者對(duì)應(yīng)的私鑰不正確窘拯。
···
public static byte[] signData(String algorithm, byte[] data, PrivateKey key) throws Exception {
Signature signer = Signature.getInstance(algorithm);
signer.initSign(key);
signer.update(data);
return (signer.sign());
}
public static boolean verifySign(String algorithm, byte[] data, PublicKey key, byte[] sig) throws Exception {
Signature signer = Signature.getInstance(algorithm);
signer.initVerify(key);
signer.update(data);
return (signer.verify(sig));
}
@Test
public void testSignVerify() throws Exception {
// 需要簽名的數(shù)據(jù)
byte[] data = new byte[1000];
for (int i=0; i<data.length; i++)
data[i] = 0xa;
// 生成秘鑰红且,在實(shí)際業(yè)務(wù)中,應(yīng)該加載秘鑰
KeyPair keyPair = KeyUtil.createKeyPairGenerator("secp256k1");
PublicKey publicKey1 = keyPair.getPublic();
PrivateKey privateKey1 = keyPair.getPrivate();
// 生成第二對(duì)秘鑰涤姊,用于測(cè)試
keyPair = KeyUtil.createKeyPairGenerator("secp256k1");
PublicKey publicKey2 = keyPair.getPublic();
PrivateKey privateKey2 = keyPair.getPrivate();
// 計(jì)算簽名
byte[] sign1 = signData("SHA256withECDSA", data, privateKey1);
byte[] sign2 = signData("SHA256withECDSA", data, privateKey1);
// sign1和sign2的內(nèi)容不同暇番,因?yàn)镋CDSA在計(jì)算的時(shí)候,加入了隨機(jī)數(shù)k砂轻,因此每次的值不一樣
// 隨機(jī)數(shù)k需要保密奔誓,并且每次不同
// 用對(duì)應(yīng)的公鑰驗(yàn)證簽名斤吐,必須返回true
Assert.assertTrue(verifySign("SHA256withECDSA", data, publicKey1, sign1));
// 數(shù)據(jù)被篡改搔涝,返回false
data[1] = 0xb;
Assert.assertFalse(verifySign("SHA256withECDSA", data, publicKey1, sign1));
data[1] = 0xa;
Assert.assertTrue(verifySign("SHA256withECDSA", data, publicKey1, sign1));
// 簽名被篡改,返回false
// 簽名為DER格式和措,前三個(gè)字節(jié)是標(biāo)識(shí)和數(shù)據(jù)長度庄呈,如果修改了這三個(gè)會(huì)拋出異常,無效簽名格式
sign1[20] = (byte)~sign1[20];
Assert.assertFalse(verifySign("SHA256withECDSA", data, publicKey1, sign1));
// 使用其他公鑰驗(yàn)證派阱,返回false
Assert.assertFalse(verifySign("SHA256withECDSA", data, publicKey2, sign1));
}
···
我們?cè)跍y(cè)試用例中可以看到诬留,只要是數(shù)據(jù)、簽名被修改贫母,或者不正確的私鑰都會(huì)引發(fā)簽名驗(yàn)證失敗.