DeDRM_tools/Kindle_Mobi_Tools/lib/kindlefix.py

173 lines
5.0 KiB
Python

class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
import prc, struct
from binascii import hexlify
def strByte(s,off=0):
return struct.unpack(">B",s[off])[0];
def strSWord(s,off=0):
return struct.unpack(">h",s[off:off+2])[0];
def strWord(s,off=0):
return struct.unpack(">H",s[off:off+2])[0];
def strDWord(s,off=0):
return struct.unpack(">L",s[off:off+4])[0];
def strPutDWord(s,off,i):
return s[:off]+struct.pack(">L",i)+s[off+4:];
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
#implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
def find_key(rec0, pid):
off1 = strDWord(rec0, 0xA8)
if off1==0xFFFFFFFF or off1==0:
print "No DRM"
return None
size1 = strDWord(rec0, 0xB0)
cnt = strDWord(rec0, 0xAC)
flag = strDWord(rec0, 0xB4)
temp_key = PC1(keyvec1, pid.ljust(16,'\0'), False)
cksum = 0
#print pid, "->", hexlify(temp_key)
for i in xrange(len(temp_key)):
cksum += ord(temp_key[i])
cksum &= 0xFF
temp_key = temp_key.ljust(16,'\0')
#print "pid cksum: %02X"%cksum
#print "Key records: %02X-%02X, count: %d, flag: %02X"%(off1, off1+size1, cnt, flag)
iOff = off1
drm_key = None
for i in xrange(cnt):
dwCheck = strDWord(rec0, iOff)
dwSize = strDWord(rec0, iOff+4)
dwType = strDWord(rec0, iOff+8)
nCksum = strByte(rec0, iOff+0xC)
#print "Key record %d: check=%08X, size=%d, type=%d, cksum=%02X"%(i, dwCheck, dwSize, dwType, nCksum)
if nCksum==cksum:
drmInfo = PC1(temp_key, rec0[iOff+0x10:iOff+0x30])
dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo)
#print "Decrypted drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c)
#print "Decrypted drmInfo:", hexlify(drmInfo)
if dw0==dwCheck:
print "Found the matching record; setting the CustomDRM flag for Kindle"
drmInfo = strPutDWord(drmInfo,4,(dw4|0x800))
dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo)
#print "Updated drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c)
return rec0[:iOff+0x10] + PC1(temp_key, drmInfo, False) + rec0[:iOff+0x30]
iOff += dwSize
return None
def replaceext(filename, newext):
nameparts = filename.split(".")
if len(nameparts)>1:
return (".".join(nameparts[:-1]))+newext
else:
return nameparts[0]+newext
def main(argv=sys.argv):
print "The Kindleizer v0.2. Copyright (c) 2007 Igor Skochinsky"
if len(sys.argv) != 3:
print "Fixes encrypted Mobipocket books to be readable by Kindle"
print "Usage: kindlefix.py file.mobi PID"
return 1
fname = sys.argv[1]
pid = sys.argv[2]
if len(pid)==10 and pid[-3]=='*':
pid = pid[:-2]
if len(pid)!=8 or pid[-1]!='*':
print "PID is not valid! (should be in format AAAAAAA*DD)"
return 3
db = prc.File(fname)
#print dir(db)
if db.getDBInfo()["creator"]!='MOBI':
print "Not a Mobi file!"
return 1
rec0 = db.getRecord(0)[0]
enc = strSWord(rec0, 0xC)
print "Encryption:", enc
if enc!=2:
print "Unknown encryption type"
return 1
if len(rec0)<0x28 or rec0[0x10:0x14] != 'MOBI':
print "bad file format"
return 1
print "Mobi publication type:", strDWord(rec0, 0x18)
formatVer = strDWord(rec0, 0x24)
print "Mobi format version:", formatVer
last_rec = strWord(rec0, 8)
dwE0 = 0
if formatVer>=4:
new_rec0 = find_key(rec0, pid)
if new_rec0:
db.setRecordIdx(0,new_rec0)
else:
print "PID doesn't match this file"
return 2
else:
print "Wrong Mobi format version"
return 1
outfname = replaceext(fname, ".azw")
if outfname==fname:
outfname = replaceext(fname, "_fixed.azw")
db.save(outfname)
print "Output written to "+outfname
return 0
if __name__ == "__main__":
sys.exit(main())