OFD 安全与权限
本节介绍 OFD 文档的加密探测、自定义安全处理器接入(用于打开加密文档时的解密)、细粒度文档权限(OFDPermission)控制及对象权限刷新。
在 OFD SDK 中,安全与权限的处理遵循以下核心链路:
- 无内置加密接口:SDK 在
OFDDoc暂未提供标准内置的加密接口(如 RC4/AES 等),亦未提供 自定义加密接口。若文档使用自定义安全类型加密,须在打开前调用OFDSecurity::RegisterHandler注册SecurityCallback,以便 SDK 在LoadDocument时完成解密。- 持久化方式:
OFDDoc对象本身不包含Save方法。对文档权限的任何修改,最终都必须通过调用OFDPackage::Save或SaveAs来持久化到文件中。
任务场景
- 打开加密 OFD,在
LoadDocument时传入用户密码或所有者密码。 - 查询文档是否加密、加密方法(密码 / 证书)及算法类型(RC4、AES128 等)。
- 注册自定义
SecurityCallback,以打开使用国密等自定义算法加密的文档。 - 读取或配置文档级细粒度权限(编辑、导出、打印、签章、水印、有效期等)。
- 使用所有者密码移除文档加密(
RemoveEncryption)。 - 密码认证后刷新文档内对象的权限状态。
API 概览
| 功能 | C++ API(foxit::ofd) | 核心参数 / 描述 |
|---|---|---|
| 探测加密状态 | OFDPackage::IsEncrypted、GetEncryptMethod、GetEncryptType | 按文档索引查询;返回 OFDDoc::EncryptMethod / EncryptType |
| 加载加密文档 | OFDPackage::LoadDocument(index, password) | 密码错误时返回的 OFDDoc 为空(IsEmpty() 为 true) |
| 注册自定义处理器 | OFDSecurity::RegisterHandler、UnregisterHandler | 绑定 SecurityCallback;security_type、crypto_type、key_length 须与文档加密方案一致 |
| 移除加密 | OFDDoc::RemoveEncryption | 传入所有者密码 |
| 获取 / 创建权限 | OFDDoc::GetPermissions、CreatePermissions | 返回 OFDPermission;CreatePermissions 与 GetPermissions 操作同一对象 |
| 配置细粒度权限 | OFDPermission | EnableEditable、EnableExportable、EnablePrintable、SetPrintCopies、有效期等 |
| 刷新对象权限 | OFDDoc::RefreshObjectsPermission | authenticated = true 表示已通过密码认证 |
| 持久化 | OFDPackage::Save、SaveAs | 加密与权限修改均为内存操作,须包层保存 |
加密方法与算法类型(OFDDoc 枚举)
EncryptMethod | 值 | 含义 |
|---|---|---|
e_OFDEncryptMethodNone | 0 | 无加密 |
e_OFDEncryptMethodPwd | 1 | 密码加密 |
e_OFDEncryptMethodCert | 2 | 证书加密 |
EncryptType | 值 | 含义 |
|---|---|---|
e_OFDEncryptTypeNone | 0 | 无 |
e_OFDEncryptTypeRC4 | 1 | RC4 |
e_OFDEncryptTypeAES128 | 2 | AES128 |
e_OFDEncryptTypeAES256 | 3 | AES256 |
e_OFDEncryptTypeSM4 | 4 | SM4 |
打开加密文档
加载前先通过 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();
}
1
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();
}
}
1
注册自定义安全处理器
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;
}
1
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);
}
}
1
移除加密
对已加密的文档,使用所有者密码可调用 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);
}
1
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);
}
}
1
配置文档权限
通过 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;
}
1
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();
}
}
1
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);
}
1
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);
}
}
1
保存并应用安全设置
权限与移除加密等修改均为内存操作,须通过 OFDPackage::Save 或 SaveAs 写回 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));
}
1
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);
}
}
1
注意事项
- 任务链路:
OFDPackage探测并加载 →(可选)OFDSecurity::RegisterHandler→OFDPermission配置 →RefreshObjectsPermission→OFDPackage::Save/SaveAs。 - API 边界:
OFDDoc无Load、Save、GetPermission/SetPermission;加载与保存分别在OFDPackage上完成,权限通过CreatePermissions/GetPermissions取得OFDPermission后配置。 - 用户密码与所有者密码:打开文档时可传任一种正确密码;
RemoveEncryption须使用所有者密码。 - 回调生命周期:
RegisterHandler接收SecurityCallback*裸指针,回调对象须在文档打开、保存全流程内保持有效;不再使用时调用UnregisterHandler并释放。 - 流式解密:处理大型 OFD 时建议在
SecurityCallback中实现DecryptStart/DecryptFinish,避免仅依赖DecryptData导致内存占用过高。 - 权限与加密:细粒度权限(如禁止导出)通常需配合文档加密方可在阅读器中形成约束;未加密文档上单独设置权限,阅读器侧未必生效。