Https通信之RSA加密簽名

Https通信的數字證書中一般採用RSA(公鑰密碼體制)。認知RSA之前補充點其他知識。

公鑰密碼體制(public-key cryptography)

公鑰密碼體制分爲三個部分,公鑰、私鑰、加密解密算法,它的加密解密過程如下:

  1. 加密:通過加密算法和公鑰對內容(或者說明文)進行加密,得到密文。加密過程需要用到公鑰。
  2. 解密:通過解密算法和私鑰對密文進行解密,得到明文。解密過程需要用到解密算法和私鑰。
    公鑰密碼體制的公鑰和算法都是公開的(這是爲什麼叫公鑰密碼體制的原因),私鑰是保密的。公鑰加密的內容只能私鑰解密,私鑰加密的內容只能公鑰解密

對稱加密算法(symmetric key algorithms)

對稱加密算法就是加密使用的密鑰和解密使用的密鑰是相同的。因此對稱加密算法要保證安全性的話,密鑰要做好保密,只能讓使用的人知道。安全係數相對較低

非對稱加密算法(asymmetric key algorithms)

非對稱加密算法就是加密使用的密鑰和解密使用的密鑰是不相同的。RSA就是一種非對稱加密算法。

祕鑰

密鑰,一般就是一個字符串或數字,在加密或者解密時傳遞給加密/解密算法。加解密雙方可以自行約定加解密方法,所以就出現了多種加解密方法。

數據加解密

加密就是是指對某個內容經過一定的加密算法及祕鑰轉化成base64、hex等格式。解密就是通過特定的解密算法及祕鑰將加密信息轉換成原始信息。數據的加解密是爲了數據傳輸的安全,防止被他人截取。加解密算法一般就是對稱加密和非對稱加密。

數據簽名

簽名就是在信息的後面再加上一段內容,可以證明信息沒有被修改過。簽名一般使用的方案:

  1. 是對信息做一個hash計算得到一個hash值,然後把這個hash值(加密後)作爲一個簽名和信息一起發出去。
  2. 接收方在收到信息後,會重新計算信息的hash值,並和信息所附帶的hash值(解密後)進行對比,如果一致,就說明信息的內容沒有被修改過。
    1. 這個過程是不可逆的,也就是說無法通過hash值得出原來的信息內容。
    2.不同的內容一定會得到不同的hash值,hash的加解密是爲了防止傳輸過程中被更改,造成信息是否被篡改無法準確驗證。

RSA

RSA採用的就是一種公鑰密碼體制。RSA是三位數學家Rivest、Shamir 和 Adleman 設計的一種算法,所以叫做RSA。它是計算機通信安全的基石,也是最重要的加密算法。

這種算法非常可靠,密鑰越長,它就越難破解。根據已經披露的文獻,目前被破解的最長RSA密鑰是768個二進制位。也就是說長度超過768位的密鑰,還無法破解(至少沒人公開宣佈)。因此可以認爲,1024位的RSA密鑰基本安全,2048位的密鑰極其安全(網絡通信中一般都是2048位的)。
對於RSA的算法原理推薦查看RSA算法原理

在實際應用中我們以Https通信爲例來分析RSA的使用過程,現在模擬https通信中client和server的常見對話:

  • client >>server:你好,我是clientA
  • server>>client:你好,我是server。
  • client >>server:請證明你是Server,str [str是隨機字符串]
  • server>>client:str{XXX-hash} [{}中是私鑰RSA加密後內容,hash爲str的has,後面都如此表示]
    [client 收到str原文,計算str的hash1,通過證書中RSA公鑰解密XXX-hash,獲得密文中的內容與hash2,比較解密內容與原內容是否一致,解密的hash和原文計算的hash是否一致,都一致說明服務器是真的,可以進行下一步]
  • client >>server:你確實是server,這是x加密算法的祕鑰{XXXX},以後就用X加密算法通信吧
  • server>>client:{可以,我做好通信準備了}[內容經x算法加密]
  • client >>server:{查詢一下我的賬戶餘額}[內容經x算法加密]

**注意**
1.驗證雙方身份的真實性後,雙方可以約定使用新的對稱加密算法通信,這個比較自由,擴張性很強,可能不同的client與server的通信加密方法是不一樣的。
2.在驗證雙方身份後,也可以client將自己生成的非對稱公鑰傳遞給server,通過非對稱加密進行雙方的通信。

通信前提:client已經拿到了server的數字證書。證書中包含有服務器RSA公鑰。關於數字證書原理和其他信息,請閱讀該篇博客數字證書鏈接地址待補充
一般我們開發應用,第一次是在程序中放了數字證書的,在快過期時,可以通過server下載新的證書進行保存並投入使用。

疑問:

1. 爲什麼要使用RSA加解密通信內容?
答:爲了數據安全,client與server直接的通信在多個層面都能被黑客攻擊,通過僞裝成server或者client進行數據的竊取和數據篡改。

2. 爲什麼不在第一次通話中獲取server的證書?或者放在某個網站下載?
答:你一開始訪問的網站未必是真的網站,下載或傳輸的證書也可以是假的證書。

3. 爲什麼要對通信內容同時使用加密和簽名?
答:雖然“黑客”無法獲知加密內容,但是可以修改加密內容,如給它首位加一段內容、信息部分內容被替換,進行信息干擾。信息通過加密和內容hash值得簽名,即可判斷通信內容是否被修改破壞,是否完整。

4. client驗證server簽名爲什麼是通過隨機字符串的hash,而不是直接通過加解密的方式?
答:因爲”黑客”可以通過發送簡單有規律的字符串,如”0,1,2,3,4”等找到加密規律,破解加密方法,這樣是不安全的。通過對字符串的hash值進行加密,client收到內容後,解密字符串的hash值並與傳過來的字符串計算出來的hash進行比較,即可驗證server的簽名。

5. 如何解決”黑客”截取加密內容,多次重複發送信息?
答:給通信內容添加序號和隨機號,client或者server收到同樣的信息,則說明通信有被幹擾,應停止通信進行其他處理。

總結:
1.信息在通信中加密可看爲是防止數據泄漏被解密竊取
2.數據簽名是爲了判斷收到的數據是否完整
保證client和server能收到完整、無關方無法解密的加密數據,纔是網絡通信數據安全的核心點。

附貼一個RSA工具類

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;
/**
 * 時間:2020/4/20 0020    星期一
 * 郵件:[email protected]
 * 說明:RSA非對稱加密
 */
class RSAUtils {
    private final String CHARSET = "UTF-8";
    private final String RSA_ALGORITHM = "RSA";

    private Map<String, String> createKeys(int keySize) {
        //爲RSA算法創建一個KeyPairGenerator對象
        KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
        }

        //初始化KeyPairGenerator對象,密鑰長度
        kpg.initialize(keySize);
        //生成密匙對
        KeyPair keyPair = kpg.generateKeyPair();
        //得到公鑰
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());

        //得到私鑰
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);
        return keyPairMap;
    }

    /**
     * 得到公鑰
     *
     * @param publicKey 密鑰字符串(經過base64編碼)
     */
    private RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通過X509編碼的Key指令獲得公鑰對象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
        return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
    }

    /**
     * 得到私鑰
     * @param privateKey 密鑰字符串(經過base64編碼)
     */
    private RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通過PKCS#8編碼的Key指令獲得私鑰對象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
        return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
    }

    /**
     * 公鑰加密
     */
    private String publicEncrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength());
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]時遇到異常", e);
        }
    }

    /**
     * 私鑰解密
     *
     * @param data    待解密數據
     * @param privateKey    私鑰
     */
    private String privateDecrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] bytes = Base64.getDecoder().decode(data);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bytes, privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]時遇到異常", e);
        }
    }

    /**
     * 私鑰加密
     *
     */
    public String privateEncrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength());
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]時遇到異常", e);
        }
    }

    /**
     * 公鑰解密
     */
    public String publicDecrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] bytes = Base64.getDecoder().decode(data);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bytes, publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]時遇到異常", e);
        }
    }

    private byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
        int maxBlock;
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try {
            while (datas.length > offSet) {
                if (datas.length - offSet > maxBlock) {
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        } catch (Exception e) {
            throw new RuntimeException("加解密閥值爲[" + maxBlock + "]的數據時發生異常", e);
        }
        byte[] resultDatas = out.toByteArray();
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultDatas;
    }

    public static void main(String[] args) throws Exception {
        RSAUtils rsaUtils = new RSAUtils();
        Map<String, String> keyMap = rsaUtils.createKeys(1024);
        String publicKey = keyMap.get("publicKey");
        String privateKey = keyMap.get("privateKey");
        System.out.println("公鑰: \n\r" + publicKey);
        System.out.println("私鑰: \n\r" + privateKey);

        System.out.println("公鑰加密——私鑰解密");
        String str = "孫子,我是你爸爸";
        System.out.println("\r明文:\r\n" + str);
        System.out.println("\r明文大小:\r\n" + str.getBytes().length);
        String encodedData = rsaUtils.publicEncrypt(str, rsaUtils.getPublicKey(publicKey));
        System.out.println("密文:\r\n" + encodedData);
        String decodedData = rsaUtils.privateDecrypt(encodedData, rsaUtils.getPrivateKey(privateKey));
        System.out.println("解密後文字: \r\n" + decodedData);
    }
}

該篇博客純屬個人觀點和見解,如有錯誤懇請留言指正,萬分感激!

相關鏈接:
RSA算法原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章