<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://i.han.rs/feed_style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh">
    <tabi:metadata xmlns:tabi="https://github.com/welpo/tabi">
        <tabi:base_url>https:&#x2F;&#x2F;i.han.rs</tabi:base_url>
        <tabi:separator>
            •
        </tabi:separator>
        <tabi:about_feeds>这是Web Feed，又称为Atom Feed，把现在的网址复制到新闻阅读器即可订阅本站文章。造访「About Feeds」来了解更多资讯。</tabi:about_feeds>
        <tabi:visit_the_site>造访网站</tabi:visit_the_site>
        <tabi:recent_posts>近期文章</tabi:recent_posts>
        <tabi:last_updated_on>更新于 $DATE</tabi:last_updated_on>
        <tabi:default_theme></tabi:default_theme>
        <tabi:post_listing_date>date</tabi:post_listing_date>
        <tabi:current_section>i.han.rs</tabi:current_section>
    </tabi:metadata><link rel="extra-stylesheet" href="https://i.han.rs/skins/sakura.css?h=2d9c69af51135683e3bd" /><title>i.han.rs</title>
        <subtitle>Emmm?</subtitle>
    <link href="https://i.han.rs/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://i.han.rs" rel="alternate" type="text/html"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-30T00:00:00+00:00</updated>
    <id>https://i.han.rs/atom.xml</id><entry xml:lang="zh">
        <title>Instructions on building RFC documents locally</title>
        <published>2026-04-30T00:00:00+00:00</published>
        <updated>2026-04-30T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/26-04-30-instructions-on-building-rfc-docs/" type="text/html"/>
        <id>https://i.han.rs/blog/26-04-30-instructions-on-building-rfc-docs/</id>
        
            <content type="html">&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;python&quot;&gt;Python&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;Python 3.x, here we use Python 3.14.1&lt;&#x2F;li&gt;
&lt;li&gt;Python virtual environment (optional but recommended)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Using pyenv:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;pyenv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; install 3.14.1&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;pyenv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; virtualenv 3.14.1 rfc&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;pyenv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; activate rfc&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then install the required packages:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;pip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; install sortedcontainers&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;nodejs&quot;&gt;NodeJS&lt;&#x2F;h3&gt;
&lt;p&gt;We make use of &lt;code&gt;aasvg&lt;&#x2F;code&gt;, just install it globally:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;npm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; install&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;g&lt;&#x2F;span&gt; aasvg&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;ietf-toolchains&quot;&gt;IETF toolchains&lt;&#x2F;h3&gt;
&lt;p&gt;We need &lt;code&gt;kramdown-rfc&lt;&#x2F;code&gt; and &lt;code&gt;xml2rfc&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;apt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; install kramdown-rfc xml2rfc&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then run &lt;code&gt;make&lt;&#x2F;code&gt; to install other dependencies. This will try to build the document, but it will fail.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-the-document&quot;&gt;Building the document&lt;&#x2F;h2&gt;
&lt;p&gt;Run the following command to build the HTML:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;cat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; draft-ietf-tls-rfc8446bis.md&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;lib&#x2F;trace.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt; draft-ietf-tls-rfc8446bis.xml&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt; preprocessor python3 mk-appendix.py&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;kramdown-rfc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;v3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt;  &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;lib&#x2F;trace.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;q&lt;&#x2F;span&gt; draft-ietf-tls-rfc8446bis.xml&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt; v2v3 xml2rfc&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;q&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;rfc-base-url&lt;&#x2F;span&gt; https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;id-base-url&lt;&#x2F;span&gt; https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;allow-local-file-access&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;cache&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-option z-shell&quot;&gt;=&lt;&#x2F;span&gt;.&#x2F;.refcache&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;v2v3&lt;&#x2F;span&gt; &#x2F;dev&#x2F;stdin&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;o&lt;&#x2F;span&gt; &#x2F;dev&#x2F;stdout &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; draft-ietf-tls-rfc8446bis.xml&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-and z-shell&quot;&gt;&amp;amp;&amp;amp;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;xml2rfc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; draft-ietf-tls-rfc8446bis.xml&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;html&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is modified from the CI build script, removing step &lt;code&gt;lib&#x2F;trace.sh -v -q draft-ietf-tls-rfc8446bis.xml -s venue python3 lib&#x2F;add-note.py&lt;&#x2F;code&gt; since it failed locally for me.&lt;&#x2F;p&gt;
&lt;p&gt;If you want a cleaner way, run &lt;code&gt;make clean&lt;&#x2F;code&gt; in advance.&lt;&#x2F;p&gt;
</content>
        <summary type="html">仅供参考.
</summary>
        </entry><entry xml:lang="zh">
        <title>了解和使用 Rootless Podman</title>
        <published>2026-02-04T00:00:00+00:00</published>
        <updated>2026-02-10T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/26-02-04-non-root-podman/" type="text/html"/>
        <id>https://i.han.rs/blog/26-02-04-non-root-podman/</id>
        
            <content type="html">&lt;h2 id=&quot;dao-yu&quot;&gt;导语&lt;&#x2F;h2&gt;
&lt;p&gt;我们需要意识到:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;日常使用 “root” 本来就存在极大安全隐患.&lt;&#x2F;li&gt;
&lt;li&gt;Docker 的守护程序设计, 导致 Docker 并不能真正实现 Rootless, Docker Rootless 的配置也是极其麻烦.&lt;&#x2F;li&gt;
&lt;li&gt;在容器中运行的进程与在宿主上直接运行的其他进程没有什么不同. 部分镜像遵循最优实践, 以自定义用户启动程序, 安全性尚可; 但部分镜像直接以容器内的 “root” 启动进程, 如果在使用该镜像时没有指定用户或配置用户映射, 则近似等价于以宿主机的 “root” 启动了程序; 如果还指定了 &lt;code&gt;--privileged&lt;&#x2F;code&gt;, 安全性直接归零.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Podman 由于其无守护程序设计, 天然支持 Rootless 模式.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;an-zhuang-podman&quot;&gt;安装 Podman&lt;&#x2F;h2&gt;
&lt;p&gt;参考官方文档 &lt;a href=&quot;https:&#x2F;&#x2F;podman.io&#x2F;docs&#x2F;installation&quot;&gt;https:&#x2F;&#x2F;podman.io&#x2F;docs&#x2F;installation&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;在 Debian 13 中, 直接通过 apt 安装即可:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; apt update&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; apt install podman&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;完成安装后, 执行 &lt;code&gt;podman info&lt;&#x2F;code&gt; 可以查看 Podman 的详细配置信息.&lt;&#x2F;p&gt;
&lt;p&gt;需要注意, 截止至本文写作时, 虽然 Podman 的最新 stable 版本是 5.7.1, 但 Debian 13 官方软件包提供的 Podman 的版本是 5.4.2, 本文所有例子均基于 5.4.2 测试.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rootless-podman-ji-ben-gai-nian&quot;&gt;Rootless Podman 基本概念&lt;&#x2F;h2&gt;
&lt;p&gt;在正式开始使用 Podman 前, 我们需要了解一些基本概念.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ming-ming-kong-jian-namespace&quot;&gt;命名空间 (Namespace)&lt;&#x2F;h3&gt;
&lt;p&gt;命名空间是 Linux 提供的一种内核级别环境隔离的机制, 容器化技术重度使用了命名空间机制来实现容器的隔离. 关于命名空间的详细内容此处不再赘述, 读者可以阅读文末的参考文献, 此处仅介绍我们最常打交道的, 也是给普通用户带来最多 “问题” 的用户命名空间和网络命名空间.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;yong-hu-ming-ming-kong-jian&quot;&gt;用户命名空间&lt;&#x2F;h4&gt;
&lt;p&gt;不严谨地说, 用户命名空间提供了一种使容器内用户能够映射到宿主机上的(通常是非特权的)用户的机制, 而容器中的 “root” 仅在容器的用户命名空间内具有 “完全” 的权限, 对容器的用户命名空间外的资源的操作权限则被限制为 “映射” 到的 “普通用户” 的操作权限, 正如官方文档所言: “Rootless Podman is not, and will never be, root”.&lt;&#x2F;p&gt;
&lt;p&gt;默认情况下, 通过 Rootless Podman 运行容器时, 容器内的 “root” (UID=0, GID=0) 会被映射为运行容器的用户, 其他 UID (GID) 则映射至宿主机上的高位 UID (GID), 这些高位 UID (GID) 往往并没有对应的宿主机用户(组), 则文件权限按照 “Others” 处理, 拥有的任何文件对象将被视为由 “nobody” (65534, &lt;code&gt;kernel.overflowuid&lt;&#x2F;code&gt;) 拥有.&lt;&#x2F;p&gt;
&lt;p&gt;可以通过 &lt;a href=&quot;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-unshare.1.html&quot;&gt;&lt;code&gt;podman-unshare&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; 工具创建并进入一新用户命名空间, 以便捷地验证上述映射关系 (值得一提, 在调试 Rootless Podman 的权限问题时我们会经常和这个工具打交道). 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=1000(hantong) gid=1000(hantong) groups=1000(hantong),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cat &#x2F;proc&#x2F;self&#x2F;uid_map
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         0       1000          1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         1     100000      65536
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cat &#x2F;proc&#x2F;self&#x2F;gid_map
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         0       1000          1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         1     100000      65536
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; exit
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可以看到, 容器内的 UID (GID) 0 被映射为宿主机的 UID (GID) 1000; 从 UID (GID) 1 开始的 65536 个 UID (GID) 逐个被映射为宿主机的 UID (GID) 100000、100001, 以此类推.&lt;&#x2F;p&gt;
&lt;p&gt;这里的映射规则是由 &lt;code&gt;&#x2F;etc&#x2F;subuid&lt;&#x2F;code&gt; 和 &lt;code&gt;&#x2F;etc&#x2F;subgid&lt;&#x2F;code&gt; 文件定义的, 通常在使用 &lt;code&gt;adduser&lt;&#x2F;code&gt; (&lt;code&gt;addgroup&lt;&#x2F;code&gt;) 添加用户(组)时会自动设置:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cat &#x2F;etc&#x2F;subuid
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hantong:100000:65536
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cat &#x2F;etc&#x2F;subgid
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hantong:100000:65536
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;前面提到, 默认情况下容器中的 “root” 被映射为运行容器的用户, 对容器外资源的访问权限与当前用户 “基本” 一致. 参考下面的例子, 相信读者就知道 “映射” 是什么含义了:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ls -ildn &#x2F;home&#x2F;hantong
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;143631 drwx------ 17 1000 1000 4096 Feb  8 21:53 &#x2F;home&#x2F;hantong
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare ls -ildn &#x2F;home&#x2F;hantong
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;143631 drwx------ 17 0 0 4096 Feb  8 21:53 &#x2F;home&#x2F;hantong
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare touch &#x2F;tmp&#x2F;test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare ls -iln &#x2F;tmp&#x2F;test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;3432 -rw-r--r-- 1 0 0 0 Feb  8 21:54 &#x2F;tmp&#x2F;test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ls -iln &#x2F;tmp&#x2F;test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;3432 -rw-r--r-- 1 1000 1000 0 Feb  8 21:54 &#x2F;tmp&#x2F;test
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如果不幸地, 你的容器进程通过某些安全漏洞逃逸了容器, 那么它也只能以映射到的高位 UID (GID) 的权限 (或者说, “nobody” 的权限) 在宿主机上运行, 这大大降低了容器逃逸后的危害.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;wang-luo-ming-ming-kong-jian&quot;&gt;网络命名空间&lt;&#x2F;h4&gt;
&lt;p&gt;(TBD.)&lt;&#x2F;p&gt;
&lt;p&gt;网络命名空间负责提供容器内网络与宿主机网络以及其他容器网络的隔离. 默认情况下, 各个容器有其独立的网络命名空间, 而不在同一网络命名空间中的进程无法直接通信.&lt;&#x2F;p&gt;
&lt;p&gt;Docker Rootless 和旧版本 Podman 使用 &lt;code&gt;slirp4netns&lt;&#x2F;code&gt; 用户态网络驱动来实现 Rootless 网络, 性能较差; Podman v5 以后, 默认使用 “性能更优” 的 &lt;code&gt;pasta&lt;&#x2F;code&gt; 代替 &lt;code&gt;slirp4netns&lt;&#x2F;code&gt;. 相对于 &lt;code&gt;slirp4netns&lt;&#x2F;code&gt;, &lt;code&gt;pasta&lt;&#x2F;code&gt; 的特点:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pasta&lt;&#x2F;code&gt; 支持 IPv6;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;pasta&lt;&#x2F;code&gt; 使用主机中的接口名称, 从主机复制 IP 地址并使用主机中的网关地址.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;下面是一个例子:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellscript&quot; class=&quot;language-shellscript z-code&quot;&gt;&lt;code class=&quot;language-shellscript&quot; data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare --rootless-netns ip addr
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;2: eth0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 65520 qdisc fq state UNKNOWN group default qlen 1000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    link&#x2F;ether 52:3a:d5:14:43:4c brd ff:ff:ff:ff:ff:ff
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    inet 192.168.1.200&#x2F;24 brd 192.168.1.255 scope global eth0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;       valid_lft forever preferred_lft forever
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare ip addr
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;2: eth0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq state UP group default qlen 1000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    link&#x2F;ether bc:24:11:46:30:a6 brd ff:ff:ff:ff:ff:ff
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    altname enp6s18
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    altname enxbc24114630a6
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    inet 192.168.1.200&#x2F;24 brd 192.168.1.255 scope global eth0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;       valid_lft forever preferred_lft forever
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;3: lan0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq state UP group default qlen 1000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    link&#x2F;ether bc:24:11:40:61:e6 brd ff:ff:ff:ff:ff:ff
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    altname enp6s19
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    altname enxbc24114061e6
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    inet 172.16.0.2&#x2F;24 brd 172.16.0.255 scope global lan0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;       valid_lft forever preferred_lft forever
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;shellscript&quot; class=&quot;language-shellscript z-code&quot;&gt;&lt;code class=&quot;language-shellscript&quot; data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; curl http:&#x2F;&#x2F;127.0.0.1:1111&#x2F; -I
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;HTTP&#x2F;1.1 200 OK
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;content-type: text&#x2F;html
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;content-length: 12252
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;date: Mon, 09 Feb 2026 05:11:24 GMT
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman unshare --rootless-netns curl http:&#x2F;&#x2F;127.0.0.1:1111&#x2F; -I
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;curl: (7) Failed to connect to 127.0.0.1 port 1111 after 0 ms: Could not connect to server
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;cun-chu-qu-dong&quot;&gt;存储驱动&lt;&#x2F;h3&gt;
&lt;p&gt;(TBD.)&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;OverlayFS 和 VFS 的权限表现似乎有差异.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;wang-luo&quot;&gt;网络&lt;&#x2F;h3&gt;
&lt;p&gt;(TBD.)&lt;&#x2F;p&gt;
&lt;p&gt;参考文档 &lt;a href=&quot;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-run.1.html#network-mode-net&quot;&gt;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-run.1.html#network-mode-net&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Podman 提供多种网络模式:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;bridge&lt;&#x2F;code&gt; (桥接).&lt;&#x2F;p&gt;
&lt;p&gt;Rootful Podman 默认, 直接桥接到主机网络接口.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;创建网络命名空间, 但是不配置网络, 容器内没有任何网络接口.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;container:id&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;加入另一个容器的网络命名空间.&lt;&#x2F;p&gt;
&lt;p&gt;(笔者注: 更推荐使用 Pod 管理需要通过网络相互访问的容器.)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;host&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;复用主机的网络命名空间. 虽然性能很好, 但这也使容器能够完全访问宿主机的 abstract unix socket 以及绑定到 localhost 的 TCP &#x2F; UDP socket, 存在安全隐患.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pasta[:OPTIONS,…]&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Rootless Podman 默认, 创建一个新的网络命名空间, 使用 &lt;code&gt;pasta&lt;&#x2F;code&gt; 创建用户态网络堆栈.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;其他模式此处暂不展开, 如有需要参考官方文档.&lt;&#x2F;p&gt;
&lt;p&gt;值得一提, &lt;code&gt;pasta&lt;&#x2F;code&gt; 的性能对比于直接使用 &lt;code&gt;host&lt;&#x2F;code&gt; 模式仍然有不小的差距. 笔者在个人的存储服务器上进行了简要测试: TCP 吞吐量由于 &lt;code&gt;splice&lt;&#x2F;code&gt; 的使用下降幅度不大, 即便是重负载仍然能有 &lt;code&gt;host&lt;&#x2F;code&gt; 模式下八九成; UDP 吞吐量则受机器负载影响大, 笔者测试时, 遇到机器本身处于重负载状态, UDP 吞吐量最差可能暴跌至 &lt;code&gt;host&lt;&#x2F;code&gt; 模式下的两三成. 造成该问题的原因大抵是 Linux 内核本来对 UDP 优化就差, 缺乏 TCP &lt;code&gt;splice&lt;&#x2F;code&gt; 那种零拷贝的机制; 此外 pasta 应该也没用上 UDP GSO &#x2F; GRO, 浪费大量 CPU 时间, 在 CPU 受限时自然性能下降.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pei-zhi-podman&quot;&gt;配置 Podman&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;配置镜像仓库&lt;&#x2F;p&gt;
&lt;p&gt;Podman 默认设置下无法像 Docker 那样不提供镜像源时默认从 &lt;code&gt;docker.io&lt;&#x2F;code&gt; 拉取镜像.&lt;&#x2F;p&gt;
&lt;p&gt;建议配置 &lt;code&gt;registries.conf&lt;&#x2F;code&gt; 文件:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; sudo nano &#x2F;etc&#x2F;containers&#x2F;registries.conf 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;修改 &lt;code&gt;unqualified-search-registries&lt;&#x2F;code&gt; 字段, 添加 &lt;code&gt;docker.io&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;conf&quot; class=&quot;language-conf z-code&quot;&gt;&lt;code class=&quot;language-conf&quot; data-lang=&quot;conf&quot;&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;unqualified-search-registries&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; [&lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;docker.io&amp;quot;&lt;&#x2F;span&gt;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;yun-xing-rootless-podman-rong-qi&quot;&gt;运行 Rootless Podman 容器&lt;&#x2F;h2&gt;
&lt;p&gt;完成上述配置后, 即可以非 root 用户运行 Podman 容器. 测试如下:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run hello
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;!... Hello Podman World ...!
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         .--&amp;quot;--.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;       &#x2F; -     - \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &#x2F; (O)   (O) \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   ~~~| -=(,Y,)=- |
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    .---. &#x2F;`  \   |~~
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt; ~&#x2F;  o  o \~~~~.----. ~~
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  | =(X)= |~  &#x2F; (O (O) \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   ~~~~~~~  ~| =(Y_)=-  |
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  ~~~~    ~~~|   U      |~~
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Project:   https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Website:   https:&#x2F;&#x2F;podman.io
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Desktop:   https:&#x2F;&#x2F;podman-desktop.io
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Documents: https:&#x2F;&#x2F;docs.podman.io
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;YouTube:   https:&#x2F;&#x2F;youtube.com&#x2F;@Podman
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;X&#x2F;Twitter: @Podman_io
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Mastodon:  @Podman_io@fosstodon.org
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;当然, Rootless Podman 的配置和使用由于命名空间等问题有和 Docker 不同的地方, 下面不完整地列举一些.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;需要配置 &lt;code&gt;&#x2F;etc&#x2F;subuid&lt;&#x2F;code&gt; 和 &lt;code&gt;&#x2F;etc&#x2F;subgid&lt;&#x2F;code&gt; 文件.&lt;&#x2F;p&gt;
&lt;p&gt;一般来说, 通过 &lt;code&gt;useradd&lt;&#x2F;code&gt; 命令创建用户时, 系统会自动为新用户在 &lt;code&gt;&#x2F;etc&#x2F;subuid&lt;&#x2F;code&gt; 和 &lt;code&gt;&#x2F;etc&#x2F;subgid&lt;&#x2F;code&gt; 中分配 UID 和 GID 范围; 如果是手动创建用户, 则需要手动添加相应条目. 例如, 为用户 &lt;code&gt;hantong&lt;&#x2F;code&gt; 分配 UID 和 GID 范围:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; usermod&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;add-subuids&lt;&#x2F;span&gt; 100000-165535&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;add-subgids&lt;&#x2F;span&gt; 100000-165535 hantong&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;请注意, 每个用户的子 UID (GID) 范围不应重叠, 否则可能导致权限冲突和安全问题.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;需要解除 Linux 对非 root 用户绑定 1024 以下端口的限制.&lt;&#x2F;p&gt;
&lt;p&gt;如果需要绑定如 80 &#x2F; 443 端口的话, 可以通过以下命令解除该限制:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; sysctl&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;w&lt;&#x2F;span&gt; net.ipv4.ip_unprivileged_port_start=0&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;用户配置文件位置和生效顺序问题.&lt;&#x2F;p&gt;
&lt;p&gt;以下三个目录保存了 Podman 的配置文件:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&#x2F;usr&#x2F;share&#x2F;containers&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;etc&#x2F;containers&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;${XDG_CONFIG_HOME}&#x2F;containers&lt;&#x2F;code&gt; (默认 &lt;code&gt;~&#x2F;.config&#x2F;containers&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;包含以下配置文件:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;containers.conf&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;按前述顺序, 后者配置项覆盖前者.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;storage.conf&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在 Rootless Podman 中, &lt;code&gt;&#x2F;etc&#x2F;containers&#x2F;storage.conf&lt;&#x2F;code&gt; 中的某些字段将被忽略:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ini&quot; class=&quot;language-ini z-code&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;graphroot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt; &lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;container&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;storage graph dir (default&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;&#x2F;var&#x2F;lib&#x2F;containers&#x2F;storage&amp;quot;&lt;&#x2F;span&gt;)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt; &lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;Default&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;directory to store all writable content created by container storage programs&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;runroot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt; &lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;container&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;storage run dir (default&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;&#x2F;run&#x2F;containers&#x2F;storage&amp;quot;&lt;&#x2F;span&gt;)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt; &lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;Default&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;directory to store all temporary writable content created by container storage programs&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在 Rootless Podman 中, 这些字段默认为:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ini&quot; class=&quot;language-ini z-code&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;graphroot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;${XDG_DATA_HOME}&#x2F;containers&#x2F;storage&amp;quot;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;runroot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-genconfig&quot;&gt;&amp;quot;${XDG_RUNTIME_DIR}&#x2F;containers&amp;quot;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;XDG_DATA_HOME&lt;&#x2F;code&gt; 默认为 &lt;code&gt;~&#x2F;.local&#x2F;share&lt;&#x2F;code&gt;, &lt;code&gt;XDG_RUNTIME_DIR&lt;&#x2F;code&gt; 默认为 &lt;code&gt;&#x2F;run&#x2F;user&#x2F;&amp;lt;uid&amp;gt;&lt;&#x2F;code&gt; (systemd).&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;registries.conf&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;按前述顺序读取.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;授权文件&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;podman login&lt;&#x2F;code&gt; 和 &lt;code&gt;podman logout&lt;&#x2F;code&gt; 命令使用的默认授权文件是 &lt;code&gt;${XDG_RUNTIME_DIR}&#x2F;containers&#x2F;auth.json&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;命名空间隔离带来的问题.&lt;&#x2F;p&gt;
&lt;p&gt;Rootless Podman 在执行用户命名空间隔离时提供了以下模式, 需要我们了解:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;host&lt;&#x2F;code&gt; (默认): 映射当前用户的 UID (GID) 到容器内的 “root” (UID=0, GID=0), 其余 UID (GID) 按照前述映射规则依次映射为宿主机高位 UID (GID).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run -it --rm --userns=host --name=&amp;quot;test&amp;quot; --replace debian:trixie-slim bash
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@0df58777f431:&#x2F;# id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=0(root) gid=0(root) groups=0(root)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@0df58777f431:&#x2F;# sleep 3000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;^C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@0df58777f431:&#x2F;# useradd test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@0df58777f431:&#x2F;# su test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=1000(test) gid=1000(test) groups=1000(test)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ sleep 3000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;^C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@0df58777f431:&#x2F;# exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;exit
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 以容器内 root 用户 (UID=0) 运行 sleep 时
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ps -ax -o pid,user,group,uid,gid,args | grep &amp;quot;sleep 3000&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;20953 hantong  hantong   1000  1000 sleep 3000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 以容器内 test 用户 (UID=1000) 运行 sleep 时
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ps -ax -o pid,user,group,uid,gid,args | grep &amp;quot;sleep 3000&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;21384 100999   100999   100999 100999 sleep 3000
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;keep-id&lt;&#x2F;code&gt;: 类似 &lt;code&gt;host&lt;&#x2F;code&gt;, 但是映射当前用户的 UID (GID) 到容器内的相同 UID (GID). 同时, 除非手动指定用户, 忽视镜像的 &lt;code&gt;USER&lt;&#x2F;code&gt; 配置, 在当前用户的 UID 下运行 init 进程.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run -it --rm --userns=keep-id --name=&amp;quot;test&amp;quot; --replace debian:trixie-slim bash
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hantong@cea54e0fd3fb:&#x2F;$ id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=1000(hantong) gid=1000(hantong) groups=1000(hantong)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hantong@cea54e0fd3fb:&#x2F;$ exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=1000(hantong) gid=1000(hantong) groups=1000(hantong),27(sudo)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;“nomap”: 不映射宿主机用户, 容器内的所有 UID (GID) 按照前述映射规则依次映射为宿主机高位 UID (GID).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run -it --rm --userns=nomap --name=&amp;quot;test&amp;quot; --replace debian:trixie-slim bash
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@f3794e591a2c:&#x2F;# id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=0(root) gid=0(root) groups=0(root)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@f3794e591a2c:&#x2F;# sleep 3000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;^C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@f3794e591a2c:&#x2F;# useradd test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@f3794e591a2c:&#x2F;# su test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ sleep 3000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;^C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;root@f3794e591a2c:&#x2F;# exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;exit
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 以容器内 root 用户 (UID=0) 运行 sleep 时
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ps -ax -o pid,user,group,uid,gid,args | grep &amp;quot;sleep 3000&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;28326 100000   100000   100000 100000 sleep 3000
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 以容器内 test 用户 (UID=1000) 运行 sleep 时
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ps -ax -o pid,user,group,uid,gid,args | grep &amp;quot;sleep 3000&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;28563 101000   101000   101000 101000 sleep 3000
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;很多时候, 我们需要将宿主机的某个目录挂载到容器内, 使用 Docker 外加关掉 (或者说根本没开启) SELinux 自然可以靠 “root” 的力量忽视 (&lt;del&gt;暴力解决&lt;&#x2F;del&gt;) 权限问题, 但 Rootless Podman 就不行了.&lt;&#x2F;p&gt;
&lt;p&gt;两种情况:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;镜像没有配置 &lt;code&gt;USER&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;此时, 默认以容器内的 “root” (UID=0, GID=0) 运行. 容器内的 “root” 在 Rootless Podman 默认的用户命名空间模式 (“–userns=host”) 下如前所述被映射到宿主机下运行容器的当前用户, 文件系统权限表现和当前用户&lt;strong&gt;基本&lt;&#x2F;strong&gt;一致.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;当镜像遵循最佳实践配置了 (rootless) &lt;code&gt;USER=UID[:GID]&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;此时, 容器内进程以容器内的该 UID (GID) 运行, 而容器内 UID (GID) 在 Rootless Podman 默认的用户命名空间模式 (“–userns=host”) 下如前所述被映射到宿主机下的高位 UID (GID), 此时文件系统权限表现和宿主机上的 “Others” 一样了.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;实例:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run -d --restart=unless-stopped -v &#x2F;home&#x2F;hantong&#x2F;.config&#x2F;openlist:&#x2F;opt&#x2F;openlist&#x2F;data -p 5244:5244 --name=&amp;quot;openlist&amp;quot; --replace openlistteam&#x2F;openlist:latest
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman logs openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Error: Current user does not have write and&#x2F;or execute permissions for the .&#x2F;data directory: &#x2F;opt&#x2F;openlist&#x2F;data
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;dockerfile&quot; class=&quot;language-dockerfile z-code&quot;&gt;&lt;code class=&quot;language-dockerfile&quot; data-lang=&quot;dockerfile&quot;&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;USER=openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;UID=1001
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;GID=1001
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;RUN addgroup -g ${GID} ${USER} &amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;    adduser -D -u ${UID} -G ${USER} ${USER} &amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;    mkdir -p &#x2F;opt&#x2F;openlist&#x2F;data
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;USER ${USER}
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;CMD [ &amp;quot;&#x2F;entrypoint.sh&amp;quot; ]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在这个案例中, “&#x2F;entrypoint.sh” 是以容器内的 UID=1001、GID=1001 运行的, 被映射到宿主机下的 UID=101000、GID=101000. 显然, 其无法访问权限为 600, 归属 UID=1000、GID=1000 的的宿主机目录 &lt;code&gt;&#x2F;home&#x2F;hantong&lt;&#x2F;code&gt;. 此时, 我们可以配置 Podman 的用户命名空间模式为 “keep-id”, 以使 init 进程 “以当前用户的 UID (GID) 运行”, 从而对 &lt;code&gt;&#x2F;home&#x2F;hantong&lt;&#x2F;code&gt; 目录具有访问权限, 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run -d --userns=keep-id --restart=unless-stopped -v &#x2F;home&#x2F;hantong&#x2F;.config&#x2F;openlist:&#x2F;opt&#x2F;openlist&#x2F;data -p 5244:5244 --name=&amp;quot;openlist&amp;quot; --replace openlistteam&#x2F;openlist:latest
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman logs openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;INFO[2026-02-08 07:14:43] reading config file: &#x2F;opt&#x2F;openlist&#x2F;data&#x2F;config.json 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;INFO[2026-02-08 07:14:43] load config from env with prefix:            
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;INFO[2026-02-08 07:14:43] max buffer limit: 1502MB                     
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;INFO[2026-02-08 07:14:43] mmap threshold: 4MB                          
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;INFO[2026-02-08 07:14:43] init logrus...                               
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;start HTTP server @ 0.0.0.0:5244
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ps -ax -o pid,user,group,uid,gid,comm | grep openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;31334 101000   101000   101000 101000 openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman exec -it openlist bash
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;695025dade25:&#x2F;opt&#x2F;openlist$ id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid=1001(openlist) gid=1001(openlist) groups=1001(openlist)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;695025dade25:&#x2F;opt&#x2F;openlist$ ps -o pid,user,group,comm | grep openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   1 openlist openlist openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  26 openlist openlist bash
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  28 openlist openlist ps
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  29 openlist openlist grep
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;695025dade25:&#x2F;opt&#x2F;openlist$ exit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;exit
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;当然, 此时非 init 进程还是以 USER 配置的 UID (GID) 运行的:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ls -aln &#x2F;home&#x2F;hantong&#x2F;.config&#x2F;openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;total 276
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxrwxrwx  4   1000   1000   4096 Jan 31 16:36 .
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxrwxr-x 14   1000   1000   4096 Jan 31 16:23 ..
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 101000 101000   2899 Feb  8 15:14 config.json
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 101000 101000   4096 Jan 31 16:28 data.db
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 101000 101000  32768 Feb  8 15:14 data.db-shm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 101000 101000 222512 Feb  8 15:14 data.db-wal
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxr--r--  2 101000 101000   4096 Jan 31 16:28 log
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxr-xr-x  2 101000 101000   4096 Jan 31 16:28 temp
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;为了避免编辑配置文件还得 &lt;code&gt;sudo&lt;&#x2F;code&gt; 的麻烦, 建议配置为 &lt;code&gt;--userns=keep-id:uid=${UID},gid=${GID}&lt;&#x2F;code&gt;, 其中 UID, GID 为构建镜像时所配置的用户的数字 ID (或者直接指定用户覆盖镜像设置), 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman run -d --userns=keep-id:uid=1001,gid=1001 --restart=unless-stopped -v &#x2F;home&#x2F;hantong&#x2F;.config&#x2F;openlist:&#x2F;opt&#x2F;openlist&#x2F;data -p 5244:5244 --name=&amp;quot;openlist&amp;quot; --replace openlistteam&#x2F;openlist:latest
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;4fd47db9916432a8d4631b99c6a6fedd9a9c94c8ca3580e40f0f6bde41b83102
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ls -aln &#x2F;home&#x2F;hantong&#x2F;.config&#x2F;openlist
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;total 260
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxrwxr-x  4 1000 1000   4096 Feb  8 16:13 .
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxrwxr-x 14 1000 1000   4096 Feb  8 16:13 ..
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 1000 1000   2842 Feb  8 16:13 config.json
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 1000 1000   4096 Feb  8 16:13 data.db
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 1000 1000  32768 Feb  8 16:13 data.db-shm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-rw-r--r--  1 1000 1000 206032 Feb  8 16:13 data.db-wal
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxr--r--  2 1000 1000   4096 Feb  8 16:13 log
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;drwxr-xr-x  2 1000 1000   4096 Feb  8 16:13 temp
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这个时候, 容器内的 init 进程和非 init 进程在宿主机上均(表现为)以当前用户 “降权” 运行, 文件系统权限表现和当前用户基本一致.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Capabilities&lt;&#x2F;p&gt;
&lt;p&gt;关于 Capabilities, 参考 &lt;a href=&quot;https:&#x2F;&#x2F;man7.org&#x2F;linux&#x2F;man-pages&#x2F;man7&#x2F;capabilities.7.html&quot;&gt;https:&#x2F;&#x2F;man7.org&#x2F;linux&#x2F;man-pages&#x2F;man7&#x2F;capabilities.7.html&lt;&#x2F;a&gt;, 暂且理解为对权限控制的细化. 无论是 Docker 还是 Podman, 都限制了容器内进程的 Capabilities, 即便是 “root”.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rhatdan&#x2F;common&#x2F;blob&#x2F;ed2840c80e47bdadaff2c7d827111d401dff282d&#x2F;pkg&#x2F;config&#x2F;containers.conf#L48-L60&quot;&gt;Podman 的默认 Capabilities&lt;&#x2F;a&gt; 和 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;moby&#x2F;moby&#x2F;blob&#x2F;docker-29.x&#x2F;daemon&#x2F;pkg&#x2F;oci&#x2F;caps&#x2F;defaults.go&quot;&gt;Docker 的默认 Capabilities&lt;&#x2F;a&gt; 是不同的:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;Capability&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Podman&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Docker&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;CHOWN&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;DAC_OVERRIDE&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;FSETID&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;FOWNER&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;MKNOD&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;❌&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;NET_RAW&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;❌&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;SETGID&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;SETUID&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;SETFCAP&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;SETPCAP&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;NET_BIND_SERVICE&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;SYS_CHROOT&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;KILL&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;AUDIT_WRITE&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;❌&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;✔️&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;在容器化现有应用程序时时, 需要注意两者的差异; 对终端用户来说没有什么直接影响, 但建议启动容器时配置 &lt;code&gt;--security-opt=no-new-privileges&lt;&#x2F;code&gt; 以禁止容器进程获得额外权限, 或者直接禁用所有 Capabilities (&lt;code&gt;--cap-drop=all&lt;&#x2F;code&gt;) 再按需添加 (&lt;code&gt;--cap-add=CAP_NAME&lt;&#x2F;code&gt;), 防止类似 &lt;code&gt;CAP_BPF&lt;&#x2F;code&gt; 这些后面才引入的高危 Capabilities 被意外添加.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;bian-xie-rootless-you-hao-de-dockerfile&quot;&gt;编写 Rootless 友好的 Dockerfile&lt;&#x2F;h2&gt;
&lt;p&gt;总体和编写普通 Dockerfile 没什么区别, 但需要注意以下几点:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;指定 rootless &lt;code&gt;USER&lt;&#x2F;code&gt;, 需要是数字 ID.&lt;&#x2F;p&gt;
&lt;p&gt;推荐: &lt;code&gt;USER=65532:65532&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;尽量使用 Distroless 镜像作为基础镜像.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;如果应用程序是完全静态编译的, 直接使用 &lt;code&gt;scratch&lt;&#x2F;code&gt; (适合现代应用程序, 没有奇奇怪怪的依赖) 或 &lt;code&gt;gcr.io&#x2F;distroless&#x2F;static&lt;&#x2F;code&gt; (适合传统应用程序, 仍然依赖系统 CA 之类的奇怪玩意).&lt;&#x2F;p&gt;
&lt;p&gt;不同语言编写的项目的静态编译方法参考:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;对于 C &#x2F; C++, 具体项目具体分析.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;对于 Go, 可以配置 &lt;code&gt;CGO_ENABLED=0&lt;&#x2F;code&gt;, 如果项目以及项目的依赖没有依赖 CGO 的话.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;对于 Rust, 可以配置 target &lt;code&gt;x86_64-unknown-linux-musl&lt;&#x2F;code&gt; (amd64) 或 &lt;code&gt;aarch64-unknown-linux-musl&lt;&#x2F;code&gt; (arm64) 目标平台进行静态编译.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; rustup target add x86_64-unknown-linux-musl
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cd &#x2F;tmp
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cargo new test-static-build
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    Creating binary (application) `test-static-build` package
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;note: see more `Cargo.toml` keys and their definitions at https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;manifest.html
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cd test-static-build
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cargo build --release --target x86_64-unknown-linux-musl
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  Compiling test-static-build v0.1.0 (&#x2F;tmp&#x2F;test-static-build)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    Finished `release` profile [optimized] target(s) in 1.22s
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; ldd &#x2F;data&#x2F;Compile&#x2F;cargo-target-dir&#x2F;x86_64-unknown-linux-musl&#x2F;release&#x2F;test-static-build
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        statically linked
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;一般来说, 能编译为 &lt;code&gt;x86_64-unknown-linux-gnu&lt;&#x2F;code&gt; 就能编译为 &lt;code&gt;x86_64-unknown-linux-musl&lt;&#x2F;code&gt;, 除非依赖到某些奇奇怪怪的 C 库.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;…&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;如果应用程序依赖于 glibc, 可以使用 &lt;code&gt;gcr.io&#x2F;distroless&#x2F;base-nossl&lt;&#x2F;code&gt;; 如果应用程序还依赖于 OpenSSL (v3.X), 可以使用 &lt;code&gt;gcr.io&#x2F;distroless&#x2F;base&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;一些问题:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;尝试运行容器时, 报告 “error while loading shared libraries: XXX: cannot open shared object file: No such file or directory” 错误.&lt;&#x2F;p&gt;
&lt;p&gt;此即缺失依赖库问题.&lt;&#x2F;p&gt;
&lt;p&gt;这种情况下, 使用 ldd 查看依赖库, 再参考相关库的安装方法直接从完整镜像复制文件即可, 如笔者在打包 Resilio Sync 时的做法:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dockerfile&quot; class=&quot;language-dockerfile z-code&quot;&gt;&lt;code class=&quot;language-dockerfile&quot; data-lang=&quot;dockerfile&quot;&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;DEBIAN_BASE_VERSION=trixie-slim
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;DEBIAN_BASE_HASH=f6e2cfac5cf956ea044b4bd75e6397b4372ad88fe00908045e9a0d21712ae3ba
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; --platform=linux&#x2F;${TARGETARCH} debian:${DEBIAN_BASE_VERSION}@sha256:${DEBIAN_BASE_HASH} AS downloader
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;RUN &lt;&#x2F;span&gt;set -e &amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;ARCH=$(dpkg --print-architecture) \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;case &lt;span class=&quot;z-string z-quoted z-double z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;${ARCH}&lt;span class=&quot;z-punctuation z-definition z-string z-end z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; in \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;amd64) LIBARCH=&lt;span class=&quot;z-string z-quoted z-double z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;x86_64-linux-gnu&lt;span class=&quot;z-punctuation z-definition z-string z-end z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; ;; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;arm64) LIBARCH=&lt;span class=&quot;z-string z-quoted z-double z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;aarch64-linux-gnu&lt;span class=&quot;z-punctuation z-definition z-string z-end z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; ;; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;*) echo &lt;span class=&quot;z-string z-quoted z-double z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Unsupported architecture: ${ARCH}&lt;span class=&quot;z-punctuation z-definition z-string z-end z-dockerfile&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &amp;amp;&amp;amp; exit 1 ;; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;esac \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;cp &#x2F;lib&#x2F;${LIBARCH}&#x2F;libcrypt.so.1 &#x2F;tmp&#x2F;resilio-sync&#x2F;lib&#x2F;libcrypt.so.1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; scratch
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ENV &lt;&#x2F;span&gt;LD_LIBRARY_PATH=&#x2F;opt&#x2F;resilio-sync&#x2F;lib
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(笔者评: Resilio Sync 怎么还在用 OpenSSL 1.1.X 啊, 硬要用就不能静态编译进程序里面么… C &#x2F; C++ 的依赖灾难在这就体现了…)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;OpenSSL CA 问题.&lt;&#x2F;p&gt;
&lt;p&gt;但凡最终应用程序报告 SSL 相关错误, 大抵是这里所述的问题.&lt;&#x2F;p&gt;
&lt;p&gt;这种情况下, 可以在构建阶段安装 ca-certificates 包, 将 CA 文件复制到最终镜像中, 并正确配置环境变量, 如笔者在打包 qBittorrent 时的做法:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dockerfile&quot; class=&quot;language-dockerfile z-code&quot;&gt;&lt;code class=&quot;language-dockerfile&quot; data-lang=&quot;dockerfile&quot;&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;ALPINE_BASE_VERSION=3.23.3
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ARG &lt;&#x2F;span&gt;ALPINE_BASE_HASH=25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; alpine:&lt;span class=&quot;z-entity z-name z-enum z-tag-digest&quot;&gt;${ALPINE_BASE_VERSION}@sha256:${ALPINE_BASE_HASH}&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;AS&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-stage-name&quot;&gt;downloader&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;RUN &lt;&#x2F;span&gt;set -e &amp;amp;&amp;amp; \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;    apk -U upgrade &amp;amp;&amp;amp; apk add --no-cache \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;    ca-certificates=20251003-r0 \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;FROM scratch
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;downloader&lt;&#x2F;span&gt; &#x2F;etc&#x2F;ssl&#x2F;certs&#x2F;ca-certificates.crt &#x2F;etc&#x2F;ssl&#x2F;certs&#x2F;ca-certificates.crt
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;ENV &lt;&#x2F;span&gt;SSL_CERT_DIR=&#x2F;etc&#x2F;ssl&#x2F;certs \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;    SSL_CERT_FILE=&#x2F;etc&#x2F;ssl&#x2F;certs&#x2F;ca-certificates.crt
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(笔者评: 还是喜欢 Rustls + WebPKI, OpenSSL 这种岁月痕迹过于重的库, 唉…)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;shi-yong-systemd-guan-li-rootless-podman-rong-qi&quot;&gt;使用 systemd 管理 Rootless Podman 容器&lt;&#x2F;h2&gt;
&lt;p&gt;参考文档: &lt;a href=&quot;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-systemd.unit.5.html&quot;&gt;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-systemd.unit.5.html&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Podman 不像 Docker 那样有守护程序, 需要以其他方式管理容器的自动启动等. Podman 提供了 Quadlet, 以通过 systemd 来管理 Podman 容器.&lt;&#x2F;p&gt;
&lt;p&gt;下面是一个示例 Quadlet 文件:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;systemd&quot; class=&quot;language-systemd z-code&quot;&gt;&lt;code class=&quot;language-systemd&quot; data-lang=&quot;systemd&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Unit]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Description=FreeNGINX Distroless Container Service
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;RequiresMountsFor=%t&#x2F;containers
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Container]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Specify the name of the container.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ContainerName=freenginx
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Specify the container image to use.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Image=ghcr.io&#x2F;han-rs&#x2F;container-ci-freenginx:latest
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Automatically pull the latest image.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AutoUpdate=registry
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Run the container in user namespace mode with the current user&amp;#39;s ID.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# *
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Don&amp;#39;t modify this field unless you know what you&amp;#39;re doing.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;UserNS=keep-id:uid=65532,gid=65532
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Use the host&amp;#39;s network stack for better performance.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Network=host
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Publish ports from the container to the host.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# *
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * If Network=host is used, these options will be ignored.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * The format is [host_ip:]host_port:container_port[&#x2F;protocol].
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Please set these according to what you configured.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# PublishPort=80:80
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# PublishPort=443:443
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# PublishPort=443:443&#x2F;udp
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Persists configuration files.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Volume=%h&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;conf:&#x2F;opt&#x2F;freenginx&#x2F;conf
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Persists logs.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Volume=%h&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;logs:&#x2F;opt&#x2F;freenginx&#x2F;logs
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Persists SSL stuffs.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Volume=%h&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;ssl:&#x2F;opt&#x2F;freenginx&#x2F;ssl
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Example of mounting a named volume.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# Volume=cloudflared.volume:&#x2F;opt&#x2F;cloudflared&#x2F;shared-volume
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Example of mounting a host directory.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# Volume=&#x2F;host&#x2F;dir:&#x2F;container&#x2F;dir
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Service]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Always restart the container if it stops.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Restart=always
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Give the container more time to start.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;TimeoutStartSec=900
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Generate configuration extracting real IP from HTTP headers added by Cloudflare.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;#
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * If you gate your servers behind Cloudflare, uncomment the following line.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ExecStartPre=%h&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;scripts&#x2F;cloudflare-real-ip-helper.sh
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * For Podman v5.4 or lower, we have to manually specify reload command.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ExecReload=&#x2F;usr&#x2F;bin&#x2F;podman exec freenginx &#x2F;opt&#x2F;freenginx&#x2F;sbin&#x2F;nginx -s reload
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Install]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# * Enable the service to start at boot.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;WantedBy=default.target
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;基本上, Podman Quadlet systemd 文件可以由 &lt;code&gt;podman run&lt;&#x2F;code&gt; 命令逐参数转换而来, 官方文档也给出了&lt;a href=&quot;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-systemd.unit.5.html#container-units-container&quot;&gt;对照表&lt;&#x2F;a&gt;, 编写并不困难.&lt;&#x2F;p&gt;
&lt;p&gt;上面的例子基本上相当于:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;podman&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; run&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;d&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;restart&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-option z-shell&quot;&gt;=&lt;&#x2F;span&gt;unless-stopped &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    --&lt;&#x2F;span&gt;restart&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-option z-shell&quot;&gt;=&lt;&#x2F;span&gt;always &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    --&lt;&#x2F;span&gt;userns&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-option z-shell&quot;&gt;=&lt;&#x2F;span&gt;keep-id:uid=65532,gid=65532 &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    --&lt;&#x2F;span&gt;network&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-option z-shell&quot;&gt;=&lt;&#x2F;span&gt;host &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-tilde&quot;&gt;&lt;span class=&quot;z-variable z-language z-tilde z-shell&quot;&gt;~&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;conf:&#x2F;opt&#x2F;freenginx&#x2F;conf &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-tilde&quot;&gt;&lt;span class=&quot;z-variable z-language z-tilde z-shell&quot;&gt;~&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;logs:&#x2F;opt&#x2F;freenginx&#x2F;logs &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-tilde&quot;&gt;&lt;span class=&quot;z-variable z-language z-tilde z-shell&quot;&gt;~&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;ssl:&#x2F;opt&#x2F;freenginx&#x2F;ssl &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    --&lt;&#x2F;span&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-option z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;freenginx&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;    --&lt;&#x2F;span&gt;replace&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;    ghcr.io&#x2F;han-rs&#x2F;container-ci-freenginx:latest&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;配置完成后, 可以通过 &lt;code&gt;systemctl --user ${CMD} ${CONTAINER_NAME}.service&lt;&#x2F;code&gt; 来管理该容器, 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; systemctl --user status freenginx.service
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;● freenginx.service - FreeNGINX Distroless Container Service
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     Loaded: loaded (&#x2F;home&#x2F;hantong&#x2F;.config&#x2F;containers&#x2F;systemd&#x2F;freenginx.container; generated)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     Active: active (running) since Sat 2026-02-07 19:29:12 CST; 21h ago
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt; Invocation: eda008ef44dc4e79b198a0c69a7ca32f
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    Process: 25667 ExecStartPre=&#x2F;home&#x2F;hantong&#x2F;.local&#x2F;share&#x2F;freenginx&#x2F;scripts&#x2F;cloudflare-real-ip-helper.sh (code=exited, status=0&#x2F;SUCCESS)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   Main PID: 25696 (conmon)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Tasks: 134 (limit: 4915)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     Memory: 467M (peak: 3.6G)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        CPU: 16min 28.154s
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;值得指出, 依托于 systemd, 像什么 &lt;code&gt;ExecStartPre&lt;&#x2F;code&gt; 之类的钩子也可以使用了, 不再依赖容器本身去实现这些功能, 这允许容器打包者大幅度简化镜像:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; podman images
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;REPOSITORY                                TAG         IMAGE ID      CREATED       SIZE
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ghcr.io&#x2F;han-rs&#x2F;container-ci-freenginx     latest      f583dc3c0362  28 hours ago  3.95 MB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ghcr.io&#x2F;han-rs&#x2F;container-ci-resilio-sync  latest      af6cae8d5f91  47 hours ago  38.3 MB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ghcr.io&#x2F;han-rs&#x2F;container-ci-qbittorrent   latest      2bf49e5c41e5  2 days ago    45.5 MB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ghcr.io&#x2F;han-rs&#x2F;container-ci-cloudflared   latest      a4bbf0388c6b  2 days ago    28 MB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;当然, 还有些注意事项和疑难杂症:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;自动启动&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;需要执行 &lt;code&gt;sudo loginctl enable-linger $USER&lt;&#x2F;code&gt;, 使得 rootless 用户的 systemd 服务能够在用户未登录时也能自动启动并驻留后台.&lt;&#x2F;li&gt;
&lt;li&gt;不需要执行 &lt;code&gt;systemctl --user enable ***&lt;&#x2F;code&gt;, Podman 创建的服务被 systemd 视为 “瞬态” (transient) 的.&lt;&#x2F;li&gt;
&lt;li&gt;需要在 Quadlet 配置文件 &lt;code&gt;[Install]&lt;&#x2F;code&gt; 项添加 &lt;code&gt;WantedBy=default.target&lt;&#x2F;code&gt; 使容器自动启动. (&lt;strong&gt;存疑&lt;&#x2F;strong&gt;: 是 &lt;code&gt;default.target&lt;&#x2F;code&gt; 还是 &lt;code&gt;multi-user.target&lt;&#x2F;code&gt;, 还是两者均使用? 笔者个人部署时仅使用前者.)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;笔者本人在部署过程中遇到 &lt;code&gt;network-online.target&lt;&#x2F;code&gt; 始终没有 active (&lt;code&gt;systemctl is-active network-online.target&lt;&#x2F;code&gt; 提示 inactive) 的问题, 导致:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;容器自动启动会延滞于服务器启动完成很久之后;&lt;&#x2F;li&gt;
&lt;li&gt;启动或重启服务等待很久 (默认 90s 超时), 但能成功;&lt;&#x2F;li&gt;
&lt;li&gt;…&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;del&gt;根本原因暂未清楚, 或许是 &lt;code&gt;netplan&lt;&#x2F;code&gt; + &lt;code&gt;systemd-networkd&lt;&#x2F;code&gt; 的问题? 等待进一步研究.&lt;&#x2F;del&gt;&lt;&#x2F;p&gt;
&lt;p&gt;参考 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman&#x2F;issues&#x2F;24796#issuecomment-2527822425&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman&#x2F;issues&#x2F;24796#issuecomment-2527822425&lt;&#x2F;a&gt;, 给出暂时性的解决方案:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; class=&quot;language-shell z-code&quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sudo sh -c &amp;quot;cat &amp;lt;&amp;lt;EOF &amp;gt; &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;podman-network-online-dummy.service
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Unit]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Description=This service simply activates network-online.target
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;After=network-online.target
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Wants=network-online.target
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Service]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ExecStart=&#x2F;usr&#x2F;bin&#x2F;echo Activating network-online.target
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Install]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;WantedBy=multi-user.target
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;EOF&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sudo systemctl daemon-reload
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sudo systemctl enable --now podman-network-online-dummy.service
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# Check status
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# sudo systemctl status podman-network-online-dummy.service
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;其他需要注意的是, 从 Podman v5 开始, &lt;code&gt;podman generate systemd&lt;&#x2F;code&gt; 已经被弃用, 不再推荐使用.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jie-yu&quot;&gt;结语&lt;&#x2F;h2&gt;
&lt;p&gt;总的来说, 如果不想折腾, 直接使用 rootful Docker 确实方便, root 解决一切权限问题, 但面对 &lt;a href=&quot;https:&#x2F;&#x2F;linux.do&#x2F;t&#x2F;topic&#x2F;1548846&quot;&gt;“飞牛变肥牛”&lt;&#x2F;a&gt; 之类由于厂商漠视安全、产品存在大量安全漏洞不积极修复放任被在野攻击者利用的教训, 对于闭源应用或未得到广泛安全审计的开源应用, 还是建议使用 Rootless Podman 容器化隔离, 并配置 CPU 和内存用量限制; 即便是开源应用, 也建议使用 Rootless Podman 来隔离部署, 以防止由于配置错误或未知漏洞导致的安全问题.&lt;&#x2F;p&gt;
&lt;p&gt;本文是笔者近日在个人存储服务器使用 Rootless Podman 隔离部署应用程序, 尤其是闭源应用程序的过程中总结的经验 (踩坑体验), 分享在此供读者参考, 相关成果开源在以下仓库中:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;han-rs&#x2F;container-ci-freenginx&quot;&gt;container-ci-freenginx&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;han-rs&#x2F;container-ci-qbittorrent&quot;&gt;container-ci-qbittorrent&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;han-rs&#x2F;container-ci-cloudflared&quot;&gt;container-ci-cloudflared&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;han-rs&#x2F;container-ci-resilio-sync&quot;&gt;container-ci-resilio-sync&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;由于笔者水平和精力有限, 文章中难免出现纰漏, 欢迎评论区批评指正、交流分享.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;can-kao-wen-xian&quot;&gt;参考文献&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;官方文档, 包括但不限于:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;官方指引: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman&#x2F;blob&#x2F;main&#x2F;docs&#x2F;tutorials&#x2F;rootless_tutorial.md&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman&#x2F;blob&#x2F;main&#x2F;docs&#x2F;tutorials&#x2F;rootless_tutorial.md&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;官方常见问题总结: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman&#x2F;blob&#x2F;main&#x2F;rootless.md&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;containers&#x2F;podman&#x2F;blob&#x2F;main&#x2F;rootless.md&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;用户命名空间模式: &lt;a href=&quot;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-run.1.html#userns-mode&quot;&gt;https:&#x2F;&#x2F;docs.podman.io&#x2F;en&#x2F;latest&#x2F;markdown&#x2F;podman-run.1.html#userns-mode&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;opensource.com&#x2F;article&#x2F;19&#x2F;2&#x2F;how-does-rootless-podman-work&quot;&gt;How does rootless Podman work?&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.lixueduan.com&#x2F;posts&#x2F;docker&#x2F;05-namespace&#x2F;&quot;&gt;探索 Linux Namespace: Docker 隔离的神奇背后&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
        <summary type="html">本文是笔者近日在个人存储服务器使用 Rootless Podman 隔离部署应用程序, 尤其是闭源应用程序的过程中总结的经验 (踩坑体验), 分享在此供读者参考.</summary>
        </entry><entry xml:lang="zh">
        <title>了解和使用 GPG</title>
        <published>2026-02-01T00:00:00+00:00</published>
        <updated>2026-02-01T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/26-02-01-gpg/" type="text/html"/>
        <id>https://i.han.rs/blog/26-02-01-gpg/</id>
        
            <content type="html">&lt;h2 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h2&gt;
&lt;p&gt;本文出现的所有命令的示例均在 Debian 13 上操作 GPG (&lt;a href=&quot;https:&#x2F;&#x2F;www.gnupg.org&#x2F;&quot;&gt;GnuPG&lt;&#x2F;a&gt;) 2.4.7 版本完成. 本文参考、借鉴、总结了大量资料, 在文末给出相关链接, 在此先表达感谢.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ji-ben-gai-nian-yu-shu-yu&quot;&gt;基本概念与术语&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;mi-ma-xue-ji-chu&quot;&gt;密码学基础&lt;&#x2F;h3&gt;
&lt;p&gt;建议阅读 &lt;a href=&quot;..&#x2F;25-09-17-tls-series-cryptography-5&quot;&gt;『从零开始了解 TLS 1.3 系列笔记 (六) —— 实用密码学之「对称加密、AEAD 与非对称密码学」』&lt;&#x2F;a&gt; 中的非对称密码学 (公钥密码学) 部分, &lt;a href=&quot;..&#x2F;25-09-16-tls-series-cryptography-4&quot;&gt;『从零开始了解 TLS 1.3 系列笔记 (五) —— 实用密码学之「密钥交换, Diffie–Hellman 与后量子加密」』&lt;&#x2F;a&gt; 中的后量子密码学部分, 此处不再赘述.&lt;&#x2F;p&gt;
&lt;p&gt;应当了解:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;非对称密码学 (公钥密码学) 的基本概念;&lt;&#x2F;li&gt;
&lt;li&gt;RSA 和 ECC 公钥密码系统的基本概念, ECC 公钥密码系统中如 Curve25519 和 X25519 以及 Ed25519 的关系;&lt;&#x2F;li&gt;
&lt;li&gt;RSA 和 ECC 公钥密码系统的作用, 如数字签名等;&lt;&#x2F;li&gt;
&lt;li&gt;常见的 EdDSA, 如基于 Curve25519 的 Ed25519, 基于 Curve448 的 Ed448;&lt;&#x2F;li&gt;
&lt;li&gt;常见的 ECDSA, 如 NIST P-256 (secp256r1);&lt;&#x2F;li&gt;
&lt;li&gt;ECDSA 和 EdDSA 的区别.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;gpg&quot;&gt;GPG&lt;&#x2F;h3&gt;
&lt;p&gt;GPG (&lt;a href=&quot;https:&#x2F;&#x2F;www.gnupg.org&#x2F;&quot;&gt;GnuPG&lt;&#x2F;a&gt;, GNU Privacy Guard) 是一款自由和开源软件 (FOSS), 通过对各类密码学原语的封装, 遵照 &lt;a href=&quot;https:&#x2F;&#x2F;www.openpgp.org&#x2F;&quot;&gt;OpenPGP&lt;&#x2F;a&gt; 标准, 实现了加密 (&lt;strong&gt;E&lt;&#x2F;strong&gt;crypt)、签名 (&lt;strong&gt;S&lt;&#x2F;strong&gt;ign) 和认证 (&lt;strong&gt;A&lt;&#x2F;strong&gt;uthenticating) 功能, 以及密钥管理功能 (&lt;strong&gt;C&lt;&#x2F;strong&gt;ertify). PGP 早期为一具备类似功能的闭源专有程序&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;#fn-1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, 虽然如今称呼 PGP 等价于称呼 OpenPGP 了.&lt;&#x2F;p&gt;
&lt;p&gt;GPG 生成并管理以下几种公私钥对:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;主密钥对&lt;&#x2F;p&gt;
&lt;p&gt;包含主私钥 (sec) 和主公钥 (pub).&lt;&#x2F;p&gt;
&lt;p&gt;虽然可以让主密钥对提供上述全部功能, 但一般不推荐那么做. 相反, 我们推荐仅授予主密钥对以密钥管理功能, 完成密钥准备工作后即离线保存主私钥, 仅在需要时加载, 包括但不限于:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;添加或吊销子密钥;&lt;&#x2F;li&gt;
&lt;li&gt;添加、更改或吊销密钥关联身份 (UID);&lt;&#x2F;li&gt;
&lt;li&gt;添加或更改本身或其他子密钥的到期时间;&lt;&#x2F;li&gt;
&lt;li&gt;为了网络信任目的为其他密钥签名, 等.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;后文我们再继续介绍.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;子密钥对&lt;&#x2F;p&gt;
&lt;p&gt;包含子私钥 (ssb) 和子公钥 (sub), 按前述用途可细分为 ssb S &#x2F; A &#x2F; E, sub S &#x2F; A &#x2F; E.&lt;&#x2F;p&gt;
&lt;p&gt;我们推荐为不同用途分别生成子密钥对.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;以下称 “主&#x2F;子密钥” 时, 均指公钥和私钥的组合, 即密钥对.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;an-zhuang-gpg&quot;&gt;安装 GPG&lt;&#x2F;h2&gt;
&lt;p&gt;不再赘述, 现代 Linux 发行版一般默认安装, Windows 系统下可以安装 &lt;a href=&quot;https:&#x2F;&#x2F;www.openpgp.org&#x2F;software&#x2F;kleopatra&#x2F;&quot;&gt;Kleopatra&lt;&#x2F;a&gt;, 会捎带着安装命令行版本的 GPG. 下面的示例均使用命令行操作.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zhun-bei-gpg-mi-yao&quot;&gt;准备 GPG 密钥&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;sheng-cheng-zhu-mi-yao&quot;&gt;生成主密钥&lt;&#x2F;h3&gt;
&lt;p&gt;执行 &lt;code&gt;gpg --full-gen-key --expert&lt;&#x2F;code&gt; 进行交互式生成.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;xuan-ze-mi-yao-lei-xing&quot;&gt;选择密钥类型&lt;&#x2F;h4&gt;
&lt;p&gt;出于性能原因, 我们推荐选择 ECC 密钥类型, 除非需要兼容老设备等才选择 RSA. 虽然, ECC 和 RSA 一样并不具备后量子安全性, 但后量子安全的算法目前尚未成熟, OpenPGP 尚未跟进相关进展.&lt;&#x2F;p&gt;
&lt;p&gt;由于我们希望主密钥专用于管理子密钥, 即执行 Certify 的功能, 我们选择 “(11) ECC (set your own capabilities)”, 然后手动设置主密钥功能 (初始时自带 &lt;code&gt;Sign&lt;&#x2F;code&gt; 和 &lt;code&gt;Certify&lt;&#x2F;code&gt; 的功能, 输入 &lt;code&gt;S&lt;&#x2F;code&gt; 关闭签名功能即可, 然后按 &lt;code&gt;Q&lt;&#x2F;code&gt; 保存).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select what kind of key you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (1) RSA and RSA
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (2) DSA and Elgamal
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) DSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) RSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) DSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) RSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (9) ECC (sign and encrypt) *default*
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (10) ECC (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (11) ECC (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (13) Existing key
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (14) Existing key from card
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 11
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Possible actions for this ECC key: Sign Certify Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Current allowed actions: Sign Certify
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (S) Toggle the sign capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (A) Toggle the authenticate capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (Q) Finished
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Possible actions for this ECC key: Sign Certify Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Current allowed actions: Certify
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (S) Toggle the sign capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (A) Toggle the authenticate capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (Q) Finished
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? Q
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;xuan-ze-ecc-qu-xian&quot;&gt;选择 ECC 曲线&lt;&#x2F;h4&gt;
&lt;p&gt;出于兼容性原因, 我们选择 Curve25519 而不是 Curve448; 出于安全考虑&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-2-1&quot;&gt;&lt;a href=&quot;#fn-2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, 不推荐 NIST 系列曲线、Brainpool 系列曲线或 secp256k1.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select which elliptic curve you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (1) Curve 25519 *default*
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (2) Curve 448
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) NIST P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) NIST P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) NIST P-521
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) Brainpool P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) Brainpool P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) Brainpool P-512
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (9) secp256k1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;pei-zhi-you-xiao-qi&quot;&gt;配置有效期&lt;&#x2F;h4&gt;
&lt;p&gt;出于安全考虑, 不建议配置密钥永不过期. 此处我们设置为 4 年.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please specify how long the key should be valid.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         0 = key does not expire
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;  = key expires in n days
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;w = key expires in n weeks
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;m = key expires in n months
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;y = key expires in n years
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key is valid for? (0) 4y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key expires at Fri Feb  1 15:45:55 2030 CST
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Is this correct? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;pei-zhi-uid&quot;&gt;配置 UID&lt;&#x2F;h4&gt;
&lt;p&gt;用户 ID 用于将密钥和真人关联, 同一个主密钥下可以有多个 UID 适应多个身份.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;GnuPG needs to construct a user ID to identify your key.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Real name: Hantong Chen
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Email address: public-service@7rs.net
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Comment: 2026
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;You selected this USER-ID:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    &amp;quot;Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Change (N)ame, (C)omment, (E)mail or (O)kay&#x2F;(Q)uit? O
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;额外说一句, 大部分情况下别真填自己的真名上去; 此外邮箱至少应该能够收邮件, 后面会用到; 公钥会包含电子邮件地址, 注意隐私和垃圾邮件问题.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;pei-zhi-mi-ma-bao-hu&quot;&gt;配置密码保护&lt;&#x2F;h4&gt;
&lt;p&gt;输入密码, 按 Tab 移动到下一项, 再次输入密码, 按 Tab 移动到 &lt;code&gt;&amp;lt;OK&amp;gt;&lt;&#x2F;code&gt; 确认.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;┌─────────────────────────────────────────────────────────────┐
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ Please enter the passphrase to                              │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ protect your new key                                        │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│                                                             │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ Passphrases match.                                          │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│                                                             │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ Passphrase: _______________________________________________ │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│                                                             │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ Repeat: ___________________________________________________ │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ ┌─────────────────────────────────────────────────────────┐ │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ │                                                         │ │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│ └─────────────────────────────────────────────────────────┘ │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;│        &amp;lt;OK&amp;gt;                                   &amp;lt;Cancel&amp;gt;      │
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;└─────────────────────────────────────────────────────────────┘
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;注意不要超时, 输入完毕后等待完成即可:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;We need to generate a lot of random bytes. It is a good idea to perform
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;some other action (type on the keyboard, move the mouse, utilize the
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;disks) during the prime generation; this gives the random number
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;generator a better chance to gain enough entropy.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;We need to generate a lot of random bytes. It is a good idea to perform
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;some other action (type on the keyboard, move the mouse, utilize the
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;disks) during the prime generation; this gives the random number
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;generator a better chance to gain enough entropy.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;xiao-jie&quot;&gt;小结&lt;&#x2F;h4&gt;
&lt;p&gt;现在, 我们的主密钥就已经生成完毕了:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: revocation certificate stored as &amp;#39;&#x2F;home&#x2F;hantong&#x2F;.gnupg&#x2F;openpgp-revocs.d&#x2F;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.rev&amp;#39;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;public and secret key created and signed.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;pub   ed25519 2026-02-02 [C] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid                      Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;请务必牢记密码, 后面所有用到私钥的地方都会要求输入密码.&lt;&#x2F;p&gt;
&lt;p&gt;可以使用 &lt;code&gt;gpg --list-keys --keyid-format long&lt;&#x2F;code&gt; 和 &lt;code&gt;gpg --list-secret-keys --keyid-format long&lt;&#x2F;code&gt; 列出所有公钥和私钥:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --list-keys --keyid-format long
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 略去部分输出
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;pub   ed25519&#x2F;F95DD10B4BFF68A2 2026-02-02 [C] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid                 [ultimate] Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --list-secret-keys --keyid-format long
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 略去部分输出
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sec   ed25519&#x2F;F95DD10B4BFF68A2 2026-02-02 [C] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid                 [ultimate] Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可以看到:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;长 ID: &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;短 ID: &lt;code&gt;F95DD10B4BFF68A2&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;主密钥用途: “[C]”&lt;&#x2F;li&gt;
&lt;li&gt;主密钥创建日期: “2026-02-02”&lt;&#x2F;li&gt;
&lt;li&gt;主密钥有效期止: “2030-02-01”&lt;&#x2F;li&gt;
&lt;li&gt;UID: 信任状态 (完全信任, “ultimate”), Real Name (“Hantong Chen”), 备注 (“2026”) 和电子邮件 (“public-service#7rs.net”)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;sheng-cheng-zi-mi-yao&quot;&gt;生成子密钥&lt;&#x2F;h3&gt;
&lt;p&gt;PKI 体系有证书信任链的存在, GPG 的子密钥机制 (subkey) 承担了类似功能, 即仅当配置了信任主公钥, 才信任主密钥下的子密钥; 吊销主密钥则全部子密钥失去信任; 吊销单个子密钥不影响其他子密钥或主密钥.&lt;&#x2F;p&gt;
&lt;p&gt;我们建议并在前述步骤中配置主密钥仅承担签发的功能, 接下来需要添加子密钥分别承担签名&#x2F;认证&#x2F;加密的功能.&lt;&#x2F;p&gt;
&lt;p&gt;使用 &lt;code&gt;gpg --expert --edit-key &amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt; 进入编辑模式, 下面是示例操作过程:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --expert --edit-key 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg (GnuPG) 2.4.7; Copyright (C) 2024 g10 Code GmbH
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;This is free software: you are free to change and redistribute it.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;There is NO WARRANTY, to the extent permitted by law.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Secret key is available.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sec  ed25519&#x2F;F95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     trust: ultimate      validity: ultimate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[ultimate] (1). Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg&amp;gt; addkey
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select what kind of key you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) DSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) RSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) Elgamal (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) RSA (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) DSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) RSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (10) ECC (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (11) ECC (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (12) ECC (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (13) Existing key
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (14) Existing key from card
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 10
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select which elliptic curve you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (1) Curve 25519 *default*
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (2) Curve 448
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) NIST P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) NIST P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) NIST P-521
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) Brainpool P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) Brainpool P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) Brainpool P-512
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (9) secp256k1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please specify how long the key should be valid.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         0 = key does not expire
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;  = key expires in n days
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;w = key expires in n weeks
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;m = key expires in n months
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;y = key expires in n years
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key is valid for? (0) 4y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key expires at Fri Feb  1 16:22:25 2030 CST
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Is this correct? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Really create? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;We need to generate a lot of random bytes. It is a good idea to perform
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;some other action (type on the keyboard, move the mouse, utilize the
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;disks) during the prime generation; this gives the random number
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;generator a better chance to gain enough entropy.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sec  ed25519&#x2F;F95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     trust: ultimate      validity: ultimate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb  ed25519&#x2F;29774A85FD26125D
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[ultimate] (1). Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg&amp;gt; addkey
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select what kind of key you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) DSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) RSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) Elgamal (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) RSA (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) DSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) RSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (10) ECC (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (11) ECC (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (12) ECC (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (13) Existing key
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (14) Existing key from card
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 11
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Possible actions for this ECC key: Sign Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Current allowed actions: Sign
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (S) Toggle the sign capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (A) Toggle the authenticate capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (Q) Finished
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Possible actions for this ECC key: Sign Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Current allowed actions:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (S) Toggle the sign capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (A) Toggle the authenticate capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (Q) Finished
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? A
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Possible actions for this ECC key: Sign Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Current allowed actions: Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (S) Toggle the sign capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (A) Toggle the authenticate capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (Q) Finished
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? Q
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select which elliptic curve you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (1) Curve 25519 *default*
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (2) Curve 448
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) NIST P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) NIST P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) NIST P-521
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) Brainpool P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) Brainpool P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) Brainpool P-512
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (9) secp256k1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? `1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Invalid selection.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please specify how long the key should be valid.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         0 = key does not expire
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;  = key expires in n days
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;w = key expires in n weeks
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;m = key expires in n months
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;y = key expires in n years
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key is valid for? (0) 4y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key expires at Fri Feb  1 16:23:08 2030 CST
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Is this correct? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Really create? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;We need to generate a lot of random bytes. It is a good idea to perform
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;some other action (type on the keyboard, move the mouse, utilize the
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;disks) during the prime generation; this gives the random number
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;generator a better chance to gain enough entropy.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sec  ed25519&#x2F;F95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     trust: ultimate      validity: ultimate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb  ed25519&#x2F;29774A85FD26125D
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb  ed25519&#x2F;11E7B3A29E772EB3
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: A
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[ultimate] (1). Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg&amp;gt; addkey
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select what kind of key you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) DSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) RSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) Elgamal (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) RSA (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) DSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) RSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (10) ECC (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (11) ECC (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (12) ECC (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (13) Existing key
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (14) Existing key from card
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 12
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select which elliptic curve you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (1) Curve 25519 *default*
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (2) Curve 448
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) NIST P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) NIST P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) NIST P-521
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) Brainpool P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) Brainpool P-384
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) Brainpool P-512
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (9) secp256k1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection? 1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please specify how long the key should be valid.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;         0 = key does not expire
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;  = key expires in n days
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;w = key expires in n weeks
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;m = key expires in n months
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;lt;n&amp;gt;y = key expires in n years
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key is valid for? (0) 4y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Key expires at Fri Feb  1 16:23:27 2030 CST
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Is this correct? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Really create? (y&#x2F;N) y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;We need to generate a lot of random bytes. It is a good idea to perform
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;some other action (type on the keyboard, move the mouse, utilize the
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;disks) during the prime generation; this gives the random number
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;generator a better chance to gain enough entropy.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sec  ed25519&#x2F;F95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: C
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     trust: ultimate      validity: ultimate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb  ed25519&#x2F;29774A85FD26125D
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb  ed25519&#x2F;11E7B3A29E772EB3
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: A
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb  cv25519&#x2F;AE4E4336187491D9
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;     created: 2026-02-02  expires: 2030-02-01  usage: E
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[ultimate] (1). Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg&amp;gt; save
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;常用的命令:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;addkey&lt;&#x2F;code&gt;: 添加子密钥&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg&amp;gt; addkey
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please select what kind of key you want:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (3) DSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (4) RSA (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (5) Elgamal (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (6) RSA (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (7) DSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (8) RSA (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (10) ECC (sign only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (11) ECC (set your own capabilities)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (12) ECC (encrypt only)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (13) Existing key
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  (14) Existing key from card
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection?
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;子密钥的密钥类型并不要求和主密钥一致, 不过我们沿用 ECC.&lt;&#x2F;p&gt;
&lt;p&gt;对于签名 (&lt;code&gt;S&lt;&#x2F;code&gt;) 用途, 我们选择 &lt;code&gt;ECC (sign only)&lt;&#x2F;code&gt;; 对于认证 (&lt;code&gt;A&lt;&#x2F;code&gt;) 用途, 我们选择 &lt;code&gt;ECC (set your own capabilities)&lt;&#x2F;code&gt;; 对于加密 (&lt;code&gt;E&lt;&#x2F;code&gt;) 用途, 我们选择 &lt;code&gt;ECC (encrypt only)&lt;&#x2F;code&gt;. 接下来的选择 ECC 曲线和配置密钥有效期和配置主密钥时的步骤一致.&lt;&#x2F;p&gt;
&lt;p&gt;特别地, 认证 (&lt;code&gt;A&lt;&#x2F;code&gt;) 用途选择 &lt;code&gt;ECC (set your own capabilities)&lt;&#x2F;code&gt; 时, 类似配置主密钥的密钥用途那样, 一开始会让我们手动选择密钥用途:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Possible actions for this ECC key: Sign Authenticate
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Current allowed actions: Sign
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (S) Toggle the sign capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (A) Toggle the authenticate capability
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   (Q) Finished
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your selection?
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里默认情况下是签名用途的, 我们输入 &lt;code&gt;A&lt;&#x2F;code&gt; 添加认证用途, 再输入 &lt;code&gt;S&lt;&#x2F;code&gt; 去掉签名用途, 最后输入 &lt;code&gt;Q&lt;&#x2F;code&gt; 完成用途设置.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;key &amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt;: 选中 &lt;code&gt;&amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt; 指示的密钥.&lt;&#x2F;p&gt;
&lt;p&gt;选中时, 密钥前会带有 &lt;code&gt;*&lt;&#x2F;code&gt; 号; 再次输入相同 ID 即为取消选中.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;delkey&lt;&#x2F;code&gt;: 删除子密钥&lt;&#x2F;p&gt;
&lt;p&gt;需要提前选中子密钥.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;expire&lt;&#x2F;code&gt;: 更改密钥有效期.&lt;&#x2F;p&gt;
&lt;p&gt;需要提前选中子密钥, 否则默认修改主密钥的有效期.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;save&lt;&#x2F;code&gt;: 保存并退出编辑模式.&lt;&#x2F;p&gt;
&lt;p&gt;千万别忘记执行此命令, 否则前面的操作都白做了.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;dao-chu-zhu-mi-yao-diao-xiao-zheng-shu&quot;&gt;导出主密钥吊销证书&lt;&#x2F;h3&gt;
&lt;p&gt;前面生成主密钥时, 还生成了一个吊销证书:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: revocation certificate stored as &amp;#39;&#x2F;home&#x2F;hantong&#x2F;.gnupg&#x2F;openpgp-revocs.d&#x2F;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.rev&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;吊销证书是需要吊销主密钥但丢失主私钥时的兜底手段, 需要妥善保存.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dao-chu-gong-yao&quot;&gt;导出公钥&lt;&#x2F;h3&gt;
&lt;p&gt;执行 &lt;code&gt;gpg --output &amp;lt;pub-key-file&amp;gt; --armor --export &amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt; 导出公钥, 例如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --output 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.pub --armor --export 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此即将 &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2&lt;&#x2F;code&gt; 指示的 GPG 密钥的公钥以 ASCII 装甲格式 (&lt;code&gt;--armor&lt;&#x2F;code&gt;, 或者说 PEM 格式, 人类可读) 导出于当前文件夹的 &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.pub&lt;&#x2F;code&gt; 文件中.&lt;&#x2F;p&gt;
&lt;p&gt;GPG 导出的公钥&lt;strong&gt;包含主公钥和全部的子公钥&lt;&#x2F;strong&gt;, 以及有效期、子密钥吊销信息、UID 等必要信息, 并使用主私钥签名防止被篡改.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dao-chu-si-yao&quot;&gt;导出私钥&lt;&#x2F;h3&gt;
&lt;p&gt;执行 &lt;code&gt;gpg --output &amp;lt;key-file&amp;gt; --armor --export-secret-keys &amp;lt;key-id&amp;gt;!&lt;&#x2F;code&gt; 导出主私钥, 例如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --output 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.primary.key --armor --export-secret-keys 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此即将 &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2&lt;&#x2F;code&gt; 指示的 GPG 密钥的&lt;strong&gt;主私钥&lt;&#x2F;strong&gt;以 ASCII 装甲格式导出于当前文件夹的 &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.primary.key&lt;&#x2F;code&gt; 文件中. 注意, 这里的 &lt;code&gt;!&lt;&#x2F;code&gt; 是必须的.&lt;&#x2F;p&gt;
&lt;p&gt;执行 &lt;code&gt;gpg --output &amp;lt;key-file&amp;gt; --armor --export-secret-subkeys &amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt; 导出子私钥, 例如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --output 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.sub.key --armor --export-secret-subkeys 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此即将 &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2&lt;&#x2F;code&gt; 指示的 GPG 密钥的&lt;strong&gt;全部子私钥&lt;&#x2F;strong&gt;以 ASCII 装甲格式导出于当前文件夹的 &lt;code&gt;6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2.sub.key&lt;&#x2F;code&gt; 文件中.&lt;&#x2F;p&gt;
&lt;p&gt;随后离线保存主私钥, 以及备份子私钥, 以转移至其他机器上使用.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;shan-chu-ben-ji-mi-yao&quot;&gt;删除本机密钥&lt;&#x2F;h3&gt;
&lt;p&gt;在导出密钥后, 我们执行 &lt;code&gt;gpg --delete-secret-keys &amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt; 执行交互式删除私钥, 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --delete-secret-keys 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如果需要在当前机器上使用子密钥, 则只需要删除主私钥, 提示删除子私钥时取消即可. 操作完成后如果你尝试列出私钥, 会发现主私钥已经被移除了 (提示 &lt;code&gt;sec#&lt;&#x2F;code&gt;), 仅剩子私钥, 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --list-secret-keys --keyid-format long
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 略去部分输出
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sec#  ed25519&#x2F;F95DD10B4BFF68A2 2026-02-02 [C] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid                 [ultimate] Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb   ed25519&#x2F;29774A85FD26125D 2026-02-02 [S] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb   ed25519&#x2F;11E7B3A29E772EB3 2026-02-02 [A] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssb   cv25519&#x2F;AE4E4336187491D9 2026-02-02 [E] [expires: 2030-02-01]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如果打算在当前机器上彻底删除, 则一路确认删除主私钥和子私钥后, 执行 &lt;code&gt;gpg --delete-keys &amp;lt;key-id&amp;gt;&lt;&#x2F;code&gt; 删除公钥, 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --delete-keys 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;xiao-jie-1&quot;&gt;小结&lt;&#x2F;h3&gt;
&lt;p&gt;到这里, 我们已经完成了 GPG 密钥生成的全部操作, 没有特殊情况, 可以直接移步应用章节了.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gong-bu-gpg-gong-yao&quot;&gt;公布 GPG 公钥&lt;&#x2F;h2&gt;
&lt;p&gt;虽然 GPG 采用点对点的信任体系, 没有 PKI 那样的中心化信任机构, 但为了便利起见, 我们仍然可以将公钥上传到公钥服务器 (Key Server), 以便对等方方便获取和信任该公钥.&lt;&#x2F;p&gt;
&lt;p&gt;需要注意, 上传公钥到公钥服务器&lt;strong&gt;存在风险&lt;&#x2F;strong&gt;. 首先, 这是不可逆的操作, 一旦上传, 即使后续吊销该密钥, 该密钥仍然可以被下载和使用; 此外, 公钥服务器一般不会删除已经上传的公钥, 即使该公钥已经吊销. 更详细的风险说明参阅 &lt;a href=&quot;https:&#x2F;&#x2F;ulyc.github.io&#x2F;2021&#x2F;01&#x2F;26&#x2F;2021%E5%B9%B4-%E7%94%A8%E6%9B%B4%E7%8E%B0%E4%BB%A3%E7%9A%84%E6%96%B9%E6%B3%95%E4%BD%BF%E7%94%A8PGP-%E4%B8%8B&#x2F;#%E5%AE%89%E5%85%A8%E9%A3%8E%E9%99%A9%E5%92%8C%E4%BA%89%E8%AE%AE-%E8%A2%AB%E7%8E%A9%E5%9D%8F%E7%9A%84keyserver&quot;&gt;『2021 年，用更现代的方法使用PGP（下） - 安全风险和争议， 被玩坏的KeyServer』&lt;&#x2F;a&gt;. 除非你非常确定需要上传公钥, 否则不建议上传公钥到公钥服务器.&lt;&#x2F;p&gt;
&lt;p&gt;上传公钥到公钥服务器的命令如下:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --keyserver hkps:&#x2F;&#x2F;keys.openpgp.org --send-keys 6D5497B80A0F26FDCED00EFCF95DD10B4BFF68A2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;diao-xiao-gpg-mi-yao&quot;&gt;吊销 GPG 密钥&lt;&#x2F;h2&gt;
&lt;p&gt;如果一个主&#x2F;子密钥或者 UID 不再使用了, 将其删除是没有意义的, 因为我们已经将公钥分发给了对等方, 他们仍然会信任该密钥; 此外, 如果私钥泄露, 删除密钥并不能阻止他人继续使用该密钥. 为此, 我们需要吊销该密钥或 UID, 以告知对等方该密钥或 UID 不再可信, 方式包括:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用前面导出的吊销证书文件吊销主密钥:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;import&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;path-to-revocation-certificate-file&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;使用主私钥吊销密钥:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;edit-key&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;key-id&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在交互式界面中按前面所述选中需要吊销的子密钥, 输入 &lt;code&gt;revkey&lt;&#x2F;code&gt; 吊销; 不选中任何子密钥时即吊销主密钥.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;完成吊销后, 重新导出公钥并分发给对等方即可宣告吊销状态; 如果上传了公钥到公钥服务器, 也需要重新上传吊销后的公钥. 密钥吊销是不可逆的操作, 请谨慎执行.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gpg-ying-yong&quot;&gt;GPG 应用&lt;&#x2F;h2&gt;
&lt;p&gt;下文示例仍然使用前面生成的 GPG 密钥, 信息再次指出如下:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;签名用途子密钥 ID: &lt;code&gt;29774A85FD26125D&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;认证用途子密钥 ID: &lt;code&gt;11E7B3A29E772EB3&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;加密用途子密钥 ID: &lt;code&gt;AE4E4336187491D9&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;git-commit-qian-ming&quot;&gt;Git commit 签名&lt;&#x2F;h3&gt;
&lt;p&gt;下面是一个简单例子说明如何使用 GPG 签名 Git commit.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; export GPG_TTY=$(tty)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cd &#x2F;tmp
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; git init .&#x2F;test-gpg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cd test-gpg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; echo &amp;quot;Hello, GPG!&amp;quot; &amp;gt; README.md
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; git add *
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; git commit -S29774A85FD26125D -m &amp;quot;Initial commit with GPG signature&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;输入 GPG 密钥密码后, 即可完成签名提交:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; git log --show-signature
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;commit 745048eeb6e87f27d5e5a8aa1f6e33f3543b8942 (HEAD -&amp;gt; main)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: Signature made Mon Feb  2 17:09:22 2026 CST
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg:                using EDDSA key 652C18ED73F115A6F9369B9029774A85FD26125D
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: Good signature from &amp;quot;Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;&amp;quot; [ultimate]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Author: Hantong Chen &amp;lt;public-service@7rs.net&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Date:   Mon Feb 2 17:09:22 2026 +0800
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    Initial commit with GPG signature
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可以修改 &lt;code&gt;~&#x2F;.gitconfig&lt;&#x2F;code&gt; 配置文件, 以便每次提交时自动使用指定的 GPG 密钥签名, 如:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ini&quot; class=&quot;language-ini z-code&quot;&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-storage z-type z-genconfig&quot;&gt;[user]
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;name&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; Hantong Chen
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;email&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; public&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;-&lt;&#x2F;span&gt;service&lt;span class=&quot;z-storage z-source z-genconfig&quot;&gt;@7rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;.&lt;&#x2F;span&gt;net
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-genconfig&quot;&gt;&lt;span class=&quot;z-meta z-param z-genconfig&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-genconfig&quot;&gt;signingkey&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-genconfig&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; 29774A85FD26125D
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;显然, 我们使用的是具备签名 (&lt;strong&gt;S&lt;&#x2F;strong&gt;) 用途的子密钥 ID, 后面不再赘述.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;github-gpg-pei-zhi&quot;&gt;Github GPG 配置&lt;&#x2F;h4&gt;
&lt;p&gt;打开 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;settings&#x2F;gpg&#x2F;new&quot;&gt;Add new GPG key&lt;&#x2F;a&gt; 页面, 填写 Title, 将前面导出的公钥文件内容全部复制粘贴至 Key 的文本框中确认即可. 添加完毕的效果如下图所示:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;i.han.rs&#x2F;blog&#x2F;26-02-01-gpg&#x2F;.&#x2F;github.png&quot; alt=&quot;Github GPG keys&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;完成添加后, 如果邮箱还没有在 Github 上添加 (图中可见 Email address 是 Unverified 的) 就去添加一下即可, 和本文无关, 此处不赘述.&lt;&#x2F;p&gt;
&lt;p&gt;一切配置正确后, 带有签名的 commit 即会显示为 Verified (此处随意截取了一个 commit, 非本文生成的 commit):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;i.han.rs&#x2F;blog&#x2F;26-02-01-gpg&#x2F;.&#x2F;github-verified.png&quot; alt=&quot;Github Verified commit&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;可以配置 &lt;code&gt;Vigilant mode&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;i.han.rs&#x2F;blog&#x2F;26-02-01-gpg&#x2F;.&#x2F;github-vigilant-mode.png&quot; alt=&quot;Github Vigilant mode&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上述流程可能存在时效性, 以官方文档 &lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;authentication&#x2F;managing-commit-signature-verification&#x2F;adding-a-gpg-key-to-your-github-account&quot;&gt;GitHub Docs: Adding a GPG key to your GitHub account&lt;&#x2F;a&gt; 说明的最新流程为准.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;gitea-forgejo-gpg-pei-zhi&quot;&gt;Gitea &#x2F; Forgejo GPG 配置&lt;&#x2F;h4&gt;
&lt;p&gt;打开 &lt;a href=&quot;https:&#x2F;&#x2F;%7BYOUR_GIT_SERVER_HOST%7D&#x2F;user&#x2F;settings&#x2F;keys&quot;&gt;https:&#x2F;&#x2F;{YOUR_GIT_SERVER_HOST}&#x2F;user&#x2F;settings&#x2F;keys&lt;&#x2F;a&gt;, 添加 GPG 密钥:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;i.han.rs&#x2F;blog&#x2F;26-02-01-gpg&#x2F;.&#x2F;forgejo-gpg.png&quot; alt=&quot;Forgejo GPG seetting&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;如果 GPG 密钥的电子邮件和当前用户一致电子邮件不匹配, 则会要求执行一次签名确认:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; echo &amp;quot;[REDACTED]&amp;quot; | gpg -a --default-key F95DD10B4BFF68A2 --detach-sig
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: using &amp;quot;F95DD10B4BFF68A2&amp;quot; as default secret key for signing
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN PGP SIGNATURE-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[REDACTED]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END PGP SIGNATURE-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;将执行结果 (“&lt;code&gt;-----BEGIN PGP SIGNATURE-----&lt;&#x2F;code&gt;” 和 “&lt;code&gt;-----END PGP SIGNATURE-----&lt;&#x2F;code&gt;” 之间的部分, 包括这两行) 复制粘贴至确认框中完成确认.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;yi-nan-za-zheng&quot;&gt;疑难杂症&lt;&#x2F;h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;“gpg: signing failed: Inappropriate ioctl for device”&lt;&#x2F;p&gt;
&lt;p&gt;出现该错误时, 一般是因为 &lt;code&gt;GPG_TTY&lt;&#x2F;code&gt; 环境变量未正确设置.&lt;&#x2F;p&gt;
&lt;p&gt;解决方案: 需要执行 &lt;code&gt;export GPG_TTY=$(tty)&lt;&#x2F;code&gt;, 或者添加至 &lt;code&gt;~&#x2F;.bashrc&lt;&#x2F;code&gt; 等 shell 配置文件中.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;“not a tty”&lt;&#x2F;p&gt;
&lt;p&gt;使用 zsh 时, 在 VSCode 远程开发等场景中, 将 &lt;code&gt;export GPG_TTY=$(tty)&lt;&#x2F;code&gt; 添加至 &lt;code&gt;~&#x2F;.zshrc&lt;&#x2F;code&gt;, 可能出现 “not a tty” 错误.&lt;&#x2F;p&gt;
&lt;p&gt;解决方案: 需要在 &lt;code&gt;~&#x2F;.zshenv&lt;&#x2F;code&gt; 中添加而不是 &lt;code&gt;~&#x2F;.zshrc&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;其他奇奇怪怪无法弹出 GPG 密码输入窗口的问题.&lt;&#x2F;p&gt;
&lt;p&gt;解决方案: 建议重新打开终端再尝试一下.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;wen-jian-jia-jie-mi&quot;&gt;文件加解密&lt;&#x2F;h3&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-keyword z-control z-regexp z-set z-begin z-shell&quot;&gt;[&lt;&#x2F;span&gt;-&lt;span class=&quot;z-keyword z-operator z-word z-shell&quot;&gt;-&lt;&#x2F;span&gt;output &amp;lt;output&lt;span class=&quot;z-keyword z-operator z-word z-shell&quot;&gt;-&lt;&#x2F;span&gt;file&amp;gt;&lt;span class=&quot;z-keyword z-control z-regexp z-set z-end z-shell&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;encrypt&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;recipient&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;recipient-key-id&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;input-file&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;即: 使用 &lt;code&gt;&amp;lt;recipient-key-id&amp;gt;&lt;&#x2F;code&gt; 密钥对的公钥加密 &lt;code&gt;&amp;lt;input-file&amp;gt;&lt;&#x2F;code&gt; 文件, 输出加密结果至 &lt;code&gt;&amp;lt;output-file&amp;gt;&lt;&#x2F;code&gt;, 如果未指定 &lt;code&gt;--output &amp;lt;output-file&amp;gt;&lt;&#x2F;code&gt;, 默认输出至 &lt;code&gt;&amp;lt;input-file&amp;gt;.gpg&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;output&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;decrypted-file&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-terminator z-file-descriptor z-shell&quot;&gt;-&lt;&#x2F;span&gt;-decrypt &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;encrypted-file&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;即: 使用和加密时使用的公钥成对的私钥解密 &lt;code&gt;&amp;lt;encrypted-file&amp;gt;&lt;&#x2F;code&gt; 文件, 输出解密结果至 &lt;code&gt;&amp;lt;decrypted-file&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;一个例子:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cd &#x2F;tmp
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; echo &amp;quot;Hello, GPG file encryption&amp;quot; &amp;gt; secret.test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --output secret.test.gpg --encrypt --recipient AE4E4336187491D9 secret.test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 提示输入密码时输入密码; 短时间内重复加密不会再提示输入密码
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --output secret.decrypted.test --decrypt secret.test.gpg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: encrypted with cv25519 key, ID AE4E4336187491D9, created 2026-02-02
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      &amp;quot;Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cat secret.decrypted.test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Hello, GPG file encryption
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;qian-ming-he-yan-qian&quot;&gt;签名和验签&lt;&#x2F;h3&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;armor&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-regexp z-set z-begin z-shell&quot;&gt;[&lt;&#x2F;span&gt;-u &amp;lt;your&lt;span class=&quot;z-keyword z-operator z-word z-shell&quot;&gt;-&lt;&#x2F;span&gt;key&lt;span class=&quot;z-keyword z-operator z-word z-shell&quot;&gt;-&lt;&#x2F;span&gt;id&amp;gt;&lt;span class=&quot;z-keyword z-control z-regexp z-set z-end z-shell&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;output&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;output-sig-file&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-terminator z-file-descriptor z-shell&quot;&gt;-&lt;&#x2F;span&gt;-detach-sign &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;file-to-be-signed&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;即: 使用 &lt;code&gt;&amp;lt;your-key-id&amp;gt;&lt;&#x2F;code&gt; 密钥对的私钥 (不提供则使用 GPG 配置的默认密钥) 签名 &lt;code&gt;&amp;lt;file-to-be-signed&amp;gt;&lt;&#x2F;code&gt; 文件, 生成 PEM 格式的签名 (&lt;code&gt;--armor&lt;&#x2F;code&gt;, &lt;code&gt;-a&lt;&#x2F;code&gt;) 并保存至 &lt;code&gt;&amp;lt;output-sig-file&amp;gt;&lt;&#x2F;code&gt; 而不是嵌入待签名的文件内 (&lt;code&gt;--detach-sign&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-keyword z-control z-regexp z-set z-begin z-shell&quot;&gt;[&lt;&#x2F;span&gt;-u &amp;lt;your&lt;span class=&quot;z-keyword z-operator z-word z-shell&quot;&gt;-&lt;&#x2F;span&gt;key&lt;span class=&quot;z-keyword z-operator z-word z-shell&quot;&gt;-&lt;&#x2F;span&gt;id&amp;gt;&lt;span class=&quot;z-keyword z-control z-regexp z-set z-end z-shell&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;verify&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;sig-file&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;file-to-be-verified&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;即: 使用 &lt;code&gt;&amp;lt;your-key-id&amp;gt;&lt;&#x2F;code&gt; 密钥对的公钥 (不提供则自动匹配), 对 &lt;code&gt;&amp;lt;file-to-be-verified&amp;gt;&lt;&#x2F;code&gt; 文件, 读取并验证 &lt;code&gt;&amp;lt;sig-file&amp;gt;&lt;&#x2F;code&gt; 中的签名, 如果签名有效且文件未被篡改, 则输出 “Good signature” 信息.&lt;&#x2F;p&gt;
&lt;p&gt;一个例子:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cd &#x2F;tmp
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; echo &amp;quot;Hello, GPG signing&amp;quot; &amp;gt; signing.test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --armor -u 29774A85FD26125D --output signing.test.sig --detach-sign signing.test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --verify signing.test.sig signing.test
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: Signature made Mon Feb  2 23:01:40 2026 CST
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg:                using EDDSA key 652C18ED73F115A6F9369B9029774A85FD26125D
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gpg: Good signature from &amp;quot;Hantong Chen (2026) &amp;lt;public-service@7rs.net&amp;gt;&amp;quot; [ultimate]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这是相当常见的用途, 例如 Debian 软件包管理系统就使用 GPG 对软件包进行签名, 在早期 HTTPS 并不成熟时用以保证软件包在网络传输时不被篡改:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shellsession&quot; class=&quot;language-shellsession z-code&quot;&gt;&lt;code class=&quot;language-shellsession&quot; data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; cat &#x2F;etc&#x2F;apt&#x2F;sources.list.d&#x2F;debian.sources
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Types: deb deb-src
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;URIs: http:&#x2F;&#x2F;deb.debian.org&#x2F;debian
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Suites: trixie trixie-updates trixie-backports
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Components: main contrib non-free non-free-firmware
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Signed-By: &#x2F;usr&#x2F;share&#x2F;keyrings&#x2F;debian-archive-keyring.gpg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Types: deb deb-src
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;URIs: http:&#x2F;&#x2F;deb.debian.org&#x2F;debian-security
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Suites: trixie-security
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Components: main contrib non-free non-free-firmware
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Signed-By: &#x2F;usr&#x2F;share&#x2F;keyrings&#x2F;debian-archive-keyring.gpg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;gt; gpg --show-keys &#x2F;usr&#x2F;share&#x2F;keyrings&#x2F;debian-archive-keyring.gpg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;pub   rsa4096 2021-01-17 [SC] [expires: 2029-01-15]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      1F89983E0081FDE018F3CC9673A4F27B8DD47936
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: 80E976F14A508A48E9CA3FE9BC372252CA1CF964
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: FBFABDB541B5DC955BD9BA6EDB16CF5BB12525C4
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: 8C823DED10AA8041639E12105ACE8D6E0C14A470
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: 309911BEA966D0613053045711B4E5FF15B0FD82
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: C74F6AC9E933B3067F52F33FA459EC6715B0705F
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid                      Debian Archive Automatic Signing Key (11&#x2F;bullseye) &amp;lt;ftpmaster@debian.org&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sub   rsa4096 2021-01-17 [S] [expires: 2029-01-15]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;pub   rsa4096 2021-01-17 [SC] [expires: 2029-01-15]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      AC530D520F2F3269F5E98313A48449044AAD5C5D
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: 80E976F14A508A48E9CA3FE9BC372252CA1CF964
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: FBFABDB541B5DC955BD9BA6EDB16CF5BB12525C4
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: 8C823DED10AA8041639E12105ACE8D6E0C14A470
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: 309911BEA966D0613053045711B4E5FF15B0FD82
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;      Revocable by: C74F6AC9E933B3067F52F33FA459EC6715B0705F
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uid                      Debian Security Archive Automatic Signing Key (11&#x2F;bullseye) &amp;lt;ftpmaster@debian.org&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sub   rsa4096 2021-01-17 [S] [expires: 2029-01-15]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# 其余输出省略
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可以看到, 该公钥文件包含了多个主密钥, 每个主密钥下有一个签名用途的子密钥.&lt;&#x2F;p&gt;
&lt;p&gt;(碎碎念: 本文的写作动机之一就是笔者想自行维护一个 APT 软件源, 对官方源不包含的一些较新的开源软件使用 Github Actions 执行可信自动构建并打包为 deb 包…)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jie-yu&quot;&gt;结语&lt;&#x2F;h2&gt;
&lt;p&gt;(唉, 年纪大了, 先总结到这里, 假装有个结语吧; 后面看情况增删补改.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;参考资料&lt;&#x2F;h2&gt;
&lt;p&gt;本文参考了大量资料, 在此不完整列出, 以表感谢:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zhuanlan.zhihu.com&#x2F;p&#x2F;137801979&quot;&gt;简明 GPG 概念&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.rectcircle.cn&#x2F;posts&#x2F;understand-and-use-gpg&#x2F;&quot;&gt;使用 GPG&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gaoweix.com&#x2F;gpg-guide&#x2F;&quot;&gt;val’s Blog - GPG 使用指南&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.cnblogs.com&#x2F;usmile&#x2F;p&#x2F;12873604.html&quot;&gt;GPG配置、命令、实例与apt-key密钥测试&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;musigma.blog&#x2F;2021&#x2F;05&#x2F;09&#x2F;gpg-ssh-ed25519.html&quot;&gt;An abridged guide to using ed25519 PGP keys with GnuPG and SSH&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;blog.darkthread.net&#x2F;blog&#x2F;gpg-gen-key-n-encrypt&#x2F;&quot;&gt;GPG 筆記 - 產生 ECC 金鑰及加解密檔案&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;…&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;hr &#x2F;&gt;
&lt;footer class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;GNU_Privacy_Guard&quot;&gt;WikiPedia: GNU Privacy Guard&lt;&#x2F;a&gt;. &lt;a href=&quot;#fr-1-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;参阅 &lt;a href=&quot;https:&#x2F;&#x2F;safecurves.cr.yp.to&#x2F;&quot;&gt;https:&#x2F;&#x2F;safecurves.cr.yp.to&#x2F;&lt;&#x2F;a&gt;. &lt;a href=&quot;#fr-2-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;footer&gt;
</content>
        <summary type="html">简要总结 GPG 基本概念、术语, GPG 密钥生成及管理相关内容, 以便快速参考.</summary>
        </entry><entry xml:lang="zh">
        <title>TLS 加密 ClientHello (Encrypted Client Hello, ECH)</title>
        <published>2025-10-05T00:00:00+00:00</published>
        <updated>2025-10-05T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/translation/draft-ietf-tls-esni/" type="text/html"/>
        <id>https://i.han.rs/translation/draft-ietf-tls-esni/</id>
        
            <content type="html"></content>
        <summary type="html">本译文材料参见 [draft-ietf-tls-esni](https:&#x2F;&#x2F;github.com&#x2F;han-rs&#x2F;draft-ietf-tls-esni), 手动构建为 HTML. 全文字数颇多, 难免错漏, 还请谅解, 欢迎反馈交流.
</summary>
        </entry><entry xml:lang="zh">
        <title>混合公钥加密 (hybrid public key encryption, HPKE) 方案</title>
        <published>2025-10-05T00:00:00+00:00</published>
        <updated>2025-10-08T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/translation/draft-irtf-cfrg-hpke/" type="text/html"/>
        <id>https://i.han.rs/translation/draft-irtf-cfrg-hpke/</id>
        
            <content type="html"></content>
        <summary type="html">本译文材料参见 [draft-irtf-cfrg-hpke](https:&#x2F;&#x2F;github.com&#x2F;han-rs&#x2F;draft-irtf-cfrg-hpke), 手动构建为 HTML. 全文字数颇多, 难免错漏, 还请谅解, 欢迎反馈交流.
</summary>
        </entry><entry xml:lang="zh">
        <title>从零开始了解 TLS 1.3 系列笔记 (六) —— 实用密码学之「对称加密、AEAD 与非对称密码学」</title>
        <published>2025-09-17T00:00:00+00:00</published>
        <updated>2025-09-25T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-09-17-tls-series-cryptography-5/" type="text/html"/>
        <id>https://i.han.rs/blog/25-09-17-tls-series-cryptography-5/</id>
        
            <content type="html">&lt;blockquote&gt;
&lt;p&gt;一些密码学高频英文术语需要我们了解&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;加密 (encrypt), 解密 (decrypt)&lt;&#x2F;li&gt;
&lt;li&gt;密文 (ciphertext), 明文 (plaintext)&lt;&#x2F;li&gt;
&lt;li&gt;对称加密 (symmetric encryption), 非对称加密 (asymmetric encryption)&lt;&#x2F;li&gt;
&lt;li&gt;“cipher”: 一般指 “加密算法”, 但有些翻译会直译为 “密码”, 需要和 “password” 区分.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;密码学中, 依据加解密密钥是否相同, 可以将加密算法分为对称加密和非对称加密两大类.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dui-cheng-jia-mi&quot;&gt;对称加密&lt;&#x2F;h2&gt;
&lt;p&gt;对称加密, 即加密和解密使用相同的密钥. 这种加密方式的优点是加解密速度快, 适合处理大量数据. 常见的对称加密算法包括 AES 和 Chacha20 等.
其中绝大多数都是 “块密码算法 (Block Cipher)” 或者叫 “&lt;strong&gt;分组密码算法&lt;&#x2F;strong&gt;”, 这种算法&lt;strong&gt;一次只能加密固定大小的块&lt;&#x2F;strong&gt; (例如 128 位), 少部分是 “&lt;strong&gt;流密码算法&lt;&#x2F;strong&gt; (Stream Cipher)”, 流密码算法允许将数据逐字节地加密为密文流.
可以使用称为&lt;em&gt;分组密码工作模式&lt;&#x2F;em&gt;的技术, 让分组密码算法也能做到流式加密, 我们会在后文详细了解.&lt;&#x2F;p&gt;
&lt;p&gt;用于加密&#x2F;解密的密钥也被称为共享密钥(需要在通信双方之间共享). 可以是 PSK, 如从密码利用 KDF 派生得到, 或编码为 Base58 &#x2F; Base64 格式; 或者通过密钥交换方案协商或传输.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;小贴士&lt;&#x2F;p&gt;
&lt;p&gt;对称加密所使用的密钥一般是 128 &#x2F; 192 &#x2F; 256 位的二进制数据, 直接使用不便于存储和传输, 因此通常会编码为 Base58 &#x2F; Base64 格式.&lt;&#x2F;p&gt;
&lt;p&gt;原始 (按 HEX 格式给出):&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;02c324648931b89e3e8a0fc42c96e8e3be2e42812986573a40d46563bceaf75110
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Base58 编码:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;pbPRqYDxnKZfs8j4KKiqYmx6nzipAjTJf1oCD1WKgy99
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Base64 编码:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AsMkZIkxuJ4+ig&#x2F;ELJbo474uQoEphlc6QNRlY7zq91EQ
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;大多数现代对称密钥密码算法都是抗量子的 (quantum-resistant), 当使用长度足够的密钥时 (如 AES-256), 即便是量子计算机也无法破坏其安全性.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dui-cheng-jia-mi-fang-an&quot;&gt;对称加密方案&lt;&#x2F;h3&gt;
&lt;p&gt;我们知道, 单纯使用加密算法只能保证数据的安全性, 并不能满足我们对消息真实性、完整性与不可否认性的需求, 因此通常我们会将对称加密算法跟其他算法组合成一个对称加密方案来使用:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;KDF (如果是密码作 PSK 的模式, 需要使用 KDF 从密码学强度低的密码派生出密码学强度高的伪随机密钥)&lt;&#x2F;li&gt;
&lt;li&gt;分组密码工作模式 (如 CBC 或 CTR) + 消息填充算法 (如 PKCS7): 分组密码算法, 如 AES, 需要借助这两种算法, 才能加密任意大小的数据 (即所谓的数据流). 而一个流密码加密方案本身就能加密任意长度的数据, 因此不需要分组密码模式与消息填充算法.&lt;&#x2F;li&gt;
&lt;li&gt;密码算法.&lt;&#x2F;li&gt;
&lt;li&gt;消息认证算法: 如 HMAC, 用于验证消息的真实性、完整性、不可否认性.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;如 AES-256-CTR-HMAC-SHA256 就表示一个使用 AES-256 与 Counter 分组模式 (CTR) 进行加密, 使用 HMAC-SHA256 进行消息认证的加密方案.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fen-zu-mi-ma-gong-zuo-mo-shi&quot;&gt;分组密码工作模式&lt;&#x2F;h3&gt;
&lt;p&gt;前面我们提到分组密码工作模式可以让分组密码算法也能加密任意长度的数据. 其中涉及一些概念值得我们了解.&lt;&#x2F;p&gt;
&lt;p&gt;加密方案的名称中就带有具体的分组模式名称, 如:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;AES-256-GCM - AES 密码算法 + 256 位加密密钥 + Galois Counter Mode (GCM) 分组模式&lt;&#x2F;li&gt;
&lt;li&gt;AES-128-CTR - AES 密码算法 + 128 位加密密钥 + Counter (CTR) 分组模式&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;分组密码工作模式背后的主要思想是把明文分成多个长度固定的组, 再在这些分组上重复应用分组密码算法进行加密&#x2F;解密, 以实现安全地加密&#x2F;解密任意长度的数据.
某些分组模式 (如 CBC) 要求将输入拆分为分组, 并使用填充算法将最末尾的分组填充到块大小, 也有些分组模式 (如 CTR、CFB、OFB、CCM、EAX 和 GCM) 不需要, 因为它们在每个步骤中, 都直接在明文部分和内部密码状态之间执行异或 (XOR) 运算.&lt;&#x2F;p&gt;
&lt;p&gt;使用 “分组模式” 加密大量数据的流程基本如下:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;使用加密密钥 + IV (Initialization Vector, 初始向量) 初始化加密算法状态&lt;&#x2F;li&gt;
&lt;li&gt;加密数据的第一个分组&lt;&#x2F;li&gt;
&lt;li&gt;使用加密密钥和其他参数转换加密算法的当前状态&lt;&#x2F;li&gt;
&lt;li&gt;加密下一个分组&lt;&#x2F;li&gt;
&lt;li&gt;再次转换加密状态&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;依此类推, 直到处理完所有输入数据. 解密的流程类似.&lt;&#x2F;p&gt;
&lt;p&gt;值得注意的是, 显然, 使用 CTR &#x2F; GCM 分组模式时, &lt;strong&gt;密文的大小与明文相同&lt;&#x2F;strong&gt;, 必要时应当进行填充 (padding) 以掩盖明文的真实长度.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;iv&quot;&gt;IV&lt;&#x2F;h4&gt;
&lt;p&gt;IV 也被称作 salt 或者 &lt;strong&gt;nonce&lt;&#x2F;strong&gt;, 通常是一个随机数, 主要作用是往密文中添加随机性, 使同样的明文被多次加密也会产生不同的密文, 从而确保密文的不可预测性.&lt;&#x2F;p&gt;
&lt;p&gt;IV 的大小应与密码块大小相同.&lt;&#x2F;p&gt;
&lt;p&gt;IV 通常无需保密, 但应当足够随机 (GCM 模式下除外). 绝不允许重用 (nonce 即 number once, 一次性的数字).&lt;&#x2F;p&gt;
&lt;h4 id=&quot;gcm-fen-zu-mo-shi&quot;&gt;GCM 分组模式&lt;&#x2F;h4&gt;
&lt;p&gt;GCM (Galois Counter Mode) 是一种广泛使用的分组密码工作模式, 结合了 CTR 模式的加密和 Galois 域上的&lt;strong&gt;消息认证&lt;&#x2F;strong&gt;. GCM 模式下的 AES 加密过程除了得到密文外, 还会得到一个&lt;strong&gt;认证标签&lt;&#x2F;strong&gt; (authentication tag), 一般附在密文后面.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ae-ad&quot;&gt;AE(AD)&lt;&#x2F;h3&gt;
&lt;p&gt;AE(AD) 即 Authenticated Encryption (with Associated Data), (带关联数据的)认证加密, 我们前面已经提到过这个名词, 其包含了 “认证” 和 “加密” 两大部分.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;认证 (Authentication): 通常使用 HMAC, Poly1305, 或者 GCM 分组模式自带.&lt;&#x2F;li&gt;
&lt;li&gt;加密 (Encryption): 使用对称加密算法, 如 AES 或 ChaCha.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;正确实现了消息认证的对称加密方案, 如 AES-256-CTR-HMAC-SHA256, AES-128-GCM, AES-256-GCM 或 ChaCha20-Poly1305 等, 均可称为 AE(AD) 加密方案. 今天的大多数应用程序应该优先选用 AEAD 加密方案进行对称加密, 而不是自己造轮子, 由于密码学的复杂性, 自己造轮子往往会出错.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;性能小贴士: AES 和 Chacha 怎么选择?&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;机器 CPU 支持 AES 硬件加速 (大部分现代 CPU 都支持), 使用 AES.&lt;&#x2F;li&gt;
&lt;li&gt;机器 CPU 不支持 AES 硬件加速, 如 2014 年以前的 x86 架构处理器, 或者移动端相当一部分 ARM 架构处理器, 或者路由器所使用的大部分的 MIPS 架构处理器. 此时使用 ChaCha 会更快 (本来就是为移动端特别优化的).&lt;&#x2F;li&gt;
&lt;li&gt;如果机器 CPU 足够现代且性能强劲, AES 和 ChaCha 差别不大, 在 HTTPS 场景下基本不可能是加密能力瓶颈, 任选其一即可.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;如果想测试, 可以尝试运行:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;openssl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; speed&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;evp&lt;&#x2F;span&gt; aes-128-gcm&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;openssl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; speed&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;evp&lt;&#x2F;span&gt; chacha20-poly1305&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;参考输出:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; openssl &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;speed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;evp&lt;&#x2F;span&gt; chacha20-poly1305&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; (略&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;&#x2F;span&gt;) &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;The&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;numbers&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; are in 1000s of bytes per second processed.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-type z-shell&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;              2 bytes     31 bytes    136 bytes   1024 bytes   8192 bytes  16384 bytes&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;ChaCha20-Poly1305&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;   135090.41k   398677.07k   685502.68k  4633523.20k  5013787.99k  5053565.35k&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; openssl &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;speed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;evp&lt;&#x2F;span&gt; aes-128-gcm&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; (略&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;&#x2F;span&gt;) &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;The&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;numbers&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; are in 1000s of bytes per second processed.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-type z-shell&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;              2 bytes     31 bytes    136 bytes   1024 bytes   8192 bytes  16384 bytes&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;AES-128-GCM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;      16855.33k   246339.19k  1061738.45k  4942513.83k 13130011.57k 14806788.78k&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;结果显示, 在笔者的机器上, AES-128-GCM 的加密速度 (14.8 GB&#x2F;s) 明显快于 ChaCha20-Poly1305 (5.1 GB&#x2F;s).
然而, 5 GB&#x2F;s 是 40 Gbps 的网络带宽才能达到的速度了, 还只用到了一个核心.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;dui-cheng-jia-mi-suan-fa&quot;&gt;对称加密算法&lt;&#x2F;h3&gt;
&lt;p&gt;以下列举一些常见的对称加密算法.&lt;&#x2F;p&gt;
&lt;p&gt;安全的:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;AES (Advanced Encryption Standard, 高级加密标准): 目前最流行的对称加密算法, 由 NIST 于 2001 年发布. 支持 128, 192 和 256 位密钥长度. AES 是一种分组密码算法, 块大小为 128 位, 使用 128 位的 IV. AES-GCM 是目前最常用的对称加密方案之一.&lt;&#x2F;li&gt;
&lt;li&gt;ChaCha: Salsa 改进版变种, 包括 Chacha20 等. 使用 128 &#x2F; 256 位密钥和 64 位 IV. ChaCha20-Poly1305 也是一种流行的对称加密方案, 结合了 ChaCha20 流密码和 Poly1305 消息认证码.&lt;&#x2F;li&gt;
&lt;li&gt;其他: 包括但不限于 RC5, RC6, 韩国的 ARIA (类似 AES), 中国的 SM4 (类似 AES) 等.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;由于出现安全问题已弃用的:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;DES&lt;&#x2F;li&gt;
&lt;li&gt;3DES&lt;&#x2F;li&gt;
&lt;li&gt;RC4&lt;&#x2F;li&gt;
&lt;li&gt;Blowfish&lt;&#x2F;li&gt;
&lt;li&gt;…&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;blockquote&gt;
&lt;p&gt;在介绍完对称加密后, 我们来了解一下非对称加密. 在此之前, 我们先了解一下非对称密码学(公钥密码学)的基本概念.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;gong-yao-mi-ma-xue&quot;&gt;公钥密码学&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;公钥密码系统的密钥始终以公钥&#x2F;私钥对(&lt;strong&gt;密钥对&lt;&#x2F;strong&gt;, Key Pair)的形式出现, 公钥密码系统提供数学框架和算法来生成密钥对. 公钥通常与所有人共享, 而私钥则保密. 公钥密码系统在设计时就确保了在预期的算力下, 几乎不可能从其公开的公钥逆向演算出对应的私钥.&lt;&#x2F;li&gt;
&lt;li&gt;公钥密码系统主要有三大用途: &lt;strong&gt;加密与解密&lt;&#x2F;strong&gt;、&lt;strong&gt;签名与验证&lt;&#x2F;strong&gt;、&lt;strong&gt;密钥交换&lt;&#x2F;strong&gt;. 每种算法都需要使用到公钥和私钥, 比如由公钥加密的消息只能由私钥解密, 由私钥签名的消息需要用公钥验证. 由于加密解密、签名验证均需要两个不同的密钥, 故公钥密码学也被称为非对称密码学.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;比较著名的公钥密码系统有: RSA、ECC、(EC)DH. 不同的公钥密码系统可以提供以下一项或多项功能:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;执行密钥对生成: 随机生成私钥和对应的公钥;&lt;&#x2F;li&gt;
&lt;li&gt;执行加解密: 通过公钥加密, 通过私钥解密;&lt;&#x2F;li&gt;
&lt;li&gt;作为密钥交换算法;&lt;&#x2F;li&gt;
&lt;li&gt;执行数字签名 (消息认证): 通过私钥对消息进行签名, 通过公钥验证签名.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;公钥密码系统的密钥交换功能, 我们已经在前面的文章讲述过, 在此不再赘述; 公钥密码系统的数字签名功能, 我们会在后文单独介绍.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fei-dui-cheng-jia-mi&quot;&gt;非对称加密&lt;&#x2F;h3&gt;
&lt;p&gt;应用公钥密码系统的加(解)密功能即称非对称加(解)密. 非对称加密安全性相对于对称加密高, 但缺点也明显:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;算法更为复杂, 加解密速度比对称加密慢非常多.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;只能加密&#x2F;解密很短的消息.&lt;&#x2F;p&gt;
&lt;p&gt;如在 RSA 系统中, 输入消息需要被转换为大整数, 例如使用 OAEP 填充, 然后才能被加密为密文. (密文实质上就是另一个大整数.)&lt;&#x2F;p&gt;
&lt;p&gt;一些非对称密码系统, 如 ECC, 不直接提供加密能力, 需要结合使用更复杂的方案才能实现加解密.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;为此, 现代密码学协议一般&lt;strong&gt;混合使用对称加密和非对称加密&lt;&#x2F;strong&gt; 以发挥各自的优势, 如使用非对称加密方案来安全地生成、交换对称加密所需的共享密钥, 然后使用对称加密方案来加密实际的消息内容.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;KEM: 仅使用非对称加密算法加密另一个密钥, 实际数据的加解密由该密钥完成. 前面介绍 TLS 1.3 后量子密码学改进时提到过 ML-KEM, 这就是一个典型的 KEM.&lt;&#x2F;p&gt;
&lt;p&gt;RSA-OAEP, RSA-KEM, ECIES-KEM 和 PSEC-KEM. 都是 KEM 加密方案.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;IKS: 集成加密方案, 在 KEM 技术上添加 KDF 等其他密码学算法达成更高安全性, 此处不展开.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;rsa-gong-yao-mi-ma-xi-tong&quot;&gt;RSA 公钥密码系统&lt;&#x2F;h3&gt;
&lt;p&gt;RSA 密码系统是最早的公钥密码系统之一, 它基于模幂的数学和 RSA 问题的计算难度以及密切相关的整数分解问题 (IFP). RSA 算法以其作者的首字母命名, 并在计算机密码学的早期广泛使用.&lt;&#x2F;p&gt;
&lt;p&gt;RSA 密码系统是典型的公钥密码系统, 完整提供了密钥对生成、加解密、密钥交换和数字签名等功能.&lt;&#x2F;p&gt;
&lt;p&gt;RSA 私钥长度可以是 1024, 2048, 3072 或 4096 位及更长, 但 3072 位是目前的推荐最小长度, 过长的密钥也会导致加解密速度过慢.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;shi-li&quot;&gt;示例&lt;&#x2F;h4&gt;
&lt;p&gt;2048 位 RSA 公钥示例 (表示为 2048 位十六进制整数模数 n 和 24 位公指数 e):&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;n = 0xa709e2f84ac0e21eb0caa018cf7f697f774e96f8115fc2359e9cf60b1dd8d4048d974cdf8422bef6be3c162b04b916f7ea2133f0e3e4e0eee164859bd9c1e0ef0357c142f4f633b4add4aab86c8f8895cd33fbf4e024d9a3ad6be6267570b4a72d2c34354e0139e74ada665a16a2611490debb8e131a6cffc7ef25e74240803dd71a4fcd953c988111b0aa9bbc4c57024fc5e8c4462ad9049c7f1abed859c63455fa6d58b5cc34a3d3206ff74b9e96c336dbacf0cdd18ed0c66796ce00ab07f36b24cbe3342523fd8215a8e77f89e86a08db911f237459388dee642dae7cb2644a03e71ed5c6fa5077cf4090fafa556048b536b879a88f628698f0c7b420c4b7
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;e = 0x010001
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;以 RSA PKCS#8 PEM ASN.1 格式编码:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN PUBLIC KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApwni+ErA4h6wyqAYz39p
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;f3dOlvgRX8I1npz2Cx3Y1ASNl0zfhCK+9r48FisEuRb36iEz8OPk4O7hZIWb2cHg
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;7wNXwUL09jO0rdSquGyPiJXNM&#x2F;v04CTZo61r5iZ1cLSnLSw0NU4BOedK2mZaFqJh
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;FJDeu44TGmz&#x2F;x+8l50JAgD3XGk&#x2F;NlTyYgRGwqpu8TFcCT8XoxEYq2QScfxq+2FnG
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;NFX6bVi1zDSj0yBv90uelsM226zwzdGO0MZnls4AqwfzayTL4zQlI&#x2F;2CFajnf4no
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;agjbkR8jdFk4je5kLa58smRKA+ce1cb6UHfPQJD6+lVgSLU2uHmoj2KGmPDHtCDE
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;twIDAQAB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END PUBLIC KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;2048 位 RSA 私钥示例, 对应于上述给定的公钥 (表示为十六进制 2048 位整数模数 n 和 2048 位秘密指数 d):&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;n = 0xa709e2f84ac0e21eb0caa018cf7f697f774e96f8115fc2359e9cf60b1dd8d4048d974cdf8422bef6be3c162b04b916f7ea2133f0e3e4e0eee164859bd9c1e0ef0357c142f4f633b4add4aab86c8f8895cd33fbf4e024d9a3ad6be6267570b4a72d2c34354e0139e74ada665a16a2611490debb8e131a6cffc7ef25e74240803dd71a4fcd953c988111b0aa9bbc4c57024fc5e8c4462ad9049c7f1abed859c63455fa6d58b5cc34a3d3206ff74b9e96c336dbacf0cdd18ed0c66796ce00ab07f36b24cbe3342523fd8215a8e77f89e86a08db911f237459388dee642dae7cb2644a03e71ed5c6fa5077cf4090fafa556048b536b879a88f628698f0c7b420c4b7
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;d = 0x10f22727e552e2c86ba06d7ed6de28326eef76d0128327cd64c5566368fdc1a9f740ad8dd221419a5550fc8c14b33fa9f058b9fa4044775aaf5c66a999a7da4d4fdb8141c25ee5294ea6a54331d045f25c9a5f7f47960acbae20fa27ab5669c80eaf235a1d0b1c22b8d750a191c0f0c9b3561aaa4934847101343920d84f24334d3af05fede0e355911c7db8b8de3bf435907c855c3d7eeede4f148df830b43dd360b43692239ac10e566f138fb4b30fb1af0603cfcf0cd8adf4349a0d0b93bf89804e7c2e24ca7615e51af66dccfdb71a1204e2107abbee4259f2cac917fafe3b029baf13c4dde7923c47ee3fec248390203a384b9eb773c154540c5196bce1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;以传统的 RSA PKCS#1 PEM ASN.1 格式编码, 看起来更长一些:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN RSA PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIIEowIBAAKCAQEApwni+ErA4h6wyqAYz39pf3dOlvgRX8I1npz2Cx3Y1ASNl0zf
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hCK+9r48FisEuRb36iEz8OPk4O7hZIWb2cHg7wNXwUL09jO0rdSquGyPiJXNM&#x2F;v0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;4CTZo61r5iZ1cLSnLSw0NU4BOedK2mZaFqJhFJDeu44TGmz&#x2F;x+8l50JAgD3XGk&#x2F;N
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;lTyYgRGwqpu8TFcCT8XoxEYq2QScfxq+2FnGNFX6bVi1zDSj0yBv90uelsM226zw
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;zdGO0MZnls4AqwfzayTL4zQlI&#x2F;2CFajnf4noagjbkR8jdFk4je5kLa58smRKA+ce
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1cb6UHfPQJD6+lVgSLU2uHmoj2KGmPDHtCDEtwIDAQABAoIBABDyJyflUuLIa6Bt
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ftbeKDJu73bQEoMnzWTFVmNo&#x2F;cGp90CtjdIhQZpVUPyMFLM&#x2F;qfBYufpARHdar1xm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;qZmn2k1P24FBwl7lKU6mpUMx0EXyXJpff0eWCsuuIPonq1ZpyA6vI1odCxwiuNdQ
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;oZHA8MmzVhqqSTSEcQE0OSDYTyQzTTrwX+3g41WRHH24uN479DWQfIVcPX7u3k8U
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;jfgwtD3TYLQ2kiOawQ5WbxOPtLMPsa8GA8&#x2F;PDNit9DSaDQuTv4mATnwuJMp2FeUa
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;9m3M&#x2F;bcaEgTiEHq77kJZ8srJF&#x2F;r+OwKbrxPE3eeSPEfuP+wkg5AgOjhLnrdzwVRU
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;DFGWvOECgYEAyIk7F0S0AGn2aryhw9CihDfimigCxEmtIO5q7mnItCfeQwYPsX72
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1fLpJNgfPc9DDfhAZ2hLSsBlAPLUOa0Cuny9PCBWVuxi1WjLVaeZCV2bF11mAgW2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;fjLkAXT34IX+HZl60VoetSWq9ibfkJHeCAPnh&#x2F;yjdB3Vs+2wxNkU8m8CgYEA1Tzm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;mjJq7M6f+zMo7DpRwFazGMmrLKFmHiGBY6sEg7EmoeH2CkAQePIGQw&#x2F;Rk16gWJR6
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;DtUZ9666sjCH6&#x2F;79rx2xg+9AB76XTFFzIxOk9cm49cIosDMk4mogSfK0Zg8nVbyW
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;5nEb&#x2F;&#x2F;9JCrZ18g4lD3IrT5VJoF4MhfdBUjAS1jkCgYB+RDIpv3+bNx0KLgWpFwgN
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Omb667B6SW2ya4x227KdBPFkwD9HYosnQZDdOxvIvmUZObPLqJan1aaDR2Krgi1S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;oNJCNpZGmwbMGvTU1Pd+Nys9NfjR0ykKIx7&#x2F;b9fXzman2ojDovvs0W&#x2F;pF6bzD3V&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;FH5HWKLOrS5u4X3JJGqVDwKBgQCd953FwW&#x2F;gujld+EpqpdGGMTRAOrXqPC7QR3X5
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Beo0PPonlqOUeF07m9&#x2F;zsjZJfCJBPM0nS8sO54w7ESTAOYhpQBAPcx&#x2F;2HMUsrnIj
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;HBxqUOQKe6l0zo6WhJQi8&#x2F;+cU8GKDEmlsUlS3iWYIA9EICJoTOW08R04BjQ00jS7
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1A1AUQKBgHlHrV&#x2F;6S&#x2F;4hjvMp+30hX5DpZviUDiwcGOGasmIYXAgwXepJUq0xN6aa
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;lnT+ykLGSMMY&#x2F;LABQiNZALZQtwK35KTshnThK6zB4e9p8JUCVrFpssJ2NCrMY3SU
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;qw87K1W6engeDrmunkJ&#x2F;PmvSDLYeGiYWmEKQbLQchTxx1IEddXkK
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END RSA PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;rsa-mi-yao-sheng-cheng&quot;&gt;RSA 密钥生成&lt;&#x2F;h4&gt;
&lt;p&gt;略, 相关应用参见加密库的文档.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;rsa-jia-jie-mi&quot;&gt;RSA 加解密&lt;&#x2F;h4&gt;
&lt;p&gt;RSA 一次只能加密&#x2F;解密一个(大)整数, 一般采用 OAEP 对消息编码为一个个整数再逐个加解密.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;rsa-qian-ming-yu-yan-zheng&quot;&gt;RSA 签名与验证&lt;&#x2F;h4&gt;
&lt;p&gt;参见后文介绍数字签名算法.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ecc-gong-yao-mi-ma-xi-tong&quot;&gt;ECC 公钥密码系统&lt;&#x2F;h3&gt;
&lt;p&gt;ECC 椭圆曲线密码学于 1985 年被首次提出, 并于 2004 年开始被广泛应用. ECC 被认为是 RSA 的继任者, 新一代的非对称加密算法. 其最大的特点在于相同密码强度下, ECC 的密钥和签名的大小都要显著低于 RSA. 256 位的 ECC 密钥, 安全性与 3072 位的 RSA 密钥安全性相当. ECC 的密钥对生成、密钥交换与签名算法的速度都要比 RSA 快.&lt;&#x2F;p&gt;
&lt;p&gt;ECC 中的椭圆曲线可以以多种形式呈现, 这些形式被证明是&lt;strong&gt;同构&lt;&#x2F;strong&gt;的:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;魏尔施特拉斯 (Weierstrass) 形式: 这是最常见的椭圆曲线形式, 其方程为 $y^2=x^3+ax+b$&lt;&#x2F;p&gt;
&lt;p&gt;如: $y^2=x^3+7$ (secp256k1, 加密货币领域常用).&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;需要指出, NSA 推荐的 secp256r1 (NIST P-256, prime256v1, nist256p1) 和 secp256k1 名称只有一字之差, k 和 r 的不同. 其中 k 表示 Koblitz (ECC 发明人), 而 r 表示随机, 即参数是随机选取的, 但美国国家安全局 NSA 并没有公布随机数的挑选规则, 有质疑称 NSA 可能对随机数动过手脚, 让破解难度大幅降低.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;蒙哥马利 (Montgomery) 形式: 其方程为 $By^2=x^3+Ax^2+x$&lt;&#x2F;p&gt;
&lt;p&gt;如 Curve25519: $y^2 = x^3+486662x^2+x$.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;爱德华兹 (Edwards) 形式: 其方程为 $x^2+y^2=1+dx^2y^2$&lt;&#x2F;p&gt;
&lt;p&gt;如 Ed448: $x^2+y^2=1-39081x^2y^2$. (也可以写成蒙哥马利形式, 称 Curve448).&lt;&#x2F;p&gt;
&lt;p&gt;一般而言, Edwards 形式性能更好, 如 Ed25519 是基于 Curve25519 同构地 “扭曲” 而来, 专为计算速度优化而设计的.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;我们推荐使用 Curve52219 &#x2F; Ed25519 以及 Curve448 &#x2F; Ed448, 其性能和安全性均优于 NSA 推荐的 NIST P-256 (secp256r1), NIST P-384 (secp384r1), NIST P-521 (secp521r1) 等曲线. 参见 &lt;a href=&quot;https:&#x2F;&#x2F;safecurves.cr.yp.to&quot;&gt;https:&#x2F;&#x2F;safecurves.cr.yp.to&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;轶事一则&lt;&#x2F;p&gt;
&lt;p&gt;参考: &lt;a href=&quot;https:&#x2F;&#x2F;www.cnblogs.com&#x2F;greencollar&#x2F;p&#x2F;14363535.html&quot;&gt;https:&#x2F;&#x2F;www.cnblogs.com&#x2F;greencollar&#x2F;p&#x2F;14363535.html&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Daniel J. Bernstein 是世界著名的密码学家, 他在大学曾经开设过一门 UNIX 系统安全的课程给学生, 结果一学期下来, 发现了 UNIX 程序中的 91 个安全漏洞; 他早年在美国依然禁止出口加密算法时, 曾因为把自己设计的加密算法发布到网上遭到了美国政府的起诉, 他本人抗争六年, 最后美国政府撤销所有指控, 目前另一个非常火的高性能安全流密码 ChaCha20 也是出自 Bernstein 之手.&lt;&#x2F;p&gt;
&lt;p&gt;Curve25519 自 2006 年发表以来, 除了学术界无人问津. 但自 2013 年斯诺登曝光棱镜计划后, 该算法突然大火. Curve25519 安全性高, 不同于 NIST 推荐的 P-256 等曲线, Curve25519 的参数是公开透明的, 任何人都可以验证其安全性, 而 NSA 推荐的 NIST P-256 等曲线方程的系数使用了来历不明的随机种子, 在密码学上难以验证其安全性; 此外, Curve25519 的设计也充分考虑了缓存攻击等, 尽可能避免分支跳转, 在实践上安全性也非常高.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;shu-zi-qian-ming-suan-fa-digital-signature-algorithm-dsa&quot;&gt;数字签名算法 (digital signature algorithm, DSA)&lt;&#x2F;h2&gt;
&lt;p&gt;在密码学中, 数字签名为数字文档提供消息身份验证、完整性和不可否认性. 数字签名基于公钥密码系统, 消息签名由私有密钥执行, 消息验证由相应的公钥执行.&lt;&#x2F;p&gt;
&lt;p&gt;数字签名的过程本质还是加解密, 但是加解密的对象变成了消息的哈希值: 使用私钥签名 (加密哈希值), 公钥验证 (解密后比对实际的哈希值).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;rsa-shu-zi-qian-ming-suan-fa&quot;&gt;RSA 数字签名算法&lt;&#x2F;h3&gt;
&lt;p&gt;即使用 RSA 公钥密码系统的数字签名算法.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;刚刚好, 哈希值就是一个大整数.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;ecc-shu-zi-qian-ming-suan-fa&quot;&gt;ECC 数字签名算法&lt;&#x2F;h3&gt;
&lt;p&gt;即使用 ECC 公钥密码系统的数字签名算法. ECC 相对于 RSA, 在相同的安全强度下, 使用更短的密钥长度, 因此在计算和存储方面更为高效.&lt;&#x2F;p&gt;
&lt;p&gt;常用的 ECC 数字签名算法包括:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ECDSA: 在经典 Weierstrass 形式的有限域上使用加密椭圆曲线.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;出于特定实体影响, 目前的 ECC 证书一般使用 ECDSA 数字签名算法.&lt;&#x2F;p&gt;
&lt;p&gt;需要指出, DSA 是 digital signature algorithm (数字签名算法) 的缩写, 但也可能特指 FIPS 186 Digital Signature Standard (DSS) 中定义的数字签名算法. 一般地, ECDSA 特指使用了 NIST P-256, NIST P-384 或 NIST P-521 曲线的 NIST DSA 的椭圆曲线实现.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;EdDSA: 在 Edwards 形式的有限域上使用加密椭圆曲线.&lt;&#x2F;p&gt;
&lt;p&gt;常用变体包括 Ed25519 (使用 Ed25519 曲线, SHA512 哈希算法) 或 Ed448 (使用 Ed448 曲线, SHAKE256 哈希算法).&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;我们推荐使用 EdDSA 作为数字签名算法, 原因前面已经提到过.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;shu-zi-zheng-shu&quot;&gt;数字证书&lt;&#x2F;h2&gt;
&lt;p&gt;(本节内容大部分摘录自 &lt;a href=&quot;https:&#x2F;&#x2F;thiscute.world&#x2F;posts&#x2F;about-tls-cert&quot;&gt;https:&#x2F;&#x2F;thiscute.world&#x2F;posts&#x2F;about-tls-cert&lt;&#x2F;a&gt;) 的总结, 向原作者表示感谢!)&lt;&#x2F;p&gt;
&lt;p&gt;我们已经熟悉了对称密码与非对称密码两个密码学体系:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;对称密码算法: 计算速度快、安全强度高, 但是缺乏安全保存、管理乃至交换密钥的手段 (PSK 一旦泄露, 则所有使用该密钥加密的通信均不再安全).&lt;&#x2F;li&gt;
&lt;li&gt;非对称密码算法: 计算速度慢, 但是它给出了安全的密钥交换算法 DHE&#x2F;ECDHE, 且公钥是可公开的, 这降低了密钥的保存与管理难度.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;但是问题来了:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;公钥该如何分发? 比如 Alice 跟 Bob 交换公钥时, 如何确定收到的确实是对方的公钥, 也就是说如何确认公钥的真实性、完整性、认证其来源身份? (DH 是匿名的无认证的, 无法防止中间人攻击)&lt;&#x2F;li&gt;
&lt;li&gt;如果 Alice 的私钥泄漏了, 她该如何作废自己旧的公钥?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;数字证书与公钥基础架构就是为了解决上述问题而设计的.&lt;&#x2F;p&gt;
&lt;p&gt;首先简单介绍下 PKI (Public Key Infrastructure, 公钥基础架构), 它是一组由硬件、软件、参与者、管理政策与流程组成的基础架构, 其目的在于创造、管理、分配、使用、存储以及撤销数字证书. PKI 是一个总称, 而并非指单独的某一个规范或标准, 因此显然数字证书的规范 (X.509)、存储格式等都是 PKI 的一部分.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;gong-yao-zheng-shu&quot;&gt;公钥证书&lt;&#x2F;h3&gt;
&lt;p&gt;前面我们介绍了公钥密码系统存在的一个问题是 “在分发公钥时, 难以确认公钥的真实性、完整性及其来源身份”. PKI 通过数字证书和证书认证机构来解决这个问题.&lt;&#x2F;p&gt;
&lt;p&gt;数字证书指的其实就是公钥证书 (也可直接简称为证书). 在现代网络通讯中通行的公钥证书格式标准名为 X.509 v3, 由 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5280&quot;&gt;RFC5280&lt;&#x2F;a&gt; 定义, 被广泛应用在 TLS 等众多加密通讯协议中.&lt;&#x2F;p&gt;
&lt;p&gt;一个公钥证书内容应当包括:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;证书&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;序列号 (Serial Number): 用以识别每一张证书, 在作废证书的时候会用到它.&lt;&#x2F;li&gt;
&lt;li&gt;版本: 证书的规范版本.&lt;&#x2F;li&gt;
&lt;li&gt;公钥 (Public Key): 证书所包含的公钥.&lt;&#x2F;li&gt;
&lt;li&gt;公钥指纹: 即公钥的 Hash 值, 当前大部分证书都使用 SHA256 计算此指纹.&lt;&#x2F;li&gt;
&lt;li&gt;公钥用途 (Key Usage + Extended Key Usage): 记录了此证书可用于哪些用途, 如数字签名、身份认证等.&lt;&#x2F;li&gt;
&lt;li&gt;主体 (Subject): 即姓名、组织、邮箱、地址等证书拥有者的个人信息.&lt;&#x2F;li&gt;
&lt;li&gt;证书有效期的开始时间、结束时间 (Not Before + Not After): 为了确保安全性, 每个证书都会记录一个自身的有效期.&lt;&#x2F;li&gt;
&lt;li&gt;签发者 (Issuer): 签发此证书的实体.&lt;&#x2F;li&gt;
&lt;li&gt;其他拓展信息.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;数字签名 (Signature): 对整个证书计算数字签名, 来确保这些数据的真实性、完整性, 确保证书未被恶意篡改&#x2F;伪造.&lt;&#x2F;p&gt;
&lt;p&gt;此数字签名由证书签发者使用其私钥+证书内容计算得出.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;数字签名算法 (Signature Algorithm): 证书所使用的签名算法, 常用的有 RSA-SHA-256 与 ECDSA-SHA-256.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;每个证书都有唯一的 ID, 这样在私钥泄漏的情况下, 我们可以通过公钥基础设施的 OCSP (Online Certificate Status Protocol) 协议吊销某个证书. 吊销证书的操作还是比较罕见的, 毕竟私钥泄漏并不容易遇到, 因此这里就略过不提了, 有需要的可以自行搜索.&lt;&#x2F;p&gt;
&lt;p&gt;感兴趣的, 可以使用浏览器查看证书详情的功能查看一下自己常用网站的证书, 了解一下证书的具体内容, 这里是旧版 Firefox 查看谷歌网站的示例:&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;.&#x2F;google-cert-content.webp&quot; alt=&quot;Google&#x27;s Cert&quot; style=&quot;max-width: 50%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;h3 id=&quot;zheng-shu-qian-fa-ji-gou-ca-yu-zheng-shu-lian&quot;&gt;证书签发机构 (CA) 与证书链&lt;&#x2F;h3&gt;
&lt;p&gt;前面介绍证书内容时, 提到了签发者会使用签发者私钥和证书内容生成数字签名, 显然需要使用签发者公钥来验证被签发证书中的签名. 但是问题又来了, 签发者的公钥该如何分发? 这不又回到了最初的问题吗?&lt;&#x2F;p&gt;
&lt;p&gt;为此, PKI 引入了一个可信第三方 (Trusted third party, TTP) 来解决这个问题.&lt;&#x2F;p&gt;
&lt;p&gt;在 Alice 与 Bob 的案例中, 就是说还有个可信第三方 Eve, 他使用自己的私钥为自己的公钥证书签了名, 生成了一个&lt;em&gt;自签名&lt;&#x2F;em&gt;证书, 并且已经提前将这个自签名证书分发(比如当面交付、预置在操作系统中等手段)给了 Alice 跟 Bob.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现在 Alice 首先使用自己的公钥以及个人信息制作了自己的公钥证书, 但是这个证书还缺乏一个 Issuer 以及数字签名.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Alice 找到 Eve, 将这个 CSR 文件提交给 Eve, 希望 Eve 为自己的证书签名. 此时, 我们称之为提交&lt;strong&gt;CSR&lt;&#x2F;strong&gt; (Certificate Signing Request, CSR)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Eve 验证了 Alice 的身份后, 再使用这个 CSR 签发出完整的证书文件 (Issuer 就填 Eve, 然后 Eve 使用自己的私钥计算出证书的数字签名, 这样就得到了最终的证书), 交付给 Alice.&lt;&#x2F;p&gt;
&lt;p&gt;Eve 曾经 “跨越千里之遥”, 将自己的公钥证书分发给了 Bob, 所以在给 Alice 签发证书时, 他显然可能会要求 Alice 给付&lt;em&gt;签名费&lt;&#x2F;em&gt;. 目前许多证书机构就是靠这个赚钱的, 当然也有非盈利的证书颁发机构, 如 ZeroSSL 等 (真的是做慈善么).&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;现在 Alice 再将经 Eve 签名的证书发送给 Bob&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Bob 收到证书后, 看到 Issuer 是 Eve, 于是找出以前 Eve 给他的&lt;em&gt;自签名&lt;&#x2F;em&gt;证书, 然后使用其中的公钥验证收到的证书.&lt;&#x2F;p&gt;
&lt;p&gt;如果验证成功, 就说明证书的内容是经过 Eve 认证的. 如果 Eve 没老糊涂了, 那这个证书应该确实就是 Alice 的. 否则, 这是某个攻击者伪造的证书.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;在现实世界中, Eve 这个角色被称作 CA (Certification Authority, 证书认证机构), 全世界只有几十家这样的权威机构, 它们&lt;em&gt;都通过了各大软件厂商的严格审核&lt;&#x2F;em&gt;, 从而将根证书(CA 证书)直接内置于主流操作系统与浏览器中, 也就是说早就提前分发给了因特网世界的几乎所有用户. 由于许多操作系统或软件的更新迭代缓慢, 根证书的有效期通常都在十年以上.&lt;&#x2F;p&gt;
&lt;p&gt;但是, 如果 CA 机构直接使用自己的私钥处理各种证书签名请求, 这将是非常危险的. 因为全世界有海量的 HTTPS 网站, 也就是说有海量的证书需求, 可一共才几十家 CA 机构. 频繁的动用私钥会产生私钥泄漏的风险, 如果这个私钥泄漏了, 那将直接影响海量网站的安全性.&lt;&#x2F;p&gt;
&lt;p&gt;PKI 架构使用数字证书链的机制来解决这个问题:&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;.&#x2F;chain-of-trust.webp&quot; alt=&quot;Chain of trust&quot; style=&quot;max-width: 50%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CA 机构首先生成自己的根证书与私钥, 并使用私钥给根证书签名&lt;&#x2F;p&gt;
&lt;p&gt;因为私钥跟证书本身就是一对, 因此根证书也被称作自签名证书.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;CA 交付根证书给各大软硬件厂商, 内置在主流的操作系统与浏览器中.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;CA 使用私钥签发一些所谓的中间证书, 之后就把私钥雪藏了, 非必要不会再拿出来使用.&lt;&#x2F;p&gt;
&lt;p&gt;根证书的私钥通常离线存储在安全地点; 中间证书的有效期通常会比根证书短一些; 部分中间证书会被作为备份使用, 平常不会启用.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;CA 机构使用这些中间证书的私钥, 为用户提交的所有 CSR 请求签名.&lt;&#x2F;li&gt;
&lt;li&gt;CA 机构也可以使用中间证书的私钥, 为子一级 CA 机构签发中间证书, 形成更长的证书链.&lt;&#x2F;li&gt;
&lt;li&gt;不同 CA 机构也可能执行交叉签名, 共同为对方的中间证书签名. 客户端只要安装有其中任何一个根证书, 就能验证该中间证书.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;上述这个全球互联网上, 由证书认证机构、操作系统与浏览器内置的根证书、TLS 加密认证协议、OCSP 证书吊销协议等等组成的架构, 我们可以称它为 &lt;strong&gt;Web PKI&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Web PKI 通常是&lt;em&gt;可信&lt;&#x2F;em&gt;的, 但是并不意味着它们&lt;em&gt;可靠&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以说, 谁&lt;em&gt;掌握&lt;&#x2F;em&gt;了 CA, 谁就能对如今互联网安全所仰赖的 TLS 通信执行 MitM.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在更高安全性要求的场景中, 搭建私有 PKI 架构是有必要的, 如支付场景下银行 APP 等要求安装特定数字证书, 即安装了一个由支付机构控制的私有 CA 证书, 或者以 U 盾形式存在的硬件介质的证书; 又或者内部通讯系统, 也会使用私有 CA 证书来签发内部服务器的证书. 这种情况下, 私有 CA 证书需要被提前手动安装在客户端设备上, 否则会提示不受信任的证书错误.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tls-zheng-shu-zhu-ti-subject&quot;&gt;TLS 证书主体 (Subject)&lt;&#x2F;h3&gt;
&lt;p&gt;TLS 证书的主体字段包含了证书持有者的身份信息, 我们主要关注其中的通用名称 (Common Name, CN) 和主体备用名称 (Subject Alternative Name, SAN).&lt;&#x2F;p&gt;
&lt;p&gt;CN 是证书的 “主域名”, SAN 则是该证书所覆盖的所有域名或 IP 地址. 由于 CN 字段只能有一个, 无法满足单证书保护多个子域的需求, 实际上已被弃用, 我们通过 SAN 字段来指定证书所覆盖的域名&#x2F;IP 地址.&lt;&#x2F;p&gt;
&lt;p&gt;一般地, 我们使用的证书均为域名证书, 但也有 IP 证书 (只能是公网 IP, 难以自动化申请, 而且收费一般不便宜, 目前应该只有 ZeroSSL 提供了&lt;em&gt;免费&lt;&#x2F;em&gt;的 IP 证书), 如著名的 &lt;code&gt;https:&#x2F;&#x2F;1.1.1.1&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;SAN 支持配置泛域名, 即 &lt;code&gt;*.example.com&lt;&#x2F;code&gt; 的形式, 但不支持多级泛域名, 即 &lt;code&gt;*.*.example.com&lt;&#x2F;code&gt; 的形式; &lt;code&gt;*.example.com&lt;&#x2F;code&gt; 只能匹配到同级子域名 &lt;code&gt;a.example.com&lt;&#x2F;code&gt;, 不能匹配 &lt;code&gt;a.b.example.com&lt;&#x2F;code&gt; 等.&lt;&#x2F;p&gt;
&lt;p&gt;SAN 可以设置很多个, 如:&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;.&#x2F;1.1.1.1-cert.webp&quot; alt=&quot;1.1.1.1 Cert&quot; style=&quot;max-width: 50%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;bu-chong-nei-rong-zheng-shu-yi-ji-gong-si-yao-de-wen-ben-bian-ma&quot;&gt;补充内容: 证书以及公私钥的(文本)编码&lt;&#x2F;h2&gt;
&lt;p&gt;由于历史原因, 实践上对证书以及公私钥的存储格式可谓五花八门, 这里参考了 RFC 7468 以及 &lt;a href=&quot;https:&#x2F;&#x2F;thiscute.world&#x2F;posts&#x2F;about-tls-cert&#x2F;#pki&quot;&gt;https:&#x2F;&#x2F;thiscute.world&#x2F;posts&#x2F;about-tls-cert&#x2F;#pki&lt;&#x2F;a&gt;, 试图做一个实用意义上比较全面的总结.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ji-ben-gai-nian&quot;&gt;基本概念&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;X.509: PKI 标准, 规定了证书应该包含哪些信息 (但是未定义证书该如何存储). PKIX 即 “Public Key Infrastructure X.509”.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;ASN.1 (Abstract Syntax Notation One, 抽象语法标记法第 1 部分): 一种用于描述数据结构的标准, 广泛应用于通信协议和加密标准中. ASN.1 定义了数据的语法和语义, 使得不同系统之间能够理解和交换数据. 被应用于 X.509 证书、PKCS 标准等.&lt;&#x2F;p&gt;
&lt;p&gt;感兴趣的, 可以参考 &lt;a href=&quot;https:&#x2F;&#x2F;letsencrypt.org&#x2F;zh-cn&#x2F;docs&#x2F;a-warm-welcome-to-asn1-and-der&#x2F;&quot;&gt;https:&#x2F;&#x2F;letsencrypt.org&#x2F;zh-cn&#x2F;docs&#x2F;a-warm-welcome-to-asn1-and-der&#x2F;&lt;&#x2F;a&gt; 的总结.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;DER (Distinguished Encoding Rules, 可辨别编码规则): ASN.1 的一种序列化规则, 用于将 ASN.1 数据结构编码为二进制格式. 定义于 X.690 标准中.&lt;&#x2F;p&gt;
&lt;p&gt;DER 编码的证书&lt;em&gt;通常&lt;&#x2F;em&gt;使用 &lt;code&gt;.cer&lt;&#x2F;code&gt; 或 &lt;code&gt;.der&lt;&#x2F;code&gt; 作为文件扩展名, 但也有使用 &lt;code&gt;.crt&lt;&#x2F;code&gt; 的 (RFC 7468 要求使用此拓展名保存文本格式), 更有使用 &lt;code&gt;.cer&lt;&#x2F;code&gt; 保存 PEM 格式的证书的. 在实践上, 尝试以纯文本形式打开看看就知道了.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;PEM (Privacy-Enhanced Mail, 隐私增强邮件): “隐私增强邮件” 自身已鲜少使用, 但其定义的基于文本的编码格式, 如今被广泛应用于证书(链)和公私钥的文本编码中. PEM 格式可以理解为对 DER 编码的证书或密钥进行 Base64 编码并添加封装边界.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;PKCS (Public Key Cryptography Standards, 公钥密码学标准).&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;pem-ge-shi&quot;&gt;PEM 格式&lt;&#x2F;h3&gt;
&lt;p&gt;(以下来自 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt;, 但是由于历史因素, 实践中 PEM 格式的使用也存在不完全符合 RFC 7468 的定义的情况)&lt;&#x2F;p&gt;
&lt;p&gt;文本编码以一行包含 “&lt;code&gt;-----BEGIN &lt;&#x2F;code&gt;”、标签和 “&lt;code&gt;-----&lt;&#x2F;code&gt;” 开始, 并以一行包含 “&lt;code&gt;-----END &lt;&#x2F;code&gt;”、标签和 “&lt;code&gt;-----&lt;&#x2F;code&gt;” 结束. 在这些行 (或称“封装边界“) 之间, 是根据 RFC4648 第 4 节进行 Base64 编码的数据 (PEM &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc1421&quot;&gt;RFC1421&lt;&#x2F;a&gt; 称这部分数据为 “封装文本部分”). 封装边界之前允许存在数据, 解析器在处理此类数据时&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;出错. 此外, 解析器&lt;strong&gt;必须&lt;&#x2F;strong&gt;忽略空白字符及其他非 Base64 字符, 且&lt;strong&gt;必须&lt;&#x2F;strong&gt;支持不同的换行规范.&lt;&#x2F;p&gt;
&lt;p&gt;编码数据的类型由 “&lt;code&gt;-----BEGIN &lt;&#x2F;code&gt;” (前置封装边界) 行中的类型标签决定. 例如, “&lt;code&gt;-----BEGIN CERTIFICATE-----&lt;&#x2F;code&gt;” 表示内容是一个 PKIX 证书. 生成器&lt;strong&gt;必须&lt;&#x2F;strong&gt;在 “&lt;code&gt;-----END &lt;&#x2F;code&gt;” (后置封装边界) 行使用与对应 “&lt;code&gt;-----BEGIN &lt;&#x2F;code&gt;” 行相同的标签. 标签由区分大小写的、全大写的零个或多个字符组成; 不得包含连续空格或连字符, 首尾也不得出现空格或连字符. 若标签不匹配, 解析器&lt;strong&gt;可以&lt;&#x2F;strong&gt;选择忽略后置封装边界中的标签而非报错: 现存实现中部分要求标签匹配, 部分不作要求.&lt;&#x2F;p&gt;
&lt;p&gt;“BEGIN” 或 “END” 与标签之间必须严格用一个空格字符 (SP) 分隔. 封装边界两端的连字符 (即短横线 “-”) 必须正好五个, 不能多也不能少.&lt;&#x2F;p&gt;
&lt;p&gt;标签类型暗示编码数据遵循特定语法. 解析器&lt;strong&gt;必须&lt;&#x2F;strong&gt;能够优雅处理不符合规范的数据. 但需注意本文档发布前的解析器或生成器行为并不统一. 合法的解析器&lt;strong&gt;可能&lt;&#x2F;strong&gt;将内容解释为其他标签类型, 但应注意安全考量章节讨论的潜在风险. 本文档描述的标签标识的容器格式不与特定加密算法绑定, 这符合算法敏捷性原则. 这些格式采用 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5280&quot;&gt;RFC5280&lt;&#x2F;a&gt; 第 4.1.1.2 节所述的 ASN.1 AlgorithmIdentifier 结构.&lt;&#x2F;p&gt;
&lt;p&gt;与传统 PEM 编码 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc1421&quot;&gt;RFC1421&lt;&#x2F;a&gt;、OpenPGP ASCII armor 及 OpenSSH 密钥文件格式不同, 文本编码&lt;em&gt;不&lt;&#x2F;em&gt;定义也不允许在数据旁编码头信息. 前置封装边界与 Base64 数据之间允许存在空白, 但生成器&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;产生此类空白 (保留此空白区域是对 PEM “封装头部分” 定义的沿袭).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pkcs-1-biao-zhun&quot;&gt;PKCS #1 标准&lt;&#x2F;h3&gt;
&lt;p&gt;专用于编码 RSA 公私钥, PEM 标签为 “RSA PUBLIC KEY” 和 “RSA PRIVATE KEY”.&lt;&#x2F;p&gt;
&lt;p&gt;示例:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN RSA PUBLIC KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIIBCgKCAQEApwni+ErA4h6wyqAYz39pf3dOlvgRX8I1npz2Cx3Y1ASNl0zfhCK+
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;9r48FisEuRb36iEz8OPk4O7hZIWb2cHg7wNXwUL09jO0rdSquGyPiJXNM&#x2F;v04CTZ
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;o61r5iZ1cLSnLSw0NU4BOedK2mZaFqJhFJDeu44TGmz&#x2F;x+8l50JAgD3XGk&#x2F;NlTyY
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gRGwqpu8TFcCT8XoxEYq2QScfxq+2FnGNFX6bVi1zDSj0yBv90uelsM226zwzdGO
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;0MZnls4AqwfzayTL4zQlI&#x2F;2CFajnf4noagjbkR8jdFk4je5kLa58smRKA+ce1cb6
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;UHfPQJD6+lVgSLU2uHmoj2KGmPDHtCDEtwIDAQAB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END RSA PUBLIC KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN RSA PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIIEowIBAAKCAQEApwni+ErA4h6wyqAYz39pf3dOlvgRX8I1npz2Cx3Y1ASNl0zf
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hCK+9r48FisEuRb36iEz8OPk4O7hZIWb2cHg7wNXwUL09jO0rdSquGyPiJXNM&#x2F;v0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;4CTZo61r5iZ1cLSnLSw0NU4BOedK2mZaFqJhFJDeu44TGmz&#x2F;x+8l50JAgD3XGk&#x2F;N
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;lTyYgRGwqpu8TFcCT8XoxEYq2QScfxq+2FnGNFX6bVi1zDSj0yBv90uelsM226zw
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;zdGO0MZnls4AqwfzayTL4zQlI&#x2F;2CFajnf4noagjbkR8jdFk4je5kLa58smRKA+ce
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1cb6UHfPQJD6+lVgSLU2uHmoj2KGmPDHtCDEtwIDAQABAoIBABDyJyflUuLIa6Bt
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ftbeKDJu73bQEoMnzWTFVmNo&#x2F;cGp90CtjdIhQZpVUPyMFLM&#x2F;qfBYufpARHdar1xm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;qZmn2k1P24FBwl7lKU6mpUMx0EXyXJpff0eWCsuuIPonq1ZpyA6vI1odCxwiuNdQ
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;oZHA8MmzVhqqSTSEcQE0OSDYTyQzTTrwX+3g41WRHH24uN479DWQfIVcPX7u3k8U
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;jfgwtD3TYLQ2kiOawQ5WbxOPtLMPsa8GA8&#x2F;PDNit9DSaDQuTv4mATnwuJMp2FeUa
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;9m3M&#x2F;bcaEgTiEHq77kJZ8srJF&#x2F;r+OwKbrxPE3eeSPEfuP+wkg5AgOjhLnrdzwVRU
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;DFGWvOECgYEAyIk7F0S0AGn2aryhw9CihDfimigCxEmtIO5q7mnItCfeQwYPsX72
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1fLpJNgfPc9DDfhAZ2hLSsBlAPLUOa0Cuny9PCBWVuxi1WjLVaeZCV2bF11mAgW2
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;fjLkAXT34IX+HZl60VoetSWq9ibfkJHeCAPnh&#x2F;yjdB3Vs+2wxNkU8m8CgYEA1Tzm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;mjJq7M6f+zMo7DpRwFazGMmrLKFmHiGBY6sEg7EmoeH2CkAQePIGQw&#x2F;Rk16gWJR6
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;DtUZ9666sjCH6&#x2F;79rx2xg+9AB76XTFFzIxOk9cm49cIosDMk4mogSfK0Zg8nVbyW
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;5nEb&#x2F;&#x2F;9JCrZ18g4lD3IrT5VJoF4MhfdBUjAS1jkCgYB+RDIpv3+bNx0KLgWpFwgN
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Omb667B6SW2ya4x227KdBPFkwD9HYosnQZDdOxvIvmUZObPLqJan1aaDR2Krgi1S
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;oNJCNpZGmwbMGvTU1Pd+Nys9NfjR0ykKIx7&#x2F;b9fXzman2ojDovvs0W&#x2F;pF6bzD3V&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;FH5HWKLOrS5u4X3JJGqVDwKBgQCd953FwW&#x2F;gujld+EpqpdGGMTRAOrXqPC7QR3X5
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Beo0PPonlqOUeF07m9&#x2F;zsjZJfCJBPM0nS8sO54w7ESTAOYhpQBAPcx&#x2F;2HMUsrnIj
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;HBxqUOQKe6l0zo6WhJQi8&#x2F;+cU8GKDEmlsUlS3iWYIA9EICJoTOW08R04BjQ00jS7
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1A1AUQKBgHlHrV&#x2F;6S&#x2F;4hjvMp+30hX5DpZviUDiwcGOGasmIYXAgwXepJUq0xN6aa
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;lnT+ykLGSMMY&#x2F;LABQiNZALZQtwK35KTshnThK6zB4e9p8JUCVrFpssJ2NCrMY3SU
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;qw87K1W6engeDrmunkJ&#x2F;PmvSDLYeGiYWmEKQbLQchTxx1IEddXkK
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END RSA PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如今已经统一转向使用 PKCS #8 格式保存私钥, 公钥则按 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt; Section 13 所述的格式保存.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ansi-x9-62-itu-t-x-894-biao-zhun&quot;&gt;ANSI X9.62 (ITU-T X.894) 标准&lt;&#x2F;h3&gt;
&lt;p&gt;类似 PKCS #1 标准, 但此标准仅用于编码 ECC 私钥, PEM 标签为 “EC PRIVATE KEY”.&lt;&#x2F;p&gt;
&lt;p&gt;示例 (使用 &lt;code&gt;openssl ecparam -name secp384r1 -genkey -noout -out privkey.pem&lt;&#x2F;code&gt; 生成):&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN EC PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIGkAgEBBDDhhKJcKC7jnRbKLYKAzY1f&#x2F;9aC9JGGuqqo2d4FejHSi&#x2F;TOZiLf3JhP
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;xAb+IJdQYEqgBwYFK4EEACKhZANiAARPyjgUZlw+IWfP&#x2F;+PD8m1HZRWbEvUB+ai9
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;pTV63SI7MWw5OzM3qKwTUNP+NK3xFt14A5lJwFMzl5AB7QhJYarW5ra3e2yhP8Vn
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;oC9E7Om31FLwVX+AHTM4Rg0ZQQoqH0c=
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END EC PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;类似地, 如今已经统一转向使用 PKCS #8 格式保存私钥.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pkcs-7-cms-biao-zhun&quot;&gt;PKCS #7 &#x2F; CMS 标准&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;PKCS #7 &#x2F; CMS, 是一个多用途的证书描述格式. 它包含一个数据填充规则, 这个填充规则常被用在需要数据填充的分组加密、数字签名等算法中.&lt;&#x2F;p&gt;
&lt;p&gt;由于历史原因, 有使用 PKCS #7 标准保存证书的, 后缀通常使用 &lt;code&gt;.p7b&lt;&#x2F;code&gt; 或者 &lt;code&gt;.p7c&lt;&#x2F;code&gt;, 可以使用 DER 或 PEM 格式保存:&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
 &lt;img src=&quot;.&#x2F;p7b.png&quot; alt=&quot;.p7b in real world&quot; style=&quot;max-width: 50%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;以下内容摘抄自 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt; Section 8 和 Section 9.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;PKCS #7 加密消息语法结构使用 “PKCS7” 标签进行编码. 编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 ContentInfo 结构, 如 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc2315&quot;&gt;RFC2315&lt;&#x2F;a&gt; 所述.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN PKCS7-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIHjBgsqhkiG9w0BCRABF6CB0zCB0AIBADFho18CAQCgGwYJKoZIhvcNAQUMMA4E
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;CLfrI6dr0gUWAgITiDAjBgsqhkiG9w0BCRADCTAUBggqhkiG9w0DBwQIZpECRWtz
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;u5kEGDCjerXY8odQ7EEEromZJvAurk&#x2F;j81IrozBSBgkqhkiG9w0BBwEwMwYLKoZI
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hvcNAQkQAw8wJDAUBggqhkiG9w0DBwQI0tCBcU09nxEwDAYIKwYBBQUIAQIFAIAQ
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;OsYGYUFdAH0RNc1p4VbKEAQUM2Xo8PMHBoYdqEcsbTodlCFAZH4=
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END PKCS7-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;标签 “CERTIFICATE CHAIN” 曾用于表示仅包含证书列表的退化 (degenerated) PKCS #7 结构 (参见 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc2315&quot;&gt;RFC2315&lt;&#x2F;a&gt; 第 9 节). 多个现代工具已不再支持此标签. 生成器&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;生成 “CERTIFICATE CHAIN” 标签. 解析器&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;将 “CERTIFICATE CHAIN” 视为等同于 “PKCS7”. PKCS #7 是一个已被 CMS &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5652&quot;&gt;RFC5652&lt;&#x2F;a&gt; 长期取代的旧规范. 在 CMS 可作为替代方案时, 实现&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;生成 PKCS #7.&lt;&#x2F;p&gt;
&lt;p&gt;CMS 结构使用 “CMS” 标签进行编码. 编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 ContentInfo 结构, 如 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5652&quot;&gt;RFC5652&lt;&#x2F;a&gt; 所述.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN CMS-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIGDBgsqhkiG9w0BCRABCaB0MHICAQAwDQYLKoZIhvcNAQkQAwgwXgYJKoZIhvcN
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AQcBoFEET3icc87PK0nNK9ENqSxItVIoSa0o0S&#x2F;ISczMs1ZIzkgsKk4tsQ0N1nUM
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;dvb05OXi5XLPLEtViMwvLVLwSE0sKlFIVHAqSk3MBkkBAJv0Fx0=
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END CMS-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;CMS 是 IETF 对 PKCS #7 的继承. &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5652&quot;&gt;RFC5652&lt;&#x2F;a&gt; 第 1.1.1 节描述了自 PKCS #7 v1.5 以来的变更. 实现方案在可选时&lt;strong&gt;必须&lt;&#x2F;strong&gt;生成 CMS 格式, 以促进互操作性和前向兼容性.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pkcs-8-biao-zhun&quot;&gt;PKCS #8 标准&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;此标准专用于编码私钥, 且允许对私钥加密, 是现代非对称密钥存储的事实标准.&lt;&#x2F;p&gt;
&lt;p&gt;私钥建议使用 &lt;code&gt;.key&lt;&#x2F;code&gt; 作为文件扩展名, 但也有相当一部分人使用通用拓展名 &lt;code&gt;.pem&lt;&#x2F;code&gt; 的.&lt;&#x2F;p&gt;
&lt;p&gt;可以使用 &lt;code&gt;openssl pkcs8 -topk8 -inform PEM -in private-key.pem -outform PEM -nocrypt -out private-key-pkcs8.pem&lt;&#x2F;code&gt; 来将传统私钥转换为 PKCS #8 格式.&lt;&#x2F;p&gt;
&lt;p&gt;以下内容摘抄自 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt; Section 10 和 Section 11.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;未加密的 PKCS #8 私钥信息语法结构 (PrivateKeyInfo, 现更名为非对称密钥包 OneAsymmetricKey) 使用 “PRIVATE KEY” 标签编码. 编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 PrivateKeyInfo 结构, 如 PKCS #8 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5208&quot;&gt;RFC5208&lt;&#x2F;a&gt; 所述, 或 OneAsymmetricKey 结构, 如 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5958&quot;&gt;RFC5958&lt;&#x2F;a&gt; 所述. 两者语义相同, 可通过版本号区分.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIGHTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQglQanBRiYVPX7F2Rd
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;4CqyjEN0K4qfHw4tM&#x2F;yMIh21wamhRANCAARsxaI4jT1b8zbDlFziuLngPcExbYzz
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ePAHUmgWL&#x2F;ZCeqlODF&#x2F;l&#x2F;XvimkjaWC2huu1OSWB9EKuG+mKFY2Y5k+vF
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;加密的 PKCS #8 私钥信息语法结构 (EncryptedPrivateKeyInfo, &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5958&quot;&gt;RFC5958&lt;&#x2F;a&gt; 中名称相同) 使用 “ENCRYPTED PRIVATE KEY” 标签编码. 编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 PrivateKeyInfo 结构, 如 PKCS #8 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5208&quot;&gt;RFC5208&lt;&#x2F;a&gt; 和 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5958&quot;&gt;RFC5958&lt;&#x2F;a&gt; 所述.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN ENCRYPTED PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIHNMEAGCSqGSIb3DQEFDTAzMBsGCSqGSIb3DQEFDDAOBAghhICA6T&#x2F;51QICCAAw
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;FAYIKoZIhvcNAwcECBCxDgvI59i9BIGIY3CAqlMNBgaSI5QiiWVNJ3IpfLnEiEsW
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Z0JIoHyRmKK&#x2F;+cr9QPLnzxImm0TR9s4JrG3CilzTWvb0jIvbG3hu0zyFPraoMkap
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;8eRzWsIvC5SVel+CSjoS2mVS87cyjlD+txrmrXOVYDE+eTgMLbrLmsWh3QkCTRtF
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;QC7k0NNzUHTV9yGDwfqMbw==
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END ENCRYPTED PRIVATE KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;pkcs-12-biao-zhun&quot;&gt;PKCS #12 标准&lt;&#x2F;h3&gt;
&lt;p&gt;PKCS #12 是一个归档文件格式, 不是文本编码, 但这里也顺带说一下了. 主要用于存储多个私钥及相关的 X.509 证书, 常用拓展名为 &lt;code&gt;.p12&lt;&#x2F;code&gt;, &lt;code&gt;.pfx&lt;&#x2F;code&gt;. 因为保存了私钥, 为了安全性它通常是加密的, 需要解密后才能使用.&lt;&#x2F;p&gt;
&lt;p&gt;安卓开发者应该比较熟悉, APK 签名证书通常就是使用 PKCS #12 格式存储的, 拓展名为 &lt;code&gt;.keystore&lt;&#x2F;code&gt; 或者 &lt;code&gt;.jks&lt;&#x2F;code&gt; 而已.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;gong-yao-public-key-pem-bian-ma-biao-zhun-rfc-7468-section-13&quot;&gt;公钥 (Public Key) PEM 编码标准 (RFC 7468 Section 13)&lt;&#x2F;h3&gt;
&lt;p&gt;公钥信息结构 (SubjectPublicKeyInfo) 使用 “PUBLIC KEY” 标签进行编码. 编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 SubjectPublicKeyInfo 结构, 如 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5280&quot;&gt;RFC5280&lt;&#x2F;a&gt; 第 4.1.2.7 节所述.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN PUBLIC KEY-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEn1LlwLN&#x2F;KBYQRVH6HfIMTzfEqJOVztLe
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;kLchp2hi78cCaMY81FBlYs8J9l7krc+M4aBeCGYFjba+hiXttJWPL7ydlE+5UG4U
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Nkn3Eos8EiZByi9DVsyfy9eejh+8AXgp
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END PUBLIC KEY-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;zheng-shu-certificate-pem-bian-ma-biao-zhun-rfc-7468-section-5&quot;&gt;证书 (Certificate) PEM 编码标准 (RFC 7468 Section 5)&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这里的 “证书”, 指互联网 X.509 公钥基础设施证书及证书吊销列表 (Certificate Revocation Lists, CRL) 规范 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5280&quot;&gt;RFC5280&lt;&#x2F;a&gt; 中的 “证书”.&lt;&#x2F;p&gt;
&lt;p&gt;以下内容摘抄自 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt; Section 5.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;5.1. 编码&lt;&#x2F;p&gt;
&lt;p&gt;公钥证书使用 “CERTIFICATE” 标签进行编码.&lt;&#x2F;p&gt;
&lt;p&gt;编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 Certificate 结构, 如 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5280&quot;&gt;RFC5280&lt;&#x2F;a&gt; 第 4 节所述.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN CERTIFICATE-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj&#x2F;Niyv2394BWnW4X
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;uQ4RTEiywK87WRcWMGgJB5kX&#x2F;t2no0MwQTAPBgNVHRMBAf8EBTADAQH&#x2F;MA8GA1Ud
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;DwEB&#x2F;wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;l4wOuDwKQa+upc8GftXE2C&#x2F;&#x2F;4mKANBC6It01gUaTIpo=
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END CERTIFICATE-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;以上为示例.&lt;&#x2F;p&gt;
&lt;p&gt;历史上曾使用过 “X509 CERTIFICATE” 标签, 以及较少使用的 “X.509 CERTIFICATE” 标签. 符合本文件的生成器&lt;strong&gt;必须&lt;&#x2F;strong&gt;生成 “CERTIFICATE” 标签, &lt;strong&gt;绝不能&lt;&#x2F;strong&gt;生成 “X509 CERTIFICATE” 或 “X.509 CERTIFICATE” 标签. 解析器&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;将 “X509 CERTIFICATE” 或 “X.509 CERTIFICATE” 视为等同于 “CERTIFICATE”, 但有效的例外可能是为了向后兼容性 (可能附带警告).&lt;&#x2F;p&gt;
&lt;p&gt;5.2. 说明性文本&lt;&#x2F;p&gt;
&lt;p&gt;已知许多工具在 PKIX 证书的 BEGIN 行之前和 END 行之后会输出说明性文本, 比其他类型的证书更为常见. 如果输出此类文本, 应使其与证书相关, 例如提供证书中关键数据元素的文本表示.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Subject: CN=Atlantis
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Issuer: CN=Atlantis
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Validity: from 7&#x2F;9&#x2F;2012 3:10:38 AM UTC to 7&#x2F;9&#x2F;2013 3:10:37 AM UTC
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN CERTIFICATE-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIIBmTCCAUegAwIBAgIBKjAJBgUrDgMCHQUAMBMxETAPBgNVBAMTCEF0bGFudGlz
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MB4XDTEyMDcwOTAzMTAzOFoXDTEzMDcwOTAzMTAzN1owEzERMA8GA1UEAxMIQXRs
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;YW50aXMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAu+BXo+miabDIHHx+yquqzqNh
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Ryn&#x2F;XtkJIIHVcYtHvIX+S1x5ErgMoHehycpoxbErZmVR4GCq1S2diNmRFZCRtQID
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AQABo4GJMIGGMAwGA1UdEwEB&#x2F;wQCMAAwIAYDVR0EAQH&#x2F;BBYwFDAOMAwGCisGAQQB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gjcCARUDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzA1BgNVHQEE
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;LjAsgBA0jOnSSuIHYmnVryHAdywMoRUwEzERMA8GA1UEAxMIQXRsYW50aXOCASow
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;CQYFKw4DAh0FAANBAKi6HRBaNEL5R0n56nvfclQNaXiDT174uf+lojzA4lhVInc0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ILwpnZ1izL4MlI9eCSHhVQBHEp2uQdXJB+d5Byg=
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END CERTIFICATE-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;5.3. 文件扩展名&lt;&#x2F;p&gt;
&lt;p&gt;尽管 PKIX 结构的文本编码可以出现在任何地方, 但已知许多工具在序列化 PKIX 结构时会提供输出此编码的选项. 为了促进互操作性并将 DER 编码与文本编码分开, 证书的文本编码应使用 “.crt” 扩展名. 实现应意识到, 尽管有此建议, 许多工具仍默认使用 “.cer” 扩展名对证书进行此类文本编码.&lt;&#x2F;p&gt;
&lt;p&gt;本节不会以任何方式干扰官方的 application&#x2F;pkix-cert 注册 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc2585&quot;&gt;RFC2585&lt;&#x2F;a&gt; (其中规定 “每个 ‘.cer’ 文件包含一个 DER 格式编码的证书”), 而仅是阐明一种广泛存在的事实上的替代方案.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;证书链 (Certificate chain) 通常是多个证书的集合, 但似乎并没有一个统一的标准来表示它.&lt;&#x2F;p&gt;
&lt;p&gt;实践上, 可以将多个 PEM 编码的证书直接串联在一个文件中, &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt; 也允许这么干.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;pkcs-10-biao-zhun&quot;&gt;PKCS #10 标准&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这个标准是为证书请求语法 (Certificate Request Syntax) 准备的, 不过笔者个人没怎么见过, 但还是顺带说一下了.&lt;&#x2F;p&gt;
&lt;p&gt;以下内容摘抄自 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc7468&quot;&gt;RFC7468&lt;&#x2F;a&gt; Section 7.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;PKCS #10 证书请求使用 “CERTIFICATE REQUEST” 标签进行编码. 编码内容&lt;strong&gt;必须&lt;&#x2F;strong&gt;是 BER (强烈推荐 DER 格式) 编码的 ASN.1 CertificationRequest 结构, 如 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc2986&quot;&gt;RFC2986&lt;&#x2F;a&gt; 所述.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN CERTIFICATE REQUEST-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MIIBWDCCAQcCAQAwTjELMAkGA1UEBhMCU0UxJzAlBgNVBAoTHlNpbW9uIEpvc2Vm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;c3NvbiBEYXRha29uc3VsdCBBQjEWMBQGA1UEAxMNam9zZWZzc29uLm9yZzBOMBAG
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ByqGSM49AgEGBSuBBAAhAzoABLLPSkuXY0l66MbxVJ3Mot5FCFuqQfn6dTs+9&#x2F;CM
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;EOlSwVej77tj56kj9R&#x2F;j9Q+LfysX8FO9I5p3oGIwYAYJKoZIhvcNAQkOMVMwUTAY
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;BgNVHREEETAPgg1qb3NlZnNzb24ub3JnMAwGA1UdEwEB&#x2F;wQCMAAwDwYDVR0PAQH&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;BAUDAwegADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgM&#x2F;ADA8
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AhxBvfhxPFfbBbsE1NoFmCUczOFApEuQVUw3ZP69AhwWXk3dgSUsKnuwL5g&#x2F;ftAY
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;dEQc8B8jAcnuOrfU
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END CERTIFICATE REQUEST-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;标签 “NEW CERTIFICATE REQUEST” 也被广泛使用. 符合本文档的生成器&lt;strong&gt;必须&lt;&#x2F;strong&gt;生成 “CERTIFICATE REQUEST” 标签. 解析器&lt;strong&gt;可以&lt;&#x2F;strong&gt;将 “NEW CERTIFICATE REQUEST” 视为等同于 “CERTIFICATE REQUEST”.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bu-chong-nei-rong-2-an-interface-and-algorithms-for-authenticated-encryption-rfc-5116&quot;&gt;补充内容 2: An Interface and Algorithms for Authenticated Encryption (RFC 5116)&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;为了更好地理解 AEAD, 这里提供 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5116&quot;&gt;RFC5116&lt;&#x2F;a&gt; 的翻译, 供读者参考学习.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;1-yin-yan&quot;&gt;1. 引言&lt;&#x2F;h3&gt;
&lt;p&gt;认证加密 (Authenticated Encryption) 是一种加密形式, 除了为加密的明文提供机密性之外, 还提供了一种检查其完整性和真实性的方法.
带关联数据的认证加密 (Authenticated Encryption with Associated Data), 或简称 &lt;strong&gt;AEAD&lt;&#x2F;strong&gt;, 增加了检查某些关联数据 (AD) 的完整性和真实性的能力, 这些关联数据也被称为 “附加认证数据” (&lt;strong&gt;AAD&lt;&#x2F;strong&gt;, Additional Authenticated Data), 且未被加密.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-aead-jie-kou&quot;&gt;2. AEAD 接口&lt;&#x2F;h3&gt;
&lt;p&gt;AEAD 算法具有两种操作: 认证加密和认证解密. 这些算法的输入和输出在下面以八位字节字符串的形式定义.&lt;&#x2F;p&gt;
&lt;p&gt;实现&lt;strong&gt;可以&lt;&#x2F;strong&gt;接受额外的输入. 例如, 可以提供一个输入来允许用户在不同的实现策略之间进行选择. 然而, 此类扩展&lt;strong&gt;绝不能&lt;&#x2F;strong&gt;影响与其他实现的互操作性.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;2-1-ren-zheng-jia-mi&quot;&gt;2.1. 认证加密&lt;&#x2F;h4&gt;
&lt;p&gt;认证加密操作具有四个输入, 每个输入都是一个八位字节字符串:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;一个秘密密钥 (&lt;strong&gt;secret key&lt;&#x2F;strong&gt;) K, 它&lt;strong&gt;必须&lt;&#x2F;strong&gt;以均匀随机或伪随机的方式生成.&lt;&#x2F;li&gt;
&lt;li&gt;一个随机数 (&lt;strong&gt;nonce&lt;&#x2F;strong&gt;) N. 对于密钥的任何特定值, 提供给认证加密操作不同调用的每个随机数&lt;strong&gt;必须&lt;&#x2F;strong&gt;是不同的, 除非每个随机数都是零长度. 能够生成不同随机数的应用程序&lt;strong&gt;必须&lt;&#x2F;strong&gt;使用第 3.2 节中定义的随机数形成方法, 并可以使用任何其他满足唯一性要求的方法. 其他应用程序&lt;strong&gt;必须&lt;&#x2F;strong&gt;使用零长度随机数.&lt;&#x2F;li&gt;
&lt;li&gt;一个明文 (plaintext) P, 其中包含要要认证且需要被加密的数据.&lt;&#x2F;li&gt;
&lt;li&gt;关联数据 (associated data) A, 其中包含要认证但不需要被加密的数据.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;敲黑板: nonce 必须唯一, 但不必保密.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;有一个单一输出:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;一个密文 C, 其长度至少与明文一样长, 或者&lt;&#x2F;li&gt;
&lt;li&gt;一个指示请求的加密操作无法执行的指示.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;所有输入和输出都是可变长度的八位字节字符串, 其长度遵守以下限制:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;密钥 K 中的八位字节数在 1 到 255 之间. 对于每个 AEAD 算法, K 的长度&lt;strong&gt;必须&lt;&#x2F;strong&gt;是固定的.
对于密钥的任何特定值, 要么 1) 提供给认证加密操作不同调用的每个随机数必须是不同的, 要么 2) 每个随机数必须是零长度. 如果使用零长度随机数与特定密钥一起, 则与该密钥一起使用的每个随机数必须具有零长度. 否则, 随机数的八位字节数应为十二 (12). 可以与特定密钥一起使用不同长度的随机数. 有些算法不能与零长度随机数一起使用, 但其他算法可以; 参见第 4 节. 符合推荐随机数长度的应用程序将避免根据所使用的算法构造不同长度的随机数. 此指导有助于将算法特定逻辑保持在应用程序之外.&lt;&#x2F;li&gt;
&lt;li&gt;明文 P 中的八位字节数可以为零.&lt;&#x2F;li&gt;
&lt;li&gt;关联数据 A 中的八位字节数可以为零.&lt;&#x2F;li&gt;
&lt;li&gt;密文 C 中的八位字节数可以为零.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;本规范不对随机数、明文、密文或附加认证数据设置最大长度限制. 然而, 特定 AEAD 算法可以进一步限制这些输入和输出的长度. 特定 AEAD 实现可以进一步限制其输入和输出的长度. 如果特定 AEAD 算法的实现被要求处理超出可接受长度范围的输入, 或超出该实现支持的长度范围的输入, 它必须返回错误代码, 并且不得输出任何其他信息. 特别是, 不得返回部分加密或部分解密的数据.&lt;&#x2F;p&gt;
&lt;p&gt;明文 P 同时提供机密性和消息认证. 当 P 的长度为零时, AEAD 算法充当输入 A 的消息认证码.&lt;&#x2F;p&gt;
&lt;p&gt;关联数据 A 用于保护需要认证但不需要保密的信息. 例如, 在使用 AEAD 保护网络协议时, 此输入可以包括地址、端口、序列号、协议版本号以及其他指示如何处理、转发或处理明文或密文的字段. 在许多情况下, 希望认证这些字段, 尽管它们必须保持明文以允许网络或系统正常运行. 当此数据包含在输入 A 中时, 可以在不将数据复制到明文中提供认证.
秘密密钥 K 不得包含在任何其他输入 (N、P 和 A) 中. (此限制并不意味着必须检查这些输入的值以确保它们不包含匹配密钥的子字符串; 相反, 它意味着密钥不得明确复制到这些输入中. )&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;敲黑板: TLS 里面, TLS Record 的 TLV 结构, 类型和长度是不能加密的, 故作为 AAD.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;随机数在算法内部被认证, 因此没有必要将其包含在 AD 输入中. 如果对应用程序方便, 可以将随机数包含在 P 或 A 中.&lt;&#x2F;p&gt;
&lt;p&gt;随机数可以与密文一起存储或传输, 或者可以在认证解密操作之前立即重建. 只需向解密模块提供足够的信息以允许其构造随机数即可. (例如, 系统可以使用特定格式的序列号作为随机数, 在这种情况下, 可以从密文的顺序推断出来.) 由于认证解密过程会检测不正确的随机数值, 如果随机数被不正确重建并输入到认证解密操作中, 不会导致安全失败. 任何随机数重建方法都需要考虑加密和解密过程之间密文丢失或重新排序的可能性.&lt;&#x2F;p&gt;
&lt;p&gt;应用程序不得假设密文的任何特定结构或格式.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;2-2-ren-zheng-jie-mi&quot;&gt;2.2. 认证解密&lt;&#x2F;h4&gt;
&lt;p&gt;认证解密操作具有四个输入: K、N、A 和 C, 如上定义. 它只有一个单一输出, 要么是明文值 P, 要么是一个特殊符号 FAIL, 表示输入不是认证的. 当使用输入 K、N、P 和 A 通过加密操作生成 C (对于 N、P 和 A 的某些值) 时, 密文 C、随机数 N 和关联数据 A 对于密钥 K 是认证的. 认证解密操作将以高概率返回 FAIL, 只要输入 N、P 和 A 是由遵守随机数的对手精心制作的, 且该对手不知道秘密密钥(假设 AEAD 算法是安全的).&lt;&#x2F;p&gt;
&lt;h4 id=&quot;2-3-shu-ju-ge-shi&quot;&gt;2.3. 数据格式&lt;&#x2F;h4&gt;
&lt;p&gt;本文档未指定 AEAD 输入和输出的任何特定编码, 因为编码不影响 AEAD 算法提供的安全服务.&lt;&#x2F;p&gt;
&lt;p&gt;在选择应用程序数据格式时, 应用程序应将密文 C 放置在认证解密操作的其他输入所需的任何其他数据之后. 例如, 如果随机数和密文都出现在数据包中, 前者应先于后者. 此规则有助于 AEAD 算法的高效和简单的硬件实现.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-aead-suan-fa-de-shi-yong-zhi-dao&quot;&gt;3. AEAD 算法的使用指导&lt;&#x2F;h3&gt;
&lt;p&gt;本节提供必须遵循的建议, 以安全地使用 AEAD 算法.&lt;&#x2F;p&gt;
&lt;p&gt;如果应用程序无法满足随机数生成的唯一性要求, 则它必须使用零长度随机数. 下面定义的随机化或有状态算法适合与此类应用程序一起使用. 否则, 应用程序应使用长度为十二个八位字节的随机数. 由于鼓励算法支持该长度, 应用程序应使用该长度以辅助互操作性.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-1-nonce-sheng-cheng-yao-qiu&quot;&gt;3.1. Nonce 生成要求&lt;&#x2F;h4&gt;
&lt;blockquote&gt;
&lt;p&gt;略, 简要总结: 重复使用 nonce 会有严重的安全问题, 具体因特定 AEAD 算法而异; nonce 必须唯一, 但不必保密, 可以将 nonce 直接附加在密文前面, 又或者像 TLS 那样, 通过握手协商出一个初始值, 然后每次加密时依据顺序号生成.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h4 id=&quot;3-2-tui-jian-de-nonce-gou-zao-fang-fa&quot;&gt;3.2. 推荐的 Nonce 构造方法&lt;&#x2F;h4&gt;
&lt;p&gt;(略)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-aead-suan-fa-gui-fan-yao-qiu&quot;&gt;4. AEAD 算法规范要求&lt;&#x2F;h3&gt;
&lt;p&gt;每个 AEAD 算法必须只接受固定密钥长度 K_LEN 的密钥, 并且不得要求所提供的密钥具有任何特定的数据格式. 需要这种结构 (例如, 包含特定奇偶校验格式子密钥的算法) 的算法将需要在内部提供它.&lt;&#x2F;p&gt;
&lt;p&gt;每个 AEAD 算法必须接受长度在零到 P_MAX Bytes (含零和 P_MAX) 之间的任何明文, 其中 P_MAX 值是该算法特有的. P_MAX 的值必须大于零, 并且应至少为 65536 (2^16) Bytes. 此大小是网络数据包的典型上限. 其他应用程序可能使用更大的 P_MAX 值,  因此通用算法支持更高的值是可取的.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;某些 AEAD 算法存在 “漏洞”, 一旦发送了一定数量的消息, 它们就不再安全, 为此, TLS 1.3 引入了 KeyUpdate 机制.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;每个 AEAD 算法必须接受长度在零到 A_MAX Bytes (含零和 A_MAX) 之间的任何关联数据,  其中 A_MAX 值是该算法特有的. A_MAX 的值必须大于零, 并且应至少为 65536 (2^16) Bytes. 其他应用程序可能使用更大的 A_MAX 值,  因此通用算法支持更高的值是可取的.&lt;&#x2F;p&gt;
&lt;p&gt;每个 AEAD 算法必须接受长度在 N_MIN 和 N_MAX Bytes (含 N_MIN 和 N_MAX) 之间的任何随机数 (nonce), 其中 N_MIN 和 N_MAX 的值是该算法特有的. N_MAX 和 N_MIN 的值可以相等. 每个算法应接受长度为 12 Bytes 的随机数. 下面描述的随机化或有状态算法的 N_MAX 值可以为零.&lt;&#x2F;p&gt;
&lt;p&gt;AEAD 算法可以以任何方式构造其密文输出; 例如, 密文可以包含一个认证标签. 每个算法都应选择一种有利于高效处理的结构.
认证加密算法可以包含或使用随机源, 例如, 用于生成包含在密文输出中的内部初始化向量. 这种 AEAD 算法被称为随机化算法; 但请注意, 只有加密是随机的, 而解密始终是确定性的. 随机化算法的 N_MAX 值可以为零.&lt;&#x2F;p&gt;
&lt;p&gt;认证加密算法可以包含在加密操作的调用之间维护的内部状态信息, 例如, 允许构造用作算法内部随机数的不同值. 这种 AEAD 算法被称为有状态算法. 即使应用程序输入零长度随机数, 此方法也可以用于算法以提供良好的安全性. 有状态算法的 N_MAX 值可以为零.&lt;&#x2F;p&gt;
&lt;p&gt;AEAD 算法的规范必须包含上面定义的 K_LEN、P_MAX、A_MAX、N_MIN 和 N_MAX 值. 此外, 它必须指定最大可能密文的八位字节数, 我们将其表示为 C_MAX.&lt;&#x2F;p&gt;
&lt;p&gt;每个 AEAD 算法必须提供一个描述, 说明明文长度与密文长度之间的关系. 这种关系不得依赖于外部参数, 例如认证强度参数 (例如, 认证标签长度). 这种依赖性会通过创建一种情况来使算法的使用复杂化, 即 AEAD 注册表中的信息不足以确保互操作性.&lt;&#x2F;p&gt;
&lt;p&gt;每个 AEAD 算法规范应描述由于意外重复使用随机数值而导致的安全降级.&lt;&#x2F;p&gt;
&lt;p&gt;每个 AEAD 算法规范应提供详细安全分析的参考文献. 本文档未指定特定的安全模型, 因为文献中已使用了几种不同的模型. 安全分析应定义或引用一个安全模型.&lt;&#x2F;p&gt;
&lt;p&gt;如上所述, 随机化或有状态的算法应使用这些术语来描述自己.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-aead-suan-fa&quot;&gt;5. AEAD 算法&lt;&#x2F;h3&gt;
&lt;p&gt;(以下简要总结, 不再翻译原文)&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;K_LEN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;P_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;A_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MIN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;C_MAX&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;密钥长度&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;明文最大长度&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;AAD 最大长度&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;Nonce 最低长度&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;Nonce 最大长度&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;密文最大长度&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;单位均为 Bytes (即 &lt;code&gt;&amp;lt;&amp;amp;[u8]&amp;gt;::len&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;h4 id=&quot;5-1-aes-128-gcm&quot;&gt;5.1. AES-128-GCM&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;K_LEN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;P_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;A_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MIN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;C_MAX&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;16&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^36 - 31&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^61 - 1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;12&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;12&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^36 - 15&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;blockquote&gt;
&lt;p&gt;对于 GCM, C.len = P.len + 16, 多出来的 16 Bytes 是认证标签.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;GCM 模式下, 加密操作重复使用 nonce 值会严重削弱安全性, 必须保证 nonce 的唯一性.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;5-2-aes-256-gcm&quot;&gt;5.2. AES-256-GCM&lt;&#x2F;h4&gt;
&lt;p&gt;除 K_LEN 为 32 Bytes 外, 其他均与 AES-128-GCM 相同.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;5-3-aes-128-ccm&quot;&gt;5.3. AES-128-CCM&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;K_LEN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;P_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;A_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MIN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;C_MAX&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;16&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^24 - 1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^64 - 1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;12&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;12&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^24 + 15&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;blockquote&gt;
&lt;p&gt;对于 CCM, C.len = P.len + 16, 多出来的 16 Bytes 是认证标签.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;CCM 模式下, 加密操作重复使用 nonce 值会严重削弱安全性, 必须保证 nonce 的唯一性.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;5-4-aes-256-ccm&quot;&gt;5.4. AES-256-CCM&lt;&#x2F;h4&gt;
&lt;p&gt;除 K_LEN 为 32 Bytes 外, 其他均与 AES-128-CCM 相同.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;5-5-fei-rfc-5116-suo-lie-ci-chu-jian-yao-bu-chong-chacha20-poly1305-rfc-8439&quot;&gt;5.5. (非 RFC 5116 所列, 此处简要补充) Chacha20-Poly1305 (RFC 8439)&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;K_LEN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;P_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;A_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MIN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;C_MAX&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;32&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;274,877,906,880 (2^38 - 64)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^64 - 1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;12&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;12&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;274,877,906,896 (2^38 - 48)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;blockquote&gt;
&lt;p&gt;C.len = P.len + 16, 多出来的 16 Bytes 是认证标签.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;类似地, 加密操作重复使用 nonce 值会严重削弱安全性, 必须保证 nonce 的唯一性.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;5-6-fei-rfc-5116-suo-lie-ci-chu-jian-yao-bu-chong-xchacha20-poly1305-draft-irtf-cfrg-xchacha-03&quot;&gt;5.6. (非 RFC 5116 所列, 此处简要补充) XChacha20-Poly1305 (draft-irtf-cfrg-xchacha-03)&lt;&#x2F;h4&gt;
&lt;blockquote&gt;
&lt;p&gt;XChacha20 是 Chacha20 的变种, 使用 192-bit (24 Bytes) 的 nonce.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;K_LEN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;P_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;A_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MIN&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;N_MAX&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;C_MAX&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;32&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;~2^80 (TBD)&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2^64 - 1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;24&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;24&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;~2^80 (TBD)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;blockquote&gt;
&lt;p&gt;C.len = P.len + 16, 多出来的 16 Bytes 是认证标签.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;类似地, 加密操作重复使用 nonce 值会严重削弱安全性, 必须保证 nonce 的唯一性.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-iana-considerations&quot;&gt;6. IANA Considerations&lt;&#x2F;h3&gt;
&lt;p&gt;(略)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;7-qi-ta-kao-lu&quot;&gt;7. 其他考虑&lt;&#x2F;h3&gt;
&lt;p&gt;可以使用 &lt;code&gt;encrypt-then-MAC&lt;&#x2F;code&gt; (加密后 MAC) 策略组合加密算法和消息认证码算法提供 AEAD.&lt;&#x2F;p&gt;
&lt;p&gt;(其余略)&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;xiao-jie&quot;&gt;小结&lt;&#x2F;h2&gt;
&lt;p&gt;阅读完本文, 我们应当已经熟悉了对称加密和非对称密码学(公钥密码学)的基本概念, 以及下面这些术语:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;RSA 公钥密码系统&lt;&#x2F;li&gt;
&lt;li&gt;ECC 公钥密码系统&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;使用到的椭圆曲线: Curve25519 &#x2F; Ed25519, Curve448 &#x2F; Ed448 等&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;X25519 (ECDH) &#x2F; X448 (EdDH).&lt;&#x2F;li&gt;
&lt;li&gt;ECDSA&lt;&#x2F;li&gt;
&lt;li&gt;EdDSA (Ed25519 &#x2F; Ed448).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;此外, 我们还应熟悉 PEM 格式下证书、公私钥的文本编码方式, 以及其与 DER 的关系.&lt;&#x2F;p&gt;
&lt;p&gt;再有, 我们应当熟悉 AEAD 算法基本流程, 了解 AAD, nonce 的概念与作用, 熟记常用的 AEAD 算法, 铭记对于绝大多数 (本文所列全部) AEAD 算法来说, nonce 必须唯一.&lt;&#x2F;p&gt;
</content>
        <summary type="html">本文介绍了对称加密和非对称密码学, 重点介绍了 AEAD 加密方案, 以及公钥密码系统在数字签名领域的应用. 最后补充介绍了数字证书相关内容.</summary>
        </entry><entry xml:lang="zh">
        <title>从零开始了解 TLS 1.3 系列笔记 (五) —— 实用密码学之「密钥交换, Diffie–Hellman 与后量子加密」</title>
        <published>2025-09-16T00:00:00+00:00</published>
        <updated>2025-09-16T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-09-16-tls-series-cryptography-4/" type="text/html"/>
        <id>https://i.han.rs/blog/25-09-16-tls-series-cryptography-4/</id>
        
            <content type="html">&lt;h2 id=&quot;dao-yu&quot;&gt;导语&lt;&#x2F;h2&gt;
&lt;p&gt;在密码学中, &lt;a href=&quot;http:&#x2F;&#x2F;cacr.uwaterloo.ca&#x2F;hac&#x2F;about&#x2F;chap12.pdf&quot;&gt;密钥建立 (key establishment)&lt;&#x2F;a&gt;, 或称&lt;strong&gt;密钥交换&lt;&#x2F;strong&gt; (key exchange, KE), 是一个过程或协议. 通过该过程或协议, 通信双方共享密钥.&lt;&#x2F;p&gt;
&lt;p&gt;密钥交换过程一定基于公私钥加密, 也就是非对称加密, 我们将在后续章节详细介绍.&lt;&#x2F;p&gt;
&lt;p&gt;密钥交换可以分为两类:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在&lt;strong&gt;密钥协商&lt;&#x2F;strong&gt;中, 双方都对密钥的生成做出贡献, 因此没有一方能单独决定最终的密钥.&lt;&#x2F;p&gt;
&lt;p&gt;如 Diffie-Hellman 密钥交换协议 (DH), 或 ECDH (Elliptic-Curve DH, 椭圆曲线 DH). 大多数不严谨的场景, 密钥交换和密钥协商指的是一件事.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;在&lt;strong&gt;密钥传输&lt;&#x2F;strong&gt;中, 一方生成密钥并将其传输给另一方.&lt;&#x2F;p&gt;
&lt;p&gt;如 RSA 密钥交换过程, 一方通过私钥加密随机会话密钥, 并将其发送到另一方, 另一方使用相应公钥将其解密.&lt;&#x2F;p&gt;
&lt;p&gt;由于此过程是静态的, 高度依赖 RSA 的安全性, 在 TLS 1.3 中已被弃用, 即便是 TLS 1.2 中也不推荐使用.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;通过密码学设计, 密钥交换方案 (Key exchange schemes) 允许通信双方安全地交换加密密钥, 通常这个过程发生在加密通讯开始时, 例如在 TLS 握手阶段.&lt;&#x2F;p&gt;
&lt;p&gt;大体上, 密钥协商可以基于匿名(&lt;strong&gt;无认证&lt;&#x2F;strong&gt;)的密钥交换协议 (如 DH), 密码或预共享密钥 (pre-shared key, PSK), 数字证书或许多元素的组合. 某些通信协议仅建立并从头到尾使用同一个共享密钥, 而另一些通信协议随着时间的流逝不断更改共享密钥.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;小贴士&lt;&#x2F;p&gt;
&lt;p&gt;由于密钥交换协议自身是匿名的, 密钥协商在一些需要消息完整性的场景 (如 TLS) 会配合签名算法组成最终的加密方案, 如 TLS 1.2 的加密套件有 &lt;code&gt;TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384&lt;&#x2F;code&gt;, 即:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ECDHE: Ephemeral ECDH, 密钥交换方案;&lt;&#x2F;li&gt;
&lt;li&gt;ECDSA: 数字签名方案;&lt;&#x2F;li&gt;
&lt;li&gt;AES-256-GCM: 对称加密方案;&lt;&#x2F;li&gt;
&lt;li&gt;SHA384: 消息摘要算法.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;这里超前提一下, 后面会继续介绍相关内容.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;diffie-hellman-mi-yao-jiao-huan-dhke&quot;&gt;Diffie-Hellman 密钥交换 (DHKE)&lt;&#x2F;h2&gt;
&lt;p&gt;DHKE 是最早的公钥协议之一, 允许两个没有事先了解的当事方在不安全的渠道上安全地交换共享密钥. 请注意, DHKE 可以对抗嗅探攻击 (即直接读取数据包), 但无法对抗中间人攻击 MitM (无认证, 中间人可以冒充).&lt;&#x2F;p&gt;
&lt;p&gt;DHKE 的实现包括经典的基于离散对数的 DH, 和基于椭圆曲线的 ECDH.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cong-hun-he-yan-se-jie-shi&quot;&gt;从混合颜色解释&lt;&#x2F;h3&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img src=&quot;.&#x2F;Diffie-Hellman_Key_Exchange.svg&quot; alt=&quot;Diffie-Hellman key exchange&quot; style=&quot;max-width: 50%;&quot;&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;这是来自维基百科的一幅经典图片, 利用混合颜色来解释 DH 的工作原理:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Alice 和 Bob 公开同意使用黄色;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 选择一个秘密的红色, 并将其与黄色混合, 得到橙色;&lt;&#x2F;li&gt;
&lt;li&gt;Bob 选择一个秘密的海洋绿色, 并将其与黄色混合, 得到浅天蓝色;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 和 Bob 交换他们混合后的颜色, 我们依然假设混合颜色难以分离出原始颜色;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 将 Bob 交换过来的浅天蓝色与她的秘密颜色混合, 得到黄褐色;&lt;&#x2F;li&gt;
&lt;li&gt;Bob 将 Alice 交换过来的橙色与他的秘密颜色混合, 也得到黄褐色;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;到此, Alice 和 Bob 都得到了相同的颜色, 即共享密钥, 而中间人 Eve 只能看到黄色, 橙色和浅天蓝色, 无法计算出黄褐色.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;jing-dian-dh&quot;&gt;经典 DH&lt;&#x2F;h3&gt;
&lt;p&gt;对于传统基于离散对数的 DH, 其过程类似于前面的颜色混合解释.&lt;&#x2F;p&gt;
&lt;p&gt;首先介绍一点数学背景知识:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;模幂: 是指求 $g$ 的 $a$ 次幂模 $p$ 的值 $c$ 的过程, 记作 $c = g^a \mod p$, 其中 $p$ 是一个质数;&lt;&#x2F;li&gt;
&lt;li&gt;离散对数, 即模幂的逆运算, 是指已知 $g$, $p$, $c$, 求 $a$ 的过程&lt;&#x2F;li&gt;
&lt;li&gt;模幂运算律: $g^{ab} \mod p = {(g^a \mod p)}^b \mod p$&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;模幂过程计算非常快, 然而离散对数求解被认为在 $p$ 足够大的情况下是在现有计算能力下无法完成的, 此即&lt;strong&gt;离散对数难题&lt;&#x2F;strong&gt; (discrete logarithm problem, DLP). 经典的 DHKE 就是基于 DLP 设计的.&lt;&#x2F;p&gt;
&lt;p&gt;那么类似地就有 DHKE 的过程:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Alice 和 Bob 公开同意使用一个大质数 $p$ 和一个生成元 $g$ (通常 $g$ 是 2 或 5, 不需要很大), 在代码层面是写死的;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 选择一个秘密整数 $a$, 并计算 $A = g^a \mod p$, 将 $A$ 发送给 Bob;&lt;&#x2F;li&gt;
&lt;li&gt;Bob 选择一个秘密整数 $b$, 并计算 $B = g^b \mod p$, 将 $B$ 发送给 Alice;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 收到 $B$ 后, 计算共享密钥 $S_1 = B^a \mod p$;&lt;&#x2F;li&gt;
&lt;li&gt;Bob 收到 $A$ 后, 计算共享密钥 $S_2 = A^b \mod p$;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;由模幂的运算律可以找到 $S_1 = S_2$, 即 Alice 和 Bob 都得到了相同的共享密钥 $S$; 而中间人获得 $A$ 和 $B$ 后, 只能通过离散对数求解 $a$ 或 $b$.&lt;&#x2F;p&gt;
&lt;p&gt;在最常见的 DHKE 实现中 (RFC 3526), 基数是 $g = 2$, 模数 $p$ 是一个 1536 到 8192 比特的大质数. 而整数 $A$ $B$ 通常会使用非常大的数字以防范暴力破解. 对于这种离散对数问题 (DLP), 目前还不存在有效的算法.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ecdh&quot;&gt;ECDH&lt;&#x2F;h3&gt;
&lt;p&gt;ECDH 应用椭圆曲线代替了 DH 中的模幂.&lt;&#x2F;p&gt;
&lt;p&gt;我们依然有运算律:&lt;&#x2F;p&gt;
&lt;p&gt;$$(a * G) * b = (b * G) * a$$&lt;&#x2F;p&gt;
&lt;p&gt;其中 $a, b$ 为常数, $G$ 为椭圆曲线上的某一点的坐标 $(x, y)$.&lt;&#x2F;p&gt;
&lt;p&gt;类似地有 ECDH 的过程:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Alice 和 Bob 公开同意使用一个椭圆曲线和基点 $G$;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 生成一个随机的 ECC 密钥对: $A = a * G$, 将公钥 $A$ 发送给 Bob;&lt;&#x2F;li&gt;
&lt;li&gt;Bob 生成一个随机的 ECC 密钥对: $B = b * G$, 将公钥 $B$ 发送给 Alice;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 收到 $B$ 后, 计算共享密钥 $S_1 = a * B$;&lt;&#x2F;li&gt;
&lt;li&gt;Bob 收到 $A$ 后, 计算共享密钥 $S_2 = b * A$&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;显然, $S_1 = S_2$, 即 Alice 和 Bob 都得到了相同的共享密钥 $S$, 而私钥 $a$, $b$ 的安全性, 则类似地由 ECDLP 问题提供保证, 即「通过公开的 $kG$ 以及 $G$ 这两个参数, 目前没有有效的手段能快速求解出 $k$ 的值. 」&lt;&#x2F;p&gt;
&lt;h4 id=&quot;shi-li&quot;&gt;示例&lt;&#x2F;h4&gt;
&lt;blockquote&gt;
&lt;p&gt;如果读者还是对 (EC)DH 感到陌生, 相信 X25519 这个名字会更熟悉一些, 这是 TLS 1.3 中最常用的密钥交换算法.&lt;&#x2F;p&gt;
&lt;p&gt;以下内容参考自 &lt;a href=&quot;https:&#x2F;&#x2F;crypto-in-action.github.io&#x2F;intro-ed25519&#x2F;190902-intro-x25519.pdf&quot;&gt;深入理解 X25519&lt;&#x2F;a&gt;, 在此表示感谢.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Curve25519 是一种椭圆曲线, 由 Daniel J. Bernstein 在 2005 年设计, 其设计目标是提供高安全性和高性能的椭圆曲线密码学 (ECC) 操作. Curve25519 的名称来源于其使用的素数 $2^{255} - 19$, 该素数定义了曲线所在的有限域. 而 X25519 是基于 Curve25519 的密钥交换协议, 具体来说, X25519 是一种椭圆曲线 Diffie-Hellman (ECDH) 密钥交换协议的实现. 关于 ECC 公钥密码学的更详细内容, 我们将在下一章介绍.&lt;&#x2F;p&gt;
&lt;p&gt;值得记忆的常识:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;X25519 协议对应的私钥是 32 字节.&lt;&#x2F;li&gt;
&lt;li&gt;X25519 协议对应的公钥是 &lt;strong&gt;32&lt;&#x2F;strong&gt; 字节.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;dhe-diffie-hellman-ephemeral-lin-shi-dh&quot;&gt;DHE (Diffie-Hellman Ephemeral, 临时 DH)&lt;&#x2F;h3&gt;
&lt;p&gt;前面介绍的经典 DH 与 ECDH 协议流程, 都是在最开始时交换一次密钥, 之后就一直使用该密钥通讯. 因此如果密钥被破解, 整个会话的所有信息对攻击者而言就完全透明了.&lt;&#x2F;p&gt;
&lt;p&gt;为了进一步提高安全性, 密码学家提出了「完全前向保密 (Perfect Forward Secrecy, PFS)」的概念, 并在 DHKE 与 ECDH 的基础上提出了支持 PFS 的 DHE &#x2F; ECDHE 协议.&lt;&#x2F;p&gt;
&lt;p&gt;PFS 是指长期使用的主密钥泄漏不会导致过去的会话密钥泄漏, 从而保护过去进行的通讯不受密码或密钥在未来暴露的威胁.&lt;&#x2F;p&gt;
&lt;p&gt;DHE &#x2F; ECDHE 协议的流程与前面介绍的 DH &#x2F; ECDH 类似, 但每次通讯时都会重新生成一对密钥对, 计算出新的共享密钥随密文发送供下一轮使用. 这样即使某次会话的密钥被破解, 也不会影响到之前或之后的会话.&lt;&#x2F;p&gt;
&lt;p&gt;(&lt;del&gt;一个小问题: 如果能破解一次, 那么破解下一次应该也不难了?&lt;&#x2F;del&gt;)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;liang-zi-ji-suan-ji-de-ying-xiang&quot;&gt;量子计算机的影响&lt;&#x2F;h3&gt;
&lt;p&gt;在量子计算机面前, DLP 可以通过 Shor 算法解决, 因此 DH 和 ECDH 在量子计算机面前不再安全. 值得庆幸的是, 当前量子计算机还远未达到能破解这些加密算法的规模, 但从长远来看, 我们需要考虑量子安全的密钥交换算法.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hou-liang-zi-mi-ma-xue-post-quantum-cryptography-pqc-ying-yong-hun-he-mi-yao-jiao-huan-hybrid-key-exchange&quot;&gt;后量子密码学 (Post-Quantum Cryptography, PQC) 应用: 混合密钥交换 (Hybrid Key Exchange)&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;相信读者一定提到过 X25519MLKEM768, X25519Kyber768Draft00 这样的名字, 这是 TLS 1.3 中的混合密钥交换方案, 本节简要介绍.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;前面提到, 量子计算机的出现对 (EC)DH 的密码学安全保证构成威胁, 因此需要考虑量子安全的密钥交换算法. 目前, NIST 在 &lt;a href=&quot;https:&#x2F;&#x2F;nvlpubs.nist.gov&#x2F;nistpubs&#x2F;FIPS&#x2F;NIST.FIPS.203.pdf&quot;&gt;FIPS. 203&lt;&#x2F;a&gt; 中标准化了 ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism, 基于模格的密钥封装机制), 各大厂商亦跟进用之替代了先前测试阶段的 Kyber (ML-KEM 的前身).&lt;&#x2F;p&gt;
&lt;p&gt;在介绍混合密钥交换之前, 先简要介绍一下 KEM.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;kem-key-encapsulation-mechanism-mi-yao-feng-zhuang-ji-zhi&quot;&gt;KEM (Key Encapsulation Mechanism, 密钥封装机制)&lt;&#x2F;h3&gt;
&lt;p&gt;如其名, 密钥封装机制核心就是对密文的封装 (encapsulate) 与解封装 (decapsulate), 示意图如下:&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;.&#x2F;FIPS.206-MLKEM-KE.png&quot; alt=&quot;KEM&quot; style=&quot;max-width: 50%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;主要流程为:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Alice 生成一对公私钥 $(pk, sk)$, 并将公钥 $pk$ 发送给 Bob; 公钥也称封装密钥 (encapsulation key), 私钥也称解封装密钥 (decapsulation key);&lt;&#x2F;li&gt;
&lt;li&gt;Bob 使用公钥 $pk$ 执行 Encaps 操作, 获得一个密钥和关联的&lt;strong&gt;封装密文&lt;&#x2F;strong&gt;; Bob 将关联密文发送给 Alice, 密钥则作为共享密钥使用;&lt;&#x2F;li&gt;
&lt;li&gt;Alice 使用私钥 $sk$ 对关联密文执行 Decaps 解封装操作, 获得共享密钥;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;到此, Alice 和 Bob 得到了相同的共享密钥.&lt;&#x2F;p&gt;
&lt;p&gt;而 ML-KEM 是 KEM 中的一种, 其安全性基于模格 (Module-Lattice) 问题, 目前已知的量子计算机无法有效解决.&lt;&#x2F;p&gt;
&lt;p&gt;稍微跑题, 还有 ML-DSA, 作为数字签名算法 (DSA), 应用于 TLS 证书验证模块 (draft-ietf-tls-mldsa).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tls-1-3-zhong-de-hun-he-mi-yao-jiao-huan&quot;&gt;TLS 1.3 中的混合密钥交换&lt;&#x2F;h3&gt;
&lt;p&gt;(也许是)出于兼容性考虑, 目前 TLS 1.3 中的后量子密钥交换方案均以混合密钥交换的形式出现.&lt;&#x2F;p&gt;
&lt;p&gt;感谢 &lt;a href=&quot;https:&#x2F;&#x2F;www.netmeister.org&#x2F;blog&#x2F;tls-hybrid-kex.html&quot;&gt;@jschauma 总结的一图流&lt;&#x2F;a&gt;,  (图示为 X25519Kyber768, 但其他的基于 ML-KEM 的混合密钥交换方案也是类似的流程):&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;.&#x2F;kyber-kex.png&quot; alt=&quot;TLS 1.3 Hybrid Key Exchange&quot; style=&quot;max-width: 100%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;简单地来说, 就是在原 (EC)DH 的基础上, 额外增加了 KEM 的公钥交换与密文交换过程: 客户端将 PQC 公钥拼接在客户端向服务端共享的 (EC)DH 公钥后, 服务端将封装密文拼接在服务端向客户端共享的 (EC)DH 公钥后, 双方最终将通过 (EC)DH 得到的共享密钥与 KEM 拿到的或解封装出来的共享密钥拼接起来, 就得到了最终的共享密钥, 是不是非常简单粗暴呢.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;碎碎念&lt;&#x2F;p&gt;
&lt;p&gt;后量子算法的引入, 直接导致 ClientHello 消息体积暴增, 如 X25519MLKEM768, 会额外多出 32 字节的用于回落的 X25519 公钥, 以及 1184 字节的 ML-KEM-768 的公钥, 加上原有的 500 字节左右, 大概是 1700 字节左右, 已经超过了 IPv4 TCP 最大 MSS (1500 - 20 - 20 = 1460 Bytes) 导致需要分包, 一旦丢包, 握手延迟蹭蹭涨.&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
 &lt;img src=&quot;.&#x2F;wireshark-large-client-hello.png&quot; alt=&quot;wireshark-large-client-hello&quot; style=&quot;max-width: 60%;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;即便使用纯 PQC, 也只是堪堪减少 32 Bytes, 于事无补.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;xiao-jie&quot;&gt;小结&lt;&#x2F;h2&gt;
&lt;p&gt;读完本文, 我们应该已经熟悉以下术语及相应的流程:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;密钥交换 (Key Exchange, KE)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;密钥协商&lt;&#x2F;strong&gt; (Key Agreement)&lt;&#x2F;li&gt;
&lt;li&gt;密钥传输 (Key Transport)&lt;&#x2F;li&gt;
&lt;li&gt;Diffie-Hellman (&lt;strong&gt;DH&lt;&#x2F;strong&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;椭圆曲线 Diffie-Hellman (&lt;strong&gt;ECDH&lt;&#x2F;strong&gt;)
&lt;ul&gt;
&lt;li&gt;Curve25519, &lt;strong&gt;X25519&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;secp256r1 (NIST &lt;strong&gt;P-256&lt;&#x2F;strong&gt;), etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;临时 Diffie-Hellman (&lt;strong&gt;DHE&lt;&#x2F;strong&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;完全前向保密 (Perfect Forward Secrecy, PFS)&lt;&#x2F;li&gt;
&lt;li&gt;后量子安全; 密钥封装机制 (Key Encapsulation Mechanism, KEM)
&lt;ul&gt;
&lt;li&gt;ML-KEM (Module-Lattice-based KEM)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;混合密钥交换 (Hybrid Key Exchange)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        <summary type="html">本文介绍了密钥交换的基本概念, 着重介绍了 TLS 1.3 中使用的 (EC)DH 密钥交换协议, 以及后量子加密参与的混合密钥交换方案.</summary>
        </entry><entry xml:lang="zh">
        <title>从零开始了解 TLS 1.3 系列笔记 (四) —— 实用密码学之「HMAC、HKDF 与安全随机数」</title>
        <published>2025-09-15T00:00:00+00:00</published>
        <updated>2025-09-15T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-09-15-tls-series-cryptography-3/" type="text/html"/>
        <id>https://i.han.rs/blog/25-09-15-tls-series-cryptography-3/</id>
        
            <content type="html">&lt;h2 id=&quot;dao-yu&quot;&gt;导语&lt;&#x2F;h2&gt;
&lt;p&gt;MAC (Message Authentication Code, 消息认证码)、HMAC (Hash-based MAC, 哈希消息认证码)和 KDF (&lt;strong&gt;Key Derivation&lt;&#x2F;strong&gt; Function, &lt;strong&gt;密钥派生&lt;&#x2F;strong&gt;函数) 在密码学中发挥着重要作用. 让我们解释一下何时需要 MAC, 如何计算 HMAC 以及它与 KDF 的关系.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mac&quot;&gt;MAC&lt;&#x2F;h3&gt;
&lt;p&gt;MAC 由给定密钥和消息计算而来:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;MAC = f(key, message)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;通常, 它的表现类似于加密哈希函数:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;难以分析&lt;&#x2F;strong&gt;: 密钥或消息的微小变化会生成完全不同的 MAC 值.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;不可逆&lt;&#x2F;strong&gt;: 从其哈希值逆向演算出输入值应该是不可行的. 这意味着没有比暴力破解更好的破解方法.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;抗碰撞&lt;&#x2F;strong&gt;: 几乎不可能找到具有相同哈希值的两条不同消息.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;例如, MAC 码可以通过 HMAC-SHA256 算法计算, 如下所示:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;HMAC-SHA256(&amp;#39;key&amp;#39;, &amp;#39;some msg&amp;#39;) = 32885b49c8a1009e6d66662f8462e7dd5df769a7b725d1d546574e6d5d6e76ad
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;MAC 码是&lt;em&gt;数字真实性代码&lt;&#x2F;em&gt; (digital authenticity code), 类似于数字签名 (digital signature), 但具有预共享密钥 (pre-shared key).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mac-suan-fa&quot;&gt;MAC 算法&lt;&#x2F;h3&gt;
&lt;p&gt;包括但不限于:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;HMAC&quot;&gt;HMAC&lt;&#x2F;a&gt; (Hash-based MAC, 哈希消息认证码), 如 HMAC-SHA256;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.cryptosys.net&#x2F;manapi&#x2F;api_kmac.html&quot;&gt;KMAC&lt;&#x2F;a&gt; (Keccak-based MAC, 基于 Keccak 的消息认证码);&lt;&#x2F;li&gt;
&lt;li&gt;基于对称密码算法的 MAC, 如 &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Poly1305&quot;&gt;&lt;strong&gt;Poly1305&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; (Bernstein 一次性身份验证) 等等.&lt;&#x2F;li&gt;
&lt;li&gt;其他, 如 &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;SipHash&quot;&gt;SipHash&lt;&#x2F;a&gt; (一种快速的、加密的哈希函数, 适用于哈希表).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;mac-zhu-yao-ying-yong&quot;&gt;MAC 主要应用&lt;&#x2F;h3&gt;
&lt;p&gt;MAC 码主要用于验证消息未被篡改. 如当通信双方持有相同的共享密钥时, 发送方可以计算消息的 MAC 码并将其附加到消息中; 接收方收到消息后, 使用相同的密钥计算消息的 MAC 码, 并将其与附加的 MAC 码进行比较; 如果两个 MAC 码匹配, 则消息被认为是完整且未被篡改的.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ren-zheng-jia-mi-authenticated-encryption-ae&quot;&gt;认证加密 (Authenticated Encryption, AE)&lt;&#x2F;h3&gt;
&lt;p&gt;使用 MAC 码的另一种场景是认证加密.&lt;&#x2F;p&gt;
&lt;p&gt;加密方:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;首先, 我们派生一密钥.&lt;&#x2F;li&gt;
&lt;li&gt;使用该密钥加密消息.&lt;&#x2F;li&gt;
&lt;li&gt;使用该密钥和&lt;strong&gt;原始消息&lt;&#x2F;strong&gt;计算 MAC 码, 并将其附加到第二步的输出中.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;接收方:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;使用一密钥解密密文, 此密钥并不一定是正确的.&lt;&#x2F;li&gt;
&lt;li&gt;使用该密钥和解密结果计算 MAC 码, 并将其与附加的 MAC 码进行比较; 如果两个 MAC 码匹配, 则密钥一致, 解密成功且密文未被篡改; 否则, 密钥不正确或密文被篡改.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;blockquote&gt;
&lt;p&gt;背景知识&lt;&#x2F;p&gt;
&lt;p&gt;可能大家并不熟悉 AE 这个术语, 但 AEAD (Authenticated Encryption with Associated Data, 带关联数据的认证加密) 应该就是熟面孔了. AEAD 是 AE 的一种扩展, 允许在认证加密过程中包含未加密的关联数据 (Associated Data, AD), 如路由信息等需要中间盒处理的信息. 例如, TLS 1.3 中使用的 AES-{128|256}-GCM 和 ChaCha20-Poly1305 都是 AEAD 加密方案, 我们将在介绍对称加密方案时再提到.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;由此, 我们同时验证了通信双方持有相同的共享密钥 (即认证, AE 代表 Authenticated Encryption, 经过认证的加密), 以及消息未被篡改 (完整性).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ji-yu-mac-de-wei-sui-ji-sheng-cheng-han-shu-prf-pseudo-random-function&quot;&gt;基于 MAC 的伪随机生成函数 (PRF, Pseudo-Random Function)&lt;&#x2F;h3&gt;
&lt;p&gt;MAC 码的另一个应用是作 PRF. 我们可以从某个盐和一个随机数(种子)开始.我们可以计算 &lt;code&gt;next_seed&lt;&#x2F;code&gt; 如下:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;next_seed = MAC(salt, seed)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;每次计算上述公式后, 下一个伪随机数都会被 “随机变化”, 我们可以用它来生成一定范围内的下一个随机数.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hmac-he-mi-yao-pai-sheng&quot;&gt;HMAC 和密钥派生&lt;&#x2F;h2&gt;
&lt;p&gt;简单计算 &lt;code&gt;MAC = H(key || msg)&lt;&#x2F;code&gt; 来获取 MAC &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;HMAC#Design_principles&quot;&gt;被认为是不安全的&lt;&#x2F;a&gt;. 建议改用 HMAC 算法, 例如 HMAC-SHA256.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;背景知识&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;大多数哈希函数存在长度拓展攻击, 即在不知道密钥的情况下, 可以通过已知的 &lt;code&gt;H(key || msg)&lt;&#x2F;code&gt; 来计算有效的 &lt;code&gt;H(key || msg || extra)&lt;&#x2F;code&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;计算 &lt;code&gt;MAC = H(msg || key)&lt;&#x2F;code&gt;, 则存在哈希冲突的可能性;&lt;&#x2F;li&gt;
&lt;li&gt;计算 &lt;code&gt;MAC = H(key || msg || key)&lt;&#x2F;code&gt; 仍然被证明是不安全的;&lt;&#x2F;li&gt;
&lt;li&gt;HMAC 计算 &lt;code&gt;HMAC = H(key || H(key || msg))&lt;&#x2F;code&gt;, 采用双重哈希, 消除 msg 长度影响, 目前尚未被证明不安全; 对于 SHA-3 (Keccak) 则不需要双重哈希, 只需 &lt;code&gt;HMAC = H(key || msg)&lt;&#x2F;code&gt;, 因为 SHA-3 不易受长度拓展攻击.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;shen-me-shi-hmac&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;HMAC#Definition&quot;&gt;什么是 HMAC&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;HMAC (Hash-based Message Authentication Code, 哈希消息认证码) 是一种基于哈希函数的 MAC 算法. 它使用一个密钥和一个消息, 通过两次哈希计算得到 MAC 码.&lt;&#x2F;p&gt;
&lt;p&gt;$$\operatorname {HMAC} (K,m)=\operatorname {H} {\Bigl (}{\bigl (}K’\oplus opad{\bigr )}\parallel \operatorname {H} {\bigl (}\left(K’\oplus ipad\right)\parallel m{\bigr )}{\Bigr )}$$&lt;&#x2F;p&gt;
&lt;p&gt;其中:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$\operatorname {H}$ 是 HMAC 过程所使用的加密哈希函数, 如 SHA-256;&lt;&#x2F;li&gt;
&lt;li&gt;$m$ 是消息;&lt;&#x2F;li&gt;
&lt;li&gt;$K$ 是一个密钥;&lt;&#x2F;li&gt;
&lt;li&gt;$K’$ 是密钥 $K$ 经过右侧补 0 填充或哈希后(必要时右侧补 0)的结果, 以确保其长度等于哈希函数的块大小 (block size);&lt;&#x2F;li&gt;
&lt;li&gt;$ipad$ 和 $opad$ 分别是内填充 (inner padding) 和外填充 (outer padding), 长度与块大小相同, 分别由值为 0x5c, 0x36 的重复字节组成;&lt;&#x2F;li&gt;
&lt;li&gt;$\oplus$ 表示按位异或 (XOR) 操作;&lt;&#x2F;li&gt;
&lt;li&gt;$\parallel$ 表示拼接.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;HMAC 用于验证消息真实性和完整性, 有时还用于密钥派生.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;kdf&quot;&gt;KDF&lt;&#x2F;h3&gt;
&lt;p&gt;KDF (Key Derivation Function, 密钥派生函数) 是一种将可变长度密码转换为固定长度密钥的函数:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;key = KDF(password)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;我们可以使用 SHA-256 对密码进行哈希处理, 使用哈希值作密钥, 这就是一个简单的 KDF. 但请不要这样做, 因为它不安全: 简单哈希容易受到字典攻击 (查表法).&lt;&#x2F;p&gt;
&lt;p&gt;为此, 我们可以引入盐 (salt) 执行 HMAC 操作, 此即 &lt;strong&gt;HKDF&lt;&#x2F;strong&gt; (HMAC-based KDF), 具体内容见下文.&lt;&#x2F;p&gt;
&lt;p&gt;使用 HKDF 进行密钥派生不如现代 KDF 安全, 如 PBKDF2, Bcrypt, Scrypt, Argon2 等.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;kdf-cong-mi-ma-huo-mi-yao-pai-sheng-mi-yao&quot;&gt;KDF: 从密码(或密钥)派生密钥&lt;&#x2F;h2&gt;
&lt;p&gt;PBKDF2, Bcrypt, Scrypt, Argon2 等 KDF 函数, 通过引入盐并结合多次迭代来增加密码破解的难度, 迭代过程也称 &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Key_stretching&quot;&gt;key stretching (密钥拉伸)&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;要计算安全的 KDF, 需要一些 CPU 时间来派生密钥 + 一些内存. 因此, 派生密钥是计算成本高昂的, 因此密码破解的尝试也会面临高昂计算成本.&lt;&#x2F;p&gt;
&lt;p&gt;当现代 KDF 与适当的配置参数一起使用时, 破解密码的速度会很慢 (例如, 每秒只能作 5-10 次尝试, 而不是每秒数千或数百万次尝试).&lt;&#x2F;p&gt;
&lt;p&gt;上述现代 KDF 均未申请专利, 可公开使用.&lt;&#x2F;p&gt;
&lt;p&gt;由于本系列主要介绍 TLS 1.3, 因此不再赘述现代 KDF 的细节 (我们只用到了 HKDF), 有兴趣的读者可参考 &lt;a href=&quot;https:&#x2F;&#x2F;practicalcryptography.dev&#x2F;&quot;&gt;Practical Cryptography for Developers Book&lt;&#x2F;a&gt;. 下一节对 HKDF 的介绍, 主要翻译自 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc5869&quot;&gt;RFC 5869&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hkdf-rfc-5869&quot;&gt;HKDF (RFC 5869)&lt;&#x2F;h2&gt;
&lt;p&gt;本文档规定了一个简单的基于 HMAC 的 KDF, 它可以作为各种协议和应用程序中的 (加密) 组件. KDF 旨在支持广泛的应用程序和需求, 并在使用加密哈希函数方面是保守的.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-yin-yan&quot;&gt;1. 引言&lt;&#x2F;h3&gt;
&lt;p&gt;KDF 是加密系统的基本和必要组件. 其目标是获取一些初始密钥材料源, 并从中派生出一个或多个具备强加密强度的密钥.&lt;&#x2F;p&gt;
&lt;p&gt;本文档规定了一个简单的基于 HMAC 的 KDF, 名为 HKDF, 它可以作为各种协议和应用程序中的 (加密) 组件, 并且已经在几个 IETF 协议中使用, 包括 IKEv2、PANA 和 EAP-AKA. 本文档旨在以通用化的方式, 促进其在未来协议和应用中的采纳, 并防止多种 KDF 机制的无序发展. 它不是要求更改现有协议的呼吁, 也不会更改或更新使用此 KDF 的现有规范.&lt;&#x2F;p&gt;
&lt;p&gt;HKDF 遵循 “提取然后扩展” (extract-then-expand) 的范式, 其中 KDF 在逻辑上由两个模块组成. 第一阶段获取输入密钥材料并从中 “提取” 固定长度的伪随机密钥 K. 第二阶段将密钥 K “扩展” 为几个额外的伪随机密钥 (KDF 的输出).&lt;&#x2F;p&gt;
&lt;p&gt;在许多应用程序中, 输入密钥材料不一定均匀分布, 攻击者可能对其有部分了解 (例如, 由密钥交换协议计算的 Diffie-Hellman 值), 甚至对其有部分控制 (如在一些熵收集应用程序中). 因此, “提取” 阶段的目标是将输入密钥材料可能分散的熵 “集中” 到一个短而高强度的&lt;strong&gt;伪随机密钥&lt;&#x2F;strong&gt; (pseudorandom key, &lt;strong&gt;PRK&lt;&#x2F;strong&gt;) 中. 在一些应用程序中, 输入可能已经是一个良好的 PRK; 在这些情况下, “提取” 阶段不是必需的, “扩展” 部分可以单独使用. 第二阶段, 将伪随机密钥 “扩展” 到所需长度; 输出密钥的数量和长度取决于需要密钥的特定加密算法.&lt;&#x2F;p&gt;
&lt;p&gt;请注意, 一些现有的 KDF 规范要么只考虑第二阶段, 要么没有明确区分 “提取” 和 “扩展” 阶段, 经常导致设计缺陷. 本规范的目标是适应广泛的 KDF 需求, 同时最小化对底层哈希函数的假设. 提取然后扩展“ 范式很好地支持了这一目标.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-hkdf&quot;&gt;2. HKDF&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;2-1-fu-hao-xi-tong&quot;&gt;2.1. 符号系统&lt;&#x2F;h4&gt;
&lt;p&gt;HMAC-Hash 表示使用哈希函数 “Hash” 的 HMAC 函数. HMAC 始终有两个参数: 第一个是密钥, 第二个是输入(或消息). (请注意, 在提取步骤中,  IKM 用作 HMAC 输入, 而不是 HMAC 密钥.)&lt;&#x2F;p&gt;
&lt;p&gt;当消息由多个元素组成时, 我们在第二个参数中将其串接 (表示为 |); 例如, HMAC(K, elem1 | elem2 | elem3).&lt;&#x2F;p&gt;
&lt;p&gt;本文档中的关键词 “必须” (“MUST” &#x2F; “SHALL” &#x2F; “REQUIRED”)、“绝不能” (“MUST NOT” &#x2F; “SHALL NOT”), “推荐” (“SHOULD” &#x2F; “RECOMMENDED”)、“不推荐” (“SHOULD NOT” &#x2F; “NOT RECOMMENDED”), “可以&#x2F;可能&#x2F;可选” (“MAY” &#x2F; “OPTIONAL”) 按照 BCP 14, RFC 2119 中的描述进行解释.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;2-2-di-yi-bu-ti-qu-extract&quot;&gt;2.2. 第一步: 提取 (extract)&lt;&#x2F;h4&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;HKDF-Extract(salt, IKM) -&amp;gt; PRK
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可选:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Hash&lt;&#x2F;code&gt;: 用于 HMAC 的哈希函数, HashLen 是其输出长度 (以字节为单位);&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;入参:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;salt&lt;&#x2F;code&gt;: 可选的随机盐值 (一个非秘密随机值); 如果未提供, 则将其设置为 HashLen 字节的零值;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;IKM&lt;&#x2F;code&gt;: 输入密钥材料 (input key material);&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;输出:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PRK&lt;&#x2F;code&gt;: 输出的长度为 HashLen 字节的伪随机密钥 (pseudorandom key).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;PRK 由以下公式计算:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PRK = HMAC-Hash(salt, IKM)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;2-3-di-er-bu-kuo-zhan-expand&quot;&gt;2.3. 第二步: 扩展 (expand)&lt;&#x2F;h4&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;HKDF-Expand(PRK, info, L) -&amp;gt; OKM
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可选:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Hash&lt;&#x2F;code&gt;: 用于 HMAC 的哈希函数, HashLen 是其输出长度 (以字节为单位);&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;入参:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PRK&lt;&#x2F;code&gt;: 伪随机密钥 (pseudorandom key), 长度至少为 HashLen 字节 (通常, 这是由 HKDF-Extract 生成的输出);&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;info&lt;&#x2F;code&gt;: 可选的上下文和应用程序特定信息 (可以是零长度);&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;L&lt;&#x2F;code&gt;: 所需输出密钥材料 (output keying material) 的长度 (以字节为单位); L 必须小于或等于 255 * HashLen;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;输出:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OKM&lt;&#x2F;code&gt;: 输出的密钥材料 (output keying material), 长度为 L 字节;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;OKM 由以下公式计算:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;N = ceil(L&#x2F;HashLen)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;T = T(1) | T(2) | T(3) | ... | T(N)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;OKM = first L bytes of T
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;其中:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;T(0) = empty string (zero length)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;3-hkdf-de-shi-yong-zhu-yi-shi-xiang&quot;&gt;3. HKDF 的使用注意事项&lt;&#x2F;h3&gt;
&lt;p&gt;本节讨论 HKDF 的一些使用注意事项.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-1-guan-yu-yan-salt&quot;&gt;3.1. 关于盐 (salt)&lt;&#x2F;h4&gt;
&lt;p&gt;HKDF 被定义为可以在有或没有随机盐值的情况下运行. 这样做是为了适应那些无法获得盐值的应用程序. 然而, 我们强调, 使用盐值可以显著增强 HKDF 的强度, 确保哈希函数不同用途之间的独立性, 支持 “源独立地” 提取, 并强化支持 HKDF 设计的分析结果.&lt;&#x2F;p&gt;
&lt;p&gt;随机盐值与初始密钥材料在两个方面有根本不同: 它不是保密的, 并且可以重复使用. 因此, 许多应用程序都可以应用盐值. 例如, 一个通过将 HKDF 应用于可再生熵池 (例如, 采样的系统事件) 来持续生成输出的伪随机数生成器 (PRNG) 可以固定一个盐值, 并将其用于 HKDF 的多个应用, 而无需保护盐值的秘密性. 在另一个应用领域, 一个从 Diffie-Hellman 交换中导出加密密钥的密钥协商协议可以从通信双方作为密钥协商一部分交换和认证的公开随机数中导出盐值 (这是 IKEv2 中采用的方法).&lt;&#x2F;p&gt;
&lt;p&gt;理想情况下, 盐值是长度为 HashLen 的随机或伪随机字符串. 然而, 即使是质量较低的盐值 (尺寸较短或熵有限) 也可能对输出密钥材料的安全性做出显著贡献; 因此, 鼓励应用程序设计者在应用程序能够获取此类值时, 向 HKDF 提供盐值.&lt;&#x2F;p&gt;
&lt;p&gt;值得注意的是, 虽然不常见, 但某些应用程序甚至可能拥有保密的盐值; 在这种情况下, HKDF 提供了更强的安全保证. 这种应用程序的一个例子是 IKEv1 的 “公钥加密模式”, 其中提取器的 “盐值” 是从秘密随机数计算出来的; 类似地, IKEv1 的预共享模式使用从预共享密钥派生的秘密盐值.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-2-guan-yu-info&quot;&gt;3.2. 关于 info&lt;&#x2F;h4&gt;
&lt;p&gt;尽管 “info” 值在 HKDF 的定义中是可选的, 但它在应用程序中通常非常重要. 其主要目标是将派生密钥材料与应用程序和上下文特定的信息绑定. 例如, “info” 可能包含协议号、算法标识符、用户身份等. 特别是, 它可以防止为不同上下文派生相同的密钥材料 (当在这些不同上下文中使用相同的 IKM 时) . 如果需要, 它还可以容纳密钥扩展部分的额外输入 (例如, 应用程序可能希望将密钥材料与其长度 L 绑定, 从而使 L 成为 info 字段的一部分) . “info” 有一个技术要求: 它应该独立于 IKM.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-3-tiao-guo-huan-shi-bu-tiao-guo-ti-qu&quot;&gt;3.3. 跳过还是不跳过(提取)&lt;&#x2F;h4&gt;
&lt;p&gt;在某些应用程序中, IKM 可能已经作为密码学强度高的密钥存在 (例如, TLS RSA 密码套件中的预主密钥将是一个伪随机字符串, 除了前两个八位字节) . 在这种情况下, 可以跳过提取部分, 直接使用 IKM 作为扩展步骤中的密钥. 另一方面, 为了与一般情况兼容, 应用程序仍可能使用提取部分. 特别是, 如果 IKM 是随机的 (或伪随机的) , 但比 HMAC 密钥长, 则提取步骤可以用于输出一个合适的 HMAC 密钥 (在 HMAC 的情况下, 通过提取器进行这种缩短并非严格必要, 因为 HMAC 也被定义为可以处理长密钥) . 然而, 请注意, 如果 IKM 是 Diffie-Hellman 值, 如同 TLS 与 Diffie-Hellman 的情况, 则不应跳过提取部分. 这样做将导致使用 Diffie-Hellman 值 g^{xy} 本身 (它不是一个均匀随机或伪随机字符串) 作为 HMAC 的密钥 PRK. 相反, HKDF 应该将提取步骤应用于 g^{xy} (最好带有一个盐值) , 并使用生成的 PRK 作为 HMAC 在扩展部分中的密钥.&lt;&#x2F;p&gt;
&lt;p&gt;在所需的密钥位数 L 不超过 HashLen 的情况下, 可以直接将 PRK 截断获得最终的 OKM. 然而, 不推荐这样做, 特别是因为它会省略在派生过程中使用的 “info” (也不建议将 “info” 作为输入添加到提取步骤中) .&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-4-du-li-xing-de-zuo-yong&quot;&gt;3.4. 独立性的作用&lt;&#x2F;h4&gt;
&lt;p&gt;KDF 的分析假设 IKM 来自某个源, 该源被建模为特定长度比特流的概率分布 (例如, 由熵池产生的流, 从随机选择的 Diffie-Hellman 指数派生的值等) ; IKM 的每个实例都是该分布中的一个样本. 密钥派生函数的一个主要目标是确保, 当将 KDF 应用于从 (相同) 源分布中抽取的任意两个值 IKM 和 IKM’ 时, 生成的密钥 OKM 和 OKM’ 在本质上是相互独立的 (在统计或计算意义上) . 为了实现这一目标, KDF 的输入必须从适当的输入分布中选择, 并且输入也必须相互独立选择 (从技术上讲, 即使以 KDF 的其他输入为条件, 每个样本也必须具有足够的熵) .&lt;&#x2F;p&gt;
&lt;p&gt;独立性也是提供给 KDF 的盐值的一个重要方面. 虽然没有必要对盐值保密, 并且同一个盐值可以与多个 IKM 值一起使用, 但假定盐值独立于输入密钥材料. 特别是, 应用程序需要确保盐值不是由攻击者选择或操纵的. 例如, 考虑 (如 IKE 中) 盐值是从密钥交换协议中各方提供的随机数派生的情况. 在协议使用此类盐值派生密钥之前, 它需要确保这些随机数被认证为来自合法方, 而不是由攻击者选择的 (在 IKE 中, 例如, 这种认证是经过认证的 Diffie-Hellman 交换的一个组成部分).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-hkdf-de-ying-yong&quot;&gt;4. HKDF 的应用&lt;&#x2F;h3&gt;
&lt;p&gt;HKDF 旨在用于各种 KDF 应用. 这些应用包括从弱随机性来源获得伪随机性; 在密钥协商协议中从共享的 Diffie-Hellman 值导出加密密钥; 从混合公钥加密方案导出对称密钥; 用于密钥封装机制的密钥导出; 以及更多. 所有这些应用都可以受益于 HKDF 的简洁性和多功能性, 以及其分析基础.&lt;&#x2F;p&gt;
&lt;p&gt;另一方面, 预计某些应用由于特定的操作要求将无法 “按原样” 使用 HKDF, 或者可以使用但无法充分发挥该方案的全部优势. 一个重要的例子是从低熵源 (例如用户密码) 导出加密密钥. HKDF 中的提取步骤可以集中现有熵, 但不能放大熵. 对于基于密码的 KDF, 主要目标是使用两个要素来减缓字典攻击: 一个盐值, 以及有意减慢密钥导出计算. HKDF 自然支持使用盐; 然而, 减速机制并非本规范的一部分. 对基于密码的 KDF 感兴趣的应用应考虑例如 PKCS5 是否比 HKDF 更能满足其需求.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-an-quan-zhu-yi-shi-xiang&quot;&gt;5. 安全注意事项&lt;&#x2F;h3&gt;
&lt;p&gt;尽管 HKDF 组成简单, 但在其设计和分析中已考虑了许多安全因素. 对所有这些方面的阐述超出了本文档的范围.&lt;&#x2F;p&gt;
&lt;p&gt;相关论文已投入大量精力, 对 HKDF 作为一种多用途 KDF 进行了密码学分析, 该分析在利用加密哈希函数的方式上非常谨慎. 鉴于我们对当前哈希函数强度的信心有限, 这一点尤为重要. 然而, 这项分析并不意味着任何方案的绝对安全性, 并且它在很大程度上取决于底层哈希函数的强度和建模选择. 尽管如此, 它有力地表明了 HKDF 设计的正确结构及其相对于其他常见 KDF 方案的优势.&lt;&#x2F;p&gt;
&lt;p&gt;(余下内容略去不译)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;an-quan-sui-ji-shu&quot;&gt;安全随机数&lt;&#x2F;h2&gt;
&lt;p&gt;密码学中, 随机性 (熵) 起着非常重要的作用. 在许多算法中, 我们需要随机的 (不可预测的) 数字. 如果这些数字不是不可预测的, 算法就会受到损害.&lt;&#x2F;p&gt;
&lt;p&gt;在计算机科学中, 随机数通常来自伪随机数生成器 (pseudo-random numbers generators, PRNG), 由一些初始随机性初始化, 这些初始随机性一般来自极难预测的用户输入等, 可从操作系统读取, 如 Linux 的 &lt;code&gt;&#x2F;dev&#x2F;random&lt;&#x2F;code&gt;;
在密码学中, 使用安全 PRNG, 称为 CSPRNG, 它通常将熵与 PRNG 和其他技术相结合, 使生成的随机性在密码学意义上不可预测.&lt;&#x2F;p&gt;
&lt;p&gt;应当注意, 获取安全随机数的开销通常是比较大的, 而许多语言提供的随机数生成函数并非 CSPRNG (例如 Python 的 &lt;code&gt;random&lt;&#x2F;code&gt; 库), 而是基于时间戳等可预测的种子获得的伪随机数, 不能用于密码学场景.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;xiao-jie&quot;&gt;小结&lt;&#x2F;h2&gt;
&lt;p&gt;本文介绍了 MAC、HMAC、KDF 和 HKDF 的基本概念及其应用. 除却这四个术语外, 以下再摘抄若干术语、表达式供读者快速回顾:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HMAC = H(key || H(key || msg))&lt;&#x2F;code&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;AEAD&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PRK = HKDF-Extract(salt, IKM)&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;OKM = HKDF-Expand(PRK, info, L)&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;在阅读完本文后, 我们应该对上述表达式及相关术语不再陌生.&lt;&#x2F;p&gt;
</content>
        <summary type="html">本文介绍了 MAC (消息认证码)、HMAC (哈希消息认证码) 和 KDF (密钥派生函数), 着重讲解了 HMAC 和 HKDF 的工作原理及其应用场景.</summary>
        </entry><entry xml:lang="zh">
        <title>从零开始了解 TLS 1.3 系列笔记（三）—— 实用密码学之「哈希函数」</title>
        <published>2025-09-14T00:00:00+00:00</published>
        <updated>2026-02-10T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-09-14-tls-series-cryptography-2/" type="text/html"/>
        <id>https://i.han.rs/blog/25-09-14-tls-series-cryptography-2/</id>
        
            <content type="html">&lt;h2 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h2&gt;
&lt;p&gt;在本章中, 我们将介绍哈希函数的基本概念, 以及常见的安全哈希算法. 哈希函数在 TLS 协议中被广泛使用, 例如在数字签名算法、消息认证码 (MAC) 和密钥派生函数 (KDF) 中都有应用.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ha-xi-han-shu-yu-jia-mi-ha-xi-han-shu&quot;&gt;哈希函数与加密哈希函数&lt;&#x2F;h2&gt;
&lt;p&gt;在计算机编程中, 哈希函数将输入不可逆地映射到一个整数. 通常不同的输入映射到不同的输出; 在密码学中, 哈希函数将任意大小的输入数据转换为因算法而异的固定大小的结果, 该结果称为哈希值. 计算机密码学中使用的哈希函数称为 “加密哈希函数”.&lt;&#x2F;p&gt;
&lt;p&gt;对于哈希函数, 我们期待不同输入映射到不同输出, 但由于输入空间无限而输出空间有限, 不可避免地会发生冲突 (也称哈希碰撞); 而对于一个合格的加密哈希函数, 我们要求这个冲突的发生概率非常非常低, 以至于在实际应用中可以忽略不计.&lt;&#x2F;p&gt;
&lt;p&gt;一个理想的加密哈希函数应当具有如下特性:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;确定性&lt;&#x2F;strong&gt;, 即对同样的输入, 应该总是产生同样的输出;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;快速&lt;&#x2F;strong&gt;, 计算速度要足够快;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;难以分析&lt;&#x2F;strong&gt;, 即对输入的任何微小改动, 都应该使输出完全发生变化;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;不可逆&lt;&#x2F;strong&gt;, 即从其哈希值逆向演算出输入值应该是不可行的, 这意味着没有比暴力破解更好的破解方法;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;抗碰撞&lt;&#x2F;strong&gt;, 即几乎不可能找到具有相同哈希值的两条不同消息.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;现代加密哈希函数, 如 SHA-2 和 SHA-3, 都至少在当前已知的攻击方法下满足上述特性.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jia-mi-ha-xi-han-shu-de-ying-yong&quot;&gt;加密哈希函数的应用&lt;&#x2F;h2&gt;
&lt;p&gt;加密哈希函数有很多应用, 其中最常见的包括:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;数据完整性校验&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;这是加密哈希函数最常见的应用, 此时的哈希值作为 “校验和” (checksum). 例如, 当从互联网下载文件, 下载完毕后计算文件字节流的 SHA256 校验和跟官方公布的一致, 那就说明文件没有损坏.&lt;&#x2F;p&gt;
&lt;p&gt;但需要指出: 哈希函数&lt;strong&gt;自身不能保证文件的真实性&lt;&#x2F;strong&gt;, 目前来讲, 从网上下载的文件的真实性通常是 TLS 协议要保证的, 它确保你看到的网站所公布的「SHA256 校验和」是未被篡改的.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;密码存储&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;加密哈希函数还被用于密码的安全存储, 以避免因直接存储明文密码、而某日数据库泄露导致密码泄露的问题.&lt;&#x2F;p&gt;
&lt;p&gt;应当使用专门设计的安全哈希算法, 如 Scrypt, 计算用户密码的哈希值再持久化保存. 这种算法往往被设计为 CPU 密集和&#x2F;或内存密集的, 以及难以并行化的, 提高单次计算的耗时, 以增加暴力破解的成本.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;唯一 ID&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;将任意长的文件内容哈希化生成一个定长的唯一的 ID, 用以索引等.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;伪随机数生成&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;哈希函数也可以用来生成伪随机数, 例如, 可以将一个随机种子值(从随机事件中收集熵, 如随机的鼠标移动或键盘输入)并附加一个值然后哈希化, 得到一个伪随机数. 可将结果附加一个值再哈希化, 得到另一个伪随机数. Linux 内核的 &lt;code&gt;&#x2F;dev&#x2F;urandom&lt;&#x2F;code&gt; 就是基于此原理实现的.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工作量证明 (PoW, Proof of Work) 算法&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;略.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;an-quan-ha-xi-suan-fa&quot;&gt;安全哈希算法&lt;&#x2F;h2&gt;
&lt;p&gt;以下简要介绍几种常见的安全哈希算法.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sha-2&quot;&gt;SHA-2&lt;&#x2F;h3&gt;
&lt;p&gt;依据哈希位数, 可分为 SHA-256, SHA-384 和 SHA-512 等若干变体. SHA-2 基于 “Merkle-Damgård 构造” , 目前被认为高度安全.&lt;&#x2F;p&gt;
&lt;p&gt;示例:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;plain&quot; class=&quot;language-plain z-code&quot;&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;SHA-256(&amp;#39;hello&amp;#39;) = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;SHA-384(&amp;#39;hello&amp;#39;) = 59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;SHA-512(&amp;#39;hello&amp;#39;) = 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;sha-3&quot;&gt;SHA-3&lt;&#x2F;h3&gt;
&lt;p&gt;比 SHA-2 更安全, 不容易遭受&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Length_extension_attack&quot;&gt;长度扩展攻击&lt;&#x2F;a&gt;; 在 TLS 1.3 中并未使用.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;blake2-blake3&quot;&gt;BLAKE2 &#x2F; BLAKE3&lt;&#x2F;h3&gt;
&lt;p&gt;BLAKE2 &#x2F; BLAKE3 安全性与 SHA-3 相当, 但针对现代硬件进行高度优化, 哈希速度比 SHA-3 快得多; 在 TLS 1.3 中并未使用.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;qi-ta&quot;&gt;其他&lt;&#x2F;h3&gt;
&lt;p&gt;如中国的商密系列 SM3 &#x2F; SM4 等.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bu-an-quan-de-ha-xi-han-shu&quot;&gt;不安全的哈希函数&lt;&#x2F;h2&gt;
&lt;p&gt;应当避免使用以下被认为不安全或安全性有争议的的哈希算法: MD2、MD4、MD5、SHA-0、SHA-1、Panama、HAVAL (有争议的安全性, HAVAL-128 发现冲突)、Tiger (有争议, 发现弱点)、SipHash (它不是加密哈希函数).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ben-zhang-xiao-jie&quot;&gt;本章小结&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;哈希函数将任意大小的输入数据不可逆地映射为固定大小的结果, 该结果称为哈希值.&lt;&#x2F;li&gt;
&lt;li&gt;加密哈希函数要求映射冲突的发生概率非常低, 以至于在实际应用中可以忽略不计, 保证无法伪造数据和原始数据具备一致的哈希值.&lt;&#x2F;li&gt;
&lt;li&gt;加密哈希函数只能保证数据完整性, 不能保证数据真实性.&lt;&#x2F;li&gt;
&lt;li&gt;现代哈希函数包括 SHA-2, SHA-3, BLAKE2 &#x2F; BLAKE3 等; 应当避免使用 MD5, SHA-1 等被认为不安全的哈希函数.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;bu-fen-can-kao-wen-xian&quot;&gt;部分参考文献&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nakov&#x2F;Practical-Cryptography-for-Developers-Book&quot;&gt;Practical Cryptography for Developers Book&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        <summary type="html">本文介绍了哈希函数的基本概念, 以及常见的安全哈希算法.</summary>
        </entry><entry xml:lang="zh">
        <title>从零开始了解 TLS 1.3 系列笔记（二）—— 实用密码学之「引言」</title>
        <published>2025-09-13T00:00:00+00:00</published>
        <updated>2026-02-10T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-09-13-tls-series-cryptography-1/" type="text/html"/>
        <id>https://i.han.rs/blog/25-09-13-tls-series-cryptography-1/</id>
        
            <content type="html">&lt;h2 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h2&gt;
&lt;p&gt;TLS 协议是一种密码学实践, 其目标如其名曰 “传输层安全”, 在假设完全不可信的信道上提供安全的通信. 这要求 TLS 协议必须满足以下安全目标:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;确保数据不会被中间人获取;&lt;&#x2F;li&gt;
&lt;li&gt;确保数据不被中间人篡改;&lt;&#x2F;li&gt;
&lt;li&gt;确定对端的真实身份, 确保通信双方的身份可信.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;通俗地说, 我们需要维持消息的&lt;strong&gt;机密性&lt;&#x2F;strong&gt; (confidentiality), 以及消息的&lt;strong&gt;真实性&lt;&#x2F;strong&gt; (authenticity)、&lt;strong&gt;完整性&lt;&#x2F;strong&gt; (integrity) 和&lt;strong&gt;不可否认性&lt;&#x2F;strong&gt; (non-repudiation). 为此, TLS 协议使用了大量的密码学算法原语、组件, 如哈希函数, 对称加密算法, 非对称加密算法, 数字签名算法等. 在系统地理解 TLS 协议之前, 我们需要先了解这些密码学基础概念.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;shen-me-shi-mi-ma-xue&quot;&gt;什么是密码学&lt;&#x2F;h2&gt;
&lt;p&gt;密码学 (Cryptography) 是提供&lt;strong&gt;信息安全和保护&lt;&#x2F;strong&gt;的科学. 它在我们的数字世界中无处不在, 当你打开网站时、发送电子邮件时、连接到 WiFi 网络时, 使用账号密码登录 APP 时、使用二步认证验证码认证身份时, 都有涉及到密码学相关技术.
因此, 开发人员应该对密码学有基本的了解, 以避免写出不安全的代码. 至少也得知道如何使用密码算法和密码库, 了解哈希、对称密码算法、非对称密码算法与加密方案这些概念, 知晓数字签名及其背后的密码系统和算法.&lt;&#x2F;p&gt;
&lt;p&gt;以下密码学基础概念(术语)将在本文以及后续文章中频繁出现, 需要熟悉.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;熵 (entropy) 与安全随机数生成器.&lt;&#x2F;li&gt;
&lt;li&gt;哈希函数 (Hash Function, 也称散列函数), 如 MD5, SHA-1, SHA-2 (主要包括 SHA-256, SHA-384, SHA-512 三个变体) 等.&lt;&#x2F;li&gt;
&lt;li&gt;哈希消息认证码 (Hash-based Message Authentication Code, &lt;strong&gt;HMAC&lt;&#x2F;strong&gt;), 如 HMAC-SHA-256.&lt;&#x2F;li&gt;
&lt;li&gt;密钥派生函数 (Key Derivation Function, &lt;strong&gt;KDF&lt;&#x2F;strong&gt;), 如 Scrypt.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;密钥交换&lt;&#x2F;strong&gt;算法 (Key Exchange Algorithm), 如 Diffie-Hellman (DH) 和 ECDH (椭圆曲线 Diffie-Hellman).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;对称密钥加密&lt;&#x2F;strong&gt;方案 (Symmetric key encryption schemes), 如 AES-128-GCM.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;非对称密钥加密&lt;&#x2F;strong&gt;方案 (Asymmetric key encryption schemes), 如 RSA 和 ECC (elliptic curves-based cryptography, 椭圆曲线密码学).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;数字签名算法&lt;&#x2F;strong&gt; (Digital Signature Algorithm, DSA).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;后量子&lt;&#x2F;strong&gt; (Post-Quantum) 安全.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;如果你有过一些开发经验, 可能会很熟悉其中部分名词. 如果不熟也没任何关系, 都会在本系列文章内介绍, 请坐和放宽.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mi-ma-xue-de-yong-tu&quot;&gt;密码学的用途&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;shang-yu-an-quan-sui-ji-shu&quot;&gt;熵与安全随机数&lt;&#x2F;h3&gt;
&lt;p&gt;熵 (entropy, 指不可预测的随机性) 是密码学的重要部分, 大量算法的安全性都仰赖于具备足够熵的随机数.&lt;&#x2F;p&gt;
&lt;p&gt;计算机无法产生真正的随机数, 只能从外部获得 “真” 随机性:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Linux 系统下的 &lt;code&gt;&#x2F;dev&#x2F;urandom&lt;&#x2F;code&gt; 的随机性来源于硬件中断、按键输入、鼠标移动、磁盘 IO 等不可预测的系统事件;&lt;&#x2F;li&gt;
&lt;li&gt;Cloudflare 使用&lt;a href=&quot;https:&#x2F;&#x2F;www.cloudflare.com&#x2F;zh-cn&#x2F;learning&#x2F;ssl&#x2F;lava-lamp-encryption&#x2F;&quot;&gt;一面熔岩墙&lt;&#x2F;a&gt; 获得真随机性…&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;区别于真随机性, 通过特定算法从一定的随机性种子 (seed) 生成的随机性我们称之为伪随机性, 伪随机性显然由于随机性种子的来源有高低优劣之分. 密码学操作在需求随机性时, 往往要求使用安全伪随机数生成器 (Cryptographically secure pseudo-random number generator, CSPRNG), 基于具备足够熵的随机性种子生成高质量的伪随机数: 通过 CSPRNG 生成的随机数即便攻击者在统计学意义上能收集大量过往产生的随机数进行分析, 或者计算上通过特定手段探知得 CSPRNG 内部状态, 均无法推测出未来的随机数. 而大部分编程语言内置的伪随机数生成器出于性能因素, 在设计上是基于时间戳等可预测性强的随机数种子的, 不具备密码学安全性.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ha-xi-han-shu-yu-hmac&quot;&gt;哈希函数与 HMAC&lt;&#x2F;h3&gt;
&lt;p&gt;哈希函数, 如 MD5、SHA-1、SHA-2, SHA-3 和 BLAKE2 等, 将任意长消息&lt;strong&gt;不可逆地&lt;&#x2F;strong&gt;转换固定长度的哈希值, 称消息摘要 (digest). 哈希函数被设计为几乎不可能找到具有相同哈希值的两条不同消息, 违背这一点的哈希函数被认为是不安全的, 如 MD5 和 SHA-1.&lt;&#x2F;p&gt;
&lt;p&gt;基于哈希函数的特性, 可以将特定哈希函数用于验证消息完整性, 称 HMAC.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;kdf&quot;&gt;KDF&lt;&#x2F;h3&gt;
&lt;p&gt;文本密码绝大部分情况下不具备足够的熵, 不能直接作加密密钥. &lt;strong&gt;KDF&lt;&#x2F;strong&gt; (Key Derivation Function, 密钥派生函数) 负责从文本密码安全地派生出具备足够熵的密钥, 且往往还通过注入随机参数 (盐, salt) 和使用大量迭代和计算资源使暴力计算所有可能的密钥变得不可行. 常用的 KDF 包括 PBKDF2、Scrypt 和 Argon2 等.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mi-yao-jiao-huan&quot;&gt;密钥交换&lt;&#x2F;h3&gt;
&lt;p&gt;密钥交换算法、方案允许通信双方经由完全不可信的信道安全地协商共享密钥. TLS 中使用到的密钥交换算法包括传统 Diffie-Hellman (&lt;strong&gt;DH&lt;&#x2F;strong&gt;) 和基于椭圆曲线的 &lt;strong&gt;ECDH&lt;&#x2F;strong&gt; (Elliptic Curve DH), 以及后量子安全的密钥交换算法 (如 &lt;strong&gt;ML-KEM&lt;&#x2F;strong&gt;).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;jia-jie-mi&quot;&gt;加解密&lt;&#x2F;h3&gt;
&lt;p&gt;密码学自诞生之日起最大用途, 就是进行数据的安全存储和安全传输. 按密钥数量, 可将加密算法分为两类:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对称 (Symmetric) 加密: 加密和解密&lt;strong&gt;使用相同的密钥&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;如 AES 和 ChaCha20.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;非对称 (Asymmetric) 加密: 加密和解密&lt;strong&gt;使用成对的不同的密钥&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;非对称加密用到的两个密钥在数学上是关联的, 我们称其为 “&lt;strong&gt;密钥对&lt;&#x2F;strong&gt;”; 其中一把密钥公开, 另一把密钥保密, 我们分别称其为 “&lt;strong&gt;公钥&lt;&#x2F;strong&gt;” 和 “&lt;strong&gt;私钥&lt;&#x2F;strong&gt;”; 公钥可以由私钥计算得到, 但反过来则几乎不可能实现.&lt;&#x2F;p&gt;
&lt;p&gt;非对称加密体系也称公钥密码学体系.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;一般而言, &lt;strong&gt;对称加密比非对称加密更快, 但非对称加密更安全&lt;&#x2F;strong&gt;. TLS 等现代加密协议通常会结合使用对称和非对称加密, 以平衡最佳的安全性和性能.&lt;&#x2F;p&gt;
&lt;p&gt;单纯使用加密算法是不够的, 这是因为有的加密算法只能按块进行加密, 而且很多加密算法并不能保证密文的真实性、完整性. 因此, 现实中我们通常会使用一套&lt;strong&gt;加密方案&lt;&#x2F;strong&gt;进行数据的加解密. 加密方案由一系列加密算法、消息认证算法或数字签名算法、块密码模式等多种密码学原语组合得到, 以同时保证数据的安全性、真实性、完整性;加密方案的名称就是使用到的各种密码算法名称的组合, 如 AES-128-GCM、ChaCha20-Poly1305 等. 后面我们再详细了解.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;shu-zi-qian-ming-he-xiao-xi-ren-zheng&quot;&gt;数字签名和消息认证&lt;&#x2F;h3&gt;
&lt;p&gt;密码学当中, 数字签名算法 (Digital Signature Algorithm, DSA) 算法和消息认证 (Message Authentication) 算法提供了对消息真实性、完整性和不可否认性的保证.&lt;&#x2F;p&gt;
&lt;p&gt;DSA 首先是个公钥密码学体系, 大多数数字签名算法使用非对称加密原语: 使用私钥加密特定信息, 此即&lt;strong&gt;签名&lt;&#x2F;strong&gt;; 由相应的公钥解密, 此即&lt;strong&gt;验签&lt;&#x2F;strong&gt; (保证双方持有合法的密钥对).&lt;&#x2F;p&gt;
&lt;p&gt;MAC 跟数字签名的功能实际上是一致的, 区别在于其使用哈希算法 (HMAC), 或者对称加密原语.&lt;&#x2F;p&gt;
&lt;p&gt;现代加密协议通常会结合使用数字签名和消息认证.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hun-yao-yu-kuo-san&quot;&gt;混淆与扩散&lt;&#x2F;h2&gt;
&lt;p&gt;在密码学当中, 香农提出的「混淆」 (confusion) 与「扩散」 (diffusion) 是设计安全密码学算法的两个原则.&lt;&#x2F;p&gt;
&lt;p&gt;「混淆」使密文和对称加密中密钥的映射关系变得尽可能的复杂, 使之难以分析. 如果使用了混淆, 那么输出密文中的每个比特位都应该依赖于密钥和输入数据的多个部分, 确保两者无法建立直接映射.  混淆常用的方法是「替换」与「排列」.&lt;&#x2F;p&gt;
&lt;p&gt;「扩散」将明文的统计结构扩散到大量密文中, 隐藏明文与密文之间的统计学关系. 使单个明文或密钥位的影响尽可能扩大到更多的密文中去, 确保改变输入中的任意一位都应该导致输出中大约一半的位发生变化, 反过来改变输出密文的任一位, 明文中大约一半的位也必须发生变化. 扩散常用的方法是「置换」.&lt;&#x2F;p&gt;
&lt;p&gt;这两个原则被包含在大多数哈希函数、MAC 算法、随机数生成器、对称和非对称密码算法中.&lt;&#x2F;p&gt;
&lt;p&gt;值得指出, “XOR 加密” 的说法本质上是错误的. XOR 操作本身并不提供混淆或扩散.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-ku-de-shi-yong&quot;&gt;关于库的使用&lt;&#x2F;h2&gt;
&lt;p&gt;程序员常自嘲为 Ctrl-C &#x2F; Ctrl-V 工程师, 但在密码学领域反而推荐那么干, 以获得经大量实践验证的当下最优的安全性. 当然, 盲目地从互联网复制粘贴代码也可能会导致安全问题; 曾经安全的代码、算法或者最佳实践, 随着时间的推移也可能变得不再安全.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ben-zhang-xiao-jie&quot;&gt;本章小结&lt;&#x2F;h2&gt;
&lt;p&gt;密码学是一个庞大而复杂的领域, 在后续的文章中, 我们将继续深入探讨 TLS 1.3 协议这一密码学最佳实践. 虽然本章很短, 但不管怎么样, 通过本章内容先建立一个全局观念是极好的.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bu-fen-can-kao-wen-xian&quot;&gt;部分参考文献&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nakov&#x2F;Practical-Cryptography-for-Developers-Book&quot;&gt;Practical Cryptography for Developers Book&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.cloudflare.com&#x2F;zh-cn&#x2F;learning&#x2F;ssl&#x2F;lava-lamp-encryption&#x2F;&quot;&gt;Cloudflare: 熔岩灯如何为 Internet 加密助力？&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        </entry><entry xml:lang="zh">
        <title>从零开始了解 TLS 1.3 系列笔记（一）—— 导语</title>
        <published>2025-09-12T00:00:00+00:00</published>
        <updated>2026-02-09T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-09-12-tls-series-intro/" type="text/html"/>
        <id>https://i.han.rs/blog/25-09-12-tls-series-intro/</id>
        
            <content type="html">&lt;h2 id=&quot;dao-yu&quot;&gt;导语&lt;&#x2F;h2&gt;
&lt;p&gt;笔者一直认为, 个人是难以面面俱到地从零设计一个安全协议的: 密码学算法固然重要, 但错误的密码学实践与不安全的算法具有同等危害; 总有人试图自创加密协议, 但结果往往是造方形轮子乃至破轮子, 不如直接使用成熟的协议来得安全可靠. Transport Layer Security (TLS, &lt;em&gt;传输层安全&lt;&#x2F;em&gt;) 协议可谓现代互联网安全的基石, 经历数十年的发展, 在实践中缝缝补补、推陈出新, 如今来到了 1.3 版本. 出于实际需求, 笔者最近半年的空余时间都在阅读 &lt;a href=&quot;https:&#x2F;&#x2F;www.ietf.org&#x2F;archive&#x2F;id&#x2F;draft-ietf-tls-rfc8446bis-14.html&quot;&gt;draft-ietf-tls-rfc8446bis&lt;&#x2F;a&gt; (即 &lt;a href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc8446&quot;&gt;RFC8446&lt;&#x2F;a&gt;), 并结合 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rustls&#x2F;rustls&quot;&gt;rustls&lt;&#x2F;a&gt; 源码研究 TLS 1.3 实现. 苦于互联网关于 TLS 1.3 的中文内容相当零散, 遂开个新坑, 从零开始介绍(&lt;del&gt;学习&lt;&#x2F;del&gt;) TLS 1.3 协议的实现细节, 一方面方便笔者自己随时查阅, 另一方面也方便后来者参考学习, 减轻阅读晦涩的英文资料的烦恼.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ben-xi-lie-nei-rong&quot;&gt;本系列内容&lt;&#x2F;h2&gt;
&lt;p&gt;本系列按照以下大纲进行撰写:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;密码学应用基础.&lt;&#x2F;li&gt;
&lt;li&gt;RFC 8446 规范详解.&lt;&#x2F;li&gt;
&lt;li&gt;TLS 协议拓展, 包括但不限于:
&lt;ul&gt;
&lt;li&gt;ECH, 见 &lt;a href=&quot;https:&#x2F;&#x2F;www.ietf.org&#x2F;archive&#x2F;id&#x2F;draft-ietf-tls-esni-25.html&quot;&gt;draft-ietf-tls-esni&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;实战: 从零开始, 实现一个简易但 Production Ready 的 TLS 1.3 加密库 (in Rust).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;由于涉及的内容繁杂且笔者精力有限只能抽空闲时间学习、总结, 预计这个系列在一年内完成. 欢迎各位读者参考学习、批评指正. 感兴趣的读者可以点击 Tag 订阅 RSS.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;can-kao-wen-xian&quot;&gt;参考文献&lt;&#x2F;h2&gt;
&lt;p&gt;笔者在编写此系列文章的过程中学习参考了大量资料, 在此列出质量较高的部分, 以飨读者:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;halfrost.com&#x2F;tag&#x2F;https&#x2F;&quot;&gt;冰霜之地 (Halfrost)&lt;&#x2F;a&gt; 分享的系列文章&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;chaochaogege.com&quot;&gt;超超哥哥 (taikulawo)&lt;&#x2F;a&gt; 分享的文章&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;thiscute.world&#x2F;&quot;&gt;於清樂 (thiscute)&lt;&#x2F;a&gt; 分享的翻译文章
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nakov&#x2F;Practical-Cryptography-for-Developers-Book&quot;&gt;Practical Cryptography for Developers Book&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;…&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        </entry><entry xml:lang="zh">
        <title>This Week In Rust (#613) 速报</title>
        <published>2025-08-23T00:00:00+00:00</published>
        <updated>2025-08-23T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-08-23-twir-meme/" type="text/html"/>
        <id>https://i.han.rs/blog/25-08-23-twir-meme/</id>
        
            <content type="html">&lt;p&gt;最新一期 This Week In Rust (#613) 新鲜出炉: &lt;a href=&quot;https:&#x2F;&#x2F;this-week-in-rust.org&#x2F;blog&#x2F;2025&#x2F;08&#x2F;20&#x2F;this-week-in-rust-613&#x2F;&quot;&gt;This Week in Rust #613&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;updates-from-rust-community&quot;&gt;Updates from Rust Community&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;official&quot;&gt;Official&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;blog.rust-lang.org&#x2F;2025&#x2F;08&#x2F;19&#x2F;demoting-x86-64-apple-darwin-to-tier-2-with-host-tools&#x2F;&quot;&gt;Demoting x86_64-apple-darwin to Tier 2 with host tools&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;评: 光阴似箭, M1 已经是五年前的产物, 在苹果的强力推动下, MacOS 生态对 ARM 架构的支持已经相当完善. 笔者表示, 不愧是苹果.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;仍然在使用搭载 Intel 处理器的 x86_64 架构的苹果电脑的人越来越少, 自然也就难以继续维持 Tier 1 的支持了.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Leadership Council September 2025 Representative Selections&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Electing new Project Directors 2025&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;newsletters&quot;&gt;Newsletters&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;This Month in Rust OSDev: July 2025&lt;&#x2F;li&gt;
&lt;li&gt;The Embedded Rustacean Issue #52&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;project-tooling-updates&quot;&gt;Project&#x2F;Tooling Updates&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zed.dev&#x2F;blog&#x2F;windows-progress-report&quot;&gt;Zed for Windows: What’s Taking So Long?!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;评: 笔者表示, 已经受不了 VSCode 动辄几个 GB 的内存占用了… 期待 Zed 能够早日发布 Windows 版本.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;SeaQuery just made writing raw SQL more enjoyable (&lt;em&gt;数据库&lt;&#x2F;em&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;r3bl-cmdr v0.0.22 (&lt;em&gt;TUI&lt;&#x2F;em&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;r3bl_tui v0.7.4 (&lt;em&gt;TUI&lt;&#x2F;em&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Heapless v0.9.1 - static friendly data structures that don’t require dynamic memory allocation (&lt;em&gt;嵌入式开发&lt;&#x2F;em&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;asterinas.github.io&#x2F;2025&#x2F;08&#x2F;04&#x2F;announcing-asterinas-0.16.0.html&quot;&gt;Announcing Asterinas 0.16.0&lt;&#x2F;a&gt; (&lt;em&gt;操作系统内核, 仓库创始人是国人来着, 文档有中文, 泪目&lt;&#x2F;em&gt;)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;observations-thoughts&quot;&gt;Observations&#x2F;Thoughts&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;blog.yoshuawuyts.com&#x2F;placing-arguments&#x2F;&quot;&gt;Placing Arguments&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;此文介绍了可能在未来引入标准库的 &lt;code&gt;#[placing]&lt;&#x2F;code&gt; 属性的 API 设计. &lt;code&gt;#[placing]&lt;&#x2F;code&gt; 主要是为了解决实现返回类型在调用方的堆栈帧中构造的问题.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;tweedegolf.nl&#x2F;en&#x2F;blog&#x2F;160&#x2F;update-on-our-advocacy-for-memory-safety&quot;&gt;Update on our advocacy for memory-safety - Tweede golf&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;TL, DR: please use Rust for memory safety!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;nnethercote.github.io&#x2F;2025&#x2F;08&#x2F;16&#x2F;speed-wins-when-fuzzing-rust-code-with-derive-arbitrary.html&quot;&gt;Speed wins when fuzzing Rust code with #[derive(Arbitrary)]&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;TL, DR: 请升级 &lt;code&gt;arbitrary&lt;&#x2F;code&gt; 到 1.4.2, 性能大提升!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;blog.numaproj.io&#x2F;rewriting-numaflows-data-plane-a-foundation-for-the-future-a64fd2470cf0&quot;&gt;Rewriting Numaflow’s Data Plane: A Foundation for the Future&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Numaflow 是一个开源 K8s 原生平台, 将使用 Rust 重构控制平面&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;其实国内也有个中科院软研所主办的 &lt;a href=&quot;https:&#x2F;&#x2F;r2cn.dev&#x2F;&quot;&gt;R2CN（RISC-V Rust for Cloud Native）开源实习计划&lt;&#x2F;a&gt;, 旨在推动 RISC-V 生态和 Rust 在云原生领域的应用, 仓库是 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;r2cn-dev&#x2F;rk8s&quot;&gt;rk8s&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Terminal sessions you can bookmark: Building Zellij’s web client&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Zellij is a workspace aimed at developers, ops-oriented people and anyone who loves the terminal. Similar programs are sometimes called “Terminal Multiplexers”.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;forgestream.idverse.com&#x2F;blog&#x2F;20250814-testing-failure-modes&#x2F;&quot;&gt;Testing failure modes using error injection&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;使用错误注入测试故障模式&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;system.joekain.com&#x2F;2025&#x2F;08&#x2F;17&#x2F;ownership-driven-debugger-design.html&quot;&gt;Multiple Breakpoints in Rust: Ownership-Driven Debugger Design&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;对调试器开发感兴趣的可以看看&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;deepcausality.com&#x2F;blog&#x2F;lessons-learned-from-rewriting-ultragraph&#x2F;&quot;&gt;Lessons learned from rewriting the UltraGraph crate&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ideas.reify.ing&#x2F;en&#x2F;blog&#x2F;scientific-computing-in-rust-with-pytorch&#x2F;&quot;&gt;Scientific Computing in Rust&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Rust 做科学计算么, 有意思&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;r2cn.dev&#x2F;blog&#x2F;rkl-a-docker-like-command-line-interface-built-in-rust&quot;&gt;RKL: A Docker-like Command-line Interface Built in Rust&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;巧了, 刚才才提到 R2CN. &lt;del&gt;就是, 得把国人用英文写的文章再翻回去么, 有意思&lt;&#x2F;del&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;pwy.io&#x2F;posts&#x2F;kruci-post-mortem&#x2F;&quot;&gt;kruci: Post-mortem of a UI library&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;&lt;del&gt;TL, DR: 嫌弃 rataui 性能不行, 作者重写了一个&lt;&#x2F;del&gt; 感兴趣的可以尝鲜.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@carlmkadie&#x2F;nine-rules-for-generalizing-your-rust-library-part-2-92bb899d47ef&quot;&gt;Nine Rules for Generalizing Your Rust Library: Lessons from Extending RangeSetBlaze to Maps (Part 2)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;好文, 已加入 TWIR 翻译计划 (咕咕咕)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;[audio] Intrusive lists for fun and profit&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;rust-walkthroughs&quot;&gt;Rust Walkthroughs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;blog.cuongle.dev&#x2F;p&#x2F;constructor-best-practices-in-rust&quot;&gt;Constructor Best Practices in Rust&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;好文, 已加入 TWIR 翻译计划 (咕咕咕)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;hackeryarn.com&#x2F;post&#x2F;rust-macros-1&#x2F;&quot;&gt;Let’s write a macro in Rust - Part 1&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;好文, 已加入 TWIR 翻译计划 (咕咕咕)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;rumcajs.dev&#x2F;posts&#x2F;memory-analysis-in-rust&#x2F;&quot;&gt;Memory analysis in Rust&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;好文! 已加入 TWIR 翻译计划 (咕咕咕)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;miscellaneous&quot;&gt;Miscellaneous&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Rust At Microsoft And Chairing The Rust Foundation&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Talking To Zed Industries- Makers Of The 100% Rust, Super-Performant, Collaborative Code Editor&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;seanborg.tech&#x2F;blog&#x2F;huge-tutorial-list&#x2F;&quot;&gt;All the Rust Tutorials&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;收录了 Rust 开发各种 tutorial, 就是网站略为卡顿.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;July 2025 Rust Jobs Report&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;call-for-participation-projects-and-speakers&quot;&gt;Call for Participation; projects and speakers&lt;&#x2F;h2&gt;
&lt;p&gt;略.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;updates-from-the-rust-project&quot;&gt;Updates from the Rust Project&lt;&#x2F;h2&gt;
&lt;p&gt;简要列举几个我觉得很实用的:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;pull&#x2F;144922&quot;&gt;implement &lt;code&gt;#[derive(From)]&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;将于 Rust 1.91.0 推出. 说实在的, 不知道为什么拖了这么久.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;pull&#x2F;143717&quot;&gt;add Default impls for &lt;code&gt;Pin&lt;&#x2F;code&gt;ned &lt;code&gt;Box&lt;&#x2F;code&gt;, &lt;code&gt;Rc&lt;&#x2F;code&gt;, &lt;code&gt;Arc&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;将于 Rust 1.91.0 推出. 也是比较常用的了.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;pull&#x2F;141744&quot;&gt;stabilize &lt;code&gt;#![feature(ip_from)]&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;将于 Rust 1.91.0 推出. 这个也是咕了很久了, 还好我习惯使用 nightly Rust. 不知道为什么拖了这么久.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;upcoming-events&quot;&gt;Upcoming Events&lt;&#x2F;h2&gt;
&lt;p&gt;略.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="zh">
        <title>使用 Rust 全局变量的注意事项</title>
        <published>2025-08-02T00:00:00+00:00</published>
        <updated>2025-09-14T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-08-02-global-variables/" type="text/html"/>
        <id>https://i.han.rs/blog/25-08-02-global-variables/</id>
        
            <content type="html">&lt;p&gt;应当区分 &lt;code&gt;static&lt;&#x2F;code&gt; 和 &lt;code&gt;const&lt;&#x2F;code&gt; 关键字的作用, 正如其&lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;keyword.const.html&quot;&gt;文档&lt;&#x2F;a&gt;所言:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A static item is a value which is valid for the entire duration of your program (a &lt;code&gt;&#x27;static&lt;&#x2F;code&gt; lifetime).&lt;&#x2F;p&gt;
&lt;p&gt;静态变量是一个在程序的整个生命周期内有效的值 (拥有 &lt;code&gt;&#x27;static&lt;&#x2F;code&gt; 的生命周期).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;const&lt;&#x2F;code&gt; items look remarkably similar to &lt;code&gt;static&lt;&#x2F;code&gt; items, which introduces some confusion as to which one should be used at which times. To put it simply, constants are inlined wherever they’re used, making using them identical to simply replacing the name of the &lt;code&gt;const&lt;&#x2F;code&gt; with its value. Static variables, on the other hand, point to a single location in memory, which all accesses share. This means that, unlike with constants, they can’t have destructors, and act as a single value across the entire codebase.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;const&lt;&#x2F;code&gt; 与 &lt;code&gt;static&lt;&#x2F;code&gt; 看起来非常相似, 可能在使用上引起困惑. 简单来说, 常量在使用的地方都会被内联, 这使得使用它们就像简单地用其值替换 &lt;code&gt;const&lt;&#x2F;code&gt; 的名称一样. 另一方面, 静态变量指向内存中的一个单一位置, 所有访问都共享该位置. 这意味着, 与常量不同, 它们不能有析构函数, 并且在整个代码库中充当一个单一值.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;参考资料:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;13lsdq8&#x2F;global_statics_from_libraries&#x2F;&quot;&gt;reddit&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        </entry><entry xml:lang="zh">
        <title>译: Mimalloc Cigarette: Losing one week of my life catching a memory leak</title>
        <published>2025-07-26T00:00:00+00:00</published>
        <updated>2025-07-26T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-07-26-mimalloc-cigarette/" type="text/html"/>
        <id>https://i.han.rs/blog/25-07-26-mimalloc-cigarette/</id>
        
            <content type="html">&lt;blockquote&gt;
&lt;p&gt;本文翻译自 &lt;a href=&quot;https:&#x2F;&#x2F;pwy.io&#x2F;posts&#x2F;mimalloc-cigarette&#x2F;&quot;&gt;Mimalloc Cigarette: Losing one week of my life catching a memory leak&lt;&#x2F;a&gt;, 英文原文版权由原作者所有, 中文翻译版权遵照 CC BY-NC-SA 协议开放.&lt;&#x2F;p&gt;
&lt;p&gt;囿于译者自身水平, 译文虽已力求准确, 但仍可能词不达意, 欢迎批评指正.&lt;&#x2F;p&gt;
&lt;p&gt;2025 年 7 月 26 日晨, 于广州.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;details&gt;
    &lt;summary&gt;目录&lt;&#x2F;summary&gt;
    &lt;!-- toc --&gt;
&lt;&#x2F;details&gt;
&lt;p&gt;One of applications at my work has always been RAM-bound - it’s a pricing engine that basically loads tons of hotels, builds some in-memory indices and then allows you to issue queries like find me the cheapest hotel in berlin, pronto.&lt;&#x2F;p&gt;
&lt;p&gt;我工作中的一个应用程序一直受到 RAM 限制: 它是一个定价引擎, 基本上是加载大量酒店数据, 构建一些内存索引, 然后允许你发出查询, 比如快速找到柏林最便宜的酒店.&lt;&#x2F;p&gt;
&lt;p&gt;A pricing engine’s main purpose is to price engines hotels, but in order to do that effectively, there’s a lot of “meta work” involved, like:&lt;&#x2F;p&gt;
&lt;p&gt;定价引擎的主要目的是为酒店定价, 但为了有效地做到这一点, 涉及很多“元工作“, 比如：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Where do we load the data from, how do we do it?&lt;&#x2F;p&gt;
&lt;p&gt;我们从哪里加载数据, 如何加载？&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Do we load the entire dataset or just some parts of it?&lt;&#x2F;p&gt;
&lt;p&gt;我们是加载整个数据集还是只加载其中的一部分？&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Should we precalculate prices in order to speed-up the most popular queries?&lt;&#x2F;p&gt;
&lt;p&gt;我们是否应该预先计算价格以加速最受欢迎的查询？&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Such an engine poses an interesting technical challenge, even greater so when one day it starts OOMing on production, even though the entire dataset should fit in the memory multiple times…&lt;&#x2F;p&gt;
&lt;p&gt;这样的引擎带来了有趣的技术挑战, 当有一天它在生产环境中开始出现内存不足时, 挑战变得更大, 尽管整个数据集应该能在内存中放下多次…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hello-world&quot;&gt;Hello, World!&lt;&#x2F;h2&gt;
&lt;p&gt;Simplifying &lt;del&gt;a bit&lt;&#x2F;del&gt; a lot, what we’re dealing with is:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;del&gt;简要&lt;&#x2F;del&gt;大大简化一下, 我们处理的是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;State&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;hotels&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-support z-type z-rust&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Hotel&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;Hotel&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;prices&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-support z-type z-rust&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; ---
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-impl z-rust&quot;&gt;impl&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-impl z-rust&quot;&gt;State&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;Self&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;Self&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            hotels&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-rust&quot;&gt;load_hotels&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;collect&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;refresh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;for&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; hotel&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;in&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-rust&quot;&gt;load_hotels&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;enumerate&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-variable z-language z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;hotels&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; hotel&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;load_hotels&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; impl &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-support z-type z-rust&quot;&gt;Iterator&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Item = Hotel&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-range z-rust&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;1_000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;_&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;Hotel &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        prices&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-range z-rust&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;1_000_000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;n &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;as&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;f32&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;collect&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; ---
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; state &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;RwLock&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;State&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; Spawn a separate thread responsible for checking which hotels have
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; changed and need to be refreshed etc.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;thread&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; state &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;clone&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;state&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;move&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;||&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;loop&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;thread&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;sleep&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Duration&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;from_secs&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            state&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;write&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;refresh&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; Now in reality we start a Rocket server here, but for practical
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; purposes stalling the main thread will be sufficient.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;loop&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;thread&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;sleep&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Duration&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;from_secs&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Of course IRL there’s &lt;code&gt;ArcSwap&lt;&#x2F;code&gt; instead of &lt;code&gt;RwLock&lt;&#x2F;code&gt;, every hotel contains much more information (like taxes, discounts or supplements) etc., but we’ve got a reasonably good approximation here.&lt;&#x2F;p&gt;
&lt;p&gt;当然在现实生活中有 &lt;code&gt;ArcSwap&lt;&#x2F;code&gt; 而不是 &lt;code&gt;RwLock&lt;&#x2F;code&gt;, 每个酒店包含更多信息（如税费、折扣或附加费）等, 但我们在这里有一个相当好的近似.&lt;&#x2F;p&gt;
&lt;p&gt;Now, what if I told you that this code has totally different memory characteristics depending on which allocator you use?&lt;&#x2F;p&gt;
&lt;p&gt;现在, 如果我告诉你这段代码根据你使用的分配器有完全不同的内存特性, 你会怎么想？&lt;&#x2F;p&gt;
&lt;h2 id=&quot;practical-reasons-yuan-yin-fen-xi&quot;&gt;Practical Reasons | 原因分析&lt;&#x2F;h2&gt;
&lt;p&gt;Memory allocator is the piece of software invoked whenever your program needs to get hands on extra memory, like when you’re calling &lt;code&gt;Box::new()&lt;&#x2F;code&gt;. And memory allocation is quite a complex topic, with different solutions offering different trade-offs.&lt;&#x2F;p&gt;
&lt;p&gt;内存分配器是当你的程序需要获得额外内存时调用的软件, 比如当你调用 &lt;code&gt;Box::new()&lt;&#x2F;code&gt; 时. 内存分配是一个相当复杂的话题, 不同的解决方案提供不同的权衡.&lt;&#x2F;p&gt;
&lt;p&gt;Say, when implementing a firmware you might pick an allocator that works slower, because its implementation is just simpler and doesn’t occupy much space in the final binary.&lt;&#x2F;p&gt;
&lt;p&gt;比如说, 在实现固件时, 你可能会选择一个工作较慢的分配器, 因为它的实现更简单, 在最终二进制文件中不占用太多空间.&lt;&#x2F;p&gt;
&lt;p&gt;(some would argue that embeddeed programs shouldn’t allocate, yadda yadda, but you get the idea – replace firmware with wasm and you end up with the same problem.)&lt;&#x2F;p&gt;
&lt;p&gt;（有些人会争论说嵌入式程序不应该分配内存, 等等等等, 但你懂我意思: 把固件替换为 WASM, 你最终会遇到同样的问题. ）&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;mimalloc&lt;&#x2F;code&gt; is an allocator that fights tooth and nail for performance - and while most applications don’t have to worry about allocation performance, in our case internal benchmarks have proven it gives us extra 10% for free; when your response times have to be within milliseconds, this matters.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;mimalloc&lt;&#x2F;code&gt; 是一个为性能而生的分配器: 虽然大多数应用程序不必担心分配性能, 但在我们的情况下, 内部基准测试证明它无需额外配置即给我们带来额外的 10% 性能提升；当你的响应时间必须在毫秒内时, 这很重要.&lt;&#x2F;p&gt;
&lt;p&gt;But there’s a catch.&lt;&#x2F;p&gt;
&lt;p&gt;但有一个陷阱.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;desenchantee-shi-wang&quot;&gt;Désenchantée | 失望&lt;&#x2F;h2&gt;
&lt;p&gt;That program from before, on my x86_64 Linux machine it allocates around 4 GB of memory and it remains on this level through the refreshing.&lt;&#x2F;p&gt;
&lt;p&gt;之前的那个程序, 在我的 x86_64 Linux 机器上分配大约 4 GB 内存, 并在刷新过程中保持在这个水平.&lt;&#x2F;p&gt;
&lt;p&gt;This makes sense, right? We’re using lazy iterators, replacing stuff in place one-by-one, there’s no reason we’d need more RAM.&lt;&#x2F;p&gt;
&lt;p&gt;这是有道理的, 对吧？我们使用惰性迭代器, 逐个地就地替换东西, 没有理由需要更多 RAM.&lt;&#x2F;p&gt;
&lt;p&gt;But if you use &lt;code&gt;mimalloc&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;但如果你使用 &lt;code&gt;mimalloc&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;mimalloc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;MiMalloc&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;global_allocator&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;static&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;GLOBAL&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; MiMalloc &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; MiMalloc&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;… the program will first allocate 4 GB and then allocate extra 4 GB during the refreshing, oh noes!&lt;&#x2F;p&gt;
&lt;p&gt;… 程序将首先分配 4 GB, 然后在刷新期间分配额外的 4 GB, 哦 f***!&lt;&#x2F;p&gt;
&lt;p&gt;Getting to this point already took three days of my life - believe me or not, when faced with 200k lines of Arc-ridden Rust code that seems to generate a memory leak, one’s first thought is not “let’s try with different allocator”, but rather “probably something’s holding onto an Arc for too long”.&lt;&#x2F;p&gt;
&lt;p&gt;确认这一点已经花费了我三天的生命: 信不信由你, 当面对 20 万行充满 Arc 的 Rust 代码似乎产生内存泄漏时, 人们的第一个想法不是 “让我们尝试不同的分配器”, 而是 “可能有什么东西持有 Arc 太久了”.&lt;&#x2F;p&gt;
&lt;p&gt;And so I’ve valgrind-ed. I’ve perf-ed. I’ve analyzed assembly. I’ve headbanged and I’ve cried.&lt;&#x2F;p&gt;
&lt;p&gt;我用了 &lt;code&gt;valgrind&lt;&#x2F;code&gt;. 我用了 &lt;code&gt;perf&lt;&#x2F;code&gt;. 我分析了汇编. 我撞头, 我哭.&lt;&#x2F;p&gt;
&lt;p&gt;No more.&lt;&#x2F;p&gt;
&lt;p&gt;不再这样了.&lt;&#x2F;p&gt;
&lt;p&gt;From now on I’m always assuming it’s someone else’s fault - it’s the allocator, it’s the compiler, it’s that crate Mike pulled last night. WHY DO YOU HATE ME MIKE, WHY ARE YOU PULLING RANDOM CRATES TO MY PURE ~~~tv noise~~~&lt;&#x2F;p&gt;
&lt;p&gt;从现在开始, 我将假设这是别人的错: 是分配器的错, 是编译器的错, 是 Mike 昨晚拉取的那个 crate 的错. (碎碎念)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;remede-jie-jue-fang-an&quot;&gt;Remède | 解决方案&lt;&#x2F;h2&gt;
&lt;p&gt;Allocators have different characteristics for a reason - they do some things differently between each other. What do you think &lt;code&gt;mimalloc&lt;&#x2F;code&gt; does that could account for this behavior?&lt;&#x2F;p&gt;
&lt;p&gt;分配器有不同的特性是有原因的: 它们彼此之间做一些不同的事情. 你认为 &lt;code&gt;mimalloc&lt;&#x2F;code&gt; 做了什么可能导致这种行为？&lt;&#x2F;p&gt;
&lt;p&gt;Let me give you two hints, two pieces of code that solve the problem, but feel cursed:&lt;&#x2F;p&gt;
&lt;p&gt;让我给你两个提示, 两段解决问题但感觉被诅咒的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; Approach 1:
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; state &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;thread&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;RwLock&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;State&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;join&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;*&lt;&#x2F;span&gt; ... &lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; Approach 2:
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;*&lt;&#x2F;span&gt; ... &lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;loop&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Box&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;1234&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Any ideas? Last chance to win a plushie polar bear!&lt;&#x2F;p&gt;
&lt;p&gt;有什么想法吗？赢得毛绒北极熊的最后机会!&lt;&#x2F;p&gt;
&lt;p&gt;Alright then, the issue is that mimalloc assumes that every thread allocates every now and then.&lt;&#x2F;p&gt;
&lt;p&gt;好吧, 问题是 &lt;code&gt;mimalloc&lt;&#x2F;code&gt; 假设每个线程时不时地分配内存.&lt;&#x2F;p&gt;
&lt;p&gt;Every now and then during &lt;code&gt;malloc()&lt;&#x2F;code&gt;, &lt;code&gt;mimalloc&lt;&#x2F;code&gt; performs some internal bookkeeping, so when a thread goes to sleep (say, because it delegates handling HTTP requests into a separate thread pool…), this bookkeeping doesn’t happen (for that particular thread).&lt;&#x2F;p&gt;
&lt;p&gt;在 &lt;code&gt;malloc()&lt;&#x2F;code&gt; 期间, &lt;code&gt;mimalloc&lt;&#x2F;code&gt; 时不时地执行一些内部整理, 所以当一个线程进入睡眠状态时（比如, 它将处理 HTTP 请求委托给一个单独的线程池…）, 这种整理不会发生（对于那个特定的线程）.&lt;&#x2F;p&gt;
&lt;p&gt;The most nasty edge case that can happen here, and the one that we’ve stumbled upon, is when your thread allocates a lot of data, then launches other threads to work on that data, and then goes to sleep. As other threads work on memory and override stuff, Rust destructors are launched properly, but the underlying memory blocks simply get marked as “to be released”.&lt;&#x2F;p&gt;
&lt;p&gt;这里可能发生的最讨厌的边缘情况, 也是我们遇到的情况, 是当你的线程分配大量数据, 然后启动其他线程来处理这些数据, 然后进入睡眠状态. 当其他线程处理内存并覆盖东西时, Rust 析构函数被正确启动, 但底层内存块只是被标记为 “待释放”.&lt;&#x2F;p&gt;
&lt;p&gt;Under normal conditions, these blocks get processed a moment later, during a call to &lt;code&gt;malloc()&lt;&#x2F;code&gt; on the thread that created them - but if that thread is asleep, those blocks never become available again (unless the thread dies, of course).&lt;&#x2F;p&gt;
&lt;p&gt;在正常条件下, 这些块会在稍后处理, 在创建它们的线程调用 &lt;code&gt;malloc()&lt;&#x2F;code&gt; 期间. 但如果那个线程在睡眠, 这些块永远不会再次可用（当然, 除非线程终止）.&lt;&#x2F;p&gt;
&lt;p&gt;To be sure, the problem is not that the memory is not returned to the kernel - that’s alright. It’s that unless this bookkeeping happens, mimalloc can’t even use the memory for itself - all this free estate just lays there, dormant:&lt;&#x2F;p&gt;
&lt;p&gt;可以肯定的是, 问题不是内存没有返回给内核, 那没关系. 问题是除非这种整理发生, 否则 &lt;code&gt;mimalloc&lt;&#x2F;code&gt; 甚至不能为自己使用内存: 所有这些空闲空间就躺在那里, 休眠：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;mimalloc&#x2F;issues&#x2F;537&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;mimalloc&#x2F;issues&#x2F;537&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;mimalloc&#x2F;issues&#x2F;214&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;mimalloc&#x2F;issues&#x2F;214&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Anyway, the solution we went with was to keep all refreshing on the same thread - when program starts, we spawn a dedicated refreshing-thread and use channels to let it know to do its thing.&lt;&#x2F;p&gt;
&lt;p&gt;无论如何, 我们采用的解决方案是将所有刷新保持在同一个线程上: 当程序启动时, 我们生成一个专用的刷新线程, 并使用通道让它知道要做什么.&lt;&#x2F;p&gt;
&lt;p&gt;So yeah, that was fun; and health-wise probably more like seven cigarettes.&lt;&#x2F;p&gt;
&lt;p&gt;所以是的, 那很有趣；从健康角度来说, 可能更像七支香烟.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;blockquote&gt;
&lt;p&gt;推荐阅读: &lt;a href=&quot;https:&#x2F;&#x2F;www.cnblogs.com&#x2F;linkwk7&#x2F;p&#x2F;11221730.html&quot;&gt;https:&#x2F;&#x2F;www.cnblogs.com&#x2F;linkwk7&#x2F;p&#x2F;11221730.html&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
</content>
        <summary type="html">Mimalloc 香烟: 我因内存泄漏而失去了生命中的一周</summary>
        </entry><entry xml:lang="zh">
        <title>随笔一则</title>
        <published>2025-04-26T00:00:00+00:00</published>
        <updated>2025-07-26T00:00:00+00:00</updated>
        <author>
            <name>Hantong Chen</name>
        </author>
        <link rel="alternate" href="https://i.han.rs/blog/25-04-26-note/" type="text/html"/>
        <id>https://i.han.rs/blog/25-04-26-note/</id>
        
            <content type="html">&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;std&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;borrow&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Cow&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;hash&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Hash&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;SniMap&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;HashMap&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;PeerKey&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;, Peer&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-impl z-rust&quot;&gt;impl&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-impl z-rust&quot;&gt;SniMap&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Get peer by SNI.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;crate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;get&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;s&lt;&#x2F;span&gt;, &lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;sni&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;s&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;self&lt;&#x2F;span&gt;, &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;sni&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;sni&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;str&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-support z-type z-rust&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;s&lt;&#x2F;span&gt; Peer&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-variable z-language z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-float z-rust&quot;&gt;0.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;get&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;PeerKey&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Exact&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Cow&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Borrowed&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;sni&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;or_else&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-variable z-language z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-float z-rust&quot;&gt;0.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;get&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;PeerKey&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Subdomain&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Cow&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Borrowed&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;                sni&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;split_once&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;_&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;prior&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;prior&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;sni&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;            &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;Debug&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; PartialEq&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; Eq&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-enum z-rust&quot;&gt;enum&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-enum z-rust&quot;&gt;PeerKey&lt;&#x2F;span&gt;&amp;lt;&amp;#39;k&amp;gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Match exactly the SNI. This is the default mode.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Will be serialized or deserialized as `example.com`.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    Exact&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;Cow&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;k&lt;&#x2F;span&gt;, &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;str&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Subdomain match
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Should be serialized or deserialized as `domain:example.com`.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; ## Notice
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Matches direct subdomains only; does not match nested subdomains
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; (second-level or deeper).
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; Take `domain:example.com` as an example, it will match `*.example.com`
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-documentation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt; but not `*.*.example.com`, etc.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    Subdomain&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;Cow&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-lifetime z-rust&quot;&gt;&amp;#39;k&lt;&#x2F;span&gt;, &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;str&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-enum z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-impl z-rust&quot;&gt;impl&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;Hash &lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;for&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt; &lt;span class=&quot;z-entity z-name z-impl z-rust&quot;&gt;PeerKey&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&amp;#39;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;hash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;H&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;std&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;hash&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Hasher&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;self&lt;&#x2F;span&gt;, &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;state&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; H&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;match&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-language z-rust&quot;&gt;self&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;PeerKey&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Exact&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; s&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;hash&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;state&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;PeerKey&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Subdomain&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;                state&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;write&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-string z-rust&quot;&gt;b&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;domain:&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;                s&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;hash&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;state&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-impl z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;Peer&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;非常平平无奇的代码. 于是有新的需求: 想缓存 get 的结果 (把按 &lt;code&gt;domain:example.com&lt;&#x2F;code&gt; 匹配到的 &lt;code&gt;sub.example.com&lt;&#x2F;code&gt; 当 &lt;code&gt;full:sub.example.com&lt;&#x2F;code&gt; 写入表内), 达到下次读哈希表一次就能把 Peer 读出来.&lt;&#x2F;p&gt;
&lt;p&gt;有这么几个方案:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;改用三方库 &lt;code&gt;dashmap&lt;&#x2F;code&gt; 提供的 DashMap, 类似 RwLock 但 &lt;em&gt;API 层面&lt;&#x2F;em&gt; 是 Lock-free 的.&lt;&#x2F;li&gt;
&lt;li&gt;套一层 Mutex &#x2F; RwLock.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;del&gt;RefCell&lt;&#x2F;del&gt; &lt;em&gt;毙掉, 我们需要 Send + Sync, 应该用其线程安全版本: RwLock&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;但事实上都是不可行的, 为什么? 欢迎 Rust 初学者自行查阅资料 (问 AI 也行)~&lt;&#x2F;p&gt;
&lt;p&gt;答案 (实际上编译器已经提示了):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对于具有内部可变性的 Cell 类似物, 子类型型变要求是不变 (invariant) 的&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;#fn-1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. 本例中 DashMap 内部用了 lock_api 库的 RwLock, 这玩意内部用了 UnsafeCell, 故相应字段涉及的泛型类型 K 要求是不变的.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;不能返回本地变量 (拿到的 &amp;amp;Peer 只能活得和上锁后拿到的 Guard 一样久). 起码在当前的 API 设计下不能这么干. 当然, 写出来 &lt;code&gt;get_and_map&lt;&#x2F;code&gt; 一类的奇怪 API 也不是不行.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;评价: &lt;del&gt;写 Rust 的福报&lt;&#x2F;del&gt;&lt;&#x2F;p&gt;
&lt;footer class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;参见 &lt;a href=&quot;https:&#x2F;&#x2F;twir.han.rs&#x2F;595&#x2F;rust-type-system-deep-dive#lifetime-variance-and-static--%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9E%8B%E5%8F%98%E5%92%8C-static&quot;&gt;https:&#x2F;&#x2F;twir.han.rs&#x2F;595&#x2F;rust-type-system-deep-dive#lifetime-variance-and-static–%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9E%8B%E5%8F%98%E5%92%8C-static&lt;&#x2F;a&gt; &lt;a href=&quot;#fr-1-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;footer&gt;
</content>
        <summary type="html">Meme: Rust 生命周期与内部可变性</summary>
        </entry>
</feed>
