证书是一份公开文件。它的 SHA-256 指纹由证书内容算出,任何拿到同一份证书的人算出来都一样;指纹一旦改动一个字节就完全不同。
| 协商结果 | TLSv1.3 · TLS_AES_256_GCM_SHA384 |
| 现场证书 SHA-256 | F0:2B:12:79:91:E9:24:0F:BE:26:96:56:E1:13:B5:C2:E6:D1:BD:A3:27:0E:E2:E9:55:DD:9F:E9:8B:BD:D3:B5 |
| 与留档副本 | 无法比对 |
上面这段是服务端在 2026-11-08 15:54:56 用域名 SNI 连本站 443 端口取回的证书指纹(标准证书链校验,证书不受信/过期会直接报错)。
# 1) 看浏览器里正在用的证书指纹 # Chrome/Edge:地址栏锁图标 → 连接安全 → 证书 → 详细信息 → 指纹 # 对照上面「证书 SHA-256 指纹」那一行 # 2) 命令行拉一次证书并算指纹(Windows PowerShell 可用 openssl,Linux/macOS 同理) openssl s_client -connect tianyihome.top:443 -servername tianyihome.top </dev/null 2>/dev/null \ | openssl x509 -noout -fingerprint -sha256 -subject -issuer -dates # 3) 只要公钥(可从任何地方拿到证书,无需私钥) openssl s_client -connect tianyihome.top:443 -servername tianyihome.top </dev/null 2>/dev/null \ | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256
Let's Encrypt 签发的每张证书都会被登记到公开的证书透明度日志。这不是我们的服务器,也不由我们控制,所以它是一份独立证据:能查到这张证书何时、由哪个机构签发。
查询地址:https://crt.sh/?q=tianyihome.top
在结果里核对上面那串证书 SHA-256 指纹(crt.sh 显示为 Certificate SHA-256 Fingerprint),以及签发时间是否与本地留档一致。
我们不开源整站(数据与密钥配置都在同一目录,直接公开仓库等于把密钥送到别人手上)。替代做法是把与账号安全直接相关的那几个文件的 SHA-256 指纹公开:你只要拿到项目源码的任意一份副本,就能算出同一个哈希,验证「这份代码就是线上跑的那份」。
| 文件 | 作用 | SHA-256 |
|---|---|---|
| app.py | 门户主程序:会话、安全响应头、CSRF 纵深校验、密码哈希、加密归档 | 20a1361d70ba1fad708aad9747c6d2cf71a5cb0a64caa8cf48765d7ecca10e0d |
| routes/auth_points_routes.py | 登录/注册/改密/风控:失败计数、限流、设备指纹 | 66b440c8a87c96a19c68c9420ba5c3b1b0321416ac3567b7aedfe2124444cd94 |
| routes/account_security_routes.py | 安全事件时间线、安全检查清单、登录提醒 | a2fc4f441c3bd82a0fe1a03fa3cbb56c974c654e886557acdf99a4572bfbee6b |
| routes/privacy_snapshot_routes.py | 隐私修改快照:5 天内可一键还原(非本人操作可回滚) | 51a4ae305a7e94c53473130886d2c4787cd3693b8b918ed2f8a6a095508fffa6 |
| routes/account_deletion_routes.py | 账号注销:7 天冷静期与彻底删除 | 6ffe4e1a1c1e0e40755eff36c8b052152c8806288b9f566cab1b522a6dc6b90e |
| routes/risk_login_routes.py | 异地/新设备风险登录二次校验 | 5765f4232853af107fd54dc297a20081bb6e4ff0d5a9a769c71904ed80893c87 |
| routes/qr_login_routes.py | 扫码登录:一次性令牌与设备绑定 | de1ecb245309ded4f8cb1ff115db252b23d61f38fe3e1e90d94c057881c14835 |
| routes/device_key_routes.py | 设备密钥:明文只出现一次,库内只存 SHA-256 | 76eeb97ff03ef66912d8577b25095fb4700125a950bdf894354a63f7c583987e |
| routes/usage_routes.py | 用量统计页:转义与鉴权(原 SSTI 修复点) | a6ca7ab90490885da1b86614bb4a57f30dc76b4ac678a49b725b75975846e316 |
| routes/store_admin_routes.py | 管理接口鉴权与公开最小化接口拆分 | f55c1dcf3dda607af2a34339a45905b1af7d54e1ddc6a37b175f4e426426e5f2 |
| app_api/security.py | APP 侧安全层:bcrypt、TOTP、恢复码、令牌哈希 | 66d0f068026e35e60832188da3620a2f5562d459d824078a923fbd4f4c187afb |
| handshake_token.py | 门户到聊天子应用的一次性握手令牌 | 6e91362228a3d70359d026994d1b2ab6fa8b1f52645f91e77e6b9877866887b5 |
| account_deletion_service.py | 注销状态机与到期清理 | f9f9a4ca71e733d818eb25e4a6287d46483f29db059680035f94d44298af7def |
清单外的文件不在公开范围;这里也不列出文件大小以外的任何配置值。哈希每次打开本页都会重新计算,不会展示过期缓存值。
# Windows PowerShell Get-FileHash .\app.py -Algorithm SHA256 | Format-List # Linux / macOS sha256sum app.py
下面这些是按 app.py 的 after_request 规则推算的本次响应头(响应头在渲染之后才写入,页面本身读不到自己这一次的响应)。要独立核对,用浏览器开发者工具 Network 面板看任意一个请求的 Response Headers,或把本站地址丢给任意在线 Header 检测服务。
| 响应头 | 本次取值 |
|---|---|
| Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; img-src 'self' data: blob: https:; media-src 'self' data: blob:; worker-src 'self' blob:; connect-src 'self' https://api.open-meteo.com https://air-quality-api.open-meteo.com https://api.bigdatacloud.net https://api-bdc.io https://ipapi.co https://weather.tianyihome.top https://weather.tianyiapi.top https://status.yizhihuafenya.com; |
| X-Frame-Options | SAMEORIGIN |
| X-Content-Type-Options | nosniff |
| X-XSS-Protection | 1; mode=block |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera=(self), microphone=(self), geolocation=(self), payment=(), usb=(), serial=(), midi=(), display-capture=() |
| Strict-Transport-Security | max-age=31536000; includeSubDomains |
| Server | TianyiHome/1.0 |
一般用户可用的建议:CSP 里已经去掉 unsafe-eval,但仍保留 unsafe-inline(前端存在大量内联脚本),这一点我们如实标注、不假装它是满分的头。
| 防护项 | 在代码里的落点 |
|---|---|
| bcrypt(cost=12)+ 每用户独立盐 | app.py 的 hash_password / verify_password;bcrypt 缺失时直接报错,不降级为无盐 SHA-256 |
| 旧哈希登录即时升级 | 历史无盐 SHA-256 仅在登录成功那一刻校验一次,随后就地改写为 bcrypt |
| 改密即踢掉其它会话 | users.password_version 递增,旧会话版本号不匹配立即强制重新登录 |
| TOTP 两步验证(RFC 6238) | 6 位动态码、30 秒周期、前后各容忍 1 个周期;密钥 20 字节随机 |
| 恢复码只存哈希 | sha256("tianyi-recovery:" + 去横杠大写),明文不落库 |
| 设备密钥与登录令牌只存哈希 | tk_ / at_ 明文仅生成时返回一次,库内只有 SHA-256 |
| 登录失败临时封禁 | 5 分钟窗口内同一 IP ≥10 次或同一账号 ≥5 次失败 → 临时封禁 15 分钟 |
| 新设备/异地登录二次校验 | 按「UA+语言+平台+机型」算设备指纹(不含 IP 偏移),命中风险则要求动态码/恢复码 |
| 隐私修改快照与一键回滚 | 改密、改邮箱、换 TOTP 等操作先存修改前状态,5 天内非本人操作可校验后还原 |
| 注销冷静期与彻底删除 | 注销申请后 7 天冷静期,期间可撤回;到期彻底删除账号数据 |
| 归档内容端到端加密 | AES-256-GCM,密钥由用户自设的归档密码派生,服务端只存密文 |
| 响应侧安全头 | CSP(已移除 unsafe-eval)、X-Frame-Options: DENY、nosniff、Referrer-Policy、Permissions-Policy |
你可以配合上一节的哈希清单,验证这些实现确实存在于线上那份文件里。
127.0.0.1 或局域网 IP 直接访问门户,链路没有加密(浏览器与服务器在同一台机器上时这是常规做法)。请通过 HTTPS 域名访问以获得传输加密。发现问题或想核对某个文件?可以直接在站内反馈提交,附上你要核对的指纹和算指纹的命令。