Skip to content

Instantly share code, notes, and snippets.

@ento
Forked from aallan/drmcheck.py
Last active May 11, 2021 05:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ento/9f60c9a9ee6b0c08e235c54f2e0e4e96 to your computer and use it in GitHub Desktop.
Save ento/9f60c9a9ee6b0c08e235c54f2e0e4e96 to your computer and use it in GitHub Desktop.
Python script to check encryption status of various types of ebook formats.
#!/usr/bin/python
#
# Changelog
# 1.00 - Initial version, with code from various other scripts.
# 1.01 - Moved authorship announcement to usage section.
# Written in 2011 by Paul Durrant
#
# 1.02 - Added recognition of Apple Fairplay encryption.
# Modified in 2015 by Alasdair Allan
#
# Released with unlicense. See http://unlicense.org/
#
#############################################################################
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
#############################################################################
__version__ = '1.02'
import sys, struct, os
import zlib
import zipfile
_FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024
def uncompress(cmpdata):
dc = zlib.decompressobj(-15)
data = ''
while len(cmpdata) > 0:
if len(cmpdata) > _MAX_SIZE :
newdata = cmpdata[0:_MAX_SIZE]
cmpdata = cmpdata[_MAX_SIZE:]
else:
newdata = cmpdata
cmpdata = ''
newdata = dc.decompress(newdata)
unprocessed = dc.unconsumed_tail
if len(unprocessed) == 0:
newdata += dc.flush()
data += newdata
cmpdata += unprocessed
unprocessed = ''
return data
def getfiledata(file, zi):
# get file name length and exta data length to find start of file data
local_header_offset = zi.header_offset
file.seek(local_header_offset + _FILENAME_LEN_OFFSET)
leninfo = file.read(2)
local_name_length, = struct.unpack('<H', leninfo)
file.seek(local_header_offset + _EXTRA_LEN_OFFSET)
exinfo = file.read(2)
extra_field_length, = struct.unpack('<H', exinfo)
file.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
data = None
# if not compressed we are good to go
if zi.compress_type == zipfile.ZIP_STORED:
data = file.read(zi.file_size)
# if compressed we must decompress it using zlib
if zi.compress_type == zipfile.ZIP_DEFLATED:
cmpdata = file.read(zi.compress_size)
data = uncompress(cmpdata)
return data
def main():
if len(sys.argv) < 2:
print("drmcheck v%s Written 2011 Paul Durrant" % __version__)
print("Released with unlicense. See http://unlicense.org/\n")
print("\nDescription:\n Determines whether given ebook has DRM.")
print("Usage:\n %s <infile>" % sys.argv[0])
return 1
infile = sys.argv[1]
kind = "Unknown"
compression = ""
encryption = ""
infileobject = open(infile,'rb')
bookdata = infileobject.read()
# Check for Mobipocket/Kindle
if bookdata[60:60+8] == b'BOOKMOBI':
kind = "Mobipocket"
offset, = struct.unpack('>L',bookdata[78:78+4])
compressionid, = struct.unpack('>H',bookdata[offset:offset+2])
if compressionid==1:
compression = "uncompressed"
elif compressionid==2:
compression = "PalmDOC compression"
elif compressionid==17480:
compression = "HUFF/CDIC compression"
else:
compression = "unknown compression type %d" % compresionid
encryptionid, = struct.unpack('>H',bookdata[offset+12:offset+12+2])
if encryptionid==0:
encryption = "unencrypted"
elif encryptionid==1:
encryption = "encrypted with old encrytion method"
elif encryptionid==2:
encryption = "encrypted with current encrytion method"
else:
encryption = "encrypted with unknown encryption method %d" % encryptionid
if bookdata[60:60+8] == b'TEXtREAd':
kind = "PalmDoc or early Mobipocket"
offset, = struct.unpack('>L',bookdata[78:78+4])
compressionid, = struct.unpack('>H',bookdata[offset:offset+2])
if compressionid==1:
compression = "uncompressed"
elif compressionid==2:
compression = "PalmDOC compression"
else:
compression = "unknown compression type %d" % compresionid
encryptionid, = struct.unpack('>H',bookdata[offset+12:offset+12+2])
if encryptionid==1:
encryption = "encrypted with old encrytion method"
elif encryptionid==2:
encryption = "encrypted with current encrytion method"
else:
encryption = "unencrypted"
if bookdata[60:60+8] == b'PNRdPPrs':
kind = "eReader"
offset, = struct.unpack('>L',bookdata[78:78+4])
compressionid, = struct.unpack('>H',bookdata[offset:offset+2])
if compressionid==2:
compression = "PalmDOC compression"
encryption = "unencrypted"
elif compressionid==10:
compression = "zlib compression"
encryption = "unencrypted"
elif compressionid==260 or compressionid==259 or compressionid==272:
compression = "zlib compression"
encryption = "encrypted"
else:
compression = "unknown compression/encryption type %d" % compresionid
encryption = ""
if bookdata[0:0+4] == b'TPZ0':
kind = "Amazon Topaz"
compression = "compressed"
encryption = "encrypted"
if bookdata[0:0+2] == b"PK":
if bookdata[30:30+28] == b'mimetypeapplication/epub+zip':
kind = "ePub"
else:
kind = "Probably a ZIP file, possibly an ePub"
compression = ""
encryption = "unencrypted"
foundrights = 0
foundencryption = 0
inzip = zipfile.ZipFile(infile,'r')
for zinfo in inzip.infolist():
if zinfo.filename.find("encryption.xml") != -1:
foundencryption = 1
if foundrights == 0:
encryption = "Unknown encryption type"
if zinfo.filename.find("sinf.xml") != -1:
foundencryption = 1
fairdata = getfiledata(infileobject, zinfo)
offset = fairdata.find("<fairplay:sID>")
if fairdata[offset+14] == "1":
encryption = "Apple Fairplay"
if zinfo.filename.find("rights.xml") != -1:
foundrights = 1
encryption = "encrypted"
rightsdata = getfiledata(infileobject, zinfo)
firstoffset = rightsdata.find("<encryptedKey>")
secondoffset = rightsdata.find("</encryptedKey>")
if secondoffset-firstoffset == 78:
encryption = "Barnes & Noble encryption"
elif secondoffset-firstoffset == 186:
encryption = "Adobe Digital Editions encryption"
else:
encryption = "Unknown encryption type"
if foundencryption == 0:
encryption = "unencrypted"
infileobject.close()
print("%s - %s ebook, %s %s.\n" % (infile, kind, compression, encryption))
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment