Added a new authentication method to replace official Kate's tokens

This commit is contained in:
Manuel Cortez 2019-01-19 23:20:51 -06:00
parent 076f64788a
commit 29ceca055e
11 changed files with 131 additions and 262 deletions

View File

@ -2,6 +2,9 @@
## changes in this version ## changes in this version
* Changed authentication tokens in Socializer. It is mandatory to download a fresh copy of socializer and start a new configuration for your account.
* Stable versions of Socializer are built with Python 3. Previous versions are built with Python 2, however support for Python 2 will be dropped very soon.
* There is an installer file for Socializer, available in our downloads page. Installed version of Socializer will be more confortable for some people.
* For users from countries where VK is not allowed, Socializer includes a proxy to bypass country restrictions. The first time you start socializer, it will ask you whether you need a proxy or not. It is suggested to use a proxy only if you need it. * For users from countries where VK is not allowed, Socializer includes a proxy to bypass country restrictions. The first time you start socializer, it will ask you whether you need a proxy or not. It is suggested to use a proxy only if you need it.
* Now it is possible to post in someone else's wall. When viewing a timeline of an user, the "post" button will post in his/her wall. To post in your own wall, you'll need to go to the newsfeed or your own wall and press the post button. * Now it is possible to post in someone else's wall. When viewing a timeline of an user, the "post" button will post in his/her wall. To post in your own wall, you'll need to go to the newsfeed or your own wall and press the post button.
* If you are not allowed to post in someone's wall, the post button will not be visible. * If you are not allowed to post in someone's wall, the post button will not be visible.

View File

View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
""" Set of methods used to retrieve access tokens by simulating the official VK application for Android. """
import random
import requests
import logging
from hashlib import md5
from .wxUI import two_factor_auth
log = logging.getLogger("authenticator.official")
class AuthenticationError(Exception):
pass
# Data extracted from official VK android APP.
client_id = "2274003"
client_secret = "hHbZxrka2uZ6jB1inYsH"
api_ver="5.93"
scope = "nohttps,all"
user_agent = "VKAndroidApp/5.23-2978 (Android 4.4.2; SDK 19; x86; unknown Android SDK built for x86; en; 320x240)"
api_url = "https://api.vk.com/method/"
def get_device_id():
""" Generate a random device ID, consisting in 16 alphanumeric characters."""
return "".join(random.choice(["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "a", "b", "c", "d", "e", "f"]) for _ in range(16))
def get_sig(method, values, secret):
""" Create a signature for parameters passed to VK API. """
postdata = ""
for key in values:
postdata = postdata + "{key}={value}&".format(key=key, value=values[key])
# Remove the last "&" character.
postdata = postdata[:-1]
sig = md5(b"/method/"+method.encode("utf-8")+b"?"+postdata.encode("utf-8")+secret.encode("utf-8"))
return sig.hexdigest()
def perform_request(method, postdata, secret):
""" Send a request to VK servers by signing the data with the 'sig' parameter."""
url = api_url+method
sig = get_sig(method, postdata, secret)
postdata.update(sig=sig)
headers = {'User-Agent': user_agent}
r = requests.post(url, data=postdata, headers=headers)
return r.json()
def get_non_refreshed(login, password, scope=scope):
""" Retrieves a non-refreshed token, this should be the first token needed to authenticate in VK.
returns the access_token which still needs to be refreshed, device_id, and secret code, needed to sign all petitions in VK."""
if not (login or password):
raise ValueError
device_id = get_device_id()
# Let's authenticate.
url = "https://oauth.vk.com/token"
params = dict(grant_type="password",
client_id=client_id, client_secret=client_secret, username=login,
password=password, v=api_ver, scope=scope, lang="en", device_id=device_id)
# Add two factor auth later due to python's syntax.
params["2fa_supported"] = 1
headers = {'User-Agent': user_agent}
r = requests.get(url, params=params, headers=headers)
# If a 401 error is raised, we need to use 2FA here.
# see https://vk.com/dev/auth_direct (switch lang to russian, english docs are very incomplete in the matter)
# ToDo: this needs testing after implemented official VK tokens.
if r.status_code == 401 and "phone_mask" in r.text:
t = r.json()
code, remember = two_factor_auth()
url = "https://oauth.vk.com/token"
params = dict(grant_type="password", lang="en",
client_id=client_id, client_secret=client_secret, username=login,
password=password, v=api_ver, scope=scope, device_id=device_id, code=code)
r = requests.get(url, params=params, headers=headers)
if r.status_code == 200 and 'access_token' in r.text:
res = r.json()
# Retrieve access_token and secret.
access_token = res['access_token']
secret = res['secret']
return access_token, secret, device_id
else:
raise AuthenticationError(r.text)
def refresh_token(token, secret, device_id):
method = "execute.getUserInfo"
postdata = dict(v=api_ver, https=1, androidVersion=19, androidModel="Android SDK built for x86", info_fields="audio_ads,audio_background_limit,country,discover_design_version,discover_preload,discover_preload_not_seen,gif_autoplay,https_required,inline_comments,intro,lang,menu_intro,money_clubs_p2p,money_p2p,money_p2p_params,music_intro,audio_restrictions,profiler_settings,raise_to_record_enabled,stories,masks,subscriptions,support_url,video_autoplay,video_player,vklive_app,community_comments,webview_authorization,story_replies,animated_stickers,community_stories,live_section,playlists_download,calls,security_issue,eu_user,wallet,vkui_community_create,vkui_profile_edit,vkui_community_manage,vk_apps,stories_photo_duration,stories_reposts,live_streaming,live_masks,camera_pingpong,role,video_discover", device_id=device_id, lang="en", func_v=11, androidManufacturer="unknown", fields="photo_100,photo_50,exports,country,sex,status,bdate,first_name_gen,last_name_gen,verified,trending", access_token=token)
perform_request(method, postdata, secret)
method = "auth.refreshToken"
postdata = dict(v=api_ver, https=1, timestamp=0, receipt2="", device_id=device_id, receipt="", lang="en", nonce="", access_token=token)
return perform_request(method, postdata, secret)
def login(user, password):
access_token, secret, device_id = get_non_refreshed(user, password)
response = refresh_token(access_token, secret, device_id)
try:
return response["response"]["token"], secret, device_id
except KeyError:
log.exception("An exception has occurred while attempting to authenticate. Printing response returned by vk...")
log.exception(response)

24
src/authenticator/wxUI.py Normal file
View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import time
import wx
import widgetUtils
code = None
remember = True
def two_factor_auth():
global code, remember
wx.CallAfter(get_code)
while code == None:
time.sleep(0.5)
return (code, remember)
def get_code():
global code, remember
dlg = wx.TextEntryDialog(None, _("Please provide the authentication code you have received from VK."), _("Two factor authentication code"))
response = dlg.ShowModal()
if response == widgetUtils.OK:
code = dlg.GetValue()
dlg.Destroy()
dlg.Destroy()

View File

@ -1,16 +0,0 @@
import os
import ssl
from . import utils
try:
context = ssl.create_default_context()
der_certs = context.get_ca_certs(binary_form=True)
pem_certs = [ssl.DER_cert_to_PEM_cert(der) for der in der_certs]
path = os.path.join(utils.getBundleDir(), 'nativecacerts.pem')
with open(path, 'w') as outfile:
for pem in pem_certs:
outfile.write(pem + '\n')
os.environ['REQUESTS_CA_BUNDLE'] = path
except:
pass

View File

@ -1,132 +0,0 @@
from builtins import range
import webbrowser
import random
import requests
import string
from uuid import getnode
def create_mac_string(num, splitter=':'):
"""Return the mac address interpretation of num,
in the form eg '00:11:22:33:AA:BB'.
:param num: a 48-bit integer (eg from uuid.getnode)
:param spliiter: a string to join the hex pairs with
"""
mac = hex(num)[2:]
# trim trailing L for long consts
if mac[-1] == 'L':
mac = mac[:-1]
pad = max(12 - len(mac), 0)
mac = '0' * pad + mac
mac = splitter.join([mac[x:x + 2] for x in range(0, 12, 2)])
mac = mac.upper()
return mac
from . import _sslfixer
from .wxUI import two_factor_auth
class AuthenticationError(Exception):
pass
class ValidationError(Exception):
pass
class C2DMError(Exception):
pass
client_id = '2685278'
client_secret = 'lxhD8OD7dMsqtXIm5IUY'
api_ver='5.92'
scope = 'all'
user_agent = 'KateMobileAndroid/47-427 (Android 6.0.1; SDK 23; armeabi-v7a; samsung SM-G900F; ru)'
mac_int = getnode()
device_id = create_mac_string(mac_int)
android_id = device_id.replace(':', '')
#android_id = '4119748609680577006'
android_token = '5228540069896927210'
api_url = 'https://api.vk.com/method/'
def requestAuth(login, password, scope=scope):
if not (login or password):
raise ValueError
url = 'https://oauth.vk.com/token?grant_type=password&2fa_supported=1&force_sms=1&client_id='+client_id+'&client_secret='+client_secret+'&username='+login+'&password='+password+'&v='+api_ver+'&scope='+scope
headers = {
'User-Agent': user_agent
}
r = requests.get(url, headers=headers)
# If a 401 error is raised, we need to use 2FA here.
# see https://vk.com/dev/auth_direct (switch lang to russian, english docs are very incomplete in the matter)
if r.status_code == 401 and "phone_mask" in r.text:
t = r.json()
code, remember = two_factor_auth()
url = 'https://oauth.vk.com/token?grant_type=password&client_id='+client_id+'&client_secret='+client_secret+'&username='+login+'&password='+password+'&v='+api_ver+'&scope='+scope+'&code='+code
r = requests.get(url, headers=headers)
if r.status_code == 200 and 'access_token' in r.text:
res = r.json()
access_token = res['access_token']
user_id = str(res['user_id'])
return access_token, user_id
else:
raise AuthenticationError(r.text)
def getReceipt(user_id):
if not user_id:
raise ValueError
url = 'https://android.clients.google.com/c2dm/register3'
headers = {
'Authorization': 'AidLogin {0}:{1}'.format(android_id, android_token),
'app': 'com.perm.kate',
'Gcm-ver': '11951438',
'Gcm-cert': 'ca7036ce4c5abe56b9f4439ea275171ceb0d35a4',
#'User-Agent': 'Android-GCM/1.5 (klte NJH47F)',
'content-type': 'application/x-www-form-urlencoded',
}
data = {
'X-subtype': '54740537194',
'X-X-subscription': '54740537194',
'X-X-subtype': '54740537194',
'X-app_ver': '427',
'X-kid': '|ID|1|',
#'X-osv': '23',
'X-cliv': 'iid-9452000',
'X-gmsv': '11951438',
'X-X-kid': '|ID|1|',
'X-appid': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(11)),
'X-scope': 'id'+user_id,
'X-subscription': '54740537194',
'X-app_ver_name': '47',
'app': 'com.perm.kate',
'sender': '54740537194',
'device': android_id,
'cert': 'ca7036ce4c5abe56b9f4439ea275171ceb0d35a4',
'app_ver': '427',
'gcm_ver': '11951438'
}
r = requests.post(url, headers=headers, data=data)
if r.status_code == 200 and 'token' in r.text:
return r.text[13:]
else:
raise C2DMError(r.text)
def validateToken(token, receipt):
if not (token or receipt):
raise ValueError
url = api_url+'auth.refreshToken?access_token='+token+'&receipt='+receipt+'&v='+api_ver
headers = {'User-Agent': user_agent}
r = requests.get(url, headers=headers)
if r.status_code == 200 and 'token' in r.text:
res = r.json()
received_token = res['response']['token']
if token == received_token or received_token is None :
raise ValidationError(r.text)
else:
return received_token
else:
raise ValidationError(r.text)

View File

@ -1,80 +0,0 @@
""" Set of methods used to retrieve access tokens by simulating an official VK application. """
import random
import requests
from hashlib import md5
from .wxUI import two_factor_auth
class AuthenticationError(Exception):
pass
# Data extracted from official VK android APP.
client_id = '2274003'
client_secret = 'hHbZxrka2uZ6jB1inYsH'
api_ver='5.93'
scope = 'nohttps,all'
user_agent = 'VKAndroidApp/5.23-2978 (Android 4.4.2; SDK 19; x86; unknown Android SDK built for x86; en; 320x240)'
api_url = 'https://api.vk.com/method/'
def get_device_id():
""" Generate a random device ID, consisting in 16 alphanumeric characters."""
return "".join(random.choice(["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "a", "b", "c", "d", "e", "f"]) for _ in range(16))
def get_non_refreshed(login, password, scope=scope):
""" Retrieves a non-refreshed token, this should be the first token needed to authenticate in VK.
returns the access_token which still needs to be refreshed, current user_id, and secret code, needed to sign all petitions in VK."""
if not (login or password):
raise ValueError
device_id = get_device_id()
# Let's authenticate.
url = "https://oauth.vk.com/token"
params = dict(grant_type="password", lang="en",
client_id=client_id, client_secret=client_secret, username=login,
password=password, v=api_ver, scope=scope, device_id=device_id)
# Add two factor auth later due to python's syntax.
params["2fa_supported"] = 1
headers = {'User-Agent': user_agent}
r = requests.get(url, params=params, headers=headers)
# If a 401 error is raised, we need to use 2FA here.
# see https://vk.com/dev/auth_direct (switch lang to russian, english docs are very incomplete in the matter)
# ToDo: this needs testing after implemented official VK tokens.
if r.status_code == 401 and "phone_mask" in r.text:
t = r.json()
code, remember = two_factor_auth()
url = "https://oauth.vk.com/token"
params = dict(grant_type="password", lang="en",
client_id=client_id, client_secret=client_secret, username=login,
password=password, v=api_ver, scope=scope, device_id=device_id, code=code)
r = requests.get(url, params=params, headers=headers)
if r.status_code == 200 and 'access_token' in r.text:
res = r.json()
# Retrieve access_token, user_id and secret.
access_token = res['access_token']
print(access_token)
user_id = str(res['user_id'])
secret = res['secret']
# return access_token, user_id, secret
response = refresh_token(access_token, secret, device_id)
print(response["response"]["token"])
return response["response"]["token"], secret, device_id
else:
raise AuthenticationError(r.text)
def perform_request(method, postdata, secret):
""" Send a request to VK servers by signing the data with the 'sig' parameter."""
url = "https://api.vk.com/method/"+method
sig = md5(b"/method/"+method.encode("utf-8")+b"?"+postdata.encode("utf-8")+secret.encode("utf-8"))
postdata = postdata+"&sig="+sig.hexdigest()
headers = {
'User-Agent': user_agent
}
r = requests.post(url+"?"+postdata, headers=headers)
return r.json()
def refresh_token(token, secret, device_id):
method = "execute.getUserInfo"
postdata = "v=5.93&https=1&androidVersion=19&androidModel=Android SDK built for x86&info_fields=audio_ads,audio_background_limit,country,discover_design_version,discover_preload,discover_preload_not_seen,gif_autoplay,https_required,inline_comments,intro,lang,menu_intro,money_clubs_p2p,money_p2p,money_p2p_params,music_intro,audio_restrictions,profiler_settings,raise_to_record_enabled,stories,masks,subscriptions,support_url,video_autoplay,video_player,vklive_app,community_comments,webview_authorization,story_replies,animated_stickers,community_stories,live_section,playlists_download,calls,security_issue,eu_user,wallet,vkui_community_create,vkui_profile_edit,vkui_community_manage,vk_apps,stories_photo_duration,stories_reposts,live_streaming,live_masks,camera_pingpong,role,video_discover&device_id="+device_id+"&lang=en&func_v=11&androidManufacturer=unknown&fields=photo_100,photo_50,exports,country,sex,status,bdate,first_name_gen,last_name_gen,verified,trending&access_token="+token
perform_request(method, postdata, secret)
method = "auth.refreshToken"
postdata = "v=5.93&https=1&timestamp=0&receipt=''&receipt2=''&device_id="+device_id+"&lang=en&nonce=''&access_token="+token
return perform_request(method, postdata, secret)

View File

@ -111,9 +111,10 @@ class vkSession(object):
def login(self): def login(self):
try: try:
config_filename = os.path.join(paths.config_path(), self.session_id, "vkconfig.json") config_filename = os.path.join(paths.config_path(), self.session_id, "vkconfig.json")
self.vk.login(self.settings["vk"]["user"], self.settings["vk"]["password"], token=self.settings["vk"]["token"], alt_token=self.settings["vk"]["use_alternative_tokens"], filename=config_filename) self.vk.login(self.settings["vk"]["user"], self.settings["vk"]["password"], token=self.settings["vk"]["token"], secret=self.settings["vk"]["secret"], device_id=self.settings["vk"]["device_id"], alt_token=self.settings["vk"]["use_alternative_tokens"], filename=config_filename)
self.settings["vk"]["token"] = self.vk.session_object.token["access_token"] self.settings["vk"]["token"] = self.vk.session_object.token["access_token"]
self.settings["vk"]["secret"] = self.vk.session_object.secret self.settings["vk"]["secret"] = self.vk.session_object.secret
self.settings["vk"]["device_id"] = self.vk.session_object.device_id
self.settings.write() self.settings.write()
self.logged = True self.logged = True
self.get_my_data() self.get_my_data()

View File

@ -1,9 +1,9 @@
#!/usr/bin/python #!/usr/bin/python
import keys import keys
import logging import logging
from . import gettokens from authenticator import official
from vk_api.audio import VkAudio from vk_api.audio import VkAudio
from . wxUI import two_factor_auth from authenticator.wxUI import two_factor_auth
log = logging.getLogger("vkSessionHandler") log = logging.getLogger("vkSessionHandler")
@ -12,14 +12,14 @@ class vkObject(object):
def __init__(self): def __init__(self):
self.api_key = keys.keyring.get_api_key() self.api_key = keys.keyring.get_api_key()
def login(self, user, password, token, alt_token, filename): def login(self, user, password, token, secret, device_id, alt_token, filename):
if alt_token == False: if alt_token == False:
log.info("Using kate's token...") log.info("Using kate's token...")
# Let's import the patched vk_api module for using a different user agent # Let's import the patched vk_api module for using a different user agent
from . import vk_api_patched as vk_api from . import vk_api_patched as vk_api
if token == "" or token == None: if token == "" or token == None:
log.info("Token is not valid. Generating one...") log.info("Token is not valid. Generating one...")
original_token = gettokens.get_non_refreshed(user, password) original_token = official.login(user, password)
token = original_token[0] token = original_token[0]
secret = original_token[1] secret = original_token[1]
device_id = original_token[2] device_id = original_token[2]

View File

@ -7,6 +7,7 @@ import logging
import vk_api import vk_api
import threading import threading
import requests import requests
from authenticator.official import get_sig
from . import jconfig_patched as jconfig from . import jconfig_patched as jconfig
from vk_api.enums import VkUserPermissions from vk_api.enums import VkUserPermissions
from vk_api.exceptions import * from vk_api.exceptions import *
@ -93,7 +94,7 @@ class VkApi(vk_api.VkApi):
if delay > 0: if delay > 0:
time.sleep(delay) time.sleep(delay)
values.update(https=1, device_id=self.device_id) values.update(https=1, device_id=self.device_id)
sig = self.get_sig(method, values, self.secret) sig = get_sig(method, values, self.secret)
values.update(sig=sig) values.update(sig=sig)
response = self.http.post( response = self.http.post(
'https://api.vk.com/method/' + method, 'https://api.vk.com/method/' + method,
@ -134,11 +135,3 @@ class VkApi(vk_api.VkApi):
raise error raise error
return response if raw else response['response'] return response if raw else response['response']
def get_sig(self, method, values, secret):
postdata = ""
for key in values:
postdata = postdata + "{key}={value}&".format(key=key, value=values[key])
postdata = postdata[:-1]
sig = hashlib.md5(b"/method/"+method.encode("utf-8")+b"?"+postdata.encode("utf-8")+secret.encode("utf-8"))
return sig.hexdigest()

View File

@ -1,31 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import time
import wx import wx
import widgetUtils import widgetUtils
code = None
remember = True
def new_account_dialog(): def new_account_dialog():
return wx.MessageDialog(None, _("In order to continue, you need to configure your VK account before. Would you like to autorhise a new account now?"), _("Authorisation"), wx.YES_NO).ShowModal() return wx.MessageDialog(None, _("In order to continue, you need to configure your VK account before. Would you like to autorhise a new account now?"), _("Authorisation"), wx.YES_NO).ShowModal()
def two_factor_auth():
global code, remember
wx.CallAfter(get_code)
while code == None:
time.sleep(0.5)
return (code, remember)
def get_code():
global code, remember
dlg = wx.TextEntryDialog(None, _("Please provide the authentication code you have received from VK."), _("Two factor authentication code"))
response = dlg.ShowModal()
if response == widgetUtils.OK:
code = dlg.GetValue()
dlg.Destroy()
dlg.Destroy()
class newSessionDialog(widgetUtils.BaseDialog): class newSessionDialog(widgetUtils.BaseDialog):
def __init__(self): def __init__(self):
super(newSessionDialog, self).__init__(parent=None, id=wx.NewId(), title=_("Authorise VK")) super(newSessionDialog, self).__init__(parent=None, id=wx.NewId(), title=_("Authorise VK"))