
当业务账号从“人”扩展到“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 典型使用场景从这类格式的特性来看比较适合的场景包括跨平台身份聚合 把 GitHub、Twitter、邮箱、个人博客统一挂在一个自托管文件下Agent 拉取一份文件即可获取全套公开联系信息。自动化招聘筛选 招聘系统读取候选人的 username.md验证签名后提取邮箱、作品集链接、社交账号降低候选人在多个平台重复填写信息的成本。开发者工具身份关联 终端工具执行agent read https://example.com/.well-known/username.md完成身份校验后自动把你贡献过的项目与你本人关联。邮件防伪 邮件正文里携带 username.md 地址和签名哈希接收方可以验证这封邮件确实是来自公开身份声明中注册的邮箱。这些场景有一个共同特征调用方是程序不是人。程序需要的是稳定字段、明确格式、可验证签名。2.3 信任模型签名只解决“是否篡改”还要解决“公钥是谁的”理解 Username.md 时最容易被误解的一点是签名能证明“内容没有被修改”但签名本身不能证明“写内容的人就是现实中的那个你”。签名验证的逻辑是用公钥验证文件签名 → 通过 → 文件确实由持有对应私钥的人发布但这里还有一个前提问题公钥从哪来如果攻击者控制了你的域名同时替换了 username.md 和公钥文件签名验证依然可以通过但内容已经是攻击者的了。所以真正完整的信任模型是两层签名层私钥证明文件完整性和签发者身份。信任锚点层公钥指纹通过 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.pemusername.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 | exampleexample.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 中的handle、display_name、created_at、updated_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 pyyamlcryptography用于生成 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.md5.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( encodingserialization.Encoding.PEM, formatserialization.PrivateFormat.PKCS8, encryption_algorithmserialization.NoEncryption(), ) ) # 保存公钥 with open(public_key.pem, wb) as f: f.write( public_key.public_bytes( encodingserialization.Encoding.PEM, formatserialization.PublicFormat.SubjectPublicKeyInfo, ) ) print(私钥已保存到 private_key.pem不要公开) print(公钥已保存到 public_key.pem)运行python generate_keys.py此时目录中会生成private_key.pem和public_key.pem。私钥文件权限建议设置为当前用户可读写chmod 600 private_key.pem5.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(), passwordNone) 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( encodingserialization.Encoding.PEM, formatserialization.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-2569f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08这个指纹要记录下来后续可以发布到多个平台作为交叉验证依据。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( encodingserialization.Encoding.PEM, formatserialization.PublicFormat.SubjectPublicKeyInfo, ) fingerprint hashlib.sha256(public_bytes).hexdigest() print(当前公钥指纹SHA-256, fingerprint) if __name__ __main__: main()运行python verify_username.py预期输出签名验证通过当前 username.md 内容未被篡改。 当前公钥指纹SHA-2569f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08你可以尝试修改username.md的任意一个字符再运行验证脚本会看到验证失败。这是理解“签名防篡改”最直观的方式。5.5 部署到静态站点把username.md、username.md.sig、public_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_asciiFalse, indent2)) 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 验签失败怎么排查验签失败是实操中最常见的问题。按以下顺序排查确认签名文件没有损坏对比本地和服务器上的username.md.sig文件大小与内容。确认username.md没有被编辑器自动转换有些编辑器会默认把行尾改成 CRLF导致内容字节变化。使用规范化函数参考 5.3 节的normalize统一处理换行符。重新签名如果确认是文件内容变化修改后重新运行sign_username.py并重新部署三个文件。检查浏览器缓存如果 Agent 命中 CDN 缓存可能拉取到旧文件。6.3 私钥泄露处理流程如果私钥泄露签名体系不再可信。处理顺序如下立即在本地生成新的密钥对。更新 username.md 中的 updated_at 字段。用新私钥重新签名并部署。在 GitHub、社交账号等多处更新公钥指纹标注旧指纹已失效。如果可能在旧地址发布失效声明说明旧公钥在什么时间之后不再可信。这个流程的前提是你有多个身份渠道可以交叉发布消息。设计时不要只依赖单一平台否则身份恢复会很难。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 放在哪个域名下评论区可以聊聊你的方案和踩坑经历。