KeePass:把 KDBX、主密钥与离线副本管清楚
KeePass 的权威边界不是在线账号或组织,而是一份 KDBX 文件及其主密钥组件。文件 ACL 可以阻止以后下载,却不能收回已经复制到成员设备、同步盘和备份中的旧库;因此共享、离职和恢复都必须从文件副本与目标凭据两条链同时验证。
KeePass:KDBX 文件就是权威边界
KeePass 2.x 把数据保存在 KDBX database 中。database 内有 group,group 内有 entry;entry 包含 title、username、password、URL、notes、自定义字段、附件、历史版本和自动输入设置。database 由 master key 加密,master key 可以由 master password、key file 以及特定平台组件组合而成。官方 Master Key 文档明确:只要组合中的任一组件丢失,数据库就无法恢复,没有后门。
这套模型没有 SaaS organization、服务端 group、按 entry 的在线撤权或集中 item 审计。文件服务器 ACL 能阻止以后下载 KDBX,却不能收回员工已经复制的 KDBX。知道 master password 且拥有 key file 的人,可以离线打开旧副本。团队共享 KeePass 的安全性因此取决于三件事:KDBX 副本分布是否可盘点,master key 组件是否分离保存,目标凭据是否能在人员变化时批量轮换。
group 是数据库内的组织结构,不是强访问控制。把生产条目放进 Production group 不会让只应访问开发条目的成员看不到它;能解锁同一 database 的人原则上能读取其中全部 entry。需要不同人员集合时,应拆成不同 database,并为每份 database 使用独立 master key、存储 ACL、备份与 owner。拆库会增加同步和恢复成本,但这是 KeePass 模型下真实的权限边界。
安装 KeePass 并创建可恢复的临时库
Windows 从 KeePass 官方下载页选择 2.x installer 或 portable ZIP。installer 写入程序目录并使用用户配置目录,portable 版可以解压即用并把配置放在应用目录。官方 安装与便携说明给出了更新和静默安装参数。企业分发应校验发布包来源,明确插件白名单,并记录 portable 副本位置;“便携”不等于“不留痕”,数据库、配置、最近文件、操作系统缓存和备份仍会留下副本。
$keepass = Get-Command KeePass.exe -ErrorAction Stop
$keepass | Format-List Source,Version
(Get-Item $keepass.Source).VersionInfo |
Select-Object FileName,FileVersion,ProductVersion桌面正向实验在临时目录创建 catalog-demo.kdbx:选择 File -> New,使用现场生成的长合成主密码;新增 Catalog/Demo group;在其中创建 demo-api entry,用户名为 demo-user,密码由 KeePass 生成器产生;保存后锁定 workspace,再用同一 master key 解锁。可信证据是锁定时 entry 不可读,重新解锁后 title、group 与密码指纹一致,KDBX 文件修改时间随保存更新。
如果加入 key file,要让数据库与 key file 位于不同控制域。KDBX 放在受控文件存储,key file 可放在单独的硬件介质或受限 secret store;不要把两者放进同一个同步文件夹。更换 key file 必须通过 File -> Change Master Key 正式重写数据库,直接编辑或替换 key file 会失去访问。恢复演练要在隔离机器上用备份 KDBX、主密码和 key file 全部打开一次,而不是只检查三个文件“都存在”。
KDF、锁定与剪贴板决定 KeePass 的离线攻击面
KDBX 4.1 文件包含经过认证的加密结构、KDF 参数和加密后的 XML 数据。KDF 把 master key 组件转换成数据库加密密钥,增加每次猜测成本。KeePass 2.x 可配置 AES-KDF 与 Argon2 变体;官方 数据库设置建议通过本机基准选择转换成本。团队不应抄一个固定迭代数,而要在最慢的受支持设备上测量解锁时间,在可接受交互延迟内提高内存与计算成本,并把 KDF 参数随数据库备份。参数过低会降低离线猜测成本,参数过高则可能让老设备无法在应急窗口解锁。
锁定 workspace 时,KeePass 关闭 database,只保留路径和少量视图参数;再次解锁等同重新打开数据库。配置应在系统锁屏、用户切换、睡眠和空闲后自动锁定。官方还提供 enforced configuration,可由管理员强制剪贴板自动清除、会话切换锁定和休眠锁定。它只能约束受管 KeePass 实例,无法阻止恶意进程在明文已进入剪贴板时立即读取。
Auto-Type 通过模拟按键把字段送到匹配窗口,减少剪贴板暴露,但目标窗口匹配错误仍会把密码输入到聊天框或钓鱼窗口。KeePass 本体的 Auto-Type 不要求浏览器插件;额外浏览器集成插件会增加代码执行和供应链边界。官方 Auto-Type 文档说明 entry 与 group 可配置窗口关联和序列。高敏 entry 应精确匹配窗口与 URL,不使用宽泛通配;执行前观察焦点和窗口标题,不把 KeePass 以管理员身份常驻来解决普通窗口权限问题。
KeePass 的命令行可以打开指定 database、触发 Auto-Type 或锁定现有 workspace,但它不是带租约、工作负载身份和逐次审计的 headless secret API。KPScript 能扩展单次操作,仍需要安全交付 master key;把主密码放进 -pw: 参数会暴露到命令历史和进程参数。-pw-stdin 不把值放进命令行,却仍要求调用方和管道可信,也不会补上机器身份或逐项授权。需要 CI 动态取值、短期凭据或自动撤销时,应使用专用机器 secret 平台;KeePass 更适合作为离线恢复库或受控人工入口。
桌面端安装后可以用命令行验证程序入口和锁定动作,但不要把 master password 拼进参数:
& (Get-Command KeePass.exe).Source --help
& (Get-Command KeePass.exe).Source --lock-all第一条应打开或显示当前发行版支持的命令行帮助,第二条只作用于正在运行的 KeePass workspace。若无人值守脚本必须读取 entry,问题已经从“是否有 CLI”变成“谁向脚本交付完整 master key、脚本能否导出整个数据库、失败后怎样撤销”;KeePass 本身没有适合 CI 的逐任务机器身份,因此不能用 -pw: 或共享 key file 假装补齐这条控制面。
用 KeePass 做正反实验,并看见文件共享的极限
完成临时库后,复制 catalog-demo.kdbx 为 offline-copy.kdbx,模拟成员设备已经同步一份加密副本。随后在主库删除测试 entry、保存并清空回收站,再打开离线副本。只要旧 master key 仍可用,旧 entry 仍能读取。这不是 KeePass 漏洞,而是离线加密文件的必然语义:新文件无法远程擦除旧文件。
$lab = Join-Path $env:TEMP ("keepass-lab-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $lab | Out-Null
# 在 KeePass 中把临时数据库保存为 $lab\catalog-demo.kdbx 后执行:
Copy-Item "$lab\catalog-demo.kdbx" "$lab\offline-copy.kdbx"
Get-FileHash "$lab\catalog-demo.kdbx" -Algorithm SHA256
Get-FileHash "$lab\offline-copy.kdbx" -Algorithm SHA256删除主库 entry 后,两份文件 hash 会分叉;旧副本仍能解锁就是反向证据。真正的回收动作是到目标测试服务轮换合成密码或删除公钥,再用离线副本中的旧值验证认证失败。若整个 database 的成员集合变化,还应更换 master key 并将新 KDBX 分发给留任人员;这只能保护新版本,旧数据库里的旧凭据仍必须逐项在目标端失效。
多成员编辑同一 KDBX 时,不要依赖云盘“最后写入获胜”。KeePass 2.x 有内建同步,会合并两份数据库并把合并结果写回;直接覆盖则可能丢失并发变更。官方 Synchronization说明保存时会检测磁盘文件变化并提示覆盖或同步。团队要保留版本化备份,规定谁可以同步,冲突后核对 entry 历史,并把一次恢复演练纳入升级门禁。
实验清理顺序是先关闭 KeePass、确认主库不再需要、删除临时 KDBX 与离线副本、清空操作系统回收站,再检查同步客户端版本历史和备份策略是否自动留存。SSD、云盘和企业备份通常无法保证“安全覆写”,因此敏感实验只能使用合成值;真实凭据一旦进入不受控 KDBX,就按已复制处理并轮换,不能依赖文件删除消除风险。
& (Get-Command KeePass.exe).Source --lock-all
Remove-Item -LiteralPath $lab -Recurse -Force
Test-Path -LiteralPath $lab # 预期 FalseKeePass 的共享、离职和导出实际上是一条文件治理链。共享时只把 KDBX 放入受控文件存储,按 database 拆分人员集合,并通过另一控制域交付 key file;离职时先撤销文件存储 ACL、收回受管设备,再更换留任库的 master key,但仍假定离职者持有旧 KDBX。随后逐项轮换旧库中可读的目标凭据,用旧值失败作为结案证据。KDBX 可作为加密离线副本,CSV、HTML、XML 等导出则是明文或降级格式;导出前要确认接收格式会丢失哪些字段、附件和历史,导入候选库后做字段级核对,最后清除中间文件。因为 KeePass 没有服务端逐人审计,文件服务器下载日志、终端管理记录、备份目录和轮换台账共同承担证据链。
用本地合成实验确认撤权不等于旧值失效
没有隔离设备时,可以先运行下面的教学模型,再用真实的 KDBX 复制实验复核。模型不调用 KeePass,也不能证明文件锁定、同步和 KDF 行为;它只演示离线库最关键的不变量:文件存储权限被撤销后,已复制的数据库仍可能保留旧值,只有目标端轮换才能让旧副本失去业务效力。脚本使用随机合成值,只输出 hash 指纹并清空临时目录。
// password-vault-lifecycle-lab.mjs
import { createHash, randomBytes } from "node:crypto";
import { mkdtemp, rm, writeFile, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
const dir = await mkdtemp(join(tmpdir(), "password-vault-lab-"));
const fp = (value) => createHash("sha256").update(value).digest("hex").slice(0, 12);
const randomSecret = () => `synthetic-${randomBytes(24).toString("base64url")}`;
const authority = {
itemId: "catalog-demo-api",
version: 1,
readers: ["developer", "leaver"],
value: randomSecret(),
};
const target = { acceptedFingerprint: fp(authority.value) };
const readOnline = (actor) => {
if (!authority.readers.includes(actor)) throw new Error("ACCESS_DENIED");
return authority.value;
};
const authenticate = (value) => fp(value) === target.acceptedFingerprint;
try {
const oldValue = readOnline("leaver");
await writeFile(join(dir, "offline-cache.json"), JSON.stringify({ value: oldValue }));
console.log(JSON.stringify({ phase: "positive", online: authenticate(oldValue), fp: fp(oldValue) }));
authority.readers = authority.readers.filter((name) => name !== "leaver");
try {
readOnline("leaver");
throw new Error("revocation_test_did_not_fail");
} catch (error) {
if (error.message !== "ACCESS_DENIED") throw error;
console.log(JSON.stringify({ phase: "revoked", onlineRead: "ACCESS_DENIED" }));
}
const cached = JSON.parse(await readFile(join(dir, "offline-cache.json"), "utf8")).value;
console.log(JSON.stringify({ phase: "offline-copy", oldValueStillWorks: authenticate(cached), fp: fp(cached) }));
authority.value = randomSecret();
authority.version += 1;
target.acceptedFingerprint = fp(authority.value);
console.log(JSON.stringify({
phase: "rotated",
version: authority.version,
oldValueStillWorks: authenticate(cached),
newValueWorks: authenticate(readOnline("developer")),
}));
} finally {
await rm(dir, { recursive: true, force: true });
console.log(JSON.stringify({ phase: "cleanup", removed: true }));
}node .\password-vault-lifecycle-lab.mjs
Remove-Item .\password-vault-lifecycle-lab.mjs预期顺序为:正向读取成功;撤销在线入口后返回 ACCESS_DENIED;模拟 KDBX 副本中的旧值在轮换前仍可认证;目标端轮换后 oldValueStillWorks 变为 false,新值验证成功,临时目录被清理。KeePass 的离职流程若缺少这组旧值失败证据,就只是收紧了文件分发,没有关闭已经复制的凭据风险。
浏览器、剪贴板与自动填充仍是独立攻击面
KeePass 的客户端负责解密与填充,服务端权限正确并不意味着终端没有旁路。浏览器扩展只从官方渠道安装,工作资料与个人 profile 分离,关闭浏览器自带密码保存以避免双写。自动填充前核对协议、完整域名、端口和 iframe;相似域名、HTTP 页面和不受信窗口应拒绝自动填充。复制动作会经过系统剪贴板、远程桌面、跨设备同步和录屏环境,短时清除只是降低暴露窗口,不能撤回已经被其他进程读取的内容。
KeePass 不提供适合流水线的逐任务机器身份;需要无人值守取值时,应改用专用机器 Secret 平台,不把主密码、Key File 或 -pw: 参数塞进脚本。交互使用优先选择精确窗口匹配的 Auto-Type,排障只记录数据库 hash、客户端版本、锁定状态和不可逆短指纹。共享 KDBX 时要登记接收者、文件版本、主密钥组件和回收时点,并始终假定对方能够保留副本。
导出、恢复与迁移要验证数据可读性
KDBX 可以作为加密备份,但恢复能力取决于数据库、主密码、Key File、兼容客户端和插件依赖能否同时获得。CSV、HTML 与 XML 导出属于明文或降级副本,迁移前应明确附件、历史、自定义字段和 Auto-Type 配置会丢失什么。备份存在不等于主密钥组件仍可用。
KeePass 恢复演练要在隔离设备上同时取得备份 KDBX、主密码、Key File 和兼容客户端,实际解锁并核对 Group、Entry、自定义字段、附件、历史与 Auto-Type 配置。迁移到其他产品时,导入成功只证明数据可读,不能证明新的人员组、机器身份和权限模型已经重建。CSV、HTML 或 XML 中转会降低保护强度,经过这些文件的真实凭据必须轮换。
离职与失窃设备按两阶段回收
撤销文件服务器 ACL 不能远程擦除旧 KDBX。成员离职时要盘点受管设备、同步盘、版本历史和备份目录,更换留任数据库的主密钥,并逐项在目标系统轮换旧库中可读的密码、公钥和恢复码。旧 KDBX 仍可能被打开,但其中的业务凭据必须已经失效。
KeePass 的第一阶段是撤销文件存储 ACL、停止同步并隔离失窃设备,同时保留下载与版本历史证据;第二阶段按离职成员曾能打开的全部数据库盘点目标凭据,逐项轮换。结案时必须证明旧 KDBX 中的密码、公钥或恢复码已被目标系统拒绝,新数据库和新值由留任成员打开并验证,备份与同步副本也已纳入最坏情况清单。
长期治理与退出条件
每份 KDBX 都要有明确人员集合、文件存储位置、主密钥组件保管人、KDF 基线、同步方式、备份周期和恢复责任。需要不同访问集合时拆分数据库;需要逐人在线撤权、机器身份、动态凭据或集中审计时,应承认 KeePass 控制面不匹配,而不是用共享主密码和脚本参数补洞。
每份 KDBX 都要登记业务 owner、技术保管人、成员集合、存储位置、主密钥组件、KDF 基线、备份周期和退出路径。持续检查无 owner 数据库、未盘点副本、同步冲突、恢复失败和离职后缺少旧值拒绝证据的条目。停止使用前冻结旧库写入,完成受控导出、导入与字段核对,再销毁不需要的明文中间文件;旧库可以长期存在时,只有其中所有业务凭据已经失效,迁移才算完成。
