移除base64编码时的尾部"="符号

79

我注意到每当我对一个字符串进行base64编码时,末尾会添加一个"="字符。我可以删除这个字符,然后在解码之前将其添加回去吗?这样做是否存在风险?换句话说,这个"="字符是否总是被添加,还是只在某些情况下添加?

我希望我的编码字符串尽可能地短,因此想知道是否总是可以删除"="字符,然后在解码之前再添加回去。


7
请允许我翻译一下:让我们把“base64-sensible”定义为“没有填充的base64”,可以吗?这些等号完全是多余的,因此毫无意义。如果你编写了一个base64解码器,请考虑不要拒绝没有填充的输入。 - Roman Starkov
4
可以,但如果您剥离填充,请注意不要将任何已剥离的base64编码字符串连接在一起。当然,还要确保您的解码器不需要填充内容。 - Reed Sandberg
最好的方法来确定是否需要添加'='或'=='或什么都不添加是保持该信息不变。这样做不会更简短。移除一个'='意味着移除两个位。 - undefined
11个回答

84
= 是填充字符。<!------------> 根据维基百科,Base64编码会额外添加一个填充字符,可用于将编码结果强制转换为由4个字符组成的整数倍(即在未编码的原二进制文本长度不是3的倍数时);在解码时需要丢弃这些填充字符,但可计算出未编码的文本的有效长度。通常最后一个非填充字符被编码为表示其代表的6位块将在其最低有效位上补齐0,最多可以在编码流的结尾处出现两个填充字符。 如果您控制另一端,在传输时可以将其删除,然后在解码之前通过检查字符串长度重新插入它。请注意,在传输过程中,数据将无法作为有效的Base64进行传输。 另外,另一位用户指出(适用于PHP用户): 请注意,在PHP中,base64_decode将接受没有填充的字符串,因此,如果您删除了填充以便稍后在PHP中处理,则无需将其添加回来。- Mahn Oct 16'14 at 16:33 因此,如果目标是PHP,您可以安全地删除填充并进行解码,而无需进行复杂的计算。

看起来这似乎实际上行不通,因为在解码端我们需要知道编码端是否删除了“=”。我无法包含那些信息。 - Steve N
55
如果长度不是4的倍数,就加上=字符直到成为4的倍数。在.NET中,可以使用以下代码实现:if (str.Length % 4 != 0) str += new string('=', 4 - str.Length % 4) - SLaks
24
请注意,在PHP中,base64_decode函数会接受没有填充字符的字符串,因此如果您在PHP中移除了填充字符以便稍后处理,就不需要再添加回来。 - Mahn
5
正如 @Mahn 提到的,即使是 JavaScript 的 atob() 函数也不需要填充就能成功解码一个 base64 编码的字符串。 - Akshay Raje
@Mahn Ruby的Base64.decode64方法也是一样的;它可以在没有填充的情况下正常工作。我认为.NET的Convert.FromBase64String方法是其中相对较严格的之一,实际上需要填充。 - Per Lundberg

35

在 JavaScript 中,你可以像这样做:

// if this is your Base64 encoded string
var str = 'VGhpcyBpcyBhbiBhd2Vzb21lIHNjcmlwdA=='; 

// make URL friendly:
str = str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');

// reverse to original encoding
if (str.length % 4 != 0){
  str += ('===').slice(0, 4 - (str.length % 4));
}
str = str.replace(/-/g, '+').replace(/_/g, '/');

请参考这个 Fiddle:http://jsfiddle.net/7bjaT/66/


1
这是一个很棒的脚本。 - Aaron Brager

33

我曾经参与编写 Apache 的 commons-codec-1.4.jar Base64 解码器。在那段逻辑中,我们无需填充字符即可正常解码。文件结尾和流结尾本身就足以表明 Base64 消息已完成,不需要任何数量的 '=' 字符来标记!

我们在 commons-codec-1.4 中引入了 URL-Safe 变体,有意省略了填充字符以使数据更小。

http://commons.apache.org/codec/apidocs/src-html/org/apache/commons/codec/binary/Base64.html#line.478

或许更安全的回答是 "取决于你的解码器实现",但从逻辑上说,不需要填充符号也能写出一个解码器。


1
有趣的观点,谢谢。我想知道填充是否旨在优化硬件实现。 - Steve N
用户应该注意,如果您将编码设置为URL安全,然后另一个程序使用非Apache的解码方式进行解码,它将无法正确解码。 - smcg
Url安全编码还进行了额外的转换:encodeUrlSafe(decode("d8vb15jT4MYKb7RpvtJq+/EH8K1h5XH14Oi+3NtrLcM")) = ="d8vb15jT4MYKb7RpvtJq-_EH8K1h5XH14Oi-3NtrLcM"在这里,您可以看到它将加号替换为减号,将斜杠替换为下划线。 - Sergey Ponomarev
当然可以。加号和斜杠是URL中的特殊字符,因此不安全! - Julius Musseau

18

=用于填充。base64字符串的长度应该是4的倍数,因此需要根据需要添加1或2个=

阅读:不,您不应该将其删除。


告诉我,为什么在使用Base64 URL安全编码时,Java不会出现这种情况? - Pwnstar
“URL安全”?这听起来像是一个Java函数,它已经修剪了“=”字符,因为它们不安全。 - Rudey
= 是填充字符,与 URL 安全无关。https://dev59.com/k2855IYBdhLWcg3wKxHc#4492448 - Pwnstar
@Rudey,有一种“base64 url”编码,它将base64中的“+”替换为“-”,将“/”替换为“_”。它还省略填充,因为填充没有信息价值(实际上,许多base64解码器已经不使用它)。 - sisisisi
1
如果您正在使用base64_encode对JWT主体进行编码,则可能需要。 - John Hunt

7
在Android上我正在使用以下内容:
全局
String CHARSET_NAME ="UTF-8";

编码

String base64 = new String(
            Base64.encode(byteArray, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_CLOSE | Base64.NO_WRAP),
            CHARSET_NAME);
return base64.trim();

解码

byte[] bytes = Base64.decode(base64String,
            Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_CLOSE | Base64.NO_WRAP);

在Java中,这等同于:

编码

private static String base64UrlEncode(byte[] input)
{
    Base64 encoder = new Base64(true);
    byte[] encodedBytes = encoder.encode(input);
    return StringUtils.newStringUtf8(encodedBytes).trim();
}

解码

private static byte[] base64UrlDecode(String input) {
    byte[] originalValue = StringUtils.getBytesUtf8(input);
    Base64 decoder = new Base64(true);
    return decoder.decode(originalValue);
}

我从未遇到过尾随“=”的问题,而且我也在使用Bouncycastle。


5
如果你正在编码字节(在固定的位长度下),则填充是多余的。大多数人都是这种情况。
Base64每次消耗6位并生成一个8位字节,只使用6位合法组合。
如果你的字符串是1个字节(8位),则输出为12位,因为8适合多个6,还有4个额外的位。 如果您的字符串是2个字节,则必须输出18位,并有两个额外的位。 对于6的倍数与8的倍数,可以有0、2或4位的余数。
填充指示忽略那些额外的四个(==)或两个(=)比特。填充存在是为了告诉解码器关于您的填充的情况。
当您编码字节时,填充实际上不是必需的。 Base64编码器可以简单地忽略总共少于8位的剩余位。在这种情况下,最好将其删除。
对于流和任意长度的位序列,只要它们是二的倍数,填充可能会有一些用处。如果剩余位全部为零,人们可能还想仅发送最后4位。有些人可能希望使用它来检测不完整的序列,但这几乎不可靠。我从未见过这种优化实际应用。大多数人使用base64来处理离散字节序列。
如果您看到建议将其保留的答案,那不是很好的鼓励,如果您只是编码字节,则启用了一组您没有的情况的功能。在这种情况下打开它的唯一原因可能是为了使那些无法正常工作而没有填充的解码器具有容错性。如果您控制双端,那就不必担心这个问题。

3
如果您正在使用PHP,则以下函数将使被剥离的字符串以适当的填充方式恢复到其原始格式:
<?php

$str = 'base64 encoded string without equal signs stripped';
$str = str_pad($str, strlen($str) + (4 - ((strlen($str) % 4) ?: 4)), '=');

echo $str, "\n";

2
或者 $str = str_pad($str, ceil(strlen($str)/4)*4, '='); (结果相同) - Alexandre T.

2
使用Python,您可以像这样删除和添加base64填充:

使用Python,您可以这样删除和添加base64填充:

from math import ceil

stripped = original.rstrip('=')

original = stripped.ljust(ceil(len(stripped) / 4) * 4, '=')

1
我用Java8+做了类似这样的事情。
private static String getBase64StringWithoutPadding(String data) {
    if(data == null) {
        return "";
    }
    Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
    return encoder.encodeToString(data.getBytes());
}

该方法获取一个不包含填充的编码器。
如其他答案中已经提到的,如果需要将其解码回来,则可以在计算后添加填充。

1

是的,有一些合法的情况下可以省略Base 64编码中的填充。

JSON Web Signature (JWS)标准 (RFC 7515) 要求 Base 64编码的数据省略填充。它期望:

使用所有结尾的 '=' 字符被省略的Base64编码(按照3.2节的规定),不包括任何换行符、空格或其他附加字符。请注意,空字节序列的base64url编码为空字符串。 (有关实现不带填充的base64url编码的说明,请参见附录C。)

JSON Web Token (JWT)标准 (RFC 7519)同样适用于此。

此外,Julius Musseau 的答案 表明 Apache's Base 64解码器不需要在 Base 64 编码数据中存在填充。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接