给个人 Bitwarden 保险库做定期加密备份:脚本用
bw export --format encrypted_json --password导出受密码保护的文件,校验确实是加密格式,只保留最近 12 份,文件权限 600。万一账号出问题,用备份密码就能导入任意 Bitwarden 账号恢复。
先看清成品
本机安装了 Bitwarden CLI 2026.10.0,确认了
bw export支持encrypted_json和--password;并从 CLI 源码确认受密码保护的导出文件包含"encrypted": true和"passwordProtected": true。备份脚本用假的bw命令跑了 5 项测试全部通过(保留份数、权限、拦截未加密导出、删除明文文件、缺少密码时拒绝运行)。没有登录真实账号导出,也没有做真实导入恢复。
注意:计划和价格以 Bitwarden 官方价格页为准。不要共享个人账号或主密码,团队共享请用组织和集合。只导入、导出你自己有权管理的凭据。
安全提示:备份文件即使加密,也等于你整个保险库。备份密码要和主密码不同,单独记在纸上或另一个安全位置;不要把备份放在公开网盘的共享文件夹里。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
bw_backup.sh |
同步 → 导出 → 校验 → 轮转 |
| 加密备份 | 受密码保护,可导入任意 Bitwarden 账号 |
| 测试 | 不需要真实账号 |
| 恢复演练步骤 | 每季度做一次 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Bitwarden CLI | npm install -g @bitwarden/cli,本文用 2026.10.0 |
也可下载官方独立程序 |
| Python 3 | 校验 JSON | 系统自带即可 |
| 存放位置 | 本地加密磁盘或离线 U 盘 | 至少一份离线 |
文件结构
24-bitwarden-B-src/
├── bw_backup.sh
└── tests/
├── fakebin/bw # 测试用的假 bw
└── test_backup.sh
第 1 步:两种加密导出的区别
| 类型 | 能导入到哪里 | 风险 |
|---|---|---|
账号限定(不带 --password) |
只能导回原账号 | 轮换账号加密密钥后旧备份无法解密 |
密码保护(带 --password) |
任意 Bitwarden 账号 | 备份密码丢了就无法解密 |
做灾难恢复备份选密码保护。
第 2 步:备份脚本
bw_backup.sh:
#!/usr/bin/env bash
# Bitwarden 保险库加密备份:导出为“受密码保护”的 encrypted_json,保留最近 N 份。
# 需要:已登录的 bw CLI;环境变量 BW_SESSION(bw unlock --raw 得到)和 BACKUP_PASSWORD(备份文件密码)
set -euo pipefail
umask 077
BACKUP_DIR="${BACKUP_DIR:-$HOME/bw-backups}"
KEEP="${KEEP:-12}"
: "${BW_SESSION:?请先 export BW_SESSION=\$(bw unlock --raw)}"
: "${BACKUP_PASSWORD:?请设置 BACKUP_PASSWORD(与 Bitwarden 主密码不同,单独保存)}"
mkdir -p "$BACKUP_DIR"
bw sync >/dev/null
out="$BACKUP_DIR/bitwarden_$(date +%Y%m%d_%H%M%S).json"
bw export --format encrypted_json --password "$BACKUP_PASSWORD" --output "$out" >/dev/null
# 校验:必须是 JSON 且 encrypted 为 true、passwordProtected 为 true,否则删除并报错
if ! python3 - "$out" <<'PY'
import json, sys
d = json.load(open(sys.argv[1], encoding="utf-8"))
sys.exit(0 if d.get("encrypted") is True and d.get("passwordProtected") is True else 1)
PY
then
rm -f "$out"; echo "✗ 导出文件不是受密码保护的加密格式,已删除" >&2; exit 1
fi
# 只保留最近 KEEP 份
ls -1t "$BACKUP_DIR"/bitwarden_*.json | tail -n +"$((KEEP + 1))" | xargs -r rm -f
echo "✓ 备份完成:$out(共保留 $(ls -1 "$BACKUP_DIR"/bitwarden_*.json | wc -l) 份)"
要点:
umask 077让新文件默认只有自己可读写。- 校验
encrypted和passwordProtected,防止参数写错导出成明文。 - 附件是否包含在导出中,以官方“导出保险库数据”说明为准;重要附件另外保存。
第 3 步:测试
tests/fakebin/bw:
#!/usr/bin/env bash
# 测试用的假 bw:模拟 sync 和 export
case "$1" in
sync) echo "Syncing complete." ;;
export)
while [ $# -gt 0 ]; do [ "$1" = "--output" ] && out="$2"; shift; done
if [ "${FAKE_PLAIN:-0}" = 1 ]; then echo '{"encrypted":false,"items":[]}' > "$out"
else echo '{"encrypted":true,"passwordProtected":true,"salt":"x","kdfType":0,"kdfIterations":600000,"encKeyValidation_DO_NOT_EDIT":"x","data":"x"}' > "$out"; fi ;;
esac
tests/test_backup.sh:
#!/usr/bin/env bash
set -u
cd "$(dirname "$0")/.."
export PATH="$PWD/tests/fakebin:$PATH" BW_SESSION=fake BACKUP_PASSWORD=test-only
export BACKUP_DIR="$(mktemp -d)" KEEP=3
fail=0
for i in 1 2 3 4 5; do ./bw_backup.sh >/dev/null || fail=1; sleep 1; done
n=$(ls "$BACKUP_DIR" | wc -l); [ "$n" -eq 3 ] && echo "ok 保留 3 份" || { echo "FAIL 份数 $n"; fail=1; }
perm=$(stat -c %a "$(ls -1t "$BACKUP_DIR"/*.json | head -1)"); [ "$perm" = 600 ] && echo "ok 权限 600" || { echo "FAIL 权限 $perm"; fail=1; }
FAKE_PLAIN=1 ./bw_backup.sh 2>/dev/null && { echo "FAIL 明文未拦截"; fail=1; } || echo "ok 拦截未加密导出"
[ "$(ls "$BACKUP_DIR" | wc -l)" -eq 3 ] && echo "ok 明文文件已删除" || fail=1
unset BACKUP_PASSWORD; ./bw_backup.sh 2>&1 | grep -q BACKUP_PASSWORD && echo "ok 缺少密码时拒绝运行" || fail=1
rm -rf "$BACKUP_DIR"; exit $fail
bash tests/test_backup.sh
检查结果(本机实际输出):
ok 保留 3 份
ok 权限 600
ok 拦截未加密导出
ok 明文文件已删除
ok 缺少密码时拒绝运行
第 4 步:真实运行(本文未执行)
bw login # 首次
export BW_SESSION=$(bw unlock --raw)
read -rs BACKUP_PASSWORD && export BACKUP_PASSWORD
./bw_backup.sh
bw lock
read -rs 不回显、不进入 shell 历史。--password 参数在运行期间会出现在进程列表里,多人共用的机器上不要运行这个脚本。
预期结果:✓ 备份完成:~/bw-backups/bitwarden_YYYYMMDD_HHMMSS.json(共保留 N 份)。
第 5 步:恢复演练
- 注册一个测试用的 Bitwarden 账号(或使用空的测试组织)。
- 网页版 工具 → 导入数据,格式选 Bitwarden (json),选择备份文件,输入备份密码。
- 抽查几个条目的用户名、网址、TOTP。
- 演练完删除测试账号中的数据。
调试示例
未解锁就运行,本机实际输出:
./bw_backup.sh: line 9: BW_SESSION: 请先 export BW_SESSION=$(bw unlock --raw)
: "${VAR:?提示}" 在变量缺失时立即退出并打印提示,避免脚本跑到一半才失败。
出错了怎么办?
| 现象 | 原因 | 解决 |
|---|---|---|
You are not logged in. |
没有 bw login |
先登录 |
Vault is locked. |
BW_SESSION 过期 | 重新 bw unlock --raw |
| 导入时提示密码错误 | 备份密码记错 | 只能用正确密码;所以要演练 |
| 份数没有减少 | KEEP 设置太大 | 调整 KEEP |
| 自建服务器 | CLI 默认连官方云 | bw config server https://你的服务器 |
模板规则
- 只做密码保护的加密导出,脚本校验格式。
- 备份密码与主密码不同,离线保存。
- 每季度做一次恢复演练。
项目资源
| 用途 | 地址 |
|---|---|
| 导出保险库数据 | https://bitwarden.com/help/export-your-data/ |
| 加密导出 | https://bitwarden.com/help/encrypted-export/ |
| Bitwarden CLI | https://bitwarden.com/help/cli/ |
| 导入数据 | https://bitwarden.com/help/import-data/ |
| Bitwarden 服务条款 | https://bitwarden.com/terms/ |
备份的价值只在恢复时体现。脚本保证每次导出都是加密的、份数可控,恢复演练保证这些文件真的能用。