JSEncrypt前端加密以及java後端解密
阿新 • • 發佈:2019-01-12
1.前端程式碼
1.1 匯入js
<script src="js/jquery.js" type="text/javascript"></script>
<!-- 引入非對稱RSA加密工具 -->
<script src="js/encrypt/csoft_encrypt.min.js"></script>
1.2 html 頁面存放公鑰
<!-- 公鑰區域 公鑰通過JDK生成 也可通過後端的CsoftSecurityUtil.createKeyPairs() 生成 --><textarea id="publickey" rows="20" cols="60" style="display:none"> -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyvrVNCDKcN9sGujXqAdj a3L9hYl76Tx/hiWxCvtVE+kgUSZFOPpSHA0hbX3S/+fGh1kGR989IETLRNhn860W 4WOrbtPFSFRQP1uGseH8Of3dU5v+XrZBVuhicSFXhfnGgJpig+Lkx tlGpy01H5XA2Q7iyi2Oa7BCrSx0sb/CfGjFpKTyEmQqkgUw3C2NJkgwjCG9CrJiI jt2W5xdhdazP3jPWRRemx5bE+GtkhvZETFErIkUb55vNiXc/rYZWOa3+SZpw8oey pQIDAQAB -----END PUBLIC KEY----- </textarea>
function qyzx(nbxh,url,ff){ // 獲取公鑰 var publicKey = $('#publickey').val(); // 建立加密例項 var jsencrypt = new JSEncrypt(); // 初始化公鑰 jsencrypt.setPublicKey(publicKey); // 加密資料 nbxh =jsencrypt.encrypt(nbxh); var e = document.createElement("a"); e.href = url+"xxxx.do?method="+ff+"&nbxh="+nbxh+"&bqz=hz"; e.target="_blank"; document.body.appendChild(e); e.click(); }
注意:Http請求特殊符號變空格的問題:引數經過Base64編碼會有'+'加號,後端request.request.getParameter(name)獲取的值中+號變成了空格(解決辦法:request.getParameter(nms).replaceAll(" ", "+"));
2.1 封裝工具類(jdk1.7.0)
(請自行測試,選擇適合的自己的方法)
package com.centralsoft.core.utils; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; /** * @Description: 中研通用加密工具 RSA+ASE+SHA256(非對稱加密(對稱祕鑰),對稱加密資料,Sha256訊息摘要,RSA簽名) * @author wh.huang DateTime 2018年11月15日 下午3:00:21 * @version 1.0 */ public class CsoftSecurityUtil { // 加密資料和祕鑰的編碼方式 public static final String UTF_8 = "UTF-8"; // 填充方式 public static final String AES_ALGORITHM = "AES/CFB/PKCS5Padding"; public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding"; public static final String RSA_ALGORITHM_NOPADDING = "RSA"; /** * Description: 解密接收資料 * @author wh.huang DateTime 2018年11月15日 下午5:06:42 * @param externalPublicKey * @param selfPrivateKey * @param receivedMap * @throws InvalidKeyException * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws UnsupportedEncodingException * @throws InvalidAlgorithmParameterException * @throws DecoderException */ public static String decryptReceivedData(PublicKey externalPublicKey, PrivateKey selfPrivateKey, String receiveData) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException, DecoderException { @SuppressWarnings("unchecked") Map<String, String> receivedMap = (Map<String, String>) JSON.parse(receiveData); // receivedMap為請求方通過from urlencoded方式,請求過來的引數列表 String inputSign = receivedMap.get("sign"); // 用請求方提供的公鑰驗籤,能配對sign,說明來源正確 inputSign = decryptRSA(externalPublicKey, inputSign); // 校驗sign是否一致 String sign = sha256(receivedMap); if (!sign.equals(inputSign)) { // sign校驗不通過,說明雙方傳送出的資料和對方收到的資料不一致 System.out.println("input sign: " + inputSign + ", calculated sign: " + sign); return null; } // 解密請求方在傳送請求時,加密data欄位所用的對稱加密金鑰 String key = receivedMap.get("key"); String salt = receivedMap.get("salt"); key = decryptRSA(selfPrivateKey, key); salt = decryptRSA(selfPrivateKey, salt); // 解密data資料 String data = decryptAES(key, salt, receivedMap.get("data")); System.out.println("接收到的data內容:" + data); return data; } /** * Description: 加密資料組織示例 * @author wh.huang DateTime 2018年11月15日 下午5:20:11 * @param externalPublicKey * @param selfPrivateKey * @return 加密後的待發送資料 * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException * @throws InvalidKeyException * @throws NoSuchPaddingException * @throws UnsupportedEncodingException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws InvalidAlgorithmParameterException */ public static String encryptSendData(PublicKey externalPublicKey, PrivateKey selfPrivateKey,JSONObject sendData) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException { // 隨機生成對稱加密的金鑰和IV (IV就是加鹽的概念,加密的偏移量) String aesKeyWithBase64 = genRandomAesSecretKey(); String aesIVWithBase64 = genRandomIV(); // 用接收方提供的公鑰加密key和salt,接收方會用對應的私鑰解密 String key = encryptRSA(externalPublicKey, aesKeyWithBase64); String salt = encryptRSA(externalPublicKey, aesIVWithBase64); // 組織業務資料資訊,並用上面生成的對稱加密的金鑰和IV進行加密 System.out.println("傳送的data內容:" + sendData.toJSONString()); String cipherData = encryptAES(aesKeyWithBase64, aesIVWithBase64, sendData.toJSONString()); // 組織請求的key、value對 Map<String, String> requestMap = new TreeMap<String, String>(); requestMap.put("key", key); requestMap.put("salt", salt); requestMap.put("data", cipherData); requestMap.put("source", "由接收方提供"); // 新增來源標識 // 計算sign,並用請求方的私鑰加簽,接收方會用請求方發放的公鑰驗籤 String sign = sha256(requestMap); requestMap.put("sign", encryptRSA(selfPrivateKey, sign)); // TODO: 以form urlencoded方式呼叫,引數為上面組織出來的requestMap // 注意:請務必以form urlencoded方式,否則base64轉碼後的個別字元可能會被轉成空格,對方接收後將無法正常處理 JSONObject json = new JSONObject(); json.putAll(requestMap); return json.toString(); } /** * Description: 獲取隨機的對稱加密的金鑰 * @author wh.huang DateTime 2018年11月15日 下午5:25:53 * @return 對稱祕鑰字元 * @throws NoSuchAlgorithmException * @throws UnsupportedEncodingException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidKeyException * @throws NoSuchPaddingException */ private static String genRandomAesSecretKey() throws NoSuchAlgorithmException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchPaddingException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(128); SecretKey secretKey = keyGen.generateKey(); String keyWithBase64 = Base64.encodeBase64(secretKey.getEncoded()).toString(); return keyWithBase64; } private static String genRandomIV() { SecureRandom r = new SecureRandom(); byte[] iv = new byte[16]; r.nextBytes(iv); String ivParam = Base64.encodeBase64(iv)+""; return ivParam; } /** * 對稱加密資料 * * @param keyWithBase64 * @param aesIVWithBase64 * @param plainText * @return * @throws NoSuchAlgorithmException * @throws NoSuchPaddingException * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws UnsupportedEncodingException * @throws InvalidAlgorithmParameterException */ private static String encryptAES(String keyWithBase64, String ivWithBase64, String plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException { byte[] keyWithBase64Arry = keyWithBase64.getBytes(); byte[] ivWithBase64Arry = ivWithBase64.getBytes(); SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(keyWithBase64Arry), "AES"); IvParameterSpec iv = new IvParameterSpec(Base64.decodeBase64(ivWithBase64Arry)); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, key, iv); return Base64.encodeBase64(cipher.doFinal(plainText.getBytes(UTF_8))).toString(); } /** * 對稱解密資料 * * @param keyWithBase64 * @param cipherText * @return * @throws NoSuchAlgorithmException * @throws NoSuchPaddingException * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws UnsupportedEncodingException * @throws InvalidAlgorithmParameterException */ private static String decryptAES(String keyWithBase64, String ivWithBase64, String cipherText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException { byte[] keyWithBase64Arry = keyWithBase64.getBytes(); byte[] ivWithBase64Arry = ivWithBase64.getBytes(); byte[] cipherTextArry = cipherText.getBytes(); SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(keyWithBase64Arry), "AES"); IvParameterSpec iv = new IvParameterSpec(Base64.decodeBase64(ivWithBase64Arry)); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key, iv); return new String(cipher.doFinal(Base64.decodeBase64(cipherTextArry)), UTF_8); } /** * 非對稱加密,根據公鑰和原始內容產生加密內容 * * @param key * @param content * @return * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws UnsupportedEncodingException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws InvalidAlgorithmParameterException */ private static String encryptRSA(Key key, String plainText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException { Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, key); return Base64.encodeBase64(cipher.doFinal(plainText.getBytes(UTF_8))).toString(); } /** * 根據私鑰和加密內容產生原始內容 * @param key * @param content * @return * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws DecoderException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws UnsupportedEncodingException * @throws InvalidAlgorithmParameterException */ private static String decryptRSA(Key key, String content) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, DecoderException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException { Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); byte[] contentArry = content.getBytes(); return new String(cipher.doFinal(Base64.decodeBase64(contentArry)), UTF_8); } /** * 計算sha256值 * * @param paramMap * @return 簽名後的所有資料,原始資料+簽名 */ private static String sha256(Map<String, String> paramMap) { Map<String, String> params = new TreeMap<String, String>(paramMap); StringBuilder concatStr = new StringBuilder(); for (Entry<String, String> entry : params.entrySet()) { if ("sign".equals(entry.getKey())) { continue; } concatStr.append(entry.getKey() + "=" + entry.getValue() + "&"); } return DigestUtils.md5Hex(concatStr.toString()); } /** * 建立RSA的公鑰和私鑰示例 將生成的公鑰和私鑰用Base64編碼後打印出來 * @throws NoSuchAlgorithmException */ public static void createKeyPairs() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); System.out.println("公鑰"+Base64.encodeBase64(publicKey.getEncoded())); System.out.println("私鑰"+Base64.encodeBase64(privateKey.getEncoded())); } /** * Description:預設的RSA解密方法 一般用來解密 引數 小資料 * @author wh.huang DateTime 2018年12月14日 下午3:43:11 * @param privateKeyStr * @param data * @return * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException * @throws NoSuchPaddingException * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException */ public static String decryptRSADefault(String privateKeyStr,String data) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM_NOPADDING); byte[] privateKeyArray = privateKeyStr.getBytes(); byte[] dataArray = data.getBytes(); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyArray)); PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); Cipher cipher = Cipher.getInstance(RSA_ALGORITHM_NOPADDING); cipher.init(Cipher.DECRYPT_MODE, privateKey); return new String(cipher.doFinal(Base64.decodeBase64(dataArray)), UTF_8); } }
2.2 呼叫工具類 解密引數
// 初始化私鑰 儲存成常量或資料庫紀錄
final String PRIVATE_KEY_STR = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDK+tU0IMpw32wa6NeoB2Nrcv2FiXvpPH+GJbEK+1UT6SBRJkU4+lIcDSFtfdL/58aHWQZH3z0gRMtE2GfzrRbhY6tu08VIVFA/W4ax4fw59CZY/rTbDPtooYNYvKQFYvUOS4T+IQZc6B16FxGtQLrjJGGSfbU5RZe4bv92U5x9r1YgZYb5629jd1Tm/5etkFW6GJxIVeF+caAmmKD4uTG2UanLTUflcDZDuLKLY5rsEKtLHStLsSOVZqfuahPH5VJ/vESDq2DNdJ7E8aY6o1wBbdX3X4+umJPUiUt8e+gbrGwi5uEexLaEl94HNFutLVF7X8U1GKbom18eLX67kBQuBUWoREfmb7JjSvrD2yD0zu0rBdG2wrrpLRCd4holsoQKBgQDEkwWiHoDzBrJrs/0F7Yzw1jctlJ1E2RoQszgyEq+s0/f7rIWj3YPkFqOjQExnTS4tjZKbI7PCWzQu+KR5CIFjEIC6FQXXDaBsyOJxb6gG6XyBqxsKLITvauCnhnXlAbUUOaekod1XMvbbquFzjzStoWemUeVxeNIAUFpKa4P2UQKBgQC07nILuHFFgAX90VOMF7MR5psnWRunk8ORZFlLSGE3ksisDUn1OuOHVcBhPcUnBUwiO1RtHR7FjjAIY51+THHKRnEMfcTYxXP7VHPX85jeGjhBM8gEuWlDVrzX17UjxY0mYRAwTf3DOrfRI7hwfGfIVkUOq/hO3wfH/mHho6q1YQKBgQDKwxIYzgYrViDwH9wEbxx7WsOtHp1hnd45nu3B/JFXlK/4PTzCgoo2SpoubhGN1M2ywHvui1jsBhKS5P3QPqK3GTwOHl2SjV67FCXbmXqLQa27UZbMDQk/NssopjVPgz8i6C7L8oIdIAo/KIdSXaiEv7apHrEg09pDwam/fCeZUg==" ;
nbxh = CsoftSecurityUtil.decryptRSADefault(PRIVATE_KEY_STR,nbxh);
注意: 本文祕鑰被我篡改,請自行在工具類中獲取。