如何在Python中解码Base64 URL?

32
对于Facebook的fbml应用程序,Facebook会发送一个signed_request参数,其解释在这里:

http://developers.facebook.com/docs/authentication/canvas

他们已经提供了解码这个签名请求的php版本。

http://pastie.org/1054154

如何在Python中实现相同的功能?
我尝试了base64模块,但是出现了错误的填充错误:
>>> base64.urlsafe_b64decode("eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEyNzk3NDYwMDAsIm9hdXRoX3Rva2VuIjoiMjk1NjY2Njk1MDY0fDIuRXpwem5IRVhZWkJVZmhGQ2l4ZzYzUV9fLjM2MDAuMTI3OTc0NjAwMC0xMDAwMDA0ODMyNzI5MjN8LXJ6U1pnRVBJTktaYnJnX1VNUUNhRzlNdEY4LiIsInVzZXJfaWQiOiIxMDAwMDA0ODMyNzI5MjMifQ")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/base64.py", line 112, in urlsafe_b64decode
    return b64decode(s, '-_')
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/base64.py", line 76, in b64decode
    raise TypeError(msg)
TypeError: Incorrect padding

谢谢,我尝试了Base64,但是我得到了这个错误:http://pastie.org/1054201 - kevin
请实际发布显示错误和实际错误的最小代码。我们中的大多数人都没有耐心跟随到处的链接。 - S.Lott
注意:如果您偶然使用从搜索返回的Azure Blob URL,则需要从编码的URL中删除末尾的“0” - Geordie
10个回答

41

尝试

s = 'iEPX-SQWIR3p67lj_0zigSWTKHg'
base64.urlsafe_b64decode(s + '=' * (4 - len(s) % 4))

正如这里所写的那样。


4
确保你使用的字符串s是str类型的实例——如果是unicode类型会出错。如果遇到这种情况,请使用str(s)函数进行转换。 - sax
4
链接已损坏。 - zabop
链接仍然无效,大约18个月过去了。这可能是为什么在SO上通常不鼓励简单地添加外部网站链接的原因之一。 - ssc

25

我在一个基于Python的Facebook Canvas应用程序中分享了解析signed_request参数的代码片段,网址为http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas

import base64
import hashlib
import hmac
import simplejson as json

def base64_url_decode(inp):
    padding_factor = (4 - len(inp) % 4) % 4
    inp += "="*padding_factor 
    return base64.b64decode(unicode(inp).translate(dict(zip(map(ord, u'-_'), u'+/'))))

def parse_signed_request(signed_request, secret):

    l = signed_request.split('.', 2)
    encoded_sig = l[0]
    payload = l[1]

    sig = base64_url_decode(encoded_sig)
    data = json.loads(base64_url_decode(payload))

    if data.get('algorithm').upper() != 'HMAC-SHA256':
        log.error('Unknown algorithm')
        return None
    else:
        expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest()

    if sig != expected_sig:
        return None
    else:
        log.debug('valid signed request received..')
return data

1
dae.eklen的解决方案实现相同且更加优雅。(base64.urlsafe_b64decode(s + '=' * (4 - len(s) % 4))) - sax
1
谢谢。这是一个非常简短的代码片段- 如果能将其包含在答案中就太好了。 - dgel

23

显然,当您复制原始的Base64编码字符串时,您错过了最后两个字符。在输入字符串后缀两个相等号(=),它将被正确地解码。


2
Geert,谢谢你。但这正是我从Facebook得到的代码,它没有在结尾处加上=。这是否正常? - kevin
2
我想说,这是意料之外的。 但是,您可以通过检查base64输入的长度来验证它的长度:长度必须始终是4个字节的倍数(这实际上是解码器抛出错误的原因)。 如果不是,则可以添加等号,直到达到4个字节的倍数,然后字符串将被正确解码。 - Geert
5
似乎在所有变体中都不一定需要填充=: http://en.wikipedia.org/wiki/Base64 - Nas Banov
7
RFC 3548和RFC 4648均指出:“除非涉及到此文档的规范明确说明,否则实现必须在编码数据末尾包含适当的填充字符。”这可能就是为什么Python的base64不接受未正确填充的字符串。 - Geert
3
警告:这个答案是错误的。它忽略了“-”和“_”字符,这些字符替换了“+”和“/”字符。 - Maarten Bodewes
显示剩余4条评论

12

除了 @dae.eklen 的解决方案,你还可以在它后面添加 ===

s = 'iEPX-SQWIR3p67lj_0zigSWTKHg'
base64.urlsafe_b64decode(s + '===')

这行代码有效是因为Python只会报告缺少填充的错误,而不是额外的填充。


好的,这很有道理,并且根据您的示例可以工作。但我有点困惑,我有一个长度为4的倍数的字符串,没有填充,返回“不正确的填充”错误,当我有一个=时,我仍然遇到同样的问题,但如果我至少有==它就可以工作了。这是怎么回事? - gdvalderrama

6

令人惊讶的是,目前被接受的答案并不完全正确。就像其他答案所述,它被称为base64url编码,并且是RFC7515的一部分。

基本上,他们用“-”和“_”代替了“+”和“/”字符;此外,他们还删除了任何尾随的“=”字符,因为您始终可以通过查看编码后的字符串长度来知道您缺少多少个字符。

这是来自RFC7515的一个C#的说明性例子:

 static string base64urlencode(byte [] arg)
 {
   string s = Convert.ToBase64String(arg); // Regular base64 encoder
   s = s.Split('=')[0]; // Remove any trailing '='s
   s = s.Replace('+', '-'); // 62nd char of encoding
   s = s.Replace('/', '_'); // 63rd char of encoding
   return s;
 }

 static byte [] base64urldecode(string arg)
 {
   string s = arg;
   s = s.Replace('-', '+'); // 62nd char of encoding
   s = s.Replace('_', '/'); // 63rd char of encoding
   switch (s.Length % 4) // Pad with trailing '='s
   {
     case 0: break; // No pad chars in this case
     case 2: s += "=="; break; // Two pad chars
     case 3: s += "="; break; // One pad char
     default: throw new System.Exception(
       "Illegal base64url string!");
   }
   return Convert.FromBase64String(s); // Standard base64 decoder
 }

1
该功能已被提议添加到标准库中(https://bugs.python.org/issue29427)。 - Franklin Yu
1
谢谢!我真的恢复了我的帐户来点赞这个。我一直在尝试将Python代码更改为PHP,并进行一些哈希和编码,然后注意到了这个区别。这救了我!!! - Ray

2
import base64
import simplejson as json

def parse_signed_request( signed_request ):
    encoded_sig, payload = signed_request.split('.',2)
    data = json.loads(base64.b64decode( payload.replace('-_', '+/') ))
    return data

0

我的解决方案是将旧的C#代码翻译成Python。

import base64

def base64_encode_url(value):
    encoded = str(base64.b64encode(bytes(value, "utf-8")), 'utf-8')
    return encoded.replace('=', '').replace('+', '-').replace('/', '_')

def base64_decode_url(data):
    value = data.replace('-', '+').replace('_', '/')
    value += '=' * (len(value) % 4)
    return str(base64.urlsafe_b64decode(value), 'utf-8')

0

这是正确的解决方案。在Python中,有base64.b64encode,但它只进行base64编码,与base64 URL编码不同。以下是将base64编码转换为base64 URL编码字符串的正确步骤:
1. 从结果字符串中,用“_”替换“/”,用“-”替换“+”
2. 去掉尾部的“==”。

Et voila!这将使其成为适用于base64 URL解码的有效字符串。顺便说一句,@dae.eklen的答案中的链接现在已经失效了。


0

如果您在.net中将base64字符串作为参数发送,则似乎具有URI中特殊含义的字符,例如+/会被替换为" "空格。

因此,在发送 .net 中的字符串之前,您应该执行类似于这样的操作

base64img.Replace("+", "-").Replace("/", "_"))

然后在Python中解码字符串(还要添加'=',直到长度可被4整除)

def decode_base64(data):
    data += '=' * (len(data) % 4)
    return base64.urlsafe_b64decode(data)

如果你想在openCV中使用这张图片

def get_cv2_img_from_base64(base_64_string):
    data = decode_base64(base_64_string)
    np_data = np.frombuffer(data, dtype=np.uint8)
    return cv2.imdecode(np_data, cv2.IMREAD_UNCHANGED)

-1

只是

base64.urlsafe_b64decode(s)

1
请编辑您的答案,添加一些解释/文档。 - CMartins
1
即使使用URL安全变体,仍然需要在操作之前调整输入的填充。 - Scott Johnson

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