一、簡介
二、RSA加密、簽名區別
加密和簽名都是爲了安全性考慮,但略有不同。 加密是爲了防止信息被泄露,而簽名是爲了防止信息被篡改。
eg1:
第一個場景:戰場上,P要給H傳遞一條消息,內容爲某一指令。【P爲第三方支付平臺:H爲商戶平臺】 RSA的加密過程如下: (1)H生成一對密鑰(公鑰和私鑰),H私鑰不公開自己保留。H公鑰爲公開的,任何人可以獲取。 (2)H傳遞自己的公鑰給P,P用H的公鑰對消息進行加密。【支付平臺發的消息,是用的商戶的公鑰進行加密的,這樣商戶才能根據自己手裏的私鑰進行解密】 (3)H接收到P(用H的公鑰)加密的消息,利用H自己的私鑰對消息進行解密。【商戶收到支付平臺回調消息,用自己的私鑰進行解密】 在這個過程中,值有2次傳遞過程,第一次是商戶傳遞公鑰給平臺,第二次是平臺傳遞加密消息給商戶,即使都被敵方截獲,也沒有危險性,因爲只有商戶的私鑰才能對消息進行解密,防止了消息內容的泄露。【但是不能防止篡改!】 |
倒過來看: 商戶要給平臺傳遞一條消息,內容爲某一指令 平臺生成一對祕鑰【公鑰與私鑰】,私鑰平臺自己保留,然後公鑰對外公開。 商戶拿到平臺公開的私鑰,然後對要發送的信息,使用平臺的公鑰進行加密,然後發送給平臺 平臺接受到加密的消息之後,用未公開的商戶私鑰進行解密。 |
【第一個場景雖然被截獲的消息沒有泄露,但是可以利用截獲的公鑰,將假指令進行加密,然後傳遞給A】
第二個場景:
RSA簽名的過程如下: (1)H生成一對密鑰(公鑰和私鑰),H的私鑰不公開,H自己保留。公鑰爲公開的,任何人可以獲取。 (2)H用自己的私鑰對消息加簽,形成 簽名,並將加簽的消息和消息本身一起傳遞給平臺。 (3)P收到消息後,在獲取H的公鑰進行驗籤,如果驗簽出來的內容與消息本身一致,證明消息是H發送的。 在這個過程中,只有2次傳遞過程,第一次是商戶傳遞加簽的消息和消息本身給B,第二次是平臺獲取商戶的公鑰,即使都被敵方截獲,也沒有危險性,因爲只有商戶的私鑰才能對消息進行簽名,即使知道了消息內容,也無法僞造帶簽名的回覆給平臺,防止了消息內容的篡改。 |
【第二個場景可以防止篡改,但是信息都TM漏了!】
上述兩種情況都不完美: 要根據情況使用,也可以同時使用加密和簽名,
比如A和B都有一套自己的公鑰和私鑰,當A要給B發送消息時,先用B的公鑰對消息加密,再對加密的消息使用自己的【A的】私鑰加簽名,達到既不泄露也不被篡改,更能保證消息的安全性。【先後順序是確定的嘛?先加密後簽名】
總結:別人的公鑰加密、自己的私鑰解密、自己的私鑰簽名、別人的公鑰驗籤。
完美情況:
SH:商戶 -------------- PT:支付平臺
SH生成一對公私鑰,分別爲SH公鑰與SH私鑰;PT生成一對公私鑰,分別爲PT公鑰與PT私鑰。
下面我要模擬支付的過程,進行說明整個流程。
(1) SH與PT互相交換公鑰。私鑰各自留存
(2) SH 發起支付請求,SH首先用自己的SH私鑰對自己發送的消息進行簽名,然後將簽名生成的數據
放到要發送的消息內,然後對整個消息用PT公鑰進行加密(或者部分消息進行加密)。
(3)SH發送消息到PT, 然後PT首先使用手中的PT私鑰,對消息進行解密,然後拿到其中的簽名信息,
並用SH公鑰進行驗證簽名。然後進行相應操作後
PT發送請求異步回調消息
(一)PT使用PT私鑰,對要進行發送的消息進行簽名,然後將消息使用SH公鑰進行加密,然後發送給SH。
(二)SH接收到PT的消息後,首先使用SH私鑰進行解密,然後獲取到其中的簽名信息,
然後使用PT公鑰進行驗籤。如果沒有問題處理下面的過程.
(三)SH根據獲取到的消息,進行相應的返回,SH使用SH私鑰進行簽名,然後用PT公鑰進行加密,
發送給PT,PT收到消息之後,首先用PT私鑰進行解密,獲取簽名數據後,使用PT公鑰進行驗籤。
可用工具類:
package com.oumasoft.web.test;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
public class RSATest {
/**
* * RSA最大加密明文大小
*
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 獲取祕鑰對 生成啥祕鑰對?
* @return
* @throws Exception
*/
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
return generator.generateKeyPair();
}
/**
* 根據已知Base64後的私鑰字符串 獲取私鑰
* @param privateKey
* @return
*
Interfaces KeySpec
-java.security.spec.EncodedKeySpec (實現這個接口)
-java.security.spec.PKCS8EncodedKeySpec 該類代表私有密鑰的ASN.1編碼,根據ASN.1類型PrivateKeyInfo進行編碼
-java.security.spec.X509EncodedKeySpec 該類表示公鑰的ASN.1編碼,根據ASN.1類型SubjectPublicKeyInfo進行編碼。
-java.security.spec.RSAPrivateKeySpec
Interfaces Key
Interface - PrivateKey extends Key, Destroyable
Interface - RSAPrivateKey extends PrivateKey, RSAKey
* @throws Exception
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");//單例模式 返回一個KeyFactory對象,它轉換指定RSA算法的公鑰/私鑰。
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());//進行base64解碼
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);//使用給定的編碼密鑰創建一個新的PKCS8EncodedKeySpec,decodedKey假設按照PKCS#8標準進行編碼的密鑰。 複製數組的內容以防止後續修改。。
return keyFactory.generatePrivate(keySpec);//generatePrivate(KeySpec keySpec) 從提供的密鑰規範(密鑰材料)生成私鑰對象。
}
/**
* 獲取公鑰
* @param publicKey 公鑰字符串
* @return
Interfaces Key
Interface - PublicKey extends extends Key
Interface - RSAPrivateKey extends PublicKey, RSAKey
* @throws Exception
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");//獲取單例模式的對象
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());//進行base64編碼
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
/**
* RSA加密
*
* @param data 待加密數據,可能包含了已經進行簽名的內容
* @param publicKey 公鑰
* @return
*/
public static String RSAEncrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");//該類提供加密和解密的加密密碼的功能。 它構成了Java加密擴展(JCE)框架的核心。
cipher.init(Cipher.ENCRYPT_MODE, publicKey);//用密鑰初始化此密碼。
int inputLen = data.getBytes().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.getBytes(), offset, MAX_ENCRYPT_BLOCK);//在單一部分操作中加密或解密數據,或完成多部分操作。
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 獲取加密內容使用base64進行編碼,並以UTF-8爲標準轉化成字符串
// 加密後的字符串
return new String(Base64.encodeBase64String(encryptedData));
}
/**
* RSA解密
* @param data 待解密數據
* @param privateKey 私鑰
* @return
*/
public static String RSADecrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.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(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密後的內容
return new String(decryptedData, "UTF-8");
}
/**
* 簽名
*
* @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()));
}
public static String getSignStr(TreeMap<String,String> params) {
if(params.containsKey("sign"))//簽名明文組裝不包含sign字段
params.remove("sign");
StringBuilder sb = new StringBuilder();
for(Map.Entry<String, String> entry:params.entrySet()){
if(entry.getValue()!=null&&entry.getValue().length()>0){
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
if(sb.length()>0){
sb.deleteCharAt(sb.length()-1);
}
return sb.toString();
}
public static void main(String[] args) {
try {
TreeMap<String,String> params = new TreeMap<String,String>();
params.put("PARAM1","remarkparam");
params.put("INIP","127.0.0.1");
params.put("STYLE","01");
params.put("ORDNUM","D15082500000002");
params.put("SERVICE","com.bs.pay");
params.put("ORDDATE","20150825");
String signStr = getSignStr(params);
//System.out.println("signStr="+signStr);
//String str2Sign="INIP=127.0.0.1&ORDDATE=20150825&ORDNUM=D15082500000002&PARAM1=remarkparam&SERVICE=com.bs.pay&STYLE=01";
// 生成密鑰對
//KeyPair keyPair = getKeyPair();
String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKstf0WW7CXfXHbk137gKVolqeJLwNPMWYzpfvGXFt0PnfqNbVq0FFt3qZIXfeW9s1bBFLeV12vps99/1EfSj82vBVahEHPsPuLFh/oJ6pm1i4H2tvLVM34GWQpK7G6S/y9ldw8jAZHXXte/39+64XGUNngrQPrPPUGAMbiG6lHbAgMBAAECgYAvWgGX7XzbqSL53Knj9fxabIrHB6+Kpz+qZDjyeZoggp5v3cl/SstK1ho5SHhSIhzHBdhGRJP3zQkt7T16tLuEdQE0oR4h+RPXhtpFVXzApS6xnGP3K0IKfnIt5+48Lz0n5eEZ8CLlioJeSmIJxmD/LBeFGU/+ycQNuTho6+nfWQJBAORqUlGWx3K+df9+kPA75KS5xcSWFl+C3h9IKApjYCb6Pgyj4+VSKRpUmk1aRiU3FP5zA64hpr/9dHJzurluLTUCQQC/2Z7YqhRNs2vnAx/CV7vqUhHlJYNhlCHESl2RxYj4uwDPGkG34HEe3yjcxyXCFE3MjrI6bW4omUPOvxOksTTPAkBtSqpEu76HGrbfHzI+oInQf8svOxlNbhLWKPJvtgkF3WrfJXQf/+YG4UBLjsxNdkOnVGUIZrWg26diNBm+LAeZAkBdEoRfdFOZRnwIfJmBvPJrQRlxY3uTcTvYcINoXr6OfJpt3r0XKYl3gqfeQ7brqtH7dRa+Bay1gXVF7ajn8xg9AkAjmPZxgfllYW9uRojFzH91bkCHK7f41nocWPPkax3wkCjMtKisUXL+2B1Hgr9U9Zp6naNbw/43xNRSVSXQg8IW";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrLX9Fluwl31x25Nd+4ClaJaniS8DTzFmM6X7xlxbdD536jW1atBRbd6mSF33lvbNWwRS3lddr6bPff9RH0o/NrwVWoRBz7D7ixYf6CeqZtYuB9rby1TN+BlkKSuxukv8vZXcPIwGR117Xv9/fuuFxlDZ4K0D6zz1BgDG4hupR2wIDAQAB";
//String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
//System.out.println("私鑰:" + privateKey);
//System.out.println("公鑰:" + publicKey);
// RSA加密
//String data = "待加密的文字內容";
String encryptData = RSAEncrypt(signStr, getPublicKey(publicKey));
System.out.println("加密後內容:" + encryptData);
// RSA解密
String decryptData = RSADecrypt(encryptData, getPrivateKey(privateKey));
System.out.println("解密後內容:" + decryptData);
// RSA簽名
String sign = sign(signStr, getPrivateKey(privateKey));
System.out.println("sign=="+sign);
// RSA驗籤
boolean result = verify(signStr, getPublicKey(publicKey), sign);
System.out.print("驗簽結果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.print("加解密異常");
}
}
}