
보안 터미널 채팅. E2E 암호화, 제로 메타데이터 블라인드 포워더 서버. PyNaCl XSalsa20-Poly1305 + Ed25519 + 순방향 비밀성. 크로스 플랫폼 Python.
종단 간 암호화된 그룹 채팅, 개인 메시지, 파일 전송을 터미널에서 누리세요. 서버는 블라인드 포워더입니다. 서버는 메시지를 읽을 수 없고, 사용자 이름을 알 수 없으며, 참여 중인 룸도 알 수 없습니다. 설령 서버가 완전히 침해당하더라도 두 메시지를 같은 사람에게 연결할 수 없습니다.
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 터미널 채팅 도구입니다. 서버는 어떤 것도 복호화하지 않으며 사용자가 누구인지 절대 보지 못합니다 - 단지 불투명한 토큰을 처리하고 암호화된 바이트를 전달할 뿐입니다.
사용자가 키를 생성해 대역 외로 공유하면, 서버는 대화 내용에 대해 아무것도 알 수 없습니다.
제3자 서버를 신뢰하지 않고 암호화 통신을 원하는 소규모 신뢰 집단, 진정한 종단 간 암호화가 적용된 비공개 채팅을 자체 호스팅하려는 사람, 또는 서버가 정확히 무엇을 보고 무엇을 볼 수 없는지 이해하려는 모든 사람에게 유용합니다.
| 기능 | 설명 |
|---|---|
| 제로 메타데이터 서버 | 서버는 사용자 이름, 룸 이름, 공개 키를 절대 볼 수 없고 오직 불투명한 토큰만 봅니다 |
| 봉인된 발신자 | 발신자 신원은 라우팅 헤더가 아닌 암호화된 페이로드 안에만 존재합니다 |
| 블라인드 포워더 서버 | 복호화 없음, 서버는 읽을 수 없는 암호화 블롭을 전달만 합니다 |
| 순방향 비밀성 | /ratchet start — Sender Keys 프로토콜, 각 메시지는 고유한 파생 키로 암호화되며 현재 키가 유출되어도 과거 메시지는 안전합니다 |
| 그룹 채팅 | 룸별 XSalsa20-Poly1305 키를 BLAKE2b로 파생, 룸은 암호학적으로 격리됩니다 |
| 개인 메시지 | 첫 접촉 시 X25519 DH 핸드셰이크, 두 당사자만 보유한 일대일 키 |
| 파일 전송 | ChaCha20-Poly1305 스트리밍, 크기 제한 없음, 낮은 RAM 사용량, 재연결 시 일시 중지/재개 |
| Ed25519 신원 | 자동 생성된 서명 키, 모든 메시지와 파일에 서명됩니다 |
| TOFU | 처음 본 키를 신뢰하고, 키 불일치 시 눈에 띄는 보안 경고가 표시됩니다 |
| 무작위 PBKDF2 솔트 | 배포마다 고유한 무작위 솔트가 생성되어 레인보우 테이블이 무용지물이 됩니다 |
| TLS + 인증서 고정 | 전송 계층이 암호화되고, TOFU로 첫 접촉 시 서버 인증서가 고정됩니다 |
| 재생 방지 | 룸별 메시지 ID 덱, 재생된 프레임은 조용히 폐기됩니다 |
| 분할 사이드바 패널 | 룸(위)과 사용자(아래)가 항상 표시되고 각 절반은 독립적으로 스크롤됩니다 |
| CRT 부팅 애니메이션 | 시작 시 사운드가 포함된 전체 화면 인광 효과 |
| 래칫 활성화 애니메이션 | 점자 기어 아트, 글리치 깜빡임, 스포트라이트 스윕, 동기화된 효과음, TUI 크롬이 빨간색으로 전환되는 전체 화면 CRT 효과 |
| 안내형 런처 | 화살표 키 메뉴 UI, 명령줄 경험 불필요 |
| 자동 의존성 설치 프로그램 | 플랫폼을 감지해 누락된 항목을 설치하며, 변경 전에 먼저 묻습니다 |
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를 전달하기만 하면 됩니다.
Termux를 F-Droid에서 다운로드하세요(권장): https://f-droid.org/packages/com.termux/
세션을 유지하세요 - 앱을 전환할 때 NoEyes가 계속 실행되도록 tmux를 설치하세요:```bash pkg install tmux -y tmux python ui/launch.py
**저장 권한** - 이것이 없으면 파일 전송이 실패합니다:```bash
termux-setup-storage
| 명령 | 설명 |
|---|---|
/help | 모든 명령 표시 |
/quit | 연결 끊고 종료 |
/clear | 화면에서 메시지 지우기 |
/users | 현재 방의 사용자 나열 |
/join <room> | 다른 방으로 전환 (활성 래칫 중이면 경고) |
/leave | 일반 방으로 돌아가기 (활성 래칫 중이면 경고) |
/msg <user> <text> | E2E 암호화된 비공개 메시지 보내기 |
/send <user> <file> | 암호화된 파일 보내기 |
/whoami | 자신의 신원 지문 표시 |
/trust <user> | 사용자가 재설치한 후 새 키 신뢰 |
/notify on|off | 알림 소리 켜기/끄기 전환 |
/ratchet start | 모든 방 구성원에게 순방향 비밀성 롤링 키 제안 (모두가 확인해야 함) |
/ratchet invite <u> | 사용자가 다시 합류한 후 래칫에 다시 초대 (전체 재시작 유발 — 체인 키 전달 없음) |
/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)
서버는 이러한 토큰만으로 모든 프레임을 라우팅합니다. 디스플레이 이름, 방 이름 또는 공개 키를 저장하지 않습니다. 발신자 신원은 라우팅 헤더가 아닌 암호화된 페이로드(봉인된 발신자) **내부**로 전달됩니다.