Convert.FromBase64String 的奇怪行为

Strange behaviour of Convert.FromBase64String

为什么会出现下面的代码

var s =  "2I==";
var b = Convert.FromBase64String(s);
var new_s = Convert.ToBase64String(b);

最后 new_s 变成 2A==?

s 最初是一个较长的字符串(96 个字符),但我无法包含它,因为它是一个密钥。

“2I==”表示数字 54、8(填充 x2),根据 Wikipedia

也就是说,代表的位是:

110110 000100 XXXXXX XXXXXX

(其中 X 代表一个 "I don't care, it's from padding")

但是,由于填充表示此处只有一个字节的信息,因此第二个字符的最后 4 位无关紧要。和以往一样,我们可以将4条6位的信息重新格式化为3条8位的信息,此时就更加清晰了:

11011000 0100XXXX XXXXXXXX

你可以看到第二个字节必须是填充,因为它的一些位来自填充字符。所以只有第一个字符和第二个字符的前两位是相关的——它只解码为单个字节 0b11011000.

现在当你编码 0b11011000时,你知道你将有两个填充字符,第一个字符必须是'2 '(代表位'110110')但第二个字符可以是任何字符,其前两位代表'00'。恰好 Convert.ToBase64String 使用 'A',其中不相关的部分为 0。

我心中的问题是为什么编码器会选择使用 'I' 而不是 'A'。我不认为在 Base64 中这样做是无效的,但这是一个奇怪的选择。

Jon Skeet 对观察到的行为提供了很好的解释。但是,在大多数 Base64 定义下,您的输入字符串将被视为无效。标准包含以下文本:

When fewer than 24 input bits are available in an input group, bits with value zero are added (on the right) to form an integral number of 6-bit groups.

  • RFC 1421:Internet 电子邮件隐私增强
  • RFC 2045:多用途 Internet 邮件扩展 (MIME)
  • RFC 4648:Base16、Base32 和 Base64 数据编码

RFC 4648 进一步强调:

The padding step in base 64 and base 32 encoding can, if improperly implemented, lead to non-significant alterations of the encoded data. For example, if the input is only one octet for a base 64 encoding, then all six bits of the first symbol are used, but only the first two bits of the next symbol are used. These pad bits MUST be set to zero by conforming encoders [...] Decoders MAY chose to reject an encoding if the pad bits have not been set to zero.

我们可以假设您的原始输入由一个值为 216 (0xD8) 的字节组成。二进制:

11011000

这需要分成 6 位组:

110110 00

而且,根据上面引用的定义,最后一组需要用零填充:

110110 000000

根据 Base64 字母表,110110(十进制:54)映射到字符 2,而 000000(十进制:0)映射到字符 A。添加= padding得到一个24位的组,最终结果将是2A==。这是您原始输入的唯一有效编码。