<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>安全 on 火炬树</title>
        <link>https://torchtree.com/tags/%E5%AE%89%E5%85%A8/</link>
        <description>Recent content in 安全 on 火炬树</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <copyright>TorchTree Co., Ltd.</copyright>
        <lastBuildDate>Fri, 04 Sep 2026 23:00:00 +0800</lastBuildDate><atom:link href="https://torchtree.com/tags/%E5%AE%89%E5%85%A8/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>macOS Keychain 存取 API Key</title>
        <link>https://torchtree.com/post/macos-keychain-api-key-cli/</link>
        <pubDate>Fri, 04 Sep 2026 23:00:00 +0800</pubDate>
        
        <guid>https://torchtree.com/post/macos-keychain-api-key-cli/</guid>
        <description>&lt;p&gt;macOS 上存 API Key，我们以 &lt;code&gt;security&lt;/code&gt; 命令行读写 Keychain。本文是&lt;a class=&#34;link&#34; href=&#34;https://torchtree.com/post/api-key-storage-best-practices/&#34; &gt;《API Key 不应该放在 shell 配置文件里》&lt;/a&gt;中 Keychain 方案的展开，聚焦命令行增删改查。&lt;/p&gt;
&lt;h2 id=&#34;图形界面变化&#34;&gt;图形界面变化
&lt;/h2&gt;&lt;p&gt;macOS 15 Sequoia 起，网站登录，Wi-Fi 密码和 Passkey 纳入 Passwords (密码) 应用，Keychain Access (钥匙串访问) 移入 Other 文件夹，Spotlight 搜索 Keychain Access 跳转至 Passwords。&lt;/p&gt;
&lt;p&gt;Passwords 仅收录上述三类，不显示通用的 application password (应用密码)。Sequoia 下 Secure Note (安全备忘) 新建入口失效，新建仅弹出 New Password 对话框，“文件，新建密码项”流程无响应。&lt;/p&gt;
&lt;p&gt;API Key 这类自定义项在图形界面无对应入口，命令行为可用入口。&lt;/p&gt;
&lt;h2 id=&#34;密码项类型&#34;&gt;密码项类型
&lt;/h2&gt;&lt;p&gt;API Key 存放于 &lt;code&gt;generic password&lt;/code&gt; (通用密码) 项，以 service (服务)，account (账户)，password (密码) 三个字段定位。以下是字段与 API Key 的对应关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;service&lt;/code&gt; 为项目或应用名，如 &lt;code&gt;MyApp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;account&lt;/code&gt; 为 Key 名，如 &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password&lt;/code&gt; 为 Key 值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;internet password&lt;/code&gt; (互联网密码) 另有 &lt;code&gt;server&lt;/code&gt; (服务器)，&lt;code&gt;protocol&lt;/code&gt; (协议)，&lt;code&gt;port&lt;/code&gt; (端口) 等字段，用于网站登录，不用于 API Key。&lt;/p&gt;
&lt;h2 id=&#34;命令行增删改查&#34;&gt;命令行增删改查
&lt;/h2&gt;&lt;p&gt;我们以 login keychain (登录钥匙串) 为默认对象，未指定 keychain 文件的命令均读写该钥匙串。&lt;/p&gt;
&lt;h3 id=&#34;新增&#34;&gt;新增
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;-w&lt;/code&gt; 不跟值时转交互式输入，密码值不进入 shell history：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;附加备注可用 label 和 comment：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -l &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp OpenAI&amp;#34;&lt;/span&gt; -j &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;prod key 2026-09&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;查询&#34;&gt;查询
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;-w&lt;/code&gt; 仅输出密码值，&lt;code&gt;-g&lt;/code&gt; 输出完整条目及密码值：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security find-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security find-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -g
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;更新&#34;&gt;更新
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;-U&lt;/code&gt; 支持已存在时覆盖写入，不加 &lt;code&gt;-U&lt;/code&gt; 且条目已存在时命令报错：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -U -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不使用 &lt;code&gt;-U&lt;/code&gt; 时，先删除再新增可达到相同效果：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security delete-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security add-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;删除与列举&#34;&gt;删除与列举
&lt;/h3&gt;&lt;p&gt;以下是删除指定条目与列举全部条目的命令：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security delete-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;security dump-keychain | grep -A4 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;svce&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;dump-keychain&lt;/code&gt; 输出包含全部条目与元数据。&lt;/p&gt;
&lt;h2 id=&#34;脚本与代码调用&#34;&gt;脚本与代码调用
&lt;/h2&gt;&lt;p&gt;Shell 在启动时读取一次并注入环境变量，密码值不写入文件：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export OPENAI_API_KEY&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;security find-generic-password -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt; -a &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt; -w&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Python 经 &lt;code&gt;keyring&lt;/code&gt; (密钥存取库) 调用 Keychain，&lt;code&gt;service&lt;/code&gt; 和 &lt;code&gt;account&lt;/code&gt; 与命令行三元组对应：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; keyring
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;keyring&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_password(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sk-...&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;api_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; keyring&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_password(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;原生开发调用 Security framework (系统安全框架)，以 &lt;code&gt;kSecClassGenericPassword&lt;/code&gt; 加 &lt;code&gt;kSecAttrService&lt;/code&gt; 和 &lt;code&gt;kSecAttrAccount&lt;/code&gt; 查询，Node 以 &lt;code&gt;keytar&lt;/code&gt; 调用，Go 以 &lt;code&gt;go-keychain&lt;/code&gt; 调用，三者字段语义一致。&lt;/p&gt;
&lt;h2 id=&#34;实操注意点&#34;&gt;实操注意点
&lt;/h2&gt;&lt;p&gt;实操注意点集中在以下三处：history 泄露，授权弹窗，同步范围。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，history 泄露&lt;/strong&gt;。&lt;code&gt;security add-generic-password -w sk-...&lt;/code&gt; 将密码值写入 shell history 与进程表，我们以交互式输入或 &lt;code&gt;security -i&lt;/code&gt; 交互模式避开明文进入 history 与进程表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，授权弹窗&lt;/strong&gt;。默认写入 login keychain，登录后保持解锁，创建者进程默认直接读取，其他应用首次读取触发系统授权弹窗，点按 Allow Always 后不再弹出。CI 或 SSH 无图形会话的主机无法点按弹窗，以 &lt;code&gt;-T /Applications/xxx.app&lt;/code&gt; 预先限定可访问应用，或改用专用 keychain 文件。&lt;code&gt;-A&lt;/code&gt; 允许任意应用读取，未限定访问主体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，同步范围&lt;/strong&gt;。经 iCloud 同步的条目随账号同步到多台设备，高权限 API Key 存放于本地 login keychain，换机时随 keychain 文件迁移。&lt;/p&gt;
&lt;h2 id=&#34;参考来源&#34;&gt;参考来源
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://discussions.apple.com/thread/255764804&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Apple 社区关于 Sequoia 无法新建 Secure Note 的讨论&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://forums.macrumors.com/threads/keychain-app-is-it-still-in-macos-sequoia.2434342/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;MacRumors 论坛确认 Sequoia 下 Keychain Access 仍在但被隐藏&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.unix.com/man_page/osx/1/security/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;security 命令 man page 存档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.netmeister.org/blog/keychain-passwords.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Jan Schaumann 用 Keychain 管理命令行密码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
