Android平台解密AES加密EPUB文件并基于EpubReader解析的技术咨询
在Android上解密AES加密EPUB并通过epublib展示的完整方案
嘿,我帮你梳理下实现这个需求的具体步骤,包括核心的解密逻辑、epublib的使用,还有容易踩的坑,都是实际开发里总结的经验:
一、前期准备要做足
- 先确认加密参数:既然你用的是和Java源码一致的16位密钥,那对应的应该是AES-128算法。重点要保证加密和解密的模式、填充方式、初始化向量(IV)完全一致——比如加密用的是
CBC模式+PKCS5Padding填充,解密时也得一模一样,不然肯定解不开。 - 添加上依赖:在你的
build.gradle(app)里把epublib的依赖加上,注意选适配Android的版本:implementation 'nl.siegmann.epublib:epublib-core:3.1' // 如果EPUB里有图片、字体这些资源,再加这个依赖 implementation 'nl.siegmann.epublib:epublib-resources:3.1' - 权限这块不用太担心:用App的缓存目录存临时解密文件就行,Android 10+不需要额外申请存储权限;如果是老版本(Android 9及以下),最多加个
WRITE_EXTERNAL_STORAGE,但优先用缓存目录更安全,也不会有权限问题。
二、核心步骤一步步来
1. 先从Assets拿到加密文件的流
Assets里的文件不能直接读写,得用AssetManager读取成输入流:
AssetManager assetManager = getAssets(); InputStream encryptedInputStream = assetManager.open("你的加密EPUB文件名.aes");
2. 写好AES解密的方法
这里是关键,必须和你加密时的参数完全匹配。假设你加密时用的是16位密钥直接转字节、IV和密钥相同(如果加密时用了其他IV,替换成对应的就行),示例代码如下:
private InputStream decryptAES(InputStream inputStream, String key) throws Exception { // 把16位密钥转成字节数组 byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); // 这里的IV要和加密时用的完全一致,我假设和密钥相同,你按需替换 byte[] ivBytes = key.getBytes(StandardCharsets.UTF_8); // 初始化Cipher,参数要和加密时一致 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); // 用CipherInputStream包装原输入流,实现边读边解密,省内存 return new CipherInputStream(inputStream, cipher); }
划重点:如果你是用AES Crypt客户端加密的文件,它的加密文件有自己的专属文件头,不是纯AES加密的EPUB!这时候你得先读取并跳过文件头的固定字节,再进行AES解密——不然解密出来的内容会乱码,EPUB肯定打不开。
3. 把解密后的内容写到临时文件
epublib可以直接读解密后的流,但写成临时文件会更稳定,尤其是处理大体积的EPUB:
private File writeToTempFile(InputStream inputStream) throws IOException { // 在缓存目录创建临时EPUB文件,后缀一定要用.epub File tempFile = File.createTempFile("decrypted_book", ".epub", getCacheDir()); FileOutputStream outputStream = new FileOutputStream(tempFile); // 用4KB的缓冲区读写,效率比较高 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } // 记得关流,不然会占资源 outputStream.close(); inputStream.close(); return tempFile; }
4. 用epublib加载EPUB并展示到WebView
epublib会帮你解析EPUB的结构,你可以拿到每个章节的内容,然后渲染到WebView:
private void loadEpubIntoWebView(File epubFile) throws Exception { // 初始化EPUB阅读器 EpubReader reader = new EpubReader(); Book book = reader.readEpub(new FileInputStream(epubFile)); // 这里拿第一个章节做示例,你可以遍历所有章节做目录或者连续加载 Resource firstChapter = book.getContents().get(0); String chapterContent = new String(firstChapter.getData(), StandardCharsets.UTF_8); // 配置WebView并加载内容 WebView webView = findViewById(R.id.web_view); webView.getSettings().setJavaScriptEnabled(true); // 如果章节里有相对路径的资源,把baseURL改成临时文件的目录,不然图片之类的加载不出来 webView.loadDataWithBaseURL("file://" + getCacheDir() + "/", chapterContent, "text/html", "UTF-8", null); // 可选:设置临时文件退出时自动删除,避免占缓存 epubFile.deleteOnExit(); }
三、容易踩的坑和排查方法
- 解密后EPUB打不开?
- 先核对加密和解密的所有参数:密钥、IV、模式、填充方式,只要有一个不一样就解不对
- 检查是不是AES Crypt的文件头没处理:如果是客户端加密的,先跳过开头的固定标识字节再解密
- 解密耗时久导致ANR?
- 所有解密、写文件的操作都要放在子线程里执行!用
Thread、Coroutine(Kotlin)或者AsyncTask(虽然过时但能用),绝对不能在主线程做这些耗时操作 - 可以把缓冲区调大一点,比如从4KB改成8KB,能提升读写效率
- 所有解密、写文件的操作都要放在子线程里执行!用
- WebView显示异常,图片、样式加载不出来?
- 记得设置
loadDataWithBaseURL的base参数为临时文件所在的目录,这样相对路径的资源才能正确加载 - 检查EPUB章节的HTML是不是标准格式,有些EPUB的章节可能有特殊标签,WebView需要开启相应的设置(比如允许加载本地文件)
- 记得设置
四、完整流程的调用示例(子线程执行)
把所有步骤串起来,一定要放在子线程里:
new Thread(() -> { try { AssetManager assetManager = getAssets(); InputStream encryptedStream = assetManager.open("encrypted_book.aes"); InputStream decryptedStream = decryptAES(encryptedStream, "你的16位密钥"); File tempEpub = writeToTempFile(decryptedStream); // 回到主线程更新UI,Android不能在子线程操作UI runOnUiThread(() -> { try { loadEpubIntoWebView(tempEpub); } catch (Exception e) { e.printStackTrace(); // 这里可以加个错误提示,比如Toast告诉用户加载失败 } }); } catch (Exception e) { e.printStackTrace(); // 解密失败的处理,比如提示密钥错误或者文件损坏 } }).start();
内容的提问来源于stack exchange,提问作者Lee Harding
相关产品推荐
相关产品推荐

