客户端对数据加密然后通过接口传数据到服务器端解密得到的数据会乱码的问题

##前言

这篇笔记是在遇到问题时一遍解决,一遍分析的。分析是为了让自己思路更加清晰,当与此同时,废话也可能会多一些。

总的来说,这次乱码问题是因为客户端对数据加密处理时涉及到了编码,而服务器端在解密还原时也涉及到了编码。即客户端加密过程中有一个操作是字符串转字节数组,即data.getBytes();,由于没有指明编码,因此默认使用系统编码。而服务器端在解密过程中,有一个操作是字节数组转字符串,即new String(bytes),同样由于没有指明编码,因此默认系统编码。当客户端与服务器端的使用的编码不同,就产生了乱码问题。

下面开始是我遇到问题时的记录。有些分析可能不对,有些分析可能太啰嗦。但是,这就是当时我遇到问题时的一些分析。

##0704 之前说的编码乱码的问题

电子账户接口,在解决了公钥私钥问题后,还存在一个乱码问题。当时以为是传输乱码。就凭直觉认为是传输过程中导致的乱码问题。

然后,昨天早上发现了,是因为客户端和服务器的编码不同,导致的乱码问题。然后也没细想,就坚持认为是因为通过接口传输导致的乱码问题。因为我在本地签名验签时,是不会发生乱码问题的。只有在通过接口传输数据后进行验签的时候,才会发生乱码问题。

一开始以为是因为使用了RequeseBody注解来获取Json数据导致的乱码问题,后面我增加多一个中文参数,接收时发现是不会乱码的。因此就排除了是RequestBody导致的乱码问题。

然后通过logger记录关键信息发现,银联方发送前的数据和我方接口到的密文数据是一毛一样的。这就说明了并不是传输导致的乱码问题。(但是当时并能觉悟,还是卡在传输乱码的问题)

当时还是觉得,是不是因为服务器端和客户端的编码不同,导致传输的过程中出现乱码。就是说在客户端是utf8的,然后传输过去之后,服务器端用gbk去解码,就导致了乱码。因为我用了下面的代码去测试,就更坚信这个观点了:

public static void main(String[] args)throws Exception {
	String data = "樟大桥";
	System.out.println(new String(data.getBytes(),"GBK"));
}

输出结果是:

妯熷ぇ妗�

这和服务器的乱码是一样的:

验签失败,验签源串(即key1=value1&key2=value2串,看看是不是乱码了)为:
card_expir_dt=2512&cardholder_auth_inf=0AM0010010110000000006妯熷ぇ妗?0

我就觉得是传过来是utf8编码,但是服务器端解码是用gbk的。所以导致的问题。

可是,我又传了一个中文字符,服务器端接收时并打印出来的数据是不会出现乱码的啊。。。(再证实一次。确实不会乱码啊)

这就很郁闷了。。。

这是为什么。。。

为什么我客户端是utf8编码,而服务器是gbk,传过去的数据不会出现乱码???因为我传输的不是字节码,是直接传输字符串,所以不会乱码???但是计算机怎么能识别字符串???传输应该是以字节码传输的啊。

留个坑!!
留个坑!!
留个坑!!
留个坑!!
留个坑!!

##new String时使用了默认编码导致的乱码

传过去的参数和接收到的参数是一样的,因为肯定不是传输过程导致的乱码。

那肯定是服务器端在解析的时候出现的乱码。然后找到出现乱码的这个地方:

try {
    desKey = RSAUtils.decryptByPrivateKey(tripleDesKey, HttpServer.myPrivateKey);//用私钥解密
    jsonBODYStr = TripleDesUtil.decryptMode(desKey, encodeBODY);
    logger.info("通过私钥解密,获取明文的会话秘钥成功。再用会话秘钥对称解密得到BODY,BODY包含body和header");
} catch (Exception e) {
    logger.error("用私钥解密,获取明文的会话秘钥出现异常:" + e);
    throw new Exception("x001");
}

打印jsonBODYStr是乱码的,那么就看看它是怎么生成的:

     public static String decryptMode(String passWd, String decpStr) {      
         try {
             SecretKey deskey = new SecretKeySpec(getPaddingPwd(passWd), Algorithm);
             Cipher cipher = Cipher.getInstance(TRANSFORM);
             cipher.init(Cipher.DECRYPT_MODE, deskey);    //初始化为解密模式
             byte tmpByte[] = cipher.doFinal(Base64Utils.decode(decpStr));
             
             int len = 0;
             for(int i = 0; i < tmpByte.length; i++) {
            	 if(tmpByte[i] == '\0')
            		 break;
            	 len++;
             }
             
             byte decByte[] =  new byte[len];
             System.arraycopy(tmpByte, 0, decByte, 0, len);
            
             String originalString = new String(decByte);
             return originalString;
         } catch (java.security.NoSuchAlgorithmException e1) {
             e1.printStackTrace();
         } catch (javax.crypto.NoSuchPaddingException e2) {
             e2.printStackTrace();
         } catch (java.lang.Exception e3) {
             e3.printStackTrace();
         }
         return null;
      }

然后我在想是不是String originalString = new String(decByte);这里使用了默认编码,导致的乱码。然后改成:new String(decByte,"UTF-8"),再测试,打印,发现还是会验签失败,但是不会乱码了!!!

这里还是有问题没搞明白。这里应该都没有指定编码,那么应该都是同一种编码,为什么会出现生成utf8编码的字节码,然后还原字符串时却用了系统默认的gbk编码来还原。

不懂。还是说我的分析都是错的。

然后,都已经正常了不乱码了,为什么还是会验签失败,因为我改了内容,我没改啊。只是指定了编码而已啊。

再打印一次日志,发现签名串和待签串是一样的啊:

银联:
abc=abc&acp_loc_tm=1636480113&acq_ins_id_cd=00010004&card_acp_id=F42012345678912&card_acp_loc=F43zhangjiangsoftwarepark&card_acp_term_id=F4112345&card_expir_dt=2512&cardholder_auth_inf=01000000000000090030CUP454CUPAM0010010110000000006樟大桥01113811111111&cups_reserved_data=516S220000045000400000000000000000000000¤cy_cd=156&fwd_ins_id_cd=00010004&mchnt_type=7011&normal_reserved_data=acct_holder_tp=-&acct_tp=05&card_level=&elec_trans_in=00&ic_app_tp=0&ic_cond_cd=0&ic_very_in=0&inter_method=0&no_pass_in=1&partial_amount_inf=0&reason_cd=0000&service_reserved=0&special_charge_level=0&special_charge_tp=00&term_ability=0&term_tp=03&trans_media=1&trans_method=1&trans_reserved=000&pos_entry_cd=021&pos_service_cd=00&pri_acct_no=192222222200000001137&process_cd=000000&rcv_ins_id_cd=00040005&retrivl_ref_no=000000014371&sec_ctrl_inf=20&settle_dt=0404&sys_tra_no=014377&track_data=096558990000000000001=1561560000000000000003976999236000002070000000000000000000000=000000000000=0003458990000000000001=0509567890123456&trans_at=000000000100&trans_dt=0118094748

tUDeOeI+TzlZXQY26eUF5JRGG8eNWsX278UPpoaGhAcExo/O8cOd0Zssl/NYpSZCEJi94RQxCztfEGekjkrstcbAX+3FGNTB9wMRTi5ZVP++T+mLz4DFA7IrIzMEIaaQTt45n9JpzZ9LSd5JXXVBa0+tWLDJvoyllj7qpL88lnZNHeOKMcMl85ePizO0HbOh3TTR9UWgbhu3/zX4d0Wz4aNKM2BQ9kIlPCPTvYskDv6af+e8sRzNQpUPDr9tik1Lqu4sxaBlc7y11oTMzJBzNJ80+VI0zXV7Um6mG+zFNHV25G4cW5Ir8N72ei40gM5TmbovLFAT4J6AbyAw6TDrfQ==

我方:
abc=abc&acp_loc_tm=1636480113&acq_ins_id_cd=00010004&card_acp_id=F42012345678912&card_acp_loc=F43zhangjiangsoftwarepark&card_acp_term_id=F4112345&card_expir_dt=2512&cardholder_auth_inf=01000000000000090030CUP454CUPAM0010010110000000006樟大桥01113811111111&cups_reserved_data=516S220000045000400000000000000000000000¤cy_cd=156&fwd_ins_id_cd=00010004&mchnt_type=7011&normal_reserved_data=acct_holder_tp=-&acct_tp=05&card_level=&elec_trans_in=00&ic_app_tp=0&ic_cond_cd=0&ic_very_in=0&inter_method=0&no_pass_in=1&partial_amount_inf=0&reason_cd=0000&service_reserved=0&special_charge_level=0&special_charge_tp=00&term_ability=0&term_tp=03&trans_media=1&trans_method=1&trans_reserved=000&pos_entry_cd=021&pos_service_cd=00&pri_acct_no=192222222200000001137&process_cd=000000&rcv_ins_id_cd=00040005&retrivl_ref_no=000000014371&sec_ctrl_inf=20&settle_dt=0404&sys_tra_no=014377&track_data=096558990000000000001=1561560000000000000003976999236000002070000000000000000000000=000000000000=0003458990000000000001=0509567890123456&trans_at=000000000100&trans_dt=0118094748

tUDeOeI+TzlZXQY26eUF5JRGG8eNWsX278UPpoaGhAcExo/O8cOd0Zssl/NYpSZCEJi94RQxCztfEGekjkrstcbAX+3FGNTB9wMRTi5ZVP++T+mLz4DFA7IrIzMEIaaQTt45n9JpzZ9LSd5JXXVBa0+tWLDJvoyllj7qpL88lnZNHeOKMcMl85ePizO0HbOh3TTR9UWgbhu3/zX4d0Wz4aNKM2BQ9kIlPCPTvYskDv6af+e8sRzNQpUPDr9tik1Lqu4sxaBlc7y11oTMzJBzNJ80+VI0zXV7Um6mG+zFNHV25G4cW5Ir8N72ei40gM5TmbovLFAT4J6AbyAw6TDrfQ==

刚刚,服务器端之所以验签失败,是因为,我刚刚把服务器的公钥配成了正式环境的公钥,忘了换过来,所以即使数据一致,验签也失败。

现在改过来了,服务器端验签成功了!!但是返回数据后,银联方验签失败了。靠。

为什么在jsonBODYStr = TripleDesUtil.decryptMode(desKey, encodeBODY);decryptMode方法中的new String(decByte,"UTF-8");中指定utf8编码,就能验签成功。

我的意思是为什么这个decByte是utf8编码的。对啊,为什么。就是这个字节码是utf8类型的,默认的编码是gbk,所以导致乱码的出现。

##分析

乱码的字符串是解密后的body,解密代码为:

jsonBODYStr = TripleDesUtil.decryptMode(desKey, encodeBODY);

在解密之后,jsonBODYStr中的中文就是乱码的了。而参数desKey和encodeBODY无论在客户端还是在服务器都是一样的。而在客户端执行解密,却是不会乱码的。因此,我觉得问题应该是在decryptMode中,里面应该涉及到了编码的问题。

decryptMode方法为:

 
     public static String decryptMode(String passWd, String decpStr) {      
         try {
             SecretKey deskey = new SecretKeySpec(getPaddingPwd(passWd), Algorithm);
             Cipher cipher = Cipher.getInstance(TRANSFORM);
             cipher.init(Cipher.DECRYPT_MODE, deskey);    //初始化为解密模式
             byte tmpByte[] = cipher.doFinal(Base64Utils.decode(decpStr));
             
             int len = 0;
             for(int i = 0; i < tmpByte.length; i++) {
            	 if(tmpByte[i] == '\0')
            		 break;
            	 len++;
             }
             byte decByte[] =  new byte[len];
             System.arraycopy(tmpByte, 0, decByte, 0, len);
             String originalString = new String(decByte);
             return originalString;
         } catch (java.security.NoSuchAlgorithmException e1) {
             e1.printStackTrace();
         } catch (javax.crypto.NoSuchPaddingException e2) {
             e2.printStackTrace();
         } catch (java.lang.Exception e3) {
             e3.printStackTrace();
         }
         return null;
      }

首先,decpStr即密文body是没问题的。因为客户端和服务器获取到的密文body是一样的。所以,不存在说客户端body加密成密文body时涉及到了编码。

不对!!!!!是有可能的!!!!虽然在客户端和服务器端获取到的密文body是一样的,这只能说明传输没问题啊。

我试下,让客户端的默认环境为gbk,然后看看输出的密文是否和环境为uf8时的一样。如果不一样,就说明涉及到了编码问题:

gbk
Ztj/vryMatbBBhUF4IA5C2Jn6DN+im4dLATCsAn/q63CwOBZ2bUoIkE87CHftuR3luZxbqibPynz5LTZaBfCc0XgAPow7B3/OovSEGaqCajvSNZt9JlA6rU1EFGTIuCv2nqyRecNsBfmvMMQRQlKKi4TruzQ9TjnruA44Rl0vJr3SlGSG7dNGYPJJ6DapN1IHbrQqQLP9jgel2MQd9NadNIWGv/bO0EI5R8BIAnS7OusmRYq2nGz5C5Vbk5cf2wMmG+xAjYl/yiWfm4MG9nMk/4kLEAk9glhAyq5WyK3LWz6GL5WHeWMketPXFqCEQfrcFv17Wu3lzbaBTDQPwwyNA==

utf8
tUDeOeI+TzlZXQY26eUF5JRGG8eNWsX278UPpoaGhAcExo/O8cOd0Zssl/NYpSZCEJi94RQxCztfEGekjkrstcbAX+3FGNTB9wMRTi5ZVP++T+mLz4DFA7IrIzMEIaaQTt45n9JpzZ9LSd5JXXVBa0+tWLDJvoyllj7qpL88lnZNHeOKMcMl85ePizO0HbOh3TTR9UWgbhu3/zX4d0Wz4aNKM2BQ9kIlPCPTvYskDv6af+e8sRzNQpUPDr9tik1Lqu4sxaBlc7y11oTMzJBzNJ80+VI0zXV7Um6mG+zFNHV25G4cW5Ir8N72ei40gM5TmbovLFAT4J6AbyAw6TDrfQ==

是不一样的!!!!!所以,就是这个问题!!!

所以,问题就是出在客户端加密body的时候涉及到了编码,使用的是系统默认编码,而服务器端在解密时也是用系统默认编码,因此当两边系统环境的编码不一样时,就出现了乱码问题。

加密方法:

     public static String encryptMode(String passWd, String encpStr) {
         try {
        	 SecretKey deskey = new SecretKeySpec(getPaddingPwd(passWd), Algorithm);    //生成密钥
             Cipher cipher = Cipher.getInstance(TRANSFORM);    //实例化负责加密/解密的Cipher工具类
             byte tmpByte[] = encpStr.getBytes();
             /* 采用zero-padding的方式进行补全  */
        	 int blockSize = cipher.getBlockSize();
             int len = tmpByte.length;
             if (len % blockSize != 0) {
                 len = len + (blockSize - (len % blockSize));
             }

             byte[] encByte = new byte[len];
             Arrays.fill(encByte, (byte)('\0'));

             System.arraycopy(tmpByte, 0, encByte, 0, tmpByte.length);
             cipher.init(Cipher.ENCRYPT_MODE, deskey);    //初始化为加密模式
             return Base64Utils.encode(cipher.doFinal(encByte));
          } catch (java.security.NoSuchAlgorithmException e1) {
              e1.printStackTrace();
          } catch (javax.crypto.NoSuchPaddingException e2) {
              e2.printStackTrace();
          } catch (java.lang.Exception e3) {
              e3.printStackTrace();
          }
          return null;
      }

在这一步:byte tmpByte[] = encpStr.getBytes();

没有指定编码,因此就是使用系统默认编码,因此生成的就是客户端环境的编码的字节码。即生成的密文body就是跟系统编码有关的密文字符串。

而在服务器端解析时,同样,当通过密文字符串解密还原成字节码时,此时是客户端系统编码的字节码,当你用另一种编码去解,即执行new String(bytes)时,就会出现乱码问题。

终于!!!解决了!!!!!

##感想
冷静分析!!!不要靠自觉啊!!!!

##疑问

那么,生产环境中是怎么处理这个问题的呢???

在接口文档中,说如果value包含中文,则要进行gb18030编码就是为了解决这个问题吗:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhOw6qZ6-1572401111761)(https://i.imgur.com/Kf8iZoY.png)]

待跟进!!!

你可能感兴趣的:(工作总结,记录)