CryptoJS encrypted string can pass different parameters in python can only pass a string? How to implement the second CryptoJS implementation in python
,how to get clientKey2,This will only give the first result.Thanks!
> saltedPassword=CryptoJS.PBKDF2("key", "salt", {keySize: 8,iterations:500,hasher: CryptoJS.algo.SHA256});
> clientKey1=CryptoJS.HmacSHA256(saltedPassword.toString(), "Client Key")
> clientKey2=CryptoJS.HmacSHA256(saltedPassword, "Client Key")
> clientKey1.toString()
> "857ef8988876a3bb6bcadb85ca257787074e73e830d7dc14c1f838ba46aef1f5"
> clientKey2.toString()
> "9a8574da9b276ee1162dcb92071df587f4513bc03060bda1e9b3897d46233416"
> saltedPassword.toString()
> "6e441ccd26e6b35198b4b17457dc0266d36b751d0062b5850b0e302ceb1d6dcc"
i use this way can get clientKey1,
import hashlib
import hmac
def HmacSHA256(k,v):
message = bytes(k).encode('utf-8')
secret = bytes(v).encode('utf-8')
signature = hmac.new(secret, message, digestmod=hashlib.sha256).hexdigest()
return signature
signature = HmacSHA256("6e441ccd26e6b35198b4b17457dc0266d36b751d0062b5850b0e302ceb1d6dcc","Client Key")
print signature
How to get the second result in Python,Thanks!
To get the desired clientKey2 you need to encode the hex digits of your saltedPassword string to bytes. One way to do that which works on both Python 2 & Python 3 is to use binascii.unhexlify.
Your HmacSHA256 function is a bit odd. It won't work on Python 3, since bytes objects don't have an .encode method. In Python 2, bytes is just a synonym for str.
Anyway, here's some code that works on both Python 2 & Python 3.
from __future__ import print_function
import hashlib
import hmac
import binascii
key = "Client Key".encode()
salted = "6e441ccd26e6b35198b4b17457dc0266d36b751d0062b5850b0e302ceb1d6dcc"
raw = binascii.unhexlify(salted)
signature = hmac.new(key, salted.encode(), digestmod=hashlib.sha256).hexdigest()
print(signature)
signature = hmac.new(key, raw, digestmod=hashlib.sha256).hexdigest()
print(signature)
output
857ef8988876a3bb6bcadb85ca257787074e73e830d7dc14c1f838ba46aef1f5
9a8574da9b276ee1162dcb92071df587f4513bc03060bda1e9b3897d46233416
The output is identical on Python 2 & Python 3.
BTW, it would be simpler to do this task in Python 3, which makes a clean distinction between text and byte strings. Also, the Python 3 hashlib module has a pbkdf2 function.
Related
I'm trying to encode a has which is computed by hmac sha256 in Haskell and sometimes my function has different output when compared to it's Python counterpart.
This is the python funciton:
import requests
import json
import hmac
import hashlib
import base64
import time
def strToSign(time, method, endpoint, body):
return time + method + endpoint + body
str_to_sign2 = strToSign('1','GET','/api/v1/position?symbol=XBTUSDM','')
signature2 = base64.b64encode(
hmac.new(api_secret.encode('utf-8'), str_to_sign2.encode('utf-8'), hashlib.sha256).digest())
And this is the Haskell function:
import qualified Data.ByteString.Char8 as BC
import qualified Data.Text as T
import qualified Data.ByteString.Base64.URL as U
import Data.Text.Encoding (encodeUtf8)
import qualified Crypto.Hash.SHA256 as H
apiSignTest :: BC.ByteString -> BC.ByteString -> BC.ByteString -> BC.ByteString -> IO BC.ByteString
apiSignTest time method endpoint body = do
let timeStamp = time
let secret = mconcat [timeStamp,method,endpoint,body]
let hash = H.hmac (BC.pack C.apiSecretFuture) secret
return $ (encodeUtf8 . U.encodeBase64) hash
some examples where the encoded outputs are different
Haskell : b'KbCFw8OYGeGB433L93vQvbsnzSXxG88r_-HR5AGDJmo='
Python : "KbCFw8OYGeGB433L93vQvbsnzSXxG88r/+HR5AGDJmo="
Python : b'dwSmCd75wZToIDt6I0Ik/sX8Vxk4W+RA0Sv1TO+x4WI='
Haskell : "dwSmCd75wZToIDt6I0Ik_sX8Vxk4W-RA0Sv1TO-x4WI="
Python : b'X8SE3ohju6VAu2Dt5nGIQP40+KU9RrhXORAUOdL7rJg='
Haskell : "X8SE3ohju6VAu2Dt5nGIQP40-KU9RrhXORAUOdL7rJg="
Data.ByteString.Base64.URL.encodeBase64 is specifically using the base64url encoding rather than vanilla base64. The purpose, as the name suggests, is that these encodings can be directly embedded in URLs, which the vanilla version cannot because / and + have special meanings in URLs.
To get the same behaviour as Python's b64encode, use Data.ByteString.Base64.encodeBase64 instead. Or, the other way around, in Python you can use urlsafe_b64encode.
I am communicating with our clients server. For an api I need to sign a string with my private key. They have the following condition to follow
User SHA 256 algorithm to calculate the hash of the string
Use the private key and RSA (PKCS1_PADDING) algorithm to sign the Hash Value.
Base64 encode the encrypted Hash Value
and I am doing following
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
import base64
pkey = RSA.importKey(keystring)
message = "Hello world"
h = SHA256.new(message.encode())
signature = PKCS1_v1_5.new(pkey).sign(h)
result = base64.b64encode(signature).decode()
Here I am getting a string as result. But on the server side my signature is not matching.
Is there anything I am going wrong with ?? Can anyone help me on this ?
I came back to this question recently and noticed it was never resolved. I don't know what was going wrong with the OPs setup but the following code worked for me.
First, the python code that generates the signature of "Hello world":
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.Hash import SHA256
from Cryptodome.PublicKey import RSA
import base64
def sign(message: str, private_key_str: str) -> str:
priv_key = RSA.importKey(private_key_str)
h = SHA256.new(message.encode('utf-8'))
signature = PKCS1_v1_5.new(priv_key).sign(h)
result = base64.b64encode(signature).decode()
return result
And now the Java code that verifies it:
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
...
...
public static boolean verify(String message, String b64Sig, byte[] pubkey_spki) throws GeneralSecurityException {
var pubKey = (PublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pubkey_spki));
var verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(pubKey);
verifier.update(message.getBytes(StandardCharsets.UTF_8));
return verifier.verify(Base64.getDecoder().decode(b64Sig));
}
Perhaps the trickiest part of this is specifying the correct padding scheme in each language/library. These signatures use the scheme identified as RSASSA-PKCS1-v1_5 in the PKCS#1 RFC 8017. On the python side this is accomplished by providing the SHA256 hash object to the PKCS1_v1_5 signature object. In Java it is perhaps a little more straightforward in that you ask for Signature object that implements the RSA algorithm with SHA256 as the hash function, but still have to know that this is RSASSA-PKCS1-v1_5 and not some other possibility in RFC 8017.
I think if one is not already something of an expert then understanding that these magic choices in python and Java produce compatible code is going to be difficult.
Currently I have some code that signs a byte string with the SHA256 algorithm using the native OpenSSL binary, the code calls an external process, sends the parameters, and receive the result back into the Python code.
The current code is as follows:
signed_digest_proc = subprocess.Popen(
['openssl', 'dgst', '-sha256', '-sign', tmp_path],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
signed_digest_proc.stdin.write(original_string)
signed_digest, _ = signed_digest_proc.communicate()
base64.encodestring(signed_digest).decode().replace('\n', '')
When original_string is too big, I might have problems with the result (from the communication with an external process I think), that's why I'm trying to change it to a Python only solution:
import hmac, hashlib
h = hmac.new(bytes(key_pem(), 'ASCII'), original_string, hashlib.sha256)
result = base64.encodestring(h).decode().replace('\n', '')
This result in a completely different string than the first one.
What would be the way to implement the original code without calling an external process?
The openssl command you used does three things:
Create a hash of the data, using SHA256
If RSA is used, pad out the message to a specific length, using PKCS#1 1.5
Sign the (padded) hash, using the private key you provided. It'll depend on the type of key what algorithm was used.
The hmac module does not serve the same function.
You'll need to install a cryptography package like cryptography to replicate what openssl dgst -sign does. cryptography uses OpenSSL as a backend, so it will produce the same output.
You can then
load the key with the load_pem_private_key() function. This returns the right type of object for the algorithm used.
use the key to sign the message; each key type has a sign() method, and this method will take care of hashing the message for you if you so wish. See for example the Signing section for RSA.
However, you'll need to provide different kinds of config for the different .sign() methods. Only the RSA, DSA and Elliptic Curve keys can be used to create a signed digest.
You'll have to switch between the types to get the signature right:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, utils
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
# configuration per key type, each lambda takes a hashing algorithm
_signing_configs = (
(dsa.DSAPrivateKey, lambda h: {
'algorithm': h}),
(ec.EllipticCurvePrivateKey, lambda h: {
'signature_algorithm': ec.ECDSA(h)}),
(rsa.RSAPrivateKey, lambda h: {
'padding': padding.PKCS1v15(),
'algorithm': h
}),
)
def _key_singing_config(key, hashing_algorithm):
try:
factory = next(
config
for type_, config in _signing_configs
if isinstance(key, type_)
)
except StopIteration:
raise ValueError('Unsupported key type {!r}'.format(type(key)))
return factory(hashing_algorithm)
def sign(private_key, data, algorithm=hashes.SHA256()):
with open(private_key, 'rb') as private_key:
key = serialization.load_pem_private_key(
private_key.read(), None, default_backend())
return key.sign(data, **_key_singing_config(key, algorithm))
If you need to hash a large amount of data, you can hash the data yourself first, in chunks, before passing in just the digest and the special util.Prehashed() object:
def sign_streaming(private_key, data_iterable, algorithm=hashes.SHA256()):
with open(private_key, 'rb') as private_key:
key = serialization.load_pem_private_key(
private_key.read(), None, default_backend())
hasher = hashes.Hash(algorithm, default_backend())
for chunk in data_iterable:
hasher.update(chunk)
digest = hasher.finalize()
prehashed = utils.Prehashed(algorithm)
return key.sign(digest, **_key_singing_config(key, prehashed))
with open(large_file, 'rb') as large_file:
signature = sign_streaming(private_key_file, iter(lambda: large_file.read(2 ** 16), b''))
This uses the iter() function to read data from a binary file in chunks of 64 kilobytes.
Demo; I'm using an RSA key I generated in /tmp/test_rsa.pem. Using the command-line to produce a signed digest for Hello world!:
$ echo -n 'Hello world!' | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
R1bRhzEr+ODNThyYiHbiUackZpx+TCviYR6qPlmiRGd28wpQJZGnOFg9tta0IwkT
HetvITcdggXeiqUqepzzT9rDkIw6CU7mlnDRcRu2g76TA4Uyq+0UzW8Ati8nYCSx
Wyu09YWaKazOQgIQW3no1e1Z4HKdN2LtZfRTvATk7JB9/nReKlXgRjVdwRdE3zl5
x3XSPlaMwnSsCVEhZ8N7Gf1xJf3huV21RKaXZw5zMypHGBIXG5ngyfX0+aznYEve
x1uBrtZQwUGuS7/RuHw67WDIN36aXAK1sRP5Q5CzgeMicD8d9wr8St1w7WtYLXzY
HwzvHWcVy7kPtfIzR4R0vQ==
or using the Python code:
>>> signature = sign(keyfile, b'Hello world!')
>>> import base64
>>> print(base64.encodebytes(signature).decode())
R1bRhzEr+ODNThyYiHbiUackZpx+TCviYR6qPlmiRGd28wpQJZGnOFg9tta0IwkTHetvITcdggXe
iqUqepzzT9rDkIw6CU7mlnDRcRu2g76TA4Uyq+0UzW8Ati8nYCSxWyu09YWaKazOQgIQW3no1e1Z
4HKdN2LtZfRTvATk7JB9/nReKlXgRjVdwRdE3zl5x3XSPlaMwnSsCVEhZ8N7Gf1xJf3huV21RKaX
Zw5zMypHGBIXG5ngyfX0+aznYEvex1uBrtZQwUGuS7/RuHw67WDIN36aXAK1sRP5Q5CzgeMicD8d
9wr8St1w7WtYLXzYHwzvHWcVy7kPtfIzR4R0vQ==
Although the line lengths differ, the base64 data the two output is clearly the same.
Or, using a generated file with random binary data, size 32kb:
$ dd if=/dev/urandom of=/tmp/random_data.bin bs=16k count=2
2+0 records in
2+0 records out
32768 bytes transferred in 0.002227 secs (14713516 bytes/sec)
$ cat /tmp/random_data.bin | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrF
tIMb0Ti60TtBhbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweF
yyzWNaTBYATTt0RF0fkVioE6Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBy
dU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB3IIIDFP6rN0lY1KpFfdBPlXqZlJw
PJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqpn6+ojTElGJZXCnn7
Sn0Oh3FidCxIeO/VIhgiuQ==
Processing the same file in Python:
>>> with open('/tmp/random_data.bin', 'rb') as random_data:
... signature = sign_streaming('/tmp/test_rsa.pem', iter(lambda: random_data.read(2 ** 16), b''))
...
>>> print(base64.encodebytes(signature).decode())
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrFtIMb0Ti60TtB
hbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweFyyzWNaTBYATTt0RF0fkVioE6
Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBydU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB
3IIIDFP6rN0lY1KpFfdBPlXqZlJwPJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqp
n6+ojTElGJZXCnn7Sn0Oh3FidCxIeO/VIhgiuQ==
I am trying to encrypt a small string using Python and Ruby. I've written code in both these languages that should do exactly the same thing:
In Python:
from Crypto.PublicKey import RSA
from Crypto.Util import asn1
from Crypto import Random
import sys, time, signal, socket, requests, re, base64
pubkey = "9B596422997705A8805F25232C252B72C6B68752446A30BF9117783FE094F8559CA4A7AA5DBECAEC163596E96CD9577BDF232EF2F45DC474458BDA8EC272311924B8A176896E690135323D16800CFB9661352737FEDA9FB8DD44B6025EA8037CBA136DAE2DC0061B2C66A6F564E2C9E9579DAFD9BFF09ACF3B6E39BF363370F4A8AD1F88E3AE9A7F2C1B1C75AC00EAE308F57EC9FBDA244FC8B0D1234677A6BEE228FEE00BF653E8E010D0E59A30D0A1194298E052399A62E6FBD490CF03E16A1166F901E996D53DE776169B4EE8705E1512CCB69F8086C66213667A070A65DA28AF18FC8FC01D37158706A0B952D0963A9A4E4A6451758710C9ADD1245AB057389535AB0FA1D363A29ED8AE797D1EE958352E55D4AD4565C826E9EF12FA53AE443418FD704091039E190690FD55BF32E7E8C7D7668B8F0550C5E650C7D021F63A5055B7C1AEE6A669079494C4B964C6EA7D131FA1662CF5F5C83721D6F218038262E9DDFE236015EE331A8556D934F405B4203359EE055EA42BE831614919539A183C1C6AD8002E7E58E0C2BCA8462ADBF3916C62857F8099E57C45D85042E99A56630DF545D10DD338410D294E968A5640F11C7485651B246C5E7CA028A5368A0A74E040B08DF84C8676E568FC12266D54BA716672B05E0AA4EE40C64B358567C18791FD29ABA19EACA4142E2856C6E1988E2028703B3E283FA12C8E492FDB"
foobar = "foobar"
pubkey_int = long(pubkey,16)
pub_exp = 65537L
pubkey_obj = RSA.construct((pubkey_int, pub_exp))
encypted_data = pubkey_obj.encrypt(foobar, pub_exp)
encypted_data_b64 = base64.b64encode(encypted_data[0])
print encypted_data_b64
In Ruby:
require 'openssl'
require 'base64'
pubkey = "9B596422997705A8805F25232C252B72C6B68752446A30BF9117783FE094F8559CA4A7AA5DBECAEC163596E96CD9577BDF232EF2F45DC474458BDA8EC272311924B8A176896E690135323D16800CFB9661352737FEDA9FB8DD44B6025EA8037CBA136DAE2DC0061B2C66A6F564E2C9E9579DAFD9BFF09ACF3B6E39BF363370F4A8AD1F88E3AE9A7F2C1B1C75AC00EAE308F57EC9FBDA244FC8B0D1234677A6BEE228FEE00BF653E8E010D0E59A30D0A1194298E052399A62E6FBD490CF03E16A1166F901E996D53DE776169B4EE8705E1512CCB69F8086C66213667A070A65DA28AF18FC8FC01D37158706A0B952D0963A9A4E4A6451758710C9ADD1245AB057389535AB0FA1D363A29ED8AE797D1EE958352E55D4AD4565C826E9EF12FA53AE443418FD704091039E190690FD55BF32E7E8C7D7668B8F0550C5E650C7D021F63A5055B7C1AEE6A669079494C4B964C6EA7D131FA1662CF5F5C83721D6F218038262E9DDFE236015EE331A8556D934F405B4203359EE055EA42BE831614919539A183C1C6AD8002E7E58E0C2BCA8462ADBF3916C62857F8099E57C45D85042E99A56630DF545D10DD338410D294E968A5640F11C7485651B246C5E7CA028A5368A0A74E040B08DF84C8676E568FC12266D54BA716672B05E0AA4EE40C64B358567C18791FD29ABA19EACA4142E2856C6E1988E2028703B3E283FA12C8E492FDB"
foobar = "foobar"
asn1_sequence = OpenSSL::ASN1::Sequence.new(
[
OpenSSL::ASN1::Integer.new("0x#{pubkey}".to_i(16)),
OpenSSL::ASN1::Integer.new("0x10001".to_i(16))
]
)
public_key = OpenSSL::PKey::RSA.new(asn1_sequence)
data = Base64.encode64(public_key.public_encrypt(foobar))
puts data
Both these scripts are trying to encrypt the string foobar using the same public key. I expected both of them to output the same results each time, however this is not the case. Furthermore, every time the Ruby Script is executed, it outputs a different result.
Can someone help me identify the difference between these two scripts that is responsible for this behavior?
I am able to solve this issue by reading the documentation for Class _RSAobj (https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey.RSA._RSAobj-class.html#encrypt)
Attention: this function performs the plain, primitive RSA encryption
(textbook). In real applications, you always need to use proper
cryptographic padding, and you should not directly encrypt data with
this method. Failure to do so may lead to security vulnerabilities. It
is recommended to use modules Crypto.Cipher.PKCS1_OAEP or
Crypto.Cipher.PKCS1_v1_5 instead.
Looks like cryptographic padding is not used by default in the RSA module for Python, hence the difference.
Modified Python Script:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import sys, time, signal, socket, requests, re, base64
pubkey = "9B596422997705A8805F25232C252B72C6B68752446A30BF9117783FE094F8559CA4A7AA5DBECAEC163596E96CD9577BDF232EF2F45DC474458BDA8EC272311924B8A176896E690135323D16800CFB9661352737FEDA9FB8DD44B6025EA8037CBA136DAE2DC0061B2C66A6F564E2C9E9579DAFD9BFF09ACF3B6E39BF363370F4A8AD1F88E3AE9A7F2C1B1C75AC00EAE308F57EC9FBDA244FC8B0D1234677A6BEE228FEE00BF653E8E010D0E59A30D0A1194298E052399A62E6FBD490CF03E16A1166F901E996D53DE776169B4EE8705E1512CCB69F8086C66213667A070A65DA28AF18FC8FC01D37158706A0B952D0963A9A4E4A6451758710C9ADD1245AB057389535AB0FA1D363A29ED8AE797D1EE958352E55D4AD4565C826E9EF12FA53AE443418FD704091039E190690FD55BF32E7E8C7D7668B8F0550C5E650C7D021F63A5055B7C1AEE6A669079494C4B964C6EA7D131FA1662CF5F5C83721D6F218038262E9DDFE236015EE331A8556D934F405B4203359EE055EA42BE831614919539A183C1C6AD8002E7E58E0C2BCA8462ADBF3916C62857F8099E57C45D85042E99A56630DF545D10DD338410D294E968A5640F11C7485651B246C5E7CA028A5368A0A74E040B08DF84C8676E568FC12266D54BA716672B05E0AA4EE40C64B358567C18791FD29ABA19EACA4142E2856C6E1988E2028703B3E283FA12C8E492FDB"
foobar = "foobar"
pubkey_int = long(pubkey,16)
pub_exp = 65537L
pubkey_obj = RSA.construct((pubkey_int, pub_exp))
cipher = PKCS1_v1_5.new(pubkey_obj)
encypted_data = cipher.encrypt(foobar)
encypted_data_b64 = base64.b64encode(encypted_data)
print encypted_data_b64
I'm trying to unzip a base64 string,
this is the code I'm using
def unzip_string(s) :
s1 = base64.decodestring(urllib.unquote(s))
sio = StringIO.StringIO(s1)
gzf = gzip.GzipFile(fileobj=sio)
guff = gzf.read()
return json.loads(guff)
i'm getting error Error: Incorrect padding
where I try to unzip the same string using node.js code it works without a problem.
where:
s == H4sIAAAAAAAAA22PW0/CQBCF/8s81wQosdA3TESJhhhb9cHwMN1O6Ybtbt0LhDT97+5yU4yPc+bMnO90YCyyDaSfHRimieQSG4IUaldABC1qbAykHbQsrzWZWokSUumEiMCQ3nJGCy9ADH0EFvWarJ+eHv11v4qgEIptqHyTlovzWes0q9HQ3X87Lh80Msp5gDhqzGlN0or9B1pWU5ldxV72c2/ODg0C7lUXu/U2p8XLpY35+6Mmtsn4WqLILFrnTRUKQxFwk7+fSL23+zX215VD/jE16CeojIzhSi5kpQ6xzVkIz76wuSmHRVINRuVtheMxDuLJJB5Nk5hRMkriaTGJh8MDn5LWv8v3bejzvFjez15/5EsNbuZo7FzpHepyJoTaBWqrHfX9N0/UAJ7qAQAA.bi0I1YDZ3V6AXu6aYTGO1JWi5tE5CoZli7aa6bFtqM4
I've seen some suggestions to add '=' and other magic but it just results in the gzip module failing to open the file.
any ideas?
This worked for me (Python 3). The padding is indeed important, as you've seen in other answers:
import base64
import zlib
import json
s = b'H4sIAAAAAAAAA22PW0/CQBCF/8s81wQosdA3TESJhhhb9cHwMN1O6Ybtbt0LhDT97+5yU4yPc+bMnO90YCyyDaSfHRimieQSG4IUaldABC1qbAykHbQsrzWZWokSUumEiMCQ3nJGCy9ADH0EFvWarJ+eHv11v4qgEIptqHyTlovzWes0q9HQ3X87Lh80Msp5gDhqzGlN0or9B1pWU5ldxV72c2/ODg0C7lUXu/U2p8XLpY35+6Mmtsn4WqLILFrnTRUKQxFwk7+fSL23+zX215VD/jE16CeojIzhSi5kpQ6xzVkIz76wuSmHRVINRuVtheMxDuLJJB5Nk5hRMkriaTGJh8MDn5LWv8v3bejzvFjez15/5EsNbuZo7FzpHepyJoTaBWqrHfX9N0/UAJ7qAQAA.bi0I1YDZ3V6AXu6aYTGO1JWi5tE5CoZli7aa6bFtqM4'
decoded = base64.urlsafe_b64decode(s + b'=')
uncompressed = zlib.decompress(decoded, 16 + zlib.MAX_WBITS)
unjsoned = json.loads(uncompressed.decode('utf-8'))
print(unjsoned)
The zlib.decompress(decoded, 16 + zlib.MAX_WBITS) is a slightly more compact way to un-gzip a byte string.