From b267957abeb7ecd306a159c43ce6e57ecf31a4e2 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 24 Apr 2017 15:16:38 +0300
Subject: [PATCH] Apply all MTP Key generation checks to calls.

Also move all OpenSSL BN_* calls to base/openssl_help header.
---
 Telegram/SourceFiles/base/openssl_help.h      |  81 ++-
 Telegram/SourceFiles/calls/calls_call.cpp     | 107 ++--
 Telegram/SourceFiles/calls/calls_call.h       |   8 +-
 Telegram/SourceFiles/calls/calls_instance.cpp |   2 +-
 Telegram/SourceFiles/core/utils.h             |   9 +
 Telegram/SourceFiles/mtproto/auth_key.h       |  18 +-
 Telegram/SourceFiles/mtproto/connection.cpp   | 470 +++++++-----------
 Telegram/SourceFiles/mtproto/connection.h     |  18 +-
 Telegram/SourceFiles/mtproto/dc_options.cpp   |  12 +-
 .../SourceFiles/mtproto/rsa_public_key.cpp    |  42 +-
 Telegram/SourceFiles/mtproto/rsa_public_key.h |  10 +-
 Telegram/SourceFiles/storage/localstorage.cpp |   9 +-
 .../SourceFiles/storage/serialize_common.h    |  63 ++-
 13 files changed, 446 insertions(+), 403 deletions(-)

diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h
index 90639e241..b8f260e06 100644
--- a/Telegram/SourceFiles/base/openssl_help.h
+++ b/Telegram/SourceFiles/base/openssl_help.h
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 #include <openssl/bn.h>
 #include <openssl/sha.h>
+#include <openssl/rand.h>
 
 namespace openssl {
 
@@ -98,9 +99,69 @@ public:
 			_failed = true;
 		}
 	}
+	void setSub(const BigNum &a, const BigNum &b) {
+		if (a.failed() || b.failed()) {
+			_failed = true;
+		} else if (!BN_sub(raw(), a.raw(), b.raw())) {
+			_failed = true;
+		}
+	}
+	void setSubWord(unsigned int word) {
+		if (failed()) {
+			return;
+		} else if (!BN_sub_word(raw(), word)) {
+			_failed = true;
+		}
+	}
+	unsigned int setDivWord(unsigned int word) {
+		Expects(word != 0);
+		if (failed()) {
+			return (BN_ULONG)-1;
+		}
+
+		auto result = BN_div_word(raw(), word);
+		if (result == (BN_ULONG)-1) {
+			_failed = true;
+		}
+		return result;
+	}
 
 	bool isNegative() const {
-		return BN_is_negative(raw());
+		return failed() ? false : BN_is_negative(raw());
+	}
+
+	bool isPrime(const Context &context = Context()) const {
+		if (failed()) {
+			return false;
+		}
+		constexpr auto kMillerRabinIterationCount = 30;
+		auto result = BN_is_prime_ex(raw(), kMillerRabinIterationCount, context.raw(), NULL);
+		if (result == 1) {
+			return true;
+		} else if (result != 0) {
+			_failed = true;
+		}
+		return false;
+	}
+
+	unsigned int modWord(unsigned int word) const {
+		Expects(word != 0);
+		if (failed()) {
+			return (BN_ULONG)-1;
+		}
+
+		auto result = BN_mod_word(raw(), word);
+		if (result == (BN_ULONG)-1) {
+			_failed = true;
+		}
+		return result;
+	}
+
+	int bitsSize() const {
+		return failed() ? 0 : BN_num_bits(raw());
+	}
+	int bytesSize() const {
+		return failed() ? 0 : BN_num_bytes(raw());
 	}
 
 	std::vector<gsl::byte> getBytes() const {
@@ -125,12 +186,24 @@ public:
 		return _failed;
 	}
 
+	static BigNum ModExp(const BigNum &base, const BigNum &power, const openssl::BigNum &mod) {
+		BigNum result;
+		result.setModExp(base, power, mod);
+		return result;
+	}
+
 private:
 	BIGNUM _data;
-	bool _failed = false;
+	mutable bool _failed = false;
 
 };
 
+inline BigNum operator-(const BigNum &a, const BigNum &b) {
+	BigNum result;
+	result.setSub(a, b);
+	return result;
+}
+
 inline std::array<gsl::byte, SHA256_DIGEST_LENGTH> Sha256(base::const_byte_span bytes) {
 	auto result = std::array<gsl::byte, SHA256_DIGEST_LENGTH>();
 	SHA256(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size(), reinterpret_cast<unsigned char*>(result.data()));
@@ -143,4 +216,8 @@ inline std::array<gsl::byte, SHA_DIGEST_LENGTH> Sha1(base::const_byte_span bytes
 	return result;
 }
 
+inline int FillRandom(base::byte_span bytes) {
+	return RAND_bytes(reinterpret_cast<unsigned char*>(bytes.data()), bytes.size());
+}
+
 } // namespace openssl
diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp
index 42a5e719f..560397cc6 100644
--- a/Telegram/SourceFiles/calls/calls_call.cpp
+++ b/Telegram/SourceFiles/calls/calls_call.cpp
@@ -24,9 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "mainwidget.h"
 #include "calls/calls_instance.h"
 #include "base/openssl_help.h"
-
-#include <openssl/rand.h>
-#include <openssl/sha.h>
+#include "mtproto/connection.h"
 
 #ifdef slots
 #undef slots
@@ -59,25 +57,6 @@ void ConvertEndpoint(std::vector<tgvoip::Endpoint> &ep, const MTPDphoneConnectio
 	ep.push_back(Endpoint((int64_t)mtc.vid.v, (uint16_t)mtc.vport.v, ipv4, ipv6, EP_TYPE_UDP_RELAY, (unsigned char*)mtc.vpeer_tag.v.data()));
 }
 
-std::vector<gsl::byte> ComputeModExp(const DhConfig &config, const openssl::BigNum &base, const std::array<gsl::byte, Call::kRandomPowerSize> &randomPower) {
-	using namespace openssl;
-
-	BigNum resultBN;
-	resultBN.setModExp(base, BigNum(randomPower), BigNum(config.p));
-	auto result = resultBN.getBytes();
-	constexpr auto kMaxModExpSize = 256;
-	t_assert(result.size() <= kMaxModExpSize);
-	return result;
-}
-
-std::vector<gsl::byte> ComputeModExpFirst(const DhConfig &config, const std::array<gsl::byte, Call::kRandomPowerSize> &randomPower) {
-	return ComputeModExp(config, openssl::BigNum(config.g), randomPower);
-}
-
-std::vector<gsl::byte> ComputeModExpFinal(const DhConfig &config, base::const_byte_span first, const std::array<gsl::byte, Call::kRandomPowerSize> &randomPower) {
-	return ComputeModExp(config, openssl::BigNum(first), randomPower);
-}
-
 constexpr auto kFingerprintDataSize = 256;
 uint64 ComputeFingerprint(const std::array<gsl::byte, kFingerprintDataSize> &authKey) {
 	auto hash = openssl::Sha1(authKey);
@@ -102,11 +81,20 @@ Call::Call(gsl::not_null<Delegate*> delegate, gsl::not_null<UserData*> user, Typ
 	}
 }
 
-void Call::generateRandomPower(base::const_byte_span random) {
-	Expects(random.size() == _randomPower.size());
-	memset_rand(_randomPower.data(), _randomPower.size());
-	for (auto i = 0, count = int(_randomPower.size()); i != count; i++) {
-		_randomPower[i] ^= random[i];
+void Call::generateModExpFirst(base::const_byte_span randomSeed) {
+	auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed);
+	if (first.modexp.empty()) {
+		LOG(("Call Error: Could not compute mod-exp first."));
+		setState(State::Failed);
+		return;
+	}
+
+	_randomPower = first.randomPower;
+	if (_type == Type::Incoming) {
+		_gb = std::move(first.modexp);
+	} else {
+		_ga = std::move(first.modexp);
+		_gaHash = openssl::Sha256(_ga);
 	}
 }
 
@@ -117,27 +105,21 @@ void Call::start(base::const_byte_span random) {
 	t_assert(_dhConfig.g != 0);
 	t_assert(!_dhConfig.p.empty());
 
-	generateRandomPower(random);
-
-	if (_type == Type::Outgoing) {
-		startOutgoing();
-	} else {
-		startIncoming();
+	generateModExpFirst(random);
+	if (_state != State::Failed) {
+		if (_type == Type::Outgoing) {
+			startOutgoing();
+		} else {
+			startIncoming();
+		}
 	}
 }
 
 void Call::startOutgoing() {
-	_ga = ComputeModExpFirst(_dhConfig, _randomPower);
-	if (_ga.empty()) {
-		LOG(("Call Error: Could not compute mod-exp first."));
-		setState(State::Failed);
-		return;
-	}
-	_gaHash = openssl::Sha256(_ga);
-	auto randomID = rand_value<int32>();
+	Expects(_type == Type::Outgoing);
 
 	setState(State::Requesting);
-	request(MTPphone_RequestCall(_user->inputUser, MTP_int(randomID), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
+	request(MTPphone_RequestCall(_user->inputUser, MTP_int(rand_value<int32>()), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
 		Expects(result.type() == mtpc_phone_phoneCall);
 		auto &call = result.c_phone_phoneCall();
 		App::feedUsers(call.vusers);
@@ -163,17 +145,17 @@ void Call::startOutgoing() {
 }
 
 void Call::startIncoming() {
-	setState(State::Ringing);
+	Expects(_type == Type::Incoming);
+
+	request(MTPphone_ReceivedCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)))).done([this](const MTPBool &result) {
+		setState(State::Ringing);
+	}).fail([this](const RPCError &error) {
+		setState(State::Failed);
+	}).send();
 }
 
 void Call::answer() {
 	Expects(_type == Type::Incoming);
-	_gb = ComputeModExpFirst(_dhConfig, _randomPower);
-	if (_gb.empty()) {
-		LOG(("Call Error: Could not compute mod-exp first."));
-		setState(State::Failed);
-		return;
-	}
 
 	setState(State::ExchangingKeys);
 	request(MTPphone_AcceptCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_gb), _protocol)).done([this](const MTPphone_PhoneCall &result) {
@@ -300,23 +282,15 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
 	Expects(_type == Type::Outgoing);
 
-	// TODO check isGoodGaAndGb
-	auto computedAuthKey = ComputeModExpFinal(_dhConfig, byteVectorFromMTP(call.vg_b), _randomPower);
+	auto firstBytes = bytesFromMTP(call.vg_b);
+	auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
 	if (computedAuthKey.empty()) {
 		LOG(("Call Error: Could not compute mod-exp final."));
 		setState(State::Failed);
 		return;
 	}
 
-	auto computedAuthKeySize = computedAuthKey.size();
-	t_assert(computedAuthKeySize <= kAuthKeySize);
-	auto authKeyBytes = gsl::make_span(_authKey);
-	if (computedAuthKeySize < kAuthKeySize) {
-		base::set_bytes(authKeyBytes.subspan(0, kAuthKeySize - computedAuthKeySize), gsl::byte());
-		base::copy_bytes(authKeyBytes.subspan(kAuthKeySize - computedAuthKeySize), computedAuthKey);
-	} else {
-		base::copy_bytes(authKeyBytes, computedAuthKey);
-	}
+	MTP::AuthKey::FillData(_authKey, computedAuthKey);
 	_keyFingerprint = ComputeFingerprint(_authKey);
 
 	setState(State::ExchangingKeys);
@@ -346,23 +320,14 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
 		return;
 	}
 
-	// TODO check isGoodGaAndGb
-	auto computedAuthKey = ComputeModExpFinal(_dhConfig, firstBytes, _randomPower);
+	auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
 	if (computedAuthKey.empty()) {
 		LOG(("Call Error: Could not compute mod-exp final."));
 		setState(State::Failed);
 		return;
 	}
 
-	auto computedAuthKeySize = computedAuthKey.size();
-	t_assert(computedAuthKeySize <= kAuthKeySize);
-	auto authKeyBytes = gsl::make_span(_authKey);
-	if (computedAuthKeySize < kAuthKeySize) {
-		base::set_bytes(authKeyBytes.subspan(0, kAuthKeySize - computedAuthKeySize), gsl::byte());
-		base::copy_bytes(authKeyBytes.subspan(kAuthKeySize - computedAuthKeySize), computedAuthKey);
-	} else {
-		base::copy_bytes(authKeyBytes, computedAuthKey);
-	}
+	MTP::AuthKey::FillData(_authKey, computedAuthKey);
 	_keyFingerprint = ComputeFingerprint(_authKey);
 
 	createAndStartController(call);
diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h
index bb6e274b7..040bf541d 100644
--- a/Telegram/SourceFiles/calls/calls_call.h
+++ b/Telegram/SourceFiles/calls/calls_call.h
@@ -21,8 +21,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #pragma once
 
 #include "base/weak_unique_ptr.h"
-#include "mtproto/sender.h"
 #include "base/timer.h"
+#include "mtproto/sender.h"
+#include "mtproto/auth_key.h"
 
 namespace tgvoip {
 class VoIPController;
@@ -93,14 +94,13 @@ public:
 	~Call();
 
 private:
-	static constexpr auto kAuthKeySize = 256;
 	static constexpr auto kSha256Size = 32;
 
 	void finish(const MTPPhoneCallDiscardReason &reason);
 	void startOutgoing();
 	void startIncoming();
 
-	void generateRandomPower(base::const_byte_span random);
+	void generateModExpFirst(base::const_byte_span randomSeed);
 	void handleControllerStateChange(tgvoip::VoIPController *controller, int state);
 	void createAndStartController(const MTPDphoneCall &call);
 
@@ -129,7 +129,7 @@ private:
 	std::vector<gsl::byte> _gb;
 	std::array<gsl::byte, kSha256Size> _gaHash;
 	std::array<gsl::byte, kRandomPowerSize> _randomPower;
-	std::array<gsl::byte, kAuthKeySize> _authKey;
+	MTP::AuthKey::Data _authKey;
 	MTPPhoneCallProtocol _protocol;
 
 	uint64 _id = 0;
diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp
index ce222b154..9e0a67b34 100644
--- a/Telegram/SourceFiles/calls/calls_instance.cpp
+++ b/Telegram/SourceFiles/calls/calls_instance.cpp
@@ -63,7 +63,7 @@ void Instance::refreshDhConfig() {
 		switch (result.type()) {
 		case mtpc_messages_dhConfig: {
 			auto &config = result.c_messages_dhConfig();
-			if (!MTP::IsPrimeAndGood(config.vp.v, config.vg.v)) {
+			if (!MTP::IsPrimeAndGood(bytesFromMTP(config.vp), config.vg.v)) {
 				LOG(("API Error: bad p/g received in dhConfig."));
 				callFailed(call.get());
 				return;
diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h
index c320b8815..ab1e3099b 100644
--- a/Telegram/SourceFiles/core/utils.h
+++ b/Telegram/SourceFiles/core/utils.h
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 #include "core/basic_types.h"
 #include <array>
+#include <vector>
 #include <algorithm>
 #include <set>
 #include <gsl/gsl>
@@ -204,6 +205,9 @@ using set_of_shared_ptr = std::set<std::shared_ptr<T>, base::pointer_comparator<
 
 using byte_span = gsl::span<gsl::byte>;
 using const_byte_span = gsl::span<const gsl::byte>;
+using byte_vector = std::vector<gsl::byte>;
+template <size_t N>
+using byte_array = std::array<gsl::byte, N>;
 
 inline void copy_bytes(byte_span destination, const_byte_span source) {
 	Expects(destination.size() >= source.size());
@@ -214,6 +218,11 @@ inline void set_bytes(byte_span destination, gsl::byte value) {
 	memset(destination.data(), gsl::to_integer<unsigned char>(value), destination.size());
 }
 
+inline int compare_bytes(const_byte_span a, const_byte_span b) {
+	auto aSize = a.size(), bSize = b.size();
+	return (aSize > bSize) ? 1 : (aSize < bSize) ? -1 : memcmp(a.data(), b.data(), aSize);
+}
+
 } // namespace base
 
 // using for_const instead of plain range-based for loop to ensure usage of const_iterator
diff --git a/Telegram/SourceFiles/mtproto/auth_key.h b/Telegram/SourceFiles/mtproto/auth_key.h
index 64b38f588..d11ae7cc0 100644
--- a/Telegram/SourceFiles/mtproto/auth_key.h
+++ b/Telegram/SourceFiles/mtproto/auth_key.h
@@ -28,7 +28,7 @@ namespace MTP {
 class AuthKey {
 public:
 	static constexpr auto kSize = 256; // 2048 bits.
-	using Data = std::array<char, kSize>;
+	using Data = std::array<gsl::byte, kSize>;
 	using KeyId = uint64;
 
 	enum class Type {
@@ -94,13 +94,25 @@ public:
 	}
 
 	void write(QDataStream &to) const {
-		to.writeRawData(_key.data(), _key.size());
+		to.writeRawData(reinterpret_cast<const char*>(_key.data()), _key.size());
 	}
 
 	bool equals(const std::shared_ptr<AuthKey> &other) const {
 		return other ? (_key == other->_key) : false;
 	}
 
+	static void FillData(Data &authKey, base::const_byte_span computedAuthKey) {
+		auto computedAuthKeySize = computedAuthKey.size();
+		t_assert(computedAuthKeySize <= kSize);
+		auto authKeyBytes = gsl::make_span(authKey);
+		if (computedAuthKeySize < kSize) {
+			base::set_bytes(authKeyBytes.subspan(0, kSize - computedAuthKeySize), gsl::byte());
+			base::copy_bytes(authKeyBytes.subspan(kSize - computedAuthKeySize), computedAuthKey);
+		} else {
+			base::copy_bytes(authKeyBytes, computedAuthKey);
+		}
+	}
+
 private:
 	void countKeyId() {
 		auto sha1 = hashSha1(_key.data(), _key.size());
@@ -111,7 +123,7 @@ private:
 
 	Type _type = Type::Generated;
 	DcId _dcId = 0;
-	Data _key = { { 0 } };
+	Data _key = { { gsl::byte{} } };
 	KeyId _keyId = 0;
 
 };
diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp
index 711b46d94..3c04d7c26 100644
--- a/Telegram/SourceFiles/mtproto/connection.cpp
+++ b/Telegram/SourceFiles/mtproto/connection.cpp
@@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include <openssl/rand.h>
 #include "zlib.h"
 #include "lang.h"
+#include "base/openssl_help.h"
 
 #include "mtproto/rsa_public_key.h"
 #include "messenger.h"
@@ -40,6 +41,149 @@ namespace {
 
 constexpr auto kRecreateKeyId = AuthKey::KeyId(0xFFFFFFFFFFFFFFFFULL);
 constexpr auto kIntSize = static_cast<int>(sizeof(mtpPrime));
+constexpr auto kMaxModExpSize = 256;
+
+bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) {
+	auto diff = prime - modexp;
+	if (modexp.failed() || prime.failed() || diff.failed()) {
+		return false;
+	}
+	constexpr auto kMinDiffBitsCount = 2048 - 64;
+	if (diff.isNegative() || diff.bitsSize() < kMinDiffBitsCount || modexp.bitsSize() < kMinDiffBitsCount) {
+		return false;
+	}
+	t_assert(modexp.bytesSize() <= kMaxModExpSize);
+	return true;
+}
+
+bool IsPrimeAndGoodCheck(const openssl::BigNum &prime, int g) {
+	constexpr auto kGoodPrimeBitsCount = 2048;
+
+	if (prime.failed() || prime.isNegative() || prime.bitsSize() != kGoodPrimeBitsCount) {
+		LOG(("MTP Error: Bad prime bits count %1, expected %2.").arg(prime.bitsSize()).arg(kGoodPrimeBitsCount));
+		return false;
+	}
+
+	openssl::Context context;
+	if (!prime.isPrime(context)) {
+		LOG(("MTP Error: Bad prime."));
+		return false;
+	}
+
+	switch (g) {
+	case 2: {
+		auto mod8 = prime.modWord(8);
+		if (mod8 != 7) {
+			LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8));
+			return false;
+		}
+	} break;
+	case 3: {
+		auto mod3 = prime.modWord(3);
+		if (mod3 != 2) {
+			LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3));
+			return false;
+		}
+	} break;
+	case 4: break;
+	case 5: {
+		auto mod5 = prime.modWord(5);
+		if (mod5 != 1 && mod5 != 4) {
+			LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5));
+			return false;
+		}
+	} break;
+	case 6: {
+		auto mod24 = prime.modWord(24);
+		if (mod24 != 19 && mod24 != 23) {
+			LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24));
+			return false;
+		}
+	} break;
+	case 7: {
+		auto mod7 = prime.modWord(7);
+		if (mod7 != 3 && mod7 != 5 && mod7 != 6) {
+			LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7));
+			return false;
+		}
+	} break;
+	default: {
+		LOG(("BigNum PT Error: bad g value: %1").arg(g));
+		return false;
+	} break;
+	}
+
+	auto primeSubOneDivTwo = prime;
+	primeSubOneDivTwo.setSubWord(1);
+	primeSubOneDivTwo.setDivWord(2);
+	if (!primeSubOneDivTwo.isPrime(context)) {
+		LOG(("MTP Error: Bad (prime - 1) / 2."));
+		return false;
+	}
+
+	return true;
+}
+
+bool IsPrimeAndGood(base::const_byte_span primeBytes, int g) {
+	static constexpr unsigned char GoodPrime[] = {
+		0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73,
+		0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F,
+		0x48, 0x19, 0x8A, 0x0A, 0xA7, 0xC1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xD2, 0x25, 0x30, 0xF4, 0xDB,
+		0xFA, 0x33, 0x6F, 0x6E, 0x0A, 0xC9, 0x25, 0x13, 0x95, 0x43, 0xAE, 0xD4, 0x4C, 0xCE, 0x7C, 0x37,
+		0x20, 0xFD, 0x51, 0xF6, 0x94, 0x58, 0x70, 0x5A, 0xC6, 0x8C, 0xD4, 0xFE, 0x6B, 0x6B, 0x13, 0xAB,
+		0xDC, 0x97, 0x46, 0x51, 0x29, 0x69, 0x32, 0x84, 0x54, 0xF1, 0x8F, 0xAF, 0x8C, 0x59, 0x5F, 0x64,
+		0x24, 0x77, 0xFE, 0x96, 0xBB, 0x2A, 0x94, 0x1D, 0x5B, 0xCD, 0x1D, 0x4A, 0xC8, 0xCC, 0x49, 0x88,
+		0x07, 0x08, 0xFA, 0x9B, 0x37, 0x8E, 0x3C, 0x4F, 0x3A, 0x90, 0x60, 0xBE, 0xE6, 0x7C, 0xF9, 0xA4,
+		0xA4, 0xA6, 0x95, 0x81, 0x10, 0x51, 0x90, 0x7E, 0x16, 0x27, 0x53, 0xB5, 0x6B, 0x0F, 0x6B, 0x41,
+		0x0D, 0xBA, 0x74, 0xD8, 0xA8, 0x4B, 0x2A, 0x14, 0xB3, 0x14, 0x4E, 0x0E, 0xF1, 0x28, 0x47, 0x54,
+		0xFD, 0x17, 0xED, 0x95, 0x0D, 0x59, 0x65, 0xB4, 0xB9, 0xDD, 0x46, 0x58, 0x2D, 0xB1, 0x17, 0x8D,
+		0x16, 0x9C, 0x6B, 0xC4, 0x65, 0xB0, 0xD6, 0xFF, 0x9C, 0xA3, 0x92, 0x8F, 0xEF, 0x5B, 0x9A, 0xE4,
+		0xE4, 0x18, 0xFC, 0x15, 0xE8, 0x3E, 0xBE, 0xA0, 0xF8, 0x7F, 0xA9, 0xFF, 0x5E, 0xED, 0x70, 0x05,
+		0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
+		0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
+		0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B };
+
+	if (!base::compare_bytes(gsl::as_bytes(gsl::make_span(GoodPrime)), primeBytes)) {
+		if (g == 3 || g == 4 || g == 5 || g == 7) {
+			return true;
+		}
+	}
+
+	return IsPrimeAndGoodCheck(openssl::BigNum(primeBytes), g);
+}
+
+std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes) {
+	using openssl::BigNum;
+	BigNum first(firstBytes);
+	BigNum prime(primeBytes);
+	if (!IsGoodModExpFirst(first, prime)) {
+		LOG(("AuthKey Error: Bad first prime in CreateAuthKey()."));
+		return std::vector<gsl::byte>();
+	}
+	return BigNum::ModExp(first, BigNum(randomBytes), prime).getBytes();
+}
+
+ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed) {
+	Expects(randomSeed.size() == ModExpFirst::kRandomPowerSize);
+
+	using namespace openssl;
+
+	BigNum prime(primeBytes);
+	ModExpFirst result;
+	constexpr auto kMaxModExpFirstTries = 5;
+	for (auto tries = 0; tries != kMaxModExpFirstTries; ++tries) {
+		FillRandom(result.randomPower);
+		for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) {
+			result.randomPower[i] ^= randomSeed[i];
+		}
+		auto modexp = BigNum::ModExp(BigNum(g), BigNum(result.randomPower), prime);
+		if (IsGoodModExpFirst(modexp, prime)) {
+			result.modexp = modexp.getBytes();
+			break;
+		}
+	}
+	return result;
+}
 
 void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest = 0) {
 	mtpMsgId afterId(*(mtpMsgId*)(from->after->data() + 4));
@@ -106,240 +250,6 @@ bool parsePQ(const QByteArray &pqStr, QByteArray &pStr, QByteArray &qStr) {
 	return true;
 }
 
-class BigNumCounter {
-public:
-	BigNumCounter() : ctx(BN_CTX_new()) {
-		BN_init(&bnPower);
-		BN_init(&bnModul);
-		BN_init(&bn_g);
-		BN_init(&bn_g_a);
-		BN_init(&bnResult);
-		BN_init(&bnTemp);
-	}
-	~BigNumCounter() {
-		BN_CTX_free(ctx);
-		BN_clear_free(&bnPower);
-		BN_clear_free(&bnModul);
-		BN_clear_free(&bn_g);
-		BN_clear_free(&bn_g_a);
-		BN_clear_free(&bnResult);
-		BN_clear_free(&bnTemp);
-	}
-
-	bool count(const void *power, const void *modul, uint32 g, void *gResult, const void *g_a, void *g_aResult) {
-		DEBUG_LOG(("BigNum Info: counting g_b = g ^ b % dh_prime and auth_key = g_a ^ b % dh_prime"));
-		uint32 g_be = qToBigEndian(g);
-		if (
-			!BN_bin2bn((const uchar*)power, 64 * sizeof(uint32), &bnPower) ||
-			!BN_bin2bn((const uchar*)modul, 64 * sizeof(uint32), &bnModul) ||
-			!BN_bin2bn((const uchar*)&g_be, sizeof(uint32), &bn_g) ||
-			!BN_bin2bn((const uchar*)g_a, 64 * sizeof(uint32), &bn_g_a)
-			) {
-			ERR_load_crypto_strings();
-			LOG(("BigNum Error: BN_bin2bn failed, error: %1").arg(ERR_error_string(ERR_get_error(), 0)));
-			DEBUG_LOG(("BigNum Error: base %1, power %2, modul %3").arg(Logs::mb(&g_be, sizeof(uint32)).str()).arg(Logs::mb(power, 64 * sizeof(uint32)).str()).arg(Logs::mb(modul, 64 * sizeof(uint32)).str()));
-			return false;
-		}
-
-		if (!BN_mod_exp(&bnResult, &bn_g, &bnPower, &bnModul, ctx)) {
-			ERR_load_crypto_strings();
-			LOG(("BigNum Error: BN_mod_exp failed, error: %1").arg(ERR_error_string(ERR_get_error(), 0)));
-			DEBUG_LOG(("BigNum Error: base %1, power %2, modul %3").arg(Logs::mb(&g_be, sizeof(uint32)).str()).arg(Logs::mb(power, 64 * sizeof(uint32)).str()).arg(Logs::mb(modul, 64 * sizeof(uint32)).str()));
-			return false;
-		}
-
-		// check g_b > 2^{2048 - 8} and get the value of g_b
-		if (BN_is_negative(&bnResult)) {
-			LOG(("BigNum Error: bad g_b - negative"));
-			return false;
-		}
-		uint32 resultLen = BN_num_bytes(&bnResult);
-		if (resultLen != 64 * sizeof(uint32)) {
-			LOG(("BigNum Error: bad g_b len (%1)").arg(resultLen));
-			return false;
-		}
-		resultLen = BN_bn2bin(&bnResult, (uchar*)gResult);
-		if (resultLen != 64 * sizeof(uint32)) {
-			LOG(("BigNum Error: bad g_b export len (%1)").arg(resultLen));
-			return false;
-		}
-
-		// check g_b < dh_prime - 2^{2048 - 8}
-		BN_sub(&bnTemp, &bnModul, &bnResult);
-		if (BN_is_negative(&bnTemp)) {
-			DEBUG_LOG(("BigNum Error: bad g_b > dh_prime"));
-			return false;
-		}
-		if (BN_num_bytes(&bnTemp) != 64 * sizeof(uint32)) {
-			DEBUG_LOG(("BigNum Error: bad g_b > dh_prime - 2^{2048 - 8}"));
-			return false;
-		}
-
-		if (!BN_mod_exp(&bnResult, &bn_g_a, &bnPower, &bnModul, ctx)) {
-			ERR_load_crypto_strings();
-			LOG(("BigNum Error: BN_mod_exp failed, error: %1").arg(ERR_error_string(ERR_get_error(), 0)));
-			DEBUG_LOG(("BigNum Error: base %1, power %2, modul %3").arg(Logs::mb(&g_be, sizeof(uint32)).str()).arg(Logs::mb(power, 64 * sizeof(uint32)).str()).arg(Logs::mb(modul, 64 * sizeof(uint32)).str()));
-			return false;
-		}
-
-		resultLen = BN_num_bytes(&bnResult);
-		if (resultLen != 64 * sizeof(uint32)) {
-			DEBUG_LOG(("BigNum Error: bad g_aResult len (%1)").arg(resultLen));
-			return false;
-		}
-		resultLen = BN_bn2bin(&bnResult, (uchar*)g_aResult);
-		if (resultLen != 64 * sizeof(uint32)) {
-			DEBUG_LOG(("BigNum Error: bad g_aResult export len (%1)").arg(resultLen));
-			return false;
-		}
-
-		// check g_a > 2^{2048 - 8}
-		if (BN_is_negative(&bn_g_a)) {
-			LOG(("BigNum Error: bad g_a - negative"));
-			return false;
-		}
-		resultLen = BN_num_bytes(&bn_g_a);
-		if (resultLen != 64 * sizeof(uint32)) {
-			LOG(("BigNum Error: bad g_a len (%1)").arg(resultLen));
-			return false;
-		}
-
-		// check g_a < dh_prime - 2^{2048 - 8}
-		BN_sub(&bnTemp, &bnModul, &bn_g_a);
-		if (BN_is_negative(&bnTemp)) {
-			LOG(("BigNum Error: bad g_b > dh_prime"));
-			return false;
-		}
-		if (BN_num_bytes(&bnTemp) != 64 * sizeof(uint32)) {
-			LOG(("BigNum Error: bad g_b > dh_prime - 2^{2048 - 8}"));
-			return false;
-		}
-
-		return true;
-	}
-
-private:
-	BIGNUM bnPower, bnModul, bn_g, bn_g_a, bnResult, bnTemp;
-	BN_CTX *ctx;
-
-};
-
-// Miller-Rabin primality test
-class BigNumPrimeTest {
-public:
-	BigNumPrimeTest() : _context(BN_CTX_new()) {
-		BN_init(&_prime);
-	}
-	~BigNumPrimeTest() {
-		BN_clear_free(&_prime);
-		BN_CTX_free(_context);
-	}
-
-	bool isPrimeAndGood(const QByteArray &data, int g) {
-		constexpr auto kMillerRabinIterationCount = 30;
-		constexpr auto kGoodPrimeSize = 256;
-
-		if (data.size() != kGoodPrimeSize) {
-			LOG(("BigNum PT Error: data size %1").arg(data.size()));
-			return false;
-		}
-		if (!memcmp(data.constData(), "\
-\xC7\x1C\xAE\xB9\xC6\xB1\xC9\x04\x8E\x6C\x52\x2F\x70\xF1\x3F\x73\
-\x98\x0D\x40\x23\x8E\x3E\x21\xC1\x49\x34\xD0\x37\x56\x3D\x93\x0F\
-\x48\x19\x8A\x0A\xA7\xC1\x40\x58\x22\x94\x93\xD2\x25\x30\xF4\xDB\
-\xFA\x33\x6F\x6E\x0A\xC9\x25\x13\x95\x43\xAE\xD4\x4C\xCE\x7C\x37\
-\x20\xFD\x51\xF6\x94\x58\x70\x5A\xC6\x8C\xD4\xFE\x6B\x6B\x13\xAB\
-\xDC\x97\x46\x51\x29\x69\x32\x84\x54\xF1\x8F\xAF\x8C\x59\x5F\x64\
-\x24\x77\xFE\x96\xBB\x2A\x94\x1D\x5B\xCD\x1D\x4A\xC8\xCC\x49\x88\
-\x07\x08\xFA\x9B\x37\x8E\x3C\x4F\x3A\x90\x60\xBE\xE6\x7C\xF9\xA4\
-\xA4\xA6\x95\x81\x10\x51\x90\x7E\x16\x27\x53\xB5\x6B\x0F\x6B\x41\
-\x0D\xBA\x74\xD8\xA8\x4B\x2A\x14\xB3\x14\x4E\x0E\xF1\x28\x47\x54\
-\xFD\x17\xED\x95\x0D\x59\x65\xB4\xB9\xDD\x46\x58\x2D\xB1\x17\x8D\
-\x16\x9C\x6B\xC4\x65\xB0\xD6\xFF\x9C\xA3\x92\x8F\xEF\x5B\x9A\xE4\
-\xE4\x18\xFC\x15\xE8\x3E\xBE\xA0\xF8\x7F\xA9\xFF\x5E\xED\x70\x05\
-\x0D\xED\x28\x49\xF4\x7B\xF9\x59\xD9\x56\x85\x0C\xE9\x29\x85\x1F\
-\x0D\x81\x15\xF6\x35\xB1\x05\xEE\x2E\x4E\x15\xD0\x4B\x24\x54\xBF\
-\x6F\x4F\xAD\xF0\x34\xB1\x04\x03\x11\x9C\xD8\xE3\xB9\x2F\xCC\x5B", kGoodPrimeSize)) {
-			if (g == 3 || g == 4 || g == 5 || g == 7) {
-				return true;
-			}
-		}
-		if (!BN_bin2bn((const uchar*)data.constData(), kGoodPrimeSize, &_prime)) {
-			ERR_load_crypto_strings();
-			LOG(("BigNum PT Error: BN_bin2bn failed, error: %1").arg(ERR_error_string(ERR_get_error(), 0)));
-			DEBUG_LOG(("BigNum PT Error: prime %1").arg(Logs::mb(data.constData(), kGoodPrimeSize).str()));
-			return false;
-		}
-
-		auto numBits = BN_num_bits(&_prime);
-		if (numBits != 2048) {
-			LOG(("BigNum PT Error: BN_bin2bn failed, bad dh_prime num bits: %1").arg(numBits));
-			return false;
-		}
-
-		if (BN_is_prime_ex(&_prime, kMillerRabinIterationCount, _context, NULL) == 0) {
-			return false;
-		}
-
-		switch (g) {
-		case 2: {
-			auto mod8 = BN_mod_word(&_prime, 8);
-			if (mod8 != 7) {
-				LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8));
-				return false;
-			}
-		} break;
-		case 3: {
-			auto mod3 = BN_mod_word(&_prime, 3);
-			if (mod3 != 2) {
-				LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3));
-				return false;
-			}
-		} break;
-		case 4: break;
-		case 5: {
-			auto mod5 = BN_mod_word(&_prime, 5);
-			if (mod5 != 1 && mod5 != 4) {
-				LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5));
-				return false;
-			}
-		} break;
-		case 6: {
-			auto mod24 = BN_mod_word(&_prime, 24);
-			if (mod24 != 19 && mod24 != 23) {
-				LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24));
-				return false;
-			}
-		} break;
-		case 7: {
-			auto mod7 = BN_mod_word(&_prime, 7);
-			if (mod7 != 3 && mod7 != 5 && mod7 != 6) {
-				LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7));
-				return false;
-			}
-		} break;
-		default: {
-			LOG(("BigNum PT Error: bad g value: %1").arg(g));
-			return false;
-		} break;
-		}
-
-		BN_sub_word(&_prime, 1); // (p - 1) / 2
-		BN_div_word(&_prime, 2);
-
-		if (BN_is_prime_ex(&_prime, kMillerRabinIterationCount, _context, NULL) == 0) {
-			return false;
-		}
-
-		return true;
-	}
-
-private:
-	BIGNUM _prime;
-	BN_CTX *_context;
-
-};
-
 } // namespace
 
 Connection::Connection(Instance *instance) : _instance(instance) {
@@ -2467,11 +2377,11 @@ void ConnectionPrivate::pqAnswered() {
 	req_DH_params.vpublic_key_fingerprint = MTP_long(rsaKey.getFingerPrint());
 	req_DH_params.vp = p_q_inner.c_p_q_inner_data().vp;
 	req_DH_params.vq = p_q_inner.c_p_q_inner_data().vq;
-	req_DH_params.vencrypted_data = MTP_string(std::move(dhEncString));
+	req_DH_params.vencrypted_data = MTP_bytes(dhEncString);
 	sendRequestNotSecure(req_DH_params);
 }
 
-std::string ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key) {
+base::byte_vector ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key) {
 	auto p_q_inner_size = data.innerLength();
 	auto encSize = (p_q_inner_size >> 2) + 6;
 	if (encSize >= 65) {
@@ -2480,7 +2390,7 @@ std::string ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data,
 		data.write(tmp);
 		LOG(("AuthKey Error: too large data for RSA encrypt, size %1").arg(encSize * sizeof(mtpPrime)));
 		DEBUG_LOG(("AuthKey Error: bad data for RSA encrypt %1").arg(Logs::mb(&tmp[0], tmp.size() * 4).str()));
-		return std::string(); // can't be 255-byte string
+		return base::byte_vector(); // can't be 255-byte string
 	}
 
 	auto encBuffer = mtpBuffer();
@@ -2495,11 +2405,9 @@ std::string ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data,
 		memset_rand(&encBuffer[encSize], (65 - encSize) * sizeof(mtpPrime));
 	}
 
-	auto dhEncString = std::string();
-	if (!key.encrypt(reinterpret_cast<const char*>(&encBuffer[0]) + 3, dhEncString)) {
-		return std::string();
-	}
-	return dhEncString;
+	auto bytes = gsl::as_bytes(gsl::make_span(encBuffer));
+	auto bytesToEncrypt = bytes.subspan(3, 256);
+	return key.encrypt(bytesToEncrypt);
 }
 
 void ConnectionPrivate::dhParamsAnswered() {
@@ -2576,24 +2484,15 @@ void ConnectionPrivate::dhParamsAnswered() {
 		}
 		unixtimeSet(dh_inner_data.vserver_time.v);
 
-		auto &dhPrime = dh_inner_data.vdh_prime.v;
-		auto &g_a = dh_inner_data.vg_a.v;
-		if (dhPrime.length() != 256 || g_a.length() != 256) {
-			LOG(("AuthKey Error: bad dh_prime len (%1) or g_a len (%2)").arg(dhPrime.length()).arg(g_a.length()));
-			DEBUG_LOG(("AuthKey Error: dh_prime %1, g_a %2").arg(Logs::mb(dhPrime.constData(), dhPrime.length()).str()).arg(Logs::mb(g_a.constData(), g_a.length()).str()));
+		// check that dhPrime and (dhPrime - 1) / 2 are really prime
+		if (!IsPrimeAndGood(bytesFromMTP(dh_inner_data.vdh_prime), dh_inner_data.vg.v)) {
+			LOG(("AuthKey Error: bad dh_prime primality!"));
 			return restart();
 		}
 
-		// check that dhPrime and (dhPrime - 1) / 2 are really prime using openssl BIGNUM methods
-		if (!IsPrimeAndGood(dhPrime, dh_inner_data.vg.v)) {
-			LOG(("AuthKey Error: bad dh_prime primality!").arg(dhPrime.length()).arg(g_a.length()));
-			DEBUG_LOG(("AuthKey Error: dh_prime %1").arg(Logs::mb(dhPrime.constData(), dhPrime.length()).str()));
-			return restart();
-		}
-
-		_authKeyStrings->dh_prime = QByteArray(dhPrime.data(), dhPrime.size());
+		_authKeyStrings->dh_prime = byteVectorFromMTP(dh_inner_data.vdh_prime);
 		_authKeyData->g = dh_inner_data.vg.v;
-		_authKeyStrings->g_a = QByteArray(g_a.data(), g_a.size());
+		_authKeyStrings->g_a = byteVectorFromMTP(dh_inner_data.vg_a);
 		_authKeyData->retry_id = MTP_long(0);
 		_authKeyData->retries = 0;
 	} return dhClientParamsSend();
@@ -2630,25 +2529,28 @@ void ConnectionPrivate::dhClientParamsSend() {
 		return restart();
 	}
 
-	auto g_b_string = std::string(256, ' ');
-
 	// gen rand 'b'
-	uint32 b[64];
-	auto g_b = reinterpret_cast<uint32*>(&g_b_string[0]);
-	memset_rand(b, sizeof(b));
-
-	// count g_b and auth_key using openssl BIGNUM methods
-	MTP::internal::BigNumCounter bnCounter;
-	if (!bnCounter.count(b, _authKeyStrings->dh_prime.constData(), _authKeyData->g, g_b, _authKeyStrings->g_a.constData(), _authKeyStrings->auth_key.data())) {
-		return dhClientParamsSend();
+	auto randomSeed = std::array<gsl::byte, ModExpFirst::kRandomPowerSize>();
+	openssl::FillRandom(randomSeed);
+	auto g_b_data = CreateModExp(_authKeyData->g, _authKeyStrings->dh_prime, randomSeed);
+	if (g_b_data.modexp.empty()) {
+		LOG(("AuthKey Error: could not generate good g_b."));
+		return restart();
 	}
 
+	auto computedAuthKey = CreateAuthKey(_authKeyStrings->g_a, g_b_data.randomPower, _authKeyStrings->dh_prime);
+	if (computedAuthKey.empty()) {
+		LOG(("AuthKey Error: could not generate auth_key."));
+		return restart();
+	}
+	AuthKey::FillData(_authKeyStrings->auth_key, computedAuthKey);
+
 	// count auth_key hashes - parts of sha1(auth_key)
 	auto auth_key_sha = hashSha1(_authKeyStrings->auth_key.data(), _authKeyStrings->auth_key.size());
 	memcpy(&_authKeyData->auth_key_aux_hash, auth_key_sha.data(), 8);
 	memcpy(&_authKeyData->auth_key_hash, auth_key_sha.data() + 12, 8);
 
-	auto client_dh_inner = MTP_client_DH_inner_data(_authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->retry_id, MTP_string(std::move(g_b_string)));
+	auto client_dh_inner = MTP_client_DH_inner_data(_authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->retry_id, MTP_bytes(g_b_data.modexp));
 
 	auto sdhEncString = encryptClientDHInner(client_dh_inner);
 
@@ -2826,28 +2728,28 @@ void ConnectionPrivate::authKeyCreated() {
 }
 
 void ConnectionPrivate::clearAuthKeyData() {
-	auto zeroMemory = [](void *data, int size) {
-#ifdef Q_OS_WIN
-		SecureZeroMemory(data, size);
+	auto zeroMemory = [](base::byte_span bytes) {
+#ifdef Q_OS_WIN2
+		SecureZeroMemory(bytes.data(), bytes.size());
 #else // Q_OS_WIN
-		auto end = static_cast<char*>(data) + size;
-		for (volatile auto p = static_cast<volatile char*>(data); p != end; ++p) {
+		auto end = reinterpret_cast<char*>(bytes.data()) + bytes.size();
+		for (volatile auto p = reinterpret_cast<volatile char*>(bytes.data()); p != end; ++p) {
 			*p = 0;
 		}
 #endif // Q_OS_WIN
 	};
 	if (_authKeyData) {
-		zeroMemory(_authKeyData.get(), sizeof(AuthKeyCreateData));
+		zeroMemory(gsl::make_span(reinterpret_cast<gsl::byte*>(_authKeyData.get()), sizeof(AuthKeyCreateData)));
 		_authKeyData.reset();
 	}
 	if (_authKeyStrings) {
-		if (!_authKeyStrings->dh_prime.isEmpty()) {
-			zeroMemory(_authKeyStrings->dh_prime.data(), _authKeyStrings->dh_prime.size());
+		if (!_authKeyStrings->dh_prime.empty()) {
+			zeroMemory(_authKeyStrings->dh_prime);
 		}
-		if (!_authKeyStrings->g_a.isEmpty()) {
-			zeroMemory(_authKeyStrings->g_a.data(), _authKeyStrings->g_a.size());
+		if (!_authKeyStrings->g_a.empty()) {
+			zeroMemory(_authKeyStrings->g_a);
 		}
-		zeroMemory(_authKeyStrings->auth_key.data(), _authKeyStrings->auth_key.size());
+		zeroMemory(_authKeyStrings->auth_key);
 		_authKeyStrings.reset();
 	}
 }
@@ -3082,8 +2984,16 @@ void ConnectionPrivate::stop() {
 
 } // namespace internal
 
-bool IsPrimeAndGood(const QByteArray &data, int g) {
-	return MTP::internal::BigNumPrimeTest().isPrimeAndGood(data, g);
+bool IsPrimeAndGood(base::const_byte_span primeBytes, int g) {
+	return internal::IsPrimeAndGood(primeBytes, g);
+}
+
+ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed) {
+	return internal::CreateModExp(g, primeBytes, randomSeed);
+}
+
+std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes) {
+	return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes);
 }
 
 } // namespace MTP
diff --git a/Telegram/SourceFiles/mtproto/connection.h b/Telegram/SourceFiles/mtproto/connection.h
index a1354fd92..8ae4aed3d 100644
--- a/Telegram/SourceFiles/mtproto/connection.h
+++ b/Telegram/SourceFiles/mtproto/connection.h
@@ -29,7 +29,15 @@ namespace MTP {
 
 class Instance;
 
-bool IsPrimeAndGood(const QByteArray &data, int g);
+bool IsPrimeAndGood(base::const_byte_span primeBytes, int g);
+struct ModExpFirst {
+	static constexpr auto kRandomPowerSize = 256;
+
+	std::vector<gsl::byte> modexp;
+	std::array<gsl::byte, kRandomPowerSize> randomPower;
+};
+ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed);
+std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes);
 
 namespace internal {
 
@@ -185,7 +193,7 @@ private:
 
 	bool setState(int32 state, int32 ifState = Connection::UpdateAlways);
 
-	std::string encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key);
+	base::byte_vector encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key);
 	std::string encryptClientDHInner(const MTPClient_DH_Inner_Data &data);
 
 	Instance *_instance = nullptr;
@@ -272,9 +280,9 @@ private:
 		uint32 msgs_sent = 0;
 	};
 	struct AuthKeyCreateStrings {
-		QByteArray dh_prime;
-		QByteArray g_a;
-		AuthKey::Data auth_key = { { 0 } };
+		std::vector<gsl::byte> dh_prime;
+		std::vector<gsl::byte> g_a;
+		AuthKey::Data auth_key = { { gsl::byte{} } };
 	};
 	std::unique_ptr<AuthKeyCreateData> _authKeyData;
 	std::unique_ptr<AuthKeyCreateStrings> _authKeyStrings;
diff --git a/Telegram/SourceFiles/mtproto/dc_options.cpp b/Telegram/SourceFiles/mtproto/dc_options.cpp
index dd6b6fb36..d56a8ce62 100644
--- a/Telegram/SourceFiles/mtproto/dc_options.cpp
+++ b/Telegram/SourceFiles/mtproto/dc_options.cpp
@@ -229,15 +229,15 @@ QByteArray DcOptions::serialize() const {
 	}
 	struct SerializedPublicKey {
 		DcId dcId;
-		QByteArray n;
-		QByteArray e;
+		base::byte_vector n;
+		base::byte_vector e;
 	};
 	std::vector<SerializedPublicKey> publicKeys;
 	publicKeys.reserve(count);
 	for (auto &keysInDc : _cdnPublicKeys) {
 		for (auto &entry : keysInDc.second) {
 			publicKeys.push_back({ keysInDc.first, entry.second.getN(), entry.second.getE() });
-			size += sizeof(qint32) + Serialize::bytearraySize(publicKeys.back().n) + Serialize::bytearraySize(publicKeys.back().e);
+			size += sizeof(qint32) + Serialize::bytesSize(publicKeys.back().n) + Serialize::bytesSize(publicKeys.back().e);
 		}
 	}
 
@@ -260,7 +260,7 @@ QByteArray DcOptions::serialize() const {
 		}
 		stream << qint32(publicKeys.size());
 		for (auto &key : publicKeys) {
-			stream << qint32(key.dcId) << key.n << key.e;
+			stream << qint32(key.dcId) << Serialize::bytes(key.n) << Serialize::bytes(key.e);
 		}
 	}
 	return result;
@@ -309,8 +309,8 @@ void DcOptions::constructFromSerialized(const QByteArray &serialized) {
 
 		for (auto i = 0; i != count; ++i) {
 			qint32 dcId = 0;
-			QByteArray n, e;
-			stream >> dcId >> n >> e;
+			base::byte_vector n, e;
+			stream >> dcId >> Serialize::bytes(n) >> Serialize::bytes(e);
 			if (stream.status() != QDataStream::Ok) {
 				LOG(("MTP Error: Bad data for CDN config inside DcOptions::constructFromSerialized()"));
 				return;
diff --git a/Telegram/SourceFiles/mtproto/rsa_public_key.cpp b/Telegram/SourceFiles/mtproto/rsa_public_key.cpp
index c0b667cd8..a512d7ad2 100644
--- a/Telegram/SourceFiles/mtproto/rsa_public_key.cpp
+++ b/Telegram/SourceFiles/mtproto/rsa_public_key.cpp
@@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #include "mtproto/rsa_public_key.h"
 
+#include "base/openssl_help.h"
 #include <openssl/rsa.h>
 #include <openssl/pem.h>
 #include <openssl/bio.h>
@@ -37,10 +38,10 @@ public:
 			computeFingerprint();
 		}
 	}
-	Private(const QByteArray &n, const QByteArray &e) : _rsa(RSA_new()) {
+	Private(base::const_byte_span nBytes, base::const_byte_span eBytes) : _rsa(RSA_new()) {
 		if (_rsa) {
-			_rsa->n = BN_bin2bn((const uchar*)n.data(), n.size(), _rsa->n);
-			_rsa->e = BN_bin2bn((const uchar*)e.data(), e.size(), _rsa->e);
+			_rsa->n = BN_dup(openssl::BigNum(nBytes).raw());
+			_rsa->e = BN_dup(openssl::BigNum(eBytes).raw());
 			if (!_rsa->n || !_rsa->e) {
 				RSA_free(base::take(_rsa));
 			} else {
@@ -48,11 +49,11 @@ public:
 			}
 		}
 	}
-	QByteArray getN() const {
+	base::byte_vector getN() const {
 		Expects(isValid());
 		return toBytes(_rsa->n);
 	}
-	QByteArray getE() const {
+	base::byte_vector getE() const {
 		Expects(isValid());
 		return toBytes(_rsa->e);
 	}
@@ -62,17 +63,18 @@ public:
 	bool isValid() const {
 		return _rsa != nullptr;
 	}
-	bool encrypt(const void *data, string &result) const {
+	base::byte_vector encrypt(base::const_byte_span data) const {
 		Expects(isValid());
 
-		result.resize(256);
-		auto res = RSA_public_encrypt(256, reinterpret_cast<const unsigned char*>(data), reinterpret_cast<uchar*>(&result[0]), _rsa, RSA_NO_PADDING);
-		if (res != 256) {
+		constexpr auto kEncryptSize = 256;
+		auto result = base::byte_vector(kEncryptSize, gsl::byte {});
+		auto res = RSA_public_encrypt(kEncryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING);
+		if (res != kEncryptSize) {
 			ERR_load_crypto_strings();
 			LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
-			return false;
+			return base::byte_vector();
 		}
-		return true;
+		return result;
 	}
 	~Private() {
 		RSA_free(_rsa);
@@ -89,10 +91,10 @@ private:
 		uchar sha1Buffer[20];
 		_fingerprint = *(uint64*)(hashSha1(&string[0], string.size() * sizeof(mtpPrime), sha1Buffer) + 3);
 	}
-	static QByteArray toBytes(BIGNUM *number) {
-		auto size = static_cast<int>(BN_num_bytes(number));
-		auto result = QByteArray(size, 0);
-		BN_bn2bin(number, reinterpret_cast<uchar*>(result.data()));
+	static base::byte_vector toBytes(BIGNUM *number) {
+		auto size = BN_num_bytes(number);
+		auto result = base::byte_vector(size, gsl::byte {});
+		BN_bn2bin(number, reinterpret_cast<unsigned char*>(result.data()));
 		return result;
 	}
 
@@ -104,7 +106,7 @@ private:
 RSAPublicKey::RSAPublicKey(base::const_byte_span key) : _private(std::make_shared<Private>(key)) {
 }
 
-RSAPublicKey::RSAPublicKey(const QByteArray &n, const QByteArray &e) : _private(std::make_shared<Private>(n, e)) {
+RSAPublicKey::RSAPublicKey(base::const_byte_span nBytes, base::const_byte_span eBytes) : _private(std::make_shared<Private>(nBytes, eBytes)) {
 }
 
 bool RSAPublicKey::isValid() const {
@@ -116,19 +118,19 @@ uint64 RSAPublicKey::getFingerPrint() const {
 	return _private->getFingerPrint();
 }
 
-QByteArray RSAPublicKey::getN() const {
+base::byte_vector RSAPublicKey::getN() const {
 	Expects(isValid());
 	return _private->getN();
 }
 
-QByteArray RSAPublicKey::getE() const {
+base::byte_vector RSAPublicKey::getE() const {
 	Expects(isValid());
 	return _private->getE();
 }
 
-bool RSAPublicKey::encrypt(const void *data, string &result) const {
+base::byte_vector RSAPublicKey::encrypt(base::const_byte_span data) const {
 	Expects(isValid());
-	return _private->encrypt(data, result);
+	return _private->encrypt(data);
 }
 
 } // namespace internal
diff --git a/Telegram/SourceFiles/mtproto/rsa_public_key.h b/Telegram/SourceFiles/mtproto/rsa_public_key.h
index e5adc183a..7b66e9df7 100644
--- a/Telegram/SourceFiles/mtproto/rsa_public_key.h
+++ b/Telegram/SourceFiles/mtproto/rsa_public_key.h
@@ -28,8 +28,8 @@ class RSAPublicKey final {
 public:
 	// key in RSAPublicKey "-----BEGIN RSA PUBLIC KEY----- ..." format
 	RSAPublicKey() = default;
-	RSAPublicKey(base::const_byte_span key);
-	RSAPublicKey(const QByteArray &n, const QByteArray &e);
+	explicit RSAPublicKey(base::const_byte_span key);
+	RSAPublicKey(base::const_byte_span nBytes, base::const_byte_span eBytes);
 	RSAPublicKey(RSAPublicKey &&other) = default;
 	RSAPublicKey(const RSAPublicKey &other) = default;
 	RSAPublicKey &operator=(RSAPublicKey &&other) = default;
@@ -37,11 +37,11 @@ public:
 
 	bool isValid() const;
 	uint64 getFingerPrint() const;
-	QByteArray getN() const;
-	QByteArray getE() const;
+	base::byte_vector getN() const;
+	base::byte_vector getE() const;
 
 	// data has exactly 256 chars to be encrypted
-	bool encrypt(const void *data, std::string &result) const;
+	base::byte_vector encrypt(base::const_byte_span data) const;
 
 private:
 	class Private;
diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp
index ed15fbe95..3fa80eeea 100644
--- a/Telegram/SourceFiles/storage/localstorage.cpp
+++ b/Telegram/SourceFiles/storage/localstorage.cpp
@@ -146,7 +146,7 @@ auto PassKey = MTP::AuthKeyPtr();
 auto LocalKey = MTP::AuthKeyPtr();
 
 void createLocalKey(const QByteArray &pass, QByteArray *salt, MTP::AuthKeyPtr *result) {
-	auto key = MTP::AuthKey::Data { { 0 } };
+	auto key = MTP::AuthKey::Data { { gsl::byte{} } };
 	auto iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
 	auto newSalt = QByteArray();
 	if (!salt) {
@@ -931,9 +931,8 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
 
 	case dbiKey: {
 		qint32 dcId;
-		auto key = MTP::AuthKey::Data { { 0 } };
 		stream >> dcId;
-		stream.readRawData(key.data(), key.size());
+		auto key = Serialize::read<MTP::AuthKey::Data>(stream);
 		if (!_checkStreamStatus(stream)) return false;
 
 		Messenger::Instance().setMtpKey(dcId, key);
@@ -1900,8 +1899,8 @@ ReadMapState _readMap(const QByteArray &pass) {
 		LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password..."));
 		return ReadMapPassNeeded;
 	}
-	auto key = MTP::AuthKey::Data { { 0 } };
-	if (keyData.stream.readRawData(key.data(), key.size()) != key.size() || !keyData.stream.atEnd()) {
+	auto key = Serialize::read<MTP::AuthKey::Data>(keyData.stream);
+	if (keyData.stream.status() != QDataStream::Ok || !keyData.stream.atEnd()) {
 		LOG(("App Error: could not read pass-protected key from map file"));
 		return ReadMapFailed;
 	}
diff --git a/Telegram/SourceFiles/storage/serialize_common.h b/Telegram/SourceFiles/storage/serialize_common.h
index 725a8b4eb..26296609c 100644
--- a/Telegram/SourceFiles/storage/serialize_common.h
+++ b/Telegram/SourceFiles/storage/serialize_common.h
@@ -32,6 +32,67 @@ inline int bytearraySize(const QByteArray &arr) {
 	return sizeof(quint32) + arr.size();
 }
 
+inline int bytesSize(base::const_byte_span bytes) {
+	return sizeof(quint32) + bytes.size();
+}
+
+struct ReadBytesVectorWrap {
+	base::byte_vector &bytes;
+};
+
+inline ReadBytesVectorWrap bytes(base::byte_vector &bytes) {
+	return ReadBytesVectorWrap { bytes };
+}
+
+// Compatible with QDataStream &operator>>(QDataStream &, QByteArray &);
+inline QDataStream &operator>>(QDataStream &stream, ReadBytesVectorWrap data) {
+	auto &bytes = data.bytes;
+	bytes.clear();
+	quint32 len;
+	stream >> len;
+	if (stream.status() != QDataStream::Ok || len == 0xFFFFFFFF) {
+		return stream;
+	}
+
+	constexpr auto kStep = quint32(1024 * 1024);
+	for (auto allocated = quint32(0); allocated < len;) {
+		auto blockSize = qMin(kStep, len - allocated);
+		bytes.resize(allocated + blockSize);
+		if (stream.readRawData(reinterpret_cast<char*>(bytes.data()) + allocated, blockSize) != blockSize) {
+			bytes.clear();
+			stream.setStatus(QDataStream::ReadPastEnd);
+			return stream;
+		}
+		allocated += blockSize;
+	}
+
+	return stream;
+}
+
+struct WriteBytesWrap {
+	base::const_byte_span bytes;
+};
+
+inline WriteBytesWrap bytes(base::const_byte_span bytes) {
+	return WriteBytesWrap { bytes };
+}
+
+inline QDataStream &operator<<(QDataStream &stream, WriteBytesWrap data) {
+	auto bytes = data.bytes;
+	if (bytes.empty()) {
+		stream << quint32(0xFFFFFFFF);
+	} else {
+		auto size = quint32(bytes.size());
+		stream << size;
+		stream.writeRawData(reinterpret_cast<const char*>(bytes.data()), size);
+	}
+	return stream;
+}
+
+inline QDataStream &operator<<(QDataStream &stream, ReadBytesVectorWrap data) {
+	return stream << WriteBytesWrap { data.bytes };
+}
+
 inline int dateTimeSize() {
 	return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8));
 }
@@ -50,7 +111,7 @@ inline T read(QDataStream &stream) {
 template <>
 inline MTP::AuthKey::Data read<MTP::AuthKey::Data>(QDataStream &stream) {
 	auto result = MTP::AuthKey::Data();
-	stream.readRawData(result.data(), result.size());
+	stream.readRawData(reinterpret_cast<char*>(result.data()), result.size());
 	return result;
 }