【问题标题】:Remove trailing "=" when base64 encodingbase64编码时去掉尾随“=”
【发布时间】:2011-05-28 09:43:00
【问题描述】:

我注意到,每当我对字符串进行 base64 编码时,都会在末尾附加一个“=”。我可以删除这个字符,然后通过添加回来可靠地解码它,还是这很危险?换句话说,是“=”始终附加,还是仅在某些情况下附加?

我希望我的编码字符串尽可能短,这就是为什么我想知道我是否总是可以删除“=”字符并在解码之前将其添加回来。

【问题讨论】:

  • 让我们将base64-sensible 定义为base64-without-padding,好吗?这些等号字符完全是多余的,因此完全没有意义。如果您编写 base64 解码器,请考虑不要拒绝没有填充的输入。
  • 当然,但是如果您确实剥离了填充,请注意您不会将任何剥离的 base64 编码字符串连接在一起。当然,还要确保您的解码器不期待填充。

标签: base64


【解决方案1】:

= 是填充。

Wikipedia

一个额外的填充字符是 分配的可用于强制 编码输出为整数 4 个字符的倍数(或 等效地,当未编码的二进制 text 不是 3 个字节的倍数); 这些填充字符必须是 解码时丢弃但仍然 允许计算有效 未编码文本的长度,当它的 输入二进制长度不会是 3 个字节的倍数(最后一个非填充 字符通常被编码,使得 它代表的最后一个 6 位块 最少会被零填充 有效位,最多两个填充 字符可能出现在末尾 编码流)。

如果您控制另一端,则可以在传输时将其移除,然后在解码前重新插入(通过检查字符串长度)。
请注意,传输中的数据将不是有效的 Base64。

另外,另一位用户指出(与 PHP 用户相关):

请注意,在 PHP 中 base64_decode 将接受没有填充的字符串,因此如果您将其删除以稍后在 PHP 中进行处理,则无需将其添加回来。 – Mahn 2014 年 10 月 16 日 16:33

因此,如果您的目标是 PHP,您可以安全地去除填充和解码,而无需花哨的计算。

【讨论】:

  • 看起来这实际上可能不起作用,因为在解码端我们需要知道“=”是否在编码端被删除。我无法包含该信息。
  • @Steve:如果长度不是 4 个字符的倍数,则添加 = 字符直到达到。在 .Net 中,if (str.Length % 4 != 0) str += new string('=', 4 - str.Length % 4)
  • 请注意,在 PHP 中 base64_decode 将接受字符串 没有 填充,因此如果您将其删除以稍后在 PHP 中进行处理,则无需将其添加回来。
  • 就像@Mahn 提到的,即使是 Javascript 的 atob() 函数也不需要填充来成功解码 base64 编码的字符串
  • @Mahn 与 Ruby 的 Base64.decode64 方法相同;没有填充它工作正常。我认为 .NET 的 Convert.FromBase64String 方法是更严格的方法之一,因为它实际上需要填充。
【解决方案2】:

我编写了 Apache 的 commons-codec-1.4.jar Base64 解码器的一部分,按照这种逻辑,我们可以不用填充字符。 End-of-file 和 End-of-stream 与任意数量的 '=' 字符一样是 Base64 消息完成的良好指标!

我们在 commons-codec-1.4 中引入的 URL-Safe 变体故意省略了填充字符以使内容更小!

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

我想一个更安全的答案是,“取决于你的解码器实现”,但从逻辑上讲,编写一个不需要填充的解码器并不难。

【讨论】:

  • 有趣-感谢您的观点。我想知道填充是否旨在优化硬件实现。
  • 用户应该注意,如果您编码为 URL 安全,然后另一个程序使用 Apache 以外的其他程序进行解码,它将无法正确解码。
  • Url safe 还进行了额外的转换,: encodeUrlSafe(decode("d8vb15jT4MYKb7RpvtJq+/EH8K1h5XH14Oi+3NtrLcM")) = ="d8vb15jT4MYKb7RpvtJq-_EH8K1h5XH14Oi-3NtrL 用减号和 / 替换你可能会看到它与_
  • 是的,当然。 + 和 / 是 URLS 中的特殊字符,因此不是 url 安全的!
【解决方案3】:

在 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, '/');

另见这个小提琴:http://jsfiddle.net/7bjaT/66/

【讨论】:

    【解决方案4】:

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

    阅读:不,你不应该删除它。

    【讨论】:

    • 一个有用的答案。
    • 那么告诉我。为什么编码 Base64 URL 安全时 java 不会发生这种情况?
    • @Code.IT “网址安全”?这听起来像是一个已经修剪 = 字符的 Java 函数,因为这些不是 URL 安全的。
    • = 是一个填充字符。它与 URL 安全无关。 stackoverflow.com/a/4492448/2739334
    • @Rudey 有一个“base64 url​​”编码,它将base64的“+”替换为“-”,将“/”替换为“_”。它还省略了填充,因为填充没有信息值(实际上许多 base64 解码器已经不使用它)。
    【解决方案5】:

    在 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

    【讨论】:

      【解决方案6】:

      如果您正在编码字节(以固定位长),则填充是多余的。大多数人都是这种情况。

      Base64 一次消耗 6 位,并产生一个 8 位字节,只使用 6 位的组合。

      如果您的字符串是 1 字节(8 位),您将得到 12 位的输出,作为 8 的 6 的最小倍数,另外还有 4 位。如果你的字符串是 2 个字节,你必须输出 18 位,另外还有 2 位。对于 6 的倍数和 8 的倍数,余数可以是 0、2 或 4 位。

      填充表示忽略那些额外的四个 (==) 或两个 (=) 位。填充在那里告诉解码器你的填充。

      当您对字节进行编码时,实际上并不需要填充。 base64 编码器可以简单地忽略总共少于 8 位的剩余位。在这种情况下,您最好将其移除。

      填充可能对流式传输和任意长度的位序列有用,只要它们是 2 的倍数。它也可能用于人们只想发送最后 4 位的情况,如果剩余位全为零,则剩余更多位。有些人可能想用它来检测不完整的序列,尽管这并不可靠。我在实践中从未见过这种优化。人们很少有这些情况,大多数人使用 base64 处理离散字节序列。

      如果您看到建议保留它的答案,如果您只是对字节进行编码,这不是一个很好的鼓励,它会为您没有的一组情况启用一项功能。在这种情况下启用它的唯一原因可能是增加对没有填充就无法工作的解码器的容忍度。如果你控制两端,那就不用担心了。

      【讨论】:

        【解决方案7】:

        如果您使用的是 PHP,以下函数将使用适当的填充将剥离的字符串恢复为其原始格式:

        <?php
        
        $str = 'base64 encoded string without equal signs stripped';
        $str = str_pad($str, strlen($str) + (4 - ((strlen($str) % 4) ?: 4)), '=');
        
        echo $str, "\n";
        

        【讨论】:

        • $str = str_pad($str, ceil(strlen($str)/4)*4, '=');(结果相同)
        【解决方案8】:

        使用 Python,您可以删除 base64 填充并将其添加回来,如下所示:

        from math import ceil
        
        stripped = original.rstrip('=')
        
        original = stripped.ljust(ceil(len(stripped) / 4) * 4, '=')
        

        【讨论】:

          【解决方案9】:

          是的,在 Base 64 编码中省略填充的有效用例。

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

          Base64 编码 [...] 带有所有尾随 '=' 省略的字符(如第 3.2 节所允许的)并且没有 包含任何换行符、空格或其他额外的 人物。注意空八位字节的base64url编码 序列是空字符串。 (见附录 C 的注释 实现 base64url 编码而不进行填充。)

          这同样适用于JSON Web Token (JWT) standard (RFC 7519)。

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

          【讨论】:

            【解决方案10】:

            我用 java8+ 做这样的事情

            private static String getBase64StringWithoutPadding(String data) {
                if(data == null) {
                    return "";
                }
                Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
                return encoder.encodeToString(data.getBytes());
            }
            

            这个方法得到一个没有填充的编码器。

            正如其他答案中提到的,如果您需要将其解码回来,可以在计算后添加填充。

            【讨论】:

              【解决方案11】:

              对于 Android,如果您想使用 android.util.base64 类,您可能会遇到麻烦,因为它不允许您执行 UnitTest 其他集成测试 - 那些使用 Adnroid 环境。

              另一方面,如果您将使用 java.util.base64,编译器会警告您,您的 sdk 可能会低于(低于 26)才能使用它。

              所以我建议Android开发者使用

              implementation "commons-codec:commons-codec:1.13"
              

              编码对象

              fun encodeObjectToBase64(objectToEncode: Any): String{
                  val objectJson = Gson().toJson(objectToEncode).toString()
                  return encodeStringToBase64(objectJson.toByteArray(Charsets.UTF_8))
              }
              
              fun encodeStringToBase64(byteArray: ByteArray): String{
                  return Base64.encodeBase64URLSafeString(byteArray).toString() // encode with no padding
              }
              

              解码成对象

              fun <T> decodeBase64Object(encodedMessage: String, encodeToClass: Class<T>): T{
                  val decodedBytes = Base64.decodeBase64(encodedMessage)
                  val messageString = String(decodedBytes, StandardCharsets.UTF_8)
                  return Gson().fromJson(messageString, encodeToClass)
              }
              

              当然你可以省略Gson解析,直接把你的String转换成ByteArray的方法

              【讨论】:

                猜你喜欢
                • 2012-04-11
                • 2022-06-15
                • 2023-03-23
                • 1970-01-01
                • 2022-11-15
                • 2018-09-26
                • 2021-12-04
                • 2021-10-15
                • 1970-01-01
                相关资源
                最近更新 更多