How to decode base64 url in python?
For Facebook fbml Apps Facebook is sending in a signed_request parameter explained here:
http://developers.facebook.com/docs/authentication/canvas
They have given the php version of decoding this signed request:
How to do the same in python?
I tried base64 module but I am getting Incorrect padding error:
>>> 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
Answers
Apparently you missed the last two characters when copying the original base64-encoded string. Suffix the input string with two is-equal (=) signs and it will be decoded correctly.
I have shared a code snippet for parsing signed_request parameter in a python based facebook canvas application at 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
try
s = 'iEPX-SQWIR3p67lj_0zigSWTKHg' base64.urlsafe_b64decode(s + '=' * (4 - len(s) % 4))
as it is written here
Alternative to @dae.eklen's solution, you can append === to it:
s = 'iEPX-SQWIR3p67lj_0zigSWTKHg' base64.urlsafe_b64decode(s + '===')
This works because Python only complains about missing padding, but not extra padding.
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
Surprising, but currently accepted answer is not exactly correct. Like some other answers stated, it's something called base64url encoding, and it's a part of RFC7515.
Basically, they replaced '+' and '/' chars by '-' and '_' respectively; and additionally removed any trailing '=' chars, because you can always tell how many chars you're missing, just by looking at the encoded string length.
Here's illustrative example from RFC7515 in 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 }
This is the right solution. In python there is base64.b64encode but that only base64 encodes and its is different from base64 url encoding. Here is the right set to of steps to convert form base64encoded to base64urlencoded string: 1. From the resultant string, replace "/" with "_" and "+" with "-" 2. Strip the trailing "==".
Et voila! That will make it a valid string for base64 url decoding. Btw, that link in @dae.eklen 's answer above is broken now.