# -*- coding: utf-8 -*- """ This module contains methods for creating request tokens and encryption/decryption of snaps """ import json import os from hashlib import sha256, sha1, md5 from time import time from datetime import datetime from uuid import uuid4, uuid1 from base64 import b64encode, b64decode from binascii import unhexlify from zipfile import is_zipfile, ZipFile from urllib import urlencode import requests from Crypto.Cipher import AES,PKCS1_OAEP from Crypto.PublicKey import RSA SECRET = b'iEk21fuwZApXlz93750dmW22pw389dPwOk' STATIC_TOKEN = 'm198sOkJEn37DjqZ32lpRu76xmw288xSQ9' BLOB_ENCRYPTION_KEY = 'M02cnQ51Ji97vwT4' HASH_PATTERN = ('00011101111011100011110101011110' '11010001001110011000110001000110') def make_request_token(a, b): hash_a = sha256(SECRET + a.encode('utf-8')).hexdigest() hash_b = sha256(b.encode('utf-8') + SECRET).hexdigest() return ''.join((hash_b[i] if c == '1' else hash_a[i] for i, c in enumerate(HASH_PATTERN))) def get_token(auth_token=None): return STATIC_TOKEN if auth_token is None else auth_token def pkcs5_pad(data, blocksize=16): pad_count = blocksize - len(data) % blocksize return data + (chr(pad_count) * pad_count).encode('utf-8') def decrypt(data): cipher = AES.new(BLOB_ENCRYPTION_KEY, AES.MODE_ECB) return cipher.decrypt(pkcs5_pad(data)) def decrypt_story(data, key, iv): akey = b64decode(key) aiv = b64decode(iv) cipher = AES.new(akey, AES.MODE_CBC, aiv) return cipher.decrypt(pkcs5_pad(data)) def timestamp(): return int(round(time() * 1000)) def encrypt(data): cipher = AES.new(BLOB_ENCRYPTION_KEY, AES.MODE_ECB) return cipher.encrypt(pkcs5_pad(data)) def get_attestation(username, password, timestamp): binary = requests.get('https://api.casper.io/droidguard/create/binary').json() tosend = b64decode(binary['binary']) headers = { 'User-Agent': 'DroidGuard/7329000 (A116 _Quad KOT49H); gzip', 'Content-type': 'application/x-protobuf' } url = 'https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI' androidantiabuse = requests.post(url, tosend, headers=headers) if androidantiabuse.status_code != 200: print('Attestation androidantiabuse HTTP status code != 200') return hashString = username + "|" + password + "|" + timestamp + "|/loq/login" url = 'https://api.casper.io/droidguard/attest/binary' tosend = { 'bytecode_proto': b64encode(androidantiabuse.content), 'nonce': b64encode(sha256(hashString).digest()), 'apk_digest': '5O40Rllov9V8PpwD5zPmmp+GQi7UMIWz2A0LWZA7UX0=' } droidguard = requests.post(url, tosend) if droidguard.status_code != 200: print('Attestation droidguard HTTP status code != 200') return if 'binary' not in droidguard.json(): print('Attestation error: Invalid droidguard JSON / no signedAttestation') return url = 'https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=JSON&key=AIzaSyDqVnJBjE5ymo--oBJt3On7HQx9xNm1RHA' tosend = b64decode(droidguard.json()['binary']) headers = { 'User-Agent': 'SafetyNet/7899000 (WIKO JZO54K); gzip', 'Content-Type': 'application/x-protobuf' } androidcheck = requests.post(url, tosend, headers=headers) if androidcheck.status_code != 200: print('Attestation androidcheck HTTP status code != 200') return if 'signedAttestation' not in androidcheck.json(): print('Attestation error: Invalid androidcheck JSON / no signedAttestation') return return androidcheck.json()['signedAttestation'] def get_client_auth_token(username, password, timestamp): url = 'https://api.casper.io/security/login/signrequest/' tosend = { 'username': username, 'password': password, 'timestamp': timestamp } r = requests.post(url, data=tosend) result = r.json() return result def encryptPassword(email, password): gdpk = "AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pKRI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/6rmf5AAAAAwEAAQ==" binaryKey = b64decode(gdpk).encode('hex') half = binaryKey[8:264] modulus = long(half, 16) half = binaryKey[272:278] exponent = long(half, 16) sha1hash = sha1(b64decode(gdpk)).digest() signature = "00" + sha1hash[:4].encode('hex') key = RSA.construct((modulus, exponent)) cipher = PKCS1_OAEP.new(key) plain = email + "\x00" + password encrypted = cipher.encrypt(plain).encode('hex') ste = signature + encrypted output = unhexlify(ste) encryptedPassword = b64encode(output).encode('ascii').replace("+","-").replace("/","_") return encryptedPassword def get_auth_token(email, password): encryptedPasswd = encryptPassword(email, password) postfields = { 'device_country': 'us', 'operatorCountry': 'us', 'lang': 'en_US', 'sdk_version': '19', 'google_play_services_version': '7097038', 'accountType': 'HOSTED_OR_GOOGLE', 'Email': email, 'service': 'audience:server:client_id:694893979329-l59f3phl42et9clpoo296d8raqoljl6p.apps.googleusercontent.com', 'source': 'android', 'androidId': '378c184c6070c26c', 'app': 'com.snapchat.android', 'client_sig': '49f6badb81d89a9e38d65de76f09355071bd67e7', 'callerPkg': 'com.snapchat.android', 'callerSig': '49f6badb81d89a9e38d65de76f09355071bd67e7', 'EncryptedPasswd': encryptedPasswd } headers = { 'device': '378c184c6070c26c', 'app': 'com.snapchat.android', 'User-Agent': 'GoogleAuth/1.4 (mako JDQ39)', 'Accept-Encoding': 'gzip' } r = requests.post("https://android.clients.google.com/auth", headers=headers, data=postfields, verify=False) if r.status_code == 200: splitted = r.text.split('\n') expiry = datetime.fromtimestamp(int(splitted[2].split('=')[1])) return (splitted[0][5:], expiry) else: print "Invalid gmail address" def request(endpoint, auth_token, data=None, params=None, files=None, raise_for_status=True, req_type='post', moreheaders={}): """Wrapper method for calling Snapchat API which adds the required auth token before sending the request. :param endpoint: URL for API endpoint :param data: Dictionary containing form data :param raise_for_status: Raise exception for 4xx and 5xx status codes :param req_type: The request type (GET, POST). Defaults to POST """ if params is not None: if 'now' in params: now = params['now'] else: now = str(timestamp()) if 'gauth' in params: gauth = params['gauth'] else: gauth = "" else: now = str(timestamp()) gauth = "" if data is None: data = {} headers = { 'User-Agent': 'Snapchat/9.16.2.0 (HTC One; Android 5.0.2#482424.2#21; gzip)', 'Accept-Language': 'en', 'Accept-Locale': 'en_US', 'X-Snapchat-Client-Auth-Token': "Bearer " + gauth } headers.update(moreheaders) URL = 'https://feelinsonice-hrd.appspot.com' if endpoint == '/loq/login': headers.update({ 'Accept-Encoding': 'gzip' }) if endpoint == '/bq/blob': headers.update({ 'X-Timestamp': now }) if endpoint == '/loq/login' or endpoint == '/loq/device_id': req_token = make_request_token(STATIC_TOKEN, now) else: req_token = make_request_token(auth_token, now) if endpoint != '/bq/story_blob': data.update({ 'timestamp': now, 'req_token': req_token }) if req_type == 'post': r = requests.post(URL + endpoint, data=data, files=files, headers=headers, verify=False) else: if gauth == "": headers = None r = requests.get(URL + endpoint, params=data, headers=headers, verify=False) if raise_for_status: r.raise_for_status() return r def make_media_id(username): """Create a unique media identifier. Used when uploading media""" #partial = md5(str(uuid1())).hexdigest() #uuid = "%s-%s-%s-%s-%s" % (partial[:8], partial[8:12], partial[12:16], partial[16:20], partial[20:32]) #print uuid return '{username}~{uuid}'.format(username=username.upper(),uuid=str(uuid1())) def unzip_snap_mp4(abspath, quiet=False): zipped_snap = ZipFile(abspath) # unzip /path/to/zipfile.mp4 to /path/to/zipfile unzip_dir = os.path.splitext(abspath)[0] zipped_snap.extractall(unzip_dir) # move /path/to/zipfile.mp4 to /path/to/zipfile.zip os.rename(abspath, unzip_dir + '.zip') for f in os.listdir(unzip_dir): # mv /path/to/zipfile/media~* /path/to/zipfile.mp4 if f.split('~')[0] == 'media': os.rename(os.path.join(unzip_dir, f), unzip_dir + '.mp4') # mv /path/to/zipfile/overlay~* /path/to/zipfile_overlay.png elif f.split('~')[0] == 'overlay': os.rename(os.path.join(unzip_dir, f), unzip_dir + '_overlay.png') try: os.rmdir(unzip_dir) except OSError: print('Something other than a video or overlay was in {0}. \ Cannot remove directory, not empty.' .format(unzip_dir + '.zip')) if not quiet: print('Unzipped {0}'.format(abspath))