RSA 非对称加密传输的实现及分段加密

对网络安全的要求最近是越来越高,尤其是互联网上提供服务的系统,要求敏感信息(如登录用户名、密码、用户真名、手机号码)都要加密传输。

加密传输的含义是:在前端用户输入的页面上,要先用前端代码(如 JS)加密, SUBMIT 到后台来,在后台对加密串进行解密。这样的话,网络上的任何对传输流的拦截都不再产生风险,因为看到的都是一串串密文。

关键词:前端 JS 加密,后端 JAVA 代码解密。

就是说:两种技术路线的加解密算法,必须是一致的,否则不可能解密还原。听着就很难,好在大神们已经帮我们做好了,那就是 RSA 加密工具包。

到CSDN的下载资源区去找,很多:前端 JS 包 security.js ;后端是采用 org.bouncycastle.jce.provider.BouncyCastleProvider 提供的加密解密类,在包  bcprov-jdk14-145.jar 中 。

RSA 是著名的非对称加密算法,十分玄妙,利用的是数学中的“互质”概念,来达成一种“锁”和“钥匙”的效果。

前端加密和后端解密整个过程,用容易理解的通俗语言可以描述为:

1. 客户端在填写登录表单时,同时跟后端要了一把锁(公钥),并用 JS 语言使用这把锁(公钥)为自己的用户名和密码加密了;

2. 后端在响应前端的请求生成这把锁(公钥)的时候,同步生成了能开这把锁的一枚钥匙(公钥对应的私钥),自己存起来了;

3. 客户端将用户名、密码、公钥一起 SUBMIT 给后端;

4. 后端根据提交上来的公钥,找到对应的保存起来的私钥,对用户名密码解密,得到原始的客户端填写的值。

这个过程我们可以看到,解密用的私钥并没有在网络上传递,而光有传递的公钥是无法对加密串做解密的,这样就保证了安全。

非对称加密就是上锁的公钥即使被别人窃取了,但是窃锁人没有私钥,也打不开锁,没用。

明白了原理,我们来上代码:

前端 JS:

<%

         String  IModulus = "";

         String  IExponent = "";

                    

         try {

                            Map keys = RSAUtil.getKeyMap(1);

                           

                            RSAPublicKey pubKey = (RSAPublicKey)keys.get("publicKey");

                  

                            IModulus = pubKey.getModulus().toString(16);

                            IExponent = pubKey.getPublicExponent().toString(16);

                   }

                   catch (Exception e) {

                            //e.printStackTrace();

                   }

%>

如上这段就是表单页面跟后端要锁的过程,得到的公钥表现为两个值:IModulus、IExponent,将这两个值赋值给 HTML 页面的元素,以备 JS 加密时使用。

下面这段 JS 函数,就是在 SUBMIT 前对页面上输入的字符串做加密

         function RSAEncrypeValue (inValue) {

                           

                   var Modulus = document.getElementById("Modulus").value;   //加密模

                   var public_exponent =  document.getElementById("Exponent").value;                //公钥指数

                   var myPubKey = new RSAUtils.getKeyPair(public_exponent, "", Modulus);   //通过模和公钥参数获取公钥

                   var UnicodeValue = encodeURIComponent(inValue);    //转义为 UNICODE ,以兼容中英文和各种符号

                   var ReverseValue = UnicodeValue.split("").reverse().join("");      //颠倒被加密串的顺序,要不然后解密后会发现密码顺序是反的

                   var EncrypedValue = RSAUtils.encryptedString(myPubKey, ReverseValue); //对密码进行加密传输(这个加密函数,是有长度限制的,超过 20 个字符的串会被分段加密,每段之间用空格分隔,后端在解密时要做分段分别处理,并合理拼接起来)

                   return EncrypedValue;

  }

   

后端 JAVA:先实现一个秘钥工具类 RSAUtil(我用一个静态hashMap 来缓存一个秘钥对,每次应用起来后生成一对秘钥,后续就一直使用这对秘钥即可,无需每个请求都新生成秘钥对,那样会造成性能消耗)

public class RSAUtil {

         static HashMap<String,Object> cacheMap = null;

        

         static public HashMap<String,Object> getKeyMap(int size) {

                   if (cacheMap == null)  {

                            cacheMap = createKey(size);

                   }

                  

                   HashMap<String,Object> retMap = (HashMap<String,Object>) cacheMap.clone();

        

                   return retMap;

         }

         /**

          * 自动生成密钥对

          * @throws Exception

          */

         static public  HashMap<String,Object> createKey( int size){

        

                   int nSize = (size < 256) ? 256 : size;

                 try {

        

                 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());

                           

                 SecureRandom random = new SecureRandom(); 

                 keyPairGenerator.initialize(nSize, random); 

          

                 // 生成钥匙对

                 KeyPair keyPair = keyPairGenerator.generateKeyPair();

          

                 // 得到公钥

                 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();

                 // 得到私钥

                 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

                

                 HashMap<String,Object> map = new HashMap<String, Object>();

                 map.put("publicKey", publicKey);

                 map.put("privateKey", privateKey);

                

                 /* 把私钥保存到硬盘上

                 saveKey(privateKey,"C:/hyhtemp/private_key");

                    //把公钥保存到硬盘上

                 saveKey(publicKey,"C:/hyhtemp/public_key"); */

          

                 return map;

                 }

                 catch (Exception e) {

                            // TODO Auto-generated catch block

                            e.printStackTrace();

                            return null;

                 }

         }

        

         /**

          * 加密(用公钥)

          * @param publicKey 公钥

          * @param content 需要加密的内容

          * @return

          * @throws Exception

          */

         static public String encrypttoStr(Key publicKey,String content) throws Exception{

                   String endata = parseByte2HexStr( publicEnrypy (publicKey, content) );

                   return endata;

         }

        

         /**

          * 解密(用私钥)

          * @param privateKey 私钥

          * @param endata 需要解密的内容

          * @return

          * @throws Exception

          */

         static public String decrypttoStr(Key privateKey,String endata) throws Exception{

                   String data = new String(privateEncode(privateKey, parseHexStr2Byte(endata)));

                   return data;

         }

        

         static public String decrypttoStr_normal(Key privateKey,String endata) throws Exception{

                   String data = new String(privateEncode(privateKey, endata.getBytes()));

                   return data;

         }

        

                

        //

         /**

           * 加密的方法,使用公钥进行加密

           * @param publicKey 公钥

           * @param data 需要加密的数据

           * @throws Exception

           */

   private static byte[] publicEnrypy(Key publicKey,String data) throws Exception {

                             /* 这句的含义是:我们的 RSA 算法是用 org.bouncycastle.jce.provider.BouncyCastleProvider 这个类具体实现的,这个类在 JAR 包 bcprov-jdk14-145.jar 中   */

       Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider() );

       // 设置为加密模式

       cipher.init(Cipher.ENCRYPT_MODE, publicKey);

       // 对数据进行加密

       byte[] result = cipher.doFinal(data.getBytes());

      

       return result;

   }

   /**

    * 解密的方法,使用私钥进行解密

    * privateKey  私钥

    * encoData 需要解密的数据

    * @throws Exception

    */

   private  static byte[]  privateEncode(Key privateKey,byte[] encoData) throws Exception {

       Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());

        

       //设置为解密模式,用私钥解密

        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        //解密

        byte[] data = cipher.doFinal(encoData);

//        System.out.println("解密后的数据:"+data);

        return data;

   }

   

    /**将二进制转换成16进制

         * @param buf

         * @return  String

         */ 

    static private String parseByte2HexStr(byte buf[]) { 

                StringBuffer sb = new StringBuffer(); 

                for (int i = 0; i < buf.length; i++) { 

                        String hex = Integer.toHexString(buf[i] & 0xFF); 

                        if (hex.length() == 1) { 

                                hex = '0' + hex; 

                        } 

                        sb.append(hex.toUpperCase()); 

                } 

                return sb.toString(); 

         }

    /**将16进制转换为二进制

         * @param hexStr

         * @return  byte[]

         */ 

         static private  byte[] parseHexStr2Byte(String hexStr) { 

                if (hexStr.length() < 1) 

                        return null; 

                byte[] result = new byte[hexStr.length()/2]; 

                for (int i = 0;i< hexStr.length()/2; i++) { 

                        int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16); 

                        int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16); 

                        result[i] = (byte) (high * 16 + low); 

                } 

                return result; 

         } 

}

有了上述工具类,我们就可以开始对前端提交上来的加密串做解密工作了(只需要改造从 REQUEST 获取到的 USERNAME PASSWORD 即可):

public String setUserName(String strValue) {

                   String UserName = "";

                  

                   try {

                            Map keys = RSAUtil.getKeyMap(1);

                            RSAPrivateKey privateKey = (RSAPrivateKey)keys.get("privateKey");  /* 从工具类获取私钥 */

                           

                            String strCrytedValue = strValue;

                            if (Tool.getDebug())  System.out.println("解密前  strCrytedValue: " + strCrytedValue); 

                           

                           

                            /* 因为可能超长,尝试分段解密  2021-08-24  by hanyanhua */

                            String        strTemp = null;

                            String  strWhole = "";

                           

                            String[] strTempArray = strCrytedValue.split(" ");

                            if ((strTempArray != null) && (strTempArray.length > 0))  {

                                     for (int i = 0; i < strTempArray.length; i++)  {

                                               if (Tool.getDebug())  System.out.println("片段  " + i + ": /" + strTempArray[i] + "/");       

                                               strTemp = RSAUtil.decrypttoStr(privateKey, strTempArray[i] );

                                               if (Tool.getDebug())  System.out.println("片段解密后  " + i + ": /" + strTemp + "/");

                                               strWhole = strTemp + strWhole;  //注意这里的拼接顺序,因为前端加密前是做了一个reverse动作,所以段与段之间的顺序也是 reverse 的

                                     }

                            }

                            strTemp = java.net.URLDecoder.decode(strWhole,"UTF-8");

                            if (Tool.getDebug())  System.out.println("整段 UTF-8 解码后  : /" + strTemp + "/");

                           

                            UserName = strTemp.toString();

                            if (Tool.getDebug())  System.out.println("解密后 UserName: " + UserName);

                   }

                   catch (Exception e)  {

                            e.printStackTrace();

                   }        /* 用户名密码加密传输结束 */

                  

                   return UserName;

         }

总结:

  1. 如果被加密的串可能有中文,注意一定要在真正做加密前转化成 UNICODE 字符串,就是类似 %E5%D2  这样的串,如此一来,每个中文字就会产生 6 个字符;
  2.  JS 前端加密算法有长度限制,限定字符是 30 个,超过 30 字符的会自动分段,段与段之间是空格分隔。
  3. 相应的,解密端要注意分段处理,各段之间的顺序也是倒序的,拼接时注意。(尤其是中文的UNICODE是有严格顺序的,所以一定要注意)
  4. 如果被加密的是中文,5 个字以上就会超长分隔了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值