I'm working on an e-reader app (using skyepub) that basically downloads encrypted books into the file system (and it saves is decryption key in the database), and when the user tries to read it it loads the book into memory and decrypts it.
The problem is that some books have their first chapter truncated (epub books are actually zip files, with each chapter being a separate file).. this result in this dreaded error:
this XML file does not appear to have any style information associated with it. The document tree is shown below
what i tried
I've verified that the encrypted book is downloaded properly, b/c if I copy the file over to my desktop (from my rooted android) and run this command on it:
openssl aes-192-cbc -d -K *** -iv *** -in test.epub.encrypted -out test.epub
it works just fine. However if i pretty much try to do the same with the following android code
public ContentData getContentData(String baseDirectory, String contentPath) {
if( contentPath.startsWith("/fonts/")) {
... // handle font suff
}
int secondSlash = contentPath.indexOf('/', 1);
if( secondSlash == -1) return null;
String bookEditionID = contentPath.substring(1,secondSlash);
String zipEntryName = contentPath.substring(secondSlash+1);
final ContentData data = new ContentData();
try {
InputStream stream = dbUtil.getBookStream(bookEditionID);
if( stream == null) return null;
final ZipInputStream zip = new ZipInputStream(stream);
ZipEntry entry;
do {
entry = zip.getNextEntry();
Log.e("Abjjad","looping through entry: "+entry);
if( entry == null) {
zip.close();
return null;
}
} while( !entry.getName().equals(zipEntryName));
Log.e("debug","going through data with entry: " +entry+", contentLength: "+entry.getSize());
see the method dbUtil.getBookStream
:
public InputStream getBookStream( String bookEditionId) {
BookInfo book = getBookInfo(bookEditionId);
InputStream origStream = null;
try {
// Open the downloaded ePub
origStream = openFileInput(bookEditionId + ".epub");
// De-obfuscate the key
SecretKeySpec sks = getObfuscationKeySpec(bookEditionId);
Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");
c.init(Cipher.DECRYPT_MODE, sks);
byte[] decodedBytes = c.doFinal(Base64.decode(book.decryptionKey, Base64.DEFAULT));
String keyPair = new String(decodedBytes);
// Split the key and parse into binary
int separator = keyPair.indexOf(':');
byte[] key = DatatypeConverter.parseHexBinary(keyPair.substring(0, separator));
byte[] iv = DatatypeConverter.parseHexBinary(keyPair.substring(separator + 1));
c = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
return new CipherInputStream(origStream, c);
} catch( Exception e) {
try {
if (origStream != null) origStream.close();
} catch( Exception x) {}
return null;
}
}
then the log of entry.getSize() returns -1
in the first code block.
bonus (works on iOS!)
we wrote the same code in iOS, and it works perfectly (on the same book):
+ (NSData *)encryptKey:(NSString *)key ivParam:(NSString *)iv bookId:(NSString *)bookId
{
NSString *keyPair = [NSString stringWithFormat:@"%@:%@", key, iv];
NSString *secret = [self getObfuscationSecretWithValue:bookId];
NSData *data = [keyPair dataUsingEncoding:NSASCIIStringEncoding];
char keyPtr[kCCKeySizeAES128];
bzero(keyPtr, sizeof(keyPtr));
[[NSData dataWithHexString:secret] getBytes:keyPtr length:sizeof(keyPtr)];
NSUInteger dataLength = [data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesEncrypted;
CCCryptorStatus status = CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCKeySizeAES128,
NULL,
[data bytes], [data length],
buffer, bufferSize, &numBytesEncrypted);
if (status == kCCSuccess) {
return [NSData dataWithBytes:buffer length:numBytesEncrypted];
}
else {
free(buffer);
return nil;
}
}
update
i noticed that this truncation happens only after reading the toc (which seems like the last chapter from the above?).. from the logs:
:::::::::::::::::::::::::::::::
getInputStream: /24748681/OEBPS/toc.ncx
:::::::::::::::::::::::::::::::
looping through entry: mimetype
looping through entry: OEBPS/hayat-ghayr.html
looping through entry: OEBPS/content.opf
looping through entry: OEBPS/images/978-614-425-313-7-hayat-ghayr-cover.png
looping through entry: OEBPS/images/978-614-425-313-7-hayat_fmt.png
looping through entry: OEBPS/template.css
looping through entry: OEBPS/hayat-ghayr-2.html
looping through entry: OEBPS/hayat-ghayr-1.html
looping through entry: OEBPS/hayat-ghayr-3.html
looping through entry: OEBPS/hayat-ghayr-4.html
looping through entry: OEBPS/hayat-ghayr-5.html
looping through entry: OEBPS/hayat-ghayr-6.html
looping through entry: OEBPS/hayat-ghayr-7.html
looping through entry: OEBPS/hayat-ghayr-8.html
looping through entry: OEBPS/hayat-ghayr-9.html
looping through entry: OEBPS/hayat-ghayr-10.html
looping through entry: OEBPS/hayat-ghayr-11.html
looping through entry: OEBPS/hayat-ghayr-12.html
looping through entry: OEBPS/hayat-ghayr-13.html
looping through entry: OEBPS/hayat-ghayr-14.html
looping through entry: OEBPS/hayat-ghayr-15.html
looping through entry: OEBPS/hayat-ghayr-16.html
looping through entry: OEBPS/hayat-ghayr-17.html
looping through entry: OEBPS/hayat-ghayr-18.html
looping through entry: OEBPS/hayat-ghayr-19.html
looping through entry: OEBPS/hayat-ghayr-20.html
looping through entry: OEBPS/hayat-ghayr-21.html
looping through entry: OEBPS/hayat-ghayr-22.html
looping through entry: META-INF/container.xml
looping through entry: OEBPS/images/277.png
looping through entry: OEBPS/toc.ncx
going through data with entry: OEBPS/toc.ncx, contentLength: 5549
returning data
:::::::::::::::::::::::::::::::
getInputStream: /24748681/OEBPS/hayat-ghayr.html
:::::::::::::::::::::::::::::::
looping through entry: mimetype
looping through entry: OEBPS/hayat-ghayr.html
going through data with entry: OEBPS/hayat-ghayr.html, contentLength: -1
returning data