Telegram's MTProto : RSA encryption

Hello,

I’m trying a few things with Telegram’s MTProto.
Here is some doc related to the problem : https://core.telegram.org/mtproto/auth_key
With example : https://core.telegram.org/mtproto/samples-auth_key

What it’s supposed to do (making an authorization key) :

client -> server : req_pq#60469778
server -> client : resPQ#05162463
client -> server : req_DH_params#d712e4be # contains data encrypted with RSA
server -> client : server_DH_params_ok#d0e8075c # contains data enctrypted by AES256 IGE
... # more things, but we don't care

In req_DH_params#d712e4be, the data is encrypted (RSA) by the client following :

data_with_hash := SHA1(data) + data + (any random bytes); such that the length equal 255 bytes;encrypted_data := RSA (data_with_hash, server_public_key); a
255-byte long number (big endian) is raised to the requisite power over 
the requisite modulus, and the result is stored as a 256-byte number.

I know SHA1(data) for some data. I checked and it’s fine.
I have an other test checking if I’m able to properly decrypt in server_DH_params_ok#d0e8075c : It works.

What happens :

Half of the time, the server returns an error (useless error, won’t help _ -404) instead of server_DH_params_ok#d0e8075c. For the other half, the message is accepted, server_DH_params_ok#d0e8075c is returned but contains wrong values.

I suspect :
There may be something wrong while encrypting encrypted_data with RSA in req_DH_params#d712e4be. I can’t check with the example since they padded with “random bytes”. Otherwise there’s something wrong about (de)serialization (they’re messing with both big and little endian and the documentation in not straightforward…).

The code (encrypting data in req_DH_params#d712e4be only) :

 data = :crypto.hash(:sha, data) <> data
 padding = 255 - byte_size(data)
 data_with_hash = data <> <<0::size(padding)-unit(8)>>
 # encrypted_data := RSA (data_with_hash, server_public_key); a 255-byte long number (big endian)
 # is raised to the requisite power over the requisite modulus, and the result is stored as a
 # 256-byte number.
 {e, n} = Crypto.get_key # get RSA public key components
 encrypted_data = :crypto.mod_pow data_with_hash, e, n # data_with_hash^e % n

Do your see anything weird ?

Thank you !

This works for the RSA_ENC portion of MTProto:

The fields of v follow directly from the Auth-Key creation step: Server -> Client: ResPQ, and have their usual meanings:

  defp _proc(%TL.ResPQ{} = v, s) do
    <<pq::8*8>> = v.pq
    {p, q} = TL.Crypto.factorize(pq)
    new_nonce = :crypto.strong_rand_bytes(32)
    pq_bytes = TL.encode(%TL.P_Q_Inner_Data{pq: v.pq, p: <<p::4*8>>, q: <<q::4*8>>, nonce: v.nonce, server_nonce: v.server_nonce, new_nonce: new_nonce})
    data_hash = <<TL.Crypto.sha1(pq_bytes)::binary, pq_bytes::binary>>
    data_hash =
      case 255 - byte_size(data_hash) do
        0 -> data_hash
        pad -> <<data_hash::binary, :crypto.strong_rand_bytes(pad)::binary>>
      end

    f = Enum.at(v.server_public_key_fingerprints, 0)
    zb = TL.Crypto.rsa_enc(data_hash, f)

    s =  %{s | new_nonce: new_nonce}

    send_plain(TL.req_dh_params(v.nonce, v.server_nonce, <<p::4*8>>, <<q::4*8>>, f, zb), s)
  end

I also use these in my TL.Crypto Module.

Notice Telegram uses a finite set of server_public_key_fingerprints, hence the following direct lookups work fine

  def rsa_enc(bin, 0xC3B42B026CE86B21) do
    :crypto.mod_pow(bin, 0x010001, 0x0c150023e2f70db7985ded064759cfecf0af328e69a41daf4d6f01b538135a6f91f8f8b2a0ec9ba9720ce352efcf6c5680ffc424bd634864902de0b4bd6d49f4e580230e3ae97d95c8b19442b3c0a10d8f5633fecedd6926a7f6dab0ddb7d457f9ea81b8465fcd6fffeed114011df91c059caedaf97625f6c96ecc74725556934ef781d866b34f011fce4d835a090196e9a5f0e4449af7eb697ddb9076494ca5f81104a305b6dd27665722c46b60e5df680fb16b210607ef217652e60236c255f6a28315f4083a96791d7214bf64c1df4fd0db1944fb26a2a57031b32eee64ad15a8ba68885cde74a5bfc920f6abf59ba5c75506373e7130f9042da922179251f)
  end
  def rsa_enc(bin, 0x9A996A1DB11C729B) do
    :crypto.mod_pow(bin, 0x010001, 0x0c6aeda78b02a251db4b6441031f467fa871faed32526c436524b1fb3b5dca28efb8c089dd1b46d92c895993d87108254951c5f001a0f055f3063dcd14d431a300eb9e29517e359a1c9537e5e87ab1b116faecf5d17546ebc21db234d9d336a693efcb2b6fbcca1e7d1a0be414dca408a11609b9c4269a920b09fed1f9a1597be02761430f09e4bc48fcafbe289054c99dba51b6b5eb7d9c3a2ab4e490545b4676bd620e93804bcac93bf94f73f92c729ca899477ff17625ef14a934d51dc11d5f8650a3364586b3a52fcff2fedec8a8406cac4e751705a472e55707e3c8cd5594342b119c6c3293532d85dbe9271ed54a2fd18b4dc79c04a30951107d5639397)
  end
  def rsa_enc(bin, 0xB05B2A6F70CDEA78) do
    :crypto.mod_pow(bin, 0x010001, 0x0b1066749655935f0a5936f517034c943bea7f3365a8931ae52c8bcb14856f004b83d26cf2839be0f22607470d67481771c1ce5ec31de16b20bbaa4ecd2f7d2ecf6b6356f27501c226984263edc046b89fb6d3981546b01d7bd34fedcfcc1058e2d494bda732ff813e50e1c6ae249890b225f82b22b1e55fcb063dc3c0e18e91c28d0c4aa627dec8353eee6038a95a4fd1ca984eb09f94aeb7a2220635a8ceb450ea7e61d915cdb4eecedaa083aa3801daf071855ec1fb38516cb6c2996d2d60c0ecbcfa57e4cf1fb0ed39b2f37e94ab4202ecf595e167b3ca62669a6da520859fb6d6c6203dfdfc79c75ec3ee97da8774b2da903e3435f2cd294670a75a526c1)
  end
  def rsa_enc(bin, 0x71E025B6C76033E3) do
    :crypto.mod_pow(bin, 0x010001, 0x0c2a8c55b4a62e2b78a19b91cf692bcdc4ba7c23fe4d06f194e2a0c30f6d9996f7d1a2bcc89bc1ac4333d44359a6c433252d1a8402d9970378b5912b75bc8cc3fa76710a025bcb9032df0b87d7607cc53b928712a174ea2a80a8176623588119d42ffce40205c6d72160860d8d80b22a8b8651907cf388effbef29cd7cf2b4eb8a872052da1351cfe7fec214ce48304ea472bd66329d60115b3420d08f6894b0410b6ab9450249967617670c932f7cbdb5d6fbcce1e492c595f483109999b2661fcdeec31b196429b7834c7211a93c6789d9ee601c18c39e521fda9d7264e61e518add6f0712d2d5228204b851e13c4f322e5c5431c3b7f31089668486aadc59f)
  end

  def ige_dec(key, iv, bytes), do: :crypto.block_decrypt(:aes_ige256, key, iv, bytes)

  def ige_enc(key, iv, bytes), do: :crypto.block_encrypt(:aes_ige256, key, iv, bytes)

Hope this helps.

1 Like

Thank you ! There is some useful tips. I will take a look tomorrow.
Is the project related to this code public ? It would be pretty cool to have a MTProto implementation for elixir.

PS : I’m somewhat learning elixir and trying to implement MTProto in elixir.

How much progress have you made?

Well, I currently don’t have a lot of time to work on this project. I’m starting to use the Telegram API (https://core.telegram.org/schema) but I realized that if I want multiple users I would need to handle connection to multiple DCs (multiple auth keys, etc.). I’m going to change a few things in order to handle it, then start to implement sign in.