Skip to content

Commit c3dadbb

Browse files
authored
Create kobo.py
1 parent 24c62bd commit c3dadbb

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

DRM/kobo.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import re
2+
import requests
3+
import xml.etree.ElementTree as ET
4+
import string
5+
import io
6+
import base64
7+
import zipfile
8+
import binascii
9+
from Crypto.Cipher import AES
10+
import hashlib
11+
import StringIO
12+
import sys
13+
import os
14+
15+
EMAIL = sys.argv[1]
16+
print EMAIL
17+
PASSWORD = sys.argv[2]
18+
DOMAIN = 'rakuten'
19+
20+
def SHA256(raw):
21+
return hashlib.sha256(raw).hexdigest()
22+
23+
def RemoveAESPadding(contents):
24+
lastchar = binascii.b2a_hex(contents[-1:])
25+
strlen = int(lastchar, 16)
26+
padding = strlen
27+
if(strlen == 1):
28+
return contents[:-1]
29+
if(strlen < 16):
30+
for i in range(strlen):
31+
testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
32+
if(testchar != lastchar):
33+
padding = 0
34+
if(padding > 0):
35+
contents = contents[:-padding]
36+
return contents
37+
38+
baseheaders = {'MobileAPIVersion':'4.0',
39+
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/538.1 (KHTML, like Gecko) Kobo Desktop Edition Safari/538.1',}
40+
41+
a = requests.Session()
42+
request = a.post('https://secure.kobobooks.com/90034/auth/%s/Authenticate' % DOMAIN,
43+
params={'wscfv':'2.0','wscf':'kepub','wsa':'kobodesktop','pwspid':'00000000-0000-0000-0000-000000000010','pwsdid':'3','pwspt':'Desktop','pwspov':'6.1.7601','pwsav':'3.15.0'},
44+
data={'RequireSharedToken':'False','AffiliateObject.Name':'Kobo','EditModel.AuthenticationAction':'Authenticate','IsFTE':'False',
45+
'EditModel.Email':EMAIL,'EditModel.Password':PASSWORD})
46+
47+
matcher = re.search('userId=(.*?)&userKey=(.*?)&', request.text)
48+
if matcher is not None:
49+
userid = matcher.group(1)
50+
userkey = matcher.group(2)
51+
else:
52+
matcher = re.search('<input id="InterstitialModel_SUID" name="InterstitialModel.SUID" type="hidden" value="(.*?)"', request.text)
53+
SUID = matcher.group(1)
54+
matcher = re.search('<input id="InterstitialModel_SUK" name="InterstitialModel.SUK" type="hidden" value="(.*?)"', request.text)
55+
SUK = matcher.group(1)
56+
request = a.post('https://secure.kobobooks.com/90034/auth/%s/AcceptInterstitial' % DOMAIN,
57+
params={'wscfv':'2.0','wscf':'kepub','wsa':'kobodesktop','pwspid':'00000000-0000-0000-0000-000000000010','pwsdid':'3','pwspt':'Desktop','pwspov':'6.1.7601','pwsav':'3.15.0'},
58+
data={'RequireSharedToken':'False','AffiliateObject.Name':'Kobo','EditModel.AuthenticationAction':'Interstitial','IsFTE':'False',
59+
'InterstitialModel.SUID':SUID,'InterstitialModel.SUK':SUK,'InterstitialModel.IsNewUser':'False',
60+
'EditModel.PartnerEmailCheckbox.Checked':'false','EditModel.TermsOfUseCheckbox.Checked':'true'})
61+
62+
matcher = re.search('userId=(.*?)&userKey=(.*?)&', request.text)
63+
userid = matcher.group(1)
64+
userkey = matcher.group(2)
65+
66+
print userid
67+
print userkey
68+
69+
subheaders = {"Host": "mobile.kobobooks.com",'Content-Type':'text/xml','MobileRequestType':'TabRequest'}
70+
subheaders.update(baseheaders)
71+
request = a.post('http://mobile.kobobooks.com/90034/mobileRequest.ashx', """
72+
<?xml version="1.0" encoding="UTF-8"?>
73+
<TabRequest xmlns="http://kobobooks.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://kobobooks.com Requests.xsd">
74+
<RequestHeader>
75+
<UserID>%s</UserID>
76+
<UserKey>%s</UserKey>
77+
</RequestHeader>
78+
<RequestBody>
79+
<TabID>abcdefff-ffff-ffff-ffff-fffffffffffe</TabID>
80+
<TabType>1</TabType>
81+
<BrowseTabType>1</BrowseTabType>
82+
<SortBy>0</SortBy>
83+
<PageNumber>1</PageNumber>
84+
<ItemsPerPage>300</ItemsPerPage>
85+
<MonetizationState>3</MonetizationState>
86+
<ImageType />
87+
<EPubPlatform>4</EPubPlatform>
88+
<Locale>en-NZ</Locale>
89+
</RequestBody>
90+
</TabRequest>""" % (userid, userkey), headers=subheaders)
91+
92+
#print request.text
93+
matches = re.findall('ContentID="(.*?)"', request.text)
94+
95+
for content in matches:
96+
subheaders = {"Host": "mobile.kobobooks.com",'Content-Type':'text/xml','MobileRequestType':'KEpubRequest'}
97+
subheaders.update(baseheaders)
98+
request = a.post("http://mobile.kobobooks.com/75993/mobileRequest.ashx", data = """<?xml version="1.0" encoding="UTF-8"?>
99+
<KEpubRequest
100+
xmlns="http://kobobooks.com"
101+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
102+
xsi:schemaLocation="http://kobobooks.com Requests.xsd">
103+
<RequestHeader>
104+
<UserID>%s</UserID>
105+
<UserKey>%s</UserKey>
106+
</RequestHeader>
107+
<RequestBody>
108+
<ContentType>6</ContentType>
109+
<ContentID>%s</ContentID>
110+
<EPubPlatform>4</EPubPlatform>
111+
<NeedUserInfo>false</NeedUserInfo>
112+
<NeedDownloadURLs>true</NeedDownloadURLs>
113+
</RequestBody>
114+
</KEpubRequest>""" % (userid, userkey, content), headers=subheaders)
115+
if True:
116+
it = ET.iterparse(StringIO.StringIO(request.text.encode('utf-8')))
117+
for _, el in it:
118+
el.tag = el.tag.split('}', 1)[1] # strip all namespaces
119+
root = it.root
120+
url = root.find('./ResponseBody/KePub/EpubInfo/Epub/EpubURLList/URL/DownloadURL').text
121+
request = a.get(url, headers=baseheaders)
122+
123+
volumekeys = {}
124+
preuserkey = string.join((a.cookies.get('pwsdid'), a.cookies.get('wsuid')), "")
125+
userkey = SHA256(preuserkey)[32:]
126+
userkey = binascii.a2b_hex(userkey)
127+
enc = AES.new(userkey, AES.MODE_ECB)
128+
129+
for pair in root.findall('./ResponseBody/KePub/EpubInfo/Epub/EpubContentKeys/EpubContentKey'):
130+
key = base64.decodestring(pair.find('./Key').text)
131+
filename = pair.find('./ContentPath').text
132+
volumekeys[filename] = enc.decrypt(key)
133+
134+
title = root.find('./ResponseBody/KePub/Title').text
135+
136+
zippath = io.BytesIO(request.content)
137+
z = zipfile.ZipFile(zippath, "r")
138+
# make filename out of Unicode alphanumeric and whitespace equivalents from title
139+
outname = "%s.epub" % (re.sub("[^\s\w]", "", title, 0, re.UNICODE))
140+
if os.path.exists(outname):
141+
print "SKIPPED:", title
142+
continue
143+
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
144+
for filename in z.namelist():
145+
# read in and decrypt
146+
if(filename in volumekeys):
147+
# do decrypted version
148+
pagekey = volumekeys[filename]
149+
penc = AES.new(pagekey, AES.MODE_ECB)
150+
contents = RemoveAESPadding(penc.decrypt(z.read(filename)))
151+
# need to fix padding
152+
zout.writestr(filename, contents)
153+
else:
154+
zout.writestr(filename, z.read(filename))
155+
zout.close()
156+
break

0 commit comments

Comments
 (0)