1. 程式人生 > >DH、RSA與ElGamal非對稱加密演算法實現及應用

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));
    }
}

圖解流程: