Шифрование данных

87

Теперь, когда вы ознакомились с базовыми принципами организации криптографии .NET, самое время собрать все вместе. В следующих разделах будут созданы два класса, которые используют симметричный и асимметричный алгоритмы. В разделе "Шифрование информации в базе данных" один из этих классов будет применяться для шифрования ответственной информации, такой как номер кредитной карты, сохраняемой в базе данных, а в следующей статье "Шифрование строки запроса" будет показано, как шифровать строку URL-адреса HTTP-запроса GET или POST.

Для шифрования и расшифровки важной информации потребуется выполнить следующие шаги (они будут подробно объясняться в последующих разделах):

  1. Выберите и создайте алгоритм.

  2. Сгенерируйте и сохраните секретный ключ.

  3. Зашифруйте или расшифруйте информацию через CryptoStream.

  4. Закройте соответствующим образом исходный и целевой потоки.

После создания и тестирования своих служебных классов шифрования должна быть создана база данных для хранения секретной информации и написан код для шифрования и расшифровки этой секретной информации в базе данных.

Управление ключами

Прежде чем обратиться к деталям работы с классами шифрования, следует подумать об одном дополнительном моменте: где хранить ключ? Ключ, используемый для шифрования и расшифровки - это секрет, поэтому он должен храниться в безопасном месте. Часто разработчики думают, что лучше всего хранить ключ в исходном коде. Однако это одна из наиболее серьезных ошибок, которую только можно допустить в своем приложении. Предположим, что есть следующий код в составе кода библиотеки классов, которая будет компилироваться в двоичную DLL-библиотеку:

public static class MyEncryptionUtility
{
// Тссс!! Не говорите никому!
private const string MyKey = "m$%&kljasldk$%/65asjdl";

public static byte[] Encrypt(string data)
{
    // Использовать "MyKey" для шифрования данных
    return null;
}
}

Подобного рода ключи легко раскрыть, используя инструменты дизассемблирования. Достаточно просто запустить средство ILDASM и проанализировать класс. Конечно, вы определенно сможете найти этот ключ, как показано на рисунке ниже:

ILDASM с приведенным выше классом и секретным ключом

Если вы думаете, что эта проблема характерна только для управляемого кода, попробуйте сделать что-то подобное с неуправляемым приложением на C++. Создайте класс и включите в него секретное значение как константу приложения. Поскольку константные значения сохраняются в определенном разделе внутренних исполняемых файлов, выполните перечисленные ниже шаги:

  1. Установите комплект Microsoft SDK.

  2. Откройте окно командной строки и введите следующую команду:

    dumpbin /all BadProtectCPlus.exe /out:test.txt

  3. Откройте сгенерированный файл test.txt в текстовом редакторе и прокрутите текст до раздела .rdata. Где-то в этом разделе несложно найти жестко закодированный ключ.

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

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

Для защиты данных с помощью этого ключа в Windows предусмотрен интерфейс Data Protection API (DPAPI). При использовании этого API-интерфейса вы не имеете прямого доступа к этому ключу, а просто указываете системе шифровать или расшифровать что-либо с помощью машинного ключа. Таким образом, это позволяет решить задачу управления ключами: приложение может шифровать используемый в нем ключ с помощью DPAPI. Для этой цели в .NET Framework поддерживается класс System.Security.Cryptography.ProtectedData, который применяется следующим образом:

byte[] ProtData = 
   ProtectedData.Protect(ClearBytes, null, DataProtectionScope.LocalMachine);

Чтобы использовать класс ProtectedData для защиты важной информации, понадобится добавить ссылку на сборку System.Security.dll и импортировать пространство имен System.Security.Cryptography. Возможными контекстами являются LocalMachine и CurrentUser. В первом варианте используется машинный ключ, а во втором - ключ, сгенерированный для текущего зарегистрированного пользователя.

Если пользователь является администратором машины и обладает определенными знаниями, он может расшифровать данные, написав программу, которая вызывает приведенную выше функцию. Однако все же это определенно воздвигает барьер и затрудняет доступ к ключу. И если пользователь - не администратор и не имеет прав на работу с DPAPI, он не сможет расшифровать данные, зашифрованные машинным ключом.

Не используйте DPAPI для шифрования информации в базе данных. Хотя DPAPI легко применять вместе с .NET Framework, с этим методом связана одна проблема: если используется настройка DataProtectionScope.LocalMachine, то зашифрованные данные будут привязаны к машине. Таким образом, если машина выходит из строя, и данные должны быть восстановлены на другой машине, на этот случай необходимо иметь резервную копию ключа в другом безопасном месте. Для использования DPAPI в сценариях с веб-фермой понадобится запускать приложение от имени учетной записи пользователя домена и применять ключ, созданный для пользовательского профиля (DataProtectionScope.CurrentUser). Чтобы не приходилось применять пользователя домена из внутренней сети компании, рекомендуется создать отдельный домен для веб-фермы.

Использование симметричных алгоритмов

Как уже упоминалось, симметричные алгоритмы шифрования используют один ключ и для шифрования и расшифровки данных. В следующем разделе вы узнаете подробности создания служебного класса, выполняющего шифрование и расшифровку важных данных. Затем этот класс можно будет многократно использовать в нескольких веб-приложениях. Созданный служебный класс будет иметь показанную ниже структуру, и его можно будет применять для шифрования и расшифровки строковых данных. (Обратите внимание, что на основе ProtectKey позже будет написан код, принимающий решение, шифровать ключ с использованием DPAPI или нет. Значение true означает, что ключ должен быть защищен с помощью DPAPI.)

public static class SymmetricEncryptionUtility
    {
private static bool _ProtectKey;
private static string _AlgorithmName;
public static string AlgorithmName
{
    get { return _AlgorithmName; }
    set { _AlgorithmName = value; }
}
public static bool ProtectKey
{
    get { return _ProtectKey; }
    set { _ProtectKey = value; }
}

public static void GenerateKey(string targetFile)
{
    // ...
}

public static void ReadKey(SymmetricAlgorithm algorithm, string keyFile)
{
    // ...
}

public static byte[] EncryptData(string data, string keyFile)
{
    // ...
}

public static string DecryptData(byte[] data, string keyFile)
{
    // ...
}
}

Поскольку этот класс является служебным с только статическими членами, его можно сделать статическим, чтобы никто не мог создавать его экземпляры. Класс предоставляет возможность для указания имени алгоритма (DES, TripleDES, RijnDael или RC2) в свойстве AlgorithmName.

Также он поддерживает операции генерирования нового ключа, чтения ключа из файла непосредственно в свойство ключа экземпляра алгоритма, а также шифрование и расшифровку данных. Для использования этого класса понадобится соответствующим образом указать имя алгоритма, а затем сгенерировать ключ, если он еще не существует. Затем потребуется просто вызывать методы EncryptData и DescryptData, которые внутри себя вызовут метод ReadKey для инициализации алгоритма. Свойство ProtectKey позволяет пользователю класса указать, нужно ли защищать ключ средствами DPAPI.

Ключи шифрования можно генерировать посредством классов алгоритмов. Метод GenerateKey выглядит так:

public static void GenerateKey(string targetFile)
{
    // Создать алгоритм
    SymmetricAlgorithm Algorithm = SymmetricAlgorithm.Create(AlgorithmName);
    Algorithm.GenerateKey();

    // Получить ключ
    byte[] Key = Algorithm.Key;

    if (ProtectKey)
    {
        // Использовать DPAPI для шифрования ключа
        Key = ProtectedData.Protect(
            Key, null, DataProtectionScope.LocalMachine);
    }

    // Сохранить ключ в файле key.config
    using (FileStream fs = new FileStream(targetFile, FileMode.Create))
    {
        fs.Write(Key, 0, Key.Length);
    }
}

Метод GenerateKey() класса SymmetricAlgorithm формирует новый ключ с помощью алгоритма, генерирующего строгие случайные криптографические числа через метод GenerateKey() созданного алгоритма, и инициализирует свойство Key этим новым ключом. Если вызывающий код имеет установленный в true флаг ProtectKey служебного класса, то реализация шифрует ключ с использованием DPAPI.

Метод ReadKey читает ключ из файла, созданного методом GenerateKey, как показано ниже:

public static void ReadKey(SymmetricAlgorithm algorithm, string keyFile)
{
    byte[] Key;

    using (FileStream fs = new FileStream(keyFile, FileMode.Open))
    {
        Key = new byte[fs.Length];
        fs.Read(Key, 0, (int)fs.Length);
    }

    if (ProtectKey)
        algorithm.Key = ProtectedData.Unprotect(Key, null, DataProtectionScope.LocalMachine);
    else
        algorithm.Key = Key;
}

Если ранее ключ был защищен, метод ReadKey() использует DPAPI для снятия защиты с ключа после чтения его из файла. Метод требует передачи ему существующего экземпляра симметричного алгоритма. Он напрямую инициализирует свойство Key алгоритма, так что этот ключ затем применяется автоматически во всех последующих операциях. В конечном итоге обе функции - EncryptData() и DecryptData() - используют функцию ReadKey().

Оба метода требуют параметра keyFile с путем к файлу, в котором хранится ключ. Оба они последовательно вызывают метод ReadKey для инициализации своего экземпляра алгоритма этим ключом. В то время как метод EncryptData принимает строку и возвращает байтовый массив с зашифрованным ее представлением, DecryptData принимает зашифрованный байтовый массив и возвращает строку в виде открытого текста:

Начнем с метода EncryptData():

public static byte[] EncryptData(string data, string keyFile)
{
    // Преобразовать строку data в байтовый массив
    byte[] ClearData = Encoding.UTF8.GetBytes(data);

    // Создать алгоритм шифрования
    SymmetricAlgorithm Algorithm = SymmetricAlgorithm.Create(AlgorithmName);
    ReadKey(Algorithm, keyFile);

    // Зашифровать информацию
    MemoryStream Target = new MemoryStream();

    // Сгенерировать случайный вектор инициализации (IV)
    // для использования с алгоритмом
    Algorithm.GenerateIV();
    Target.Write(Algorithm.IV, 0, Algorithm.IV.Length);

    // Зашифровать реальные данные
    CryptoStream cs = new CryptoStream(Target, Algorithm.CreateEncryptor(), CryptoStreamMode.Write);
    cs.Write(ClearData, 0, ClearData.Length);
    cs.FlushFinalBlock();

    // Вернуть зашифрованный поток данных в виде байтового массива
    return Target.ToArray();
}

Сначала метод преобразует строку в байтовый массив, поскольку все функции шифрования алгоритма требуют байтовых массивов в качестве входных параметров. Проще всего для этого применять класс Encoding из пространства имен System.Text. Далее метод создает алгоритм согласно свойству класса AlgorithmName. Это значение может быть одним из следующих: RC2, Rijndael, DES или TripleDES.

Фабричный метод SymmetricAlgorithm.Create() создает соответствующий экземпляр, в то время как дополнительные криптографические классы могут быть зарегистрированы в разделе <cryptographySettings> файла machine.config.

После этого метод создает поток в памяти, который в данном случае будет служить целевым для операции шифрования. Прежде чем класс начнет операцию шифрования через CryptoStream, он генерирует вектор инициализации (initialization vector - IV) и записывает его в целевой поток с первой позиции. Вектор инициализации добавляет случайные данные в шифрованный поток.

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

При использовании CryptoStream для шифрования информации не забывайте вызывать FlushFinalBlock() для обеспечения того, чтобы последний блок зашифрованных данных правильно записывался по назначению.

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

public static string DecryptData(byte[] data, string keyFile)
{
    // Создать алгоритм
    SymmetricAlgorithm Algorithm = SymmetricAlgorithm.Create(AlgorithmName);
    ReadKey(Algorithm, keyFile);

    // Расшифровать информацию
    MemoryStream Target = new MemoryStream();

    // Прочитать вектор инициализации (IV)
    // и инициализировать им алгоритм
    int ReadPos = 0;
    byte[] IV = new byte[Algorithm.IV.Length];
    Array.Copy(data, IV, IV.Length);
    Algorithm.IV = IV;
    ReadPos += Algorithm.IV.Length;

    CryptoStream cs = new CryptoStream(Target, Algorithm.CreateDecryptor(), 
        CryptoStreamMode.Write);
    cs.Write(data, ReadPos, data.Length - ReadPos);
    cs.FlushFinalBlock();

    // Получить байты из потока в памяти и преобразовать их в текст
    return Encoding.UTF8.GetString(Target.ToArray());
}

Структура функции расшифровки построена иначе. Она создает алгоритм и целевой поток для расшифрованной информации. Перед тем как расшифровать данные, необходимо прочитать вектор инициализации из входного зашифрованного потока, потому что он используется алгоритмом для последней трансформации. Затем применяется CryptoStream, как это делалось ранее, но с тем отличием, что на этот раз создается расшифровывающая трансформация. И, наконец, получается расшифрованное байтовое представление строки, которая была создана Encoding.UTF8.GetBytes(). Чтобы выполнить обратную операцию, необходимо вызвать метод GetString() кодирующего класса UTF-8 для получения текстового представления строки.

Использование класса SymmetricEncryptionUtility

Теперь можно создать страницу для тестирования класса. Это будет простая страница, которая позволит генерировать ключ и вводить данные в текстовом поле. Зашифрованные данные можно легко выводить с помощью Convert.ToBase64String(). Для расшифровки понадобится просто декодировать часть, закодированную Base64, обратно в байтовый массив. С помощью метода Convert.FromBase64String() необходимо получить зашифрованные байты обратно и отправить их методу DecryptData:

<body>
    <form id="form1" runat="server">
        <div style="text-align: center">
            <asp:Panel ID="MainPanel" runat="server" BorderStyle="Solid" BorderWidth="1px">
                <table border="0" style="width:100%">
                    <tr>
                        <td style="width: 252px; height: 45px; text-align: left">Шаг 1:<br />
                            Сгенерировать ключ шифрования</td>
                        <td style="height: 45px">
                            <asp:LinkButton ID="GenerateKeyCommand" runat="server"
                                 OnClick="GenerateKeyCommand_Click">Сгенерировать ключ</asp:LinkButton><br />
                            <asp:CheckBox ID="EncryptKeyCheck" runat="server" 
                                Text="Защита ключа с помощью DPAPI" /></td>
                    </tr>
                    <tr>
                        <td style="width: 252px; height: 24px; text-align: left">Шаг 2:<br />
                            Исходная строка</td>
                        <td style="height: 24px">
                            <asp:TextBox ID="ClearDataText" runat="server" Rows="5" TextMode="MultiLine" 
                                Width="98%" Columns="40"></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td style="width: 252px; text-align: left">Step 3:<br />
                            Зашифрованная строка</td>
                        <td>
                            <asp:TextBox ID="EncryptedDataText" runat="server" Rows="5" TextMode="MultiLine"
                                Width="98%" Columns="40"></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td style="width: 252px"></td>
                        <td>
                            <asp:LinkButton ID="EncryptCommand" runat="server" 
                                OnClick="EncryptCommand_Click">Зашифровать</asp:LinkButton>
                             
                            <asp:LinkButton ID="DecryptCommand" runat="server" 
                                OnClick="DecryptCommand_Click">Расшифровать</asp:LinkButton>
                             
                            <asp:LinkButton ID="ClearCommand" runat="server" 
                                OnClick="ClearCommand_Click">Очистить</asp:LinkButton>
                        </td>
                    </tr>
                </table>
            </asp:Panel>

        </div>
    </form>
</body>
public partial class _Default : System.Web.UI.Page
{
    private string KeyFileName;
    private string AlgorithmName = "DES";

    protected void Page_Load(object sender, EventArgs e)
    {
        SymmetricEncryptionUtility.AlgorithmName = AlgorithmName;
        KeyFileName = Server.MapPath("~/") + "\\symmetric_key.config";
    }

    protected void GenerateKeyCommand_Click(object sender, EventArgs e)
    {
        try
        {
            SymmetricEncryptionUtility.ProtectKey = EncryptKeyCheck.Checked;
            SymmetricEncryptionUtility.GenerateKey(KeyFileName);

            Response.Write("Ключ успешно сгенерирован!");
        }
        catch
        {
            Response.Write("Возникла ошибка при генерации ключа!");
        }
    }

    protected void EncryptCommand_Click(object sender, EventArgs e)
    {
        // Проверить наличие ключа
        if (!File.Exists(KeyFileName))
        {
            Response.Write("Отсутствует ключ шифрования!");
        }

        try
        {
            byte[] data = SymmetricEncryptionUtility.EncryptData(ClearDataText.Text, KeyFileName);
            EncryptedDataText.Text = Convert.ToBase64String(data);
        }
        catch
        {
            Response.Write("Ошибка при шифровании данных!");
        }
    }

    protected void DecryptCommand_Click(object sender, EventArgs e)
    {
        // Проверить наличие ключа
        if (!File.Exists(KeyFileName))
        {
            Response.Write("Отсутствует ключ шифрования!");
        }

        try
        {
            byte[] data = Convert.FromBase64String(EncryptedDataText.Text);
            ClearDataText.Text = SymmetricEncryptionUtility.DecryptData(data, KeyFileName);
        }
        catch
        {
            Response.Write("Ошибка при дешифровании данных!");
        }
    }

    protected void ClearCommand_Click(object sender, EventArgs e)
    {
        ClearDataText.Text = "";
        EncryptedDataText.Text = "";
    }
}

В приведенной странице используется алгоритм DES, потому что так указано в AlgorithmName. Внутри события Click() кнопки GenerateKeyCommand вызывается метод GenerateKey(). В зависимости от того, уставлен ли флажок на странице, ключ шифруется через DPAPI или нет. После того, как данные зашифрованы служебным классом внутри события Click() кнопки EncryptCommand, зашифрованные байты преобразуются в строку Base64 и затем записываются в текстовое поле EcryptedDataText. Таким образом, чтобы расшифровать информацию вновь, для этого потребуется создать байтовый массив на основе представления строки Base64 и затем вызвать метод расшифровки.

Результат показан на рисунке ниже:

Страница для тестирования симметричного алгоритма

Использование асимметричных алгоритмов

Асимметричные алгоритмы используются подобно симметричным. Отличий совсем немного. Главное отличие связано с управлением ключами. Если симметричные алгоритмы имеют дело с одним ключом, то в асимметричных используются два ключа: один для шифрования данных (открытый ключ) и другой для их расшифровки (секретный ключ). В то время как открытый ключ может быть доступен всем, кто желает зашифровать данные, секретный должен быть доступен только тем, кто ее расшифровывает. В этом разделе мы создадим служебный класс, подобный показанному ранее.

Поскольку .NET Framework поставляется только с одним асимметричным алгоритмом для реального шифрования данных (RSA, вспомните, что DSA используется только для цифровых подписей), нет необходимости включать способ выбора алгоритма:

public static class AsymmetricEncryptionUtility
{
    public static string GenerateKey(string targetFile)
    {
        // ...
    }

    private static void ReadKey(RSACryptoServiceProvider algorithm, string keyFile)
    {
        // ...
    }

    public static byte[] EncryptData(string data, string publicKey)
    {
        // ...
    }

    public static string DecryptData(byte[] data, string keyFile)
    {
        // ...
    }
}

Метод GenerateKey() создает экземпляр алгоритма RSA для генерации ключа. Он сохраняет в файле только секретный ключ, защитив его посредством DPAPI и возвратив открытый ключ в виде XML-строки с использованием метода ToXmlString() алгоритма. Это довольно реалистичная концепция - секретный ключ обычно сохраняется приложением в секрете, в то время как открытый ключ разделяется с другими, чтобы иметь возможность шифровать информацию, которая впоследствии расшифровывается приложением с помощью его секретного ключа:

public static string GenerateKey(string targetFile)
{
    RSACryptoServiceProvider Algorithm = new RSACryptoServiceProvider();

    // Сохранить секретный ключ
    string CompleteKey = Algorithm.ToXmlString(true);
    byte[] KeyBytes = Encoding.UTF8.GetBytes(CompleteKey);

    KeyBytes = ProtectedData.Protect(KeyBytes,
            null, DataProtectionScope.LocalMachine);

    using (FileStream fs = new FileStream(targetFile, FileMode.Create))
    {
        fs.Write(KeyBytes, 0, KeyBytes.Length);
    }

    // Вернуть открытый ключ
    return Algorithm.ToXmlString(false);
}

Код, вызывающий эту функцию, должен где-то сохранить полученный открытый ключ - он понадобится для шифрования информации. Извлечь ключ в XML-представлении можно с помощью метода ToXmlString(). Параметр указывает, должна включаться информация секретного ключа (true) или же нет (false). Таким образом, функция GenerateKey сначала вызывает функцию с параметром true для сохранения полной информации о ключах в файле, а затем вызывает ее с аргументом false для включения только открытого ключа. Впоследствии метод ReadKey() просто читает ключ из файла и инициализирует переданный экземпляр алгоритма через FromXml(), который противоположен методу ToXmlString():

private static void ReadKey(RSACryptoServiceProvider algorithm, string keyFile)
{
    byte[] KeyBytes;

    using (FileStream fs = new FileStream(keyFile, FileMode.Open))
    {
        KeyBytes = new byte[fs.Length];
        fs.Read(KeyBytes, 0, (int)fs.Length);
    }

    KeyBytes = ProtectedData.Unprotect(KeyBytes,
            null, DataProtectionScope.LocalMachine);

    algorithm.FromXmlString(Encoding.UTF8.GetString(KeyBytes));
}

На этот раз метод ReadKey() используется только функцией расшифровки. Функция EncryptData() принимает в виде параметра XML-представление открытого ключа, возвращенного методом GenerateKey(), поскольку секретный ключ для выполнения шифрования не требуется. Шифрование и расшифровка с помощью RSA осуществляются так, как показано ниже:

public static byte[] EncryptData(string data, string publicKey)
{
    // Создать алгоритм на основе открытого ключа
    RSACryptoServiceProvider Algorithm = new RSACryptoServiceProvider();
    Algorithm.FromXmlString(publicKey);

    // Зашифровать данные
    return Algorithm.Encrypt(
           Encoding.UTF8.GetBytes(data), true);
}

public static string DecryptData(byte[] data, string keyFile)
{
    RSACryptoServiceProvider Algorithm = new RSACryptoServiceProvider();
    ReadKey(Algorithm, keyFile);

    byte[] ClearData = Algorithm.Decrypt(data, true);
    return Convert.ToString(
           Encoding.UTF8.GetString(ClearData));
}

Теперь можно построить тестовую страницу, как показано ниже:

<body>
    <form id="form1" runat="server">
        <div style="text-align: center">
            <asp:Panel ID="MainPanel" runat="server" BorderStyle="Solid" BorderWidth="1px">
                <table border="0" style="width:100%">
                    <tr>
                        <td style="width: 252px; height: 45px; text-align: left">Шаг 1:<br />
                            Сгенерировать ключ шифрования</td>
                        <td style="height: 45px">
                            <asp:LinkButton ID="GenerateKeyCommand" runat="server" OnClick="GenerateKeyCommand_Click">Сгенерировать ключ</asp:LinkButton><br />
                            <asp:TextBox ID="PublicKeyText" runat="server" Rows="5" TextMode="MultiLine"
                                Columns="40" Width="600px"></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <td style="width: 252px; height: 24px; text-align: left">Шаг 2:<br />
                            Исходная строка</td>
                        <td style="height: 24px">
                            <asp:TextBox ID="ClearDataText" runat="server" Rows="5" TextMode="MultiLine" 
                                Width="98%" Columns="40"></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td style="width: 252px; text-align: left">Step 3:<br />
                            Зашифрованная строка</td>
                        <td>
                            <asp:TextBox ID="EncryptedDataText" runat="server" Rows="5" TextMode="MultiLine"
                                Width="98%" Columns="40"></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td style="width: 252px"></td>
                        <td>
                            <asp:LinkButton ID="EncryptCommand" runat="server" OnClick="EncryptCommand_Click">Зашифровать</asp:LinkButton>
                             
                            <asp:LinkButton ID="DecryptCommand" runat="server" 
                                OnClick="DecryptCommand_Click">Расшифровать</asp:LinkButton>
                             
                            <asp:LinkButton ID="ClearCommand" runat="server" 
                                OnClick="ClearCommand_Click">Очистить</asp:LinkButton>
                        </td>
                    </tr>
                </table>
            </asp:Panel>

        </div>
    </form>
</body>
public partial class _Default : System.Web.UI.Page
{
    private string KeyFileName;

    protected void Page_Load(object sender, EventArgs e)
    {
        KeyFileName = Server.MapPath("~/") + "\\asymmetric_key.config";
    }

    protected void GenerateKeyCommand_Click(object sender, EventArgs e)
    {
        try
        {
            PublicKeyText.Text = AsymmetricEncryptionUtility.GenerateKey(KeyFileName);
            Response.Write("Ключ успешно сгенерирован!");
        }
        catch
        {
            Response.Write("Возникла ошибка при генерации ключа!");
        }
    }

    protected void EncryptCommand_Click(object sender, EventArgs e)
    {
        // Проверить наличие ключа
        if (!File.Exists(KeyFileName))
        {
            Response.Write("Отсутствует ключ шифрования!");
        }

        try
        {
            byte[] data = AsymmetricEncryptionUtility.EncryptData(
                ClearDataText.Text, PublicKeyText.Text);
            EncryptedDataText.Text = Convert.ToBase64String(data);
        }
        catch
        {
            Response.Write("Ошибка при шифровании данных!");
        }
    }

    protected void DecryptCommand_Click(object sender, EventArgs e)
    {
        // Проверить наличие ключа
        if (!File.Exists(KeyFileName))
        {
            Response.Write("Отсутствует ключ шифрования!");
        }

        try
        {
            byte[] data = Convert.FromBase64String(EncryptedDataText.Text);
            ClearDataText.Text = AsymmetricEncryptionUtility.DecryptData(data, KeyFileName);
        }
        catch
        {
            Response.Write("Ошибка при дешифровании данных!");
        }
    }

    protected void ClearCommand_Click(object sender, EventArgs e)
    {
        ClearDataText.Text = "";
        EncryptedDataText.Text = "";
    }
}
Страница для тестирования асимметричного алгоритма
Пройди тесты
Лучший чат для C# программистов