diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl
index 92b32b113..b570aa79e 100644
--- a/Telegram/Resources/scheme.tl
+++ b/Telegram/Resources/scheme.tl
@@ -322,6 +322,7 @@ inputReportReasonSpam#58dbcab8 = ReportReason;
 inputReportReasonViolence#1e22c78d = ReportReason;
 inputReportReasonPornography#2e59d922 = ReportReason;
 inputReportReasonOther#e1746d0a text:string = ReportReason;
+inputReportReasonCopyright#9b89f93a = ReportReason;
 
 userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull;
 
@@ -473,7 +474,7 @@ config#3213dbba flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:fla
 
 nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
 
-help.appUpdate#8987f311 id:int critical:Bool url:string text:string = help.AppUpdate;
+help.appUpdate#1da7158f flags:# popup:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string = help.AppUpdate;
 help.noAppUpdate#c45a6536 = help.AppUpdate;
 
 help.inviteText#18cb9f78 message:string = help.InviteText;
@@ -589,12 +590,11 @@ authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string s
 
 account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
 
-account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes email_unconfirmed_pattern:string = account.Password;
-account.password#ca39b447 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true current_salt:bytes new_salt:bytes new_secure_salt:bytes secure_random:bytes hint:string email_unconfirmed_pattern:string = account.Password;
+account.password#68873ba5 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password;
 
-account.passwordSettings#7bd9c3f1 email:string secure_salt:bytes secure_secret:bytes secure_secret_id:long = account.PasswordSettings;
+account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
 
-account.passwordInputSettings#21ffa60d flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_salt:flags.2?bytes new_secure_secret:flags.2?bytes new_secure_secret_id:flags.2?long = account.PasswordInputSettings;
+account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings;
 
 auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
 
@@ -1021,6 +1021,15 @@ savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date:
 
 account.takeout#4dba4501 id:long = account.Takeout;
 
+passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo;
+passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000#b6425eaa salt1:bytes salt2:bytes = PasswordKdfAlgo;
+
+securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;
+
+secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1211,6 +1220,7 @@ messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:stri
 messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
 messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
 messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
+messages.clearAllDrafts#7e58ee9c = Bool;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1232,7 +1242,7 @@ upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector<Fil
 
 help.getConfig#c4f9186b = Config;
 help.getNearestDc#1fb33026 = NearestDc;
-help.getAppUpdate#ae2de196 = help.AppUpdate;
+help.getAppUpdate#522d5a7d source:string = help.AppUpdate;
 help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
 help.getInviteText#4d392343 = help.InviteText;
 help.getSupport#9cdf08cd = help.Support;
@@ -1308,4 +1318,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangP
 langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
 langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
 
-// LAYER 82
+// LAYER 83
diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h
index 132ecb7b1..3ca53719e 100644
--- a/Telegram/SourceFiles/base/openssl_help.h
+++ b/Telegram/SourceFiles/base/openssl_help.h
@@ -8,13 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "base/bytes.h"
+#include "base/algorithm.h"
+#include "core/basic_types.h"
 
 extern "C" {
 #include <openssl/bn.h>
 #include <openssl/sha.h>
 #include <openssl/rand.h>
 #include <openssl/aes.h>
+#include <openssl/modes.h>
 #include <openssl/crypto.h>
+#include <openssl/evp.h>
 } // extern "C"
 
 namespace openssl {
@@ -212,37 +216,144 @@ inline BigNum operator-(const BigNum &a, const BigNum &b) {
 	return result;
 }
 
-inline bytes::vector Sha512(bytes::const_span data) {
-	auto result = bytes::vector(SHA512_DIGEST_LENGTH);
-	SHA512(
+namespace details {
+
+template <typename Context, typename Method, typename Arg>
+inline void ShaUpdate(Context context, Method method, Arg &&arg) {
+	const auto span = bytes::make_span(arg);
+	method(context, span.data(), span.size());
+}
+
+template <typename Context, typename Method, typename Arg, typename ...Args>
+inline void ShaUpdate(Context context, Method method, Arg &&arg, Args &&...args) {
+	const auto span = bytes::make_span(arg);
+	method(context, span.data(), span.size());
+	ShaUpdate(context, method, args...);
+}
+
+template <size_type Size, typename Method>
+inline bytes::vector Sha(Method method, bytes::const_span data) {
+	auto result = bytes::vector(Size);
+	method(
 		reinterpret_cast<const unsigned char*>(data.data()),
 		data.size(),
 		reinterpret_cast<unsigned char*>(result.data()));
 	return result;
 }
 
+template <
+	size_type Size,
+	typename Context,
+	typename Init,
+	typename Update,
+	typename Finalize,
+	typename ...Args,
+	typename = std::enable_if_t<(sizeof...(Args) > 1)>>
+bytes::vector Sha(
+		Context context,
+		Init init,
+		Update update,
+		Finalize finalize,
+		Args &&...args) {
+	auto result = bytes::vector(Size);
+
+	init(&context);
+	ShaUpdate(&context, update, args...);
+	finalize(reinterpret_cast<unsigned char*>(result.data()), &context);
+
+	return result;
+}
+
+template <
+	size_type Size,
+	typename Evp>
+bytes::vector Pbkdf2(
+		bytes::const_span password,
+		bytes::const_span salt,
+		int iterations,
+		Evp evp) {
+	auto result = bytes::vector(Size);
+	PKCS5_PBKDF2_HMAC(
+		reinterpret_cast<const char*>(password.data()),
+		password.size(),
+		reinterpret_cast<const unsigned char*>(salt.data()),
+		salt.size(),
+		iterations,
+		evp,
+		result.size(),
+		reinterpret_cast<unsigned char*>(result.data()));
+	return result;
+}
+
+} // namespace details
+
+constexpr auto kSha1Size = size_type(SHA_DIGEST_LENGTH);
+constexpr auto kSha256Size = size_type(SHA256_DIGEST_LENGTH);
+constexpr auto kSha512Size = size_type(SHA512_DIGEST_LENGTH);
+
+inline bytes::vector Sha1(bytes::const_span data) {
+	return details::Sha<kSha1Size>(SHA1, data);
+}
+
+template <
+	typename ...Args,
+	typename = std::enable_if_t<(sizeof...(Args) > 1)>>
+inline bytes::vector Sha1(Args &&...args) {
+	return details::Sha<kSha1Size>(
+		SHA_CTX(),
+		SHA_Init,
+		SHA_Update,
+		SHA_Final,
+		args...);
+}
+
 inline bytes::vector Sha256(bytes::const_span data) {
-	auto result = bytes::vector(SHA256_DIGEST_LENGTH);
-	SHA256(
-		reinterpret_cast<const unsigned char*>(data.data()),
-		data.size(),
-		reinterpret_cast<unsigned char*>(result.data()));
-	return result;
+	return details::Sha<kSha256Size>(SHA256, data);
 }
 
-inline bytes::vector Sha1(bytes::const_span data) {
-	auto result = bytes::vector(SHA_DIGEST_LENGTH);
-	SHA1(
-		reinterpret_cast<const unsigned char*>(data.data()),
-		data.size(),
-		reinterpret_cast<unsigned char*>(result.data()));
-	return result;
+template <
+	typename ...Args,
+	typename = std::enable_if_t<(sizeof...(Args) > 1)>>
+inline bytes::vector Sha256(Args &&...args) {
+	return details::Sha<kSha256Size>(
+		SHA256_CTX(),
+		SHA256_Init,
+		SHA256_Update,
+		SHA256_Final,
+		args...);
+}
+
+inline bytes::vector Sha512(bytes::const_span data) {
+	return details::Sha<kSha512Size>(SHA512, data);
+}
+
+template <
+	typename ...Args,
+	typename = std::enable_if_t<(sizeof...(Args) > 1)>>
+inline bytes::vector Sha512(Args &&...args) {
+	return details::Sha<kSha512Size>(
+		SHA512_CTX(),
+		SHA512_Init,
+		SHA512_Update,
+		SHA512_Final,
+		args...);
 }
 
 inline void AddRandomSeed(bytes::const_span data) {
 	RAND_seed(data.data(), data.size());
 }
 
+inline bytes::vector Pbkdf2Sha512(
+		bytes::const_span password,
+		bytes::const_span salt,
+		int iterations) {
+	return details::Pbkdf2<kSha512Size>(
+		password,
+		salt,
+		iterations,
+		EVP_sha512());
+}
+
 } // namespace openssl
 
 namespace bytes {
diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp
index 0c28fbd64..64c15bebf 100644
--- a/Telegram/SourceFiles/boxes/passcode_box.cpp
+++ b/Telegram/SourceFiles/boxes/passcode_box.cpp
@@ -30,27 +30,29 @@ PasscodeBox::PasscodeBox(QWidget*, bool turningOff)
 
 PasscodeBox::PasscodeBox(
 	QWidget*,
-	const QByteArray &newSalt,
-	const QByteArray &curSalt,
+	const Core::CloudPasswordAlgo &curAlgo,
+	const Core::CloudPasswordAlgo &newAlgo,
 	bool hasRecovery,
 	bool notEmptyPassport,
 	const QString &hint,
-	const QByteArray &newSecureSecretSalt,
+	const Core::SecureSecretAlgo &newSecureSecretAlgo,
 	bool turningOff)
 : _turningOff(turningOff)
 , _cloudPwd(true)
-, _newSalt(newSalt)
-, _curSalt(curSalt)
-, _newSecureSecretSalt(newSecureSecretSalt)
+, _curAlgo(curAlgo)
+, _newAlgo(newAlgo)
+, _newSecureSecretAlgo(newSecureSecretAlgo)
 , _hasRecovery(hasRecovery)
 , _notEmptyPassport(notEmptyPassport)
 , _about(st::boxWidth - st::boxPadding.left() * 1.5)
 , _oldPasscode(this, st::defaultInputField, langFactory(lng_cloud_password_enter_old))
-, _newPasscode(this, st::defaultInputField, langFactory(curSalt.isEmpty() ? lng_cloud_password_enter_first : lng_cloud_password_enter_new))
+, _newPasscode(this, st::defaultInputField, langFactory(curAlgo ? lng_cloud_password_enter_new : lng_cloud_password_enter_first))
 , _reenterPasscode(this, st::defaultInputField, langFactory(lng_cloud_password_confirm_new))
-, _passwordHint(this, st::defaultInputField, langFactory(curSalt.isEmpty() ? lng_cloud_password_hint : lng_cloud_password_change_hint))
+, _passwordHint(this, st::defaultInputField, langFactory(curAlgo ? lng_cloud_password_change_hint : lng_cloud_password_hint))
 , _recoverEmail(this, st::defaultInputField, langFactory(lng_cloud_password_email))
 , _recover(this, lang(lng_signin_recover)) {
+	Expects(!_turningOff || curAlgo.has_value());
+
 	if (!hint.isEmpty()) _hintText.setText(st::passcodeTextStyle, lng_signin_hint(lt_password_hint, hint));
 }
 
@@ -62,6 +64,10 @@ rpl::producer<> PasscodeBox::passwordReloadNeeded() const {
 	return _passwordReloadNeeded.events();
 }
 
+bool PasscodeBox::currentlyHave() const {
+	return _cloudPwd ? _curAlgo.has_value() : Global::LocalPasscode();
+}
+
 void PasscodeBox::prepare() {
 	addButton(langFactory(_turningOff ? lng_passcode_remove_button : lng_settings_save), [=] { save(); });
 	addButton(langFactory(lng_cancel), [=] { closeBox(); });
@@ -73,8 +79,7 @@ void PasscodeBox::prepare() {
 		setTitle(langFactory(_cloudPwd ? lng_cloud_password_remove : lng_passcode_remove));
 		setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
 	} else {
-		auto has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
-		if (has) {
+		if (currentlyHave()) {
 			_oldPasscode->show();
 			setTitle(langFactory(_cloudPwd ? lng_cloud_password_change : lng_passcode_change));
 			setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
@@ -100,17 +105,17 @@ void PasscodeBox::prepare() {
 
 	_recover->addClickHandler([=] { recoverByEmail(); });
 
-	bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
+	const auto has = currentlyHave();
 	_oldPasscode->setVisible(_turningOff || has);
 	_recover->setVisible((_turningOff || has) && _cloudPwd && _hasRecovery);
 	_newPasscode->setVisible(!_turningOff);
 	_reenterPasscode->setVisible(!_turningOff);
 	_passwordHint->setVisible(!_turningOff && _cloudPwd);
-	_recoverEmail->setVisible(!_turningOff && _cloudPwd && _curSalt.isEmpty());
+	_recoverEmail->setVisible(!_turningOff && _cloudPwd && !has);
 }
 
 void PasscodeBox::submit() {
-	bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
+	const auto has = currentlyHave();
 	if (_oldPasscode->hasFocus()) {
 		if (_turningOff) {
 			save();
@@ -177,7 +182,7 @@ void PasscodeBox::paintEvent(QPaintEvent *e) {
 void PasscodeBox::resizeEvent(QResizeEvent *e) {
 	BoxContent::resizeEvent(e);
 
-	bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
+	const auto has = currentlyHave();
 	int32 w = st::boxWidth - st::boxPadding.left() - st::boxPadding.right();
 	_oldPasscode->resize(w, _oldPasscode->height());
 	_oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top());
@@ -291,7 +296,7 @@ void PasscodeBox::save(bool force) {
 	if (_setRequest) return;
 
 	QString old = _oldPasscode->text(), pwd = _newPasscode->text(), conf = _reenterPasscode->text();
-	bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
+	const auto has = currentlyHave();
 	if (!_cloudPwd && (_turningOff || has)) {
 		if (!passcodeCanTry()) {
 			_oldError = lang(lng_flood_error);
@@ -387,14 +392,14 @@ void PasscodeBox::clearCloudPassword(const QString &oldPassword) {
 
 void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) {
 	const auto passwordUtf = oldPassword.toUtf8();
-	const auto oldPasswordData = (_curSalt + passwordUtf + _curSalt);
-	auto oldPasswordHash = QByteArray(32, Qt::Uninitialized);
-	hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data());
+	const auto oldPasswordHash = Core::ComputeCloudPasswordHash(
+		_curAlgo,
+		bytes::make_span(passwordUtf));
 	const auto newPasswordData = QByteArray();
 	const auto newPasswordHash = QByteArray();
 	const auto hint = QString();
 	const auto email = QString();
-	const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt
+	const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
 		| MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
 		| MTPDaccount_passwordInputSettings::Flag::f_hint
 		| MTPDaccount_passwordInputSettings::Flag::f_email;
@@ -402,13 +407,11 @@ void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) {
 		MTP_bytes(oldPasswordHash),
 		MTP_account_passwordInputSettings(
 			MTP_flags(flags),
-			MTP_bytes(_newSalt),
+			Core::PrepareCloudPasswordAlgo(_newAlgo),
 			MTP_bytes(newPasswordHash),
 			MTP_string(hint),
 			MTP_string(email),
-			MTPbytes(), // new_secure_salt
-			MTPbytes(), // new_secure_secret
-			MTPlong()) // new_secure_secret_id
+			MTPSecureSecretSettings())
 	)).done([=](const MTPBool &result) {
 		setPasswordDone({});
 	}).fail([=](const RPCError &error) {
@@ -418,14 +421,14 @@ void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) {
 
 void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
 	const auto newPasswordBytes = newPassword.toUtf8();
-	const auto newPasswordData = (_newSalt + newPasswordBytes + _newSalt);
-	auto newPasswordHash = QByteArray(32, Qt::Uninitialized);
-	hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data());
+	const auto newPasswordHash = Core::ComputeCloudPasswordHash(
+		_newAlgo,
+		bytes::make_span(newPasswordBytes));
 	const auto oldPasswordData = QByteArray();
 	const auto oldPasswordHash = QByteArray();
 	const auto hint = _passwordHint->getLastText();
 	const auto email = _recoverEmail->getLastText().trimmed();
-	const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt
+	const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
 		| MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
 		| MTPDaccount_passwordInputSettings::Flag::f_hint
 		| MTPDaccount_passwordInputSettings::Flag::f_email;
@@ -433,13 +436,11 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
 		MTP_bytes(oldPasswordHash),
 		MTP_account_passwordInputSettings(
 			MTP_flags(flags),
-			MTP_bytes(_newSalt),
+			Core::PrepareCloudPasswordAlgo(_newAlgo),
 			MTP_bytes(newPasswordHash),
 			MTP_string(hint),
 			MTP_string(email),
-			MTPbytes(), // new_secure_salt
-			MTPbytes(), // new_secure_secret
-			MTPlong()) // new_secure_secret_id
+			MTPSecureSecretSettings())
 	)).done([=](const MTPBool &result) {
 		setPasswordDone(newPasswordBytes);
 	}).fail([=](const RPCError &error) {
@@ -451,9 +452,9 @@ void PasscodeBox::changeCloudPassword(
 		const QString &oldPassword,
 		const QString &newPassword) {
 	const auto passwordUtf = oldPassword.toUtf8();
-	const auto oldPasswordData = (_curSalt + passwordUtf + _curSalt);
-	auto oldPasswordHash = QByteArray(32, Qt::Uninitialized);
-	hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data());
+	const auto oldPasswordHash = Core::ComputeCloudPasswordHash(
+		_curAlgo,
+		bytes::make_span(passwordUtf));
 	_setRequest = request(MTPaccount_GetPasswordSettings(
 		MTP_bytes(oldPasswordHash)
 	)).done([=](const MTPaccount_PasswordSettings &result) {
@@ -462,20 +463,23 @@ void PasscodeBox::changeCloudPassword(
 		Expects(result.type() == mtpc_account_passwordSettings);
 		const auto &data = result.c_account_passwordSettings();
 
-		if (data.vsecure_secret.v.isEmpty()) {
+		if (!data.has_secure_settings()) {
 			const auto empty = QByteArray();
 			sendChangeCloudPassword(oldPasswordHash, newPassword, empty);
 			return;
 		}
+		const auto &wrapped = data.vsecure_settings;
+		const auto &settings = wrapped.c_secureSecretSettings();
 		const auto secret = Passport::DecryptSecureSecret(
-			bytes::make_span(data.vsecure_secret.v),
-			Passport::CountPasswordHashForSecret(
-				bytes::make_span(data.vsecure_salt.v),
+			bytes::make_span(settings.vsecure_secret.v),
+			Core::ComputeSecureSecretHash(
+				Core::ParseSecureSecretAlgo(settings.vsecure_algo),
 				bytes::make_span(passwordUtf)));
 		if (secret.empty()) {
 			LOG(("API Error: Failed to decrypt secure secret."));
 			suggestSecretReset(oldPasswordHash, newPassword);
-		} else if (Passport::CountSecureSecretId(secret) != data.vsecure_secret_id.v) {
+		} else if (Passport::CountSecureSecretId(secret)
+				!= settings.vsecure_secret_id.v) {
 			LOG(("API Error: Wrong secure secret id."));
 			suggestSecretReset(oldPasswordHash, newPassword);
 		} else {
@@ -492,7 +496,7 @@ void PasscodeBox::changeCloudPassword(
 }
 
 void PasscodeBox::suggestSecretReset(
-		const QByteArray &oldPasswordHash,
+		const bytes::vector &oldPasswordHash,
 		const QString &newPassword) {
 	const auto box = std::make_shared<QPointer<BoxContent>>();
 	const auto resetSecretAndSave = [=] {
@@ -500,16 +504,15 @@ void PasscodeBox::suggestSecretReset(
 		_setRequest = request(MTPaccount_UpdatePasswordSettings(
 			MTP_bytes(oldPasswordHash),
 			MTP_account_passwordInputSettings(
-				MTP_flags(Flag::f_new_secure_salt
-					| Flag::f_new_secure_secret
-					| Flag::f_new_secure_secret_id),
-				MTPbytes(), // new_salt
+				MTP_flags(Flag::f_new_secure_settings),
+				MTPPasswordKdfAlgo(), // new_algo
 				MTPbytes(), // new_password_hash
 				MTPstring(), // hint
 				MTPstring(), // email
-				MTP_bytes(QByteArray()), // new_secure_salt
-				MTP_bytes(QByteArray()), // new_secure_secret
-				MTP_long(0)) // new_secure_secret_id
+				MTP_secureSecretSettings(
+					MTP_securePasswordKdfAlgoUnknown(), // secure_algo
+					MTP_bytes(QByteArray()), // secure_secret
+					MTP_long(0))) // secure_secret_id
 		)).done([=](const MTPBool &result) {
 			_setRequest = 0;
 			const auto empty = QByteArray();
@@ -528,42 +531,41 @@ void PasscodeBox::suggestSecretReset(
 }
 
 void PasscodeBox::sendChangeCloudPassword(
-		const QByteArray &oldPasswordHash,
+		const bytes::vector &oldPasswordHash,
 		const QString &newPassword,
 		const QByteArray &secureSecret) {
 	const auto newPasswordBytes = newPassword.toUtf8();
-	const auto newPasswordData = (_newSalt + newPasswordBytes + _newSalt);
-	auto newPasswordHash = QByteArray(32, Qt::Uninitialized);
-	hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data());
+	const auto newPasswordHash = Core::ComputeCloudPasswordHash(
+		_newAlgo,
+		bytes::make_span(newPasswordBytes));
 	const auto hint = _passwordHint->getLastText();
-	auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt
+	auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
 		| MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
 		| MTPDaccount_passwordInputSettings::Flag::f_hint;
 	auto newSecureSecret = bytes::vector();
 	auto newSecureSecretId = 0ULL;
 	if (!secureSecret.isEmpty()) {
-		flags |= MTPDaccount_passwordInputSettings::Flag::f_new_secure_salt
-			| MTPDaccount_passwordInputSettings::Flag::f_new_secure_secret
-			| MTPDaccount_passwordInputSettings::Flag::f_new_secure_secret_id;
+		flags |= MTPDaccount_passwordInputSettings::Flag::f_new_secure_settings;
 		newSecureSecretId = Passport::CountSecureSecretId(
 			bytes::make_span(secureSecret));
 		newSecureSecret = Passport::EncryptSecureSecret(
 			bytes::make_span(secureSecret),
-			Passport::CountPasswordHashForSecret(
-				bytes::make_span(_newSecureSecretSalt),
+			Core::ComputeSecureSecretHash(
+				_newSecureSecretAlgo,
 				bytes::make_span(newPasswordBytes)));
 	}
 	_setRequest = request(MTPaccount_UpdatePasswordSettings(
 		MTP_bytes(oldPasswordHash),
 		MTP_account_passwordInputSettings(
 			MTP_flags(flags),
-			MTP_bytes(_newSalt),
+			Core::PrepareCloudPasswordAlgo(_newAlgo),
 			MTP_bytes(newPasswordHash),
 			MTP_string(hint),
 			MTPstring(), // email is not changing
-			MTP_bytes(_newSecureSecretSalt),
-			MTP_bytes(newSecureSecret),
-			MTP_long(newSecureSecretId))
+			MTP_secureSecretSettings(
+				Core::PrepareSecureSecretAlgo(_newSecureSecretAlgo),
+				MTP_bytes(newSecureSecret),
+				MTP_long(newSecureSecretId)))
 	)).done([=](const MTPBool &result) {
 		setPasswordDone(newPasswordBytes);
 	}).fail([=](const RPCError &error) {
diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h
index 1ce793c2b..78f43d6d8 100644
--- a/Telegram/SourceFiles/boxes/passcode_box.h
+++ b/Telegram/SourceFiles/boxes/passcode_box.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "boxes/abstract_box.h"
 #include "mtproto/sender.h"
+#include "core/core_cloud_password.h"
 
 namespace Ui {
 class InputField;
@@ -21,12 +22,12 @@ public:
 	PasscodeBox(QWidget*, bool turningOff);
 	PasscodeBox(
 		QWidget*,
-		const QByteArray &newSalt,
-		const QByteArray &curSalt,
+		const Core::CloudPasswordAlgo &curAlgo,
+		const Core::CloudPasswordAlgo &newAlgo,
 		bool hasRecovery,
 		bool notEmptyPassport,
 		const QString &hint,
-		const QByteArray &newSecureSecretSalt,
+		const Core::SecureSecretAlgo &newSecureSecretAlgo,
 		bool turningOff = false);
 
 	rpl::producer<QByteArray> newPasswordSet() const;
@@ -49,6 +50,7 @@ private:
 	void badOldPasscode();
 	void recoverByEmail();
 	void recoverExpired();
+	bool currentlyHave() const;
 
 	void setPasswordDone(const QByteArray &newPasswordBytes);
 	bool setPasswordFail(const RPCError &error);
@@ -66,14 +68,14 @@ private:
 		const QString &oldPassword,
 		const QString &newPassword);
 	void sendChangeCloudPassword(
-		const QByteArray &oldPasswordHash,
+		const bytes::vector &oldPasswordHash,
 		const QString &newPassword,
 		const QByteArray &secureSecret);
 	void suggestSecretReset(
-		const QByteArray &oldPasswordHash,
+		const bytes::vector &oldPasswordHash,
 		const QString &newPassword);
 	void resetSecretAndChangePassword(
-		const QByteArray &oldPasswordHash,
+		const bytes::vector &oldPasswordHash,
 		const QString &newPassword);
 	void sendClearCloudPassword(const QString &oldPassword);
 
@@ -84,7 +86,9 @@ private:
 	bool _cloudPwd = false;
 	mtpRequestId _setRequest = 0;
 
-	QByteArray _newSalt, _curSalt, _newSecureSecretSalt;
+	Core::CloudPasswordAlgo _curAlgo;
+	Core::CloudPasswordAlgo _newAlgo;
+	Core::SecureSecretAlgo _newSecureSecretAlgo;
 	bool _hasRecovery = false;
 	bool _notEmptyPassport = false;
 	bool _skipEmailWarning = false;
diff --git a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py
index fd3923123..1cb7e6df7 100644
--- a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py
+++ b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py
@@ -55,7 +55,7 @@ addChildParentFlags('MTPDchannelForbidden', 'MTPDchannel');
 parentFlagsCheck = {};
 
 countedTypeIdExceptions = {};
-for i in range(77, 83):
+for i in range(77, 84):
   countedTypeIdExceptions[i] = {}
   countedTypeIdExceptions[i]['channel'] = True
 countedTypeIdExceptions['ipPortSecret'] = True
diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h
index 309ff41b4..4345bebfb 100644
--- a/Telegram/SourceFiles/core/basic_types.h
+++ b/Telegram/SourceFiles/core/basic_types.h
@@ -22,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace func = base::functors;
 
 using gsl::not_null;
+using index_type = gsl::index;
+using size_type = gsl::index;
 
 template <typename Signature>
 using Fn = std::function<Signature>;
diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp
new file mode 100644
index 000000000..b8da67f69
--- /dev/null
+++ b/Telegram/SourceFiles/core/core_cloud_password.cpp
@@ -0,0 +1,133 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "core/core_cloud_password.h"
+
+#include "base/openssl_help.h"
+
+namespace Core {
+namespace {
+
+constexpr auto kAdditionalSalt = size_type(8);
+
+bytes::vector ComputeHash(
+		base::none_type,
+		bytes::const_span password) {
+	Unexpected("Bad cloud password algorithm.");
+}
+
+bytes::vector ComputeHash(
+		const CloudPasswordAlgoPBKDF2 &algo,
+		bytes::const_span password) {
+	const auto hash1 = openssl::Sha256(algo.salt1, password, algo.salt1);
+	const auto hash2 = openssl::Sha256(algo.salt2, hash1, algo.salt2);
+	return openssl::Pbkdf2Sha512(
+		hash2,
+		bytes::make_span(algo.salt1),
+		algo.kIterations);
+}
+
+bytes::vector ComputeHash(
+		const SecureSecretAlgoSHA512 &algo,
+		bytes::const_span password) {
+	return openssl::Sha512(algo.salt, password, algo.salt);
+}
+
+bytes::vector ComputeHash(
+		const SecureSecretAlgoPBKDF2 &algo,
+		bytes::const_span password) {
+	return openssl::Pbkdf2Sha512(password, algo.salt, algo.kIterations);
+}
+
+} // namespace
+
+CloudPasswordAlgo ParseCloudPasswordAlgo(const MTPPasswordKdfAlgo &data) {
+	return data.match([](
+	const MTPDpasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000 &data) {
+		return CloudPasswordAlgo(CloudPasswordAlgoPBKDF2{
+			bytes::make_vector(data.vsalt1.v),
+			bytes::make_vector(data.vsalt2.v) });
+	}, [](const MTPDpasswordKdfAlgoUnknown &data) {
+		return CloudPasswordAlgo();
+	});
+}
+
+CloudPasswordAlgo ValidateNewCloudPasswordAlgo(CloudPasswordAlgo &&parsed) {
+	if (!parsed.is<CloudPasswordAlgoPBKDF2>()) {
+		return base::none;
+	}
+	auto &value = parsed.get_unchecked<CloudPasswordAlgoPBKDF2>();
+	const auto already = value.salt1.size();
+	value.salt1.resize(already + kAdditionalSalt);
+	bytes::set_random(bytes::make_span(value.salt1).subspan(already));
+	return std::move(parsed);
+}
+
+MTPPasswordKdfAlgo PrepareCloudPasswordAlgo(const CloudPasswordAlgo &data) {
+	return data.match([](const CloudPasswordAlgoPBKDF2 &data) {
+		return MTP_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000(
+			MTP_bytes(data.salt1),
+			MTP_bytes(data.salt2));
+	}, [](base::none_type) {
+		return MTP_passwordKdfAlgoUnknown();
+	});
+}
+
+bytes::vector ComputeCloudPasswordHash(
+		const CloudPasswordAlgo &algo,
+		bytes::const_span password) {
+	return algo.match([&](const auto &data) {
+		return ComputeHash(data, password);
+	});
+}
+
+SecureSecretAlgo ParseSecureSecretAlgo(
+		const MTPSecurePasswordKdfAlgo &data) {
+	return data.match([](
+	const MTPDsecurePasswordKdfAlgoPBKDF2HMACSHA512iter100000 &data) {
+		return SecureSecretAlgo(SecureSecretAlgoPBKDF2{
+			bytes::make_vector(data.vsalt.v) });
+	}, [](const MTPDsecurePasswordKdfAlgoSHA512 &data) {
+		return SecureSecretAlgo(SecureSecretAlgoSHA512{
+			bytes::make_vector(data.vsalt.v) });
+	}, [](const MTPDsecurePasswordKdfAlgoUnknown &data) {
+		return SecureSecretAlgo();
+	});
+}
+
+SecureSecretAlgo ValidateNewSecureSecretAlgo(SecureSecretAlgo &&parsed) {
+	if (!parsed.is<SecureSecretAlgoPBKDF2>()) {
+		return base::none;
+	}
+	auto &value = parsed.get_unchecked<SecureSecretAlgoPBKDF2>();
+	const auto already = value.salt.size();
+	value.salt.resize(already + kAdditionalSalt);
+	bytes::set_random(bytes::make_span(value.salt).subspan(already));
+	return std::move(parsed);
+}
+
+MTPSecurePasswordKdfAlgo PrepareSecureSecretAlgo(
+		const SecureSecretAlgo &data) {
+	return data.match([](const SecureSecretAlgoPBKDF2 &data) {
+		return MTP_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(
+			MTP_bytes(data.salt));
+	}, [](const SecureSecretAlgoSHA512 &data) {
+		return MTP_securePasswordKdfAlgoSHA512(MTP_bytes(data.salt));
+	}, [](base::none_type) {
+		return MTP_securePasswordKdfAlgoUnknown();
+	});
+}
+
+bytes::vector ComputeSecureSecretHash(
+		const SecureSecretAlgo &algo,
+		bytes::const_span password) {
+	return algo.match([&](const auto &data) {
+		return ComputeHash(data, password);
+	});
+}
+
+} // namespace Core
\ No newline at end of file
diff --git a/Telegram/SourceFiles/core/core_cloud_password.h b/Telegram/SourceFiles/core/core_cloud_password.h
new file mode 100644
index 000000000..6856eeff1
--- /dev/null
+++ b/Telegram/SourceFiles/core/core_cloud_password.h
@@ -0,0 +1,73 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/bytes.h"
+
+namespace Core {
+
+struct CloudPasswordAlgoPBKDF2 {
+	static constexpr auto kIterations = 100000;
+
+	bytes::vector salt1;
+	bytes::vector salt2;
+};
+
+inline bool operator==(
+		const CloudPasswordAlgoPBKDF2 &a,
+		const CloudPasswordAlgoPBKDF2 &b) {
+	return (a.salt1 == b.salt1) && (a.salt2 == b.salt2);
+}
+
+using CloudPasswordAlgo = base::optional_variant<CloudPasswordAlgoPBKDF2>;
+
+CloudPasswordAlgo ParseCloudPasswordAlgo(const MTPPasswordKdfAlgo &data);
+CloudPasswordAlgo ValidateNewCloudPasswordAlgo(CloudPasswordAlgo &&parsed);
+MTPPasswordKdfAlgo PrepareCloudPasswordAlgo(const CloudPasswordAlgo &data);
+
+bytes::vector ComputeCloudPasswordHash(
+	const CloudPasswordAlgo &algo,
+	bytes::const_span password);
+
+struct SecureSecretAlgoSHA512 {
+	bytes::vector salt;
+};
+
+inline bool operator==(
+		const SecureSecretAlgoSHA512 &a,
+		const SecureSecretAlgoSHA512 &b) {
+	return (a.salt == b.salt);
+}
+
+struct SecureSecretAlgoPBKDF2 {
+	static constexpr auto kIterations = 100000;
+
+	bytes::vector salt;
+};
+
+inline bool operator==(
+		const SecureSecretAlgoPBKDF2 &a,
+		const SecureSecretAlgoPBKDF2 &b) {
+	return (a.salt == b.salt);
+}
+
+using SecureSecretAlgo = base::optional_variant<
+	SecureSecretAlgoSHA512,
+	SecureSecretAlgoPBKDF2>;
+
+SecureSecretAlgo ParseSecureSecretAlgo(
+	const MTPSecurePasswordKdfAlgo &data);
+SecureSecretAlgo ValidateNewSecureSecretAlgo(SecureSecretAlgo &&parsed);
+MTPSecurePasswordKdfAlgo PrepareSecureSecretAlgo(
+	const SecureSecretAlgo &data);
+
+bytes::vector ComputeSecureSecretHash(
+	const SecureSecretAlgo &algo,
+	bytes::const_span password);
+
+} // namespace Core
diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp
index b071d2a84..7b62e3322 100644
--- a/Telegram/SourceFiles/intro/introcode.cpp
+++ b/Telegram/SourceFiles/intro/introcode.cpp
@@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "application.h"
 #include "intro/introsignup.h"
 #include "intro/intropwdcheck.h"
+#include "core/update_checker.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
+#include "boxes/confirm_box.h"
 #include "styles/style_intro.h"
 
 namespace Intro {
@@ -286,22 +288,34 @@ void CodeWidget::callDone(const MTPauth_SentCode &v) {
 }
 
 void CodeWidget::gotPassword(const MTPaccount_Password &result) {
+	Expects(result.type() == mtpc_account_password);
+
 	stopCheck();
 	_sentRequest = 0;
-	switch (result.type()) {
-	case mtpc_account_noPassword: { // should not happen
+	const auto &d = result.c_account_password();
+	getData()->pwdAlgo = d.has_current_algo()
+		? Core::ParseCloudPasswordAlgo(d.vcurrent_algo)
+		: Core::CloudPasswordAlgo();
+	if (!d.has_current_algo()) {
+		LOG(("API Error: No current password received on login."));
 		_code->setFocus();
-	} break;
-
-	case mtpc_account_password: {
-		auto &d = result.c_account_password();
-		getData()->pwdSalt = qba(d.vcurrent_salt);
-		getData()->hasRecovery = d.is_has_recovery();
-		getData()->pwdHint = qs(d.vhint);
-		getData()->pwdNotEmptyPassport = d.is_has_secure_values();
-		goReplace(new Intro::PwdCheckWidget(parentWidget(), getData()));
-	} break;
+		return;
+	} else if (!getData()->pwdAlgo) {
+		const auto box = std::make_shared<QPointer<BoxContent>>();
+		const auto callback = [=] {
+			Core::UpdateApplication();
+			if (*box) (*box)->closeBox();
+		};
+		*box = Ui::show(Box<ConfirmBox>(
+			lang(lng_passport_app_out_of_date),
+			lang(lng_menu_update),
+			callback));
+		return;
 	}
+	getData()->hasRecovery = d.is_has_recovery();
+	getData()->pwdHint = qs(d.vhint);
+	getData()->pwdNotEmptyPassport = d.is_has_secure_values();
+	goReplace(new Intro::PwdCheckWidget(parentWidget(), getData()));
 }
 
 void CodeWidget::submit() {
@@ -312,7 +326,7 @@ void CodeWidget::submit() {
 	_checkRequest->start(1000);
 
 	_sentCode = _code->getLastText();
-	getData()->pwdSalt = QByteArray();
+	getData()->pwdAlgo = Core::CloudPasswordAlgo();
 	getData()->hasRecovery = false;
 	getData()->pwdHint = QString();
 	getData()->pwdNotEmptyPassport = false;
diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp
index dfa910ce2..eb6bd446b 100644
--- a/Telegram/SourceFiles/intro/intropwdcheck.cpp
+++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_intro.h"
 #include "styles/style_boxes.h"
 #include "core/file_utilities.h"
+#include "core/core_cloud_password.h"
 #include "boxes/confirm_box.h"
 #include "lang/lang_keys.h"
 #include "application.h"
@@ -17,11 +18,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/input_fields.h"
 #include "ui/widgets/labels.h"
+#include "base/openssl_help.h"
 
 namespace Intro {
 
-PwdCheckWidget::PwdCheckWidget(QWidget *parent, Widget::Data *data) : Step(parent, data)
-, _salt(getData()->pwdSalt)
+PwdCheckWidget::PwdCheckWidget(
+	QWidget *parent,
+	Widget::Data *data)
+: Step(parent, data)
+, _algo(getData()->pwdAlgo)
 , _hasRecovery(getData()->hasRecovery)
 , _notEmptyPassport(getData()->pwdNotEmptyPassport)
 , _hint(getData()->pwdHint)
@@ -31,6 +36,8 @@ PwdCheckWidget::PwdCheckWidget(QWidget *parent, Widget::Data *data) : Step(paren
 , _toRecover(this, lang(lng_signin_recover))
 , _toPassword(this, lang(lng_signin_try_password))
 , _checkRequest(this) {
+	Expects(_algo.has_value());
+
 	subscribe(Lang::Current().updated(), [this] { refreshLang(); });
 
 	connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest()));
@@ -302,9 +309,14 @@ void PwdCheckWidget::submit() {
 	} else {
 		hideError();
 
-		QByteArray pwdData = _salt + _pwdField->getLastText().toUtf8() + _salt, pwdHash(32, Qt::Uninitialized);
-		hashSha256(pwdData.constData(), pwdData.size(), pwdHash.data());
-		_sentRequest = MTP::send(MTPauth_CheckPassword(MTP_bytes(pwdHash)), rpcDone(&PwdCheckWidget::pwdSubmitDone, false), rpcFail(&PwdCheckWidget::pwdSubmitFail));
+		const auto password = _pwdField->getLastText().toUtf8();
+		const auto hash = Core::ComputeCloudPasswordHash(
+			_algo,
+			bytes::make_span(password));
+		_sentRequest = MTP::send(
+			MTPauth_CheckPassword(MTP_bytes(hash)),
+			rpcDone(&PwdCheckWidget::pwdSubmitDone, false),
+			rpcFail(&PwdCheckWidget::pwdSubmitFail));
 	}
 }
 
diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h
index 3f5cc3b7f..8ca541b17 100644
--- a/Telegram/SourceFiles/intro/intropwdcheck.h
+++ b/Telegram/SourceFiles/intro/intropwdcheck.h
@@ -54,7 +54,7 @@ private:
 	void updateDescriptionText();
 	void stopCheck();
 
-	QByteArray _salt;
+	Core::CloudPasswordAlgo _algo;
 	bool _hasRecovery = false;
 	bool _notEmptyPassport = false;
 	QString _hint, _emailPattern;
@@ -66,8 +66,6 @@ private:
 	object_ptr<Ui::LinkButton> _toPassword;
 	mtpRequestId _sentRequest = 0;
 
-	QByteArray _pwdSalt;
-
 	object_ptr<QTimer> _checkRequest;
 
 };
diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h
index aa20459c6..7effa1207 100644
--- a/Telegram/SourceFiles/intro/introwidget.h
+++ b/Telegram/SourceFiles/intro/introwidget.h
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mtproto/sender.h"
 #include "ui/rp_widget.h"
 #include "window/window_lock_widgets.h"
+#include "core/core_cloud_password.h"
 
 namespace Ui {
 class IconButton;
@@ -72,7 +73,7 @@ public:
 		int codeLength = 5;
 		bool codeByTelegram = false;
 
-		QByteArray pwdSalt;
+		Core::CloudPasswordAlgo pwdAlgo;
 		bool hasRecovery = false;
 		QString pwdHint;
 		bool pwdNotEmptyPassport = false;
diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp
index 29e81540d..11cedf357 100644
--- a/Telegram/SourceFiles/messenger.cpp
+++ b/Telegram/SourceFiles/messenger.cpp
@@ -975,10 +975,14 @@ bool Messenger::openLocalUrl(const QString &url, QVariant context) {
 				};
 				if (result.is_update_app()) {
 					const auto box = std::make_shared<QPointer<BoxContent>>();
+					const auto callback = [=] {
+						Core::UpdateApplication();
+						if (*box) (*box)->closeBox();
+					};
 					*box = Ui::show(Box<ConfirmBox>(
 						text,
 						lang(lng_menu_update),
-						[=] { Core::UpdateApplication(); if (*box) (*box)->closeBox(); }));
+						callback));
 				} else {
 					Ui::show(Box<InformBox>(text));
 				}
diff --git a/Telegram/SourceFiles/passport/passport_encryption.cpp b/Telegram/SourceFiles/passport/passport_encryption.cpp
index 57fb485eb..5e743b4fe 100644
--- a/Telegram/SourceFiles/passport/passport_encryption.cpp
+++ b/Telegram/SourceFiles/passport/passport_encryption.cpp
@@ -163,15 +163,6 @@ bytes::vector EncryptSecretBytes(
 	return Encrypt(secret, std::move(params));
 }
 
-bytes::vector CountPasswordHashForSecret(
-		bytes::const_span salt,
-		bytes::const_span password) {
-	return openssl::Sha512(bytes::concatenate(
-		salt,
-		password,
-		salt));
-}
-
 bytes::vector DecryptSecureSecret(
 		bytes::const_span encryptedSecret,
 		bytes::const_span passwordHashForSecret) {
@@ -403,7 +394,7 @@ bytes::vector DecryptData(
 bytes::vector PrepareValueHash(
 		bytes::const_span dataHash,
 		bytes::const_span valueSecret) {
-	return openssl::Sha256(bytes::concatenate(dataHash, valueSecret));
+	return openssl::Sha256(dataHash, valueSecret);
 }
 
 bytes::vector EncryptValueSecret(
diff --git a/Telegram/SourceFiles/passport/passport_encryption.h b/Telegram/SourceFiles/passport/passport_encryption.h
index 2c54d3ef2..2c5e653fe 100644
--- a/Telegram/SourceFiles/passport/passport_encryption.h
+++ b/Telegram/SourceFiles/passport/passport_encryption.h
@@ -11,9 +11,6 @@ namespace Passport {
 
 bytes::vector GenerateSecretBytes();
 
-bytes::vector CountPasswordHashForSecret(
-	bytes::const_span salt,
-	bytes::const_span password);
 bytes::vector EncryptSecureSecret(
 	bytes::const_span secret,
 	bytes::const_span passwordHashForSecret);
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
index 10ad1762f..22ca4cf9d 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -321,10 +321,7 @@ QString FormController::privacyPolicyUrl() const {
 
 bytes::vector FormController::passwordHashForAuth(
 		bytes::const_span password) const {
-	return openssl::Sha256(bytes::concatenate(
-		_password.salt,
-		password,
-		_password.salt));
+	return Core::ComputeCloudPasswordHash(_password.algo, password);
 }
 
 auto FormController::prepareFinalData() -> FinalData {
@@ -446,7 +443,7 @@ std::vector<not_null<const Value*>> FormController::submitGetErrors() {
 }
 
 void FormController::submitPassword(const QByteArray &password) {
-	Expects(!_password.salt.empty());
+	Expects(_password.algo.has_value());
 
 	const auto submitSaved = !base::take(_savedPasswordValue).isEmpty();
 	if (_passwordCheckRequestId) {
@@ -463,28 +460,42 @@ void FormController::submitPassword(const QByteArray &password) {
 
 		_passwordCheckRequestId = 0;
 		_savedPasswordValue = QByteArray();
-		const auto &data = result.c_account_passwordSettings();
 		const auto hashForAuth = passwordHashForAuth(
 			bytes::make_span(password));
-		const auto hashForSecret = (data.vsecure_salt.v.isEmpty()
-			? bytes::vector()
-			: CountPasswordHashForSecret(
-				bytes::make_span(data.vsecure_salt.v),
-				bytes::make_span(password)));
+		const auto &data = result.c_account_passwordSettings();
 		_password.confirmedEmail = qs(data.vemail);
-		validateSecureSecret(
-			bytes::make_span(data.vsecure_secret.v),
-			hashForSecret,
-			bytes::make_span(password),
-			data.vsecure_secret_id.v);
-		if (!_secret.empty()) {
-			auto saved = SavedCredentials();
-			saved.hashForAuth = hashForAuth;
-			saved.hashForSecret = hashForSecret;
-			saved.secretId = _secretId;
-			Auth().data().rememberPassportCredentials(
-				std::move(saved),
-				kRememberCredentialsDelay);
+		if (data.has_secure_settings()) {
+			const auto &wrapped = data.vsecure_settings;
+			const auto &settings = wrapped.c_secureSecretSettings();
+			const auto algo = Core::ParseSecureSecretAlgo(
+				settings.vsecure_algo);
+			if (!algo) {
+				_view->showUpdateAppBox();
+				return;
+			}
+			const auto hashForSecret = Core::ComputeSecureSecretHash(
+				algo,
+				bytes::make_span(password));
+			validateSecureSecret(
+				bytes::make_span(settings.vsecure_secret.v),
+				hashForSecret,
+				bytes::make_span(password),
+				settings.vsecure_secret_id.v);
+			if (!_secret.empty()) {
+				auto saved = SavedCredentials();
+				saved.hashForAuth = hashForAuth;
+				saved.hashForSecret = hashForSecret;
+				saved.secretId = _secretId;
+				Auth().data().rememberPassportCredentials(
+					std::move(saved),
+					kRememberCredentialsDelay);
+			}
+		} else {
+			validateSecureSecret(
+				bytes::const_span(), // secure_secret
+				bytes::const_span(), // hash for secret
+				bytes::make_span(password),
+				0); // secure_secret_id
 		}
 	}).fail([=](const RPCError &error) {
 		_passwordCheckRequestId = 0;
@@ -511,14 +522,23 @@ void FormController::checkSavedPasswordSettings(
 
 		_passwordCheckRequestId = 0;
 		const auto &data = result.c_account_passwordSettings();
-		if (!data.vsecure_secret.v.isEmpty()
-			&& data.vsecure_secret_id.v == credentials.secretId) {
-			_password.confirmedEmail = qs(data.vemail);
-			validateSecureSecret(
-				bytes::make_span(data.vsecure_secret.v),
-				credentials.hashForSecret,
-				{},
-				data.vsecure_secret_id.v);
+		if (data.has_secure_settings()) {
+			const auto &wrapped = data.vsecure_settings;
+			const auto &settings = wrapped.c_secureSecretSettings();
+			const auto algo = Core::ParseSecureSecretAlgo(
+				settings.vsecure_algo);
+			if (!algo) {
+				_view->showUpdateAppBox();
+				return;
+			} else if (!settings.vsecure_secret.v.isEmpty()
+				&& settings.vsecure_secret_id.v == credentials.secretId) {
+				_password.confirmedEmail = qs(data.vemail);
+				validateSecureSecret(
+					bytes::make_span(settings.vsecure_secret.v),
+					credentials.hashForSecret,
+					{},
+					settings.vsecure_secret_id.v);
+			}
 		}
 		if (_secret.empty()) {
 			Auth().data().forgetPassportCredentials();
@@ -584,13 +604,11 @@ void FormController::cancelPassword() {
 		MTP_bytes(QByteArray()),
 		MTP_account_passwordInputSettings(
 			MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
-			MTP_bytes(QByteArray()), // new_salt
+			MTP_passwordKdfAlgoUnknown(), // new_algo
 			MTP_bytes(QByteArray()), // new_password_hash
 			MTP_string(QString()), // hint
 			MTP_string(QString()), // email
-			MTP_bytes(QByteArray()), // new_secure_salt
-			MTP_bytes(QByteArray()), // new_secure_secret
-			MTP_long(0)) // new_secure_secret_hash
+			MTPSecureSecretSettings())
 	)).done([=](const MTPBool &result) {
 		_passwordRequestId = 0;
 		reloadPassword();
@@ -648,16 +666,15 @@ void FormController::suggestReset(bytes::vector password) {
 		_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
 			MTP_bytes(passwordHashForAuth(password)),
 			MTP_account_passwordInputSettings(
-				MTP_flags(Flag::f_new_secure_salt
-					| Flag::f_new_secure_secret
-					| Flag::f_new_secure_secret_id),
-				MTPbytes(), // new_salt
+				MTP_flags(Flag::f_new_secure_settings),
+				MTPPasswordKdfAlgo(), // new_algo
 				MTPbytes(), // new_password_hash
 				MTPstring(), // hint
 				MTPstring(), // email
-			MTP_bytes(QByteArray()), // new_secure_salt
-			MTP_bytes(QByteArray()), // new_secure_secret
-			MTP_long(0)) // new_secure_secret_id
+				MTP_secureSecretSettings(
+					MTP_securePasswordKdfAlgoUnknown(), // secure_algo
+					MTP_bytes(QByteArray()), // secure_secret
+					MTP_long(0))) // secure_secret_id
 		)).done([=](const MTPBool &result) {
 			_saveSecretRequestId = 0;
 			generateSecret(password);
@@ -1774,16 +1791,10 @@ void FormController::generateSecret(bytes::const_span password) {
 	}
 	auto secret = GenerateSecretBytes();
 
-	auto randomSaltPart = bytes::vector(8);
-	bytes::set_random(randomSaltPart);
-	auto newSecureSaltFull = bytes::concatenate(
-		_password.newSecureSalt,
-		randomSaltPart);
-
 	auto saved = SavedCredentials();
 	saved.hashForAuth = passwordHashForAuth(password);
-	saved.hashForSecret = CountPasswordHashForSecret(
-		newSecureSaltFull,
+	saved.hashForSecret = Core::ComputeSecureSecretHash(
+		_password.newSecureAlgo,
 		password);
 	saved.secretId = CountSecureSecretId(secret);
 
@@ -1795,16 +1806,15 @@ void FormController::generateSecret(bytes::const_span password) {
 	_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
 		MTP_bytes(saved.hashForAuth),
 		MTP_account_passwordInputSettings(
-			MTP_flags(Flag::f_new_secure_salt
-				| Flag::f_new_secure_secret
-				| Flag::f_new_secure_secret_id),
-			MTPbytes(), // new_salt
+			MTP_flags(Flag::f_new_secure_settings),
+			MTPPasswordKdfAlgo(), // new_algo
 			MTPbytes(), // new_password_hash
 			MTPstring(), // hint
 			MTPstring(), // email
-			MTP_bytes(newSecureSaltFull),
-			MTP_bytes(encryptedSecret),
-			MTP_long(saved.secretId))
+			MTP_secureSecretSettings(
+				Core::PrepareSecureSecretAlgo(_password.newSecureAlgo),
+				MTP_bytes(encryptedSecret),
+				MTP_long(saved.secretId)))
 	)).done([=](const MTPBool &result) {
 		Auth().data().rememberPassportCredentials(
 			std::move(saved),
@@ -2089,15 +2099,9 @@ void FormController::requestPassword() {
 }
 
 void FormController::passwordDone(const MTPaccount_Password &result) {
-	const auto changed = [&] {
-		switch (result.type()) {
-		case mtpc_account_noPassword:
-			return applyPassword(result.c_account_noPassword());
-		case mtpc_account_password:
-			return applyPassword(result.c_account_password());
-		}
-		Unexpected("Type in FormController::passwordDone.");
-	}();
+	Expects(result.type() == mtpc_account_password);
+
+	const auto changed = applyPassword(result.c_account_password());
 	if (changed && !_formRequestId) {
 		showForm();
 	}
@@ -2117,7 +2121,12 @@ void FormController::showForm() {
 		formFail(Lang::Hard::NoAuthorizationBot());
 		return;
 	}
-	if (!_password.salt.empty()) {
+	if (_password.unknownAlgo
+		|| !_password.newAlgo
+		|| !_password.newSecureAlgo) {
+		_view->showUpdateAppBox();
+		return;
+	} else if (_password.algo) {
 		if (!_savedPasswordValue.isEmpty()) {
 			submitPassword(base::duplicate(_savedPasswordValue));
 		} else if (const auto saved = Auth().data().passportCredentials()) {
@@ -2130,24 +2139,23 @@ void FormController::showForm() {
 	}
 }
 
-bool FormController::applyPassword(const MTPDaccount_noPassword &result) {
-	auto settings = PasswordSettings();
-	settings.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
-	settings.newSalt = bytes::make_vector(result.vnew_salt.v);
-	settings.newSecureSalt = bytes::make_vector(result.vnew_secure_salt.v);
-	openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
-	return applyPassword(std::move(settings));
-}
-
 bool FormController::applyPassword(const MTPDaccount_password &result) {
 	auto settings = PasswordSettings();
 	settings.hint = qs(result.vhint);
 	settings.hasRecovery = result.is_has_recovery();
 	settings.notEmptyPassport = result.is_has_secure_values();
-	settings.salt = bytes::make_vector(result.vcurrent_salt.v);
-	settings.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
-	settings.newSalt = bytes::make_vector(result.vnew_salt.v);
-	settings.newSecureSalt = bytes::make_vector(result.vnew_secure_salt.v);
+	settings.algo = result.has_current_algo()
+		? Core::ParseCloudPasswordAlgo(result.vcurrent_algo)
+		: Core::CloudPasswordAlgo();
+	settings.unknownAlgo = result.has_current_algo()
+		&& !settings.algo;
+	settings.unconfirmedPattern = result.has_email_unconfirmed_pattern()
+		? qs(result.vemail_unconfirmed_pattern)
+		: QString();
+	settings.newAlgo = Core::ValidateNewCloudPasswordAlgo(
+		Core::ParseCloudPasswordAlgo(result.vnew_algo));
+	settings.newSecureAlgo = Core::ValidateNewSecureSecretAlgo(
+		Core::ParseSecureSecretAlgo(result.vnew_secure_algo));
 	openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
 	return applyPassword(std::move(settings));
 }
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h
index d461ce09a..ec7626443 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.h
+++ b/Telegram/SourceFiles/passport/passport_form_controller.h
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mtproto/sender.h"
 #include "boxes/confirm_phone_box.h"
 #include "base/weak_ptr.h"
+#include "core/core_cloud_password.h"
 
 class BoxContent;
 
@@ -195,23 +196,25 @@ struct Form {
 };
 
 struct PasswordSettings {
-	bytes::vector salt;
-	bytes::vector newSalt;
-	bytes::vector newSecureSalt;
+	Core::CloudPasswordAlgo algo;
+	Core::CloudPasswordAlgo newAlgo;
+	Core::SecureSecretAlgo newSecureAlgo;
 	QString hint;
 	QString unconfirmedPattern;
 	QString confirmedEmail;
 	bool hasRecovery = false;
 	bool notEmptyPassport = false;
+	bool unknownAlgo = false;
 
 	bool operator==(const PasswordSettings &other) const {
-		return (salt == other.salt)
-			&& (newSalt == other.newSalt)
-			&& (newSecureSalt == other.newSecureSalt)
+		return (algo == other.algo)
+			&& (newAlgo == other.newAlgo)
+			&& (newSecureAlgo == other.newSecureAlgo)
 			&& (hint == other.hint)
 			&& (unconfirmedPattern == other.unconfirmedPattern)
 			&& (confirmedEmail == other.confirmedEmail)
-			&& (hasRecovery == other.hasRecovery);
+			&& (hasRecovery == other.hasRecovery)
+			&& (unknownAlgo == other.unknownAlgo);
 	}
 	bool operator!=(const PasswordSettings &other) const {
 		return !(*this == other);
@@ -338,7 +341,6 @@ private:
 		const std::vector<EditFile> &source) const;
 
 	void passwordDone(const MTPaccount_Password &result);
-	bool applyPassword(const MTPDaccount_noPassword &settings);
 	bool applyPassword(const MTPDaccount_password &settings);
 	bool applyPassword(PasswordSettings &&settings);
 	bytes::vector passwordHashForAuth(bytes::const_span password) const;
diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp
index e566cb862..d83ca197b 100644
--- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp
@@ -261,7 +261,17 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
 				}
 			}
 		};
-		ranges::for_each(scope.documents, addValueErrors);
+		const auto document = [&]() -> const Value* {
+			for (const auto &document : scope.documents) {
+				if (document->scansAreFilled(scope.selfieRequired)) {
+					return document;
+				}
+			}
+			return nullptr;
+		}();
+		if (document) {
+			addValueErrors(document);
+		}
 		addValueErrors(scope.fields);
 		if (!errors.isEmpty()) {
 			row.error = lang(lng_passport_fix_errors);// errors.join('\n');
diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp
index 56b9ad1b8..43123e6c7 100644
--- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp
@@ -467,36 +467,26 @@ void PanelController::setupPassword() {
 	Expects(_panel != nullptr);
 
 	const auto &settings = _form->passwordSettings();
-	if (!settings.salt.empty()) {
+	if (settings.unknownAlgo
+		|| !settings.newAlgo
+		|| !settings.newSecureAlgo) {
+		showUpdateAppBox();
+		return;
+	} else if (settings.algo) {
 		showAskPassword();
 		return;
 	}
 
-	constexpr auto kRandomPart = 8;
-	auto newPasswordSalt = QByteArray(
-		reinterpret_cast<const char*>(settings.newSalt.data()),
-		settings.newSalt.size());
-	newPasswordSalt.resize(newPasswordSalt.size() + kRandomPart);
-	bytes::set_random(
-		bytes::make_span(newPasswordSalt).subspan(settings.newSalt.size()));
-	auto newSecureSecretSalt = QByteArray(
-		reinterpret_cast<const char*>(settings.newSecureSalt.data()),
-		settings.newSecureSalt.size());
-	newSecureSecretSalt.resize(newSecureSecretSalt.size() + kRandomPart);
-	bytes::set_random(
-		bytes::make_span(
-			newSecureSecretSalt).subspan(settings.newSecureSalt.size()));
-	const auto currentSalt = QByteArray();
 	const auto hasRecovery = false;
 	const auto notEmptyPassport = false;
 	const auto hint = QString();
 	auto box = show(Box<PasscodeBox>(
-		newPasswordSalt,
-		currentSalt,
+		Core::CloudPasswordAlgo(), // current algo
+		settings.newAlgo,
 		hasRecovery,
 		notEmptyPassport,
 		hint,
-		newSecureSecretSalt));
+		settings.newSecureAlgo));
 	box->newPasswordSet(
 	) | rpl::filter([=](const QByteArray &password) {
 		return !password.isEmpty();
@@ -816,12 +806,11 @@ void PanelController::showCriticalError(const QString &error) {
 void PanelController::showUpdateAppBox() {
 	ensurePanelCreated();
 
-	const auto box = std::make_shared<QPointer<BoxContent>>();
 	const auto callback = [=] {
 		_form->cancelSure();
 		Core::UpdateApplication();
 	};
-	*box = show(
+	show(
 		Box<ConfirmBox>(
 			lang(lng_passport_app_out_of_date),
 			lang(lng_menu_update),
@@ -952,6 +941,9 @@ void PanelController::editWithUpload(int index, int documentIndex) {
 	const auto allowMany = !requiresSpecialScan;
 	const auto widget = _panel->widget();
 	EditScans::ChooseScan(widget.get(), [=](QByteArray &&content) {
+		if (_scopeDocumentTypeBox) {
+			_scopeDocumentTypeBox = BoxPointer();
+		}
 		if (!_editScope || !_editDocument) {
 			editScope(index, documentIndex);
 		}
diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp
index 5b5ee2b10..5ad4385de 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp
@@ -15,7 +15,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "auth_session.h"
 #include "data/data_session.h"
 #include "platform/platform_specific.h"
+#include "core/update_checker.h"
 #include "base/openssl_help.h"
+#include "boxes/confirm_box.h"
 #include "boxes/sessions_box.h"
 #include "boxes/passcode_box.h"
 #include "boxes/autolock_box.h"
@@ -76,13 +78,27 @@ int CloudPasswordState::resizeGetHeight(int newWidth) {
 }
 
 void CloudPasswordState::onEdit() {
+	if (_unknownPasswordAlgo
+		|| !_newPasswordAlgo
+		|| !_newSecureSecretAlgo) {
+		const auto box = std::make_shared<QPointer<BoxContent>>();
+		const auto callback = [=] {
+			Core::UpdateApplication();
+			if (*box) (*box)->closeBox();
+		};
+		*box = Ui::show(Box<ConfirmBox>(
+			lang(lng_passport_app_out_of_date),
+			lang(lng_menu_update),
+			callback));
+		return;
+	}
 	auto box = Ui::show(Box<PasscodeBox>(
-		_newPasswordSalt,
-		_curPasswordSalt,
+		_curPasswordAlgo,
+		_newPasswordAlgo,
 		_hasPasswordRecovery,
 		_notEmptyPassport,
 		_curPasswordHint,
-		_newSecureSecretSalt));
+		_newSecureSecretAlgo));
 	rpl::merge(
 		box->newPasswordSet() | rpl::map([] { return rpl::empty_value(); }),
 		box->passwordReloadNeeded()
@@ -92,31 +108,44 @@ void CloudPasswordState::onEdit() {
 }
 
 void CloudPasswordState::onTurnOff() {
-	if (_curPasswordSalt.isEmpty()) {
+	if (_unknownPasswordAlgo
+		|| !_newPasswordAlgo
+		|| !_newSecureSecretAlgo) {
+		const auto box = std::make_shared<QPointer<BoxContent>>();
+		const auto callback = [=] {
+			Core::UpdateApplication();
+			if (*box) (*box)->closeBox();
+		};
+		*box = Ui::show(Box<ConfirmBox>(
+			lang(lng_passport_app_out_of_date),
+			lang(lng_menu_update),
+			callback));
+		return;
+	}
+	if (!_curPasswordAlgo) {
 		_turnOff->hide();
 
 		MTP::send(
 			MTPaccount_UpdatePasswordSettings(
 				MTP_bytes(QByteArray()),
 				MTP_account_passwordInputSettings(
-					MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
-					MTP_bytes(QByteArray()), // new_salt
+					MTP_flags(
+						MTPDaccount_passwordInputSettings::Flag::f_email),
+					MTP_passwordKdfAlgoUnknown(), // new_algo
 					MTP_bytes(QByteArray()), // new_password_hash
 					MTP_string(QString()), // hint
 					MTP_string(QString()), // email
-					MTP_bytes(QByteArray()), // new_secure_salt
-					MTP_bytes(QByteArray()), // new_secure_secret
-					MTP_long(0))), // new_secure_secret_hash
+					MTPSecureSecretSettings())),
 			rpcDone(&CloudPasswordState::offPasswordDone),
 			rpcFail(&CloudPasswordState::offPasswordFail));
 	} else {
 		auto box = Ui::show(Box<PasscodeBox>(
-			_newPasswordSalt,
-			_curPasswordSalt,
+			_curPasswordAlgo,
+			_newPasswordAlgo,
 			_hasPasswordRecovery,
 			_notEmptyPassport,
 			_curPasswordHint,
-			_newSecureSecretSalt,
+			_newSecureSecretAlgo,
 			true));
 		rpl::merge(
 			box->newPasswordSet(
@@ -142,53 +171,41 @@ void CloudPasswordState::onReloadPassword(Qt::ApplicationState state) {
 }
 
 void CloudPasswordState::getPasswordDone(const MTPaccount_Password &result) {
+	Expects(result.type() == mtpc_account_password);
+
 	_reloadRequestId = 0;
 	_waitingConfirm = QString();
 
-	switch (result.type()) {
-	case mtpc_account_noPassword: {
-		auto &d = result.c_account_noPassword();
-		_curPasswordSalt = QByteArray();
-		_hasPasswordRecovery = false;
-		_notEmptyPassport = false;
-		_curPasswordHint = QString();
-		_newPasswordSalt = qba(d.vnew_salt);
-		_newSecureSecretSalt = qba(d.vnew_secure_salt);
-		auto pattern = qs(d.vemail_unconfirmed_pattern);
-		if (!pattern.isEmpty()) {
-			_waitingConfirm = lng_cloud_password_waiting(lt_email, pattern);
-		}
-		openssl::AddRandomSeed(bytes::make_span(d.vsecure_random.v));
-	} break;
-
-	case mtpc_account_password: {
-		auto &d = result.c_account_password();
-		_curPasswordSalt = qba(d.vcurrent_salt);
-		_hasPasswordRecovery = d.is_has_recovery();
-		_notEmptyPassport = d.is_has_secure_values();
-		_curPasswordHint = qs(d.vhint);
-		_newPasswordSalt = qba(d.vnew_salt);
-		_newSecureSecretSalt = qba(d.vnew_secure_salt);
-		auto pattern = qs(d.vemail_unconfirmed_pattern);
-		if (!pattern.isEmpty()) {
-			_waitingConfirm = lng_cloud_password_waiting(lt_email, pattern);
-		}
-		openssl::AddRandomSeed(bytes::make_span(d.vsecure_random.v));
-	} break;
+	const auto &d = result.c_account_password();
+	_curPasswordAlgo = d.has_current_algo()
+		? Core::ParseCloudPasswordAlgo(d.vcurrent_algo)
+		: Core::CloudPasswordAlgo();
+	_unknownPasswordAlgo = d.has_current_algo() && !_curPasswordAlgo;
+	_hasPasswordRecovery = d.is_has_recovery();
+	_notEmptyPassport = d.is_has_secure_values();
+	_curPasswordHint = qs(d.vhint);
+	_newPasswordAlgo = Core::ValidateNewCloudPasswordAlgo(
+		Core::ParseCloudPasswordAlgo(d.vnew_algo));
+	_newSecureSecretAlgo = Core::ValidateNewSecureSecretAlgo(
+		Core::ParseSecureSecretAlgo(d.vnew_secure_algo));
+	const auto pattern = d.has_email_unconfirmed_pattern()
+		? qs(d.vemail_unconfirmed_pattern)
+		: QString();
+	if (!pattern.isEmpty()) {
+		_waitingConfirm = lng_cloud_password_waiting(lt_email, pattern);
 	}
-	_edit->setText(lang(_curPasswordSalt.isEmpty() ? lng_cloud_password_set : lng_cloud_password_edit));
-	_edit->setVisible(_waitingConfirm.isEmpty());
-	_turnOff->setVisible(!_waitingConfirm.isEmpty() || !_curPasswordSalt.isEmpty());
-	update();
+	openssl::AddRandomSeed(bytes::make_span(d.vsecure_random.v));
 
-	_newPasswordSalt.resize(_newPasswordSalt.size() + 8);
-	memset_rand(
-		_newPasswordSalt.data() + _newPasswordSalt.size() - 8,
-		8);
-	_newSecureSecretSalt.resize(_newSecureSecretSalt.size() + 8);
-	memset_rand(
-		_newSecureSecretSalt.data() + _newSecureSecretSalt.size() - 8,
-		8);
+	_edit->setText(lang(hasCloudPassword()
+		? lng_cloud_password_edit
+		: lng_cloud_password_set));
+	_edit->setVisible(_waitingConfirm.isEmpty());
+	_turnOff->setVisible(!_waitingConfirm.isEmpty() || hasCloudPassword());
+	update();
+}
+
+bool CloudPasswordState::hasCloudPassword() const {
+	return (_curPasswordAlgo || _unknownPasswordAlgo);
 }
 
 bool CloudPasswordState::getPasswordFail(const RPCError &error) {
diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.h b/Telegram/SourceFiles/settings/settings_privacy_widget.h
index 59e840e98..15c42fea8 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_widget.h
+++ b/Telegram/SourceFiles/settings/settings_privacy_widget.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "settings/settings_block_widget.h"
 #include "settings/settings_chat_settings_widget.h"
+#include "core/core_cloud_password.h"
 #include "ui/rp_widget.h"
 
 namespace Settings {
@@ -59,16 +60,19 @@ private:
 	void offPasswordDone(const MTPBool &result);
 	bool offPasswordFail(const RPCError &error);
 
+	bool hasCloudPassword() const;
+
 	object_ptr<Ui::LinkButton> _edit;
 	object_ptr<Ui::LinkButton> _turnOff;
 
 	QString _waitingConfirm;
-	QByteArray _curPasswordSalt;
+	Core::CloudPasswordAlgo _curPasswordAlgo;
+	bool _unknownPasswordAlgo = false;
 	bool _hasPasswordRecovery = false;
 	bool _notEmptyPassport = false;
 	QString _curPasswordHint;
-	QByteArray _newPasswordSalt;
-	QByteArray _newSecureSecretSalt;
+	Core::CloudPasswordAlgo _newPasswordAlgo;
+	Core::SecureSecretAlgo _newSecureSecretAlgo;
 	mtpRequestId _reloadRequestId = 0;
 
 };
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index 7a103286b..59ab00e8e 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -143,6 +143,8 @@
 <(src_loc)/core/click_handler.h
 <(src_loc)/core/click_handler_types.cpp
 <(src_loc)/core/click_handler_types.h
+<(src_loc)/core/core_cloud_password.cpp
+<(src_loc)/core/core_cloud_password.h
 <(src_loc)/core/crash_report_window.cpp
 <(src_loc)/core/crash_report_window.h
 <(src_loc)/core/crash_reports.cpp