Skip to content

OFD 安全与权限

本节介绍 OFD 文档的加密探测、自定义安全处理器接入(用于打开加密文档时的解密)、细粒度文档权限(OFDPermission)控制及对象权限刷新。

在 OFD SDK 中,安全与权限的处理遵循以下核心链路:

  1. 无内置加密接口:SDK 在 OFDDoc 暂未提供标准内置的加密接口(如 RC4/AES 等),亦未提供 自定义加密接口。若文档使用自定义安全类型加密,须在打开前调用 OFDSecurity::RegisterHandler 注册 SecurityCallback,以便 SDK 在 LoadDocument 时完成解密。
  2. 持久化方式OFDDoc 对象本身不包含 Save 方法。对文档权限的任何修改,最终都必须通过调用 OFDPackage::SaveSaveAs 来持久化到文件中。

任务场景

  • 打开加密 OFD,在 LoadDocument 时传入用户密码或所有者密码。
  • 查询文档是否加密、加密方法(密码 / 证书)及算法类型(RC4、AES128 等)。
  • 注册自定义 SecurityCallback,以打开使用国密等自定义算法加密的文档。
  • 读取或配置文档级细粒度权限(编辑、导出、打印、签章、水印、有效期等)。
  • 使用所有者密码移除文档加密(RemoveEncryption)。
  • 密码认证后刷新文档内对象的权限状态。

API 概览

功能C++ API(foxit::ofd核心参数 / 描述
探测加密状态OFDPackage::IsEncryptedGetEncryptMethod
GetEncryptType
按文档索引查询;返回 OFDDoc::EncryptMethod / EncryptType
加载加密文档OFDPackage::LoadDocument(index, password)密码错误时返回的 OFDDoc 为空(IsEmpty() 为 true)
注册自定义处理器OFDSecurity::RegisterHandler
UnregisterHandler
绑定 SecurityCallbacksecurity_typecrypto_typekey_length 须与文档加密方案一致
移除加密OFDDoc::RemoveEncryption传入所有者密码
获取 / 创建权限OFDDoc::GetPermissionsCreatePermissions返回 OFDPermissionCreatePermissionsGetPermissions 操作同一对象
配置细粒度权限OFDPermissionEnableEditableEnableExportableEnablePrintableSetPrintCopies、有效期等
刷新对象权限OFDDoc::RefreshObjectsPermissionauthenticated = true 表示已通过密码认证
持久化OFDPackage::SaveSaveAs加密与权限修改均为内存操作,须包层保存

加密方法与算法类型(OFDDoc 枚举)

EncryptMethod含义
e_OFDEncryptMethodNone0无加密
e_OFDEncryptMethodPwd1密码加密
e_OFDEncryptMethodCert2证书加密
EncryptType含义
e_OFDEncryptTypeNone0
e_OFDEncryptTypeRC41RC4
e_OFDEncryptTypeAES1282AES128
e_OFDEncryptTypeAES2563AES256
e_OFDEncryptTypeSM44SM4

打开加密文档

加载前先通过 OFDPackage 检查加密状态;若已加密,在 LoadDocument 中传入正确密码。密码错误时 OFDDoc 为空,应停止后续操作。

c++
#include "ofd/fs_ofdpackage.h"
#include "ofd/fs_ofddoc.h"

using namespace foxit;
using namespace foxit::ofd;

bool OpenEncryptedDocument(OFDPackage& package, int doc_index,
                           const char* password) {
  if (package.IsEmpty()) {
    return false;
  }

  // 加载前检查是否加密及加密方式
  if (package.IsEncrypted(doc_index)) {
    OFDDoc::EncryptMethod method = package.GetEncryptMethod(doc_index);
    OFDDoc::EncryptType type = package.GetEncryptType(doc_index);
    (void)method;
    (void)type;
  }

  // 传入用户密码或所有者密码;未加密文档可传空字符串
  OFDDoc doc = package.LoadDocument(doc_index, password);
  return !doc.IsEmpty();
}
java
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.OFDPackage;

public class OFDOpenEncryptedExample {
    public static boolean openEncryptedDocument(OFDPackage pkg, int docIndex,
            byte[] password) throws Exception {
        if (pkg.isEmpty()) {
            return false;
        }

        // 加载前检查是否加密及加密方式
        if (pkg.isEncrypted(docIndex)) {
            int method = pkg.getEncryptMethod(docIndex);
            int type = pkg.getEncryptType(docIndex);
        }

        // 传入用户密码或所有者密码;未加密文档可传 null
        OFDDoc doc = pkg.loadDocument(docIndex, password);
        return !doc.isEmpty();
    }
}

注册自定义安全处理器

OFDSecurity 提供委托解密机制:开发者实现 SecurityCallback,通过 RegisterHandler 注册后,SDK 在打开使用对应安全类型的加密文档时会调用回调的 DecryptData / DecryptStart / DecryptFinish 等接口。

c++
#include "ofd/fs_ofdsecurity.h"

using namespace foxit;
using namespace foxit::ofd;

// 继承 SecurityCallback,实现解密逻辑(Encrypt* 接口须实现,但 SDK 不再调用)
class MySecurityCallback : public SecurityCallback {
 public:
  bool IsEncrypted(const void* src_buf, uint32 src_size) override {
    return src_buf != nullptr && src_size > 0;
  }

  String GetCryptoType() override {
    return "MyCrypto";
  }

  bool EncryptData(const void* src_buf, uint32 src_size,
                   void* dest_buf, uint32& dest_size) override {
    // 加密侧接口须实现; 
    return false;
  }

  bool DecryptData(const void* src_buf, uint32 src_size,
                   void* dest_buf, uint32& dest_size) override {
    // 实现解密逻辑
    return false;
  }

  void* EncryptStart() override { return nullptr; }

  bool EncryptFinish(void* context,
                     common::file::StreamCallback* input_callback,
                     common::file::StreamCallback* output_callback) override {
    // 加密侧接口须实现; 
    return false;
  }

  void* DecryptStart() override { return nullptr; }

  bool DecryptFinish(void* context,
                     common::file::StreamCallback* input_callback,
                     common::file::StreamCallback* output_callback) override {
    // 大文件建议实现流式解密
    return false;
  }
};

void RegisterMySecurityHandler() {
  const char* security_type = "MySecurityType";
  const char* crypto_type = "MyCrypto";
  const int key_length = 16;

  // 回调对象须在文档 Load / Save 全流程内保持有效
  MySecurityCallback* callback = new MySecurityCallback();
  OFDSecurity::RegisterHandler(security_type, crypto_type, key_length, callback);

  // 使用完毕后反注册并释放
  // OFDSecurity::UnregisterHandler(security_type, crypto_type, key_length);
  // delete callback;
}
java
import com.foxit.sdk.ofd.OFDSecurity;

public class OFDSecurityRegisterExample {
    /**
     * Java 侧须实现 SecurityCallback 并注册。
     * 注册参数 securityType、cryptoType、keyLength 须与文档加密方案一致。
     */
    public static void registerMySecurityHandler(
            com.foxit.sdk.ofd.SecurityCallback callback) throws Exception {
        String securityType = "MySecurityType";
        String cryptoType = "MyCrypto";
        int keyLength = 16;

        // 回调对象须在文档 load / save 全流程内保持有效
        OFDSecurity.registerHandler(securityType, cryptoType, keyLength, callback);
    }
}

移除加密

对已加密的文档,使用所有者密码可调用 RemoveEncryption 移除加密。修改完成后通过 OFDPackage::SaveAs 写回。

c++
#include "ofd/fs_ofddoc.h"

using namespace foxit::ofd;

bool RemoveDocumentEncryption(OFDDoc& doc, const char* owner_password) {
  if (doc.IsEmpty()) {
    return false;
  }

  // 移除加密须使用所有者密码
  return doc.RemoveEncryption(owner_password);
}
java
import com.foxit.sdk.ofd.OFDDoc;

public class OFDEncryptExample {
    public static boolean removeDocumentEncryption(OFDDoc doc,
            byte[] ownerPassword) throws Exception {
        if (doc.isEmpty()) {
            return false;
        }

        // 移除加密须使用所有者密码
        return doc.removeEncryption(ownerPassword);
    }
}

配置文档权限

通过 CreatePermissions 创建或获取可编辑的 OFDPermission,修改后 GetPermissions 返回同一对象上的最新状态。权限包括编辑、注释、导出、签章、水印、屏幕打印、打印及打印份数、有效期等。

c++
#include "ofd/fs_ofdpackage.h"
#include "ofd/fs_ofddoc.h"
#include "ofd/fs_ofdpermission.h"

using namespace foxit;
using namespace foxit::ofd;

void ConfigureDocumentPermissions(OFDDoc& doc) {
  if (doc.IsEmpty()) {
    return;
  }

  // CreatePermissions 与 GetPermissions 操作同一权限对象
  OFDPermission perm = doc.CreatePermissions();
  if (perm.IsEmpty()) {
    return;
  }

  perm.EnableEditable(true);
  perm.EnableAllowAnnot(true);
  perm.EnableExportable(false);       // 禁止导出
  perm.EnableAllowSignature(true);
  perm.EnableAllowWatermark(false);
  perm.EnableAllowPrintScreen(false); // 禁止屏幕打印(截图)
  perm.EnablePrintable(true);
  perm.SetPrintCopies(3);             // 限制打印份数

  foxit::DateTime start_date;
  start_date.year = 2025;
  start_date.month = 1;
  start_date.day = 1;
  start_date.hour = 0;
  start_date.minute = 0;
  start_date.second = 0;
  start_date.milliseconds = 0;
  start_date.utc_hour_offset = 8;
  perm.SetStartDate(start_date);

  foxit::DateTime end_date = start_date;
  end_date.year = 2026;
  perm.SetExpirationDate(end_date);

  // 修改后可通过 GetPermissions 读取并验证
  OFDPermission current = doc.GetPermissions();
  (void)current;
}
java
import com.foxit.sdk.common.DateTime;
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.OFDPermission;

public class OFDPermissionExample {
    public static void configureDocumentPermissions(OFDDoc doc) throws Exception {
        if (doc.isEmpty()) {
            return;
        }

        // createPermissions 与 getPermissions 操作同一权限对象
        OFDPermission perm = doc.createPermissions();
        if (perm.isEmpty()) {
            return;
        }

        perm.enableEditable(true);
        perm.enableAllowAnnot(true);
        perm.enableExportable(false);       // 禁止导出
        perm.enableAllowSignature(true);
        perm.enableAllowWatermark(false);
        perm.enableAllowPrintScreen(false); // 禁止屏幕打印(截图)
        perm.enablePrintable(true);
        perm.setPrintCopies(3);             // 限制打印份数

        // 设置有效期:年、月、日、时、分、秒、毫秒、时区小时偏移、时区分钟偏移
        DateTime startDate = new DateTime(2025, 1, 1, 0, 0, 0, 0, 8, 0);
        perm.setStartDate(startDate);

        DateTime endDate = new DateTime(2026, 1, 1, 0, 0, 0, 0, 8, 0);
        perm.setExpirationDate(endDate);

        // 修改后可通过 getPermissions 读取并验证
        OFDPermission current = doc.getPermissions();
    }
}

OFDPermission 权限位一览

设置 / 查询含义
EnableEditable / IsEditable是否允许编辑
EnableAllowAnnot / IsAllowAnnot是否允许注释
EnableExportable / IsExportable是否允许导出
EnableAllowSignature / IsAllowSignature是否允许签名或签章
EnableAllowWatermark / IsAllowWatermark是否允许水印
EnableAllowPrintScreen / IsAllowPrintScreen是否允许屏幕打印
EnablePrintable / IsPrintable是否允许打印
SetPrintCopies / GetPrintCopies允许打印份数
SetStartDate / GetStartDate有效期起始时间
SetExpirationDate / GetExpirationDate有效期截止时间

刷新对象权限

密码认证成功后,可调用 RefreshObjectsPermission(true) 刷新文档内各对象的权限状态,使权限约束在对象层生效。

c++
#include "ofd/fs_ofddoc.h"

using namespace foxit::ofd;

void RefreshAfterAuthentication(OFDDoc& doc, bool authenticated) {
  if (doc.IsEmpty()) {
    return;
  }

  // authenticated = true 表示已通过密码认证
  doc.RefreshObjectsPermission(authenticated);
}
java
import com.foxit.sdk.ofd.OFDDoc;

public class OFDRefreshPermissionExample {
    public static void refreshAfterAuthentication(OFDDoc doc,
            boolean authenticated) throws Exception {
        if (doc.isEmpty()) {
            return;
        }

        // authenticated = true 表示已通过密码认证
        doc.refreshObjectsPermission(authenticated);
    }
}

保存并应用安全设置

权限与移除加密等修改均为内存操作,须通过 OFDPackage::SaveSaveAs 写回 OFD 文件包。

c++
#include "ofd/fs_ofdpackage.h"

using namespace foxit::ofd;

bool SaveSecurityChanges(OFDPackage& package, const wchar_t* output_path) {
  if (package.IsEmpty()) {
    return false;
  }

  // 权限或移除加密修改后须包层保存
  return package.SaveAs(WString(output_path));
}
java
import com.foxit.sdk.ofd.OFDPackage;

public class OFDSaveSecurityExample {
    public static boolean saveSecurityChanges(OFDPackage pkg,
            String outputPath) throws Exception {
        if (pkg.isEmpty()) {
            return false;
        }

        // 权限或移除加密修改后须包层保存
        return pkg.saveAs(outputPath);
    }
}

注意事项

  • 任务链路OFDPackage 探测并加载 →(可选)OFDSecurity::RegisterHandlerOFDPermission 配置 → RefreshObjectsPermissionOFDPackage::Save / SaveAs
  • API 边界OFDDocLoadSaveGetPermission / SetPermission;加载与保存分别在 OFDPackage 上完成,权限通过 CreatePermissions / GetPermissions 取得 OFDPermission 后配置。
  • 用户密码与所有者密码:打开文档时可传任一种正确密码;RemoveEncryption 须使用所有者密码
  • 回调生命周期RegisterHandler 接收 SecurityCallback* 裸指针,回调对象须在文档打开、保存全流程内保持有效;不再使用时调用 UnregisterHandler 并释放。
  • 流式解密:处理大型 OFD 时建议在 SecurityCallback 中实现 DecryptStart / DecryptFinish,避免仅依赖 DecryptData 导致内存占用过高。
  • 权限与加密:细粒度权限(如禁止导出)通常需配合文档加密方可在阅读器中形成约束;未加密文档上单独设置权限,阅读器侧未必生效。