Flutter解密PDF后用Syncfusion Viewer显示报错排查
Flutter加密PDF解密后Syncfusion Viewer报错:Invalid Position:-1 及填充错误问题
我正在开发一款Flutter应用,需求是从网络获取PDF文件,加密后存储到应用文件目录,读取解密后在应用内显示。使用了encrypt库和Syncfusion PDF Viewer组件,但解密后显示文件时,调试模式下SfPdfViewer抛出错误:
ArgumentError{Invalid argument (position): Invalid Position:-1}
相关Viewer代码
SfPdfViewer.network( widget.file!.link!, key: _pdfViewerKey, onDocumentLoaded: (PdfDocumentLoadedDetails details) async { // final Directory tempPath = await getApplicationDocumentsDirectory(); // _tempFile = await File('${tempPath.path}/${widget.file!.title}.pdf') // .writeAsBytes( await details.document.save()); }):FutureBuilder<Uint8List>( future: getFileBytes(), builder:(context,snapshot){ if(snapshot.hasData) { logger.i("Snapshot Data is ${snapshot.data}"); return SfPdfViewer.memory(snapshot.data!,key: _pdfViewerKey, ); } else if(snapshot.hasData) // 此处存在逻辑错误,应为hasError { snapshot.printError(); return Text('Error'); } else { return const Center(child: CircularProgressIndicator()); } } ) ); } Future<Uint8List> getFileBytes() async { return await fileController.getDecryptedFile(name: widget.file!.title!); }
下载、加密和解密方法
Future<void> downloadFile({required Files file})async { logger.e("Link of file to be downloaded is ${file.link}"); Response response=await download(url: file.link!); final directory = await getApplicationDocumentsDirectory(); final filesDirectory = await Directory('${directory.path}/files').create(recursive: true); String filePath='${filesDirectory.path}/${file.title!}.aes'; logger.i("Length of key is ${key.length}"); final encryptor=Encrypter(AES(key,padding: null)); logger.i("Unencrypted bytes: ${response.data}"); final encryptedBytes = encryptor.encryptBytes(response.data,iv: iv,); final decryptedBytes = encryptor.decryptBytes(encryptedBytes,iv: iv,associatedData: encryptedBytes.bytes); logger.i("encrypted bytes: ${encryptedBytes}"); final encryptedFile=File(filePath); encryptedFile.writeAsBytesSync(encryptedBytes.bytes); changeDownloadStatus(true); } Future<dynamic> download({required String url}) async { try{ String token=url.substring(url.indexOf("token")+7,url.indexOf("_gl")); String alt=url.substring( url.indexOf("alt=")+5,url.indexOf("token=")); String gl=url.substring(url.indexOf("_gl=")+5); Map<String,String> query={ "token":token, "alt":alt, "_gl":gl }; Response response =await Dio().get(url.substring(0,url.indexOf("?")),options: Options(responseType: ResponseType.bytes, followRedirects: false,contentType: "application/pdf"),queryParameters: query); logger.i(response.data); changeDownloadStatus(true); return response; } on Exception catch(e) { logger.e(e.toString()); } } Future<Uint8List> getDecryptedFile({required String name}) async { final directory = await getApplicationDocumentsDirectory(); final filesDirectory = Directory('${directory.path}/files'); String filePath='${filesDirectory.path}/$name.aes'; File encryptedfile=File(filePath); bool isPresent=await encryptedfile.exists(); logger.i("File at path $filePath is present $isPresent"); Uint8List encryptedBytes=encryptedfile.readAsBytesSync(); logger.i("encrypted bytes: ${encryptedBytes}"); // Decrypt the file final encryptor=Encrypter(AES(key,padding: null)); final decryptedBytes=encryptor.decryptBytes(Encrypted(encryptedBytes),iv: iv,associatedData: encryptedBytes); return Uint8List.fromList(decryptedBytes); }
密钥和IV生成代码
final iv=IV.fromLength(16); final key=Key.fromSecureRandom(32);
补充信息
日志显示原始未加密字节与解密后的字节数据一致,但使用加密器默认填充方式时,decryptBytes方法会报错:Invalid or corrupted padding at decryptBytes method.
问题排查与解决方案
1. 修复FutureBuilder逻辑错误
代码中FutureBuilder的错误分支条件写错,导致无法捕获异常,修正为:
else if(snapshot.hasError) { logger.e(snapshot.error.toString()); return Text('加载失败: ${snapshot.error}'); }
2. 修正AES填充模式问题
- 你设置了
padding: null(无填充模式),此时要求明文长度必须是AES块大小(16字节)的整数倍。如果原始PDF字节长度不满足,加密后的数据会不完整,解密后PDF损坏,导致Viewer报错。 - 使用默认PKCS7填充时解密报错,是因为加密和解密的填充设置不一致,或加密时明文处理异常。统一使用默认填充模式即可解决:
// 加密时 final encryptor=Encrypter(AES(key)); // 默认PKCS7填充 final encryptedBytes = encryptor.encryptBytes(response.data, iv: iv); // 解密时 final encryptor=Encrypter(AES(key)); final decryptedBytes=encryptor.decryptBytes(Encrypted(encryptedBytes), iv: iv);
3. 统一AssociatedData参数
加密时未传入associatedData,但解密时传入了encryptedBytes,不符合AEAD模式规范。如果不需要关联数据,两边都不要传;如果需要,确保加密和解密传入相同的固定数据。
4. 持久化密钥与IV
当前代码每次启动都会重新生成密钥和IV,导致之前加密的文件无法解密。需要用安全存储(如flutter_secure_storage)持久化密钥和IV,确保加密解密使用同一套密钥:
// 首次生成并保存 final iv=IV.fromLength(16); final key=Key.fromSecureRandom(32); await secureStorage.write(key: 'aes_key', value: base64Encode(key.bytes)); await secureStorage.write(key: 'aes_iv', value: base64Encode(iv.bytes)); // 读取密钥 final keyBytes = base64Decode(await secureStorage.read(key: 'aes_key')!); final key = Key(keyBytes); final ivBytes = base64Decode(await secureStorage.read(key: 'aes_iv')!); final iv = IV(ivBytes);
5. 验证解密后PDF完整性
将解密后的字节写入临时文件,用系统PDF阅读器打开,确认文件是否完整。如果临时文件能正常打开,说明问题出在SfPdfViewer的内存加载逻辑;如果不能打开,说明加密/解密过程存在数据损坏。
内容的提问来源于stack exchange,提问作者Aryan Singh
相关产品推荐
相关产品推荐

