尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

动态OG图片+签名校验:低成本证明“我不是AI”的轻量人机验证方案

动态OG图片+签名校验:低成本证明“我不是AI”的轻量人机验证方案 这次我们来看一个不太一样的实验怎么用一张 OG 图片证明“我不是 AI”。这个系列的标题叫Going out of my way to prove Im not an AI第一集的主题就是 OG images。思路不算复杂但实际做出来之后你会发现它比想象中更适合作为“轻量人机证明”的测试载体不依赖 GPU不需要训练模型也不需要复杂的区块链签名只要一个 Web 服务、一段动态生成图片的代码以及一个签名校验接口就能生成一张带实时信息和签名的社交媒体预览卡。如果你最近被 AI 生成的内容搞得草木皆兵或者自己写的文章、代码经常被怀疑是 AI 写的这篇内容可以给你一个很实用的回击思路。下面会从核心思路、环境准备、部署启动、接口测试、批量任务到常见问题完整走一遍。1. 核心能力速览能力项说明项目类型动态 OG 图片生成 轻量人机验证实验核心功能生成带时间戳、用户名、签名的社交分享预览卡技术栈Python Flask Pillow可替换为 Node.js sharp硬件门槛纯 CPU 即可不需要独立显卡启动方式命令行启动 Web 服务接口能力HTTP GET /og 返回 PNG/verify 返回 JSON批量任务通过脚本批量请求生成多张卡片签名校验HMAC-SHA256防参数篡改适合读者开发者、内容创作者、社区运营者从实现原理来看这个方案对显卡完全无需求。重点在于“动态生成 签名校验 社交平台展示”这条链路。2. 适用场景与使用边界这个实验最适合的场景是有人质疑你的内容由 AI 生成时你随手发一个链接链接对应的 OG 图片展示着当前时间、你的用户名和一个签名。因为这张图片是服务器实时渲染的并且签名只能由你自己的服务生成所以在一定程度上能证明“背后有一个真实的人在维护这套服务”。具体来说可以用在下面几个地方个人博客、评论区签名、群聊里遇到“你肯定是 AI”的时候。给内容或文章配一张“人工动态卡片”作为发布者的身份标识。社区机器人、自动发布系统需要对外展示“本页面由真人配置”的场景。但要注意边界。这个方法不是强身份认证更不能用于金融、法律或高安全性验证。它的本质是一个低成本、可验证、带娱乐性质的实验。如果有人恶意拿它去伪装成真人骗取信任那不仅违背这个实验的初衷在部分业务场景下还可能涉及欺诈。另外要强调合规如果卡片里出现的文字、头像、品牌信息涉及他人版权需要先获得授权。不要把签名机制理解成“绝对防伪”它只是防止普通用户随便伪造同一套卡片内容。3. 环境准备与前置条件这个实验的环境要求很低基本等同于一个普通的 Python Web 服务。推荐环境Python 3.9 或更高版本。pip 包管理工具。Flask提供 HTTP 接口。Pillow负责动态绘制 OG 图片。可选cryptography 或 Python 内置 hmac用于签名校验。一台能运行 Python 的电脑本地调试即可如果要让社交平台抓取需要部署到公网服务器。如果是 Linux 服务器还需要确认中文字体是否安装。如果你生成的图片中要显示中文但系统没有中文字体Pillow 会报错或显示方框。准备目录结构og-proof/ ├── app.py ├── requirements.txt ├── items.csv # 批量生成时使用 └── output/ # 生成结果存放目录requirements.txt 内容Flask3.0.3 Pillow10.4.0依赖装好后就能启动服务了。4. 安装部署与启动方式4.1 安装依赖在项目目录下执行pip install -r requirements.txt4.2 编写动态 OG 图片服务下面是一版可以直接运行的参考实现。它包含两个接口/og根据参数动态生成 PNG 图片。/verify校验图片中签名的合法性。注意HMAC 签名默认只覆盖请求参数目的是防止别人伪造同一个用户名和文本对应的签名。如果你要更严格地校验图片本身没有被 PS 修改需要额外对图片二进制内容做哈希签名这里先演示最简版本。import io import os import time import hashlib import hmac from flask import Flask, request, send_file, jsonify from PIL import Image, ImageDraw, ImageFont app Flask(__name__) SECRET os.environ.get(OG_SECRET, dev-secret-change-me) def sign_text(payload: str) - str: 计算 HMAC-SHA256 签名并截取前 16 位 digest hmac.new( SECRET.encode(utf-8), payload.encode(utf-8), hashlib.sha256 ).hexdigest() return digest[:16] def verify_sign(username: str, text: str, ts: str, sign: str) - bool: 校验参数签名是否一致 payload f{username}{text}{ts} expected sign_text(payload) return hmac.compare_digest(expected, sign) def render_og_image(text: str, username: str, ts: int, theme: str dark): 动态绘制一张 1200x630 的 OG 图片 width, height 1200, 630 if theme dark: bg_color (17, 24, 39) text_color (255, 255, 255) accent_color (56, 189, 248) else: bg_color (255, 255, 255) text_color (15, 23, 42) accent_color (37, 99, 235) image Image.new(RGB, (width, height), bg_color) draw ImageDraw.Draw(image) # 字体加载优先使用系统字体缺失时退化为默认字体 try: font_title ImageFont.truetype( /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf, 52 ) font_body ImageFont.truetype( /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, 28 ) except Exception: font_title ImageFont.load_default() font_body ImageFont.load_default() sign sign_text(f{username}{text}{ts}) draw.text((60, 60), human-proof OG card, fontfont_title, fillaccent_color) draw.text((60, 180), text, fontfont_body, filltext_color) draw.text((60, 360), fusername: {username}, fontfont_body, filltext_color) draw.text((60, 420), ftime: {ts}, fontfont_body, filltext_color) draw.text((60, 480), fsign: {sign}, fontfont_body, fillaccent_color) buf io.BytesIO() image.save(buf, formatPNG) buf.seek(0) return buf app.route(/og) def generate_og(): text request.args.get(text, hello, I am not an AI) username request.args.get(username, guest) theme request.args.get(theme, dark) ts int(time.time()) image_buffer render_og_image(text, username, ts, theme) return send_file(image_buffer, mimetypeimage/png) app.route(/verify) def verify_og(): text request.args.get(text, ) username request.args.get(username, ) ts request.args.get(ts, ) sign request.args.get(sign, ) ok verify_sign(username, text, ts, sign) return jsonify({ok: ok, sign: sign}) if __name__ __main__: app.run(host0.0.0.0, port8000)这段代码的核心是把“参数签名”放进图片里然后通过/verify接口提供校验。只要 SECRET 不泄露别人就很难伪造出和你相同的签名前缀。4.3 启动服务启动前先设置签名密钥export OG_SECRETmy-secret-key然后启动python app.py看到 Flask 日志输出上线地址后说明服务运行正常。4.4 接入社交平台 OG 标签要让生成的图片真正作为链接卡片展示需要在网页 HTML 里写上 Open Graph 标签。一个最简单的示例meta propertyog:title contenthuman-proof card / meta propertyog:description contentThis card is generated on the fly. / meta propertyog:image contenthttps://your-domain.com/og?texthellousernameguestthemedark /当你在聊天软件里发送https://your-domain.com/og?texthellousernameguest时平台爬虫会读取这个 URL 返回的图片并生成卡片。注意一个问题很多社交平台会缓存 OG 图片。如果你希望每次访问都看到最新内容需要在 URL 中加入随机参数例如https://your-domain.com/og?texthellousernameguestv1720000000但这样会让同一个链接内容每次都可能变化需要结合具体平台测试。5. 功能测试与效果验证5.1 本机验证图片生成先用 curl 请求一张图片确认服务能正常工作curl -o og.png http://127.0.0.1:8000/og?texthellousernameadminthemedark然后查看生成的文件ls -lh og.png如果能生成og.png说明 Flask 和 Pillow 都正常工作。5.2 验证图片内容用 Python 打开图片确认尺寸和文字from PIL import Image img Image.open(og.png) print(img.size) # 输出 (1200, 630) img.show()如果文字显示不全需要调整 Pillow 中 draw.text 的坐标位置。5.3 验证签名接口先提取图片中的签名然后请求/verify接口校验curl http://127.0.0.1:8000/verify?texthellousernameadmints1720000000signxxxxxx如果参数匹配返回{ok: true, sign: xxxxxx}如果签名不对返回{ok: false}。这里的关键判断标准是只有服务端持有相同的 SECRET才能生成相同的签名前缀。别人在不知道密钥的情况下很难伪造同一个用户名和时间戳对应的合法签名。5.4 社交平台预览测试将服务部署到公网后打开一个支持 OG 卡片预览的社交平台发送你的/og链接。如果出现你生成的动态图片说明整条链路已经打通。如果没有出现多半是域名没有 HTTPS或平台抓取超时可以先用 URL 解析工具检测。6. 接口 API 与批量任务这个实验虽然简单但动态生成图片天然适合 API 化和批量处理。6.1 GET /og 参数说明参数类型说明textstring卡片上的主文字usernamestring用户名用于展示和签名themestring可选 dark / lighttsint时间戳默认由服务端生成返回image/png的图片流。早期版本可以直接把时间戳作为参数传入方便验证签名/og?texthellousernameadminthemedarkts1720000000如果你需要主动控制时间戳可以在服务端代码里读取request.args.get(ts)这里留给读者自行扩展。6.2 curl 调用示例curl -o card.png https://your-domain.com/og?textAI%20or%20humanusernameadminthemelight6.3 Python 批量生成脚本当你有几十条文本需要生成卡片时手动请求太慢可以写一个简单的批量脚本。先把待生成内容放到items.csvid,text,username,theme 1,第一张动态卡片,admin,dark 2,第二张动态卡片,alice,light 3,第三张动态卡片,bob,dark然后写批量请求脚本import csv import requests API_URL http://127.0.0.1:8000/og with open(items.csv, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: params { text: row[text], username: row[username], theme: row.get(theme, dark), } response requests.get(API_URL, paramsparams, timeout30) if response.status_code 200: filename foutput/{row[id]}_{row[username]}.png with open(filename, wb) as out: out.write(response.content) print(saved, filename) else: print(failed, row[id], response.status_code)批量任务建议加一点异常处理比如请求超时后重试两次。生成大量图片时可以加一个time.sleep(0.1)控制请求频率避免打到自己的服务太密集。6.4 批量验证思路如果要批量验证一批卡片是否为你的服务生成可以提取每张卡片中的text、username、ts、sign然后逐个调用/verify接口。实际项目中也可以把签名信息直接放到文件名或数据库表里方便审计。7. 资源占用与性能观察这个实验方案对资源占用非常克制因为它本身不涉及大模型推理只是用 Pillow 在内存里绘制一张 PNG 图片。CPU主要消耗在图片渲染和 PNG 编码上单张 1200x630 图片通常很轻量。内存Flask 进程 Pillow 渲染取决于图片字体和文字数量属于典型的轻量 Web 服务。显卡完全不需要启动时也没有 CUDA / PyTorch 相关依赖。观察资源占用的方法Linux 下用top或htoptop -p $(pgrep -f app.py)用psutil在 Python 里采样import os import psutil process psutil.Process(os.getpid()) print(process.memory_info().rss / 1024 / 1024, MB)如果后续需要提高并发处理能力建议在 Flask 前面加一层 Nginx 做静态资源缓存并把/og接口生成的图片缓存到磁盘或对象存储里。但要注意如果缓存了图片动态刷新能力就会变弱。这里的平衡取决于你的业务场景。8. 常见问题与排查方法问题现象可能原因排查方式解决方案访问 /og 返回 500Pillow 字体路径不存在查看 Flask 日志换成默认字体或安装中文字体图片能生成但中文乱码系统没有中文字体检查字体目录下载并指定一个中文字体文件端口被占用8000 端口已被其他进程使用lsof -i:8000换端口启动或关闭占用进程社交平台不显示卡片域名不是 HTTPS 或网络不可达用抓取调试工具检测部署到带 HTTPS 的服务器图片显示但签名校验失败SECRET 不一致或参数顺序不一致对比服务端签名逻辑确保签名时拼接字段的顺序一致批量脚本发送请求太慢没有控制并发查看脚本耗时使用线程池或异步请求动态卡片被平台缓存平台抓取器缓存了旧 URL在 URL 中加入随机参数生成唯一路径或使用平台刷新工具9. 最佳实践与使用建议如果要把这个实验做得更严谨有几个值得优化的点。第一签名字段应该覆盖完整请求参数并且把时间戳的过期时间也算进去。比如签名有效期为 5 分钟超过后服务端拒绝校验。这样能减少重放攻击的影响。第二生成图片时不要只用默认字体。中文场景下建议在项目中自带一个开源字体文件例如思源黑体或 Noto Sans CJK部署时就不会依赖服务器字体环境。第三批量任务要设计成可重试。每次请求/og都会动态生成图片如果网络抖动导致任务失败脚本需要有能力重新请求。更稳妥的方式是先把待生成内容写入队列再由 worker 消费。第四对外提供服务时一定要做好访问控制。如果你的服务暴露在公网任何知道 URL 的人都可以调用/og生成图片这可能会消耗服务器资源。建议在 Nginx 层限制访问频率或者在 Flask 里加一个简单的 Token 校验。第五整个方案更适合作为“趣味身份证明”而不是安全方案。真正需要确认身份的系统应该使用 WebAuthn、短信验证码、企业级 SSO 等服务。10. 总结与下一步这个项目给我最大的启发是证明“我不是 AI”不一定需要复杂的模型或昂贵设备一个动态生成的 OG 图片就能形成一条可验证的证据链。它利用了社交平台对 Open Graph 图片的抓取机制把“由真人维护的服务”这个事实以图片形式展示出来。最先值得验证的功能很简单本地启动服务生成一张带签名的卡片再用/verify接口校验一次。只要这条链路跑通后面接批量生成、接入博客、部署到公网都是顺手的事。最容易踩的坑有三个字体不显示、社交平台缓存、HTTPS 访问不通。建议先把本机流程跑通再部署到公网。下一步可以考虑扩展的方向很多把签名改成基于公钥的非对称签名这样不需要共享密钥在图片中嵌入二维码扫码后打开验证页把同一套逻辑接入 Telegram Bot 或飞书机器人让对方直接在聊天里发送指令完成验证。这套实验本身不大但作为“人机边界”话题的切入点已经值得动手试一次了。
返回列表