首页 > 其他分享 >对接银行支付,自己的demo可以调通,放到项目里,却总提示验签失败。原来竟是因为...

对接银行支付,自己的demo可以调通,放到项目里,却总提示验签失败。原来竟是因为...

时间:2024-04-28 20:12:34浏览次数:30  
标签:... 编码 String 字符 demo charset 字符集 调通 byte

原因是 字符集(charset)不一致

对接一个银行支付通道的支付API,自己java写的demo可以调通,放到项目工程里,部署到环境上,总是收到验签失败的响应。这个问题,困扰我们的开发大兄弟长达一个星期。

对接通道接口联调不通,常见的场景有许多,如:

  • 签名原串需要对key进行排序。不同的排序算法会导致联调不通。
  • json序列化,不同json序列化对数字的支持不同。可能会导致联调不通。
  • 参数大小写拼写错误,会导致联调不通。
  • 等等。

而这次呢,却是字符编码导致的。

各个加密算法,都是基于字节数据进行加密的。例如,下面的md5加密工具方法,在使用MD5算法加密时,首先要把程序中的字符串转换为byte[],见下方代码中的 text.getBytes()。

public static String md5(String text)
        throws NoSuchAlgorithmException, UnsupportedEncodingException {
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] digest = md.digest(text.getBytes());
    StringBuilder sb = new StringBuilder();
    for (byte b : digest) {
        sb.append(String.format("%02x", b & 0xff));
    }
    return sb.toString();
}

上面代码调用 String#getBytes将字符串转换为byte数组。而在这个String与byte[]的转换中,涉及到一个很重要的东西————字符编码。对于相同的字符串,不同的编码格式,得到的结果可能不同,对于中文汉字来说正是如此。


其实,技术点就可以简化为如何来理解 String#getBytes()、String#getBytes(charsetName)。与之对应的是 String的构造器String(byte[])、String(byte[], charsetName) 。这就是 String 与 byte数组 的数据互转。我们看一下它们的源码:

// - - - - - 构造器的重载  - - - - - /**  * Constructs a new String by decoding the specified array of bytes using the platform's default charset. The length of the new {@code String} is a function of the charset, and hence may not be equal to the length of the byte array.  *  * @since JDK1.1  */ public String(byte bytes[]) {     this(bytes, 0, bytes.length); }   /**  * Constructs a new {@code String} by decoding the specified array of bytes using the specified charset. The length of the new {@code String} is a function of the charset, and hence may not be equal to the length of the byte array.  *  * @param  bytes - The bytes to be decoded into characters  * @param  charsetName - The name of a supported {@linkplain java.nio.charset.Charset charset}  * @throws  UnsupportedEncodingException - If the named charset is not supported  *  * @since  JDK1.1  */ public String(byte bytes[], String charsetName)         throws UnsupportedEncodingException {     this(bytes, 0, bytes.length, charsetName); }   // - - - - - getBytes方法的重载  - - - - -   /**  * Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array.  *  * @since JDK1.1  */ public byte[] getBytes() {     return StringCoding.encode(value, 0, value.length); }   /**  * Encodes this {@code String} into a sequence of bytes using the named charset, storing the result into a new byte array.  * (使用指定的字符集将此字符串编码为字节序列,并将结果存储到一个新的字节数组中。)  *  * @param  charsetName - The name of a supported {@linkplain java.nio.charset.Charset charset}  * @return  The resultant byte array  * @throws  UnsupportedEncodingException - If the named charset is not supported  *  * @since  JDK1.1  */ public byte[] getBytes(String charsetName)         throws UnsupportedEncodingException {     if (charsetName == nullthrow new NullPointerException();     return StringCoding.encode(charsetName, value, 0, value.length); }

其中,在两个没有charset参数的String(byte bytes[])、getBytes()方法里,均会获取JVM默认字符集。String csn = Charset.defaultCharset().name();。我们来看一下java.nio.charset.Charset#defaultCharset源码:

/**  * Returns the default charset of this Java virtual machine.  * The default charset is determined during virtual-machine startup and typically depends upon the locale and charset of the underlying operating system.  *  * @return  A charset object for the default charset  *  * @since 1.5  */ public static Charset defaultCharset() {     if (defaultCharset == null) {         synchronized (Charset.class) {             String csn = AccessController.doPrivileged(                 new GetPropertyAction("file.encoding"));             Charset cs = lookup(csn);             if (cs != null)                 defaultCharset = cs;             else                 defaultCharset = forName("UTF-8");         }     }     return defaultCharset; }

 

字符集的选择在字符串和字节数组之间的转换中非常重要,特别是当涉及到非ASCII字符时。确保在转换过程中使用一致的字符集,才能正确地保留和还原字符串的内容。我们通过下面的代码来直观地感受一下区别。当 text 里包含 汉字时,不同的字符集在编码时使用不同的编码方式和字节数,编码后的结果就会有所不同; 当我们修改一下 `text = "I like 3 things in this world.";` 时,由于文本中只包含 ASCII 字符,UTF-8、GB2312 和 ISO-8859-1 都使用相同的编码来表示 ASCII 字符,因此最终的字节长度都是相同的。这就是上面String构造器javadoc里的“The length of the new {@code String} is a function of the charset, and hence may not be equal to the length of the byte array.”这句话的含义。

String text = "我是中国人"; System.out.println(text1.getBytes("ASCII").length); // 返回:5 System.out.println(text.getBytes("UTF-8").length); //返回:15 System.out.println(text.getBytes("GB2312").length); //返回:10 System.out.println(text.getBytes("ISO-8859-1").length); //返回:5

 

由上面java.nio.charset.Charset#defaultCharset源码可以看到,Java的默认字符编码通常是平台的默认编码,这个取决于操作系统。例如,在中文Windows系统上,它可能是GBK或GB2312。

Tomcat默认的字符编码是ISO-8859-1。
我们这位开发大兄弟在对接银行通道时,使用java编写的demo,用到的字符集是 GB2312, 而项目是部署到tomcat容器里的, 两者字符集不同。 所以,出现一个行而另一个不行就不难理解了。

 

Base64区分字符集吗?

Base64不区分字符集。 下面两点,可以帮助你理解。
Base64 encode 和 decode 都是基于byte[]进行编码,返回的也是byte[]。
再一点,Base64 编码表使用固定的字符集,包括大小写字母、数字和两个额外的字符作为填充。 我们常见的字符集有 ASCII、Unicode、UTF-8、ISO-8859-1、GBK、GB2312等,其中ASCII是最早的字符集,它定义了 128 个包括字母、数字和一些特殊字符的编码。其他那些字符集均兼容ASCII(重点)。

 

下图进一步帮助你来直观地理解。

 

由图中可以看到, base64本身的编码和解码都是针对ASCII编码的byte[]数据进行操作,因此,不涉及字符集。 可能存在问题的,则是我们程序的原始字符串 text。 text.getByte(charset) 与 最后的 new String(byte[], charset) ,当其中两个的 charset 不一致时, 结果就会不一致。 以下面代码为例,执行后可以发现 afterText 与 text 的内容不同了。 归根结底, 这里的技术点依然是上面的 字节数据 与 字符串 的转换。

String text = "我是中国人i love China"; String afterText = new String(text.getBytes(StandardCharsets.ISO_8859_1),"UTf-8");

 

关于base64解码,rt.jar 里的 java.util.Base64.Decoder类里,有如下两个重载方法。其中第一个重载里, 默认使用了 ISO-8859-1 对字符串进行编码。

public byte[] decode(String src) {     return decode(src.getBytes(StandardCharsets.ISO_8859_1)); } public byte[] decode(byte[] src) {     ... }

 

字符 / 字节 / 字符集 ,傻傻分不清?

字符和字节是表示数据的不同表现形式。

字符(Character):字符是指文本中的单个字符,例如字母、数字、标点符号、汉字、特殊符号(如拉丁字母)等。在计算机中,字符通常使用Unicode字符集进行表示。在Java中,表示一个字符使用`char`类型。表示一个字符序列,可以使用String、StringBuilder,它们实现了相同的接口 CharSequence。

字节(Byte):字节是计算机中存储数据、传输的最小单位。一个字节由8个二进制位组成,可以表示从0到255之间的整数。在计算机中,所有数据需要以字节的形式进行存储和传输。

上面提到的,我们编码中使用的是文本字符(character)数据,而数据传输和存储使用字节(byte)的形式。那么,就需要在这两种数据形式之间做数据转换,即字符数据的编码和解码(codec),codec中就涉及到了字符集(charset)。

字符集(Character Set):字符集是一套字符的集合,每个字符在字符集中都有一个唯一的编码值。字符集定义了字符与字节之间的映射关系。常见的字符集包括ASCII、UTF-8、UTF-16、ISO-8859-1等。

在字符串编码和解码过程中,字符集起到了关键的作用。正确选择和匹配字符集是确保字符能够正确存储和传输的关键。

编码(Encoding):编码是将字符转换为字节序列的过程。编码方案根据字符集的定义来确定如何将字符映射为字节。

解码(Decoding):解码是将字节序列转换回字符的过程。解码方案根据字符集的定义来确定如何将字节映射回字符。

 

字符集小常识

字符集(charset)是一种规定了字符与二进制数据之间对应关系的编码方案。它定义了如何将字符映射到二进制表示形式,以便计算机能够存储、处理和传输文本数据。

字符集中的字符可以是字母、数字、符号以及其他特定语言或地区的特殊字符。常见的字符集包括 ASCII、Unicode、UTF-8、ISO-8859-1 等。

ASCII(American Standard Code for Information Interchange)是最早的字符集,定义了 128 个字符的编码,包括基本的拉丁字母、数字和一些特殊字符。然而,ASCII 只适用于英语和一些西欧语言。

为了满足全球范围内的多语言需求,Unicode 被引入,它定义了几乎所有语言的字符集。Unicode 使用唯一的编码值来表示每个字符,其编码空间非常大。

UTF-8(Unicode Transformation Format-8)是一种对 Unicode 进行编码的方式,它使用变长编码来表示字符。UTF-8 是目前最常用的字符集编码方式之一,它兼容 ASCII,并支持各种语言的字符表示。

ISO-8859-1(Latin-1)是一种单字节字符集,它是 ASCII 的扩展,包含了西欧语言的字符。它是许多早期计算机系统默认的字符集编码。

GBK(GuoBiao KangXi)和 GB2312(GuoBiao 2312) 是中国国家标准的字符集,用于表示中文字符。它们都是在 ASCII 的基础上进行扩展的,保留了 ASCII 字符的编码,并添加了更多的中文字符。

字符集的选择取决于所处理数据的特定需求。在进行文本处理、编码转换、网络通信等操作时,正确地理解和使用字符集非常重要,以确保数据的正确性和互操作性。

 

 

ref:
本文物料素材

Base64编码详解

标签:...,编码,String,字符,demo,charset,字符集,调通,byte
From: https://www.cnblogs.com/buguge/p/18164408

相关文章

  • 为什么vue打印的对象在浏览器中显示...
    1.现象当在vue中打印对象的时候会发现有一些属性或者全部属性都是显示的...,点击展开后才能看到真正的值是什么.2.原因因为在vue中对象都是用了代理重写了get,由于get重写也就导致了浏览器不能直接获取到具体的值,因此才会在打印的时候为...,手动点击展开才显示具体的值......
  • Go语言实现多协程文件上传,断点续传--demo
    packagemainimport("fmt""io""os""regexp""strconv""sync""github.com/qianlnk/pgbar")/***需求:1.多协程下载文件2.断点续连**/funcmain(){//获取要下载文件DownloadFileName:=&quo......
  • WPF所有原生空间使用demo
    //前台窗体<Windowx:Class="WpfTestDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.......
  • 芯科SiWx917学习笔记:1-测试Out of Box Demo
    实验目的:测试OutofBoxDemo实验环境:SimplicityStudioV5实验器材:WirelessStarterKitMainboard(BRD4002ARevA06)+ SiWG917SingleBandWi-FiandBLE8MBFlashRadioBoard(BRD4338ARevA01)实验开始:1.新建工程:在demos中找到OutofBoxDemo(SoC)应用演示工程......
  • Vue3 简单登录管理页面Demo
    目录前言项目基础配置新建项目引入组件项目配置Vue项目配置项目基本结构基础页面布局和路由搭建新增页面,简单跳转LoginViewMainViewrouterApp嵌套路由Test[1-4]Layout.vuerouter给个简单的跳转路由守护,重定向,动态路由,路由传值。这里不做展开描述简单登录页面:烂尾了总结前言这里......
  • 没有对应芯片手册,不知道哪些IO口可以控,测试demo
     //sdk\apps\earphone\include\app_config.h//////////↓↓↓↓↓↓↓↓↓↓codesnippetfromxwh↓↓↓↓↓↓↓↓↓↓////////////////////#defineLED0_IOIO_PORTA_01#defineLED0_ONOFF(x)do{gpio_set_pull_down(LED0_IO,0);\gpio_set......
  • WPF 使用 ManipulationDemo 工具辅助调试设备触摸失效问题
    本文将和大家介绍我所在的团队开源的ManipulationDemo工具。通过ManipulationDemo工具可以提升调试设备触摸失效的效率此工具在GitHub上完全开源,请看https://github.com/dotnet-campus/ManipulationDemo/软件界面效果大概如下可以显示接收到的Win32消息、当前的触摸......
  • 【Java注解】自定义注解的简单demo
    需求场景对于特定字段进行脱敏实现步骤首先创建注解@interface1importjava.lang.annotation.ElementType;2importjava.lang.annotation.Retention;3importjava.lang.annotation.RetentionPolicy;4importjava.lang.annotation.Target;56@Retention(Reten......
  • Mysql 密码报错 You must reset your password ... 和 Your password does N
    如果MySQL数据库用户的密码设置过于简单,数据库在用户登录后会提示重置密码,并且不接受简单的密码。提示需要重置密码:ERROR1820(HY000):YoumustresetyourpasswordusingALTERUSERstatementbeforeexecutingthisstatement.Mysql数据库版本:5.7.1操作系统:CentOS7这......
  • 策略模式优化if...else
    场景根据第三方系统的事件做出不同动作优化前@Slf4j@RestController@RequiredArgsConstructorpublicclassWebHookController{@PostMapping("/webhook")publicResponseEntity<Object>webhook(@RequestBodyJSONObjectbody){ Stringevent=body.getStri......