前言
最近研究了RSA非对称加密,关于什么是RSA,网上各种文章一搜一大把,但是关于如何使用RSA完成前后端的组合加密解密,东西就非常少了,并且由于RSA的特性,一个1024位的密钥只能加密117位字节数据,当数据量超过117位字节的时候,程序就会抛出异常,下面就给出如何完成前端RSA分段解密和后端RSA分段解密。
准备
前端RSA的JS类库
jsencrypt-master
后端java的RSA辅助方法类,代码如下:
package com.cityamoy.api.util; import java.io.ByteArrayOutputStream; import java.math.BigInteger; 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.RSAPrivateCrtKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; /** * RSA非对称加密辅助类 * @author spirit * @date 2017年12月5日 下午4:34:11 */ public class RSAUtil { /** 指定加密算法为DESede */ private static String ALGORITHM = "RSA/ECB/PKCS1Padding";//MD5withRSA///RSA/ECB/PKCS1Padding /** 指定key的大小(64的整数倍,最小512位) */ private static int KEYSIZE = 1024; /* RSA最大加密明文大小 */ private static final int MAX_ENCRYPT_BLOCK = 117; /* RSA最大解密密文大小 */ private static final int MAX_DECRYPT_BLOCK = 128; /* 公钥模量 */ public static String publicModulus = null; /* 公钥指数 */ public static String publicExponent = null; /* 私钥模量 */ public static String privateModulus = null; /* 私钥指数 */ public static String privateExponent = null; private static KeyFactory keyFactory = null; static { try { keyFactory = KeyFactory.getInstance("RSA"); } catch (NoSuchAlgorithmException ex) { System.out.println(ex.getMessage()); } } public RSAUtil(){ try { generateKeyPairString(KEYSIZE); } catch (Exception e) { e.printStackTrace(); } } public RSAUtil(int keySize){ try { generateKeyPairString(keySize); } catch (Exception e) { e.printStackTrace(); } } /** * 生成密钥对字符串 */ private void generateKeyPairString(int keySize) throws Exception{ /** RSA算法要求有一个可信任的随机数源 */ SecureRandom sr = new SecureRandom(); /** 为RSA算法创建一个KeyPairGenerator对象 */ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */ kpg.initialize(keySize, sr); /** 生成密匙对 */ KeyPair kp = kpg.generateKeyPair(); /** 得到公钥 */ Key publicKey = kp.getPublic(); /** 得到私钥 */ Key privateKey = kp.getPrivate(); /** 用字符串将生成的密钥写入文件 */ String algorithm = publicKey.getAlgorithm(); // 获取算法 KeyFactory keyFact = KeyFactory.getInstance(algorithm); BigInteger prime = null; BigInteger exponent = null; RSAPublicKeySpec keySpec = (RSAPublicKeySpec)keyFact.getKeySpec(publicKey, RSAPublicKeySpec.class); prime = keySpec.getModulus(); exponent = keySpec.getPublicExponent(); RSAUtil.publicModulus = HexUtil.bytes2Hex(prime.toByteArray()); RSAUtil.publicExponent = HexUtil.bytes2Hex(exponent.toByteArray()); RSAPrivateCrtKeySpec privateKeySpec = (RSAPrivateCrtKeySpec)keyFact.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class); BigInteger privateModulus = privateKeySpec.getModulus(); BigInteger privateExponent = privateKeySpec.getPrivateExponent(); RSAUtil.privateModulus = HexUtil.bytes2Hex(privateModulus.toByteArray()); RSAUtil.privateExponent = HexUtil.bytes2Hex(privateExponent.toByteArray()); } /** * 根据给定的16进制系数和专用指数字符串构造一个RSA专用的公钥对象。 * * @param hexModulus 系数。 * @param hexPublicExponent 专用指数。 * @return RSA专用公钥对象。 */ public static RSAPublicKey getRSAPublicKey(String hexModulus, String hexPublicExponent) { if (isBlank(hexModulus) || isBlank(hexPublicExponent)) { System.out.println("hexModulus and hexPublicExponent cannot be empty. return null(RSAPublicKey)."); return null; } byte[] modulus = null; byte[] publicExponent = null; try { modulus = HexUtil.hex2Bytes(hexModulus); publicExponent = HexUtil.hex2Bytes(hexPublicExponent); } catch (Exception ex) { System.out.println("hexModulus or hexPublicExponent value is invalid. return null(RSAPublicKey)."); ex.printStackTrace(); } if (modulus != null && publicExponent != null) { return generateRSAPublicKey(modulus, publicExponent); } return null; } /** * 根据给定的系数和专用指数构造一个RSA专用的公钥对象。 * * @param modulus 系数。 * @param publicExponent 专用指数。 * @return RSA专用公钥对象。 */ public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) { RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent)); try { return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec); } catch (InvalidKeySpecException ex) { System.out.println("RSAPublicKeySpec is unavailable."); ex.printStackTrace(); } catch (NullPointerException ex) { System.out.println("RSAUtils#KEY_FACTORY is null, can not generate KeyFactory instance."); ex.printStackTrace(); } return null; } /** * 根据给定的16进制系数和专用指数字符串构造一个RSA专用的私钥对象。 * * @param hexModulus 系数。 * @param hexPrivateExponent 专用指数。 * @return RSA专用私钥对象。 */ public static RSAPrivateKey getRSAPrivateKey(String hexModulus, String hexPrivateExponent) { if (isBlank(hexModulus) || isBlank(hexPrivateExponent)) { System.out.println("hexModulus and hexPrivateExponent cannot be empty. RSAPrivateKey value is null to return."); return null; } byte[] modulus = null; byte[] privateExponent = null; try { modulus = HexUtil.hex2Bytes(hexModulus); privateExponent = HexUtil.hex2Bytes(hexPrivateExponent); } catch (Exception ex) { System.out.println("hexModulus or hexPrivateExponent value is invalid. return null(RSAPrivateKey)."); ex.printStackTrace(); } if (modulus != null && privateExponent != null) { return generateRSAPrivateKey(modulus, privateExponent); } return null; } /** * 根据给定的系数和专用指数构造一个RSA专用的私钥对象。 * * @param modulus 系数。 * @param privateExponent 专用指数。 * @return RSA专用私钥对象。 */ public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) { RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent)); try { return (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec); } catch (InvalidKeySpecException ex) { System.out.println("RSAPrivateKeySpec is unavailable."); ex.printStackTrace(); } catch (NullPointerException ex) { System.out.println("RSAUtils#KEY_FACTORY is null, can not generate KeyFactory instance."); ex.printStackTrace(); } return null; } /** * 使用给定的公钥加密给定的字符串。 * * @param publicKey 给定的公钥。 * @param plaintext 字符串。 * @return 给定字符串的密文。 */ public static String encryptString(Key key, String plaintext) { if (key == null || plaintext == null) { return null; } byte[] data = plaintext.getBytes(); try { byte[] en_data = encrypt(key, data); return new String(Base64.encodeBase64String(en_data)); // return new String(HexUtil.bytes2Hex(en_data)); } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * 使用指定的公钥加密数据。 * * @param publicKey 给定的公钥。 * @param data 要加密的数据。 * @return 加密后的数据。 */ public static byte[] encrypt(Key key, byte[] data) throws Exception { Cipher ci = Cipher.getInstance(ALGORITHM); ci.init(Cipher.ENCRYPT_MODE, key); 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 = ci.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); } else { cache = ci.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 publicKey 给定的公钥 * @param encrypttext 密文 * @return 原文字符串。 */ public static String decryptString(Key key, String encrypttext) { if (key == null || isBlank(encrypttext)) { return null; } try { byte[] en_data = Base64.decodeBase64(encrypttext); // byte[] en_data = HexUtil.hex2Bytes(encrypttext); byte[] data = decrypt(key, en_data); return new String(data); } catch (Exception ex) { ex.printStackTrace(); System.out.println(String.format("\"%s\" Decryption failed. Cause: %s", encrypttext, ex.getCause().getMessage())); } return null; } /** * 使用指定的公钥解密数据。 * @param publicKey 指定的公钥 * @param data 要解密的数据 * @return 原数据 * @throws Exception */ public static byte[] decrypt(Key key, byte[] data) throws Exception { Cipher ci = Cipher.getInstance(ALGORITHM); ci.init(Cipher.DECRYPT_MODE, key); // return ci.doFinal(data); 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 = ci.doFinal(data, offSet, MAX_DECRYPT_BLOCK); } else { cache = ci.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } byte[] decryptedData = out.toByteArray(); out.close(); return decryptedData; } /** * 判断非空字符串 * @param cs 待判断的CharSequence序列 * @return 是否非空 */ private static boolean isBlank(final CharSequence cs) { int strLen; if (cs == null || (strLen = cs.length()) == 0) { return true; } for (int i = 0; i < strLen; i++) { if (Character.isWhitespace(cs.charAt(i)) == false) { return false; } } return true; } public static void main(String[] args) { new RSAUtil(); String source = "123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!123hello你好!"; System.out.println("RSAUtil.publicModulus=="+RSAUtil.publicModulus); System.out.println("RSAUtil.publicExponent=="+RSAUtil.publicExponent); System.out.println("RSAUtil.privateModulus=="+RSAUtil.privateModulus); System.out.println("RSAUtil.privateExponent=="+RSAUtil.privateExponent); //公钥加密,私钥解密 PublicKey publicKey = RSAUtil.getRSAPublicKey(RSAUtil.publicModulus, RSAUtil.publicExponent); String encript = RSAUtil.encryptString(publicKey, source); System.out.println("加密后数据:"+encript); PrivateKey privateKey = RSAUtil.getRSAPrivateKey(RSAUtil.privateModulus, RSAUtil.privateExponent); String newSource = RSAUtil.decryptString(privateKey, encript); System.out.println("解密后数据:"+newSource); //私钥加密,公钥解密 // String priKeyStr = RSAUtil.encryptString(privateKey, source); // System.out.println("加密后数据:"+priKeyStr); // String oldSource = RSAUtil.decryptString(publicKey, priKeyStr); // System.out.println("解密后数据:"+oldSource); } }
还有一个辅助类,HexUtil
package com.init.mine.util; public class HexUtil { private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * 16进制转byte数组 * @param data 16进制字符串 * @return byte数组 * @throws Exception 转化失败的异常 */ public static byte[] hex2Bytes(final String data) throws Exception { final int len = data.length(); if ((len & 0x01) != 0) { throw new Exception("Odd number of characters."); } final byte[] out = new byte[len >> 1]; // two characters form the hex value. for (int i = 0, j = 0; j < len; i++) { int f = toDigit(data.charAt(j), j) << 4; j++; f = f | toDigit(data.charAt(j), j); j++; out[i] = (byte) (f & 0xFF); } return out; } /** * bytes数组转16进制String * @param data bytes数组 * @return 转化结果 */ public static String bytes2Hex(final byte[] data) { return bytes2Hex(data, true); } /** * bytes数组转16进制String * @param data bytes数组 * @param toLowerCase 是否小写 * @return 转化结果 */ public static String bytes2Hex(final byte[] data, final boolean toLowerCase) { return bytes2Hex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); } /** * bytes数组转16进制String * @param data bytes数组 * @param toDigits DIGITS_LOWER或DIGITS_UPPER * @return 转化结果 */ private static String bytes2Hex(final byte[] data, final char[] toDigits) { final int l = data.length; final char[] out = new char[l << 1]; // two characters form the hex value. for (int i = 0, j = 0; i < l; i++) { out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; out[j++] = toDigits[0x0F & data[i]]; } return new String(out); } /** * 16转化为数字 * @param ch 16进制 * @param index 索引 * @return 转化结果 * @throws Exception 转化失败异常 */ private static int toDigit(final char ch, final int index) throws Exception { final int digit = Character.digit(ch, 16); if (digit == -1) { throw new Exception("Illegal hexadecimal character " + ch + " at index " + index); } return digit; } /* * 16进制字符串转字符串 */ public static String hex2String(String hex) throws Exception{ String r = bytes2String(hexString2Bytes(hex)); return r; } /* * 字节数组转字符串 */ public static String bytes2String(byte[] b) throws Exception { String r = new String (b,"UTF-8"); return r; } /* * 16进制字符串转字节数组 */ public static byte[] hexString2Bytes(String hex) { if ((hex == null) || (hex.equals(""))){ return null; } else if (hex.length()%2 != 0){ return null; } else{ hex = hex.toUpperCase(); int len = hex.length()/2; byte[] b = new byte[len]; char[] hc = hex.toCharArray(); for (int i=0; i<len; i++){ int p=2*i; b[i] = (byte) (charToByte(hc[p]) << 4 | charToByte(hc[p+1])); } return b; } } /* * 字符转换为字节 */ private static byte charToByte(char c) { return (byte) "0123456789ABCDEF".indexOf(c); } /* * 字符串转16进制字符串 */ public static String string2HexString(String s) throws Exception{ String r = bytes2HexString(string2Bytes(s)); return r; } /* * 字节数组转16进制字符串 */ public static String bytes2HexString(byte[] b) { String r = ""; for (int i = 0; i < b.length; i++) { String hex = Integer.toHexString(b[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } r += hex.toUpperCase(); } return r; } /* * 字符串转字节数组 */ public static byte[] string2Bytes(String s){ byte[] r = s.getBytes(); return r; } }
具体操作
点击下载【准备】部分的JS类库,完成后解压,获取jsencrypt-master\bin\jsencrypt.js,同级目录下还有一个jsencrypt.min.js,是用于生产环境的,这里我就不做其他操作了,我就只改写jsencrypt.js,也就是我演示需要用到的js文件,将这个js文件拷贝到我们的项目中,打开之后添加一个js方法,方法如下:
JSEncrypt.prototype.encryptLong = function(string) { var k = this.getKey(); try { var lt = ""; 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) { return false; } };然后在我们的前端页面里面写上具体的加密方式,并调用我们加在jsencrypt.js里面的方法,将加密的数据传输到后台,再调用JAVA辅助类进行解密就OK了,辅助类底部的main方法也写有具体的调用方式,就一句代码的事,就不重复了,这里贴一下前端如何使用JS库加密数据:
function generateEntype(){ var words = "这里写上一句超长的话!"; var publicKey = "your publicKey";//这里的公钥是通过base64加密转化的 var encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); var encryptPwd = encrypt.encryptLong(words); $.ajax({ url: "store/savePage", type: 'post', data: {data:encryptPwd}, dataType: 'json', //contentType: 'application/json; charset=utf-8', success: function (data) { console.info(data); }, error: function (xhr) { //console.error('出错了'); } }); }
补充
写到这里,差不多已经完了,上面的方法以及代码都是自己不断探索总结出来的东西,希望可以帮助到读者。jsencrypt这个JS类库是我踩过的所有坑中最好的一个了,但是这个东西还是有缺陷的,除了上面我们解决掉的超长数据分段加密,它只能用公钥进行加密,私钥进行解密。由于我只探索到这里就已经完成了自己需要的东西,所以就暂时停止了,如果我解决了后面说的这个问题,就在后续的博客中进行更新。