ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用Markdown和GPG签名打造AI Agent可读的身份页

2026/8/29 3:24:42 拓冰建站 浏览量
用Markdown和GPG签名打造AI Agent可读的身份页 先分享一下最近在 AI 圈看到一个很有意思的项目Username.md。它的定位很直接——一个signed、agent-readable、并且由你自己拥有的身份页面。在 AI 代理Agent开始代替人类去读网页、发邮件、提交表单的背景下身份信息散落在各大平台主页上既难验证又容易被篡改。Username.md给出了一个很朴素的解做一个 Markdown 格式的纯文本身份页放在自己控制的域名或仓库里用数字签名保证内容可信让 Agent 能像读普通文件一样读取和验证你是谁。本文将完整拆解这个项目背后的思路并带你从零实现一个属于自己的identity.md包括 GPG 签名、发布、以及用 Python 编写一个可用的 Agent 校验脚本。文章既适合对 AI Agent、数字身份感兴趣的开发者也适合想给自己的技术博客或个人网站增加“机器可读身份”能力的同学。1. Username.md 是什么它解决了什么问题1.1 从“身份碎片化”说起过去几年每个开发者几乎都有这样一堆身份入口GitHub 主页、个人博客、Twitter/X、领英、Email 签名、甚至 Bento 链接页。这些身份信息风格各异、格式不同、内容还可能互相矛盾。当人类访问时可以通过视觉排版和个人简介判断“这是不是本人”但当 AI Agent 去访问时这些页面大多充斥着 JavaScript、动态渲染、图片、反爬逻辑Agent 很难稳定抽取出准确的身份信息。更关键的是安全问题。任何人都可以在平台上创建同名账号或者复制你在博客上公开的个人信息再造一个高仿主页。没有一种轻量、通用的机制能让 Agent 快速确认“这个页面确实由你签名、并且内容没有被篡改”。1.2 三个关键词拆解Username.md这个命名灵感我理解是一种把“用户名”直接映射成“Markdown 文件”的极简方案。它可以拆成三个核心特征signed已签名页面不是裸文本而是带数字签名认证过的内容。最常见的做法是使用 GPG/PGP 私钥对 Markdown 文件签名任何人都能用你的公钥验证文件完整性和来源。agent-readableAgent 可读内容采用标准 Markdown YAML Front Matter不需要浏览器渲染纯文本即可被大模型、爬虫、自动化脚本解析。Agent 拿到内容后可以直接提取身份字段并验证签名。you own你拥有身份页面托管在你自己的域名、GitHub 仓库或 IPFS/身边服务器上而不是依赖某个第三方平台的用户主页。只要你的域名和密钥还在身份就始终是你的。1.3 与传统身份方案的对比方案可验证性Agent 可读性自托管性学习成本个人博客主页弱差高低GitHub Profile中一般低低OpenID/OAuth强中低高PGP Keyserver强差中高Username.md 方案强强高中可以看到Username.md并不是要替代 OAuth 或 OpenID 这类真正的认证协议它更像是一个“身份名片”的机器可读版本验证成本低、部署简单、不依赖中心化服务。这也是它能出现在 Hacker News 首页并被讨论的原因。2. 技术原理与格式设计2.1 为什么选择 Markdown 作为身份载体Markdown 有几个属性非常适合做身份页纯文本可读性极强不需要浏览器渲染也能完整表达内容含义。生态成熟几乎每个开发者都熟悉写起来没有心理负担。机器可解析配合 YAML Front Matter可以变成结构化数据。易于版本管理Markdown 是文本非常适合放进 Git 仓库追踪每一次修改。在 Agent 场景下Markdown 还有一个额外优势Token 消耗低。相比渲染后的 HTML 动辄几十 KB 甚至几 MB一个身份 Markdown 文件通常只有 1~3 KBAgent 可以低成本抓取和处理。2.2 YAML Front Matter 与身份字段设计为了让 Agent 解析身份页我们可以在文件头部加入 YAML Front Matter。这是一个受 Jekyll、Hexo、Hugo 等静态站点生成器广泛支持的约定格式如下--- name: 张三 preferred_username: zhangsan email: zhangsanexample.com website: https://example.com pgp_fingerprint: A1B2C3D4E5F60718293A4B5C6D7E8F9A0B1C2D3 social: github: zhangsan mastodon: https://mastodon.example/zhangsan ---字段设计建议保持克制name展示用姓名或昵称。preferred_username机器匹配用户名时的首选值。email联系邮箱注意是否公开。pgp_fingerprint用于校验签名的公钥指纹Agent 可以据此去公钥服务器拉取公钥。social可选的社交账号映射Key 统一Value 为 URL 或用户名。不建议把年龄、身份证号、家庭住址等隐私信息放进这个文件。身份页越精简越容易被 Agent 稳定解析也越不容易被滥用。2.3 数字签名的放置方式“签名一个 Markdown 文件”有两种常见方式方式一分离签名Detached Signature生成独立的签名文件gpg --detach-sign --armor identity.md这会生成identity.md.asc内容类似-----BEGIN PGP SIGNATURE----- ... -----END PGP SIGNATURE-----发布时需要同时提供identity.md和identity.md.asc两个文件。验证方先验证签名再读正文。方式二内置 Clearsign 签名直接对 Markdown 进行 Clearsign 操作gpg --clearsign identity.md -o identity.signed.md生成的文件是“明文 签名块”混排Markdown 内容人类可读末尾带有BEGIN PGP SIGNED MESSAGE的签名信息。这样只需要发布一个文件但文件结构会多出一段签名头尾对某些 Markdown 渲染器可能不友好。Username.md的项目思路中分离签名更适合 Agent 场景因为 Agent 可以先拉取identity.md快速解析 Front Matter如果要求更高再验证identity.md.asc。两种方式可以在后续实践中灵活选择。2.4 Agent 如何发现和读取约定优于配置在目前没有统一协议的情况下Username.md的机制更多依赖“约定”文件命名为username.md、identity.md或者放在.well-known/identity.md。在个人网站根目录或 GitHub 仓库根目录暴露该文件。在 DNS 或公钥服务器中绑定你的域名和公钥指纹互相关联。对 Agent 来说读取流程可以很简单根据目标用户名猜测页面地址例如https://example.com/zhangsan.md。如果页面存在先拉取内容。根据 Front Matter 里的pgp_fingerprint查找公钥。用公钥验证分离签名文件。验证通过后提取结构化身份信息。这个流程不需要任何中心化服务任何人都可以搭一套自己的“身份解析器”。3. 动手实现创建并签名你的 identity.md下面进入实战环节。我们会在本地创建、签名、发布一个身份页并验证它。3.1 准备 GPG 密钥如果没有 GPG 密钥先生成一对gpg --full-generate-key过程中选择密钥类型默认 RSA and RSA。密钥长度4096 位。有效期建议设置一个合理期限比如 2 年。用户 ID填写真实姓名和邮箱。生成后查看公钥指纹gpg --list-secret-keys --keyid-formatlong输出类似sec rsa4096/A1B2C3D4E5F6 2025-05-01 [SC] [expires: 2027-05-01] A1B2C3D4E5F60718293A4B5C6D7E8F9A0B1C2D3 uid [ultimate] Zhang San zhangsanexample.com其中完整的那一长串就是pgp_fingerprint。如果已有密钥可以跳过生成步骤。3.2 编写身份页新建文件identity.md--- name: Zhang San preferred_username: zhangsan email: zhangsanexample.com website: https://example.com pgp_fingerprint: A1B2C3D4E5F60718293A4B5C6D7E8F9A0B1C2D3 social: github: zhangsan mastodon: https://mastodon.example/zhangsan --- # Identity Page This is the machine-readable and human-readable identity page of Zhang San. ## About I am a developer working on AI agents and distributed systems. ## Contact - Email: zhangsanexample.com - Website: https://example.com ## Verification This file is signed with GnuPG. Use the .asc file and the PGP fingerprint above to verify authenticity.在 Front Matter 中填入自己的真实信息。注意website要和你实际发布地址保持一致否则验证方可能认定地址不匹配。3.3 生成分离签名运行gpg --detach-sign --armor identity.md成功后当前目录会出现identity.md.asc。我们可以用gpg --verify验证gpg --verify identity.md.asc identity.md输出应包含类似gpg: Signature made ... gpg: using RSA key A1B2C3D4E5F6... gpg: Good signature from Zhang San zhangsanexample.com看见Good signature就说明签名有效。3.4 发布到你可控的位置建议至少发布到两个地方个人网站根目录如https://example.com/identity.mdGitHub 仓库根目录如https://github.com/zhangsan/zhangsan或单独仓库。发布时记得同时上传identity.md和identity.md.asc。如果网站服务器支持Content-Type: text/markdownAgent 抓取会更标准如果文件放在独立对象存储或静态托管上一般也不用特别设置纯文本已经足够。4. 让 Agent 能验证Python 校验脚本实战有了identity.md和identity.md.asc我们来实现一个最小可用的 Agent 校验脚本。这个脚本可以跑在 Agent 端也可以跑在你自己的服务器上做自检。4.1 项目结构agent-identity-verifier/ ├── check_identity.py ├── requirements.txt ├── public_keys/ │ └── zhangsan.asc ├── identity.md └── identity.md.ascpublic_keys/用于存放你有意信任的公钥文件。在生产环境里公钥可以来自公钥服务器也可以来自你自己的信任库。4.2 安装依赖requirements.txtpython-frontmatter1.1.0 python-gnupg0.5.1安装pip install -r requirements.txt需要说明的是python-gnupg底层调用了本机的gpg命令所以系统里必须安装 GnuPG。这个脚本不是完全独立于 GPG 的实现而是把底层验证交给成熟的 GnuPG 工具。4.3 编写核心校验逻辑下面是一个完整的check_identity.py#!/usr/bin/env python3 验证 identity.md 的签名并解析前端元信息。 使用方法: python check_identity.py identity.md identity.md.asc public_keys/zhangsan.asc import sys import frontmatter import gnupg def load_public_key(path: str) - None: # 把公钥导入临时 GPG 环境。 # 这里使用 homedir 参数指定独立目录避免污染用户真实密钥环。 gpg gnupg.GPG(gnupghome/tmp/username-md-gpghome) with open(path, rb) as f: result gpg.import_keys(f.read()) if result.count 0: raise ValueError(公钥导入失败请检查文件格式) print(f[OK] 已导入公钥指纹: {result.fingerprints}) def verify_signature(gpg: gnupg.GPG, signed_file: str, signature_file: str) - bool: with open(signature_file, rb) as f: verified gpg.verify_file(f, signed_file) if verified.valid: print(f[OK] 签名有效签名指纹: {verified.fingerprint}) return True print(f[FAIL] 签名验证失败: {verified.status}) return False def parse_identity(md_path: str) - dict: post frontmatter.load(md_path) # 当文件中存在 YAML Front Matter 时post.metadata 是字段字典。 return post.metadata def main() - int: if len(sys.argv) ! 4: print(用法: python check_identity.py identity.md identity.md.asc public_key.asc) return 1 md_file, sig_file, pubkey_file sys.argv[1], sys.argv[2], sys.argv[3] # 1. 导入公钥 load_public_key(pubkey_file) # 2. 创建 GPG 实例 gpg gnupg.GPG(gnupghome/tmp/username-md-gpghome) # 3. 验证签名 if not verify_signature(gpg, md_file, sig_file): return 1 # 4. 解析身份页 metadata parse_identity(md_file) print(\n 身份信息 ) for key, value in metadata.items(): print(f{key}: {value}) # 5. 简单校验必填字段 required {name, preferred_username, email, pgp_fingerprint} missing required - set(metadata.keys()) if missing: print(f\n[FAIL] 缺少字段: {missing}) return 1 print(\n[OK] 身份页校验通过) return 0 if __name__ __main__: sys.exit(main())这段脚本做了四件事导入指定的公钥到临时 GPG 环境。用公钥验证分离签名。解析 Markdown 的 YAML Front Matter。检查必填字段是否齐全。如果签名无效或字段缺失脚本返回非 0 退出码Agent 可以据此拒绝信任该身份页。4.4 运行与预期输出先导出公钥gpg --armor --export zhangsanexample.com public_keys/zhangsan.asc再运行脚本python check_identity.py identity.md identity.md.asc public_keys/zhangsan.asc成功时的预期输出[OK] 已导入公钥指纹: [A1B2C3D4E5F60718293A4B5C6D7E8F9A0B1C2D3] [OK] 签名有效签名指纹: A1B2C3D4E5F60718293A4B5C6D7E8F9A0B1C2D3 身份信息 name: Zhang San preferred_username: zhangsan email: zhangsanexample.com website: https://example.com pgp_fingerprint: A1B2C3D4E5F60718293A4B5C6D7E8F9A0B1C2D3 social: github: zhangsan mastodon: https://mastodon.example/zhangsan [OK] 身份页校验通过如果签名被篡改或公钥不匹配输出会停在[FAIL] 签名验证失败脚本返回 1。4.5 扩展读取远程 identity.mdAgent 场景下通常不会只验证本地文件而是要抓取远程页面。可以用urllib或requests先下载再做同样验证import urllib.request url https://example.com/identity.md sig_url https://example.com/identity.md.asc with urllib.request.urlopen(url, timeout10) as resp: md_content resp.read().decode(utf-8) with urllib.request.urlopen(sig_url, timeout10) as resp: sig_content resp.read().decode(utf-8) with open(remote_identity.md, w, encodingutf-8) as f: f.write(md_content) with open(remote_identity.md.asc, w, encodingutf-8) as f: f.write(sig_content)然后复用前面的校验逻辑。注意下载时一定要限制超时和文件大小避免 Agent 被恶意 URL 拖入资源消耗陷阱。5. 自动化签名与发布GitHub Actions 示例如果每次修改identity.md都要手动签名再上传体验会比较痛苦。这里提供一个 GitHub Actions 的自动化思路当主分支上的identity.md发生变化时自动用仓库中的 GPG 私钥签名并提交新的.asc文件。5.1 工作流示例在仓库中创建.github/workflows/sign-identity.ymlname: Sign Identity on: push: paths: - identity.md branches: - main workflow_dispatch: jobs: sign: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkoutv4 - name: Import GPG private key run: | echo ${{ secrets.GPG_PRIVATE_KEY }} | gpg --batch --import echo ${{ secrets.GPG_PASSPHRASE }} /tmp/gpg_passphrase - name: Sign identity.md run: | gpg --batch --yes \ --pinentry-mode loopback \ --passphrase-file /tmp/gpg_passphrase \ --detach-sign --armor identity.md - name: Commit signed file run: | git config user.name github-actions[bot] git config user.email github-actions[bot]users.noreply.github.com git add identity.md.asc git commit -m chore: update signed identity || echo No changes to commit git push在这个工作流里需要先在 GitHub 仓库的Settings - Secrets and variables - Actions中配置两个 SecretGPG_PRIVATE_KEY导出的 GPG 私钥文本。GPG_PASSPHRASE私钥对应的口令。导出私钥用的命令是gpg --armor --export-secret-keys zhangsanexample.com5.2 安全注意事项把 GPG 私钥放到 CI 环境是有风险的务必做到使用专用密钥不要把你日常开发的主密钥放到 Actions 里创建一把专门用来签名identity.md的子密钥或独立密钥。设置合理有效期密钥到期后更换会很麻烦但长期不过期的私钥风险更高。建议 1~2 年一换。开启 GitHub 环境保护规则把GPG_PRIVATE_KEY和GPG_PASSPHRASE放在 Environment 级别并设置必要的审批人。流水线只做签名和提交不要在 Actions 里用同一把私钥处理其他敏感操作。6. 常见问题与排查思路6.1 常见问题速查表问题现象常见原因解决思路gpg --verify报No public key本地没有对应的公钥用gpg --import导入发布者公钥Good signature但显示WARNING: This key is not certified with a trusted signature公钥没有经过信任链认证建立自己的 Web of Trust或手动设置密钥信任级别签名验证通过但 Front Matter 解析为空Markdown 文件头部没有 YAML 分隔线检查---是否在文件第一行且前后没有空行Agent 抓取后看到\r\n换行Windows 平台的行尾符不同发布前统一转成LFUnix 行尾identity.md.asc文件被 Git LFS 管理Git LFS 存储的是指针而非文本对.asc文件关闭 LFS确保仓库内是真实文本GPG 密钥过期后验证失败密钥未更新或未续期在密钥过期前续期并重新发布公钥python-gnupg找不到gpg命令系统未安装 GnuPG或 PATH 不正确安装 GnuPG确认gpg --version可执行6.2 高频问题详细排查问题 1Agent 拿到的哈希和签名对不上这通常不是签名问题而是文件编码差异。比如本地是 CRLF服务器传输时自动转成了 LF。在发布identity.md前可以用.gitattributes强制 LF*.md text eollf *.asc text eollf或者在上传前执行dos2unix identity.md identity.md.asc问题 2Agent 无法判断身份页域名是否可信我们的脚本目前只验证“签名有效”没有验证签名者与域名之间的绑定关系。更严格的方案是在身份页 Front Matter 中声明website。在域名 DNS 中添加一条TXT记录写入username-md-verification你的PGP指纹。Agent 验证时比对identity.md里的website、DNS 记录、签名指纹三者是否一致。这样能有效防止中间人把别人的身份页放到自己的域名下伪造。问题 3密钥需要轮换轮换密钥时记得做这些事生成新密钥后旧密钥不要立即删除留一个过渡期。用新私钥重新签名identity.md。更新 Front Matter 里的pgp_fingerprint。如果可能用旧私钥对“新指纹”写一条看板信息做交叉签名。7. 最佳实践与工程建议7.1 身份字段要克制identity.md不是个人简历。字段越少Agent 解析越稳定隐私风险越低。建议只放最基本的人类可读名称。优先一致的用户名。一个联系邮箱或通信地址。一把公钥指纹。最多 3~5 个社交入口。不要放完整生日、住址、手机号。过于详尽的履历。与身份无关的营销文案。7.2 密钥与文件安全签名身份页的私钥应该单独管理和日常运维密钥分开。建议私钥永不入库永远使用环境变量或 Secret 管理。公钥可以公开但建议通过 HTTPS 页面发布。定期检查公钥是否被泄露或误用可以通过gpg --list-sigs查看签名关系。7.3 版本管理与审计身份页的每次修改都应该通过 Git 追踪。一个简单约定是修改identity.md后必须同步生成新的identity.md.asc。提交信息统一为docs: update identity page方便检索。发布脚本中检查git status如果identity.md.asc落后于identity.md阻止发布。这样即使未来需要追溯某个时间点身份页内容也能通过 Git 历史还原。7.4 兼容性与扩展方向Username.md目前并没有统一的标准协议所以在实际项目中需要保持兼容同时提供.html和.md如果你不希望人类访问时看到纯文本可以通过同一 URL 做内容协商人或 Agent 访问时拿到不同渲染结果。保留 WebFinger 风格WebFinger 使用/.well-known/webfinger的 JSON 格式identity.md可以在/.well-known/identity.md暴露两边同时维护。未来可能结合 DID去中心化标识符DID体系比 Git/GPG 更通用可以让identity.md的 Front Matter 增加did字段逐步过渡到标准身份层。Agent 生态预埋语义在identity.md中放一个capabilities字段声明该身份支持的操作比如“可以接收加密邮件”“可以接受 BGP 通知”方便未来智能体的自动协商。这些都是很好的扩展方向但不要让这些想法阻碍你先把最小版本跑通。一个签名有效的identity.md本身就已经很有价值了。8. 写成一篇可用教程之后还是想多说一句Username.md这个项目的核心魅力不在于它发明了什么高深算法而在于它把“AI 时代的身份”重新拉回到了“一个自己掌握的 Markdown 文件”这种朴素形态。当越来越多的 Agent 在网络中代替我们完成杂务一个能被机器验证、读取、并且不会因为某个平台关闭而消失的身份页面会逐渐变成一种基础的数字资产。如果你愿意动手试我建议按下面的顺序完成一次最小闭环生成一把 GPG 密钥。写一个identity.md填好 Front Matter。用gpg --detach-sign --armor生成签名。把两个文件传到你的个人网站或 GitHub 仓库。运行本文的check_identity.py体验 Agent 视角的验证流程。当这一步跑通后再考虑要不要把验证脚本接进自己的聊天机器人、接入 CI 自动化或者和 WebFinger/DID 做联动。AI Agent 正在快速进入软件开发、数据处理和自动化协作领域对身份可信度的要求只会越来越高。与其等平台给出标准方案不如现在就用自己的密钥和 Markdown定义你的第一版机器可读身份。希望这篇教程能给你带来一些可落地的思路。