RSA算法是目前最安全的算法,因为RSA算法的密钥不需要进行网络传输,所以同一密钥在很长一段时间内不改变也不会被破解。因为RSA的安全性,也常常作为其他性能比较高的算法(如DES,亲测,对同一串DES算法解密速度是RSA算法解密速度的100倍)的密钥交换算法。项目中由于业务量增长到了一定程度了,也涉及到一些敏感的数据,所以需要采取一定的安全措施了。
每次数据传输都生成公钥私钥还是只生成一次公钥和私钥
经过测试,系统内每次都生成新的公私钥要比只用一次生成的公私钥要慢10倍,这对于我们刚刚创业的项目来说开销是不小的。而RSA算法密钥不用在网上公开,即使对方要破解,也需要借助超级计算机的大量运算才能获取到私钥,成本是巨大的,所以暂时只需要生成一次私钥和公钥就基本上能够保证系统的安全性了,以后每次加密解密都使用这个密钥,这样大大的避免了每次数据传输都生成一次公钥和私钥的CPU开销,安全性也不需要担心
经过测试,根据项目中所使用到的服务器和传输字符串的解密速度大概是1000次/秒,也就是每次解密的时间约为1ms,这对于整个系统来说影响是非常微妙的了,所以决定,仅使用RSA一次性密钥就行了
当迅速的把前后端的加解密写好,把密钥生成后,开始测试的时候,抛出了这么一个异常
javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
意思是加密的字符串超过117个字符了,然而我们在做设计的时候完全是考虑到了使用分段加密的,如下
/**
* 公钥加密
*
* @param data
* @param publicKey
* @return
* @throws Exception
*/
public static String encryptByPublicKey(String data, RSAPublicKey publicKey)
throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 模长
int key_len = publicKey.getModulus().bitLength() / 8;
// 加密数据长度 <= 模长-11
String[] datas = splitString(data, key_len - 11);
String mi = "";
//如果明文长度大于模长-11则要分组加密
for (String s : datas) {
mi += bcd2Str(cipher.doFinal(s.getBytes()));
}
return mi;
}
/**
* BCD转字符串
*/
public static String bcd2Str(byte[] bytes) {
char temp[] = new char[bytes.length * 2], val;
for (int i = 0; i < bytes.length; i++) {
val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
val = (char) (bytes[i] & 0x0f);
temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
}
return new String(temp);
}
明明使用的就是分段加密,为什么会出这样的问题,比较他大爷的了。经过各种款式的测试,发现了一个问题,就是如果有输入中文的时候切超过一定的长度就会抛这个异常,很明显了,又是万恶的编码导致的问题。java启动的默认编码是UTF-8,那么,为什么编码错误会导致这个异常?经过debug,发现原来splitString这段代码判断有误,
/**
* 拆分字符串
*/
public static String[] splitString(String string, int len) {
int x = string.length() / len;
int y = string.length() % len;
int z = 0;
if (y != 0) {
z = 1;
}
String[] strings = new String[x + z];
String str = "";
for (int i=0; i<x+z; i++) {
if (i==x+z-1 && y!=0) {
str = string.substring(i*len, i*len+y);
}else{
str = string.substring(i*len, i*len+len);
}
strings[i] = str;
}
return strings;
}
上面红色的代码在获取String的长度时是根据字符的长度,而不是根据字节的长度,经过探究发现,String内部是使用char数组的,char数组的一个元素就是一个字符,不管是中文还是ASCCI码,如下面的代码
char a[] = {'你','好'};
char b[] = {'h','i'};
System.out.println(a.length);
System.out.println(b.length);
输出的结果是两个2,通过阅读下面的String.length的源码发现,String.length()就是获取char的长度,那么问题比较明朗了,不那么他大爷的了
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
/** * Returns the length of this string. * The length is equal to the number of <a href="Character.html#unicode">Unicode * code units</a> in the string. * * @return the length of the sequence of characters represented by this * object. */ public int length() { return value.length; }
问题的根源就是切分的长度是按照字符串长度切分的,加密的时候又是限制字节数小于117。这个时候就应该轮到全编码的ISO-8859-1上场了,不清楚可以看看http://blog.csdn.net/u014476088/article/details/73205498,将加密算法做如下改动
/** * 公钥加密 * * @param data * @param publicKey * @return * @throws Exception */ public static String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 模长 int key_len = publicKey.getModulus().bitLength() / 8; // 加密数据长度 <= 模长-11 String iosDatas = new String(data.getBytes(),"ISO-8859-1"); String[] datas = splitString(iosDatas, key_len - 11); String mi = ""; //如果明文长度大于模长-11则要分组加密 for (String s : datas) { mi += bcd2Str(cipher.doFinal(s.getBytes("ISO-8859-1"))); } return mi; }
原来是解密算法是这样的
/** * 私钥解密 * * @param data * @param privateKey * @return * @throws Exception */ public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); //模长 int key_len = privateKey.getModulus().bitLength() / 8; byte[] bytes = data.getBytes(); byte[] bcd = ASCII_To_BCD(bytes, bytes.length); System.out.println(bcd.length); //如果密文长度大于模长则要分组解密 String ming = ""; byte[][] arrays = splitArray(bcd, key_len); for(byte[] arr : arrays){ ming += new String(cipher.doFinal(arr)); } return ming; }
现在的解密算法改为
/** * 私钥解密 * * @param data * @param privateKey * @return * @throws Exception */ public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); //模长 int key_len = privateKey.getModulus().bitLength() / 8; byte[] bytes = data.getBytes("ISO-8859-1"); byte[] bcd = ASCII_To_BCD(bytes, bytes.length); //如果密文长度大于模长则要分组解密 String ming = ""; byte[][] arrays = splitArray(bcd, key_len); for(byte[] arr : arrays){ ming += new String(cipher.doFinal(arr),"ISO-8859-1"); } return new String(ming.getBytes("ISO-8859-1")); }
好了,改成这样,世界清静了,干干净净的输出了中文,现在一点都不他大爷的了
附:修改好后的加解密方式代码import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.HashMap; import javax.crypto.Cipher; public class RSAUtils { /** * 生成公钥和私钥 * @throws NoSuchAlgorithmException * */ public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException{ HashMap<String, Object> map = new HashMap<String, Object>(); KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(1024); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); map.put("public", publicKey); map.put("private", privateKey); return map; } /** * 使用模和指数生成RSA公钥 * 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA * /None/NoPadding】 * * @param modulus * 模 * @param exponent * 指数 * @return */ public static RSAPublicKey getPublicKey(String modulus, String exponent) { try { BigInteger b1 = new BigInteger(modulus); BigInteger b2 = new BigInteger(exponent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2); return (RSAPublicKey) keyFactory.generatePublic(keySpec); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 使用模和指数生成RSA私钥 * 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA * /None/NoPadding】 * * @param modulus * 模 * @param exponent * 指数 * @return */ public static RSAPrivateKey getPrivateKey(String modulus, String exponent) { try { BigInteger b1 = new BigInteger(modulus); BigInteger b2 = new BigInteger(exponent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2); return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 公钥加密 * * @param data * @param publicKey * @return * @throws Exception */ public static String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 模长 int key_len = publicKey.getModulus().bitLength() / 8; // 加密数据长度 <= 模长-11 String iosDatas = new String(data.getBytes(),"ISO-8859-1"); String[] datas = splitString(iosDatas, key_len - 11); String mi = ""; //如果明文长度大于模长-11则要分组加密 for (String s : datas) { mi += bcd2Str(cipher.doFinal(s.getBytes("ISO-8859-1"))); } return mi; } /** * 私钥解密 * * @param data * @param privateKey * @return * @throws Exception */ public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); //模长 int key_len = privateKey.getModulus().bitLength() / 8; byte[] bytes = data.getBytes("ISO-8859-1"); byte[] bcd = ASCII_To_BCD(bytes, bytes.length); //如果密文长度大于模长则要分组解密 String ming = ""; byte[][] arrays = splitArray(bcd, key_len); for(byte[] arr : arrays){ ming += new String(cipher.doFinal(arr),"ISO-8859-1"); } return new String(ming.getBytes("ISO-8859-1")); } /** * ASCII码转BCD码 * */ public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) { byte[] bcd = new byte[asc_len / 2]; int j = 0; for (int i = 0; i < (asc_len + 1) / 2; i++) { bcd[i] = asc_to_bcd(ascii[j++]); bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++])) + (bcd[i] << 4)); } return bcd; } public static byte asc_to_bcd(byte asc) { byte bcd; if ((asc >= '0') && (asc <= '9')) bcd = (byte) (asc - '0'); else if ((asc >= 'A') && (asc <= 'F')) bcd = (byte) (asc - 'A' + 10); else if ((asc >= 'a') && (asc <= 'f')) bcd = (byte) (asc - 'a' + 10); else bcd = (byte) (asc - 48); return bcd; } /** * BCD转字符串 */ public static String bcd2Str(byte[] bytes) { char temp[] = new char[bytes.length * 2], val; for (int i = 0; i < bytes.length; i++) { val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f); temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); val = (char) (bytes[i] & 0x0f); temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); } return new String(temp); } /** * 拆分字符串 */ public static String[] splitString(String string, int len) { int x = string.length() / len; int y = string.length() % len; int z = 0; if (y != 0) { z = 1; } String[] strings = new String[x + z]; String str = ""; for (int i=0; i<x+z; i++) { if (i==x+z-1 && y!=0) { str = string.substring(i*len, i*len+y); }else{ str = string.substring(i*len, i*len+len); } strings[i] = str; } return strings; } /** *拆分数组 */ public static byte[][] splitArray(byte[] data,int len){ int x = data.length / len; int y = data.length % len; int z = 0; if(y!=0){ z = 1; } byte[][] arrays = new byte[x+z][]; byte[] arr; for(int i=0; i<x+z; i++){ arr = new byte[len]; if(i==x+z-1 && y!=0){ System.arraycopy(data, i*len, arr, 0, y); }else{ System.arraycopy(data, i*len, arr, 0, len); } arrays[i] = arr; } return arrays; } public static void main(String[] args) throws Exception { HashMap<String, Object> map = RSAUtils.getKeys(); //生成公钥和私钥 RSAPublicKey publicKey = (RSAPublicKey) map.get("public"); RSAPrivateKey privateKey = (RSAPrivateKey) map.get("private"); //模 String modulus = publicKey.getModulus().toString(); //公钥指数 String public_exponent = publicKey.getPublicExponent().toString(); //私钥指数 String private_exponent = privateKey.getPrivateExponent().toString(); //明文 String ming = "测试内容的法国队大飞哥rethink涂鸦跳跃冗余冗余且啊啊而同一十多分GV大飞哥地方GV的桂东尔尕尔尕前二个家已开通愉快Yui二胎温柔他为违法我 个i人退隐江湖投入和如何让晴儿尔尕安全而gargle昂贵阿尔高愉快阿尔法让人更"; //使用模和指数生成公钥和私钥 RSAPublicKey pubKey = RSAUtils.getPublicKey(modulus, public_exponent); RSAPrivateKey priKey = RSAUtils.getPrivateKey(modulus, private_exponent); String mi = encryptByPublicKey(ming, pubKey); long time = System.currentTimeMillis(); //解密后的明文 ming = decryptByPrivateKey(mi, priKey); System.out.println(System.currentTimeMillis()-time); System.out.println(ming); } }