@@ -23,6 +23,8 @@ Features
2323 See ` allowed_protocols ` option.
2424* Connection limit policies - limit number of connections by IP / tls-domain / port; IP / tls-domain
2525 blacklists / whitelists
26+ * Per-SNI derived secrets for personal proxies - each user gets a unique token tied to their
27+ fake-TLS domain; base secret cannot be extracted from user links
2628* Multiple ports with unique secret and promo tag for each port
2729* Very high performance - can handle tens of thousands connections! Scales to all CPU cores.
2830 1Gbps, 90k connections on 4-core/8Gb RAM cloud server.
@@ -621,6 +623,97 @@ And then use https://seriyps.com/mtpgen.html to generate unique link for them.
621623Be aware that domains table will be reset if proxy is restarted! Make sure you re-add them
622624when proxy restarts (eg, via [systemd hook script](https://unix.stackexchange.com/q/326181/70382)).
623625
626+ #### Strengthening personal proxies with per-SNI derived secrets
627+
628+ With the classic scheme above, the SNI domain in a user' s link is the only thing that
629+ distinguishes them — the underlying 16 - byte secret is the same for everyone and is
630+ embedded verbatim in every link , so any user who inspects their link gets the raw secret .
631+ The only protection against credential sharing is the connection - count policy and the hope
632+ that the user 's SNI domain is long and obscure enough that no one else guesses it.
633+
634+ There is a tension here: for best DPI resistance, fake-TLS SNI domains should look like
635+ real websites — short, readable names like `news.example.com` — but short readable names
636+ are exactly the ones that are easy to guess or share.
637+
638+ **Per-SNI derived secrets resolve this tension.** Instead of embedding the base secret,
639+ each user' s link carries a token derived specifically for their SNI domain :
640+
641+ ```
642+ derived = SHA256 (salt || hex (base_secret ) || sni_domain )[0 :16 ]
643+ link = ee | derived (16 bytes ) | sni_domain
644+ ```
645+
646+ A user who inspects their link sees only their own derived token . They cannot recover the
647+ base secret , cannot compute tokens for other domains , and cannot construct a valid
648+ connection using a different SNI . Combined with the domain whitelist , revoking a user
649+ (removing their domain from the whitelist ) is now cryptographically meaningful : their
650+ derived token is unique to their domain and is worthless elsewhere .
651+
652+ ** Enable in `sys .config `:**
653+
654+ ```erlang
655+ {per_sni_secrets , on },
656+ {per_sni_secret_salt , <<" my-private-salt-change-me" >>},
657+ ```
658+
659+ > ⚠️ ** Switching to `on ` invalidates all existing fake - TLS user links .** Re - issue every
660+ > user 's link after the change.
661+
662+ The salt is the sole true secret — an attacker who knows the base secret but not the salt
663+ cannot compute any derived secrets.
664+
665+ *(HMAC-SHA256 would be cryptographically more rigorous here, but SHA-256 is secure enough
666+ for this use case and lets every language express the derivation as a
667+ single hash call — see the one-liners below.)*
668+
669+ ##### Generating user links
670+
671+ Given your `SALT`, `SECRET` (32 lowercase hex chars from your config), and `SNI` (the
672+ domain in the user' s link ):
673+
674+ ** Bash (Linux — `sha256sum ` from coreutils ):**
675+ ```bash
676+ SALT = " my-private-salt-change-me"
677+ SECRET = " d0d6e111bada5511fcce9584deadbeef"
678+ SNI = " alice.example.com"
679+
680+ DERIVED = $( printf '%s%s%s' " $SALT" " $SECRET" " $SNI" | sha256sum | cut - c1 - 32 )
681+ SNI_HEX = $( printf '%s' " $SNI" | od - A n - t x1 | tr - d ' \n ' )
682+ echo " ee${DERIVED}${SNI_HEX}"
683+ ```
684+
685+ ** Bash (macOS — replace `sha256sum ` with `shasum - a 256 `):**
686+ ```bash
687+ DERIVED = $( printf '%s%s%s' " $SALT" " $SECRET" " $SNI" | shasum - a 256 | cut - c1 - 32 )
688+ ```
689+
690+ ** Python :**
691+ ```python
692+ import hashlib
693+ salt , secret , sni = " my-private-salt-change-me" , " d0d6e111bada5511fcce9584deadbeef" , " alice.example.com"
694+ derived = hashlib .sha256 ((salt + secret + sni ).encode ()).hexdigest ()[:32 ]
695+ print (f " ee{derived}{sni.encode().hex()}" )
696+ ```
697+
698+ ** Node .js :**
699+ ```javascript
700+ const crypto = require ('crypto' );
701+ const [salt , secret , sni ] = ['my-private-salt-change-me' , 'd0d6e111bada5511fcce9584deadbeef' , 'alice.example.com' ];
702+ const derived = crypto .createHash ('sha256' ).update (salt + secret + sni ).digest ('hex' ).slice (0 , 32 );
703+ console .log ('ee' + derived + Buffer .from (sni ).toString ('hex' ));
704+ ```
705+
706+ ** Browser JavaScript (no dependencies ):**
707+ ```javascript
708+ async function makeLink (salt , secret , sni ) {
709+ const data = new TextEncoder ().encode (salt + secret + sni );
710+ const hash = await crypto .subtle .digest ('SHA-256' , data );
711+ const hex = b => Array .from (b ).map (x => x .toString (16 ).padStart (2 , '0' )).join ('' );
712+ return 'ee' + hex (new Uint8Array (hash ).slice (0 , 16 )) + hex (new TextEncoder ().encode (sni ));
713+ }
714+ // makeLink ('my-private-salt-change-me' , 'd0d6...' , 'alice.example.com' ).then (console .log )
715+ ```
716+
624717### IPv6
625718
626719Currently proxy only supports client connections via IPv6 , but can only connect to Telegram servers
@@ -653,6 +746,8 @@ different `listen_ip` (one v4 and one v6):
653746< ...>
654747```
655748
749+ Keep in mind that `listen_ip => " ::" ` would listen both on IPv6 and IPv4 (!! ) addresses (in IPv6 to v4 compat mode ). If you need separate listeners , specify full IPv6 address explicitly .
750+
656751### Tune resource consumption
657752
658753If your server have low amount of RAM , try to set
0 commit comments