为什么以下代码
var s = "2I==";
var b = Convert.FromBase64String(s);
var new_s = Convert.ToBase64String(b);
最终new_s为2A==
?
s
最初是一个较长的字符串(96个字符)但我无法包含它,因为它是一个密钥。
答案 0 :(得分:5)
" 2I =="根据{{3}}表示数字54,8(填充x2)。
换句话说,代表的位是:
110110 000100 XXXXXX XXXXXX
(其中X表示"我不在乎,它来自填充")
但是,因为填充表示此处只有一个字节的信息,所以第二个字符的最后4位是无关紧要的。与以往一样,我们可以将4个6位信息重新格式化为3个8位信息,此时它变得更加清晰:
11011000 0100XXXX XXXXXXXX
您可以看到第二个字节必须是填充,因为它的一些位来自填充字符。因此,只有第一个字符和第二个字符的前两位是相关的 - 它只解码为单个字节0b11011000。
现在当你编码 0b11011000时,你知道你有两个填充字符,第一个字符必须是' 2' (代表比特' 110110')但第二个字符可以是任何字符,其前两位代表' 00'。恰好Convert.ToBase64String
使用' A',其中0位用于不相关的部分。
我心中的问题是为什么编码器会选择使用' I'而不是'。我不会认为在Base64中执行此操作无效,但这是一个奇怪的选择。
答案 1 :(得分:4)
Jon Skeet为观察到的行为提供了一个很好的解释。但是,在Base64的大多数定义下,您的输入字符串将被视为无效。标准包含以下文本:
当输入组中有少于24个输入位时,将添加值为零的位(在右侧),以形成一个6位组的整数。
RFC 4648进一步强调了这一点:
如果不正确,基础64和基本32编码中的填充步骤可以 实施后,导致编码数据的非重大改变。 例如,如果输入只是一个基本64位编码的八位字节, 然后使用第一个符号的所有六位,但只使用第一个 使用下一个符号的两位。这些填充位必须设置为 通过符合编码器[...]零 如果填充位没有,则解码器可以选择拒绝编码 已设为零。
我们可以假设您的原始输入由一个具有值216
(0xD8
)的字节组成。二进制:
11011000
这需要分成6位组:
110110 00
并且,根据上面引用的定义,最后一组需要用零填充:
110110 000000
根据Base64字母,110110
(十进制:54
)映射到字符2
,而000000
(十进制:0
)映射到字符A
。添加=
填充以获取24位组,最终结果将为2A==
。这是原始输入的唯一有效编码。