RSA加密算法淺析

一、簡介

RSA是最流行的非對稱加密算法之一。也被稱爲公鑰加密
RSA是非對稱的,也就是用來加密的密鑰和用來解密的密鑰不是同一個。【敵人怎麼破解?】
 
RSA作爲一種非對稱的加密算法,其中很重要的一特點是當數據在網絡中傳輸時,用來加密數據的密鑰並不需要也和數據一起傳送。因此,這就減少了密鑰泄露的可能性。
 
RSA在不允許加密方解密數據時也很有用,加密的一方使用一個密鑰,稱爲公鑰解密的一方使用另一個密鑰,稱爲私鑰,私鑰需要保持其私有性。【注:公私鑰需要配對,不然木法解密】

二、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("加解密異常");
        }
    }
}

 

 

 

 

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