1. 程式人生 > >java加密算法入門(三)-非對稱加密詳解

java加密算法入門(三)-非對稱加密詳解

共享數據 net clas 實例 查看 安全性 自己的 generator mir

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()方法用來生成公鑰

generatePrivate()方法用來生成私鑰

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加密算法入門(三)-非對稱加密詳解