RSA 加密解密,客戶端JS加密,服務端JAVA解密

因為Rsa加密的代碼都是比較通用的,所以沒有特意去整合,這裡參照著兩位大神的代碼重新寫了一遍,做了一些簡單的修改,符合本地運行環境


服務端代參照:http://www.cnblogs.com/zhujiabin/p/7118126.html
客戶端代碼參照:https://jackiedark.github.io/2018/02/05/JSEncrypt%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E6%AE%B5%E5%8A%A0%E8%A7%A3%E5%AF%86/

JS加密依賴:jsencrypt.jsGithub地址:https://github.com/travist/jsencrypt可客戶端儘量依賴JAVA自帶的Jar,只是Base64加密的時候額外依賴了apache的工具類commons-net-3.3.jar

服務端工RSA工具類

<code>package com.wzh.config.utils;

import org.apache.commons.net.util.Base64;
import org.apache.log4j.Logger;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * 
 * 
 * @author wzh
 * @version 2018-12-16 18:20
 * @see [相關類/方法] (可選)
 **/
public class RsaUtils
{
    
    private static Logger log = Logger.getLogger(RsaUtils.class);
    
    /**
     * 塊加密大小
     */
    private static final int CACHE_SIZE = 1024;
    
    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";
    
    /**
     * 簽名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    
    /**
     * 獲取公鑰的key
     */
    private static final String PUBLIC_KEY = "RsaPublicKey";
    
    /**
     * 獲取私鑰的key
     */
    private static final String PRIVATE_KEY = "RsaPrivateKey";
    
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    
    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;
    
    /**
     * Base64字符串解碼為二進制數據
     * @param base64
     * @return 二進制數據
     * @throws Exception
     */
    public static byte[] decodeBase64(String base64)
        throws Exception
    {
        return Base64.decodeBase64(base64.getBytes());
    }
    
    /**
     * 二進制數據編碼為Base64字符串
     * @param bytes
     * @return Base64字符串
     * @throws Exception
     */
    public static String encodeBase64(byte[] bytes)
        throws Exception
    {
        return new String(Base64.encodeBase64(bytes));
    }

    /**
     * 生成秘鑰對
     * @return 返回公鑰和私鑰的Map集合
     * @throws Exception
     */
    public static Map initKeyPair()
        throws Exception
    {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(CACHE_SIZE);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();

        Map keyMap = new HashMap(2);
        // 公鑰
        keyMap.put(PUBLIC_KEY, publicKey);
        // 私鑰
        keyMap.put(PRIVATE_KEY, privateKey);

        return keyMap;
    }

    /**
     * 獲取私鑰
     * @param keyMap 秘鑰對Map
     * @return 私鑰字符串
     * @throws Exception
     */
    public static String getPrivateKey(Map keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return encodeBase64(key.getEncoded());
    }

    /**
     * 獲取公鑰字符串
     * @param keyMap 秘鑰對Map
     * @return 公鑰字符串
     * @throws Exception
     */
    public static String getPublicKey(Map keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return encodeBase64(key.getEncoded());
    }

    /**
     * 使用私鑰生成數字簽名
     * @param data 使用私鑰加密的數據
     * @param privateKey 是喲啊字符串
     * @return 數字簽名
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        // 獲取byte數組
        byte[] keyBytes = decodeBase64(privateKey);
        // 構造PKCS8EncodedKeySpec對象
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        // 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 取私鑰匙對象
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        // 用私鑰對信息生成數字簽名
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return encodeBase64(signature.sign());
    }

    /**
     * 校驗數字簽名
     * @param data 私鑰加密的數據
     * @param publicKey 公鑰字符串
     * @param sign 私鑰生成的簽名
     * @return 校驗成功返回true 失敗返回false
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
        // 獲取byte數組
        byte[] keyBytes = decodeBase64(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        // 構造X509EncodedKeySpec對象
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 指定的加密算法
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 取公鑰匙對象
        PublicKey publicK = keyFactory.generatePublic(keySpec);
        signature.initVerify(publicK);
        signature.update(data);

        // 驗證簽名是否正常
        return signature.verify(decodeBase64(sign));
    }

    /**
     * 私鑰加密
     * @param data 需要加密的數據
     * @param privateKey 私鑰
     * @return 加密後的數據
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        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();
        return encryptedData;
    }

    /**
     * 公鑰加密
     * @param data 需要加密的數據
     * @param publicKey 公鑰字符串
     * @return 加密後的數據
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        byte[] keyBytes = decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        // 對數據加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        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();
        return encryptedData;
    }

    /**
     * 私鑰解密
     * @param encryptedData 公鑰加密的數據
     * @param privateKey 私鑰字符串
     * @return 私鑰解密的數據
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
        byte[] keyBytes = decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        int inputLen = encryptedData.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(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公鑰解密
     * @param encryptedData 私鑰加密的數據
     * @param publicKey 公鑰字符串
     * @return 公鑰解密的數據
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
        byte[] keyBytes = decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.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(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公鑰加密方法
     * @param data 需加密的字符串
     * @param PUBLICKEY 公鑰字符串
     * @return 加密後的字符串
     */
    public static String encryptedDataByPublic(String data, String PUBLICKEY) {
        try {
            data = encodeBase64(encryptByPublicKey(data.getBytes(), PUBLICKEY));
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage(),e);
        }
        return data;
    }

    /**
     * 私鑰解密方法
     * @param data 公鑰加密的字符串
     * @param PRIVATEKEY 私鑰字符串
     * @return 私鑰解密的字符串
     */
    public static String decryptDataByPrivate(String data, String PRIVATEKEY) {
        String temp = "";
        try {
            byte[] rs = decodeBase64(data);
            //以utf-8的方式生成字符串
            temp = new String(decryptByPrivateKey(rs, PRIVATEKEY),"UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return temp;
    }

    public static void main(String[] args) {
        try {
            Map keyMap = RsaUtils.initKeyPair();
            String publicKey = RsaUtils.getPublicKey(keyMap);
            String privateKey = RsaUtils.getPrivateKey(keyMap);
            System.out.println("公鑰:" + publicKey);
            System.out.println("私鑰:" + privateKey);
            String source = "我是需要私鑰加密的字符串!";
            System.out.println("簽名驗證邏輯,私鑰加密--公鑰解密,需要加密的字符串:" + source);
            byte[] data = source.getBytes();
            byte[] encodedData = RsaUtils.encryptByPrivateKey(data, privateKey);
            System.out.println("私鑰加密後:" + new String(encodedData));
            String sign = RsaUtils.sign(encodedData, privateKey);
            System.out.println("簽名:" + sign);
            boolean status = RsaUtils.verify(encodedData, publicKey, sign);
            System.out.println("驗證結果:" + status);
            byte[] decodedData = RsaUtils.decryptByPublicKey(encodedData, publicKey);
            String target = new String(decodedData);
            System.out.println("公鑰解密私鑰加密的數據:" + target);

            System.out.println("---------公鑰加密----私鑰解密----------");
            // 這裡儘量長一點,複製了一段歌詞
            String msg = "月濺星河,長路漫漫,風煙殘盡,獨影闌珊;誰叫我身手不凡,誰讓我愛恨兩難,到後來," +
                    "肝腸寸斷。幻世當空,恩怨休懷,舍悟離迷,六塵不改;且怒且悲且狂哉,是人是鬼是妖怪,不過是," +
                    "心有魔債。叫一聲佛祖,回頭無岸,跪一人為師,生死無關;善惡浮世真假界,塵緣散聚不分明,難斷!" +
                    "我要這鐵棒有何用,我有這變化又如何;還是不安,還是氐惆,金箍當頭,欲說還休。我要這鐵棒醉舞魔," +
                    "我有這變化亂迷濁;踏碎靈霄,放肆桀驁,世惡道險,終究難逃。";
            String ecodeMsg = RsaUtils.encryptedDataByPublic(msg,publicKey);
            System.out.println("加密後的歌詞:" + ecodeMsg);
            String decodeMsg = RsaUtils.decryptDataByPrivate(ecodeMsg,privateKey);
            System.out.println("解密後的歌詞:" + decodeMsg);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

/<code> 

首先測試一下工具類,main函數跑一下,成功驗證簽名,加密,解密


RSA 加密解密,客戶端JS加密,服務端JAVA解密

image.png

客戶端JS代碼,需要JSEncrypt庫,前文有給出github地址,這裡對這個庫做一個簡單的擴展,因為RSA長文本超過秘鑰長度要報錯,所以需要擴展修改下

<code>/**
 * ---------------------------
 * 此JS需加載JSEncrypt庫的後面,加密解密調用著兩個方法
 * ---------------------------
 */

/**
 * 長文本加密
 * @param {string} string 待加密長文本
 * @returns {string} 加密後的base64編碼
* */
JSEncrypt.prototype.encryptLong = function (string) {
    var k = this.getKey();
    try {
        var ct = "";
        //RSA每次加密117bytes,需要輔助方法判斷字符串截取位置
        //1.獲取字符串截取點
        var bytes = new Array();
        bytes.push(0);
        var byteNo = 0;
        var len, c;
        len = string.length;
        var temp = 0;
        for (var i = 0; i < len; i++) {
            c = string.charCodeAt(i);
            if (c >= 0x010000 && c <= 0x10FFFF) {  //特殊字符,如Ř,Ţ
                byteNo += 4;
            } else if (c >= 0x000800 && c <= 0x00FFFF) { //中文以及標點符號
                byteNo += 3;
            } else if (c >= 0x000080 && c <= 0x0007FF) { //特殊字符,如È,Ò
                byteNo += 2;
            } else { // 英文以及標點符號
                byteNo += 1;
            }
            if ((byteNo % 117) >= 114 || (byteNo % 117) == 0) {
                if (byteNo - temp >= 114) {
                    bytes.push(i);
                    temp = byteNo;
                }
            }
        }
        //2.截取字符串並分段加密
        if (bytes.length > 1) {
            for (var i = 0; i < bytes.length - 1; i++) {
                var str;
                if (i == 0) {
                    str = string.substring(0, bytes[i + 1] + 1);
                } else {
                    str = string.substring(bytes[i] + 1, bytes[i + 1] + 1);
                }
                var t1 = k.encrypt(str);
                ct += t1;
            }
            ;
            if (bytes[bytes.length - 1] != string.length - 1) {
                var lastStr = string.substring(bytes[bytes.length - 1] + 1);
                ct += k.encrypt(lastStr);
            }
            return hex2b64(ct);
        }
        var t = k.encrypt(string);
        var y = hex2b64(t);
        return y;
    } catch (ex) {
        console.log(ex);
        return false;
    }
};

/**
 * 長文本解密
 * @param {string} string 加密後的base64編碼
 * @returns {string} 解密後的原文
 * */
JSEncrypt.prototype.decryptLong = function (string) {
    var k = this.getKey();
    var maxLength = 128;
    try {
        var string = b64tohex(string);
        var ct = "";
        if (string.length > maxLength * 2) {
            var lt = string.match(/.{1,256}/g);  //128位解密。取256位
            lt.forEach(function (entry) {
                var t1 = k.decrypt(entry);
                ct += t1;
            });
            return ct;
        }
        var y = k.decrypt(string);
        return y;
    } catch (ex) {
        return false;
    }
};

function hex2b64(h) {
    var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var b64padchar="=";
    var i;
    var c;
    var ret = "";
    for(i = 0; i+3 <= h.length; i+=3) {
        c = parseInt(h.substring(i,i+3),16);
        ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
    }
    if(i+1 == h.length) {
        c = parseInt(h.substring(i,i+1),16);
        ret += b64map.charAt(c << 2);
    }
    else if(i+2 == h.length) {
        c = parseInt(h.substring(i,i+2),16);
        ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
    }
    while((ret.length & 3) > 0) ret += b64padchar;
    return ret;
}
/<code>

一個簡單的測試頁面,就不做前後臺銜接了,只是在前提用後臺生成的公鑰進行加密,然後後臺main方法解密一下。

<code>

  
    MyHtml.html
    
    
    
     
     
     

     
  
  
  
  需要加密的內容:
  公鑰:
  密文:
  

/<code>


分享到:


相關文章: