简体   繁体   中英

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:

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

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

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.

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.

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
 }
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

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.

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)

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')

just

base64.urlsafe_b64decode(s)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM