Статьи

Проверка подлинности с помощью мобильных служб Windows Azure в Windows Phone

Несмотря на свою довольно простую оболочку, мобильные службы 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.