Средства безопасности ASP.NET. Часть 3 - Криптография

Источник: RSDN Magazine, #3'2004
Сергей Бакланов

Оглавление

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

Классификация

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

Классификация на основе области применения разделяет все криптосистемы на две группы: на системы ограниченного использования и на системы общего использования . К первой группе относятся криптометоды, стойкость которых определяется тем, насколько долго алгоритм шифрования/дешифрования будет находиться в секрете. Зачастую примерами таких криптосистем являются системы, не использующие пароли. Ко второй группе, соответственно, принадлежат криптометоды, стойкость которых не зависит от доступности алгоритма, т. е., чаще всего, их надёжность упирается в ключ.

Криптография, кроме сокрытия информации от "чужих глаз и ушей", также позволяет убедиться в достоверности полученных данных, т. е. определить, были ли они искажены при передаче; а также позволяет определить отправителя, иными словами, даёт некоторую гарантию, что данные получены от легального отправителя. На основе такой широкой области применения криптометодов появился ещё один тип классификации - классификация по принципу действия.

ПРИМЕЧАНИЕ

Под термином " Искажение данных при передаче " понимается умышленный перехват и изменение информации при её трансляции по сетям.

Официально члены, классифицированные по принципу действия, принято назвать криптографическими примитивами (Cryptographic Primitives). Существуют следующие примитивы:

Криптографический примитив

Описание

Шифрование с секретным ключом (симметричная система) Представляет данные в недоступном для третьих лиц виде, используя единый секретный ключ для шифрования и дешифрования.
Шифрование с открытым ключом (асимметричная система) Представляет данные в недоступном для третьих лиц виде, используя пару из открытого и секретного ключей для шифрования и дешифрования.
Криптографическая подпись Позволяет удостовериться, что данные получены от соответствующего отправителя, путём создания цифровой подписи, свойственной лишь данному отправителю. Использует хэш-функции.
Криптографическое хэширование Преобразует данные любой длины в сочетание байтов фиксированной длины. Применяется при верификации данных.

Рассмотрим эти примитивы поподробнее.

Шифрование с секретным ключом

Криптометоды с секретным ключом, или как их ещё называют, симметричные методы , используют один и тот же ключ для шифрования и дешифрования, поэтому его нужно хранить в секрете от третьих лиц, поскольку, зная ключ и имея соответствующий криптоалгоритм, злоумышленник может дешифровать данные. Симметричные системы отличаются высокой скоростью работы (по сравнению с асимметричными), что позволяет применять их для шифрования крупных объёмов данных.

Симметричные криптосистемы делятся в свою очередь ещё на две группы: на блочные и поточные методы.

Блочные шифры применяют одно и то же преобразование к тексту, разбитому на блоки, длина которых может быть равна 8, 16, 24, 32 байтам в зависимости от криптометода. Криптосистема, предоставляемая .NET, работает по принципу построения цепочки блочных шифров (Cipher Block Chaining - CBC), которая использует ключ и вектор инициализации (Initialization Vector - IV). Простая блочная система шифрования, не использующая вектор инициализации, преобразует один и тот же блок исходного текста в тот же самый блок зашифрованного текста, т. е. безо всяких перемещений и рекомбинаций. Если у вас был дублированный блок в исходном тексте, то он появится и в зашифрованном. В результате, зная структуру исходного текста, злоумышленник может дешифровать соответствующую часть криптограммы и определить ключ шифрования. Чтобы этого избежать, в технологии CBC информация из предыдущего блока внедряется в шифруемый следующий блок. Поскольку такой подход использует предыдущий блок для шифрования следующего, то IV шифрует самый первый блок. Это позволяет защитить заголовок (первый блок), чтобы взломщик не мог использовать его для получения ключа.

ПРИМЕЧАНИЕ

Необходимость использования IV для шифрования 1-го блока возникает из-за того, что применение предыдущего блока для шифрования следующего начинается лишь со 2-го блока - у 1-го нет предыдущего блока - он и так стоит в самом начале.

Другая группа симметричных криптосистем - поточные методы. Эти методы применяют изменяющиеся во время шифрования преобразования к наборам символов, образующим единый исходный текст.

Среди симметричных алгоритмов можно выделить следующие: DES (Data Encryption Standard), DES 2, различные вариации TripleDES, Rijndael/AES (Advanced Encryption Standard), RC2, RC4, ГОСТ 28147-89… Если взглянуть на их алгоритмы, то очевидно, что многие из них основаны на операторе XOr. Вообще, впервые оператор " исключающий или " был применён в так называемом методе одноразовых блокнотов , изученном Клодом Шенноном. Действовал этот метод по следующему принципу: бралась строка исходного текста и строка ключа такой же длины, после чего каждый символ исходного текста XOr’ился с соответствующим символом строки ключа. Этот метод работает и как шифровщик, и как дешифровщик, поскольку повторный вызов функции XOr возвращает исходное значение.

ПРИМЕЧАНИЕ

Алгоритм TripleDES имеет различные конфигурации, немного отличающиеся по принципу работы:

  • DES-EEE3 - тройное шифрование с различными ключами.
  • DES-EDE3 - шифрует, дешифрует и ещё раз шифрует с различными ключами.
  • DES-EEE2 - тройное шифрование, но одинаковые ключи только при первой и третьей итерациях.
  • DES-EDE2 - шифрование, дешифрование и ещё раз шифрование с одинаковыми ключами при первой и третьей итерациях.

Инфраструктура .NET Framework содержит классы для работы со следующими методами: DES, TripleDES, RC2, Rijndael. Но если воспользоваться услугами CryptoAPI, то можно также получить возможность работать с поточным симметричным методом шифрования RC4. Позже мы рассмотрим примеры использования всех этих методов, а сейчас перейдём к следующему криптографическому примитиву - к шифрованию с открытым ключом (асимметричная система).

Шифрование с открытым ключом

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

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

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

Получив от актора Б открытый ключ, актор А шифрует сообщение с его помощью, после чего отправляет уже готовую криптограмму актору Б. Наконец, актор Б успешно получает криптотекст и дешифрует его своим, никому не известным, секретным ключом.

Рисунок 1. Принцип работы асимметричной системы

Асимметричные криптометоды очень медленны в сравнении с симметричными, и поэтому применимы для небольших объёмов информации. Такое ограничение также вызвано тем, что асимметричные алгоритмы имеют буфер фиксированной длины. Зачастую асимметричные алгоритмы применяются для шифрования ключей от симметричных систем, поскольку открытый ключ асимметричного криптометода можно спокойно отправлять в плавание по сетям, чего не скажешь о ключах симметричных методов.

ПРИМЕЧАНИЕ

Для сравнения: программа, реализующая шифрование симметричным криптометодом DES, будет работать примерно в 100 раз быстрее, чем программа с асимметричным RSA. А при аппаратной реализации этих алгоритмов соотношение составит 1000-10000 раз.

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

Среди алгоритмов с открытым ключом можно выделить следующие: RSA (Rivest-Shamir-Adleman), DSA (Digital Signature Standard), DH (Diffie-Hellman). Из этого небольшого списка .NET поддерживает лишь первые два - RSA и DSA.

Цифровая подпись

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

Пусть актор А решил отправить письмо актору Б, и чтобы актор Б не сомневался в достоверности источника, актор А оставил в письме цифровую подпись. Для этого актор А сперва хэшировал всё сообщение, в результате чего получились компактно и уникально трансформированные данные. Далее актор А использует асимметричный криптометод для генерации пары ключей и отправляет открытый ключ актору Б. После этого хэш шифруется асимметричным методом, но порядок шифрования обратный. Данные шифрует не обладатель открытого ключа, т. е. актор Б, а обладатель секретного ключа - актор А. Как только хэш был зашифрован, получилась готовая цифровая подпись, которую актор А отправляет актору Б вместе с оригинальным сообщением. Актор Б получает письмо и выполняет обратные действия: он дешифрует подпись полученным от актора А открытым ключом, хэширует данные соответствующим хэш-методом и сверяет результат с оригинальным сообщением. Если они идентичны, значит можно считать, что сообщение действительно пришло от актора А.

ПРЕДУПРЕЖДЕНИЕ

Цифровая подпись не защищает данные от несанкционированного доступа - она лишь позволяет лишний раз убедиться или же разубедиться в достоверности отправителя. Для обеспечения безопасности транслируемых данных их нужно шифровать.

В платформе .NET Framework для создания и проверки цифровой подписи существует несколько классов. О них мы поговорим чуть позже.

Хэширование

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

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

Пусть актор А решил отправить актору Б сообщение, перед этим сделав его хэш и сохранив результат у себя. Актор Б получает сообщение и, чтобы проверить его достоверность, тоже создаёт хэш (тем же самым хэш методом) полученного сообщения и отправляет его актору А для сравнения. Если хэши у акторов А и Б будут одинаковыми, значит данные не были модифицированы, в противном случае, есть все основания полагать, что сообщение было перехвачено и изменено.

Сегодня в криптографии используются различные алгоритмы хэширования. Вот список наиболее известных из них: MD2 (Message Digest), MD4, MD5, SHA1, SHA256, SHA384, SHA512, HMAC. Большинство из перечисленных алгоритмов реализовано в инфраструктуре .NET Framework.

Криптография средствами .NET

В большинстве случаев применение криптографических инструментов в среде .NET Framework сводится к обращению к пространству имён System.Security.Cryptography, поскольку именно в нём содержатся все основные классы и интерфейсы для выполнения криптоопераций.

Большинство поддерживаемых криптометодов используют CryptoAPI, но всё же есть некоторые новинки. Среди них: Rijndael/AES, SHA256, SHA384, SHA512.

ПРИМЕЧАНИЕ

Криптометод AES (Advanced Encryption Standard) есть не что иное, как расширенный DES, что, собственно, и следует из его названия. Этот метод был принят в США взамен DES.

Когда мы хотим что-то зашифровать, то исключительно с интуитивной точки зрения мы хотим работать со строкой, но большинство криптометодов не работает со строками - они оперируют массивами байтов и потоками. Для тех, кто впервые берётся за криптографию, такой подход может показаться несколько непривычным.

Симметричные системы

Простейшее шифрование с помощью метода DES

Чтобы не ходить вокруг да около, давайте создадим Web-приложение, выполняющее шифрование методом DES. Для этого создайте новый проект типа Web Application и добавьте в него следующий код:

Листинг 1.1 - CryptoTest/default.aspx

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="CryptoTest.WebForm1"%>
<html>
  <head>
    <title>CryptoTest</title>
  </head>
  <body MS_POSITIONING="GridLayout">

    <form id="Form1" method="post" runat="server">
    <b>Clear Text:</b><br>
    <textarea id="txt" runat=server></textarea>
    <asp:Button ID="btnEncrypt" Text="Encrypt" Runat=server/><br><br>
    
    <b>Encrypted Text:</b><br>
    <table><tr><td bgcolor=LightGrey>
      <asp:Label ID="lblResult" Runat=server/>
    </td></tr></table>
    </form>

  </body>
</html>

Листинг 1.2 - CryptoTest/default.aspx.vb

Imports System.Security.Cryptography
Imports System.Text
Imports System.IO

Public Class WebForm1
    Inherits System.Web.UI.Page

    Protected WithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
    Protected WithEvents btnEncrypt As System.Web.UI.WebControls.Button
    Protected WithEvents lblResult As System.Web.UI.WebControls.Label

#Region " Web Form Designer Generated Code "

    'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()

    End Sub

    'NOTE: The following placeholder declaration is required by the 
    ' Web Form Designer. Do not delete or move it.
    Private designerPlaceholderDeclaration As System.Object

    Private Sub Page_Init(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Init
        'CODEGEN: This method call is required by the Web Form Designer
        'Do not modify it using the code editor.
        InitializeComponent()
    End Sub

#End Region

    Private Sub btnEncrypt_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles btnEncrypt.Click
        ' Провайдер DES-шифрования
        Dim DES As New DESCryptoServiceProvider
        ' Интерфейс-шифратор
        Dim DES_Encryptor As ICryptoTransform = DES.CreateEncryptor
        ' Файловый поток
        Dim fs As New FileStream(Server.MapPath("temp.dat"), FileMode.Create)
        ' Поток шифрования
        Dim DESCryptoStream As New CryptoStream(fs, _
             DES_Encryptor, CryptoStreamMode.Write)
        ' Класс для получения массива байтов из строки и массив байтов
        Dim enc As New UnicodeEncoding, bytes() As Byte

        '===========ЭТАП 1: шифруем данные и сохраняем их в файле============
        ' Получаем массив байтов из строки поля txt
        bytes = enc.GetBytes(txt.Value)
        ' Шифруем данные
        DESCryptoStream.Write(bytes, 0, bytes.Length)
        ' Закрываем потоки
        DESCryptoStream.Close()
        fs.Close()
        fs = Nothing

        '===========ЭТАП 2: Читаем данные из сохранённого файла=============
        ' Открываем поток
        fs = New FileStream(Server.MapPath("temp.dat"), FileMode.Open)
        ' Инициализируем класс-читатель потока
        Dim sr As New StreamReader(fs)
        ' Читаем зашифрованный текст из файлового потока
        lblResult.Text = sr.ReadToEnd
        ' Закрываем потоки
        sr.Close()
        fs.Close()

        '===========ЭТАП 3: Удаляем временный файл 'temp.dat'===============
        File.Delete(Server.MapPath("temp.dat"))
    End Sub
End Class

В листинге 1.2 все основные действия происходят в обработчике события нажатия кнопки btnEncrypt . Давайте рассмотрим его подробнее. Вначале создаётся экземпляр провайдера шифрования. Для каждого криптометода существует свой провайдер, например, DESCryptoServiceProvider, RSACryptoServiceProvider, RijndealManaged и др. Их имена объявлены в следующих форматах: <ИмяКриптометода>CryptoServiceProvider или <ИмяКриптометода>Managed . После инициализации криптопровайдера создаётся переменная, реализующая интерфейс-шифратор; далее открывается поток для записи данных в файл. После этого инициализируется криптопоток, в аргументах которого задаётся конечный поток данных (в нашем случае - это файловый поток fs); способ криптографической трансформации, т. е. интерфейс-шифратор, и действие, которое необходимо выполнить с данными. Из возможных действий выделяются чтение и запись.

ПРИМЕЧАНИЕ

В качестве конечного потока данных (первый атрибут конструктора класса CryptoStream) чаще всего выступает файловый поток, но это вовсе не означает, что вы не можете использовать иные типы потоков. Например, в ASP.NET-приложениях можно использовать поток Response.OutputStream для вывода информации.

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

DESCryptoStream.Write(bytes, 0, bytes.Length)

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

В конце все потоки закрываются, потом открываются снова, но уже для чтения из предварительно созданного файла ( temp.dat ), далее файловый поток вновь закрывается, а временный файл удаляется.

Во время выполнения приложения вы можете столкнуться с сообщением, показанным на рисунке 2. Это сообщение говорит, что создание файла temp.dat запрещено. Такое происходит, если пользователя ASPNET нет в списке ACL к папке Web-приложения.

ПРИМЕЧАНИЕ

По умолчанию процесс ASP.NET-приложений запускается от имени пользователя ASPNET, в чём вы можете убедиться, открыв Task Manager на закладке Процессы (рисунок 3).

Рисунок 2. Система не позволяет создать файл на сервере: доступ запрещён.

Рисунок 3. По умолчанию процесс aspnet_wp.exe запускается от имени пользователя ASPNET.

Чтобы исправить положение, можно:

1. В explorer.exe открыть свойства папки Web-приложения (адрес приложения может быть следующим: C:\Inetpub\wwwroot\<ИмяПриложения> ), перейти на закладку Безопасность и добавить пользователя ASPNET в список допустимых субъектов.

2. Применить заимствование полномочий (имперсонацию) для Web-приложения. В этом случае можно либо строго прописать необходимого пользователя для заимствования его прав, либо заимствовать права от пользователя, запустившего процесс Web-приложения. Для этого достаточно изменить файл Web.config следующим образом:

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>

    <authentication mode="Windows" /> 

    <authorization>
        <allow users="*" />
    </authorization>
    <identity impersonate="true"/>

  </system.web>
</configuration>

3. Всё, что нужно было сделать - это добавить строчку <identity impersonate="true"/>

СОВЕТ

Информацию о заимствовании полномочий можно найти во 2-й части этой статьи, посвященной авторизации.

Теперь, если приложение работает без ошибок, можно поработать с шифрованием. Для этого нужно ввести какой-нибудь текст в поле Clear Text и нажать на кнопку Encrypt . Результат должен быть подобен приведенному на рисунке 4.

ПРИМЕЧАНИЕ

Заметьте, что повторное шифрование одной и той же строки возвращает различные данные.

Рисунок 4. Результат успешного выполнения приложения.

Добавляем инициализационный вектор (IV) и ключ

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

Создайте новый проект типа WebApplication и используйте листинги 2.1 и 2.2:

Листинг 2.1: AdvCrypter/default.aspx

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="AdvCrypter.WebForm1"%>
<html>
  <head>
    <title>AdvCrypter</title>
  </head>
  <body MS_POSITIONING="GridLayout">

    <form id="Form1" method="post" runat="server">
    <b>Clear Text:</b><br>
    <textarea id="txt" runat=server></textarea>
    <asp:Button ID="btnEncrypt" Runat=server Text="Encrypt"/>
    </form>

  </body>
</html>

Обратите внимание, что количество элементов уменьшилось, т. к. для отображения результата используется поток вывода объекта Response.

Листинг 2.2: AdvCrypter/default.aspx.vb

Imports System.Security.Cryptography
Imports System.Text

Public Class WebForm1
  Inherits System.Web.UI.Page

  Protected WithEvents btnEncrypt As System.Web.UI.WebControls.Button
  Protected WithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> _
  Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required by 
  ' the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Private Sub btnEncrypt_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles btnEncrypt.Click
    ' Провайдер DES-шифрования
    Dim DES As New DESCryptoServiceProvider
    ' Генерируем ключ
    DES.GenerateKey()
    ' Генерируем IV
    DES.GenerateIV()

    ' Интерфейс-шифратор, для создания которого передаются ключ и IV
    Dim DES_Encryptor As ICryptoTransform = _
      DES.CreateEncryptor(DES.Key, DES.IV)
    ' Поток шифрования
    Dim DESCryptoStream As New CryptoStream(Response.OutputStream, _
      DES_Encryptor, CryptoStreamMode.Write)
    ' Класс для получения массива байтов из строки и массив байтов
    Dim enc As New UnicodeEncoding, bytes() As Byte

    ' Получаем массив байтов из строки поля txt
    bytes = enc.GetBytes(txt.Value)
    ' Шифруем данные
    DESCryptoStream.Write(bytes, 0, bytes.Length)
    ' Закрываем потоки
    DESCryptoStream.Close()
  End Sub
End Class

В коде появились 2 новые строки, в которых через провайдер алгоритма DES генерируются ключ и IV. Затем для получения ссылки на интерфейс трансформации используется другой прототип перегруженной функции DES.CreateEncryptor, в котором в качестве аргументов передаются ключ и IV.

ПРИМЕЧАНИЕ

GenerateKey и GenerateIV - это (с точки зрения VB.NET) не функции, а процедуры, т. е. они не возвращают значения, а задают свойства Key и IV в объекте SymmetricAlgorithm . Именно поэтому в листинге 2.2 сначала вызываются функции генерации, а потом значения берутся из свойств Key и IV.

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

ПРЕДУПРЕЖДЕНИЕ

Если в криптометоде применяются ключ и IV, то их следует сохранить в секрете от посторонних лиц, поскольку эти данные будут использованы при дешифровании.

Комбинирование криптометодов

В предыдущих примерах мы работали лишь с методом DES, но существует также ряд других методов. В этом разделе будет рассмотрен процесс создания Windows-приложения, использующего одновременно все симметричные алгоритмы, которые поддерживаются платформой .NET. Плюс к этому будет добавлено дешифрование, но взамен отключим поддержку ключей и IV, поскольку иначе их придётся запоминать для каждого файла в отдельности, а хранить их в открытом виде на диске ненадёжно.

ПРИМЕЧАНИЕ

Благодаря тому, что все симметричные алгоритмы в среде .NET Framework происходят от класса SymmetricAlgorithm, их одновременная поддержка значительно упрощается.

Создайте новый проект Windows-приложения и в нем - форму (см. рисунок 5). Чтобы не тратить время на создание формы, можно воспользоваться кодом из конструктора листинга 3.1.

Рисунок 5. Примерный вид формы комбинированного шифрования/дешифрования.

Листинг 3.1: Пример комбинированного шифрования и дешифрования

Imports System.IO
Imports System.Security.Cryptography
Imports System.Text

Public Class Form1
  Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "
  ' Эта часть кода доступна на CD
#End Region

  ' ключ и IV для DES и RC2
  Dim IV() As Byte = {172, 44, 172, 193, 7, 48, 131, 165}
  Dim Key() As Byte = {93, 252, 76, 113, 209, 29, 253, 168}
  ' Ключ и IV для TripleDES
  Dim TripleKey() As Byte = {77, 79, 156, 172, 12, 40, 96, 226, 93, _
    78, 90, 103, 186, 78, 117, 0, 85, 127, 114, 91, 148, 210, 242, 255}
  Dim TripleIV() As Byte = {19, 127, 43, 85, 21, 117, 80, 151}
  ' Ключ и IV для AES
  Dim aesKey() As Byte = {120, 6, 86, 102, 66, 236, 129, 91, 164, _
    164, 192, 68, 70, 34, 40, 254, 107, 174, 201, 46, 168, 19, 125, _
    202, 188, 52, 75, 23, 108, 94, 114, 27}
  Dim aesIV() As Byte = {196, 161, 224, 67, 23, 143, 39, 43, 188, 91, _
    247, 125, 97, 95, 246, 26}

  Private Sub tlb_ButtonClick(ByVal sender As System.Object, _
    ByVal e As System.Windows.Forms.ToolBarButtonClickEventArgs) _
    Handles tlb.ButtonClick

    Dim fs As FileStream
    Dim sr As StreamReader, sw As StreamWriter

    Select Case tlb.Buttons.IndexOf(e.Button)
      Case 1      ' New
        Text = ""
        txt1.Text = ""
        txt2.Text = ""
      Case 2      ' Open
        If dlgO.ShowDialog = DialogResult.OK Then
          fs = New FileStream(dlgO.FileName, FileMode.Open)
          sr = New StreamReader(fs, Encoding.Default)

          ' Читаем данные
          txt1.Text = sr.ReadToEnd
          txt2.Text = ""
          Text = dlgO.FileName
          ' Закрываем потоки
          sr.Close()
          fs.Close()
        End If
      Case 3      ' Save
        If dlgS.ShowDialog = DialogResult.OK Then
          fs = New FileStream(dlgS.FileName, FileMode.Create)
          sw = New StreamWriter(fs)

          ' Пишем данные
          sw.Write(txt2.Text)
          Text = dlgS.FileName
          ' Закрываем потоки
          sw.Close()
          fs.Close()
        End If
    End Select
  End Sub

  ' Процедура шифрования
  Private Sub cmdEncrypt_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdEncrypt.Click
    Dim Alg As SymmetricAlgorithm = DefineAlg()   ' Определяем алгоритм
    Dim Cryptor As ICryptoTransform = CreateEnc(Alg)
    Dim fs As New FileStream(GetFileName, FileMode.Create)
    Dim CrStream As New CryptoStream(fs, Cryptor, CryptoStreamMode.Write)

    ' Получаем массив байтов
    Dim b() As Byte = ToBytes(txt1.Text)
    ' Шифруем
    CrStream.Write(b, 0, b.Length)
    ' Закрываем поток
    CrStream.Close()
    fs.Close()
    fs = Nothing


    ' Создаём читатель потока
    fs = New FileStream(GetFileName, FileMode.Open)
    Dim sr As New StreamReader(fs, Encoding.Default)
    ' Показываем результат
    txt2.Text = sr.ReadToEnd
    ' Закрываем потоки
    sr.Close()
    fs.Close()
  End Sub

  ' Процедура дешифрования
  Private Sub cmdDecrypt_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdDecrypt.Click
    Dim Alg As SymmetricAlgorithm = DefineAlg()   ' Определить алгоритм
    Dim Cryptor As ICryptoTransform = CreateDec(Alg) 
    Dim fs As New FileStream(GetFileName, FileMode.Open)
    Dim CrStream As New CryptoStream(fs, Cryptor, CryptoStreamMode.Read)
    Dim sr As New BinaryReader(CrStream)
    ' Служит для определения размера файла
    Dim f As New FileInfo(GetFileName)
    Dim enc As New UnicodeEncoding

    ' Показываем результат
    txt2.Text = enc.GetString(sr.ReadBytes(f.Length))
    ' Закрываем потоки
    sr.Close()
    fs.Close()
  End Sub

#Region "Service functions"
  ' Функция, возвращающая выбранный алгоритм
  Private Function DefineAlg() As SymmetricAlgorithm
    If optDES.Checked = True Then
      Return New DESCryptoServiceProvider
    ElseIf optTripleDES.Checked = True Then
      Return New TripleDESCryptoServiceProvider
    ElseIf optAES.Checked = True Then
      Return New RijndaelManaged
    ElseIf optRC2.Checked = True Then
      Return New RC2CryptoServiceProvider
    End If
  End Function

  ' Функция, возвращающая имя файла для шифрования
  Private Function GetFileName() As String
    If Text = "" Then
      Do
        If dlgS.ShowDialog = DialogResult.OK Then
          Text = dlgS.FileName
          Return dlgS.FileName
        Else
          MsgBox("You must create or select a file!", _
            MsgBoxStyle.Exclamation)
        End If
      Loop
    Else
      Return Text
    End If
  End Function

  ' Функция, преобразующая строку в массив байтов
  Private Function ToBytes(ByVal s As String) As Byte()
    Dim enc As New UnicodeEncoding
    Return enc.GetBytes(s)
  End Function

  ' Функция, создающая интерфейс-шифратор
  Private Function CreateEnc(ByVal AlgType As SymmetricAlgorithm) _
    As ICryptoTransform
    If (TypeOf AlgType Is DESCryptoServiceProvider) Or _
      (TypeOf AlgType Is RC2CryptoServiceProvider) Then

      Return AlgType.CreateEncryptor(Key, IV)
    ElseIf TypeOf AlgType Is TripleDESCryptoServiceProvider Then
      Return AlgType.CreateEncryptor(TripleKey, TripleIV)
    ElseIf TypeOf AlgType Is RijndaelManaged Then
      Return AlgType.CreateEncryptor(aesKey, aesIV)
    End If
  End Function

  ' Функция, создающая интерфейс-дешифратор
  Private Function CreateDec(ByVal AlgType As SymmetricAlgorithm) _ 
    As ICryptoTransform
    If (TypeOf AlgType Is DESCryptoServiceProvider) Or _
      (TypeOf AlgType Is RC2CryptoServiceProvider) Then

      Return AlgType.CreateDecryptor(Key, IV)
    ElseIf TypeOf AlgType Is TripleDESCryptoServiceProvider Then
      Return AlgType.CreateDecryptor(TripleKey, TripleIV)
    ElseIf TypeOf AlgType Is RijndaelManaged Then
      Return AlgType.CreateDecryptor(aesKey, aesIV)
    End If
  End Function
#End Region
End Class

Получился довольно громоздкий код. Давайте попробуем разобраться в нём и уловить наиболее важные моменты. В самом начале объявлены 3 пары ключей и IV. Раньше в этом не было необходимости, поскольку мы только шифровали, но не дешифровали данные. Даже если мы сами не прописываем провайдеру криптоалгоритма, что нужно использовать такой-то ключ и такой-то IV, он генерирует их самостоятельно как при шифровании, так и при дешифровании. В результате ключи и IV, сгенерированные для шифрования и дешифрования, не совпадают, т. е. расшифровать данные провайдер не сможет, и вы увидите окно, подобное рисунку 6.


Рисунок 6. Исключение, возникающее при несовпадении ключей или IV’ов шифрования и дешифрования

Из комментариев видно, что первая пара ключей соответствует DES и RC2, вторая - TripleDES и третья -AES/Rijndael. Зачем это нужно? Обратите внимание на длину этих массивов - она везде разная. Дело в том, что DES и RC2 используют 8-байтовые ключи и IV, TripleDES работает с 24-байтовым ключом и 8-байтовым IV, а Rijndael - с 32-байтовым ключом и 16-байтовым IV. Из этих данных уже можно делать смелый вывод, что наиболее надёжный метод - AES/Rijndael.

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

Private Sub tlb_ButtonClick(ByVal sender As System.Object, _
  ByVal e As System.Windows.Forms.ToolBarButtonClickEventArgs) _
  Handles tlb.ButtonClick

Здесь мы не будем останавливаться, а "спустимся" дальше по коду до процедуры шифрования:

' Процедура шифрования
  Private Sub cmdEncrypt_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdEncrypt.Click
    Dim Alg As SymmetricAlgorithm = DefineAlg()   ' Определяем алгоритм
    Dim Cryptor As ICryptoTransform = CreateEnc(Alg)
    Dim fs As New FileStream(GetFileName, FileMode.Create)
    Dim CrStream As New CryptoStream(fs, Cryptor, CryptoStreamMode.Write)

    ' Получаем массив байтов
    Dim b() As Byte = ToBytes(txt1.Text)
    ' Шифруем
    CrStream.Write(b, 0, b.Length)
    ' Закрываем поток
    CrStream.Close()
    fs.Close()
    fs = Nothing


    ' Создаём читатель потока
    fs = New FileStream(GetFileName, FileMode.Open)
    Dim sr As New StreamReader(fs, Encoding.Default)
    ' Показываем результат
    txt2.Text = sr.ReadToEnd
    ' Закрываем потоки
    sr.Close()
    fs.Close()
  End Sub

Если обратиться к листингу 2.2, то там в первой строке кода шифрования создавался экземпляр конкретного криптопровайдера, в частности - DESCryptoServiceProvider. Здесь используется общий класс, от которого наследуются все классы-провайдеры симметричных алгоритмов, - SymmetricAlgorithm. Был применён именно этот класс, потому что данный пример демонстрирует комбинированное шифрование/дешифрование, т. е. обеспечивает поддержку всех доступных в .NET Framework симметричных криптометодов: DES, TripleDES, AES/Rijndael, RC2.

Чтобы определить, какой именно криптометод используется для шифрования, вызывается функция DefineAlg. Перейдём к этой функции:

' Функция, возвращающая выбранный алгоритм
Private Function DefineAlg() As SymmetricAlgorithm
  If optDES.Checked = True Then
    Return New DESCryptoServiceProvider
  ElseIf optTripleDES.Checked = True Then
    Return New TripleDESCryptoServiceProvider
  ElseIf optAES.Checked = True Then
    Return New RijndaelManaged
  ElseIf optRC2.Checked = True Then
    Return New RC2CryptoServiceProvider
  End If
End Function

Как видно из листинга, функция имеет тип SymmetricAlgorithm, но в зависимости от выбранного переключателя она возвращает новый экземпляр соответствующего объекта, т. е. если выбран был переключатель optDes, то будет создан новый экземпляр класса DESCryptoServiceProvider.

ПРИМЕЧАНИЕ

Напомним ещё раз, что классы DESCryptoServiceProvider, TripleDESCryptoServiceProvider, RijndaelManaged и RC2CryptoServiceProvider наследуются от класса SymmetricAlgorithm , потому, несмотря на то, что функция имеет тип SymmetricAlgorithm, она может с тем же успехом возвращать экземпляр любого из наследуемых классов, что и было использовано в функции DefineAlg.

Определив алгоритм шифрования, приложение получает интерфейс трансформации, значение которого задаётся специально созданной для этого функцией CreateEnc. В качестве аргумента функция получает переменную Alg, содержащую информацию о выбранном криптометоде.

В листинге 2.2 для получения интерфейса-шифратора применялась следующая строка:

Dim DES_Encryptor As ICryptoTransform = DES.CreateEncryptor(DES.Key, DES.IV)

В листинге 3.1 функция CreateEnc делает то же самое. Но в этом примере используются несколько криптометодов, а длины ключей и IV у них разные, поэтому эта функция нужна, чтобы определить, какую пару ключ-IV нужно использовать. Если обратиться к её коду, то можно увидеть, что она лишь анализирует тип переданного класса и подставляет соответствующий ключ и IV.

Дальше шифрование происходит по тому же принципу, что и в листинге 2.2, с тем лишь отличием, что процесс получения массива байтов вынесен в отдельную функцию ToBytes:

' Функция, преобразующая строку в массив байтов
Private Function ToBytes(ByVal s As String) As Byte()
  Dim enc As New UnicodeEncoding
  Return enc.GetBytes(s)
End Function

ПРИМЕЧАНИЕ

Свойство Text формы было равно пустой строке (""), т. к. оно используется для хранения имени файла, и его обработка осуществляется в функции GetFileName.

Двигаемся дальше, в сторону дешифрования:

' Процедура дешифрования
Private Sub cmdDecrypt_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles cmdDecrypt.Click
  Dim Alg As SymmetricAlgorithm = DefineAlg()   ' Определить алгоритм
  Dim Cryptor As ICryptoTransform = CreateDec(Alg)
  Dim fs As New FileStream(GetFileName, FileMode.Open)
  Dim CrStream As New CryptoStream(fs, Cryptor, CryptoStreamMode.Read)
  Dim sr As New BinaryReader(CrStream)
  ' Служит для определения размера файла
  Dim f As New FileInfo(GetFileName)
  Dim enc As New UnicodeEncoding

  ' Показываем результат
  txt2.Text = enc.GetString(sr.ReadBytes(f.Length))
  ' Закрываем потоки
  sr.Close()
  fs.Close()
End Sub

Как видите, начало процедуры (зона объявления переменных) аналогично началу в процедуре шифрования. Только при инициализации интерфейса трансформации используется не функция CreateEnc, а CreateDec, которая работает по тому же принципу, но только создаёт дешифратор вместо шифратора.

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

Теперь самое время испытать созданное приложение (рисунок 7). Чтобы зашифровать данные, введите текст в поле Before или загрузите их из файла, выберите метод шифрования и нажмите Encrypt. Если не был выбран какой-либо файл, то программа попросит вас выбрать файл для сохранения результатов, после чего покажет результат шифрования. Чтобы дешифровать только что зашифрованные данные можно либо открыть зашифрованный файл, либо скопировать данные из Before в поле After - одним словом, криптограмма должна оказаться в поле Before . После этого просто нажмите кнопку Decrypt, и результат предстанет перед вами.

ПРИМЕЧАНИЕ

Если нажать кнопку Encrypt несколько раз подряд, то результат не изменится. Дело в том, что в этом примере ключ и IV не изменяются, поскольку они жёстко заданы. В предыдущих примерах мы либо их вообще не трогали, либо вызывали методы генерации, поэтому результаты тогда были различными.

Рисунок 7. Приложение комбинированного шифрования в действии.

Асимметричные системы

Среда .NET Framework содержит два класса, реализующих асимметричные алгоритмы: RSACryptoServiceProvider и DSACryptoServiceProvider. Оба метода пригодны для подписи и верификации данных, т. е. для создания цифровых подписей. В данном разделе рассматривается только RSA. DSA будет рассмотрен в разделе " Цифровые подписи ".

ПРИМЕЧАНИЕ

Подобно симметричным системам, асимметричные имеют базовый класс, от которого наследуются объекты, реализующие конечные криптометоды - это AsymmetricAlgorithm.

Как уже было сказано, асимметричные алгоритмы очень медленны, и потому пригодны лишь для небольших объёмов информации. Так, алгоритм RSA может обработать 43 байта.

Создадим Web-приложение, которое шифрует и дешифрует данные с помощью криптометода RSA:

Листинг 4.1: RSA/default.aspx

<%@ Page Language="vb" AutoEventWireup="false" 
         Codebehind="default.aspx.vb" Inherits="RSA.WebForm1"%>
<HTML>
  <HEAD>
    <title>RSA</title>
  </HEAD>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
      <b>Clear text:</b><br>
      <textarea id="txt" runat="server"></textarea><br>
      
      <b>Encrypted text:</b><br>
      <textarea id="txtEnc" runat="server" rows=4></textarea><br>
      
      <b>Decrypted text:</b><br>
      <textarea id="txtDec" runat="server"></textarea><br>
      
      <hr>
      <asp:Button ID="btnEnc" Runat="server" Text="Encrypt and Decrypt" />
    </form>
  </body>
</HTML>

Листинг 4.2: RSA/default.aspx.vb:

Imports System.Security.Cryptography
Imports System.Text

Public Class WebForm1
  Inherits System.Web.UI.Page

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required 
  ' by the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Protected WithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
  Protected WithEvents txtEnc As System.Web.UI.HtmlControls.HtmlTextArea
  Protected WithEvents txtDec As System.Web.UI.HtmlControls.HtmlTextArea
  Protected WithEvents btnEnc As System.Web.UI.WebControls.Button

  
  Dim enc As New UnicodeEncoding

  Private Sub btnEnc_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles btnEnc.Click
  ' Генерируем пару из открытого и секретного ключей
    Dim RSAcsp As New RSACryptoServiceProvider

    ' шифруем
    txtEnc.Value = RSACrypt(enc.GetBytes(txt.Value), _
      RSAcsp.ExportParameters(False), KindOfAction.RSAEncrypt)
    ' дешифруем
    txtDec.Value = RSACrypt(enc.GetBytes(txtEnc.Value), _
      RSAcsp.ExportParameters(True), KindOfAction.RSADecrypt)
  End Sub

  ' Функция шифрования/дешифрования
  Private Function RSACrypt(ByVal DataToEncrypt() As Byte, _
    ByVal RSAKey As RSAParameters, ByVal Action As KindOfAction) As String
    Dim RSA As New RSACryptoServiceProvider

    ' Импортируем информацию о ключах
    RSA.ImportParameters(RSAKey)
    If Action = KindOfAction.RSAEncrypt Then
      ' зашифрованные данные
      Return enc.GetString(RSA.Encrypt(DataToEncrypt, False))
    Else
      ' дешифрованные данные
      Return enc.GetString(RSA.Decrypt(DataToEncrypt, False))
    End If
  End Function

  ' Тип операции: шифрование или дешифрование
  Private Enum KindOfAction As Integer
    RSAEncrypt = 0
    RSADecrypt = 1
  End Enum
End Class

С первого взгляда видно, что код реализации асимметричного метода намного короче, чем код симметричного. Одной из причин столь малых габаритов является то, что нет нужды инициализировать множество объектов, связывая их друг с другом. В этом примере используется один-единственный класс - RSACryptoServiceProvider.

В процедуре обработки события нажатия кнопки btnEnc сначала создаётся экземпляр класса RSACryptoServiceProvider, при этом автоматически генерируется пара из открытого и секретного ключей. Далее для шифрования и дешифрования используется функция RSACrypt. Сравните вызовы этой функции для обеих операций:

' шифруем
txtEnc.Value = RSACrypt(enc.GetBytes(txt.Value), _
RSAcsp.ExportParameters(False), KindOfAction.RSAEncrypt)
' дешифруем
txtDec.Value = RSACrypt(enc.GetBytes(txtEnc.Value), _
    RSAcsp.ExportParameters(True), KindOfAction.RSADecrypt)

При шифровании в параметр includePrivateParameters метода ExportParameters передается значение False , а при дешифровании - значение True . Этот параметр отвечает за информацию, которая должна быть экспортирована. Таким образом, если стоит значение False, то экспортируются данные только об открытом ключе, а если стоит True, то об открытом и о секретном.

В функции RSACrypt создаётся ещё один экземпляр объекта RSACryptoServiceProvider (переменная RSA), но уже не для генерации ключей, а для выполнения криптоопераций с использованием ключей, переданных классом-генератором (RSAcsp). Итак, для выполнения криптоопераций с помощью объекта RSACryptoServiceProvider необходимы 2 его экземпляра: один для генерации ключей, а другой для выполнения криптоопераций.

Для шифрования и дешифрования вызываются методы Encrypt и Decrypt объекта RSACryptoServiceProvider. Остановимся на одном из них:

Public Function Encrypt(ByVal rgb() As Byte, _
  ByVal fOAEP As Boolean) As Byte()

Первый параметр этого метода принимает массив байтов для шифрования, а второй активизирует или деактивизирует Д ополнение Оптимального Асимметричного Шифрования (OAEP - Optimal Asymmetric Encryption Padding) . Этот механизм работает только на системах Windows XP и старше, т. е. XP и более новые операционные системы могут работать как со значением параметра False, так и со значением True, а все остальные поддерживают только False.

ПРЕДУПРЕЖДЕНИЕ

Объекты RSA и RSACryptoServiceProvider содержат методы EncryptValue и DecryptValue, но они не поддерживаются в .NET Framework 1.1.

Теперь запустите созданное приложение и попробуйте что-нибудь зашифровать (рисунок 8).

Рисунок 8. Успешное шифрование асимметричным методом RSA.

Хранилище ключей метода RSA

Информация о ключах алгоритма RSA хранится в структуре RSAParameters. Эта структура содержит в себе несколько параметров, имена которых никоим образом не связаны с открытым и секретным ключами. Эту структуру можно увидеть на рисунке 9.

Рисунок 9. Структура RSAParameters.

Определить, какие переменные к чему относятся, довольно легко. Для этого создайте консольное приложение и вставьте код из листинга 5.1.

Листинг 5.1: Получение информации о ключах

Imports System.Security.Cryptography
Imports System.Text

Module Module1

  Sub Main()
    ' Генерируем ключи
    Dim RSA As New RSACryptoServiceProvider

    ' Получаем и отображаем данные об открытом ключе
    Dim RSAparams As RSAParameters = RSA.ExportParameters(False)
    Console.WriteLine("Public key for encryption:")
    ShowKeyInfo(RSAparams)

    ' Получаем и отображаем данные об обоих ключах
    RSAparams = RSA.ExportParameters(True)
    Console.WriteLine(vbCrLf)
    Console.WriteLine("Public and Private keys for decryption:")
    ShowKeyInfo(RSAparams)


    ' Конец------------------------------
    Console.ReadLine()
  End Sub

  ' Процедура, отображающая информацию о ключах
  Sub ShowKeyInfo(ByVal KeyInfo As RSAParameters)
    Dim enc As New UnicodeEncoding
    On Error Resume Next

    Console.WriteLine("D - {0}", enc.GetString(KeyInfo.D))
    Console.WriteLine("DP - {0}", enc.GetString(KeyInfo.DP))
    Console.WriteLine("DQ - {0}", enc.GetString(KeyInfo.DQ))
    Console.WriteLine("Exponent - {0}", enc.GetString(KeyInfo.Exponent))
    Console.WriteLine("InverseQ - {0}", enc.GetString(KeyInfo.InverseQ))
    Console.WriteLine("Modulus - {0}", enc.GetString(KeyInfo.Modulus))
    Console.WriteLine("P - {0}", enc.GetString(KeyInfo.P))
    Console.WriteLine("Q - {0}", enc.GetString(KeyInfo.Q))
  End Sub
End Module

В листинге 5.1 сначала генерируются ключи путём создания нового экземпляра объекта RSACryptoServiceProvider, затем данные о ключах дважды экспортируются: в первый раз отправляется информация только об открытом ключе, а во второй раз - об обоих ключах. Далее процедура ShowKeyInfo просто выводит всю доступную информацию. При попытке получить отсутствующие данные выдается исключение и, так как в начале процедуры был задан оператор обработки исключений "On Error Resume Next", управление передается следующей строке кода. Таким образом, на экран выводятся значения только доступных свойств.

Сам по себе код мало о чём говорит, поэтому запустим приложение и посмотрим на результат. Из рисунка 10 видно, что данные об открытом ключе содержатся в переменных Exponent и Modulus. Их содержимое в данном случае нас не интересует. Все остальные данные, соответственно, характеризуют секретный ключ.

Возникает вопрос: "Как можно передать ключи пользователю, если они записаны в 8 переменных?" Для решения этой задачи в объекте RSA, от которого наследуется RSACryptoServiceProvider, есть два метода: FromXmlString и ToXmlString. Их описание приведено ниже:

Public Overrides Sub FromXmlString(ByVal xmlString As String)
Public Overrides Function ToXmlString(_
ByVal includePrivateParameters As Boolean) As String

Метод FromXmlString импортирует данные о ключах в объект RSA из строки, а функция ToXmlString, наоборот, генерирует строку с данными о ключах, причём можно указать, нужно ли экспортировать информацию о секретном ключе.

Рисунок 10. Информация о ключах.

Хэширование

Для использования в криптографии .NET Framework предоставляет следующие методы хэширования: MD5, SHA1, SHA256, SHA384, SHA512.

Все классы конечных хэш-алгоритмов происходят от объекта HashAlgorithm, потому синтаксис и порядок действий для выполнения операций хэширования будет одинаков для всех хэш-методов. Все хэш-методы, реализованные в .NET Framework, работают либо с массивами байтов, либо с потоками.

Создадим Web-приложение, которое хэширует данные и сравнивает получившиеся хэш-коды. В качестве алгоритма возьмём SHA384 и воспользуемся кодом из листингов 6.1 и 6.2:

Листинг 6.1: SHA384/default.aspx

<%@ Page language="vb" Codebehind="default.aspx.vb" 
         AutoEventWireup="false" Inherits="SHA384.WebForm1" %>
<html>
  <head>
    <title>SHA384</title>
  </head>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
      <table border>
    <tr bgcolor=lightGrey>
        <th>Original text</th>
      <th>Hash1 (<i>Hello World!</i>)</th>
      <th>Hash2 (new hash)</th>
    </tr>
    <tr>
      <td><textarea id="txt" runat=server>Hello World!</textarea></td>
      <td><asp:Label ID="lblOriginalHash" Runat=server/></td>
        <td><asp:Label ID="lblNewHash" Runat=server/></td>
    </tr>
    <tr bgcolor=lightGrey>
      <th align=left colspan=3>
      Result: <asp:Label ID="lblResult" Runat=server/>
      </th>
    </tr>
      </table>
      
      <asp:Button ID="btn" Runat=server Text="New hash"/>
    </form>
  </body>
</html>

Листинг 6.2: SHA384/default.aspx.vb

Imports System.Security.Cryptography
Imports System.Text
Imports System.Drawing

Public Class WebForm1
  Inherits System.Web.UI.Page

  Protected WithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
  Protected WithEvents lblOriginalHash As System.Web.UI.WebControls.Label
  Protected WithEvents lblNewHash As System.Web.UI.WebControls.Label
  Protected WithEvents lblResult As System.Web.UI.WebControls.Label
  Protected WithEvents btn As System.Web.UI.WebControls.Button

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required 
  ' by the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  ' Создаём экземпляр класса хэширования
  Dim SHA384 As New SHA384Managed
  ' Массив байтов захэшированной строки 'Hello World!'
  Dim OriginalHash() As Byte
  ' Класс для преобразования между байтами и строками
  Dim enc As New UnicodeEncoding

  Private Sub btn_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles btn.Click
    Dim i As Integer
    Dim NewHash() As Byte
    Dim same As Boolean = True

    ' Хэшируем значение в поле txt
    NewHash = SHA384.ComputeHash(enc.GetBytes(txt.Value))
    lblNewHash.Text = enc.GetString(NewHash)
    ' Сравниваем хэши
    For i = 0 To NewHash.Length - 1
      If NewHash(i) <> OriginalHash(i) Then
        ' Данные были изменены, т.е. хэши не совпадают
        same = False
        Exit For
      End If
    Next

    If same = True Then
      lblResult.ForeColor = Color.Green
      lblResult.Text = "Hash1 = Hash2"
    Else
      lblResult.ForeColor = Color.Red
      lblResult.Text = "Hash1 <> Hash2"
    End If
  End Sub

  Private Sub Page_Load(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    Const ORIGINAL_STRING As String = "Hello World!"

    ' Хэшируем строку 'Hello World!'
    OriginalHash = SHA384.ComputeHash(enc.GetBytes(ORIGINAL_STRING))
    lblOriginalHash.Text = enc.GetString(OriginalHash)
  End Sub
End Class

Код листинга 6.2 особой сложности не представляет. В нём создаются два хэш-значения: строки " Hello World! " и текста, содержащегося в поле txt. После этого хэш-значения сверяются. Если в поле txt была введена строка " Hello World! ", то хэши совпадут (рисунок 11), в противном случае - нет (рисунок 12).

Рисунок 11. Хэши совпали, значит, данные не были изменены

Рисунок 12. Хэши не совпали, значит, данные были изменены.

Чтобы воспользоваться другим методом, достаточно поменять провайдер алгоритма, т. е. вместо строки:

Dim SHA384 As New SHA384Managed

Можно использовать, скажем:

Dim SHA384 As New MD5CryptoServiceProvider

Или любую другую.

Цифровые подписи

Вспомним ещё раз, что цифровые подписи позволяют убедиться в достоверности отправителя. Для этого применяется совокупность из асимметричных криптосистем и хэш-алгоритмов. В .NET Framework все основные действия выполняются с помощью классов, реализующих асимметричные алгоритмы. Таковых в .NET Framework 1.1 два: RSACryptoServiceProvider и DSACryptoServiceProvider. Поскольку мы уже рассматривали пример с алгоритмом RSA, то в примерах этого раздела будет в основном использоваться DSA.

В .NET Framework есть несколько способов создания и проверки цифровых подписей. Мы постараемся рассмотреть большинство из них.

Классы, реализующие алгоритмы цифровой подписи, имеют единый базовый класс SignatureDescription. С помощью методов этого класса можно получить объекты, выполняющие непосредственное создание и проверку цифровых подписей. Мы не станем останавливаться на этом классе, а перейдём сразу к реализации алгоритма DSA - к классу DSACryptoServiceProvider.

Объект DSACryptoServiceProvider содержит 3 пары методов для создания и проверки подписей: SignData и VerifyData, SignHash и VerifyHash, CreateSignature и VerifySignature.

SignData и VerifyData

Самый простой и быстрый способ создания и проверки цифровых подписей - использование методов SignData и VerifyData. Метод SignData перегружен, т. е. имеет несколько прототипов:

Public Function SignData(ByVal buffer() As Byte) As Byte()
Public Function SignData(ByVal buffer() As Byte, _
  ByVal offset As Integer, ByVal count As Integer) As Byte()
Public Function SignData(ByVal inputStream As System.IO.Stream) As Byte()

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

Мы воспользуемся первым прототипом и создадим новое Web-приложение (листинг 7.1):

Листинг 7.1: Sign1/default.aspx.vb

Imports System.Security.Cryptography
Imports System.Text

Public Class WebForm1
  Inherits System.Web.UI.Page

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required 
  ' by the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    ' Исходный текст
    Const CLEAR_STRING As String = "Hello, World!"
    Dim enc As New UnicodeEncoding
    ' Массивы байт
    Dim clearText() As Byte, signText() As Byte
    ' Создаём пару ключей
    Dim DSA As New DSACryptoServiceProvider

    ' Получить массив байт исходного текста
    clearText = enc.GetBytes(CLEAR_STRING)
    ' Подписываем
    signText = DSA.SignData(clearText)
    ' Выводим результат
    Response.Write("<b>Clear text:</b> " & CLEAR_STRING & "<br>")
    Response.Write("<b>Signature:</b> " & enc.GetString(signText) & "<br>")

    ' Проверяем
    If DSA.VerifyData(clearText, signText) Then
      Response.Write("<font color='Green'>Good</font>")
    Else
      Response.Write("<font color='Red'>Bad</font>")
    End If
  End Sub
End Class

Этот код сначала преобразует строку в массив байт с помощью объекта UnicodeEncoding, после чего полученный массив используется для подписывания данных. Исходные данные и подпись выводятся на экран, и после этого происходит проверка сигнатуры путём вызова метода VerifyData, которому в качестве параметров передаются массивы байтов исходного текста и цифровой подписи.

ПРЕДУПРЕЖДЕНИЕ

В данном примере мы осуществляем подпись и проверку с помощью одного и того же экземпляра объекта DSACryptoServiceProvider, т. е. нам нет необходимости заботиться о ключах - они нигде не меняются. Но в рабочих ситуациях создание сигнатуры и её проверка происходят с применением разных объектов, разными приложениями и, более того, зачастую на разных машинах. Таким образом, в рабочих приложениях необходимо сохранять информацию о ключах с помощью пары методов ToXmlString и FromXmlString объекта DSACryptoServiceProvider.

Запустите это приложение, и, если всё было выполнено правильно, то вы увидите окно, подобное рисунку 13:

Рисунок 13. Цифровая подпись подтверждена

Как видно из рисунка, данные пришли от верного отправителя, т. е. проверка подписи прошла успешно. Чтобы увидеть другую сторону медали, достаточно добавить строку:

. signText(10) = 144

перед строкой:

    If DSA.VerifyData(clearText, signText) Then

Она меняет один из байтов цифровой подписи. Теперь запустите приложение, и искажение сигнатуры сразу даст о себе знать (рисунок 14):

Рисунок 14. Цифровая подпись была изменена.

Применение пары методов SignData и VerifyData удобно тем, что не нужно заботиться о создании хэш-значения - система делает всё за вас. Но при этом теряется гибкость, в частности нельзя поменять метод хэширования, а по умолчанию выбран SHA1.

ПРИМЕЧАНИЕ

Стоит отметить, что потеря гибкости при использовании методов SignData и VerifyData присуща только объекту DSACryptoServiceProvider - в классе RSACryptoServiceProvider те же самые методы имеют два параметра, и второй из них - это название хэш-метода. Это вызвано тем, что метод RSA поддерживает два хэш-алгоритма: SHA1 и MD5. А криптометод DSA работает только с SHA1, потому и нет смысла добавлять второй атрибут, но, несмотря на это, у метода SignHash (он будет рассмотрен далее) даже в объекте DSACryptoServiceProvider есть атрибут для указания имени хэш-метода.

Чтобы узнать, какой именно метод алгоритма цифровой подписи установлен, нужно просмотреть значение свойства SignatureAlgorithm объекта DSACryptoServiceProvider.

SignHash и VerifyHash

Метод SignHash отличается от метода SignData лишь тем, что он работает не с чистыми данными, а с их хэш-значениями, поэтому в качестве параметров ему задаются хэш-значение и имя хэш-метода:

Public Function SignHash(ByVal rgbHash() As Byte, _
  ByVal str As String) As Byte()

Этот метод имеет свои преимущества над методом SignData, в частности вы вправе сами выбирать алгоритм хэширования. Код листингов 9.1 и 9.2 демонстрирует применение метода SignHash для создания цифровых подписей (в качестве хэш-метода выбран SHA1):

Листинг 9.1: Sign2/default.aspx

<%@ Page Language="vb" AutoEventWireup="false" 
    Codebehind="default.aspx.vb" Inherits="Sign2.WebForm1"%>
<HTML>
  <HEAD>
    <title>Sign2</title>
  </HEAD>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
      <textarea id="txt" runat=server></textarea><br>
      <asp:Button ID="btn" Runat=server Text="Sign"/>
      
      <hr>
      <asp:Label ID="lbl" Runat=server/>
    </form>
  </body>
</HTML>

Листинг 9.2: Sign2/default.aspx.vb

Imports System.Security.Cryptography
Imports System.Text

Public Class WebForm1
  Inherits System.Web.UI.Page

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> _
  Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required 
  ' by the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Protected WithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
  Protected WithEvents btn As System.Web.UI.WebControls.Button
  Protected WithEvents lbl As System.Web.UI.WebControls.Label

  Private Sub btn_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles btn.Click
    Dim SHA1 As New SHA1CryptoServiceProvider
    Dim DSA As New DSACryptoServiceProvider

    Dim hashText() As Byte, signText() As Byte
    Dim enc As New UnicodeEncoding

    ' Создаём хэш
    hashText = SHA1.ComputeHash(enc.GetBytes(txt.Value))
    ' Создаём подпись
    signText = DSA.SignHash(hashText, CryptoConfig.MapNameToOID("SHA1"))
    ' Выводим результат
    lbl.Text = "<b>Signature:</b> " & enc.GetString(signText) & "<br>"

    ' Проводим верификацию
    If DSA.VerifyHash(hashText, CryptoConfig.MapNameToOID("SHA1"), signText) Then
      lbl.Text &= "<font color='Green'>Good</font>"
    Else
      lbl.Text &= "<font color='Red'>Bad</font>"
    End If
  End Sub
End Class

Код листинга 9.2 вначале создаёт экземпляры объектов MD5CryptoServiceProvider и SHA1CryptoServiceProvider. Далее создаётся хэш-значение с помощью метода ComputeHash. После этого следует создание цифровой подписи посредством метода SignHash. Этот метод подписывает не исходный чистый текст, а его хэш-значение. Обратите внимание, что во втором параметре подставляется не просто строковое имя хэш-алгоритма, а используется метод MapNameToOID объекта CryptoConfig (этот объект расположен всё в том же пространстве имён System.Security.Cryptography). Этот метод возвращает код соответствующего алгоритма. Чтобы иметь представление, на что это похоже, можно воспользоваться кодом из листинга 10.1:

Листинг 10.1: Sign2/GetAlgName.aspx

<%@ Page Language="vb" AutoEventWireup="false" Inherits="Sign2.GetAlgName"%>
<%@Import Namespace="System.Security.Cryptography"%>
<html>
  <head>
    <title>GetAlgName</title>
  </head>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
    <%= CryptoConfig.MapNameToOID("SHA1")%>
    </form>
  </body>
</html>

Но вернёмся к нашему основному приложению. Запустите его, введите в поле какой-нибудь текст, нажмите кнопку Sign , и вы получите ожидаемый результат.

CreateSignature и VerifySignature

Эти два метода есть только в объекте DSACryptoServiceProvider. Они, в сущности, действуют так же, как и методы SignHash и VerifyHash: принимают хэш в качестве параметра, но имя хэш-метода указывать не нужно. То есть у метода CreateSignature всего один атрибут - входные данные в виде хэша.

Создадим новое Web-приложение, демонстрирующее работу этих методов:

Листинг 11.1: Sign3/default.aspx.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Security.Cryptography;
using System.Text;

namespace Sign3
{
  /// <summary>
  /// Summary description for WebForm1.
  /// </summary>
  public class WebForm1 : System.Web.UI.Page
  {
    private void Page_Load(object sender, System.EventArgs e)
    {
      // Исходный текст
      const string CLEAR_TEXT = "CreateSignature and VerifySignature";

      // Инициализируем объекты
      SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
      DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();

      byte[] hashText, signText;
      UnicodeEncoding enc = new UnicodeEncoding();

      // Создаём хэш-значение и подпись
      hashText = SHA1.ComputeHash(enc.GetBytes(CLEAR_TEXT));
      signText = DSA.CreateSignature(hashText);

      // Выводим результат
      Response.Write("<b>Clear text:</b> " + CLEAR_TEXT + "<br>");
      Response.Write("<b>Signature:</b> " + enc.GetString(signText) + "<br>");
      // Проверяем подпись
      if(DSA.VerifySignature(hashText, signText) == true)
        Response.Write("<font color='Green'>Good</font>");
      else
        Response.Write("<font color='Red'>Bad</font>");
    }

    #region Web Form Designer generated code
    override protected void OnInit(EventArgs e)
    {
      //
      // CODEGEN: This call is required by the ASP.NET Web Form Designer.
      //
      InitializeComponent();
      base.OnInit(e);
    }
    
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {    
      this.Load += new System.EventHandler(this.Page_Load);
    }
    #endregion
  }
}

Думаю, нет смысла разбирать этот код, потому что он очень похож на код листинга 9.2. Запустите приложение, и вы увидите уже привычное слово "Good".

Объекты DSASignatureFormatter и DSASignatureDeformatter

Ещё один способ создать цифровую подпись - это применить объекты DSASignatureFormatter и DSASignatureDeformatter.

ПРИМЕЧАНИЕ

Для алгоритма RSA аналогами являются RSAPKCS1SignatureFormatter и RSAPKCS1SignatureDeformatter.

В следующем примере, демонстрирующем применение этих объектов, мы воспользуемся одновременно алгоритмами DSA и RSA. Но хэши распределим таким образом, что DSA будет работать с SHA1 (потому что другого он и не поддерживает), а RSA - с MD5.

Листинг 12.1: Sign4/default.aspx.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Security.Cryptography;
using System.Text;

namespace Sign4
{
  /// <summary>
  /// Summary description for WebForm1.
  /// </summary>
  public class WebForm1 : System.Web.UI.Page
  {
    private void Page_Load(object sender, System.EventArgs e)
    {
      // Исходная строка
      const string CLEAR_TEXT = "Объекты DSASignatureFormatter, " +
        "DSASignatureDeformatter, RSAPKCS1SignatureFormatter, " +
        "RSAPKCS1SignatureDeformatter";
      // Инициализируем объекты
      SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
      MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
      
      DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();
      DSASignatureFormatter DSAform = new DSASignatureFormatter(DSA);
      DSASignatureDeformatter DSAdeform = new DSASignatureDeformatter(DSA);

      RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
      RSAPKCS1SignatureFormatter RSAform = 
        new RSAPKCS1SignatureFormatter(RSA);
      RSAPKCS1SignatureDeformatter RSAdeform = 
        new RSAPKCS1SignatureDeformatter(RSA);

      byte[] DSAhashText, DSAsignText, RSAhashText, RSAsignText;
      UnicodeEncoding enc = new UnicodeEncoding();

      // Хэшируем
      DSAhashText = SHA1.ComputeHash(enc.GetBytes(CLEAR_TEXT));
      RSAhashText = MD5.ComputeHash(enc.GetBytes(CLEAR_TEXT));
      // Задаём имена хэш-алгоритмов
      DSAform.SetHashAlgorithm("SHA1");
      DSAdeform.SetHashAlgorithm("SHA1");
      RSAform.SetHashAlgorithm("MD5");
      RSAdeform.SetHashAlgorithm("MD5");

      // Создаём подпись
      DSAsignText = DSAform.CreateSignature(DSAhashText);
      RSAsignText = RSAform.CreateSignature(RSAhashText);

      Response.Write("<b>Clear text:</b> " + CLEAR_TEXT + "<br><br>");
      // Осуществляем проверку и выводим результаты на экран
      //DSA-------------
      if(DSAdeform.VerifySignature(DSAhashText, DSAsignText) == true)
        Response.Write("<b>DSA signature:</b> " + 
                enc.GetString(DSAsignText) + 
                " <font color='Green'>" +
                "Good</font><br>");
      else
        Response.Write("<b>DSA signature:</b> " + 
          enc.GetString(DSAsignText) + 
          " <font color='Red'>" +
          "Bad</font><br>");
      //RSA-------------
      if(RSAdeform.VerifySignature(RSAhashText, RSAsignText) == true)
        Response.Write("<b>RSA signature:</b> " + 
          enc.GetString(RSAsignText) + 
          " <font color='Green'>" +
          "Good</font>");
      else
        Response.Write("<b>RSA signature:</b> " + 
          enc.GetString(RSAsignText) + 
          " <font color='Red'>" +
          "Bad</font>");
    }

    #region Web Form Designer generated code
    override protected void OnInit(EventArgs e)
    {
      //
      // CODEGEN: This call is required by the ASP.NET Web Form Designer.
      //
      InitializeComponent();
      base.OnInit(e);
    }
    
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {    
      this.Load += new System.EventHandler(this.Page_Load);
    }
    #endregion
  }
}

Расзберем этот код на примерах объектов алгоритма DSA.

Сначала был создан экземпляр объекта DSACryptoServiceProvider, при этом сгенерировалась пара ключей. После этого были созданы экземпляры объектов DSASignatureFormatter и DSASignatureDeformatter. В качестве атрибута конструкторов передавался объект DSACryptoServiceProvider, содержащий информацию о ключах:

DSASignatureFormatter DSAform = new DSASignatureFormatter(DSA);
DSASignatureDeformatter DSAdeform = new DSASignatureDeformatter(DSA);

После этого все операции осуществлялись с переменными DSAform и DSAdeform. В частности, вызывался метод SetHashAlgorithm, указывающий необходимый хэш-алгоритм, путём простой передачи его имени без применения класса CryptoConfig, как это делалось в листинге 9.2. Как только все параметры были установлены, события начали развиваться по уже известному сценарию (см. рисунок 15).

Рисунок 15. Цифровые подписи, созданные различными алгоритмами.

Исполним обещанное…

В первой части статьи было обещано рассмотреть хранение имени пользователя и пароля в файле Web.config в зашифрованном виде. Напомним, что в файле Web.config, в разделе <credentials> можно хранить информацию для аутентификации, при этом сам процесс аутентификации значительно упрощается. Но хранить данные в открытом виде довольно опасно, поэтому среда .NET Framework позволяет использовать хэш-алгоритмы SHA1 и MD5 для сокрытия информации.

С точки зрения криптографии, необходимо создать приложение, которое только хэширует данные соответствующим образом. Но сразу отметим, что применение стандартных классов хэширования пространства имён System.Security.Cryptography не подойдёт. Для этого существует специальный метод HashPasswordForStoringInConfigFile объекта System.Web.Security.FormsAuthentication. Необходимо использовать именно этот метод, а не какой-нибудь другой только потому, что он хэширует и представляет данные в нужном формате. Рассмотрим листинги 13.1 и 13.2, чтобы понять, о чём идёт речь:

Листинг 13.1: HashPassword/default.aspx

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="HashPassword.WebForm1"%>
<html>
  <head>
    <title>HashPassword</title>
  </head>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
    <table border>
      <tr><th bgcolor=lightGrey>Hash algorithm</th></tr>
      <tr><td>
        <asp:RadioButtonList ID="optList" Runat=server>
          <asp:ListItem Value="SHA1" Selected=True/>
          <asp:ListItem Value="MD5"/>
        </asp:RadioButtonList>
      </td></tr>
    </table>
    
    <b>Clear text: </b>
    <textarea id="txt" runat=server></textarea><br>
    <b>Hash: </b>
    <asp:Label ID="lbl" Runat=server/>
    
    <hr>
    <asp:Button ID="btn" Runat=server Text="Hash"/>
    </form>
  </body>
</html>

Листинг 13.2: HashPassword/default.aspx.vb

Imports System.Web.Security

Public Class WebForm1
  Inherits System.Web.UI.Page

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required 
  ' by the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Protected WithEvents optList As System.Web.UI.WebControls.RadioButtonList
  Protected WithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
  Protected WithEvents lbl As System.Web.UI.WebControls.Label
  Protected WithEvents btn As System.Web.UI.WebControls.Button

  Private Sub btn_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles btn.Click
    ' хэшируем
    lbl.Text = FormsAuthentication.HashPasswordForStoringInConfigFile( _
      txt.Value, DefAlg())
  End Sub

  ' Функция, определяющая алгоритм
  Private Function DefAlg() As String
    If optList.Items(0).Selected = True Then
      Return "SHA1"
    Else
      Return "MD5"
    End If
  End Function
End Class

Как видите, код листинга 13.2 очень прост. В нём функция DefAlg возвращает имя алгоритма, в зависимости от положения переключателей, а процедура обработки нажатия кнопки вызывает заветный метод HashPasswordForStoringInConfigFile, который выполняет все необходимое без вмешательства разработчика.

Запустите приложение и поэкспериментируйте с ним. Результат должен быть подобен рисунку 16:

Рисунок 16. Хэширование паролей для файла конфигурации.

Теперь разработаем приложение, использующее эти криптограммы. Для этого создайте новый Web-проект и измените файл Web.config следующим образом:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <authentication mode="Forms">
    <forms loginUrl="login.aspx" protection="All">
      <credentials passwordFormat="MD5">
        <!--password="one"-->
        <user name="John" password="F97C5D29941BFB1B2FDAB0874906AB82"/>
        <!--password="two"-->
        <user name="Mike" password="B8A9F715DBB64FD5C56E7783C6820A61"/>
        <!--password="three"-->
        <user name="Bill" password="35D6D33467AAE9A2E3DCCB4B6B027878"/>
      </credentials>
    </forms>
    </authentication>
  </system.web>
</configuration>

После этого можно приступить к созданию самих страниц. Для этого воспользуйтесь кодом из листингов 14.1, 14.2 и 14.3.

Листинг 14.1: HashedCredentials/login.aspx

<%@ Page Language="vb" AutoEventWireup="false" 
         Codebehind="login.aspx.vb" Inherits="HashedCredentials.login"%>
<html>
  <head>
    <title>login</title>
  </head>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
    <b>Name: </b>
    <asp:TextBox ID="txtName" Runat=server/><br>
    <b>Password: </b>
    <asp:TextBox ID="txtPassword" Runat=server TextMode=Password/>
    
    <hr>
    <asp:Button ID="btn" Runat=server Text="Login"/>
    <asp:Label ID="lbl" Runat=server ForeColor="Maroon"
      Font-Bold=True Visible=False>Wrong data!</asp:Label>
    </form>
  </body>
</html>

Листинг 14.2: HashedCredentials/login.aspx.vb

Imports System.Web.Security

Public Class login
    Inherits System.Web.UI.Page

#Region " Web Form Designer Generated Code "

    'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()

    End Sub

    'NOTE: The following placeholder declaration is required 
    ' by the Web Form Designer. Do not delete or move it.
    Private designerPlaceholderDeclaration As System.Object

    Private Sub Page_Init(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Init
      'CODEGEN: This method call is required by the Web Form Designer
      'Do not modify it using the code editor.
      InitializeComponent()
    End Sub

#End Region

    Protected WithEvents txtName As System.Web.UI.WebControls.TextBox
    Protected WithEvents txtPassword As System.Web.UI.WebControls.TextBox
    Protected WithEvents btn As System.Web.UI.WebControls.Button
    Protected WithEvents lbl As System.Web.UI.WebControls.Label

    Private Sub btn_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles btn.Click
      If FormsAuthentication.Authenticate(txtName.Text, txtPassword.Text) Then
        FormsAuthentication.RedirectFromLoginPage(txtName.Text, False)
      Else
        lbl.Visible = True
      End If
    End Sub
End Class

Листинг 14.3: HashedCredentials/default.aspx.vb

Imports System.Web.Security

Public Class WebForm1
  Inherits System.Web.UI.Page

#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub

  'NOTE: The following placeholder declaration is required 
  ' by the Web Form Designer. Do not delete or move it.
  Private designerPlaceholderDeclaration As System.Object

  Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    If User.Identity.IsAuthenticated Then
      Response.Write("<h3><i>Hello, " & User.Identity.Name & "</i></h3>")
    Else
      Response.Redirect("login.aspx")
    End If
  End Sub
End Class

Можете смело запускать приложение и вводить имя пользователя и пароль. Если была введена верная пара, то вы получите приветствие, в противном случае - сообщение о неверных данных. При этом все данные хранятся в файле Web.config, но уже не в открытом виде, а в хэшированном.

Заключение

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


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=5402