Https通信的數字證書中一般採用RSA(公鑰密碼體制)。認知RSA之前補充點其他知識。
公鑰密碼體制(public-key cryptography)
公鑰密碼體制分爲三個部分,公鑰、私鑰、加密解密算法,它的加密解密過程如下:
- 加密:通過加密算法和公鑰對內容(或者說明文)進行加密,得到密文。加密過程需要用到公鑰。
- 解密:通過解密算法和私鑰對密文進行解密,得到明文。解密過程需要用到解密算法和私鑰。
公鑰密碼體制的公鑰和算法都是公開的(這是爲什麼叫公鑰密碼體制的原因),私鑰是保密的。公鑰加密的內容只能私鑰解密,私鑰加密的內容只能公鑰解密
對稱加密算法(symmetric key algorithms)
對稱加密算法就是加密使用的密鑰和解密使用的密鑰是相同的。因此對稱加密算法要保證安全性的話,密鑰要做好保密,只能讓使用的人知道。安全係數相對較低
非對稱加密算法(asymmetric key algorithms)
非對稱加密算法就是加密使用的密鑰和解密使用的密鑰是不相同的。RSA就是一種非對稱加密算法。
祕鑰
密鑰,一般就是一個字符串或數字,在加密或者解密時傳遞給加密/解密算法。加解密雙方可以自行約定加解密方法,所以就出現了多種加解密方法。
數據加解密
加密就是是指對某個內容經過一定的加密算法及祕鑰轉化成base64、hex等格式。解密就是通過特定的解密算法及祕鑰將加密信息轉換成原始信息。數據的加解密是爲了數據傳輸的安全,防止被他人截取。加解密算法一般就是對稱加密和非對稱加密。
數據簽名
簽名就是在信息的後面再加上一段內容,可以證明信息沒有被修改過。簽名一般使用的方案:
- 是對信息做一個hash計算得到一個hash值,然後把這個hash值(加密後)作爲一個簽名和信息一起發出去。
- 接收方在收到信息後,會重新計算信息的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算法原理