Base58 是一种基于 58 个字符的编码方式,常用于比特币和其他加密货币中,主要目的是将任意二进制数据转换为人类可读的字符串形式,同时避免一些容易混淆的字符和潜在的错误。
与 Base64 不同,Base58 编码中通常会排除一些容易混淆的字符,例如0(零)、O(大写字母O)、I(大写字母I)和 l(小写字母L)等,以减少人为错误。
Base58 编码是为比特币钱包地址设计的,主要考虑了一下几点:
(1)避免混淆,在某些字体下,数字 0 和字母大写 O,以及字母大写 I 和字母小写 l 会非常相似。
(2)Base64 编码中包含 "+" 和 "/",非字母或数字的字符串作为帐号较难被接受。
(3) 在邮件系统中,使用字符和数字的组合,不容易换行。
(4)双击可以选中整个字符串。
Base58 使用的字符集包括数字 0-9 和大写字母 A-Z(去除了容易混淆的数字 0、大写字母 O、大写字母 I 和小写字母 l)以及一些特殊字符如 “+” 和 “/”。
相比十六进制编码或直接的二进制表示,Base58 编码的字符串更易于人类阅读和手动输入,减少了输入错误的可能性。
虽然 Base58 编码本身不提供错误检测机制,但由于其字符集的选择,使得编码后的字符串在视觉上更容易发现错误。例如,如果一个字符被错误地替换为一个相似的字符,通常会很明显。
示例:
5Lq65Lq657yW56iL572RIGh0dHA6Ly93d3cuaHhzdHJpdmUuY29t
5Lq65Lq657yW56iL572RIGhOdHA6Ly93d3cuaHhzdHJpdmUuY29t
找一找上面两个字符串中不同的字符,是不是很不好找,特别是 I,他们的不同如下图:
初始化变量
初始化一个整数变量value为 0。这个变量将用于存储输入数据转换后的整数值。
转换为整数
遍历输入的二进制数据中的每个字节。
将当前的value乘以 256(因为一个字节的取值范围是 0 到 255),然后加上当前字节的值(将字节视为无符号整数,即范围是 0 到 255)。
这样,经过遍历所有字节后,value将存储输入数据转换后的整数值。
生成编码结果
创建一个空的字符列表来存储编码后的结果。
当value大于 0 时,执行以下操作:
计算value对 58 取模的结果,这个结果将对应于 Base58 字符集中的一个字符。
将对应的字符添加到字符列表中。
将value除以 58,继续下一次循环。
处理前导零
遍历输入的二进制数据,从前到后检查每个字节是否为 0。
如果遇到一个字节不为 0,则停止检查。
对于每个前导零字节,在编码结果的字符列表开头添加字符 '1'。这是因为 Base58 编码中,前导零用字符 '1' 表示。
构建最终结果
创建一个字符串构建器。
从字符列表的末尾开始,依次将字符添加到字符串构建器中。
最后,返回构建好的字符串作为 Base58 编码的结果。
例如,假设有输入数据为二进制形式的整数12345。
首先将其转换为整数值value = 12345。
然后,不断地将value除以 58,并将余数转换为对应的 Base58 字符。
value = 12345 % 58 = 27,对应字符集里的字符是某个特定字符(假设是 'J')。
value = 12345 / 58 = 212
value = 212 % 58 = 48,对应字符是另一个特定字符(假设是 '6')。
value = 212 / 58 = 3
value = 3 % 58 = 3,对应字符是 '3'
value = 3 / 58 = 0,结束循环。
检查输入数据没有前导零。
最终的 Base58 编码结果为 “36J”。
初始化变量
初始化一个长整型变量value为 0。这个变量将用于存储解码过程中的中间值。
遍历输入字符串
对于输入的 Base58 编码字符串中的每个字符:
在 Base58 字符集中查找该字符的位置。如果字符不在字符集中,说明输入不合法,抛出异常。
将当前的value乘以 58,然后加上找到的字符位置值。
转换为字节数组
创建一个空的字节列表来存储解码后的结果。
当value大于 0 时,执行以下操作:
将value对 256 取模,得到一个字节值,添加到字节列表中。
value除以 256,继续下一次循环。
处理前导零
遍历输入的 Base58 编码字符串,从前到后检查每个字符是否为 '1'。
对于每个前导 '1' 字符,在解码结果的字节列表开头添加一个字节值 0。
构建最终结果
将字节列表转换为字节数组,并返回作为解码结果。
例如,假设有 Base58 编码字符串 “36J”。
首先确定字符集,然后遍历这个字符串。
对于字符 “3”,在字符集中找到其位置为 3,此时value = 3。
对于字符 “6”,在字符集中找到其位置为 6,此时value = 3 * 58 + 6 = 180。
对于字符 “J”,在字符集中找到其位置为 27,此时value = 180 * 58 + 27 = 10467。
转换为字节数组。
value = 10467,value % 256 = 119,添加到字节列表中,此时字节列表为 [119]。
value = 10467 / 256 = 40,value % 256 = 40,添加到字节列表中,此时字节列表为 [40, 119]。
value = 40 / 256 = 0,结束循环。
输入字符串没有前导零,无需处理前导零。
将字节列表转换为字节数组 [40, 119] 作为解码结果。
下面使用 Java 简单实现 Base58 编码的编码和解码,代码如下:
package com.hxstrive.encryption_decryption.base58; import java.math.BigInteger; /** * Base58 编码&解码 * @author hxstrive.com */ public class Base58Demo { /** 定义 Base58 可用字符集 */ private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; /** * 编码 * @param input 字节数组 * @return 编码后的字符串 */ public static String encode(byte[] input) { // signum 参数表示为整数值:-1表示负数,0表示零,或 1表示正数 BigInteger value = new BigInteger(1, input); StringBuilder result = new StringBuilder(); // 循环直到 value 变为 0 while (value.compareTo(BigInteger.ZERO) > 0) { // divideAndRemainder 方法将返回两个 BigInteger 的数组 [商,余数] // 返回值为:[(this / val), (this % val)] BigInteger[] quotientAndRemainder = value.divideAndRemainder(BigInteger.valueOf(58)); // 余数添加到结果中 result.insert(0, ALPHABET.charAt(quotientAndRemainder[1].intValue())); value = quotientAndRemainder[0]; // 商 } // 添加前导 0 for (byte b : input) { if (b == 0) { result.insert(0, ALPHABET.charAt(0)); } else { break; } } return result.toString(); } /** * 解码 * @param input 编码后的字符串 * @return 原文 */ public static byte[] decode(String input) { BigInteger value = BigInteger.ZERO; for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); int digit = ALPHABET.indexOf(c); if (digit == -1) { throw new IllegalArgumentException("Invalid Base58 character: " + c); } value = value.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(digit)); } return value.toByteArray(); } public static void main(String[] args) { String originalData = "Hello, Base58!"; String encodedData = encode(originalData.getBytes()); System.out.println("Encoded: " + new String(encodedData)); byte[] decodedData = decode(new String(encodedData)); System.out.println("Decoded: " + new String(decodedData)); } }
运行示例,输出如下:
Encoded: TcgsE5e9XJSrakNTEQQ Decoded: Hello, Base58!