##前言
这篇笔记是在遇到问题时一遍解决,一遍分析的。分析是为了让自己思路更加清晰,当与此同时,废话也可能会多一些。
总的来说,这次乱码问题是因为客户端对数据加密处理时涉及到了编码,而服务器端在解密还原时也涉及到了编码。即客户端加密过程中有一个操作是字符串转字节数组,即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)]
待跟进!!!