最近公司一直提及网络安全,特别是密码传输安全,由于LZ一直给运营商做web平台,切均为内网访问,所以也一直未使用https管理用户登陆和密码重置页面。首先声明若想做到彻底的密码安全,https是必需的,可以防止传输过程中的抓包窃取行为,从根本上解决密码泄露问题。LZ本次尝试的js端加密,java端解密的方式,只能从一定意义上实现密码安全传输,不能防止恶意模仿http进行请求操作。
LZ本次使用的是RSA非对称加密算法,该算法需要生成一个秘钥对,称为公钥和私钥,发送方使用公钥加密,接收方使用私钥解密,只要私钥不泄露,采用1024位方式生成的秘钥加密,几乎是不可能破解的,更多该加密算法详情,请自行了解。js端加解密,参考附件jsencrypt.zip,不做赘述,使用比较简单。RSA秘钥对的生成,使用OpenSSL工具1024位方式生成,网上亦有资料可供参考。
仅贴出java端代码供大家参考使用,其中public_key.txt和private_key.txt分别存放OpenSSL生成的公私钥信息,方便管理,同时此类需要引用第三方jar包bcprov-jdk14-138.jar,版本需自己选定。
package com.ai.wyw.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
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.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.misc.BASE64Decoder;
public final class RSAUtils
{
/**
* openSSL生成的公钥串
*/
private static final String DEFAULT_PUBLIC_KEY = "";
/**
* openSSl生成的公钥串
*/
private static final String DEFAULT_PRIVATE_KEY = "";
/**
* 私钥
*/
private static RSAPrivateKey privateKey;
/**
* 公钥
*/
private static RSAPublicKey publicKey;
/**
* 字节数据转字符串专用集合
*/
private static final char[] HEX_CHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* 私有构造方法
*/
private RSAUtils()
{
}
/**
* 获取私钥
*
* @return 当前的私钥对象
*/
public static RSAPrivateKey getPrivateKey()
{
return privateKey;
}
/**
* 获取公钥
*
* @return 当前的公钥对象
*/
public static RSAPublicKey getPublicKey()
{
return publicKey;
}
/**
* 随机生成密钥对
*/
public static void genKeyPair()
{
KeyPairGenerator keyPairGen = null;
try
{
keyPairGen = KeyPairGenerator.getInstance("RSA");
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
privateKey = (RSAPrivateKey) keyPair.getPrivate();
publicKey = (RSAPublicKey) keyPair.getPublic();
}
/**
* 从文件中输入流中加载公钥
*
* @param in
* 公钥输入流
* @throws Exception
* 加载公钥时产生的异常
*/
public static void loadPublicKey(InputStream in) throws Exception
{
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String readLine = null;
StringBuilder sb = new StringBuilder();
while ((readLine = br.readLine()) != null)
{
if (readLine.charAt(0) == '-')
{
continue;
}
else
{
sb.append(readLine);
sb.append('\r');
}
}
loadPublicKey(sb.toString());
}
catch (IOException e)
{
throw new Exception("公钥数据流读取错误");
}
catch (Exception e)
{
throw new Exception("公钥输入流为空");
}
}
/**
* 从字符串中加载公钥
*
* @param publicKeyStr
* 公钥数据字符串
* @throws Exception
* 加载公钥时产生的异常
*/
public static void loadPublicKey(String publicKeyStr) throws Exception
{
try
{
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] buffer = base64Decoder.decodeBuffer(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
catch (NoSuchAlgorithmException e)
{
throw new Exception("无此算法");
}
catch (InvalidKeySpecException e)
{
throw new Exception("公钥非法");
}
catch (IOException e)
{
throw new Exception("公钥数据内容读取错误");
}
catch (Exception e)
{
throw new Exception("公钥数据为空");
}
}
/**
* 从文件中加载私钥
*
* @param keyFileName
* 私钥文件名
* @return 是否成功
* @throws Exception
*/
public static void loadPrivateKey(InputStream in) throws Exception
{
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String readLine = null;
StringBuilder sb = new StringBuilder();
while ((readLine = br.readLine()) != null)
{
if (readLine.charAt(0) == '-')
{
continue;
}
else
{
sb.append(readLine);
sb.append('\r');
}
}
loadPrivateKey(sb.toString());
}
catch (IOException e)
{
throw new Exception("私钥数据读取错误");
}
catch (NullPointerException e)
{
throw new Exception("私钥输入流为空");
}
}
public static void loadPrivateKey(String privateKeyStr) throws Exception
{
try
{
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] buffer = base64Decoder.decodeBuffer(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}
catch (NoSuchAlgorithmException e)
{
throw new Exception("无此算法");
}
catch (InvalidKeySpecException e)
{
throw new Exception("私钥非法");
}
catch (IOException e)
{
throw new Exception("私钥数据内容读取错误");
}
catch (Exception e)
{
throw new Exception("私钥数据为空");
}
}
/**
* 加密过程
*
* @param publicKey
* 公钥
* @param plainTextData
* 明文数据
* @return
* @throws Exception
* 加密过程中的异常信息
*/
public static byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception
{
if (publicKey == null)
{
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData);
return output;
}
catch (NoSuchAlgorithmException e)
{
throw new Exception("无此加密算法");
}
catch (NoSuchPaddingException e)
{
e.printStackTrace();
return null;
}
catch (InvalidKeyException e)
{
throw new Exception("加密公钥非法,请检查");
}
catch (IllegalBlockSizeException e)
{
throw new Exception("明文长度非法");
}
catch (Exception e)
{
throw new Exception("明文数据已损坏");
}
}
/**
* 解密过程
*
* @param privateKey
* 私钥
* @param cipherData
* 密文数据
* @return 明文
* @throws Exception
* 解密过程中的异常信息
*/
public static byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception
{
if (privateKey == null)
{
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
return output;
}
catch (NoSuchAlgorithmException e)
{
throw new Exception("无此解密算法");
}
catch (NoSuchPaddingException e)
{
e.printStackTrace();
return null;
}
catch (InvalidKeyException e)
{
throw new Exception("解密私钥非法,请检查");
}
catch (IllegalBlockSizeException e)
{
throw new Exception("密文长度非法");
}
catch (Exception e)
{
throw new Exception("密文数据已损坏");
}
}
/**
* 字节数据转十六进制字符串
*
* @param data
* 输入数据
* @return 十六进制内容
*/
public static String byteArrayToString(byte[] data)
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < data.length; i++)
{
// 取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移
stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]);
// 取出字节的低四位 作为索引得到相应的十六进制标识符
stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);
if (i < data.length - 1)
{
stringBuilder.append(' ');
}
}
return stringBuilder.toString();
}
public static void main(String[] args)
{
// rsaEncrypt.genKeyPair();
// 加载秘钥
try
{
//rsaUtils.loadPublicKey(RSAUtils.DEFAULT_PUBLIC_KEY);
//rsaUtils.loadPrivateKey(RSAUtils.DEFAULT_PUBLIC_KEY);
RSAUtils.loadPublicKey(new FileInputStream(new File("public_key.txt")));
RSAUtils.loadPrivateKey(new FileInputStream(new File("private_key.txt")));
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
// 测试字符串
String encryptStr = 1234!@#$;
try
{
// 加密
byte[] cipher = RSAUtils.encrypt(RSAUtils.getPublicKey(), encryptStr.getBytes());
// 解密
byte[] plainText = RSAUtils.decrypt(RSAUtils.getPrivateKey(), cipher);
System.out.println("密文长度:" + cipher.length);
System.out.println(RSAUtils.byteArrayToString(cipher));
System.out.println("明文长度:" + plainText.length);
System.out.println(RSAUtils.byteArrayToString(plainText));
System.out.println(new String(plainText));
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}
LZ看过一些关于密码安全的书籍,采用md5withRSA的数字签名的方式,可以很好的解决此类问题,首先使用md5方式生成内容摘要,对内容+salt进行公钥加密,一起发送后台,对密文内容私钥解密后去salt生成md5摘要,对比前台发送的md5摘要值,确保内容是否伪造,对此LZ没有进行实现,但的确是一个完善的安全策略。