Несмотря на свою довольно простую оболочку, мобильные службы Azure можно использовать для стандартной проверки подлинности в приложении. В этой статье я рассмотрю базовую реализацию с проверкой учетных данных на основе хеша.
На этом этапе убедитесь, что вы загрузили и добавили ссылку на Azure Mobile Services SDK для Windows Phone 8 (или вы можете сделать предварительный выпуск SDK для Windows Phone 7.5 ). NuGet — самый простой способ сделать это.
Начните с создания новой таблицы на портале управления Azure — Пользователи .
В классе App включите ссылку на экземпляр MobileServiceClient, который вы будете использовать для подключения к базовой базе данных:
public static MobileServiceClient MobileService = new MobileServiceClient( "https://YOUR_AZURE_MS.azure-mobile.net/", "KEY" );
Теперь давайте подумаем о логистике механизмов безопасности. Каждый пароль хешируется и засоляется. Позже он сохраняется в таблице Users . В этот момент вы можете подумать — хеш хранится с солью в одной таблице, разве это не угроза безопасности? Не совсем, если пароль хранится в необратимой форме — вернуть его невозможно, даже если каким-то образом получить и хеш, и соль. Кроме того, соль предполагается уникальной для каждого пароля. Это помогает защитить систему от словарных атак .
Вот как генерируется хеш пароля:
public static byte[] GenerateHash(string password, byte[] salt) { byte[] passwordData = Encoding.UTF8.GetBytes(password); byte[] composite = new byte[passwordData.Length + 32]; Array.Copy(passwordData, composite, passwordData.Length); Array.Copy(salt, 0, composite, passwordData.Length, salt.Length); SHA256 hashFunction = new SHA256Managed(); byte[] hash = hashFunction.ComputeHash(composite); return hash; }
Соль передается функции в виде байтового массива, который позже добавляется к паролю. SHA256 используется для вычисления хеша ядра — избегайте использования алгоритмов хеширования, таких как MD5 и SHA1 .
Хеш генерируется с помощью криптографически безопасного генератора случайных чисел — RNGCryptoServiceProvider .
public static byte[] GetSalt() { byte[] rngContainer = new byte[32]; RNGCryptoServiceProvider rngProvider = new RNGCryptoServiceProvider(); rngProvider.GetBytes(rngContainer); return rngContainer; }
Внутренне мне также потребуется извлечь представление байта из строки соли для целей проверки. Вот как выглядит функция:
public static byte[] GetSaltFromString(string source) { string[] raw = source.Split('-'); byte[] result = new byte[raw.Length]; for (int i = 0; i < raw.Length; i++) { result[i] = Convert.ToByte(raw[i], 16); } return result; }
Для отправки данных в Azure нам нужна сериализуемая пользовательская модель, а также возможность для разработчика выбирать, использовать новый случайный хэш или нет:
public static User GetSecureUserModel(string username, string password, string email = "", byte[] customSalt = null) { User user = new User(); user.Username = username; user.Email = email; byte[] hash; if (customSalt == null) { byte[] salt = HashHelper.GetSalt(); user.Salt = BitConverter.ToString(salt); hash = HashHelper.GenerateHash(password, salt); } else { hash = HashHelper.GenerateHash(password, customSalt); } user.Hash = BitConverter.ToString(hash); return user; }
You can infer the model structure from the table fields:
[DataTable(Name="Users")] public class User { public int? Id { get; set; } public string Username { get; set; } public string Salt { get; set; } public string Hash { get; set; } public string Email { get; set; } }
On registration, a table insertion is executed:
public async static Task<bool> RegisterUser(User user) { IMobileServiceTable<User> userTable = App.MobileService.GetTable<User>(); List<User> userList = await userTable.Take(1).Where(x => x.Username == user.Username).ToListAsync(); if (userList.Count == 0) { await App.MobileService.GetTable<User>().InsertAsync(user); return true; } else { return false; } }
Here is what we do when we need to validate credentials that are given the user — a new user model is obtained based on the input and the hash for the given username is retrieved from the database:
public static async Task<User> GetUserFromDatabase(string username) { IMobileServiceTable<User> userTable = App.MobileService.GetTable<User>(); List<User> userList = await userTable.Take(1).Where(x => x.Username == username).ToListAsync(); if (userList.Count == 0) return null; else return userList.First(); }
The login is verified against the existing model, with the existing salt being used to create a new password hash, based on the new input.
public async static Task<User> VerifyLogin(string username, string password) { User dbUser = await GetUserFromDatabase(username); if (dbUser != null) { User localUser = GetSecureUserModel(username, password, "", HashHelper.GetSaltFromString(dbUser.Salt)); if (dbUser.Hash == localUser.Hash) return dbUser; } return null; }
This model can be applied to simple mobile applications that need a core authentication system.
There are several additions that can be added to the implementation — for example, additional hashing can be added to on the server side. A more complex hashing algorithm can be also used with more variation added to the salt.