java加密算法入門(三)-非對稱加密詳解
1、簡單介紹
這幾天一直在看非對稱的加密,相比之前的兩篇內容,這次看了兩倍多的時間還雲裏霧裏的,所以這篇文章相對之前的兩篇,概念性的東西多了些,另外是代碼的每一步我都做了介紹,方便自己以後翻閱,也方便大家理解。最後就是關於代碼的demo,DH算法、RSA算法本文中只有最基礎的用法,實際在工作中可能會涉及到密鑰的轉換X509EncodedKeySpec和PKCS8EncodedKeySpec,相關的demo名分別叫DH2Test,RSA2Test,已經上傳GIT。如果對您有幫助,請給我個star。本文為學習總結筆記,如有錯誤還請指正,如有誤導概不負責哈W( ̄_ ̄)W
1.1 什麽是非對稱加密算法?
所謂非對稱加密算法,即加密和解密使用兩個不同的密鑰。非對稱主要是相對於對稱加密算法而言的,對稱加密算法有加解密使用同一個秘鑰,非對稱算法則有一個公鑰和一個私鑰,公鑰與私鑰是一對的,這兩個共同組成一個解鑰,才能實現解密。
Ps:RSA是可以雙向加密的:私鑰加密,公鑰解密;公鑰加密,私鑰解密。
1.2 主要算法
DH 秘鑰交換算法,算是非對稱加密算法的起源
RSA 基於因子分解,既能用於數字加密也能用於數字簽名
ELGamal 基於離散對數加密
1.3 非對稱加密算法的特點
算法強度復雜、安全性依賴於算法與密鑰.但是由於算法復雜,使得非對稱算法加解密速度沒有對稱算法加解密的速度快.
對稱密鑰體制中只有一種密鑰,並且是非公開的,如果要解密就得讓對方知道密鑰.所以保證其安全性就是保證密鑰的安全.
非對稱密鑰體制有兩種密鑰,其中一個是公開的,這樣就可以不需要像對稱密碼那樣向對方傳輸密鑰了.因此安全性就大了很多.
算法復雜度:對稱密鑰<非對稱密鑰
加解密速度:對稱密鑰>非對稱密鑰
安全性:對稱密鑰<非對稱密鑰
1.4 使用到的類和相關參數
KeyPairGenerator:密鑰對生成器
KeyPair:密鑰對
PublixKey:公鑰
PrivateKey :私鑰
KeyFactory:作用是生成密鑰(包括公鑰和私鑰)
generatePublic()方法用來生成公鑰
X509EncodedKeySpec:繼承EncodedKeySpec類 ,以編碼格式來表示公鑰
PKCS8EncodedKeySpec:繼承EncodedKeySpec類,以編碼格式來表示私鑰
DHPublicKey:是PublicKey的某種具體的形式
DHParameterSpec:隨從著DH算法來使用的參數的集合
KeyAgreement:該類提供密鑰一致性(或者密鑰交換)協議的功能
SecretKey:構建的本地秘鑰
Cipher:提供加解密的功能
2、DH算法
Diffie-Hellman算法(D-H算法),密鑰一致協議。是由公開密鑰密碼體制的奠基人Diffie和Hellman所提出的一種思想。簡單的說就是允許兩名用戶在公開媒體上交換信息以生成"一致"的、可以共享的密鑰。換句話說,就是由甲方產出一對密鑰(公鑰、私鑰),乙方依照甲方公鑰產生乙方密鑰對(公鑰、私鑰)。以此為基線,作為數據傳輸保密基礎,同時雙方使用同一種對稱加密算法構建本地密鑰(SecretKey)對數據加密。這樣,在互通了本地密鑰(SecretKey)算法後,甲乙雙方公開自己的公鑰,使用對方的公鑰和剛才產生的私鑰加密數據,同時可以使用對方的公鑰和自己的私鑰對數據解密。不單單是甲乙雙方兩方,可以擴展為多方共享數據通訊,這樣就完成了網絡交互數據的安全通訊!該算法源於中國的同余定理——中國餘數定理。
DH算法的默認密鑰長度是1024,密鑰長度必須是64的倍數,在512到1024位之間。
DH是一種基於密鑰一致協議的加密算法。
密鑰一致協議就是允許兩名用戶在公開媒體上交換信息以生成"一致"的、可以共享的密鑰。
由甲方產出一對密鑰(公鑰、私鑰),乙方依照甲方公鑰產生乙方密鑰對(公鑰、私鑰),以此為基線作為數據傳輸保密基礎.同時雙方使用同一種對稱加密算法構建本地密鑰(SecretKey)對數據加密。在互通了本地密鑰算法後,甲乙雙方公開自己的公鑰,使用對方的公鑰和剛才產生的私鑰加密數據,同時可以使用對方的公鑰和自己的私鑰對數據解密。
2.1 算法流程
1.甲方構建密鑰對,將公鑰公布給乙方,將私鑰保留;雙方約定數據加密算法;乙方通過甲方公鑰構建密鑰對,將公鑰公布給甲方,將私鑰保留。
2.甲方使用私鑰、乙方公鑰、約定數據加密算法構建本地密鑰,然後通過本地密鑰加密數據,發送給乙方加密後的數據;乙方使用私鑰、甲方公鑰、約定數據加密算法構建本地密鑰,然後通過本地密鑰對數據解密。
3.乙方使用私鑰、甲方公鑰、約定數據加密算法構建本地密鑰,然後通過本地密鑰加密數據,發送給甲方加密後的數據;甲方使用私鑰、乙方公鑰、約定數據加密算法構建本地密鑰,然後通過本地密鑰對數據解密。
2.2 具體實現
使用KeyPairGenerator的getInstance()靜態方法創建實例,參數是算法名稱.指定密鑰對生成器生成指定算法的密鑰對。
KeyPairGenerator senderKeyPairGenerator =KeyPairGenerator.getInstance("DH");
使用KeyPairGenerator的initialize()方法初始化密鑰的長度.
senderKeyPairGenerator.initialize(512);
使用KeyPairGenerator的generateKeyPair()方法創建KeyPair實例.一個KeyPair實例表示一對密鑰,即一個密鑰對.包括公鑰與私鑰.
KeyPair senderKeyPair = senderKeyPairGenerator.generateKeyPair();
KeyPair的getPublic()方法返回PublicKey類型(公鑰),getPrivate()方法返回PrivateKey類型(私鑰)。
DHPublicKey接口、DHPrivateKey接口分別繼承PublicKey接口與PrivateKey接口.
公鑰是要發送給接收方的,此處同一類內省略該操作
PublicKey senderPublicKey = senderKeyPair.getPublic();//發送方公鑰
使用DHPublicKey的getParams()方法返回DHParameterSpec實例.
DHParameterSpec:此類指定隨同DH算法使用的參數集合,用於通過一方的公鑰生成另一方的密鑰對.
DHParameterSpec dhParameterSpec = ((DHPublicKey) senderPublicKey).getParams();//用發送方的公鑰產生算法參數
使用KeyPairGenerator的initialize()初始化方法時,參數還可以是DHParameterSpec的實例.用於通過一方公鑰生成密鑰對.
KeyPairGenerator receiverKeyPairGenerator = KeyPairGenerator.getInstance("DH"); receiverKeyPairGenerator.initialize(dhParameterSpec);//初始化接收方的KeyPairGenerator KeyPair receiverKeyPair = receiverKeyPairGenerator.generateKeyPair();//生成接收方的密鑰對
本地密鑰構建:KeyAgreement
KeyAgreement類用於生成本地密鑰(提供密鑰協定或密鑰交換協議的功能).使用其getInstance()靜態方法創建實例,參數是算法名,指定生成的本地密鑰符合某種特定算法.
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
KeyAgreement類的init()方法用於給定密鑰初始化此密鑰協定.doPhase()方法用於用給定密鑰執行此密鑰協定的下一個階段.兩個方法的參數都是Key類型,即使用公鑰、私鑰來初始化密鑰協定.
keyAgreement.init(receiverKeyPair.getPrivate());//接收方的私鑰 keyAgreement.doPhase(senderKeyPair.getPublic(), true);//發送方的公鑰
KeyAgreement類的generateSecret()方法創建本地密鑰,參數是對稱加密算法名稱.返回SecretKey類型.
SecretKey receiverDesKey = keyAgreement.generateSecret("DES");
加密解密,同java加密算法入門(二)-對稱加密詳解 不在詳細講了
Cipher cipher=Cipher.getInstance("DES"): cipher.init(Cipher.ENCRYPT_MODE, senderDesKey); byte[] result = cipher.doFinal(src.getBytes());
2.3 實現代碼
package Asymmetric.encryption; import org.apache.commons.codec.binary.Hex; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PublicKey; import java.util.Objects; /** * {@link http://www.cnblogs.com/allanzhang/} * @author 小賣鋪的老爺爺 * DH 相當於外面的一層封裝。進行密鑰交換用的。並不是一種明確的加解密算法 */ public class DHTest { public static final String src = "laoyeye dh"; public static void main(String[] args) throws Exception { jdkDH(); } public static void jdkDH() throws Exception { //發送方初始化自己的密鑰對,並且把公鑰發送給接收方 KeyPairGenerator senderKeyPairGenerator = KeyPairGenerator.getInstance("DH"); senderKeyPairGenerator.initialize(512); KeyPair senderKeyPair = senderKeyPairGenerator.generateKeyPair(); PublicKey senderPublicKey = senderKeyPair.getPublic();//公鑰是要發送給接收方的,此處同一類內省略該操作 //接收方用發送方的公鑰初始化自己的密鑰對 DHParameterSpec dhParameterSpec = ((DHPublicKey) senderPublicKey).getParams();//用發送方的公鑰產生算法參數 KeyPairGenerator receiverKeyPairGenerator = KeyPairGenerator.getInstance("DH"); receiverKeyPairGenerator.initialize(dhParameterSpec);//初始化接收方的KeyPairGenerator KeyPair receiverKeyPair = receiverKeyPairGenerator.generateKeyPair();//生產接收方的密鑰對 //DES密鑰構建 KeyAgreement keyAgreement = KeyAgreement.getInstance("DH"); //接收方根據自己的私鑰和發送方的公鑰創建DES密鑰 keyAgreement.init(receiverKeyPair.getPrivate());//接收方的私鑰 keyAgreement.doPhase(senderKeyPair.getPublic(), true);//發送方的公鑰 SecretKey receiverDesKey = keyAgreement.generateSecret("DES"); //發送方根據自己的私鑰和接收方的公鑰創建DES密鑰 keyAgreement.init(senderKeyPair.getPrivate()); keyAgreement.doPhase(receiverKeyPair.getPublic(), true); SecretKey senderDesKey = keyAgreement.generateSecret("DES"); //判斷兩方得到的DES密鑰是不是相等的 if (Objects.equals(senderDesKey, receiverDesKey)) { System.out.println("雙方密鑰相同"); } //加密 Cipher cipher = Cipher.getInstance("DES"); cipher.init(Cipher.ENCRYPT_MODE, senderDesKey); byte[] result = cipher.doFinal(src.getBytes()); System.out.println("JDK DH ENCRYPT:" + Hex.encodeHexString(result)); //解密 cipher.init(Cipher.DECRYPT_MODE, receiverDesKey); result = cipher.doFinal(result); System.out.println("JDK DH DECRYPT:" + new String(result)); } }View Code
3、RSA算法
這種算法1978年就出現了,它是第一個既能用於數據加密也能用於數字簽名的算法。它易於理解和操作,也很流行。算法的名字以發明者的名字命名:Ron Rivest, AdiShamir 和Leonard Adleman。
這種加密算法的特點主要是密鑰的變化,java加密算法入門(二)-對稱加密詳解 一文中我們看到DES只有一個密鑰。相當於只有一把鑰匙,如果這把鑰匙丟了,數據也就不安全了。RSA同時有兩把鑰匙,公鑰與私鑰。同時支持數字簽名。數字簽名的意義在於,對傳輸過來的數據進行校驗。確保數據在傳輸工程中不被修改。
RSA算法的JDK實現默認密鑰長度是1024,BC則是2048,密鑰長度必須是64的倍數,在512到65536位之間。
RSA目前還沒有被破解,是使用最多的非對稱加密算法.
有些算法只規定了公鑰加密、私鑰解密,RSA算法則支持公鑰加密、私鑰解密.私鑰加密、公鑰解密。
3.1 算法流程
1.甲方構建密鑰對兒,將公鑰公布給乙方,將私鑰保留。
2.甲方使用私鑰加密數據,然後用私鑰對加密後的數據簽名,發送給乙方簽名以及加密後的數據;乙方使用公鑰、簽名來驗證待解密數據是否有效,如果有效使用公鑰對數據解密。
3.乙方使用公鑰加密數據,向甲方發送經過加密後的數據;甲方獲得加密數據,通過私鑰解密。
Ps:關於數字簽名會在後續的章節中介紹。可以看看這個帖子了解下http://justjavac.iteye.com/blog/1144151
一般公司這樣做的。
甲乙公司各有自己的一套公鑰私鑰。
甲用乙公布的公鑰加密,信息傳遞到乙,乙用自己的私鑰解密。 --- 這一套是乙方的公私鑰。
乙用甲公布的公鑰加密,信息傳遞到甲,甲用自己的私鑰解密。 --- 這一套是甲方的公私鑰。
註:公鑰長度遠遠小於私鑰,公鑰長度比較短,便於公鑰保存。
3.2 代碼實現
package Asymmetric.encryption; import org.apache.commons.codec.binary.Hex; import javax.crypto.Cipher; import java.security.KeyPair; import java.security.KeyPairGenerator; /** * {@link http://www.cnblogs.com/allanzhang/} * @author 小賣鋪的老爺爺 * */ public class RSATest { public static final String src = "laoyeye rsa"; public static void main(String[] args) throws Exception { jdkRSA(); } public static void jdkRSA() throws Exception { //初始化密鑰 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(512);//512~65532 KeyPair keyPair = keyPairGenerator.generateKeyPair(); //私鑰加密,公鑰解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); byte[] result = cipher.doFinal(src.getBytes()); System.out.println("**私鑰加密,公鑰解密**"); System.out.println("加密:" + Hex.encodeHexString(result)); cipher.init(Cipher.DECRYPT_MODE, keyPair.getPublic()); result = cipher.doFinal(result); System.out.println("解密:" + new String(result)); } }
4、ElGamal算法
ElGamal算法,是一種較為常見的加密算法,它是基於1985年提出的公鑰密碼體制和橢圓曲線加密體系。既能用於數據加密也能用於數字簽名,其安全性依賴於計算有限域上離散對數這一難題。在加密過程中,生成的密文長度是明文的兩倍,且每次加密後都會在密文中生成一個隨機數K,在密碼中主要應用離散對數問題的幾個性質:求解離散對數(可能)是困難的,而其逆運算指數運算可以應用平方-乘的方法有效地計算。也就是說,在適當的群G中,指數函數是單向函數。
特點:
1、在JDK裏並沒有提供對ElGamal算法的實現.而是通過BouncyCastle實現.
2、只提供公鑰加密,私鑰解密,不提供私鑰加密,公鑰解密
3、ElGamal算法支持數據加密與數字簽名.
4、密鑰長度:160~16384(8的倍數),默認的密鑰長度為1024.
4.1 算法流程
1.乙方構建密鑰對兒,將公鑰公布給甲方,將私鑰保留。
2.甲方使用公鑰加密數據,然後用公鑰對加密後的數據簽名,發送給乙方簽名以及加密後的數據;乙方使用私鑰、簽名來驗證待解密數據是否有效,如果有效使用私鑰對數據解密。
4.2 具體步驟
在使用之前需要為JDK添加新的Provider.
Security.addProvider(new BouncyCastleProvider());
RSA初始化密鑰對是通過KeyPairGenerator實現的.而ElGamal初始化密鑰對則是通過AlgorithmParameterGenerator實現的.
使用AlgorithmParameterGenerator的getInstance()靜態方法獲取AlgorithmParameterGenerator實例,參數是算法名.
AlgorithmParameterGenerator algorithmParameterGenerator = AlgorithmParameterGenerator.getInstance("Elgamal");
使用AlgorithmParameterGenerator的init()方法進行初始化密鑰長度.
algorithmParameterGenerator.init(256);
JDK沒有提供對ElGamal的支持,但是jce提供了構建秘鑰對的方式DHParameterSpec,具體方式見代碼
// 生成算法參數 AlgorithmParameters algorithmParameters = algorithmParameterGenerator.generateParameters(); // 構建參數材料 //JDK沒有提供對ei的支持,但是jce框架提供了構建秘鑰對的方式DHParameterSpec DHParameterSpec dhParameterSpec = (DHParameterSpec)algorithmParameters.getParameterSpec(DHParameterSpec.class); // 實例化密鑰對生成器 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Elgamal"); // 初始化密鑰對生成器 keyPairGenerator.initialize(dhParameterSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair();
到這一步後就和RSA的步驟類似了,可參考上文或者查看實現代碼。
4.3 實現代碼
package Asymmetric.encryption; import java.security.AlgorithmParameterGenerator; import java.security.AlgorithmParameters; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import javax.crypto.spec.DHParameterSpec; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * {@link http://www.cnblogs.com/allanzhang/} * @author 小賣鋪的老爺爺 * */ public class ElGamalTest { public static final String src = "laoyeye Elgamal test"; public static void main(String[] args) { jdkElgamal(); } // jdk實現:“公鑰加密,私鑰解密” , 因為Elgamal不支持“私鑰加密、公鑰解密”。 public static void jdkElgamal() { try { // 加入對BouncyCastle支持 Security.addProvider(new BouncyCastleProvider()); // 初始化密鑰 AlgorithmParameterGenerator algorithmParameterGenerator = AlgorithmParameterGenerator.getInstance("Elgamal"); // 初始化參數生成器 algorithmParameterGenerator.init(256); // 生成算法參數 AlgorithmParameters algorithmParameters = algorithmParameterGenerator.generateParameters(); // 構建參數材料 //JDK沒有提供對ei的支持,但是jce框架提供了構建秘鑰對的方式DHParameterSpec DHParameterSpec dhParameterSpec = (DHParameterSpec)algorithmParameters.getParameterSpec(DHParameterSpec.class); // 實例化密鑰對生成器 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Elgamal"); // 初始化密鑰對生成器 keyPairGenerator.initialize(dhParameterSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 公鑰 PublicKey elGamalPublicKey = keyPair.getPublic(); // 私鑰 PrivateKey elGamalPrivateKey = keyPair.getPrivate(); System.out.println("Public Key:" + Base64.encodeBase64String(elGamalPublicKey.getEncoded())); System.out.println("Private Key:" + Base64.encodeBase64String(elGamalPrivateKey.getEncoded())); // 初始化公鑰 // 密鑰材料轉換 X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(elGamalPublicKey.getEncoded()); // 實例化密鑰工廠 KeyFactory keyFactory = KeyFactory.getInstance("Elgamal"); // 產生公鑰 PublicKey publicKey2 = keyFactory.generatePublic(x509EncodedKeySpec2); // 數據加密 // Cipher cipher = Cipher.getInstance("Elgamal"); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, publicKey2); byte[] result2 = cipher.doFinal(src.getBytes()); System.out.println("Elgamal ---- 加密:" + Base64.encodeBase64String(result2)); // 數據解密 PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(elGamalPrivateKey.getEncoded()); keyFactory = KeyFactory.getInstance("Elgamal"); PrivateKey privateKey5 = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); // Cipher cipher5 = Cipher.getInstance("Elgamal"); Cipher cipher5 = Cipher.getInstance(keyFactory.getAlgorithm()); cipher5.init(Cipher.DECRYPT_MODE, privateKey5); byte[] result5 = cipher5.doFinal(result2); System.out.println("Elgamal ---- 解密:" + new String(result5)); } catch (Exception e) { e.printStackTrace(); } } }View Code
4.4 遇到的問題
代碼報錯:“Illegal key size or default parameters”異常,是因為美國的出口限制,Sun通過權限文件(local_policy.jar、US_export_policy.jar)做了相應限制。因此存在一些問題。
Java 6 無政策限制文件:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
Java 7 無政策限制文件:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
找到對應jdk的安裝目錄。D:\JDK1.6\jdk1.7.0_45\jre\lib\security目錄下,對應覆蓋local_policy.jar和US_export_policy.jar兩個jar包就可以了。
補充:
關於源碼中PKCS8EncodedKeySpec,X509EncodedKeySpec密鑰的轉換,實際上並不需要轉換,也不需要重新生成keyfactory。因為具體我也沒應用過,可能是實際項目中一般會這樣,因為實際接受方和發送方並不知道對方的密鑰的encoded format(編碼格式),所以需要轉換成自己使用encoded format。而KeyFactory就是轉換格式後重新生成私鑰,公鑰。
總結:
1、對稱加密算法中使用的是密鑰(SecretKey),非對稱加密算法中使用的是密鑰對(公鑰、私鑰)。
2、DH算法比較復雜,需要根據公鑰生成自己的秘鑰對,然後使用公鑰和自己的私鑰生成自己的本地秘鑰在進行加密
3、RSA相對來講簡單的多,支持公鑰加密、私鑰解密,私鑰加密、公鑰解密。
4、ElGamal算法和RSA類似,但是只支持公鑰加密,私鑰解密,不支持私鑰加密,公鑰解密。
源碼地址:https://github.com/allanzhuo/study
java加密算法入門(三)-非對稱加密詳解