奇怪的 Base64 编码/解码问题

Posted

技术标签:

【中文标题】奇怪的 Base64 编码/解码问题【英文标题】:Strange Base64 encode/decode problem 【发布时间】:2011-08-03 11:29:32 【问题描述】:

我正在使用 Grails 1.3.7。我有一些使用内置 base64Encode 函数和 base64Decode 函数的代码。在我编码一些二进制数据然后解码结果字符串并将其写入新文件的简单测试用例中,这一切都很好。在这种情况下,文件是相同的。

但后来我编写了一个 Web 服务,它将 base64 编码的数据作为 POST 调用中的参数。虽然 base64 数据的长度与我传递给函数的字符串相同,但 base64 数据的内容正在修改中。我花了几天时间对此进行调试,最后编写了一个测试控制器,该控制器将 base64 中的数据传递给 post,并使用正确的 base64 编码数据获取本地文件的名称,如下所示:

data=AAA-base-64-data...&testFilename=/name/of/file/with/base64data

在测试函数中,我将传入数据参数中的每个字节与测试文件中的相应字节进行了比较。我发现输入数据参数中的每个“+”字符都被替换为“”(空格,序数 ascii 32)。嗯?什么可以做到这一点?

为了确保我是正确的,我添加了一行内容:

data = data.replaceAll(' ', '+')

果然数据解码完全正确。我尝试使用任意长的二进制文件,现在每次都可以使用。但是我一生都无法弄清楚如何修改帖子中的数据参数以将 ord(43) 字符转换为 ord(32)?我知道加号是 base64 规范中两个与平台相关的字符之一,但鉴于我现在在同一台机器上进行编码和解码,我非常困惑是什么原因造成的。当然我有一个“修复”,因为我可以让它工作,但我对我不理解的“修复”感到紧张。

代码太大,无法在此处发布,但我得到的 base64 编码如下:

def inputFile = new File(inputFilename)
def rawData =  inputFile.getBytes()
def encoded = rawData.encodeBase64().toString()

然后,我将该编码字符串写入新文件,以便以后可以使用它进行测试。如果我重新加载该文件,我会得到相同的 rawData:

def encodedFile = new File(encodedFilename)
String encoded = encodedFile.getText()
byte[] rawData = encoded.decodeBase64()

所以一切都很好。现在假设我采用“编码”变量并将其添加到 POST 函数的参数中,如下所示:

String queryString = "data=$encoded"
String url = "http://localhost:8080/some_web_service"

def results = urlPost(url, queryString)

def urlPost(String urlString, String queryString) 
    def url = new URL(urlString)
    def connection = url.openConnection()
    connection.setRequestMethod("POST")
    connection.doOutput = true

    def writer = new OutputStreamWriter(connection.outputStream)
    writer.write(queryString)
    writer.flush()
    writer.close()
    connection.connect()

    return (connection.responseCode == 200) ? connection.content.text : "error                         $connection.responseCode, $connection.responseMessage"

在 Web 服务端,在控制器中,我得到如下参数:

String data = params?.data
println "incoming data parameter has length of $data.size()" //confirm right size

//unless I run the following line, the data does not decode to the same source
data = data.replaceAll(' ', '+')

//as long as I replace spaces with plus, this decodes correctly, why?
byte[] bytedata = data.decodeBase64()

抱歉,长篇大论,但我真的很想了解为什么我必须执行“用加号替换空格”才能正确解码。在请求参数中使用加号是否有问题?

【问题讨论】:

【参考方案1】:

填充 params 的任何内容都希望请求是 URL 编码的形式(具体来说,application/x-www-form-urlencoded,其中“+”表示空格),但您没有对其进行 URL 编码。我不知道您的语言提供了哪些功能,但在伪代码中,queryString 应该由

concat(uri_escape("data"), "=", uri_escape(base64_encode(rawBytes)))

简化为

concat("data=", uri_escape(base64_encode(rawBytes)))

+”字符将替换为“%2B”。

【讨论】:

【参考方案2】:

您必须使用一个特殊的 base64 编码,它也是 url 安全的。问题是标准 base64encode 包含+/= 字符,这些字符被百分比编码版本替换。

http://en.wikipedia.org/wiki/Base64#URL_applications

我在 php 中使用以下代码:

    /**
     * Custom base64 encoding. Replace unsafe url chars
     *
     * @param string $val
     * @return string
     */
    static function base64_url_encode($val) 

        return strtr(base64_encode($val), '+/=', '-_,');

    

    /**
     * Custom base64 decode. Replace custom url safe values with normal
     * base64 characters before decoding.
     *
     * @param string $val
     * @return string
     */
    static function base64_url_decode($val) 

        return base64_decode(strtr($val, '-_,', '+/='));

    

【讨论】:

您能解释一下“url 编码...无法正常工作”是什么意思吗? AFAIK,它工作得很好。 (其他人似乎对此唯一的抱怨是,由于将= 替换为%3D+ 替换为%2B/ 替换为@987654331,它创建了稍微 更长的字符串@.) 当然,抱歉,我的意思是你不能只对 url 使用 base64 编码/解码,因为 '+'、'/' 和 '=' 符号必须被替换。这就是诀窍。【参考方案3】:

因为它是 POST 的参数,所以您必须对数据进行 URL 编码。

见http://en.wikipedia.org/wiki/Percent-encoding

【讨论】:

【参考方案4】:

来自***链接的paraquote

默认使用的编码是基于 在一个非常早期的通用版本上 URI 百分比编码规则,带有 修改次数,例如 换行规范化和替换 空格用“+”代替“%20”

另一个隐藏的陷阱,像我这样的日常 Web 开发人员知之甚少

【讨论】:

以上是关于奇怪的 Base64 编码/解码问题的主要内容,如果未能解决你的问题,请参考以下文章

JSON 编码/解码 JavaScript 中的 base64 编码/解码

如何编码/解码为 base64?

base64编码问题

前端base64编码与解码

base 64 以角度编码和解码字符串 (2+)

Go语言实现Base64Base58编码与解码