MTProto crypto core
opentele2-core carries the cryptography an MTProto client needs, in Rust, and hands it to each
client library. The same primitives read and write tdata, so one implementation serves both.
What runs on the core
- AES-256-IGE, the MTProto message cipher, and AES-256-CTR, the cipher of the obfuscated, CDN and MTProxy transports.
- SHA-1, SHA-256, HMAC-SHA256 and PBKDF2 (SHA-1, SHA-256 or SHA-512).
- PQ factorisation of the auth key exchange.
- gzip in pure Rust, and the operating system’s random source, through one entry point.
- The 2FA SRP check (
passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow) of the QR login flows.
AES-IGE comes from grammers-crypto; the core checks every buffer length first and returns a
bad-length error instead of panicking.
Measured on one machine: AES-CTR on 1 MiB takes 0.9 ms on the core against 3.6 s with pyaes, and a one-account tdata folder loads in 0.24 ms against 1.08 ms for a pure-Python loader (best of 3 × 200 loads).
Per client
| Client | On the core | Stays with the client |
|---|---|---|
| Telethon (Python) | AES-IGE through cryptg; AES-CTR, PQ factorisation, SHA-1, SHA-256 and password and MTProxy hashing through the bridge | DH and RSA arithmetic (Python pow), os.urandom |
| mtcute (Node) | every ICryptoProvider member, from createCryptoProvider() | DH and RSA bigint maths |
| grammers (Rust) | the core’s own crypto; grammers runs grammers-crypto, which the core also uses for AES-IGE and 2FA SRP | — |
import opentele2 # before telethon: registers the core's cryptg
from telethon import TelegramClientTwo mechanisms, both set up by import opentele2:
cryptg. Telethon looks for acryptgmodule the first timetelethon.crypto.aesis imported. When no realcryptgis installed, opentele2 registersopentele2._native.cryptgunder that name, so Telethon’s AES-IGE runs on the core. An installedcryptgis left in charge. This is whyopentele2must be imported beforetelethon.- The bridge. opentele2 rebinds the names Telethon looks up at call time:
AESModeCTRin the crypto, CDN, obfuscated and MTProxy modules,Factorization.factorize, thesha1andsha256imports, and thehashlibused for password and MTProxy hashing. It imports those modules itself and patches them in place, so this part works whether Telethon was imported before or after opentele2. A module or name that is missing is skipped.
The primitives are also callable directly from opentele2._native (aes_ige_encrypt,
AesCtr, sha256, pbkdf2, factorize_pq, random_bytes and the others).
import { TelegramClient } from '@mtcute/node'
import { profile } from 'opentele2'
import { createCryptoProvider, initConnectionOptions } from 'opentele2/mtcute'
const desktop = profile('TelegramDesktop')
const client = new TelegramClient({
apiId: desktop.apiId,
apiHash: desktop.apiHash,
initConnectionOptions: initConnectionOptions(desktop),
crypto: await createCryptoProvider(),
})createCryptoProvider() implements every member of mtcute’s ICryptoProvider on the core:
hashes, HMAC, PBKDF2, AES-256-CTR and -IGE, PQ factorisation, gzip and the OS random source. It
needs neither Node’s crypto module nor @mtcute/node, and its initialize has nothing to load.
Its gzip writes a gzip stream, where mtcute’s own Node provider writes zlib.
The same primitives are exported from the root entry point: aesIgeEncrypt, AesCtr, sha1,
sha256, hmacSha256, pbkdf2, factorizePq, gzip, gunzip, randomBytes, randomFill.
use opentele2_core::crypto;
fn main() -> opentele2_core::Result<()> {
let mut nonce = [0u8; 16];
crypto::random_fill(&mut nonce)?; // the one entry point to the OS random source
Ok(())
}grammers already runs on Rust crypto: it uses grammers-crypto, which the core uses too, so
nothing needs bridging. The crypto and compress modules of opentele2-core expose the same
primitives the bindings use, for your own code.
What stays in the client libraries
- Telethon keeps its Diffie-Hellman and RSA arithmetic, done with Python’s
pow, and itsos.urandomcalls. - mtcute keeps its DH and RSA bigint maths.
- The protocol itself (sockets, transport framing, the update loop) is the client library’s. The core opens no sockets and speaks no transport: it is pure, synchronous and without network access.
Randomness that must not repeat, msg_id offsets and the delays of the login flows included, comes
from the operating system’s random source; seeding Python’s random does not reproduce it.