You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:28:13