打造Agent可读的签名身份主页:Username.md实战指南
2026/8/29 7:15:21 网站建设 项目流程

当业务账号从“人”扩展到“Bot / AI Agent”时,一个只给人看的 Markdown 个人主页已经不够用了。它需要具备机器可验证的签名,需要能被 Agent 直接解析,更需要真正属于你自己——而不是挂在某个第三方平台后面。Username.md 正是这样一个思路:把身份主页做成一份带签名、可被 Agent 读取、并且由你自托管拥有的 Markdown 文件。

下面从概念、信任模型、完整实战到常见坑点,拆解这套方案的实现过程。完整代码都可以复制直接跑,有基础的开发者可以把重点放在签名与 Agent 解析部分,新手建议从第 1 节逐步读。

1. 什么是 Username.md:一个属于你自己的身份主页

1.1 从“个人主页”到“Agent 可读的身份页”

传统个人主页通常是 HTML 页面。它解决的问题很直接:让访问者知道你是谁、做过什么、怎么联系你。

但当我们进入 AI Agent 时代,访问你主页的不再只有浏览器里的人类。自动化客服、招聘筛选工具、开源贡献者机器人、邮件自动回复程序,都会尝试读取你的公开信息,并决定“是否信任这条身份声明”。

问题在于 HTML 页面很难被程序稳定解析。页面结构五花八门,关键信息藏在 DOM 和 CSS 选择器里,Agent 要写一堆适配逻辑才能抽出来。而且普通页面没有身份签名,任何人都可以复制并伪造一份看起来差不多的主页。

Username.md 的思路是把身份主页简化成一份 Markdown 文件。文件名就是你自己的用户名,文件内容采用固定的 frontmatter 和区块结构,让 Agent 可以按约定解析。再用私钥对文件内容做签名,发布时把签名文件和公钥放在同一目录,验证方拿到文件后先验签,再解析数据。

一句话版:Username.md 是一份“你自己拥有、别人能验真、程序能读取”的 Markdown 身份主页。

1.2 signed / agent-readable / you own 三个核心词拆解

打开这个项目标题,最值得注意的是三个修饰词:

  • signed(签名的) 文件内容经过数字签名。任何人对文件做修改,签名验证都会失败。你发布 username.md 后,访问者可以通过公钥验证内容确实由持有对应私钥的人发出,而不是被中间人替换。

  • agent-readable(可被 Agent 读取) 文件格式是结构化 Markdown。使用 YAML frontmatter 作为元数据区,正文使用固定标题和表格区块。Agent 不需要理解自然语言,只需要解析 frontmatter 就能拿到 handle、links、verified accounts 等字段。

  • you own(你拥有) 文件存放在你的域名、你的对象存储、你的 Git 仓库里,不依赖某个第三方身份平台。平台可以封禁账号,但你的域名和密钥掌握在自己手中,身份声明就不会被某家公司单方面撤销。

从概念设计来看,这三个特性组成了一种“自托管 + 密码学可验证 + 机器可读”的身份声明格式。

1.3 它和普通 Markdown 个人主页有什么区别

很多开发者早就在用 Markdown 写个人主页,比如 GitHub 上的 README,或者用 VitePress、MkDocs 生成的个人网站。那 Username.md 有什么不同?

维度普通 Markdown 个人主页Username.md 思路
读者主要是人人 + Agent
身份验证依赖平台账号体系依赖数字签名
机器解析困难,格式不固定frontmatter + 固定区块
内容所有权受平台限制自托管,域名和密钥自持
信任来源平台背书密钥 + 多渠道交叉验证

这里不是批评普通 Markdown 主页,而是说明应用场景不同。如果你的主页只是给同事和网友看,普通 Markdown 完全够用;如果你希望自动化程序能够可靠地读取你的身份声明,并验证内容没有被篡改,那 Signed + Agent-readable 这套设计就更合适。

2. 为什么需要 Agent 可读的身份标识

2.1 当调用方从“人”变成“Agent”

传统互联网的信任链路是:用户通过浏览器访问网站,浏览器通过 HTTPS 证书确认网站身份。但 HTTPS 只能证明“你和 example.com 之间通信是加密的”,不能证明“example.com 上挂的内容真的是你写的”。

当调用方变成 Agent 时,问题会更突出。例如:

  • 一个自动化系统收到了某人的简历,里面附带了 GitHub 链接和个人官网。系统需要确认这个链接确实属于简历上那个人。
  • 一个 AI 助手被要求发送邮件给某位开发者。它需要确认自己拿到的邮箱是对方的真实公开邮箱,而不是伪造的。
  • 一个开源的自动化贡献者统计工具,需要区分同名账号,防止身份混淆。

如果这些信息只是普通网页上的文本,Agent 没有可靠手段验证。Username.md 的签名机制就是为了给这一层提供密码学证据。

2.2 典型使用场景

从这类格式的特性来看,比较适合的场景包括:

  1. 跨平台身份聚合 把 GitHub、Twitter、邮箱、个人博客统一挂在一个自托管文件下,Agent 拉取一份文件即可获取全套公开联系信息。

  2. 自动化招聘筛选 招聘系统读取候选人的 username.md,验证签名后提取邮箱、作品集链接、社交账号,降低候选人在多个平台重复填写信息的成本。

  3. 开发者工具身份关联 终端工具执行agent read https://example.com/.well-known/username.md,完成身份校验后,自动把你贡献过的项目与你本人关联。

  4. 邮件防伪 邮件正文里携带 username.md 地址和签名哈希,接收方可以验证这封邮件确实是来自公开身份声明中注册的邮箱。

这些场景有一个共同特征:调用方是程序,不是人。程序需要的是稳定字段、明确格式、可验证签名。

2.3 信任模型:签名只解决“是否篡改”,还要解决“公钥是谁的”

理解 Username.md 时最容易被误解的一点是:签名能证明“内容没有被修改”,但签名本身不能证明“写内容的人就是现实中的那个你”。

签名验证的逻辑是:

用公钥验证文件签名 → 通过 → 文件确实由持有对应私钥的人发布

但这里还有一个前提问题:公钥从哪来?如果攻击者控制了你的域名,同时替换了 username.md 和公钥文件,签名验证依然可以通过,但内容已经是攻击者的了。

所以真正完整的信任模型是两层:

  1. 签名层:私钥证明文件完整性和签发者身份。
  2. 信任锚点层:公钥指纹通过 HTTPS 域名、GitHub、社交平台、邮件签名等多渠道被确认属于你。

实践中常用做法是在 GitHub 个人主页、社交账号介绍、邮件正文等多处公布同一个公钥指纹。Agent 至少从两个独立渠道确认指纹一致后,再信任 username.md 的公钥。

3. 技术架构与文件规范

3.1 文件布局与部署约定

在参考常见 well-known 资源设计思路的基础上,可以约定如下文件布局:

https://example.com/.well-known/username.md https://example.com/.well-known/username.md.sig https://example.com/.well-known/username.md.pub.pem
  • username.md:身份内容主体,给人看也给 Agent 看。
  • username.md.sig:二进制签名文件,内容是内容主体的 Ed25519 签名。
  • username.md.pub.pem:公钥文件,PEM 格式,供验证方直接下载。

也可以把 username.md 放在域名根路径,比如https://example.com/username.md。关键是保持三份文件在同一目录,并且路径与文件内声明的地址一致。

3.2 frontmatter 与正文结构设计

为了让 Agent 稳定读取,文件头部使用 YAML frontmatter,正文使用固定标题区块。下面是一份示例结构:

--- schema_version: "1.0" handle: example display_name: "张三" type: username.md created_at: "YYYY-MM-DD" updated_at: "YYYY-MM-DD" --- # example ## About 全栈开发者,关注 AI Agent、自动化流程与开放身份协议。 ## Verified Accounts | 平台 | 账号 | 验证说明 | | --- | --- | --- | | GitHub | @example | 该页面已在 GitHub README 中链接回本文件 | | Email | example@example.com | 邮件签名携带本文件地址与签名哈希 | ## Links - 个人网站:https://example.com - 技术博客:https://blog.example.com ## Verification 公钥:https://example.com/.well-known/username.md.pub.pem 签名:https://example.com/.well-known/username.md.sig 请先验证签名,再信任本文件内容。

Agent 解析时优先读取 frontmatter 中的handledisplay_namecreated_atupdated_at,正文区块则作为人类阅读的补充说明。这里不要求正文达到严格的机器可读 schema,但保持区块标题稳定,Agent 后续解析会容易很多。

3.3 签名算法选型:Ed25519 与 PGP

在签名算法上,Ed25519 是一个很适合的选择:

  • 密钥短,签名短,适合放在网页目录中。
  • 性能好,验证速度快。
  • 随机数生成逻辑安全,不容易用错参数。
  • Python 的cryptography、Node.js 内置crypto都原生支持。

PGP/GPG 也可以,生态成熟,但密钥管理复杂,签名文件较大。如果你只是想让 Agent 快速验证自己的身份主页,Ed25519 更轻量。如果团队已有成熟的 PGP 基础设施,使用 PGP 也未尝不可,思路一致,只是解析库更重。

4. 环境准备与项目结构

4.1 工具链

本文实战部分使用 Python 3,建议 3.9 及以上版本。需要安装:

pip install cryptography pyyaml
  • cryptography:用于生成 Ed25519 密钥、签名、验签。
  • pyyaml:用于解析 Markdown 文件头部的 YAML frontmatter。

如果你更习惯 Node.js,也可以使用 Node.js 12+ 内置的crypto模块实现类似功能,后面会给出参考代码。

4.2 目录结构

在本地创建一个目录,结构如下:

username-md-demo/ ├── private_key.pem # 私钥,仅保留在本地,不要上传 ├── public_key.pem # 公钥,可公开 ├── username.md # 身份内容文件 ├── sign_username.py # 签名脚本 ├── verify_username.py # 验证脚本 └── agent_read_username.py # Agent 拉取与解析脚本

安全提醒:私钥文件等同于你的身份签名凭证。一旦泄露,别人可以替你签发任意身份内容。私钥不要提交到 Git,不要放在公开对象存储中。

5. 完整实战:从零创建并发布签名版 username.md

5.1 生成 Ed25519 密钥对

先写一个一次性脚本生成密钥对,并保存为两个 PEM 文件。

# 文件路径:generate_keys.py from pathlib import Path from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey private_key = Ed25519PrivateKey.generate() public_key = private_key.public_key() # 保存私钥 with open("private_key.pem", "wb") as f: f.write( private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) ) # 保存公钥 with open("public_key.pem", "wb") as f: f.write( public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ) print("私钥已保存到 private_key.pem(不要公开)") print("公钥已保存到 public_key.pem")

运行:

python generate_keys.py

此时目录中会生成private_key.pempublic_key.pem。私钥文件权限建议设置为当前用户可读写:

chmod 600 private_key.pem

5.2 编写 username.md 内容

创建username.md,内容可以参考 3.2 节的示例。注意日期字段不要留空,建议写清楚创建和更新时间,方便 Agent 判断缓存有效期。

5.3 编写签名脚本

签名脚本的核心逻辑是:读取username.md的原始字节,用私钥签名,把签名写入username.md.sig

考虑到不同平台换行符差异可能导致验签失败,可以在签名前对内容做一次规范化处理。这里采用统一把\r\n转为\n的方式,签名和验证脚本都使用同一个规范化函数。

# 文件路径:sign_username.py import hashlib from pathlib import Path from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey def normalize(content: bytes) -> bytes: """统一换行符,保证不同平台签验一致。""" return content.replace(b"\r\n", b"\n") def load_private_key(path: str = "private_key.pem"): return serialization.load_pem_private_key(Path(path).read_bytes(), password=None) def main(): private_key = load_private_key() content = normalize(Path("username.md").read_bytes()) signature = private_key.sign(content) Path("username.md.sig").write_bytes(signature) public_key = private_key.public_key() public_bytes = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) fingerprint = hashlib.sha256(public_bytes).hexdigest() print("已完成签名,输出文件:username.md.sig") print("公钥指纹(SHA-256):", fingerprint) if __name__ == "__main__": main()

运行:

python sign_username.py

输出示例:

已完成签名,输出文件:username.md.sig 公钥指纹(SHA-256):9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

这个指纹要记录下来,后续可以发布到多个平台作为交叉验证依据。

5.4 编写验证脚本

验证脚本不会修改任何文件,只校验内容与签名是否匹配。

# 文件路径:verify_username.py import hashlib import sys from pathlib import Path from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey def normalize(content: bytes) -> bytes: return content.replace(b"\r\n", b"\n") def load_public_key(path: str = "public_key.pem"): return serialization.load_pem_public_key(Path(path).read_bytes()) def main(): public_key = load_public_key() content = normalize(Path("username.md").read_bytes()) signature = Path("username.md.sig").read_bytes() try: public_key.verify(signature, content) print("签名验证通过:当前 username.md 内容未被篡改。") except Exception: print("签名验证失败:文件内容或签名不匹配。") sys.exit(1) public_bytes = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) fingerprint = hashlib.sha256(public_bytes).hexdigest() print("当前公钥指纹(SHA-256):", fingerprint) if __name__ == "__main__": main()

运行:

python verify_username.py

预期输出:

签名验证通过:当前 username.md 内容未被篡改。 当前公钥指纹(SHA-256):9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

你可以尝试修改username.md的任意一个字符,再运行验证脚本,会看到验证失败。这是理解“签名防篡改”最直观的方式。

5.5 部署到静态站点

username.mdusername.md.sigpublic_key.pem三个文件部署到你的域名下。

推荐路径:

https://example.com/.well-known/username.md https://example.com/.well-known/username.md.sig https://example.com/.well-known/username.md.pub.pem

如果使用 GitHub Pages、Gitee Pages、Cloudflare Pages 等静态托管,直接把这几个文件放在站点目录的.well-known文件夹下即可。

如果使用 Nginx,建议补充 Content-Type,让 Agent 能正确识别:

location ^~ /.well-known/ { types { text/markdown md; application/octet-stream sig; application/x-pem-file pem; } default_type application/octet-stream; }

部署完成后,浏览器访问https://example.com/.well-known/username.md应该能看到 Markdown 原文,访问.sig应该能下载二进制签名文件。

5.6 Agent 拉取验证与解析

下面写一个模拟 Agent 的脚本:拉取文件、验证签名、解析 frontmatter、输出结构化结果。

# 文件路径:agent_read_username.py import json import re import urllib.request import yaml from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey BASE_URL = "https://example.com/.well-known/username.md" def fetch(path: str) -> bytes: with urllib.request.urlopen(path) as resp: return resp.read() def normalize(content: bytes) -> bytes: return content.replace(b"\r\n", b"\n") def main(): content = fetch(BASE_URL) signature = fetch(BASE_URL + ".sig") public_key_pem = fetch(BASE_URL + ".pub.pem") public_key = serialization.load_pem_public_key(public_key_pem) try: public_key.verify(signature, normalize(content)) print("[OK] 签名验证通过") except Exception: print("[FAIL] 签名验证失败,拒绝解析") raise SystemExit(1) text = content.decode("utf-8") match = re.match(r"^---\n(.*?)\n---\n(.*)$", text, re.DOTALL) if not match: raise ValueError("无法解析 frontmatter,请检查文件格式") metadata = yaml.safe_load(match.group(1)) result = { "handle": metadata.get("handle"), "display_name": metadata.get("display_name"), "created_at": metadata.get("created_at"), "updated_at": metadata.get("updated_at"), "schema_version": metadata.get("schema_version"), } print(json.dumps(result, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()

运行:

python agent_read_username.py

输出示例:

[OK] 签名验证通过 { "handle": "example", "display_name": "张三", "created_at": "YYYY-MM-DD", "updated_at": "YYYY-MM-DD", "schema_version": "1.0" }

这里的关键步骤是:先验签,再解析。Agent 不能在验签失败后继续信任文件里的任何内容。

如果你更喜欢 Node.js,可以参考下面这段核心逻辑:

// 文件路径:sign_and_verify.mjs import { generateKeyPairSync, sign, verify } from "crypto"; import { readFileSync, writeFileSync } from "fs"; // 生成密钥 const { privateKey, publicKey } = generateKeyPairSync("ed25519"); writeFileSync("private_key.pem", privateKey.export({ type: "pkcs8", format: "pem" })); writeFileSync("public_key.pem", publicKey.export({ type: "spki", format: "pem" })); // 签名 const content = readFileSync("username.md"); const signature = sign(null, content, privateKey); writeFileSync("username.md.sig", signature); // 验签 const ok = verify(null, content, publicKey, signature); console.log(ok ? "签名验证通过" : "签名验证失败");

6. 常见问题与排查思路

6.1 高频问题速查

问题现象常见原因解决思路
验签失败,提示文件不匹配签名后编辑过 Markdown,或换行符不一致使用规范化函数;签名后不要修改文件内容
私钥泄露私钥文件提交到 Git 或公开分享立即重新生成密钥对,更新所有信任锚点
Agent 拉不到文件路径写错,或部署目录不正确检查.well-known路径与文件名大小写
yaml.safe_load 报错frontmatter 格式不符合 YAML 规范检查冒号后是否有空格,字符串是否需要引号
中文内容乱码文件编码不是 UTF-8统一使用 UTF-8 编码保存
公钥指纹对不上验证方使用了错误的公钥文件从多个独立渠道核对公钥指纹

6.2 验签失败怎么排查

验签失败是实操中最常见的问题。按以下顺序排查:

  1. 确认签名文件没有损坏:对比本地和服务器上的username.md.sig文件大小与内容。
  2. 确认username.md没有被编辑器自动转换:有些编辑器会默认把行尾改成 CRLF,导致内容字节变化。
  3. 使用规范化函数:参考 5.3 节的normalize,统一处理换行符。
  4. 重新签名:如果确认是文件内容变化,修改后重新运行sign_username.py并重新部署三个文件。
  5. 检查浏览器缓存:如果 Agent 命中 CDN 缓存,可能拉取到旧文件。

6.3 私钥泄露处理流程

如果私钥泄露,签名体系不再可信。处理顺序如下:

  1. 立即在本地生成新的密钥对。
  2. 更新 username.md 中的 updated_at 字段。
  3. 用新私钥重新签名并部署。
  4. 在 GitHub、社交账号等多处更新公钥指纹,标注旧指纹已失效。
  5. 如果可能,在旧地址发布失效声明,说明旧公钥在什么时间之后不再可信。

这个流程的前提是你有多个身份渠道可以交叉发布消息。设计时不要只依赖单一平台,否则身份恢复会很难。

7. 最佳实践:让 username.md 值得被信任

7.1 私钥安全管理

私钥是整个信任链的根。建议遵循以下原则:

  • 私钥只保存在本地或离线设备中,不要放入 Git 仓库。
  • 使用独立、专用的密钥,不要与 SSH、代码签名等其他用途共用。
  • 设置文件权限为当前用户可读写,避免其他进程读取。
  • 定期更换密钥,建议每 6 到 12 个月轮换一次。
  • 如果使用 CI/CD 自动部署,不要直接把私钥写入环境变量,优先使用云密钥管理服务或加密变量。

这里再强调一次最小权限原则:签名脚本只需要读取私钥、读取文件、写入 sig 文件,不要给脚本提权,也不要在脚本中把私钥内容打印到日志。

7.2 多平台交叉验证

单靠 username.md 本身无法证明公钥属于你。为了让公钥指纹可信,建议在多个平台公布同一个指纹:

  • GitHub 个人主页:在 README 或 profile 中写入公钥指纹。
  • 社交平台:在介绍区域写上指纹和 username.md 地址。
  • 邮件签名:把指纹和文件地址加入邮件签名。
  • 博客:专门发布一篇“我的公开身份密钥”文章。

Agent 在验证时,最少在一个独立渠道核对指纹,再信任文件内容。这样即使某个平台账号被替换,攻击者也很难同时控制所有渠道。

7.3 内容更新与版本化

身份信息会变化,建议在文件中保留:

  • created_at:首次创建时间。
  • updated_at:最后更新时间。

同时可以保留历史签名快照。例如在archive/目录下存放历史版本和对应签名,便于追溯。Agent 读取时也可以优先检查updated_at,如果文件更新频率低,可以设置较长的缓存时间;如果更新频繁,需要短缓存或每次拉取。

7.4 Agent 兼容性与长期演进

Markdown 本身的好处是类型简单、跨平台、易渲染。但 Agent 解析能力依赖约定,所以需要注意:

  • 不要随意改动 frontmatter 字段名,新增字段也不会影响旧 Agent。
  • 保持正文区块标题稳定,方便 Agent 按标题提取。
  • 如果你需要更严格的语义描述,可以在文件中嵌入 JSON-LD 或普通 JSON 区块,但建议用 frontmatter 中的schema_version字段标注版本。
  • 预先定义好“未知字段忽略”的解析策略,避免某个字段缺失导致整个身份文件不可用。

8. 总结与下一步实践方向

Username.md 提供了一个很轻的身份载体思路:用 Markdown 组织内容,用 Ed25519 保证完整性,用自托管保证拥有权,用 frontmatter 保证 Agent 可读。

今天我们完成了从密钥生成、身份文件编写、签名、验证、部署到 Agent 拉取解析的完整闭环。实现并不复杂,真正需要花心思的是信任模型:签名算法选型只是第一步,之后的公钥指纹交叉验证、私钥保护、更新轮换,才是让这个文件长期可信的关键。

下一步你可以做的几个尝试方向:

  • 把 username.md 部署到你自己的域名下,并在 GitHub 和常用社交平台公布公钥指纹。
  • 写一个更完整的 Agent 解析器,支持读取 Verified Accounts 区块,并且能校验各平台主页是否链接回了 username.md。
  • 增加 JSON-LD 或 microdata 版本的机器可读数据,让搜索引擎和 Agent 都能理解。
  • 在团队内部约定统一身份文件格式,作为员工公开资料的标准模板。

看完了这份实操教程,你打算把自己的 username.md 放在哪个域名下?评论区可以聊聊你的方案和踩坑经历。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询