Skip to content

ERITEK/fantik-core

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

      ·bg·bz·bg·                          ·bg·bx·bg·
    ·bg·bq·bg·bg·bg·bg·  ╔══════════════╗  ·bg·bg·bg·bg·bg·bg·
    ~~~~~~~~~~~~~~~~~~~~~~║              ║~~~~~~~~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~~~~~~~~║    FANTIK    ║~~~~~~~~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~~~~~~~~║              ║~~~~~~~~~~~~~~~~~~~~~~
    ·bg·bg·bg·bg·bg·bg·  ╚══════════════╝  ·bg·bg·bg·bg·bq·bg·
        ·bg·bx·bg·                          ·bg·bz·bg·

Fantik Core

Криптографическое ядро для обфускации (TCP с оговорками) и UDP трафика. Оборачивает пакеты любого протокола (WireGuard, OpenVPN, AmneziaWG и др.) в AEAD-шифрованную оболочку. На проводе результат выглядит как поток случайных байт - нет фиксированных заголовков, magic bytes или предсказуемых длин. Это не делает трафик невидимым, но убирает все очевидные сигнатуры протокола.

Что существует в этой нише

  • Shadowsocks - похожая идея (AEAD обёртка), но его распазнаёт DPI. Static mask для length prefix - слабое место, которое Fantik решает через per-frame HMAC
  • obfs4 (Tor) - Elligator2 handshake, но нет encryption длины кадра
  • Cloak - TLS mimicry --> тяжелее и сложнее
  • Hysteria/TUIC - UDP-over-TCP, но на QUIC --> тяжёлые зависимости
  • XRay/VLESS - полноценный прокси-протокол, не библиотека

Что делает fantik-core полезным

  • Это библиотека, а не приложение. Если хочешь свой протокол обернуть в обфускацию fantik-core - это go get и 10 строк кода.
  • TCP framing с HMAC-masked длиной.
  • Минимализм. Одна зависимость (golang.org/x/crypto), компилируется под MIPS/ARM. Можно засунуть на роутер с 64MB RAM.
  • Transport-agnostic ядро. obfs/ работает с []byte. Хочешь UDP - НА, хочешь TCP - НА, хочешь через WebSocket пропустить - kudasai.

DPI

DPI (Deep Packet Inspection) умеет распознавать VPN-протоколы по характерным заголовкам и блокировать их. Fantik скрывает факт использования VPN:

  • Все пакеты выглядят как случайные байты (энтропия ~8 бит/байт)
  • Нет magic bytes, фиксированных заголовков, fingerprint-ов
  • Размеры пакетов рандомизируются "cover bytes"
  • Два транспорта: UDP (нативный) и TCP (для сетей блокирующих UDP)

Fantik не заменяет криптографию upstream-протокола. Он скрывает его наличие.

Кому подходит

  • Энтузиасты, которые хотят завернуть VPN в обфускацию
  • Разработчики VPN решений
  • Администраторы VPS и роутеров с OpenWrt
  • Все кому нужен лёгкий обфускатор без тяжёлых зависимостей

Требования

  • Go 1.22+
  • Зависимость -- golang.org/x/crypto
  • Работает на Linux, macOS, Windows, OpenWrt (arm/arm64/mips)
  • Минимальные ресурсы: 1 CPUcore (1 vCPU), 128 MB RAM

Установка

go get github.com/ERITEK/fantik-core@latest

Архитектура

┌─────────────────────────────────┐
│      Upstream (WG, OVPN, ...)   │  UDP-пакеты от приложения
├─────────────────────────────────┤
│       obfs  (AEAD ядро)         │  Wrap/Unwrap, cover, replay
│       transport-agnostic        │  работает с []byte
├──────────┬──────────────────────┤
│   UDP    │   TCP                │  транспорт: доставка blob-ов
│          │   (frame/)           │  UDP: 1 датаграм = 1 blob
│          │                      │  TCP: [EncLen][blob] поток
└──────────┴──────────────────────┘

Ядро (obfs/) не знает про сеть и работает с []byte. Транспорт (proxy/, frame/) определяет как blob доставляется между клиентом и сервером.

Пакеты

Пакет Назначение
obfs/ AEAD обёртка (Wrap/Unwrap), HKDF вывод ключей, ReplayWindow
frame/ TCP framing -- маскировка длин через HMAC-PRF
session/ Управление сессиями: SessionMap, state machine, seq counters
proxy/ UDP server + client с keepalive и reconnect

Формат пакета

На проводе (blob)

[Nonce 12B][AEAD ciphertext + Tag 16B]

Nonce = 12 случайных байт. Ciphertext = зашифрованный inner packet + 16 байт auth tag. Для наблюдателя весь blob это случайные байты.

Внутри AEAD (inner packet)

[Version 1B][SessionID 8B][Seq 8B][Flags 1B][PayloadLen 2B][Payload 0..N][Cover 0..M]
  • Version - версия wire format (0x01)
  • SessionID - идентификатор сессии (crypto/rand, генерируется клиентом)
  • Seq - порядковый номер (с 1, per-direction)
  • Flags - тип пакета (Data, Keepalive, KeepaliveAck, Close)
  • PayloadLen - длина payload (big-endian)
  • Payload - upstream пакет
  • Cover - случайные байты для рандомизации размера

TCP framing

TCP frame: [EncLen 2B][blob]
EncLen = uint16_be(blob_len) XOR HMAC-SHA256(frame_key, direction || counter)[0:2]

На проводе TCP stream это сплошной поток случайных байт. Длина каждого кадра маскируется уникальным HMAC на основе монотонного счётчика.

Криптография

  • AEAD: ChaCha20-Poly1305 (IETF, RFC 8439)
  • Key derivation: HKDF-SHA256 из общего PSK
  • Три ключа: client→server, server→client, frame (TCP)
  • Nonce: 12 случайных байт per packet (crypto/rand)
  • Replay protection: sliding window 2048 позиций
prk       = HKDF-Extract(salt="fantik-core", ikm=PSK)
client_key = HKDF-Expand(prk, info="fantik-core-c2s", len=32)
server_key = HKDF-Expand(prk, info="fantik-core-s2c", len=32)
frame_key  = HKDF-Expand(prk, info="fantik-core-frame", len=32)

Использование

Пример: шифрование/расшифровка

package main

import (
    "crypto/rand"
    "fmt"
    "log"

    "github.com/ERITEK/fantik-core/obfs"
)

func main() {
    // 1. Генерируем PSK (общий секрет, >= 16 байт)
    psk := make([]byte, 32)
    rand.Read(psk)

    // 2. Создаём пару ключей
    kp, err := obfs.NewKeyPair(obfs.Config{
        PSK:   psk,
        Cover: obfs.CoverConfig{CoverMin: 4, CoverMax: 64},
    })
    if err != nil {
        log.Fatal(err)
    }

    // 3. Клиент шифрует пакет
    var sessionID uint64 = 0xDEADBEEF
    var seq uint64 = 1
    payload := []byte("hello world")

    blob, err := kp.WrapC2S(sessionID, seq, obfs.FlagData, payload, 0)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Blob: %d байт (payload %d + overhead)\n", len(blob), len(payload))

    // 4. Сервер расшифровывает
    pkt, err := kp.UnwrapC2S(blob)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("SessionID: %x, Seq: %d, Payload: %s\n",
        pkt.SessionID, pkt.Seq, pkt.Payload)
}

UDP proxy (полная цепочка)

package main

import (
    "crypto/rand"
    "log"
    "os"
    "os/signal"
    "time"

    "github.com/ERITEK/fantik-core/obfs"
    "github.com/ERITEK/fantik-core/proxy"
)

func main() {
    psk := make([]byte, 32)
    rand.Read(psk)

    kp, _ := obfs.NewKeyPair(obfs.Config{
        PSK:   psk,
        Cover: obfs.CoverConfig{CoverMin: 4, CoverMax: 64},
    })

    // Сервер (на VPS)
    srv := proxy.NewUDPServer(proxy.UDPServerOpts{
        ListenAddr:   "0.0.0.0:443",
        UpstreamAddr: "127.0.0.1:51820", // WireGuard
        Keys:         kp,
    })
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
    defer srv.Stop()

    // Клиент (на локальной машине / роутере)
    client := proxy.NewUDPClient(proxy.UDPClientOpts{
        ListenAddr:        "127.0.0.1:1618",
        ServerAddr:        "your-vps:443",
        Keys:              kp,
        KeepaliveInterval: 15 * time.Second,
    })
    if err := client.Start(); err != nil {
        log.Fatal(err)
    }
    defer client.Stop()

    // Ждём Ctrl+C
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt)
    <-sig
}

TCP framing

package main

import (
    "bufio"
    "crypto/rand"
    "log"
    "net"

    "github.com/ERITEK/fantik-core/frame"
    "github.com/ERITEK/fantik-core/obfs"
)

func main() {
    psk := make([]byte, 32)
    rand.Read(psk)

    kp, _ := obfs.NewKeyPair(obfs.Config{
        PSK:   psk,
        Cover: obfs.CoverConfig{CoverMin: 4, CoverMax: 64},
    })

    // Получаем frame_key из KeyPair
    frameKey := kp.FrameKey()

    // --- Клиент: отправка ---
    conn, _ := net.Dial("tcp", "your-vps:443")
    clientFramer, _ := frame.NewFramer(frameKey)

    // Шифруем payload -> blob
    blob, _ := kp.WrapC2S(0x1234, 1, obfs.FlagData, []byte("hello"), 0)

    // Кодируем blob в TCP кадр и отправляем
    tcpFrame := clientFramer.Encode(blob, frame.DirC2S)
    conn.Write(tcpFrame)

    // --- Сервер: приём ---
    serverFramer, _ := frame.NewFramer(frameKey)
    reader := bufio.NewReaderSize(conn, 8192)

    // Читаем TCP кадр -> blob
    blob, err := serverFramer.ReadFrame(reader, frame.DirC2S, frame.DefaultMaxBlobSize)
    if err != nil {
        log.Fatal(err) // desync -> disconnect
    }

    // Расшифровываем blob -> payload
    pkt, _ := kp.UnwrapC2S(blob)
    log.Printf("Получено: %s", pkt.Payload)
}

Схемы работы

UDP transport (рекомендуется)

Upstream app --UDP--> Fantik client ==UDP==> Fantik server --UDP--> Upstream server
                       (WrapC2S)              (UnwrapC2S)
                       (UnwrapS2C)            (WrapS2C)

TCP transport (для сетей блокирующих UDP)

Upstream app --UDP--> Fantik client ==TCP==> Fantik server --UDP--> Upstream server
                       (WrapC2S)              (ReadFrame)
                       (Encode)               (UnwrapC2S)

На роутере с OpenWrt

Устройство --UDP--> OpenWrt (Fantik client) ==UDP/TCP==> VPS (Fantik server) --UDP--> WG server

Fantik Core компилируется под MIPS/ARM (OpenWrt), потребляет минимум ресурсов.

# Кросс-компиляция для OpenWrt (пример для MT7621, mips)
GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -o fantik-client ./cmd/client/

Что Fantik Core НЕ делает

  • Нет forward secrecy (PFS) - это PSK-обфускатор, не замена TLS. Если PSK утёк - весь трафик скомпрометирован. Upstream протокол (WireGuard и т.д.) обеспечивает PFS самостоятельно
  • Нет handshake - клиент и сервер не договариваются о параметрах, всё из PSK
  • Нет key rotation - ротация ключей ответственность прикладного кода (ротация SessionID встроена, ротация PSK - нет)
  • Нет защиты от traffic analysis - тайминг, burst patterns, объём трафика, корреляция по времени - всё это видно наблюдателю. Fantik скрывает содержимое и протокол, но не факт передачи данных
  • Rate limiting и flood protection не встроены - это ответственность прикладного кода. В UDPServer есть OnNewSession callback для этого

Key usage limits

При random 96-bit nonce birthday bound наступает при ~2^48 пакетов на одном ключе. Встроенный лимит session (2^30 пакетов, ~1 млрд) даёт запас в 18 порядков. Вероятность коллизии nonce при 2^30 пакетов - примерно 2^-36 (один шанс на ~70 миллиардов).

Рекомендации:

  • Ротация SessionID происходит автоматически при достижении MaxSessionPackets
  • PSK менять вручную при подозрении на компрометацию
  • Для параноиков: XChaCha20-Poly1305 (192-bit nonce) убирает вопрос коллизий полностью - миграция запланирована

Overhead

Компонент UDP TCP
EncLen - 2
Nonce 12 12
Inner header 20 20
AEAD tag 16 16
Cover (avg) ~34 ~34
Per packet ~82 ~84

При payload 1280 байт - overhead ~6%.

Производительность

Операция Payload Время Аллокации
WrapC2S 148 B ~31 µs 5, 650 B
UnwrapC2S 148 B ~0.7 µs 3, 404 B
WrapC2S 1200 B ~33 µs 5, 3.9 KB
UnwrapC2S 1200 B ~4 µs 3, 2.6 KB
Frame Encode 200 B ~1.2 µs 8, 736 B
Frame ReadFrame 200 B ~1.3 µs 9, 736 B

Bottleneck Wrap = crypto/rand (генерация nonce + cover).

Тесты

go test ./... -v

53 теста + 6 бенчмарков, покрытие:

  • obfs/: roundtrip, direction isolation, tamper, wrong key, MTU clamping, cover variance, replay window
  • frame/: roundtrip, counter sync 100 кадров, direction isolation, bidirectional, partial reads, desync, randomness EncLen
  • session/: state machine, seq counters, replay, cleanup, callbacks
  • proxy/: integration roundtrip, burst 50 пакетов, garbage resistance, state strings

Безопасность

  • PSK более 16 байт (рекомендуется 32 или 64)
  • Random nonce per packet (crypto/rand) == нет nonce reuse
  • Раздельные ключи per direction == нет key reuse
  • Replay window 2048 == защита от replay-атак
  • TCP EncLen маскируется per-frame HMAC (не static mask) == устойчивее Shadowsocks
  • При любой ошибке TCP framing == disconnect (без попыток ресинхронизации)

Известные ограничения

  • Статический PSK --> при компрометации ==> менять вручную
  • TCP: head-of-line blocking (неизбежно для UDP-over-TCP)
  • TCP: одно соединение = single point of failure (решение: reconnect, fallback, transport = auto)
  • Высокая энтропия без TLS handshake --> DPI может флагировать (решается mimicry)
  • Flow fingerprinting (тайминг, burst) --> не решается без traffic shaping

Модель угроз

От чего защищает

  • Сигнатурный DPI, который ищет заголовки VPN протоколов (WireGuard handshake initiation, OpenVPN opcode и т.д.)
  • Блокировка по фиксированным magic bytes
  • Анализ длин пакетов (cover bytes рандомизируют размеры)
  • Replay-атаки (sliding window 2048)
  • TCP: блокировка по length prefix pattern (EncLen маскирован HMAC, не static mask как в Shadowsocks AEAD)

От чего НЕ защищает

  • ML-DPI и statistical analysis (поведение трафика, burst patterns, timing correlation)
  • Блокировка по политике "всё что не TLS/HTTP - дропать" (нужен mimicry, TLS-like handshake)
  • Компрометация PSK (нет PFS, нет key exchange)
  • Traffic volume correlation ("VPN включён = трафик на VPS скакнул")
  • Active probing (сервер отвечает на мусор тишиной, но DPI может это использовать)
  • Censorship-resistant discovery (как найти сервер если IP заблокирован)

Лицензия

MIT License

Авторы

  • ERITEK : идия, архитектура, код.
  • Loo1 : код, тестирование, комментарии.
  • кофе, много кофе. «Черней чернейшей черноты бесконечности!»

P/S

Это нулевое ядро т.к. проект рождался не для общего пользования, а кор уже отревизирован то выкладываю с чистой совестью. Многие обвязки и функции вам придётся обдумать самостоятельно. 'понять и простить' Возможно позже или сильно позже сварганим открытый exe.мпляр (но это будет что то простое) для примера.

ППС для вайбкодеров

Код написан в лучших традициях яндекс практикум, везде есть комментарий и разберётся даже папич. Если прогуливали уроки информатики, то большая просьба загуглить/перплексить все вводные ибо нейронки очень умные и могут творить вещи понятные только им.

About

Go library for UDP traffic obfuscation. AEAD encryption, TCP framing, replay protection. Designed for VPS, OpenWrt routers, and censorship bypass.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages