2026年 05月 14日 的归档
Keychain和security简介
写 CCAS 的时候要做的事其实很本质:在多个 Claude 账号之间切换。第一个要弄清楚的是 —— Claude Code 把自己的 OAuth credentials 存在哪?
翻一下之前泄露的代码很容易就知道了:在 macOS Keychain 里,service 名字是 Claude Code-credentials。你现在就可以在终端里看一眼:
|
1 |
security find-generic-password -s "Claude Code-credentials" -w |
会打印出一坨 JSON,里面就是 access token、refresh token 这些。
这不是 Claude Code 的”小众”选择 —— 各种 IDE、CLI、Apple 自家的 Safari Mail,所有在 macOS 上要存”密码、token、密钥”的程序,默认就该放进 Keychain。它是这台机器上事实标准的敏感数据存储。
那它到底是什么?

Keychain 是什么
最直白的描述:Keychain 是 macOS 自带的加密键值数据库,专门为存敏感数据设计。每条记录由系统加密,挂在登录用户的身份下,由 securityd 这个常驻守护进程管理。
它的物理形态是磁盘上的几个文件,常见的有:
|
1 2 |
~/Library/Keychains/login.keychain-db # 用户登录 keychain /Library/Keychains/System.keychain # 系统级(如 WiFi 密码) |
你不会直接读这些文件 —— 它们是加密的 SQLite,主密钥跟你的登录密码绑定。读写都要走系统 API,由 securityd 解锁后再返回明文。
它解决了什么
如果不用 Keychain,自己拿个 JSON 文件存 token 行不行?技术上可以,但你要面对几个问题:
1. 磁盘加密
文件丢在 ~/.config/ 下是明文。任何能读你 home 目录的进程都能拿到(包括别的 app、误装的恶意脚本)。即便你给文件 chmod 600,磁盘镜像被拷走照样能看。
Keychain 用 AES 加密,主密钥来自你的登录密码 —— 机器没登录、密码不对,磁盘镜像里的内容就解不出来。
2. 进程隔离
文件系统只有”用户级”权限。但同一用户跑的所有 app 是平等的:Claude Code 写下的 token,理论上你装的任何一个三方 app 都能读。
Keychain 的每条记录带 ACL(access control list),记录”谁创建的、谁可以读”。当一个 app 试图读另一个 app 创建的条目时,系统会弹窗让用户确认。这是文件系统本身做不到的。
3. 用户可见、可管理
打开 Keychain Access.app,你能看到所有条目、谁创建的、什么时候改的,可以手动删。要 export 还得再输一次登录密码。
4. 跨设备同步
勾上 “iCloud Keychain” 之后,相应条目会端到端加密同步到你登录同一 Apple ID 的其他 Mac、iPhone、iPad。Safari 的密码、WiFi 密码、Apple Pay 卡号走的都是这个。
数据模型
Keychain 里有几类 item,最常用的是 generic password(任意 key-value 的密钥/token,多数 app 自定义存储用这个)和 internet password(带 protocol/host/port/path 字段,Safari 存网站密码用)。此外还有 certificate、key(公私钥对)、identity(cert + key 对)。
generic password 的核心字段就两个:
- service:通常用 reverse-DNS 风格的命名空间,例如
com.apple.account或Claude Code-credentials。 - account:同一 service 下区分多条记录的标识,通常是用户名或邮箱。
service + account 唯一定位一条记录。Password 字段本身可以是任意字节(你完全可以塞一整段 JSON 进去,Claude Code 就是这么干的)。
怎么读写:security 命令
Apple 给开发者两套接口:
- C API:
SecItem*这一族(旧的SecKeychain*已经不推荐)。Swift / Objective-C app 一般用这个。 - 命令行
/usr/bin/security:脚本和调试用。
CCAS 走的是后者,省得对接 C API;好处是用户自己在终端就能验证一切。最常用的三条:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 写入 security add-generic-password \ -s "com.example.myapp" \ -a "alice@example.com" \ -w "the-secret" # 读出 security find-generic-password \ -s "com.example.myapp" \ -a "alice@example.com" \ -w # 删除 security delete-generic-password \ -s "com.example.myapp" \ -a "alice@example.com" |
-s 是 service,-a 是 account,-w 表示”只把 password 内容打到 stdout”。不加 -w 会打印所有属性元数据,但隐去 password。
更多有用的子命令:
|
1 2 3 4 5 |
security list-keychains -d user # 列出当前 keychain search list security unlock-keychain # 手动解锁 security dump-keychain # 列出当前 keychain 中所有 item 的元数据 security add-internet-password ... # 写网站密码 security find-certificate ... # 找证书 |
几个不那么直觉的细节
search list:系统其实管理着一个 keychain “搜索列表”,security find-* 默认遍历整个列表。常见情况下只有 login.keychain-db,但用户/MDM 可能加挂别的。如果某条记录意外落在搜索列表外的 keychain 里,会出现”dump 看得到、find 找不到”的现象,这时候要在命令末尾显式带上 keychain 路径。
密码存二进制:用 -w "value" 写入时整个参数被当作 UTF-8 字符串。如果 value 里有换行、引号、控制字符,shell 转义会很烦人。更可靠的方式是 -X hex,把内容先转 hex 再交给 security,由它自己 decode 后存原始字节。
-U 不一定是真正的 upsert:add-generic-password -U 文档说”如果存在则更新”,但匹配条件偏严,遇到某些属性差异会判定为”新条目”再插一条。同 service+account 的重复记录会越积越多。要 idempotent 的话,先 delete 再 add 更稳。
account 不是严格过滤:find-generic-password -s SVC -a ACCT 如果 account 没精确匹配,可能 fallback 命中同 service 下别的 account —— 不报错,直接返回别人的内容。读完后再校验返回值是个好习惯。
小结
Keychain 是 macOS 上存敏感数据的标准答案。它做的事不复杂 —— 加密、隔离、用户可控、可同步 —— 但是把这四件事做齐了,且对开发者基本零成本。
下次写 Mac app 要存 token、API key 或者密码,不用犹豫,直接 SecItem / security 走起,比自己掂量”放哪、加什么密、谁能读”省太多事。