第一步:定义数据契约

一、核心密码表:

字段名 (键) 数据类型 说明与作用 零知识安全要求
id String (UUID) 条目标识。 每一条密码记录的全局唯一ID。客户端生成,服务端只做存储。 公开信息,无安全风险。
user_id String (UUID) 用户标识。 归属的账号ID,用于权限隔离。 公开信息。
encrypted_data Text / BLOB 加密后的核心数据。 里面打包包含了标题、网址、账号、密码、备注等所有明文信息。 绝对密文。 服务端看到的必须是一堆乱码,无法解析。
iv String (Base64) 初始化向量。 每次使用算法加密时,必须生成一个随机的偏移量。解密时需要用到它。 明文存储,它是解密的钥匙孔,不是钥匙。
auth_tag String (Base64) 认证标签。 AES-GCM 加密后,不仅会产生乱码(Ciphertext),还会产生一段约 16 字节的 Auth Tag。
revision Integer 版本号。 每次客户端修改这条密码,版本号 +1。这是多设备同步的核心,用来判断谁的数据最新。 公开信息。
deleted Integer (0 或 1) 软删除标记。 当你在手机上删除了一个密码,不要在数据库里真删,而是标记为 1。这样另一台设备拉取同步时,才知道要“在本地删掉这条数据”。 公开信息。
updated_at Timestamp 最后修改时间。 辅助版本号处理极端冲突,或用于 UI 展示排序。 公开信息。

二、客户端侧的“明文打包”逻辑

React Native 客户端当用户填完一个表单点击保存时,流程如下:

  1. 客户端将这些零散的字段组合成一个 JSON 对象(这部分服务端永远看不到):
{
"title": "Github",
"url": "https://github.com",
"account": "your_account_or_email",
"password": "your_password",
"notes": "这是我的备用账号",
"totp_seed": "JBSWY3DPEHPK3PXP",
"custom_fields": [
{ "name": "安全问题", "value": "我的小学名字" }
]
}
  1. 客户端使用主密码派生出的密钥(Key)和随机生成的向量(IV),将上面这个 JSON 对象整体加密成一段密文(比如 U2FsdGVkX19+...)。
  2. 客户端将这段密文塞进 encrypted_data 字段,连同 id, iv, revision 等发给 Go 语言写的后端 API。

三、 辅助表:

User 表

字段名 数据类型 说明与作用
id String (UUID) 用户唯一标识。
username String 用户名,可作为账号。
master_password_hash String 验证哈希。 注意,这不是主密码本身,而是主密码经过极高强度哈希运算后的结果,仅用于服务端验证登录,不能用于解密数据。
crypto_salt String 加密盐值。 用户注册时客户端生成的一串随机字符。换新手机登录时,必须先从服务端拉取这个盐值,结合用户手打的主密码,才能在本地还原出真正的加密密钥。
User_Devices 表
字段名 数据类型 说明与作用
device_id String (UUID) 主键。 设备唯一标识。
user_id String (UUID) 所属用户。 必须关联 User 表,否则服务器不知道该设备属于谁。
device_name String 设备名称。 例如 “iPhone 15 Pro” 或 “Windows 桌面”。
login_time Timestamp 使用时间戳 (Timestamp) 方便后端自动清理过期设备或前端进行排序。
last_active Timestamp 最后活跃时间。 每次同步数据时更新。长时间未活跃的设备可被视为已失效。