2010-01-17 18:10:35 +06:00
#! /usr/bin/python
2009-01-27 18:20:37 +06:00
# For use in Topaz Scripts version 2.0
2010-01-17 18:10:35 +06:00
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2 .0
- - - - - BEGIN PUBLIC KEY - - - - -
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti + W8biT7EatpauE / 5 jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu / fpfnvNHDN / wPWWH4jynZ2M6cdcnE5LQ + FfeKqZn7gnG2No1U9h7oOHx
y2 / pHuYme7U1TsgSjwIDAQAB
- - - - - END PUBLIC KEY - - - - -
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
from struct import pack
from struct import unpack
from ctypes import windll , c_char_p , c_wchar_p , c_uint , POINTER , byref , \
create_unicode_buffer , create_string_buffer , CFUNCTYPE , addressof , \
string_at , Structure , c_void_p , cast
import _winreg as winreg
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
import hashlib
MAX_PATH = 255
kernel32 = windll . kernel32
advapi32 = windll . advapi32
crypt32 = windll . crypt32
global kindleDatabase
global bookFile
global bookPayloadOffset
global bookHeaderRecords
global bookMetadata
global bookKey
global command
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = " n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M "
charMap2 = " AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_ "
charMap3 = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ "
charMap4 = " ABCDEFGHIJKLMNPQRSTUVWXYZ123456789 "
#
# Exceptions for all the problems that might happen during the script
#
class CMBDTCError ( Exception ) :
pass
class CMBDTCFatal ( Exception ) :
pass
#
# Stolen stuff
#
class DataBlob ( Structure ) :
_fields_ = [ ( ' cbData ' , c_uint ) ,
( ' pbData ' , c_void_p ) ]
DataBlob_p = POINTER ( DataBlob )
def GetSystemDirectory ( ) :
GetSystemDirectoryW = kernel32 . GetSystemDirectoryW
GetSystemDirectoryW . argtypes = [ c_wchar_p , c_uint ]
GetSystemDirectoryW . restype = c_uint
def GetSystemDirectory ( ) :
buffer = create_unicode_buffer ( MAX_PATH + 1 )
GetSystemDirectoryW ( buffer , len ( buffer ) )
return buffer . value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory ( )
def GetVolumeSerialNumber ( ) :
GetVolumeInformationW = kernel32 . GetVolumeInformationW
GetVolumeInformationW . argtypes = [ c_wchar_p , c_wchar_p , c_uint ,
POINTER ( c_uint ) , POINTER ( c_uint ) ,
POINTER ( c_uint ) , c_wchar_p , c_uint ]
GetVolumeInformationW . restype = c_uint
def GetVolumeSerialNumber ( path ) :
vsn = c_uint ( 0 )
GetVolumeInformationW ( path , None , 0 , byref ( vsn ) , None , None , None , 0 )
return vsn . value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber ( )
def GetUserName ( ) :
GetUserNameW = advapi32 . GetUserNameW
GetUserNameW . argtypes = [ c_wchar_p , POINTER ( c_uint ) ]
GetUserNameW . restype = c_uint
def GetUserName ( ) :
buffer = create_unicode_buffer ( 32 )
size = c_uint ( len ( buffer ) )
while not GetUserNameW ( buffer , byref ( size ) ) :
buffer = create_unicode_buffer ( len ( buffer ) * 2 )
size . value = len ( buffer )
return buffer . value . encode ( ' utf-16-le ' ) [ : : 2 ]
return GetUserName
GetUserName = GetUserName ( )
def CryptUnprotectData ( ) :
_CryptUnprotectData = crypt32 . CryptUnprotectData
_CryptUnprotectData . argtypes = [ DataBlob_p , c_wchar_p , DataBlob_p ,
c_void_p , c_void_p , c_uint , DataBlob_p ]
_CryptUnprotectData . restype = c_uint
def CryptUnprotectData ( indata , entropy ) :
indatab = create_string_buffer ( indata )
indata = DataBlob ( len ( indata ) , cast ( indatab , c_void_p ) )
entropyb = create_string_buffer ( entropy )
entropy = DataBlob ( len ( entropy ) , cast ( entropyb , c_void_p ) )
outdata = DataBlob ( )
if not _CryptUnprotectData ( byref ( indata ) , None , byref ( entropy ) ,
None , None , 0 , byref ( outdata ) ) :
raise CMBDTCFatal ( " Failed to Unprotect Data " )
return string_at ( outdata . pbData , outdata . cbData )
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData ( )
#
# Returns the MD5 digest of "message"
#
def MD5 ( message ) :
ctx = hashlib . md5 ( )
ctx . update ( message )
return ctx . digest ( )
#
# Returns the MD5 digest of "message"
#
def SHA1 ( message ) :
ctx = hashlib . sha1 ( )
ctx . update ( message )
return ctx . digest ( )
#
# Open the book file at path
#
def openBook ( path ) :
try :
return open ( path , ' rb ' )
except :
raise CMBDTCFatal ( " Could not open book file: " + path )
#
# Encode the bytes in data with the characters in map
#
def encode ( data , map ) :
result = " "
for char in data :
value = ord ( char )
Q = ( value ^ 0x80 ) / / len ( map )
R = value % len ( map )
result + = map [ Q ]
result + = map [ R ]
return result
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash ( data , map ) :
return encode ( MD5 ( data ) , map )
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode ( data , map ) :
result = " "
for i in range ( 0 , len ( data ) , 2 ) :
high = map . find ( data [ i ] )
low = map . find ( data [ i + 1 ] )
value = ( ( ( high * 0x40 ) ^ 0x80 ) & 0xFF ) + low
result + = pack ( " B " , value )
return result
#
# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
#
def openKindleInfo ( ) :
regkey = winreg . OpenKey ( winreg . HKEY_CURRENT_USER , " Software \\ Microsoft \\ Windows \\ CurrentVersion \\ Explorer \\ Shell Folders \\ " )
path = winreg . QueryValueEx ( regkey , ' Local AppData ' ) [ 0 ]
return open ( path + ' \\ Amazon \\ Kindle For PC \\ {AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY} \\ kindle.info ' , ' r ' )
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo ( ) :
DB = { }
infoReader = openKindleInfo ( )
infoReader . read ( 1 )
data = infoReader . read ( )
items = data . split ( ' { ' )
for item in items :
splito = item . split ( ' : ' )
DB [ splito [ 0 ] ] = splito [ 1 ]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash ( hash ) :
names = [ " kindle.account.tokens " , " kindle.cookie.item " , " eulaVersionAccepted " , " login_date " , " kindle.token.item " , " login " , " kindle.key.item " , " kindle.name.info " , " kindle.device.info " , " MazamaRandomNumber " ]
result = " "
for name in names :
if hash == encodeHash ( name , charMap2 ) :
result = name
break
return name
#
# Print all the records from the kindle.info file (option -i)
#
def printKindleInfo ( ) :
for record in kindleDatabase :
name = findNameForHash ( record )
if name != " " :
print ( name )
print ( " -------------------------- \n " )
else :
print ( " Unknown Record " )
print getKindleInfoValueForHash ( record )
print " \n "
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash ( hashedKey ) :
global kindleDatabase
encryptedValue = decode ( kindleDatabase [ hashedKey ] , charMap2 )
return CryptUnprotectData ( encryptedValue , " " )
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey ( key ) :
return getKindleInfoValueForHash ( encodeHash ( key , charMap2 ) )
#
# Get a 7 bit encoded number from the book file
#
def bookReadEncodedNumber ( ) :
flag = False
data = ord ( bookFile . read ( 1 ) )
if data == 0xFF :
flag = True
data = ord ( bookFile . read ( 1 ) )
if data > = 0x80 :
datax = ( data & 0x7F )
while data > = 0x80 :
data = ord ( bookFile . read ( 1 ) )
datax = ( datax << 7 ) + ( data & 0x7F )
data = datax
if flag :
data = - data
return data
#
# Encode a number in 7 bit format
#
def encodeNumber ( number ) :
result = " "
negative = False
flag = 0
if number < 0 :
number = - number + 1
negative = True
while True :
byte = number & 0x7F
number = number >> 7
byte + = flag
result + = chr ( byte )
flag = 0x80
if number == 0 : break
if negative :
result + = chr ( 0xFF )
return result [ : : - 1 ]
#
# Get a length prefixed string from the file
#
def bookReadString ( ) :
stringLength = bookReadEncodedNumber ( )
return unpack ( str ( stringLength ) + " s " , bookFile . read ( stringLength ) ) [ 0 ]
#
# Returns a length prefixed string
#
def lengthPrefixString ( data ) :
return encodeNumber ( len ( data ) ) + data
#
# Read and return the data of one header record at the current book file position [[offset,decompressedLength,compressedLength],...]
#
def bookReadHeaderRecordData ( ) :
nbValues = bookReadEncodedNumber ( )
values = [ ]
for i in range ( 0 , nbValues ) :
values . append ( [ bookReadEncodedNumber ( ) , bookReadEncodedNumber ( ) , bookReadEncodedNumber ( ) ] )
return values
#
# Read and parse one header record at the current book file position and return the associated data [[offset,decompressedLength,compressedLength],...]
#
def parseTopazHeaderRecord ( ) :
if ord ( bookFile . read ( 1 ) ) != 0x63 :
raise CMBDTCFatal ( " Parse Error : Invalid Header " )
tag = bookReadString ( )
record = bookReadHeaderRecordData ( )
return [ tag , record ]
#
# Parse the header of a Topaz file, get all the header records and the offset for the payload
#
def parseTopazHeader ( ) :
global bookHeaderRecords
global bookPayloadOffset
magic = unpack ( " 4s " , bookFile . read ( 4 ) ) [ 0 ]
if magic != ' TPZ0 ' :
raise CMBDTCFatal ( " Parse Error : Invalid Header, not a Topaz file " )
nbRecords = bookReadEncodedNumber ( )
bookHeaderRecords = { }
for i in range ( 0 , nbRecords ) :
result = parseTopazHeaderRecord ( )
print result [ 0 ] , result [ 1 ]
bookHeaderRecords [ result [ 0 ] ] = result [ 1 ]
if ord ( bookFile . read ( 1 ) ) != 0x64 :
raise CMBDTCFatal ( " Parse Error : Invalid Header " )
bookPayloadOffset = bookFile . tell ( )
#
# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
# Correction, the record is correctly decompressed too
#
def getBookPayloadRecord ( name , index ) :
encrypted = False
compressed = False
try :
recordOffset = bookHeaderRecords [ name ] [ index ] [ 0 ]
except :
raise CMBDTCFatal ( " Parse Error : Invalid Record, record not found " )
bookFile . seek ( bookPayloadOffset + recordOffset )
tag = bookReadString ( )
if tag != name :
raise CMBDTCFatal ( " Parse Error : Invalid Record, record name doesn ' t match " )
recordIndex = bookReadEncodedNumber ( )
if recordIndex < 0 :
encrypted = True
recordIndex = - recordIndex - 1
if recordIndex != index :
raise CMBDTCFatal ( " Parse Error : Invalid Record, index doesn ' t match " )
if ( bookHeaderRecords [ name ] [ index ] [ 2 ] > 0 ) :
compressed = True
record = bookFile . read ( bookHeaderRecords [ name ] [ index ] [ 2 ] )
else :
record = bookFile . read ( bookHeaderRecords [ name ] [ index ] [ 1 ] )
if encrypted :
ctx = topazCryptoInit ( bookKey )
record = topazCryptoDecrypt ( record , ctx )
if compressed :
record = zlib . decompress ( record )
return record
#
# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
#
def extractBookPayloadRecord ( name , index , filename ) :
compressed = False
try :
compressed = bookHeaderRecords [ name ] [ index ] [ 2 ] != 0
record = getBookPayloadRecord ( name , index )
except :
print ( " Could not find record " )
# if compressed:
# try:
# record = zlib.decompress(record)
# except:
# raise CMBDTCFatal("Could not decompress record")
if filename != " " :
try :
file = open ( filename , " wb " )
file . write ( record )
file . close ( )
except :
raise CMBDTCFatal ( " Could not write to destination file " )
else :
print ( record )
#
# return next record [key,value] from the book metadata from the current book position
#
def readMetadataRecord ( ) :
return [ bookReadString ( ) , bookReadString ( ) ]
#
# Parse the metadata record from the book payload and return a list of [key,values]
#
def parseMetadata ( ) :
global bookHeaderRecords
global bookPayloadAddress
global bookMetadata
bookMetadata = { }
bookFile . seek ( bookPayloadOffset + bookHeaderRecords [ " metadata " ] [ 0 ] [ 0 ] )
tag = bookReadString ( )
if tag != " metadata " :
raise CMBDTCFatal ( " Parse Error : Record Names Don ' t Match " )
flags = ord ( bookFile . read ( 1 ) )
nbRecords = ord ( bookFile . read ( 1 ) )
for i in range ( 0 , nbRecords ) :
record = readMetadataRecord ( )
bookMetadata [ record [ 0 ] ] = record [ 1 ]
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField ( bitField , offset ) :
byteNumber = offset / / 4
bitPosition = 6 - 2 * ( offset % 4 )
return ord ( bitField [ byteNumber ] ) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField ( bitField , offset ) :
offset * = 3
value = ( getTwoBitsFromBitField ( bitField , offset ) << 4 ) + ( getTwoBitsFromBitField ( bitField , offset + 1 ) << 2 ) + getTwoBitsFromBitField ( bitField , offset + 2 )
return value
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID ( hash ) :
global charMap3
PID = " "
for position in range ( 0 , 8 ) :
PID + = charMap3 [ getSixBitsFromBitField ( hash , position ) ]
return PID
#
# Context initialisation for the Topaz Crypto
#
def topazCryptoInit ( key ) :
ctx1 = 0x0CAFFE19E
for keyChar in key :
keyByte = ord ( keyChar )
ctx2 = ctx1
ctx1 = ( ( ( ( ctx1 >> 2 ) * ( ctx1 >> 7 ) ) & 0xFFFFFFFF ) ^ ( keyByte * keyByte * 0x0F902007 ) & 0xFFFFFFFF )
return [ ctx1 , ctx2 ]
#
# decrypt data with the context prepared by topazCryptoInit()
#
def topazCryptoDecrypt ( data , ctx ) :
ctx1 = ctx [ 0 ]
ctx2 = ctx [ 1 ]
plainText = " "
for dataChar in data :
dataByte = ord ( dataChar )
m = ( dataByte ^ ( ( ctx1 >> 3 ) & 0xFF ) ^ ( ( ctx2 << 3 ) & 0xFF ) ) & 0xFF
ctx2 = ctx1
ctx1 = ( ( ( ctx1 >> 2 ) * ( ctx1 >> 7 ) ) & 0xFFFFFFFF ) ^ ( ( m * m * 0x0F902007 ) & 0xFFFFFFFF )
plainText + = chr ( m )
return plainText
#
# Decrypt a payload record with the PID
#
def decryptRecord ( data , PID ) :
ctx = topazCryptoInit ( PID )
return topazCryptoDecrypt ( data , ctx )
#
# Try to decrypt a dkey record (contains the book PID)
#
def decryptDkeyRecord ( data , PID ) :
record = decryptRecord ( data , PID )
fields = unpack ( " 3sB8sB8s3s " , record )
if fields [ 0 ] != " PID " or fields [ 5 ] != " pid " :
raise CMBDTCError ( " Didn ' t find PID magic numbers in record " )
elif fields [ 1 ] != 8 or fields [ 3 ] != 8 :
raise CMBDTCError ( " Record didn ' t contain correct length fields " )
elif fields [ 2 ] != PID :
raise CMBDTCError ( " Record didn ' t contain PID " )
return fields [ 4 ]
#
# Decrypt all the book's dkey records (contain the book PID)
#
def decryptDkeyRecords ( data , PID ) :
nbKeyRecords = ord ( data [ 0 ] )
records = [ ]
data = data [ 1 : ]
for i in range ( 0 , nbKeyRecords ) :
length = ord ( data [ 0 ] )
try :
key = decryptDkeyRecord ( data [ 1 : length + 1 ] , PID )
records . append ( key )
except CMBDTCError :
pass
data = data [ 1 + length : ]
return records
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable ( ) :
table = [ ]
for counter1 in range ( 0 , 0x100 ) :
value = counter1
for counter2 in range ( 0 , 8 ) :
if ( value & 1 == 0 ) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table . append ( value )
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed ( table , dsn ) :
value = 0
for counter in range ( 0 , 4 ) :
index = ( ord ( dsn [ counter ] ) ^ value ) & 0xFF
value = ( value >> 8 ) ^ table [ index ]
return value
#
# Generate the device PID
#
def generateDevicePID ( table , dsn , nbRoll ) :
seed = generatePidSeed ( table , dsn )
pidAscii = " "
pid = [ ( seed >> 24 ) & 0xFF , ( seed >> 16 ) & 0xff , ( seed >> 8 ) & 0xFF , ( seed ) & 0xFF , ( seed >> 24 ) & 0xFF , ( seed >> 16 ) & 0xff , ( seed >> 8 ) & 0xFF , ( seed ) & 0xFF ]
index = 0
for counter in range ( 0 , nbRoll ) :
pid [ index ] = pid [ index ] ^ ord ( dsn [ counter ] )
index = ( index + 1 ) % 8
for counter in range ( 0 , 8 ) :
index = ( ( ( ( pid [ counter ] >> 5 ) & 3 ) ^ pid [ counter ] ) & 0x1f ) + ( pid [ counter ] >> 7 )
pidAscii + = charMap4 [ index ]
return pidAscii
#
# Create decrypted book payload
#
def createDecryptedPayload ( payload ) :
for headerRecord in bookHeaderRecords :
name = headerRecord
if name != " dkey " :
ext = ' .dat '
if name == ' img ' : ext = ' .jpg '
for index in range ( 0 , len ( bookHeaderRecords [ name ] ) ) :
fnum = " %04d " % index
fname = name + fnum + ext
destdir = payload
if name == ' img ' :
destdir = os . path . join ( payload , ' img ' )
if name == ' page ' :
destdir = os . path . join ( payload , ' page ' )
if name == ' glyphs ' :
destdir = os . path . join ( payload , ' glyphs ' )
outputFile = os . path . join ( destdir , fname )
file ( outputFile , ' wb ' ) . write ( getBookPayloadRecord ( name , index ) )
# Create decrypted book
#
def createDecryptedBook ( outdir ) :
if not os . path . exists ( outdir ) :
os . makedirs ( outdir )
destdir = os . path . join ( outdir , ' img ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
destdir = os . path . join ( outdir , ' page ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
destdir = os . path . join ( outdir , ' glyphs ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
createDecryptedPayload ( outdir )
#
# Set the command to execute by the programm according to cmdLine parameters
#
def setCommand ( name ) :
global command
if command != " " :
raise CMBDTCFatal ( " Invalid command line parameters " )
else :
command = name
#
# Program usage
#
def usage ( ) :
print ( " \n Usage: " )
print ( " \n cmbtc_dump.py [options] bookFileName \n " )
print ( " -p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times) " )
print ( " -d Dumps the unencrypted book as files to outdir " )
print ( " -o Output directory to save book files to " )
print ( " -v Verbose (can be used several times) " )
print ( " -i Prints kindle.info database " )
#
# Main
#
def main ( argv = sys . argv ) :
global kindleDatabase
global bookMetadata
global bookKey
global bookFile
global command
progname = os . path . basename ( argv [ 0 ] )
verbose = 0
recordName = " "
recordIndex = 0
outdir = " "
PIDs = [ ]
kindleDatabase = None
command = " "
try :
opts , args = getopt . getopt ( sys . argv [ 1 : ] , " vi:o:p:d " )
except getopt . GetoptError , err :
# print help information and exit:
print str ( err ) # will print something like "option -a not recognized"
usage ( )
sys . exit ( 2 )
if len ( opts ) == 0 and len ( args ) == 0 :
usage ( )
sys . exit ( 2 )
for o , a in opts :
if o == " -v " :
verbose + = 1
if o == " -i " :
setCommand ( " printInfo " )
if o == " -o " :
if a == None :
raise CMBDTCFatal ( " Invalid parameter for -o " )
outdir = a
if o == " -p " :
PIDs . append ( a )
if o == " -d " :
setCommand ( " doit " )
if command == " " :
raise CMBDTCFatal ( " No action supplied on command line " )
#
# Read the encrypted database
#
try :
kindleDatabase = parseKindleInfo ( )
except Exception as message :
if verbose > 0 :
print ( message )
if kindleDatabase != None :
if command == " printInfo " :
printKindleInfo ( )
#
# Compute the DSN
#
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey ( " MazamaRandomNumber " )
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash ( str ( GetVolumeSerialNumber ( GetSystemDirectory ( ) . split ( ' \\ ' ) [ 0 ] + ' \\ ' ) ) , charMap1 )
# Get the current user name
encodedUsername = encodeHash ( GetUserName ( ) , charMap1 )
# concat, hash and encode
DSN = encode ( SHA1 ( MazamaRandomNumber + encodedSystemVolumeSerialNumber + encodedUsername ) , charMap1 )
if verbose > 1 :
print ( " DSN: " + DSN )
#
# Compute the device PID
#
table = generatePidEncryptionTable ( )
devicePID = generateDevicePID ( table , DSN , 4 )
PIDs . append ( devicePID )
if verbose > 0 :
print ( " Device PID: " + devicePID )
#
# Open book and parse metadata
#
if len ( args ) == 1 :
bookFile = openBook ( args [ 0 ] )
parseTopazHeader ( )
parseMetadata ( )
#
# Compute book PID
#
# Get the account token
if kindleDatabase != None :
kindleAccountToken = getKindleInfoValueForKey ( " kindle.account.tokens " )
if verbose > 1 :
print ( " Account Token: " + kindleAccountToken )
keysRecord = bookMetadata [ " keys " ]
keysRecordRecord = bookMetadata [ keysRecord ]
pidHash = SHA1 ( DSN + kindleAccountToken + keysRecord + keysRecordRecord )
bookPID = encodePID ( pidHash )
PIDs . append ( bookPID )
if verbose > 0 :
print ( " Book PID: " + bookPID )
#
# Decrypt book key
#
dkey = getBookPayloadRecord ( ' dkey ' , 0 )
bookKeys = [ ]
for PID in PIDs :
bookKeys + = decryptDkeyRecords ( dkey , PID )
if len ( bookKeys ) == 0 :
if verbose > 0 :
print ( " Book key could not be found. Maybe this book is not registered with this device. " )
else :
bookKey = bookKeys [ 0 ]
if verbose > 0 :
print ( " Book key: " + bookKey . encode ( ' hex ' ) )
if command == " printRecord " :
extractBookPayloadRecord ( recordName , int ( recordIndex ) , outputFile )
if outputFile != " " and verbose > 0 :
print ( " Wrote record to file: " + outputFile )
elif command == " doit " :
if outdir != " " :
createDecryptedBook ( outdir )
if verbose > 0 :
print ( " Decrypted book saved. Don ' t pirate! " )
elif verbose > 0 :
print ( " Output directory name was not supplied. " )
return 0
if __name__ == ' __main__ ' :
sys . exit ( main ( ) )