由于安全原因,需要对用户名密码做加密传输,,考虑几种方式,md5,base64,rsa,由于md5加密不可逆,为了减小代码的改动放弃,base64加密严格来说并不是用来加密的主要是用来方便数据传输的,所以采用rsa非对称加密的方式!
贴代码
java
public class Test {
public static void main(String[] args) throws Exception {
//生产秘钥对
//bouncy castle(轻量级密码术包)是一种用于 Java 平台的开放源码的轻量级密码术包
Provider provider = new BouncyCastleProvider();
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", provider);
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
//打印公钥
System.out.println(keyPair.getPublic());
//打印私钥
System.out.println(keyPair.getPrivate());
执行以上代码得到如下
其中private exponent是私钥用户后台解密不能泄露出去
RSA Public Key 中的modulus和public exponent 公钥用于交给前段js加密
前端:引入js文件 security.js
附上security.js地址 https://download.csdn.net/download/zuoyigehaizei/11329183
html页面中导入此js,以下为js加密代码
//rsa 私钥
var password = 'ceshi';
var username = 'user_01';
var key = RSAUtils.getKeyPair('10001', '', '9a12794abc04faa6f78350fe0e73403994eb58199d030477f97d6acd9825e9fc13b230edcb68741d1f87b257506e909df0926ad9c12ab99b40c40087222ed0686c133bf2f7f01c005c9b9f01a9891527afc41364e0d24ce8924237f0868fc87770397f0c4386dbfe178de40273adcf6a3de05d60dc4abd945a14ed257af6e033');
//加密后的密码
password = RSAUtils.encryptedString(key, password);
console.log("password:" + password);
//加密后的用户名
username = RSAUtils.encryptedString(key,username);
console.log("username" + username);
java后台解密
public static void main(String[] args) throws Exception {
//rsa js加密,后台java解密
//RSA Private CRT Key的modulus
String hexModulus = "9a12794abc04faa6f78350fe0e73403994eb58199d030477f97d6acd9825e9fc13b230edcb68741d1f87b257506e909df0926ad9c12ab99b40c40087222ed0686c133bf2f7f01c005c9b9f01a9891527afc41364e0d24ce8924237f0868fc87770397f0c4386dbfe178de40273adcf6a3de05d60dc4abd945a14ed257af6e033";
//RSA Private CRT Key的private exponent
String hexPrivateExponent = "5131951bee53cd67ba1e48cb6a00108387d832786507170ec6baf252e070728b7631bc99444d8a8b62775763ae2e6625e586b3aa87a2a5678cccfdde390464ad1d4e2ef44be958238f3fae52ee988b3190a6e61c62512de90393ee599fb528726d74d987eb174732224245de0dc3701ccf3596d9ad9ecb919fbbf41c8241b781";
// js页面加密后的密码和用户名
String passwrod = "03f87bada52fc13c892b39498cb06d4641eb0aefb92d7e243cdd7683c0aab29424954cb18cc53e92c589f9a1096cc8dbe5a75429321ba177f1804a5e23fd966d73dbfa5a6bbcdc6dc0cc231c342fec317c27c72cf7396677da2a424e808b30eaa173bf5c7247cefda4b98fb89e13d53f44e432f6e30948877616240bfbb647c6";
String username = "5354f3480cce53432b29fd212577951dd8b0fc6a002e440f96d7519a5233e4040fe9ad2d27d0fe8b20dfe390a1d21b0cf362818df9706c5f7f49dada15bc032aab11aa86a8d99ed41a21796936cea7e8391e5712fe64e6ccc5d66e4f1f4d3f811975615f0272eb7434f196bcf9740e389af74b0ef9ff88b14d71251f38be5234";
Provider provider = new BouncyCastleProvider();
KeyFactory keyFac = KeyFactory.getInstance("RSA", provider);
RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(hexModulus, 16), new BigInteger(hexPrivateExponent, 16));
// 生成用于解密的私钥
RSAPrivateKey pk = (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
// 解密
Cipher cipher = Cipher.getInstance("RSA", provider);
cipher.init(2, pk);
byte[] pwd = cipher.doFinal(Hex.decodeHex(passwrod.toCharArray()));
byte[] usn = cipher.doFinal(Hex.decodeHex(username.toCharArray()));
System.out.println(StringUtils.reverse(new String(pwd)));
System.out.println(StringUtils.reverse(new String(usn)));
运行结果,解密成功
题外话:rsa加解密效率比较慢,可以做一些小改动来提升效率,上代码,测试过户解密大概在3毫秒,改动之前解密一次需要200毫秒左右!
static Cipher cipher;
static RSAPrivateKey pk;
static org.slf4j.Logger log = LoggerFactory.getLogger(LogonAction.class);
//传入加密后的密码,进行解密 pwOrUn加密后的密码
public String passwordAndUsernameRsaDecode(String pwOrUn) {
try {
long l = System.currentTimeMillis();
pk = getPk();
// 解密
cipher = getCipher();
cipher.init(2, LogonAction.pk);
byte[] rawPasswordByte = cipher.doFinal(Hex.decodeHex(pwOrUn.toCharArray()));
pwOrUn = StringUtils.reverse(new String(rawPasswordByte));
long l1 = System.currentTimeMillis();
log.info("解密成功,用时" + (l1-l));
}catch (Exception e){
throw new RuntimeException("Rsa密码解密失败");
}
return pwOrUn;
}
public static synchronized RSAPrivateKey getPk()
throws Exception{
if(pk == null){
log.info("私钥为空,生成私钥");
String hexModulus = "c6e442535e1dd6968e4ccd7735299278d989cb938a2f97c1081c4e6796895a3063510592e2e90ed427d5a604428ce46391dcb2ba6b5f4a86af1347237d1de489a0dc2f68a1f9a265d1ec350fccd8a76be5004211cee5bf05a083afa17cf335871b141e5c4329f69d1a3546613e0fa7833b7a253c460e5bb0c075dacfccfd6d0d";
String hexPrivateExponent = "1ce625c15c66146a983d82cd493c95242ae35603ba73b4f810682c838d0f4bbb242d5c2bc9cee12b41bff1108b369885fadaa05f0f68bafeb915445e5cd006645eb816d9a8d89b155aeb8478a60325cf4d7e69c12b3076a4cf31b8c24530e7f13533826bc7e87ddf65b7ccc65951bd7c34238b9f08a1e8a1c2c4dd762c50309";
// 这就是上面html输出的密文
Provider provider = new BouncyCastleProvider();
KeyFactory keyFac = KeyFactory.getInstance("RSA", provider);
RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(hexModulus, 16), new BigInteger(hexPrivateExponent, 16));
// 生成用于解密的私钥
pk = (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
}
return pk;
}
public static synchronized Cipher getCipher()
throws NoSuchAlgorithmException, NoSuchPaddingException {
if (cipher == null) {
cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
}
return cipher;
}
以上改动后的方法经过线上测试存在问题:问题如下 :
java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
问题描述:加密后的密码,进行解密,会出现同样的加密密码有时会解密成功,有时解密失败的情况!解密失败时报以上错误!
查阅资料,出现此错误有两种可能性
1,加密的密码过长导致
2,多线程引起的
分析之后排除1,那么就是多线程引起的问题
原因:javax.crypto.Cipher 是有状态的,不要把 Cipher 当做一个静态变量,除非你的程序是单线程的,也就是说你能够保证同一时刻只有一个线程在调用 Cipher。否则你可能会遇到 java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block 异常。
解决方法:
public synchronized String passwordAndUsernameRsaDecode(String pwOrUn) {
try {
long l = System.currentTimeMillis();
pk = getPk();
// 解密
cipher = getCipher();
cipher.init(2, LogonAction.pk);
byte[] rawPasswordByte = cipher.doFinal(Hex.decodeHex(pwOrUn.toCharArray()));
pwOrUn = StringUtils.reverse(new String(rawPasswordByte));
long l1 = System.currentTimeMillis();
log.info("解密成功,用时" + (l1-l));
}catch (Exception e){
throw new RuntimeException("Rsa密码解密失败");
}
return pwOrUn;
}
在此方法上加上同步,解决问题。