%%% @author Sergey %%% @copyright (C) 2018, Sergey %%% @doc %%% MTProto proxy encryption and packet layer; "obfuscated2" protocol lib %%% @end %%% Created : 29 May 2018 by Sergey -module(mtp_obfuscated). -behaviour(mtp_codec). -export([client_create/3, client_create/4, from_header/2, new/4, encrypt/2, decrypt/2, try_decode_packet/2, encode_packet/2 ]). -export([bin_rev/1]). -export_type([codec/0]). -record(st, {encrypt :: any(), % aes state decrypt :: any() % aes state }). -define(APP, mtproto_proxy). -define(KEY_LEN, 32). -define(IV_LEN, 16). -opaque codec() :: #st{}. client_create(Secret, Protocol, DcId) -> client_create(crypto:strong_rand_bytes(58), Secret, Protocol, DcId). -spec client_create(binary(), binary(), mtp_codec:packet_codec(), integer()) -> {Packet, {EncKey, EncIv}, {DecKey, DecIv}, CliCodec} when Packet :: binary(), EncKey :: binary(), EncIv :: binary(), DecKey :: binary(), DecIv :: binary(), CliCodec :: codec(). client_create(Seed, HexSecret, Protocol, DcId) when byte_size(HexSecret) == 32 -> client_create(Seed, mtp_handler:unhex(HexSecret), Protocol, DcId); client_create(Seed, Secret, Protocol, DcId) when byte_size(Seed) == 58, byte_size(Secret) == 16, DcId > -10, DcId < 10, is_atom(Protocol) -> <> = Seed, ProtocolBin = encode_protocol(Protocol), DcIdBin = encode_dc_id(DcId), Raw = <>, %% init_up_encrypt/2 <<_:8/binary, ToRev:(?KEY_LEN + ?IV_LEN)/binary, _/binary>> = Raw, <> = bin_rev(ToRev), DecKey = crypto:hash('sha256', <>), %% init_up_decrypt/2 <<_:8/binary, EncKeySeed:?KEY_LEN/binary, EncIv:?IV_LEN/binary, _/binary>> = Raw, EncKey = crypto:hash('sha256', <>), Codec = new(EncKey, EncIv, DecKey, DecIv), {<<_:56/binary, Encrypted:8/binary>>, Codec1} = encrypt(Raw, Codec), <> = Raw, Packet = <>, {Packet, {EncKey, EncIv}, {DecKey, DecIv}, Codec1}. %% 4byte encode_protocol(mtp_abridged) -> <<16#ef, 16#ef, 16#ef, 16#ef>>; encode_protocol(mtp_intermediate) -> <<16#ee, 16#ee, 16#ee, 16#ee>>; encode_protocol(mtp_secure) -> <<16#dd, 16#dd, 16#dd, 16#dd>>. %% 4byte encode_dc_id(DcId) -> <>. %% @doc creates new obfuscated stream (MTProto proxy format) -spec from_header(binary(), binary()) -> {ok, integer(), mtp_codec:packet_codec(), codec()} | {error, unknown_protocol}. from_header(Header, Secret) when byte_size(Header) == 64 -> %% 1) Encryption key %% [--- _: 8b ----|---------- b: 48b -------------|-- _: 8b --] = header: 64b %% b_r: 48b = reverse([---------- b ------------------]) %% [-- key_seed: 32b --|- iv: 16b -] = b_r %% key: 32b = sha256( [-- key_seed: 32b --|-- secret: 32b --] ) %% iv: 16b = iv %% %% 2) Decryption key %% [--- _: 8b ---|-- key_seed: 32b --|- iv: 16b -|-- _: 8b --] = header %% key: 32b = sha256( [-- key_seed: 32b --|-- secret: 32b --] ) %% ib: 16b = ib %% %% 3) Protocol and datacenter %% decrypted_header: 64b = decrypt(header) %% [-------------- _a: 56b ----|-------- b: 6b ---------|- _: 2b -] = decrypted_header %% [- proto: 4b -|- dc: 2b -] {EncKey, EncIV} = init_up_encrypt(Header, Secret), {DecKey, DecIV} = init_up_decrypt(Header, Secret), St = new(EncKey, EncIV, DecKey, DecIV), {<<_:56/binary, Bin1:6/binary, _:2/binary>>, <<>>, St1} = decrypt(Header, St), case get_protocol(Bin1) of {error, unknown_protocol} = Err -> Err; Protocol -> DcId = get_dc(Bin1), {ok, DcId, Protocol, St1} end. init_up_encrypt(Bin, Secret) -> <<_:8/binary, ToRev:(?KEY_LEN + ?IV_LEN)/binary, _/binary>> = Bin, Rev = bin_rev(ToRev), <> = Rev, %% <<_:32/binary, RevIV:16/binary, _/binary>> = Bin, Key = crypto:hash('sha256', <>), {Key, IV}. init_up_decrypt(Bin, Secret) -> <<_:8/binary, KeySeed:?KEY_LEN/binary, IV:?IV_LEN/binary, _/binary>> = Bin, Key = crypto:hash('sha256', <>), {Key, IV}. get_protocol(<<16#ef, 16#ef, 16#ef, 16#ef, _:2/binary>>) -> mtp_abridged; get_protocol(<<16#ee, 16#ee, 16#ee, 16#ee, _:2/binary>>) -> mtp_intermediate; get_protocol(<<16#dd, 16#dd, 16#dd, 16#dd, _:2/binary>>) -> mtp_secure; get_protocol(_) -> {error, unknown_protocol}. get_dc(<<_:4/binary, DcId:16/signed-little-integer>>) -> DcId. new(EncKey, EncIV, DecKey, DecIV) -> #st{decrypt = crypto:stream_init('aes_ctr', DecKey, DecIV), encrypt = crypto:stream_init('aes_ctr', EncKey, EncIV)}. -spec encrypt(iodata(), codec()) -> {binary(), codec()}. encrypt(Data, #st{encrypt = Enc} = St) -> {Enc1, Encrypted} = crypto:stream_encrypt(Enc, Data), {Encrypted, St#st{encrypt = Enc1}}. -spec decrypt(iodata(), codec()) -> {binary(), binary(), codec()}. decrypt(Encrypted, #st{decrypt = Dec} = St) -> {Dec1, Data} = crypto:stream_encrypt(Dec, Encrypted), {Data, <<>>, St#st{decrypt = Dec1}}. -spec try_decode_packet(iodata(), codec()) -> {ok, Decoded :: binary(), Tail :: binary(), codec()} | {incomplete, codec()}. try_decode_packet(Encrypted, St) -> {Decrypted, Tail, St1} = decrypt(Encrypted, St), {ok, Decrypted, Tail, St1}. -spec encode_packet(iodata(), codec()) -> {iodata(), codec()}. encode_packet(Msg, S) -> encrypt(Msg, S). %% Helpers bin_rev(Bin) -> %% binary:encode_unsigned(binary:decode_unsigned(Bin, little)). list_to_binary(lists:reverse(binary_to_list(Bin))). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). client_server_test() -> Secret = crypto:strong_rand_bytes(16), DcId = 4, Protocol = mtp_secure, {Packet, _, _, _CliCodec} = client_create(Secret, Protocol, DcId), Srv = from_header(Packet, Secret), ?assertMatch({ok, DcId, Protocol, _}, Srv). -endif.