文档中心 / 安全

加密细节

MimiKey 使用的算法与参数、密钥层级、信封与 sealed box 格式、全部 AAD 域字符串,以及可复现的测试向量。

概览

本页是 MimiKey 密码学设计的参考说明,面向安全研究者与需要自行实现兼容客户端的开发者。设计目标与威胁模型见安全模型。

所有二进制值(密钥、nonce、密文、签名)在 JSON 中一律使用 base64url、无填充(RFC 4648 §5)编码。

算法与参数

用途算法参数
两秘密派生(2SKD)Argon2id,版本 0x13内存 65536 KiB(64 MiB),迭代 3,并行度 1,输出 32 字节,盐 16 字节
子密钥派生HKDF-SHA256输出 32 字节;salt 与 info 见域字符串
对称加密XChaCha20-Poly1305256 位密钥,192 位随机 nonce,128 位认证标签;单次明文上限 256 KiB
密钥协商X25519用于账号身份密钥、共享库包裹、第三方结果与补丁的 sealed box
签名Ed25519第三方授权的每授权审批钥
账号登录SRP-6aRFC 5054 2048 位群,SHA-256,标识为规范化邮箱
指纹SHA-256取前 8 字节,小写十六进制(16 个字符)
授权码绑定PKCE S256base64url(SHA-256(code_verifier)),verifier 43–128 字符

Secret Key 为 16 字节(128 位)随机数,显示为 PM- 前缀加 26 个 Crockford Base32 字符。主密码长度要求 12–1024 字节(UTF-8)。

密钥层级

主密码 ─┐
Secret Key ─┼─ Argon2id(64 MiB, t=3, p=1, 账号盐 16B) ──┬─ 登录材料 ── SRP 验证器(存服务器)
账号盐 ─┘        (三个域分别派生,互不可推)          │
                                                       ├─ 个人库钥 ── HKDF("item/{item_id}") ── 条目子钥 ── 条目信封
                                                       │
                                                       └─ 身份材料 ── 密封 ── 账号身份私钥(X25519)
                                                                                │
                                         ┌──────────────────────────────────────┤
                                         │                                      │
                          打开:共享库密钥包裹                     打开:第三方修改补丁
                                         │
                                 共享库钥(随机 32B)── HKDF("item/{item_id}") ── 条目信封

第三方 SDK(每次安装、每次授权):
  seal 密钥对(X25519)── 打开:设备密封给它的结果
  审批密钥对(Ed25519)── 签名:每一个数据调用
密钥来源存放
登录材料2SKD,登录域仅内存;服务器只存 SRP 验证器
个人库钥2SKD,库域仅内存,从不传输或包裹
身份材料2SKD,身份域仅内存
账号身份私钥随机 X25519用身份材料密封后存服务器
共享库钥随机 32 字节逐成员包裹给其身份公钥后存服务器
条目子钥HKDF(库钥, item/{item_id})每次加解密时派生
第三方 seal 私钥、审批私钥接入方 SDK 在设备上随机生成接入方 App 的系统钥匙串,仅本机

2SKD 输入格式

三份材料使用相同的 Argon2id 参数,仅前缀域不同。Argon2id 的 password 输入按以下字节拼接:

domain || 0x00 || u32_be(len(master_password_utf8)) || master_password_utf8 || secret_key(16 字节)
材料domain
登录材料password-manager/account-login/v1
个人库钥password-manager/account-vault/v1
身份材料password-manager/account-identity/v1

salt 为账号的 16 字节 SRP 盐。修改主密码或找回账号时盐会随机换新,因此三份材料全部改变:个人库的条目会在客户端逐条重新加密,身份私钥按新材料重新密封。

信封格式

所有对称加密的结果都是同一种 JSON 信封:

{
  "format": 2,
  "nonce": "8Ovm4dzX0s3Iw765tK-qpaCblpGMh4J9",
  "ciphertext": "9eSfVW9LOkKE0BLuJRYysVYZYiJuhwLy…"
}
字段说明
format固定为 2
nonce24 字节随机 nonce
ciphertext密文与 16 字节 Poly1305 标签的拼接

XChaCha20-Poly1305 采用标准构造:用 HChaCha20 以 nonce 前 16 字节派生子密钥,再以 IETF ChaCha20-Poly1305 加密,其 12 字节 nonce 为 4 个零字节加上 24 字节 nonce 的后 8 字节。AAD 参与认证但不加密。

条目

条目子钥为 HKDF-SHA256(ikm = 库钥, salt = "password-manager/v2", info = "item/{item_id}"),AAD 为:

password-manager/item/v2/{vault_id}/{item_id}/{revision}

AAD 绑定库、条目和修订号:密文不能被挪到其他条目、其他库,也不能回填到其他修订号。条目明文是如下 JSON(以 UTF-8 编码):

{
  "title": "示例银行",
  "username": "me@example.com",
  "password": "correct horse battery staple",
  "url": "https://bank.example.com/login",
  "notes": "",
  "kind": "login",
  "extra": {},
  "updated_at": "2026-10-23T00:00:00.000Z"
}

kind 取 login、credit_card、bank_card、api_key、server、secure_note 之一,缺省视为 login;extra 存放该类型特有的字段(例如卡号、有效期)。

Sealed box 格式

需要把数据交给「只有某个公钥持有者才能打开」的场景(共享库密钥包裹、第三方结果、第三方修改补丁)使用 sealed box:

1. 生成临时 X25519 密钥对 (esk, epk)
2. shared = X25519(esk, 收件人公钥);结果为全零时拒绝
3. key = HKDF-SHA256(ikm = shared, salt = 域字符串, info = AAD),32 字节
4. envelope = XChaCha20-Poly1305(key, 随机 24B nonce, AAD, 明文)
{
  "format": 1,
  "epk": "PHld72bSpQfUtjDl4WAtULZ7pgXRKD8lMW0U1d-OABo",
  "envelope": {
    "format": 2,
    "nonce": "8Ovm4dzX0s3Iw765tK-qpaCblpGMh4J9",
    "ciphertext": "9eSfVW9LOkKE0BLuJRYysVYZYiJuhwLy…"
  }
}
字段说明
format固定为 1
epk32 字节临时公钥
envelope上文的信封,format 为 2

在第三方授权 API 中,sealed box 以字符串形式传输:result_enc 与 patch_enc 的值是上述 JSON 序列化后的文本。

AAD 与域字符串

不同用途的域与 AAD 彼此独立,一种用途的密文不能在另一种用途下被打开。

用途密钥派生AAD
条目HKDF salt password-manager/v2,info item/{item_id}password-manager/item/v2/{vault_id}/{item_id}/{revision}
账号身份私钥密钥 = 身份材料password-manager/identity-key/v1
共享库密钥包裹sealed box,salt password-manager/vault-key-wrap/v1password-manager/vault-key-wrap/v1/{vault_id}/{recipient_account_id}
第三方结果sealed box,salt password-manager/oauth-seal/v1password-manager/oauth-result/v1/{grant_id}/{request_id}
第三方修改补丁sealed box,salt password-manager/oauth-seal/v1password-manager/oauth-patch/v1/{grant_id}/{request_id}
生物识别解锁包(Mac)包裹密钥由 Secure Enclave 保护password-manager/device-unlock/v1 + 换行 + 绑定信息
备份清单HKDF salt password-manager/v2,info backup/manifest/v1/{backup_id}password-manager/backup/manifest/v1/{vault_id}/{backup_id}
旧模式 2 库钥包裹(已停用)sealed box,salt password-manager/oauth-seal/v1password-manager/oauth-vault-key/v1/{client_id}/{account_id}/{vault_id}

第三方结果与补丁

结果(result_enc)补丁(patch_enc)
由谁密封执行请求的 MimiKey 设备接入方 SDK
收件人该授权的 seal 公钥账号身份公钥(GET /api/oauth/account 的 identity_public_key)
由谁打开接入方 SDK你已解锁的 MimiKey 设备
明文{"item":{"id","revision","record"}},list_items 为 {"v":2,"items":[…]}{"patch":{"字段名":"新值"}}

修改补丁的 AAD 需要 request_id,因此 update_item 分两步:先创建草稿拿到 request_id,再上传补丁,见 API 参考。

第三方调用签名

每个数据调用都要用该授权的审批私钥(Ed25519)签名下列规范串(UTF-8,\n 分隔,无尾随换行):

{METHOD}\n{PATH}\n{TIMESTAMP}\n{NONCE}\n{SHA256_HEX(BODY)}
  • PATH 不含查询串;BODY 为请求体原始字节,空体的 SHA-256 为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。
  • 签名为 64 字节,base64url 编码后放入 x-pm-signature。
  • 审批钥指纹 = SHA-256(公钥 32 字节) 前 8 字节十六进制,显示在同意卡上供核对。

完整的请求头约定见 API 参考。

测试向量

以下向量由 Rust 参考实现以固定种子生成,Swift SDK 的单元测试逐字节校验。它们使用公开的测试密钥,不对应任何真实账号。完整文件随 Swift SDK 一同提供:Tests/MimiKeySDKTests/Vectors/oauth_vectors.json。

审批签名

字段值
种子(Ed25519 私钥)3-bt9PsCCRAXHiUsMzpBSE9WXWRrcnmAh46VnKOqsbg
公钥lAfBINSvrkhw1qvNeOF5ug8mgbUoRkv3Fjn79UVUfIk
指纹d1984a0a3442c41c

空请求体:

GET
/api/oauth/account
1761177600
n-0123456789abcdef
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

签名:KngAgL1nChfn4NugEnlNlesKA8EtmrhpsL1GU9pU_Z0AcwkQRzP9iasdaFfe3nEVcvCGUT92eWHzzZCEX849DQ

带请求体(体为 {"op":"pick_item","vault_id":null,"hint":{"url":"https://bank.example.com"},"reason":"登录示例银行完成对账"}):

POST
/api/oauth/requests
1761177600
n-0123456789abcdef
a60bf0d8213f0871138d7ed45ba0cfdbd8aa81238a24262b2e5ce9ab985c8a07

签名:-PzhO-9DzOmyskR2C-Sq85OOWMiGgCs_bRMy1vI3_T_PN2RAJrWcysXhn_7A1GBo5_6RaVqlD_axrK2gncNkCQ

Sealed box

向量文件的 result 与 patch 两节给出固定的收件人密钥、临时私钥、nonce、AAD、明文与期望的 sealed box JSON,可同时验证密封与打开两个方向:

字段值
收件人公钥9Xwg4YSHxLN9dr2JSRYL_wfRh_0iafGiNzQtT5blNys
收件人指纹8d9b63107169f06e
grant_id6f1d2c3b-4a59-4e68-8b7c-9d0e1f2a3b4c
request_ida1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
补丁明文{"patch":{"password":"new-password-✓"}}

PKCE

服务端使用 RFC 7636 附录 B 的向量自检:verifier dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk 对应 challenge E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM。

备份清单摘要

备份清单的认证输入是按固定字段顺序紧凑编码的 JSON(不是 RFC 8785):

{"backup_format":1,"format":2,"vault_id":"00000000-0000-4000-8000-000000000001","backup_id":"00000000-0000-4000-8000-000000000002","items":[{"id":"00000000-0000-4000-8000-000000000003","revision":1,"envelope":{"format":2,"nonce":"AA","ciphertext":"BB"}}]}

SHA-256:31406c915e36b94924581c9d2a7a01f716fe1d2d36833ac54fe7c38a734d34fb

注意 信封的 nonce 每次随机生成,因此除上述固定种子的向量外,同一明文每次加密得到的密文都不同;请以「能否打开」而不是「密文是否相同」来验证互通性。

最后更新: 2026-10-23