33

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:

http://pastie.org/1054154

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
3
  • thanks i tried base64 , but i am getting this error: pastie.org/1054201 Commented Jul 21, 2010 at 19:42
  • Please actually post the smallest code that shows the error and the actual error. Most of us don't have the patiences to follow links all over the place. Commented Jul 21, 2010 at 19:44
  • Note: If you're by chance using Azure blob URLs returned from a search, you need to strip out the trailing '0' from the encoded URL stackoverflow.com/questions/44338134/… Commented Aug 24, 2019 at 0:31

10 Answers 10

44

try

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

as it is written here

Sign up to request clarification or add additional context in comments.

4 Comments

Make sure that the string s you work with is instance of str - unicode would fail with error. If that is your case use str(s) function for conversion.
Link is broken.
Link is still broken ~18 months later; suppose this is one of the reasons why simply adding links to external sites are generally discouraged on SO.
This only works for all cases as urlsafe_b64decode accepts superfluous padding as long as it's padded to a multiple of 4 bytes. If it didn't (or if the function gets changed in the future), it would fail for some cases. The correct way to add padding is s + '=' * ((4 - len(s)) % 4) (note how the extra set of parentheses changes the order of operations) and can then be simplified to the equivalent s + '=' * (-len(s) % 4).
25

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

2 Comments

The solution by dae.eklen does the same and is more elegant. (base64.urlsafe_b64decode(s + '=' * (4 - len(s) % 4)))
Thanks. That's a pretty short code snippet- it would be great to see it included in this answer.
23

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.

9 Comments

Geert, thanks for this. but that is exactly the code that i got from facebook and it did not have = at the end. is this expected?
This is not to be expected I would say. However, you can verify the length of the base64 input by checking the length of it: the length must always be a multiple of 4 bytes (this is actually the reason why the decoder threw an error). If it's not, you can add is-equal signs until it is and then the string will be decoded correctly.
Seems = padding is not always required in all variants: en.wikipedia.org/wiki/Base64
RFC 3548 & RFC 4648 both state that "...implementations MUST include appropriate pad characters at the end of encoded data unless the specification referring to this document explicitly states otherwise." That's probably why Python's base64 does not accept strings that are not correctly padded.
Warning this answer is wrong; it forgets about the - and _ characters that replaces the + and / characters.
|
14

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.

1 Comment

Ok, this makes sense and works with your example. I'm confused though, I have a string of a length multiple of 4 with no padding returning the Incorrect padding error, when I had an =, I still get the same problem, but if I had at least == it works. What's up with that?
6

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
 }

2 Comments

THANK YOU! I literally recovered by account to upvote this. I have been trying to change Python code to PHP with some hashing and encoding and noticed that difference. THIS SAVED ME!!!!!
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

Comments

1

My solution was to translate old c# code to 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')

Comments

0

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.

Comments

0

If you are sending your base64 string from .net as a param it seems that chars that have special meaning in the URI ie + or / are replaced with " " spaces.

So before you send your string in .net you should probably do something like this

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

Then in python decode the string (also add '=' until the length is divisible by 4)

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

Further if you want to use the image in 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)

Comments

-1

just

base64.urlsafe_b64decode(s)

2 Comments

Please edit your answer adding some explanation/documentation.
Even with the URL-safe variant, you still need to adjust the padding on the input before the operation will work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.