前言
对编程和开发有所了解的人,可能都听说或者了解过Base64编码。比如可能会看到使用Base64对文本信息进行编码和转换,也会看到很多证书和公钥里面的信息是用base64的,或者甚至可以看到CSS可以将图片编码成Base64字符串。其实,如果深入了解其编码方式和规则,我们其实可以发现,base64看起来是一个字符串,但其本质是二进制数据的一种表达,它就是二进制信息,因此可以编码任何格式和形式的信息和数据,具有很强的通用性。
那么为什么是base64呢? 是因为在技术上处理方便。在我们这个信息技术的世界中,虽然底层是二进制,但为了处理方便,我们通常将其进行分组,如八位作为一组,然后使用一个八位二进制的数组,就可以表示所有的信息,我们称之为字节数组,每个字节是八位二进制,也就是一个256以内的无符号小整数。 但这个字节数组,在表示的时候并不方便,理论上如果有256个字符,就可以用这些字符来表达。但标准的ASCII码只有128个,所以他们想出来一个变通的方法,使用其中的64个字符(数字、字母大小写、斜杠和加号,不知道他们怎么想的)来表达,因为可以用四个字符,刚好可以表达三个字节数组的元素(2563 == 644),就可以将字节数组分成三个一组,使用4个字符来表示,非常简单方便。这就是笔者理解的为什么选择这个技术方案的原因。
但如果是其他的字符集合呢?因为Base64所使用的字符里面有几个特殊字符,比如用在URL中可能会引起歧义,而且加号也是操作符,直接用作字符,可能会有一些风险。所以有人提出使用其他的字符集合的方案。比如比特币就使用58个字符(去除了0,o,i,1等容易看花的字符)。原理上而言,用什么字符集合都可以,但前面我们已经分析使用64的方便性,如果是其他集合,因为不能刚好分段,就需要使用其他的处理方式。
这里尝试先简单解释一下这个标准处理方法。我们已经知道,信息可以使用标准字节数组的方式来表达,其实可以进一步将这个数组转换称为一个整数数字(256进制进位累加),然后再用进制转换的方式,循环求余,得到一个目标进制的数组,最后用字符串来表达这些数组元素就可以了。这个方法,理论上可以处理使用任意字符集合的信息,但有一个技术上的限制,就是这个数字可能会非常之大,超出了一般常用的整数的范围,从而无法进行处理,所以要使用这个方法,必须要大整数程序库或者类型的支持(base64不需要,因为可以分段处理)。
笔者有一篇文章简单阐述了一种不使用大整数,处理Base58编码方式的实现,那其实是一种妥协和不完备的技术方案。而本文的研究和改进之处在于,可以利用JS程序中的BigInt数据类型,在没有第三方库支持的情况下,来编写一个完备的B58编码解码程序。
BigInt
是的,JS现在已经原生支持BigInt(好像浏览器有版本的限制,但应该比较新的都支持了)。我们先来熟悉一下它的定义和使用。
使用BigInt,我们可以基本上不考虑有效范围,来进行整数的各种运算操作。它可以直接支持各种运算符,所以使用起来非常方便,只是需要注意在运算之前,需要明确所有参与运算的数字都是大整数(通过声明数字以n作为结尾),或者进行转换(使用BigInt方法),要将比较小的大整数转换为整数,可以使用parseInt。
我们来看看下面几个例子,就很容易理解它的使用逻辑:
let i = 7;
let i1 = 1n;
let i2 = i1 + BigInt(i)**90n;
let i3 = parseInt(i1) + 10;
// 11450477594321044359340126713545146077054004823284978858214566372120240027250n
了解和熟悉了BigInt数据类型之后,我们下面就可以将其应用到Base58的编解码处理过程当中。
Base58编解码实现程序
这个编解码程序本质上是一个转换程序,编码程序(encode)可以将一个utf8字符串(可以支持中文)转换为对应的b58字符串;而解码函数(decode)就是一个逆操作,可以将b58字符串还原为utf8字符串。
基本的设计思路、流程和要点如下:
- 确定B58编码的字符表(包括58个字符的字符串,这里保留了其在ASCII码表中的顺序)
- 首先将utf8字符串转换为字节数组,使用TextEncoder
- 然后将字节数组,转换为一个大整数,使用BigInt
- 将这个大整数转换为58进制的数组(循环求余,并记录余数)
- 然后对于数组中每个元素,查找其对应的字符
- 连接这些字符,就是b58编码的结果
- 反向转换,将b58字符串,转换为一个字符数组
- 根据字符数组的元素(字符),倒查其在自字符表中的位置,就是对应的数值,得到一个位置值的数组
- 将这个位置值数组,按照58进制的表示方式,转换为一个大整数
- 将这个大整数,按照256进制的表示方式,转换为256进制表示的数组
- 使用TextEncoder,还原字节数组为原始的utf8字符串
基于上面的考虑,实现的示例代码如下: