DH、RSA與ElGamal非對稱加密演算法實現及應用
1.對稱加密與非對稱加密概述
關於對稱加密與非對稱加密的概念這裡不再多說,感興趣可以看下我之前的幾篇文章,下面說一說兩者的主要區別。
對稱加密演算法資料安全,金鑰管理複雜,金鑰傳遞過程複雜,存在金鑰洩露問題。
非對稱加密演算法強度複雜、安全性依賴於演算法與金鑰。但是由於演算法複雜,使得非對稱演算法加解密速度沒有對稱演算法加解密的速度快。
對稱金鑰體制中只有一種金鑰,並且是非公開的。如果要解密就得讓對方知道金鑰。所以保證其安全性就是保證金鑰的安全。
非對稱金鑰體制有兩種金鑰,其中一個是公開的,這樣就可以不需要像對稱密碼那樣向對方傳輸金鑰了。因此安全性就大了很多。
對稱加密 | 非對稱加密 | |
---|---|---|
演算法複雜度 | 弱 | 強 |
加解密速度 | 快 | 慢 |
安全性 | 低 | 高 |
常見演算法 | DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、AES | RSA、DSA、ECC、Diffie-Hellman、El Gamal |
2.DH演算法實現過程及相關類詳解
Diffie-Hellman演算法(D-H演算法),金鑰一致協議。是由公開金鑰密碼體制的奠基人Diffie和Hellman所提出的一種思想。簡單的說就是允許兩名使用者在公開媒體上交換資訊以生成"一致"的、可以共享的金鑰。換句話說,就是由甲方產出一對金鑰(公鑰、私鑰),乙方依照甲方公鑰產生乙方金鑰對(公鑰、私鑰)。以此為基線,作為資料傳輸保密基礎,同時雙方使用同一種對稱加密演算法構建本地金鑰(SecretKey)對資料加密。這樣,在互通了本地金鑰(SecretKey)演算法後,甲乙雙方公開自己的公鑰,使用對方的公鑰和剛才產生的私鑰加密資料,同時可以使用對方的公鑰和自己的私鑰對資料解密。不單單是甲乙雙方兩方,可以擴充套件為多方共享資料通訊,這樣就完成了網路互動資料的安全通訊!該演算法源於中國的同餘定理——中國餘數定理。
流程分析:
1.甲方構建金鑰對兒,將公鑰公佈給乙方,將私鑰保留;雙方約定資料加密演算法;乙方通過甲方公鑰構建金鑰對兒,將公鑰公佈給甲方,將私鑰保留。
2.甲方使用私鑰、乙方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰加密資料,傳送給乙方加密後的資料;乙方使用私鑰、甲方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰對資料解密。
3.乙方使用私鑰、甲方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰加密資料,傳送給甲方加密後的資料;甲方使用私鑰、乙方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰對資料解密。
Java程式碼:
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.*; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Objects; public class DH { private static final String src = "dh test"; public static void main(String[] args) { jdkDH(); } // jdk實現: public static void jdkDH() { try { // 1.傳送方初始化金鑰,將公鑰給接收方 KeyPairGenerator senderKeyPairGenerator = KeyPairGenerator.getInstance("DH"); senderKeyPairGenerator.initialize(512); KeyPair senderKeyPair = senderKeyPairGenerator.generateKeyPair(); // 傳送方公鑰,傳送給接收方(通過網路或檔案的形式) PublicKey senderPublicKey = senderKeyPair.getPublic(); // 公鑰 PrivateKey senderPrivateKey = senderKeyPair.getPrivate(); // 私鑰 // 接收方還原發送方公鑰 KeyFactory receiverKeyFactory = KeyFactory.getInstance("DH"); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(senderPublicKey.getEncoded()); senderPublicKey = receiverKeyFactory.generatePublic(x509EncodedKeySpec); // 2.接收方通過傳送方的公鑰構建金鑰,將公鑰給傳送方 DHParameterSpec dhParameterSpec = ((DHPublicKey) senderPublicKey).getParams(); KeyPairGenerator receiverKeyPairGenerator = KeyPairGenerator.getInstance("DH"); receiverKeyPairGenerator.initialize(dhParameterSpec); KeyPair receiverKeypair = receiverKeyPairGenerator.generateKeyPair(); PrivateKey receiverPrivateKey = receiverKeypair.getPrivate(); // 私鑰 PublicKey receiverPublicKey = receiverKeypair.getPublic(); // 公鑰 // 3.接收方使用自己的私鑰和傳送方的公鑰構建本地金鑰 KeyAgreement receiverKeyAgreement = KeyAgreement.getInstance("DH"); receiverKeyAgreement.init(receiverPrivateKey); receiverKeyAgreement.doPhase(senderPublicKey, true); SecretKey receiverDesKey = receiverKeyAgreement.generateSecret("DES"); // 傳送方還原接收方公鑰 KeyFactory senderKeyFactory = KeyFactory.getInstance("DH"); x509EncodedKeySpec = new X509EncodedKeySpec(receiverPublicKey.getEncoded()); receiverPublicKey = senderKeyFactory.generatePublic(x509EncodedKeySpec); // 4.傳送方使用自己的私鑰和接收方的公鑰構建本地金鑰 KeyAgreement senderKeyAgreement = KeyAgreement.getInstance("DH"); senderKeyAgreement.init(senderPrivateKey); senderKeyAgreement.doPhase(receiverPublicKey, true); SecretKey senderDesKey = senderKeyAgreement.generateSecret("DES"); if (Objects.equals(receiverDesKey, senderDesKey)) { System.out.println("雙方金鑰相同"); } // 5.傳送方使用本地金鑰加密 Cipher cipher = Cipher.getInstance("DES"); cipher.init(Cipher.ENCRYPT_MODE, senderDesKey); byte[] result = cipher.doFinal(src.getBytes()); System.out.println("bc dh encrypt:" + Base64.getEncoder().encodeToString(result)); // 6.接收方使用本地金鑰解密 cipher.init(Cipher.DECRYPT_MODE, receiverDesKey); result = cipher.doFinal(result); System.out.println("bc dh decrypt:" + new String(result)); } catch (Exception e) { e.printStackTrace(); } } }
注意:因為JDK的版本問題,如果遇到異常java.security.NoSuchAlgorithmException: Unsupported secret key algorithm: DES
,可以在執行的時候追加JVM引數-Djdk.crypto.KeyAgreement.legacyKDF=true
3.RSA演算法實現及應用
RSA是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前為止已知的絕大多數密碼攻擊,已被ISO推薦為公鑰資料加密標準。
RSA演算法支援公鑰加密、私鑰解密以及私鑰加密、公鑰解密。既可以用於加密也可用於數字簽名。
關於更多RSA演算法的實現原理等,本文不再涉及。
Java程式碼:
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RSA {
public static final String src = "rsa test";
public static void main(String[] args) {
jdkRSA();
}
// jdk實現:
public static void jdkRSA() {
try {
// 1.生成公鑰和私鑰
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(512);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
System.out.println("Public Key:" + Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded()));
System.out.println("Private Key:" + Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded()));
// 2.私鑰加密、公鑰解密 ---- 加密
//PKCS8EncodedKeySpec類表示私鑰的ASN.1編碼。
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(src.getBytes());
System.out.println("私鑰加密、公鑰解密 ---- 加密:" + Base64.getEncoder().encodeToString(result));
// 3.私鑰加密、公鑰解密 ---- 解密
//X509EncodedKeySpec類表示根據ASN.1型別SubjectPublicKeyInfo編碼的公鑰的ASN.1編碼。
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
result = cipher.doFinal(result);
System.out.println("私鑰加密、公鑰解密 ---- 解密:" + new String(result));
// 4.公鑰加密、私鑰解密 ---- 加密
//X509EncodedKeySpec類表示根據ASN.1型別SubjectPublicKeyInfo編碼的公鑰的ASN.1編碼。
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
KeyFactory keyFactory2 = KeyFactory.getInstance("RSA");
PublicKey publicKey2 = keyFactory2.generatePublic(x509EncodedKeySpec2);
Cipher cipher2 = Cipher.getInstance("RSA");
cipher2.init(Cipher.ENCRYPT_MODE, publicKey2);
byte[] result2 = cipher2.doFinal(src.getBytes());
System.out.println("公鑰加密、私鑰解密 ---- 加密:" + Base64.getEncoder().encodeToString(result2));
// 5.私鑰解密、公鑰加密 ---- 解密
//PKCS8EncodedKeySpec類表示私鑰的ASN.1編碼。
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
KeyFactory keyFactory5 = KeyFactory.getInstance("RSA");
PrivateKey privateKey5 = keyFactory5.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher5 = Cipher.getInstance("RSA");
cipher5.init(Cipher.DECRYPT_MODE, privateKey5);
byte[] result5 = cipher5.doFinal(result2);
System.out.println("公鑰加密、私鑰解密 ---- 解密:" + new String(result5));
} catch (Exception e) {
e.printStackTrace();
}
}
}
圖解流程:
4.非對稱加密演算法ElGamal
在密碼學中,ElGamal加密演算法是一個基於迪菲-赫爾曼金鑰交換的非對稱加密演算法。它在1985年由塔希爾·蓋莫爾提出。GnuPG和PGP等很多密碼學系統中都應用到了ElGamal演算法。
ElGamal演算法只提供了公鑰加密,私鑰解密形式,Jdk中沒有實現,Bouncy Castle中對其進行了實現。
匯入Bouncy Castle依賴:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>1.46</version>
</dependency>
Java程式碼:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.DHParameterSpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class ElGamal {
//非對稱金鑰演算法
public static final String EL_GAMAL = "ElGamal";
/**
* 金鑰長度,DH演算法的預設金鑰長度是1024
* 金鑰長度必須是8的倍數,在160到16384位之間
*/
private static final int KEY_SIZE = 256;
public static void main(String[] args) throws Exception {
System.out.println("=============接收方構建金鑰對=============");
// 加入對BouncyCastle支援
Security.addProvider(new BouncyCastleProvider());
AlgorithmParameterGenerator apg = AlgorithmParameterGenerator.getInstance(EL_GAMAL);
//初始化引數生成器
apg.init(KEY_SIZE);
//生成演算法引數
AlgorithmParameters params = apg.generateParameters();
//構建引數材料
DHParameterSpec elParams = params.getParameterSpec(DHParameterSpec.class);
//例項化金鑰生成器
KeyPairGenerator kpg = KeyPairGenerator.getInstance(EL_GAMAL);
//初始化金鑰對生成器
kpg.initialize(elParams, new SecureRandom());
KeyPair keyPair = kpg.generateKeyPair();
//甲方公鑰
PublicKey publicKey = keyPair.getPublic();
//甲方私鑰
PrivateKey privateKey = keyPair.getPrivate();
System.out.println("公鑰:" + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
System.out.println("私鑰:" + Base64.getEncoder().encodeToString(privateKey.getEncoded()));
System.out.println("=============金鑰對構造完畢,接收方將公鑰公佈給傳送方=============");
String str = "ElGamal密碼交換演算法";
System.out.println("原文:" + str);
System.out.println("=============傳送方還原接收方公鑰,並使用公鑰對資料進行加密=============");
//還原公鑰
KeyFactory keyFactory = KeyFactory.getInstance(EL_GAMAL);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
publicKey = keyFactory.generatePublic(x509KeySpec);
System.out.println("公鑰:" + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
//資料加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(str.getBytes());
System.out.println("加密後的資料:" + Base64.getEncoder().encodeToString(bytes));
System.out.println("=============接收方使用私鑰對資料進行解密===========");
//還原私鑰
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
keyFactory = KeyFactory.getInstance(EL_GAMAL);
privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//資料解密
cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes1 = cipher.doFinal(bytes);
System.out.println("解密後的資料:" + new String(bytes1));
}
}
圖解流程: