最近公司一直提及网络安全,特别是密码传输安全,由于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没有进行实现,但的确是一个完善的安全策略。