终端中的端到端加密群聊、私密消息和文件传输。服务器是一个盲转发器:它无法读取您的消息,不知道您的用户名,不知道您所在的房间,即使被完全入侵,也无法将任意两条消息关联到同一个人。
https://github.com/user-attachments/assets/d9faabfb-73bd-46dd-92b2-23f63daf5b06
https://github.com/user-attachments/assets/e8e0220d-cd7d-45a2-9443-9a5f20b57f12
https://github.com/user-attachments/assets/15fb383d-a02a-433e-bbd9-8ebadecf9481
https://github.com/user-attachments/assets/bca10cb1-6959-425d-96d6-fc1fbf845538
NoEyes 是一款面向小型可信群组的 Python 终端聊天工具。服务端从不解密任何内容,也从不知道您的身份——它只处理不透明的令牌并转发加密字节。
您生成密钥,通过带外方式分享出去,服务端对您的对话一无所知。
适用于希望在不信任任何第三方服务器的情况下进行加密通信的小型可信群组、希望自托管具备真正端到端加密的私密聊天的用户,或任何想确切了解服务器能看到什么、不能看到什么的人。
| 功能 | 详情 |
|---|---|
| 零元数据服务器 | 服务端永远看不到用户名、房间名或公钥,只看到不透明的令牌 |
| 密封发送者 | 发送者身份位于加密负载内部,绝不出现在路由头中 |
| 盲转发服务器 | 零解密,服务端转发其无法读取的加密数据块 |
| 前向保密 | /ratchet start — Sender Keys 协议,每条消息使用独特的派生密钥加密,即使当前密钥泄露,历史消息仍然安全 |
| 群聊 | 通过 BLAKE2b 派生每个房间独立的 XSalsa20-Poly1305 密钥,房间之间在密码学上相互隔离 |
| 私密消息 | 首次联系时进行 X25519 DH 握手,生成仅双方持有的成对密钥 |
| 文件传输 | ChaCha20-Poly1305 流式传输,任意大小,低内存占用,支持断线重连后暂停/恢复 |
| Ed25519 身份 | 自动生成签名密钥,所有消息和文件均经过签名 |
| TOFU | 首次看到的密钥即受信任;密钥不匹配会触发明显的安全警告 |
| 随机 PBKDF2 盐 | 每次部署获得独特的随机盐,彩虹表毫无用处 |
| TLS + 证书固定 | 传输加密,服务器证书在首次接触时通过 TOFU 固定 |
| 重放保护 | 每个房间的消息 ID 双端队列,重放的帧被静默丢弃 |
| 分栏侧边面板 | 房间(顶部)和用户(底部)始终可见,每半部分独立滚动 |
| CRT 启动动画 | 启动时全屏荧光效果并伴有声音 |
| 棘轮激活动画 | 全屏 CRT 效果,包含盲文齿轮图案、闪烁故障、聚光灯扫过、同步音效,以及 TUI 界面切换为红色 |
| 引导式启动器 | 方向键菜单界面,无需命令行经验 |
| 自动依赖安装器 | 检测您的平台,安装缺失组件,在更改任何内容前先征询确认 |
python ui/setup.py
python ui/launch.py
`ui/launch.py` 会逐步引导你启动服务器或连接到服务器。
---
### 选项 B - 如果尚未安装 Python
| 平台 | 请先运行 |
|---|---|
| Linux / macOS / Termux / iSH | `sh install/install.sh` |
| Windows | `install\install.bat` |
这两个脚本会在缺少 Python 时安装它,然后自动交由 `setup.py` 处理。
---
### 选项 C - 手动```bash
# 1. Install dependencies
pip install cryptography PyNaCl
# 2. On the server machine — generate the access key
python noeyes.py --generate-access-key
# Prints an access code hex string — share with clients via USB
# 3. On a client machine — generate chat.key from the access code
python noeyes.py --generate-chat-key <ACCESS_CODE_HEX> --key-file ./chat.key
# Distribute chat.key to all other clients via USB. Never put it on the server.
# 4. Start the server (does NOT need the key file)
python noeyes.py --server --port 5000
# Start without bore tunnel (LAN / static IP / custom tunnel)
python noeyes.py --server --port 5000 --no-bore
# Start without adding a firewall rule (not needed when using bore tunnel)
python noeyes.py --server --port 5000 --no-firewall
# 5. Connect clients - each person needs their own identity file
python noeyes.py --connect SERVER_IP --port 5000 --username alice --key-file ./chat.key --identity-path ~/.noeyes/identity_alice.key
python noeyes.py --connect SERVER_IP --port 5000 --username bob --key-file ./chat.key --identity-path ~/.noeyes/identity_bob.key
重要提示: 每个用户都必须拥有自己的身份文件。两个客户端共用同一个身份文件会获得相同的收件箱令牌,服务器会将第二个客户端视为重复会话并拒绝连接。身份文件会在首次运行时自动生成,只需为每个用户传入唯一的
--identity-path即可。
从 F-Droid(推荐)下载 Termux:https://f-droid.org/packages/com.termux/
保持会话存活 - 安装 tmux,这样在切换应用时 NoEyes 仍能持续运行:```bash pkg install tmux -y tmux python ui/launch.py
**存储权限** - 没有此权限,文件传输将失败:```bash
termux-setup-storage
| 命令 | 描述 |
|---|---|
/help | 显示所有命令 |
/quit | 断开连接并退出 |
/clear | 从屏幕清除消息 |
/users | 列出当前房间中的用户 |
/join <room> | 切换到某个房间(若处于活动的 ratchet 中会发出警告) |
/leave | 返回公共房间(若处于活动的 ratchet 中会发出警告) |
/msg <user> <text> | 发送端到端加密的私密消息 |
/send <user> <file> | 发送加密文件 |
/whoami | 显示你的身份指纹 |
/trust <user> | 在用户重新安装后信任其新密钥 |
/notify on|off | 切换通知声音 |
/ratchet start | 向所有房间成员提议前向保密的滚动密钥(所有人都必须确认) |
/ratchet invite <u> | 在用户重新加入后重新邀请其加入 ratchet(触发完全重启——不转发链密钥) |
/proceed | 在迁移等待期间,投票剔除离线对端并继续 |
| 键 | 操作 |
|---|---|
↑ / ↓ | 向上 / 向下滚动聊天 |
PgUp / PgDn | 按页滚动聊天 |
^P(Ctrl+P) | 显示 / 隐藏侧边栏面板 |
^C | 退出 |
▶ 高亮显示。每个部分可独立滚动。按 ^P 隐藏面板以获得全宽聊天视图。
在任何消息前加上 !tag 可为其着色并触发通知声音。标签包含在加密负载内部,服务器永远看不到它们。
| 标签 | 颜色 | 用途 |
|---|---|---|
!ok <msg> | 🟢 绿色 | 成功、已确认、已完成 |
!warn <msg> | 🟡 黄色 | 警告、提醒 |
!danger <msg> | 🔴 红色 | 严重、紧急、危急 |
!info <msg> | 🔵 蓝色 | 状态更新、仅供参考 |
!req <msg> | 🟣 紫色 | 请求、需要操作 |
!? <msg> | 🩵 青色 | 问题、征求输入 |
示例:``` !danger server is going down in 5 minutes !ok deployment successful !req can someone review my PR?
声音从 `sfx/` 文件夹播放。放入以标签命名的 `.wav`、`.mp3`、`.ogg`、`.aiff`、`.flac` 或 `.m4a` 文件(例如 `sfx/danger.wav`)。如果未找到,则回退到终端响铃。使用 `/notify off` 可禁用所有声音。
---
## 架构
> 🗺️ **[实时交互式安全地图](https://ymsniper.github.io/NoEyes/)** — 以交互式图表完整展示加密架构、威胁模型和零元数据路由。```
┌──────────────────────────────────────────────────────────────────────┐
│ Alice ──────────────────────────────────────────── Bob │
│ │ Encrypted payload (opaque) │ │
│ │ │ │ │
│ └────────────► SERVER ─┴◄──────────────────────────┘ │
│ │ │
│ Zero-metadata blind forwarder: │
│ routes by opaque inbox tokens only │
│ { "to": "3f9a1c...", "type": "privmsg" } │
│ forwards encrypted bytes verbatim │
└──────────────────────────────────────────────────────────────────────┘
WHAT THE SERVER SEES: WHAT THE SERVER NEVER SEES:
· Encrypted bytes it can't read · Usernames or display names
· Opaque inbox tokens (blake2s) · Room names
· Opaque room tokens (blake2s) · Who is messaging whom
· Frame byte length · Message content
· Connection timing · File contents
· Ed25519 public keys
· DH key exchange values
每个客户端在连接前都会在本地计算两个不透明令牌:``` inbox_token = blake2s(identity_vk_bytes, digest_size=16) room_token = blake2s((room_name + group_key_hex).encode(), digest_size=16)
服务器仅通过这些令牌路由所有帧。它从不存储显示名称、房间名称或公钥。发送者身份在加密的负载**内部**传输(密封发送者),而不是在路由头部中。
### 密钥派生链```
chat.key (shared secret)
│
├─ BLAKE2b("general") ──► room_key["general"] (isolated per room)
├─ BLAKE2b("dev") ──► room_key["dev"]
└─ BLAKE2b("ops") ──► room_key["ops"]
X25519 DH (per user pair, automatic on first /msg)
alice_ephemeral + bob_ephemeral ──► shared_secret
│
BLAKE2b
│
pairwise_key (private messages)
│
BLAKE2b(transfer_id) ──► chacha20_key (files)
password + random_salt (32 bytes, os.urandom) │ └─ BLAKE2b(password, key=salt, person="identity_v2") │ derived_key ──► encrypts Ed25519 signing key at rest
每个身份文件都会获得一个唯一的随机盐,彩虹表毫无用处。
---
## 安全摘要