一、加密算法種類
1、密鑰
密鑰,一般就是一個字符串或數字,在加密或者解密時傳遞給加密或解密算法,以使算法能夠正確對明文加密或者對密文解密。
2、加密算法分類
大體上分為單向加密和雙向加密。
2.1、單向加密
單向加密就是非可逆加密,就是不可解密的加密方法,由於其在加密后會生成唯一的加密串,故而經常用於檢測數據傳輸過程中是否被修改。常見的單向加密有MD5、SHA、HMAC。我們只是把他們作為加密的基礎,單純的以上三種加密並不可靠。
2.2、雙向加密
雙向加密又可分為對稱加密和非對稱加密。你想進行加解密操作的時候需要具備兩樣東西:秘鑰和加解密算法。
2.3、對稱加密
對稱加密算法的特點是加密使用的密鑰和解密使用的密鑰是相同的。也就是說,加密和解密都是使用的同一個密鑰。因此對稱加密算法要保證安全性的話,密鑰自然要做好保密,只能讓使用的人知道,不能對外公開。
2.4、非對稱加密
在非對稱加密算法中,有公鑰和私鑰兩種密鑰,其中,公鑰是公開的,不需要保密,私鑰由個人持有,必須妥善保管和注意保密。加密和解密使用兩種不同的密鑰,是它得名的原因。估計大家都聽說過RSA,這就是一種常見的,應用很廣的非對稱加密算法。
二、RSA
1、那RSA 是什么呢?
RSA加密算法是一種非對稱加密算法,所謂非對稱,就是指該算法需要一對密鑰,使用其中一個加密,則需要用另一個才能解密。這樣就可以在不直接傳遞密鑰的情況下,完成解密。這能夠確保信息的安全性,避免了直接傳遞密鑰所造成的被破解的風險。是由一對密鑰來進行加解密的過程,分別稱為公鑰和私鑰。兩者之間有數學相關,該加密算法的原理就是對一極大整數做因數分解的困難性來保證安全性。通常個人保存私鑰,公鑰是公開的(可能同時多人持有)。
2、原理
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
3、RSA加密、簽名區別
加密和簽名都是為了安全性考慮,但略有不同。常有人問加密和簽名是用私鑰還是公鑰?其實都是對加密和簽名的作用有所混淆。簡單的說,加密是為了防止信息被泄露,而簽名是為了防止信息被篡改。這里舉2個例子說明。
第一個場景:戰場上,B要給A傳遞一條消息,內容為某一指令。
RSA的加密過程如下:
(1)A生成一對密鑰(公鑰和私鑰),私鑰不公開,A自己保留。公鑰為公開的,任何人可以獲取。
(2)A傳遞自己的公鑰給B,B用A的公鑰對消息進行加密。
(3)A接收到B加密的消息,利用A自己的私鑰對消息進行解密。
在這個過程中,只有2次傳遞過程,第一次是A傳遞公鑰給B,第二次是B傳遞加密消息給A,即使都被敵方截獲,也沒有危險性,因為只有A的私鑰才能對消息進行解密,防止了消息內容的泄露。
第二個場景:A收到B發的消息后,需要進行回復“收到”。
RSA簽名的過程如下:
(1)A生成一對密鑰(公鑰和私鑰),私鑰不公開,A自己保留。公鑰為公開的,任何人可以獲取。
(2)A用自己的私鑰對消息加簽,形成簽名,並將加簽的消息和消息本身一起傳遞給B。
(3)B收到消息后,在獲取A的公鑰進行驗簽,如果驗簽出來的內容與消息本身一致,證明消息是A回復的。
在這個過程中,只有2次傳遞過程,第一次是A傳遞加簽的消息和消息本身給B,第二次是B獲取A的公鑰,即使都被敵方截獲,也沒有危險性,因為只有A的私鑰才能對消息進行簽名,即使知道了消息內容,也無法偽造帶簽名的回復給B,防止了消息內容的篡改。
但是,綜合兩個場景你會發現,第一個場景雖然被截獲的消息沒有泄露,但是可以利用截獲的公鑰,將假指令進行加密,然后傳遞給A。第二個場景雖然截獲的消息不能被篡改,但是消息的內容可以利用公鑰驗簽來獲得,並不能防止泄露。所以在實際應用中,要根據情況使用,也可以同時使用加密和簽名,比如A和B都有一套自己的公鑰和私鑰,當A要給B發送消息時,先用B的公鑰對消息加密,再對加密的消息使用A的私鑰加簽名,達到既不泄露也不被篡改,更能保證消息的安全性。
總結:公鑰加密、私鑰解密、私鑰簽名、公鑰驗簽。
三、測試代碼
RSAUtil 工具類
public class RsaUtil {
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
public static final String privateKeyA = "privateKeyA";
public static final String publicKeyA = "publicKeyA";
public static final String publicKeyB = "publicKeyB";
public static final String privateKeyB = "privateKeyB";
/**
* 獲取私鑰
* @param privateKey 私鑰字符串
* @return PrivateKey
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}
/**
* 獲取公鑰
* @param publicKey 公鑰字符串
* @return PublicKey
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
/**
* 隨機生成密鑰對
* @throws NoSuchAlgorithmException
*/
public static Map<Integer,String> genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator類用於生成公鑰和私鑰對,基於RSA算法生成對象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密鑰對生成器,密鑰大小為96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一個密鑰對,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私鑰
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公鑰
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私鑰字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 將公鑰和私鑰保存到Map
Map<Integer, String> keyMap = new HashMap();
keyMap.put(0,publicKeyString); //0表示公鑰
keyMap.put(1,privateKeyString); //1表示私鑰
return keyMap;
}
/**
* RSA公鑰加密
* @param str 加密字符串
* @param publicKey 公鑰
* @return 密文
* @throws Exception 加密過程中的異常信息
*/
public static String encrypt( String str, String publicKey ) throws Exception{
//base64編碼的公鑰
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = str.getBytes("UTF-8");
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 對數據分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
String outStr = Base64.encodeBase64String(encryptedData);
return outStr;
}
/**
* RSA私鑰解密
* @param str 加密字符串
* @param privateKey 私鑰
* @return 銘文
* @throws Exception 解密過程中的異常信息
*/
public static String decrypt(String str, String privateKey) throws Exception{
//64位解碼加密后的字符串
byte[] data = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64編碼的私鑰
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 對數據分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
String outStr = new String(decryptedData);
return outStr;
}
/**
* 簽名
* @param data 待簽名數據
* @param privateKey 私鑰
* @return 簽名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
byte[] keyBytes = privateKey.getEncoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return new String(Base64.encodeBase64(signature.sign()));
}
/**
* 驗簽
* @param srcData 原始字符串
* @param publicKey 公鑰
* @param sign 簽名
* @return 是否驗簽通過
*/
public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
byte[] keyBytes = publicKey.getEncoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(key);
signature.update(srcData.getBytes());
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
}
測試類client
假設 生成兩對公鑰、私鑰,如上的工具類中,以便下面使用
public class TestClient {
public static void main(String[] args) {
// client 是 A server 是 B
// testSign();
// testPassword();
testSignAndPassword();
}
public static void testPassword(){
TestServer testServer = new TestServer();
try {
PasswordOrder passwordOrder = new PasswordOrder("1",new BigDecimal(100),"描述信息","weixin");
System.out.println("加密前的數據:" + JSONObject.toJSONString(passwordOrder));
String encrypt = RsaUtil.encrypt(JSONObject.toJSONString(passwordOrder), RsaUtil.publicKeyA);
System.out.println("加密后的數據:" + encrypt);
testServer.testPassword(encrypt);
} catch (Exception e){
e.printStackTrace();
}
}
public static void testSign(){
try {
TestServer testServer = new TestServer();
ClientOrder order = new ClientOrder("1",new BigDecimal(100),"描述信息","weixin");
String data = MapUtil.mapToString(MapUtil.beanToMap(order));
String sign = RsaUtil.sign(data, RsaUtil.getPrivateKey(RsaUtil.privateKeyA));
order.setSign(sign);
System.out.println("客戶端請求數據為原數據和sign:" + JSONObject.toJSONString(order));
testServer.testSign(order);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 先簽名后加密,最后把密文傳到服務端
*/
public static void testSignAndPassword(){
try {
TestServer testServer = new TestServer();
ClientOrder order = new ClientOrder("1",new BigDecimal(100),"描述信息","weixin");
String data = MapUtil.mapToString(MapUtil.beanToMap(order));
String sign = RsaUtil.sign(data, RsaUtil.getPrivateKey(RsaUtil.privateKeyA));
order.setSign(sign);
System.out.println("簽名:" + sign);
System.out.println("加密前的數據:" + JSONObject.toJSONString(order));
String encrypt = RsaUtil.encrypt(JSONObject.toJSONString(order), RsaUtil.publicKeyB);
System.out.println("加密后的數據:" + encrypt);
testServer.testSignAndPassword(encrypt);
} catch (Exception e){
e.printStackTrace();
}
}
}
測試類server
public class TestServer {
public void testSign(ClientOrder order){
try {
System.out.println("服務端收到的數據:" + JSONObject.toJSONString(order));
String sign = order.getSign();
order.setSign(null);
String data = MapUtil.mapToString(MapUtil.beanToMap(order));
boolean verify = RsaUtil.verify(data, RsaUtil.getPublicKey(RsaUtil.publicKeyA), sign);
System.out.println("驗簽結果:" + verify);
} catch (Exception e){
e.printStackTrace();
}
}
public void testPassword(String encrypt) {
try {
String decrypt = RsaUtil.decrypt(encrypt, RsaUtil.privateKeyA);
PasswordOrder passwordOrder = JSONObject.parseObject(decrypt, PasswordOrder.class);
System.out.println("解密后的數據:" + JSONObject.toJSONString(passwordOrder));
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 先解密,再驗簽
* @param encrypt
*/
public void testSignAndPassword(String encrypt) {
try {
System.out.println("收到的數據:" + encrypt);
String decrypt = RsaUtil.decrypt(encrypt, RsaUtil.privateKeyB);
System.out.println("解密后的數據:" + decrypt);
ClientOrder clientOrder = JSONObject.parseObject(decrypt, ClientOrder.class);
String sign = clientOrder.getSign();
clientOrder.setSign(null);
String data = MapUtil.mapToString(MapUtil.beanToMap(clientOrder));
boolean verify = RsaUtil.verify(data, RsaUtil.getPublicKey(RsaUtil.publicKeyA), sign);
System.out.println("驗簽結果:" + verify);
} catch (Exception e){
e.printStackTrace();
}
}
}
注意:
1、因為rsa在加解密的時候,有長度限制,所以在加解密的時候應該采用分段式加解密。
2、當需要同時使用加解密的時候,必須是先簽名后加密,假設是 A 請求 B ,A用A的私鑰生成簽名,再用B的公鑰加密,最后把密文傳B,B 收到密文的時候,先用B的私鑰進行解密,再用A的公鑰驗簽。
使用場景:
1、只對數據加密 , 此時只需要server端生成一對公私鑰,把公鑰交給client,這種情況主要用在web與服務器的交互,比如傳密碼之類的。(防泄漏)
2、只對數據簽名,此時只需要client端生成一對公私鑰,把公鑰交給server,這種情況主要用在公司內部與第三方對接,比如,公司有個項目A需要接入微信,支付寶,京東等,即,A 是client,第三方則是server,這樣每次調用第三方都需要把數據簽名后,發給第三方,然后進行驗簽。(防篡改)
3、對數據進行簽名和加密,此時client和server兩方各自生成一對公私鑰,相互交換公鑰,這種情況主要用在企業之間的數據傳輸,比如 甲 乙 兩個企業要通信。(防篡改和防泄漏)
以上是借鑒資料和自己所得,如有原創,請諒解!