Шифрование информации в базе данных

115

В этой статье будет показано, как создать простую тестовую страницу для шифрования информации, хранящейся в таблице базы данных. Эта таблица будет подключена к пользователю, зарегистрированному службой членства. Мы советуем не создавать пользовательский поставщик членства со специальной реализацией MembershipUser, поддерживающей дополнительные свойства. До тех пор, пока вы не слишком связаны собственным кодом, можете использовать множество поставщиков членства.

В рассматриваемом примере создается таблица базы данных, которая хранит дополнительную информацию для MembershipUser, без создания собственного поставщика. Она будет просто подключаться к MembershipUser через ProviderUserKey - это означает действительный первичный ключ лежащего в основе хранилища данных. Таким образом, понадобится создать следующую таблицу в SQL Server (я подключил ее к общей базе данных aspnetdb):

CREATE TABLE ShopInfo (
	UserId UNIQUEIDENTIFIER PRIMARY KEY, 
	CreditCard VARBINARY(60),
	Street VARCHAR(80), 
	ZipCode VARCHAR(6), 
	City VARCHAR(60)
)

Первичный ключ UserId будет содержать тот же ключ, что и MembershipUser, для которого эта информация создана. Это - единственное соединение со службой членства. Как упоминалось ранее, преимущество отказа от создания собственно поставщика только для дополнительных полей состоит в том, что можно использовать других поставщиков. Рекомендуется создавать свои поставщики только для поддержки дополнительных типов хранилищ данных для службы членства.

Важная информация содержится в поле CreditCard, которое теперь имеет тип не VARCHAR, a VARBINARY. Теперь можно создать страницу, которая выглядит следующим образом:

<form id="form1" runat="server">
    <div>
        <asp:LoginView runat="server" ID="MainLoginView">
            <AnonymousTemplate>
                <asp:Login ID="MainLogin" runat="server" />
            </AnonymousTemplate>
            <LoggedInTemplate>
                Номер кредитной карты:
                <asp:TextBox ID="CreditCardText" runat="server" /><br />
                Улица:
                <asp:TextBox ID="StreetText" runat="server" /><br />
                Почтовый индекс:
                <asp:TextBox ID="ZipCodeText" runat="server" /><br />
                Город:
                <asp:TextBox ID="CityText" runat="server" /><br />
                <asp:Button runat="server" ID="LoadCommand" Text="Загрузить" OnClick="LoadCommand_Click" />
                 
                <asp:Button runat="server" ID="SaveCommand" Text="Сохранить" OnClick="SaveCommand_Click" />
            </LoggedInTemplate>
        </asp:LoginView>
    </div>
</form>

Страница включает элемент управления LoginView, который отображает элемент Login для анонимных пользователей, и несколько текстовых полей с информацией из таблицы ShopInfo. Внутри обработчика события Click кнопки "Загрузить" будет написан код для извлечения и расшифровки информации из базы данных, а внутри обработчика события Click кнопки "Сохранить" - его противоположность. Но, прежде чем сделать это, необходимо соответствующим образом сконфигурировать строку соединения. В моем случае это:

<configuration>
  <connectionStrings>
    <add name="MyMembershipConnString" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=aspnetdb;Integrated Security=True"/>
  </connectionStrings>
  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="MyLogin.aspx"/>
    </authentication>
  </system.web>
</configuration>

Теперь необходимо воспользоваться инструментом ASP.NET WAT, чтобы создать пару пользователей в хранилище членства. После того как это будет сделано, можно приступить к написанию кода чтения и записи данных в базу. Код не содержит в себе ничего особенного. Он просто использует ранее созданный служебный класс для шифрования информации перед обновлением базы данных и расшифровки сохранений в базе данных.

Сначала рассмотрим метод Page_Load(), инициализирующий экземпляр соединения ADO.NET, а затем - метод обновления, реализованный в обработчике события Click кнопки SaveCommand.

Мы полагаемся на созданный в предыдущей статье служебный класс (SymmetricEncryptionUtility), который требует указания имени файла для хранения защищенного секретного ключа. Также обратите внимание, что в коде предыдущей страницы ASP.NET использовался элемент управления LoginView. Это значит, что потребуется вручную найти элементы TextBox с помощью метода FindControl() в элементе LoginView и ассоциировать их с собственными членами, как показано в следующем фрагменте:

using System;
using System.Web.UI.WebControls;
using System.Web.Security;
using System.Configuration;
using Professorweb.ProAspNet.Utility;
using System.IO;
using System.Data;
using System.Data.SqlClient;

public partial class _Default : System.Web.UI.Page
{
    // Приватный член текущей страницы, представляющий соединение с
    // базой данных, сконфигурированной ранее
    SqlConnection DemoDb;

    // Нужно будет получить несколько элементов TextBox, которые находятся 
    // в шаблоне элемента LoginView через метод FindControl(), потому что
    // они содержатся только в нем
    private TextBox CreditCardText;
    private TextBox StreetText;
    private TextBox ZipCodeText;
    private TextBox CityText;

    // Используется для хранения ключа шифрования на основе кода, 
    // представленного ранее в классе SymmetricEncryptionUtility
    private string EncryptionKeyFile;

    protected void Page_Load(object sender, EventArgs e)
    {
        // Конфигурирование утилиты шифрования
        EncryptionKeyFile = Server.MapPath("key.config");
        SymmetricEncryptionUtility.AlgorithmName = "DES";
        if (!System.IO.File.Exists(EncryptionKeyFile))
        {
            SymmetricEncryptionUtility.GenerateKey(EncryptionKeyFile);
        }

        // Создание подключения к БД
        DemoDb = new SqlConnection(
            ConfigurationManager.ConnectionStrings["MyMembershipConnString"].ConnectionString);

        // Ассоциировать элементы управления TextBox находящиеся в элементе Login 
        // с полями, которые будем использовать в обработчиках
        CreditCardText = (TextBox)MainLoginView.FindControl("CreditCardText");
        StreetText = (TextBox)MainLoginView.FindControl("StreetText");
        ZipCodeText = (TextBox)MainLoginView.FindControl("ZipCodeText");
        CityText = (TextBox)MainLoginView.FindControl("CityText");
    }

    protected void LoadCommand_Click(object sender, EventArgs e)
    {
        // ...
    }

    protected void SaveCommand_Click(object sender, EventArgs e)
    {
        // ...
    }
}

Ниже показан код обработчика для шифрования и записи данных пользователя в базу данных. Двумя ключевыми частями этого кода являются извлечение ProviderUserKey текущего зарегистрированного MembershipUser для подключения информации к пользователю Membership и то место, где информация кредитной карты шифруется посредством ранее созданного служебного класса. Команда SQL получает в качестве параметра только зашифрованный байтовый массив. Поэтому данные сохраняются в базе в зашифрованном виде:

protected void SaveCommand_Click(object sender, EventArgs e)
{
        try
        {
            DemoDb.Open();
        }
        catch (Exception ex)
        {
            // Вместо этого нужно использовать лог-файл
            System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            return;
        }

        try
        {
            string SqlText = "UPDATE ShopInfo " +
                             "SET Street=@street, ZipCode=@zip, " +
                                 "City=@city, CreditCard=@card " +
                             "WHERE UserId=@key";

            SqlCommand Cmd = new SqlCommand(SqlText, DemoDb);

            // Добавить в параметры простые строки
            Cmd.Parameters.AddWithValue("@street", StreetText.Text);
            Cmd.Parameters.AddWithValue("@zip", ZipCodeText.Text);
            Cmd.Parameters.AddWithValue("@city", CityText.Text);
            Cmd.Parameters.AddWithValue("@key", Membership.GetUser().ProviderUserKey);

            // Зашифровать код кредитной карты
            byte[] EncryptedData =
                SymmetricEncryptionUtility.EncryptData(
                        CreditCardText.Text, EncryptionKeyFile);
            Cmd.Parameters.Add("@card", SqlDbType.Binary, EncryptedData.Length).Value = EncryptedData;

            // Выполнить команду
            int results = Cmd.ExecuteNonQuery();

            // Если обновление не прошло, значит пользователя нет еще в базе ShopInfo,
            // поэтому нужно вставить новую запись
            if (results == 0)
            {
                Cmd.CommandText = "INSERT INTO ShopInfo(UserId, City, ZipCode, Street, CreditCard)" +
                    "VALUES(@key, @city, @zip, @street, @card)";
                Cmd.ExecuteNonQuery();
            }
        }
        finally
        {
            DemoDb.Close();
        }
}

Противоположность этой функции чтение данных - выглядит похоже:

protected void LoadCommand_Click(object sender, EventArgs e)
{
        try
        {
            DemoDb.Open();
        }
        catch (Exception ex)
        {
            // Вместо этого нужно использовать лог-файл
            System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            return;
        }

        try
        {
            string SqlText = "SELECT * FROM ShopInfo WHERE UserId=@key";
            SqlCommand Cmd = new SqlCommand(SqlText, DemoDb);
            Cmd.Parameters.AddWithValue("@key", Membership.GetUser().ProviderUserKey);
            using (SqlDataReader Reader = Cmd.ExecuteReader())
            {
                if (Reader.Read())
                {
                    // Отобразить простые данные (в виде простого текста)
                    StreetText.Text = Reader["Street"].ToString();
                    ZipCodeText.Text = Reader["ZipCode"].ToString();
                    CityText.Text = Reader["City"].ToString();

                    // Расшифровать код кредитной карты
                    byte[] SecretCard = (byte[])Reader["CreditCard"];
                    CreditCardText.Text =
                        SymmetricEncryptionUtility.DecryptData(
                                    SecretCard, EncryptionKeyFile);
                }
            }
        }
        finally
        {
            DemoDb.Close();
        }
}

Опять-таки, для извлечения информации в функции используется текущее свойство ProviderUserKey зарегистрированного пользователя MembershipUser. При успешном извлечении она читает данные в виде открытого текста, а затем извлекает из таблицы базы зашифрованные байты. Эти байты затем расшифровываются и отображаются в текстовом поле кредитной карты. Результат показан на рисунке ниже:

Шифрование важной информации в базе данных

Для работоспособности этих примеров в проекте должен присутствовать файл ключа шифрования symmetric_key.config, который мы создали в предыдущей статье.

Пройди тесты
Лучший чат для C# программистов