Сообщений 19    Оценка 30        Оценить  
Система Orphus

Средства безопасности ASP.NET

Часть 3 – Криптография

Автор: Сергей Бакланов
Источник: RSDN Magazine #3-2004
Опубликовано: 15.01.2005
Исправлено: 10.12.2016
Версия текста: 1.0
Классификация
Шифрование с секретным ключом
Шифрование с открытым ключом
Цифровая подпись
Хэширование
Криптография средствами .NET
Симметричные системы
Асимметричные системы
Хэширование
Цифровые подписи
Исполним обещанное…
Заключение

Код к статье

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

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

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

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

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

ПРИМЕЧАНИЕ

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

Официально члены, классифицированные по принципу действия, принято назвать криптографическими примитивами (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

PublicClass WebForm1
    Inherits System.Web.UI.Page

    ProtectedWithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
    ProtectedWithEvents btnEncrypt As System.Web.UI.WebControls.Button
    ProtectedWithEvents 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()> _
    PrivateSub InitializeComponent()

    EndSub'NOTE: The following placeholder declaration is required by the ' Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) HandlesMyBase.Init
        'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
        InitializeComponent()
    EndSub

#End Region

    PrivateSub btnEncrypt_Click(ByVal sender AsObject, _
      ByVal e As System.EventArgs) Handles btnEncrypt.Click
        ' Провайдер DES-шифрованияDim DES AsNew DESCryptoServiceProvider
        ' Интерфейс-шифраторDim DES_Encryptor As ICryptoTransform = DES.CreateEncryptor
        ' Файловый потокDim fs AsNew FileStream(Server.MapPath("temp.dat"), FileMode.Create)
        ' Поток шифрованияDim DESCryptoStream AsNew CryptoStream(fs, _
             DES_Encryptor, CryptoStreamMode.Write)
        ' Класс для получения массива байтов из строки и массив байтовDim enc AsNew UnicodeEncoding, bytes() AsByte'===========ЭТАП 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 AsNew StreamReader(fs)
        ' Читаем зашифрованный текст из файлового потока
        lblResult.Text = sr.ReadToEnd
        ' Закрываем потоки
        sr.Close()
        fs.Close()

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

В листинге 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

PublicClass WebForm1
  Inherits System.Web.UI.Page

  ProtectedWithEvents btnEncrypt As System.Web.UI.WebControls.Button
  ProtectedWithEvents 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()> _
  PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required by ' the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

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

    ' Интерфейс-шифратор, для создания которого передаются ключ и IVDim DES_Encryptor As ICryptoTransform = _
      DES.CreateEncryptor(DES.Key, DES.IV)
    ' Поток шифрованияDim DESCryptoStream AsNew CryptoStream(Response.OutputStream, _
      DES_Encryptor, CryptoStreamMode.Write)
    ' Класс для получения массива байтов из строки и массив байтовDim enc AsNew UnicodeEncoding, bytes() AsByte' Получаем массив байтов из строки поля txt
    bytes = enc.GetBytes(txt.Value)
    ' Шифруем данные
    DESCryptoStream.Write(bytes, 0, bytes.Length)
    ' Закрываем потоки
    DESCryptoStream.Close()
  EndSubEndClass

В коде появились 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

PublicClass Form1
  Inherits System.Windows.Forms.Form

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

  ' ключ и IV для DES и RC2Dim IV() AsByte = {172, 44, 172, 193, 7, 48, 131, 165}
  Dim Key() AsByte = {93, 252, 76, 113, 209, 29, 253, 168}
  ' Ключ и IV для TripleDESDim TripleKey() AsByte = {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() AsByte = {19, 127, 43, 85, 21, 117, 80, 151}
  ' Ключ и IV для AESDim aesKey() AsByte = {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() AsByte = {196, 161, 224, 67, 23, 143, 39, 43, 188, 91, _
    247, 125, 97, 95, 246, 26}

  PrivateSub 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

    SelectCase tlb.Buttons.IndexOf(e.Button)
      Case 1      ' New
        Text = ""
        txt1.Text = ""
        txt2.Text = ""Case 2      ' OpenIf 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()
        EndIfCase 3      ' SaveIf 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()
        EndIfEndSelectEndSub' Процедура шифрованияPrivateSub 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 AsNew FileStream(GetFileName, FileMode.Create)
    Dim CrStream AsNew CryptoStream(fs, Cryptor, CryptoStreamMode.Write)

    ' Получаем массив байтовDim b() AsByte = ToBytes(txt1.Text)
    ' Шифруем
    CrStream.Write(b, 0, b.Length)
    ' Закрываем поток
    CrStream.Close()
    fs.Close()
    fs = Nothing' Создаём читатель потока
    fs = New FileStream(GetFileName, FileMode.Open)
    Dim sr AsNew StreamReader(fs, Encoding.Default)
    ' Показываем результат
    txt2.Text = sr.ReadToEnd
    ' Закрываем потоки
    sr.Close()
    fs.Close()
  EndSub' Процедура дешифрованияPrivateSub 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 AsNew FileStream(GetFileName, FileMode.Open)
    Dim CrStream AsNew CryptoStream(fs, Cryptor, CryptoStreamMode.Read)
    Dim sr AsNew BinaryReader(CrStream)
    ' Служит для определения размера файлаDim f AsNew FileInfo(GetFileName)
    Dim enc AsNew UnicodeEncoding

    ' Показываем результат
    txt2.Text = enc.GetString(sr.ReadBytes(f.Length))
    ' Закрываем потоки
    sr.Close()
    fs.Close()
  EndSub#Region"Service functions"' Функция, возвращающая выбранный алгоритмPrivateFunction DefineAlg() As SymmetricAlgorithm
    If optDES.Checked = TrueThenReturnNew DESCryptoServiceProvider
    ElseIf optTripleDES.Checked = TrueThenReturnNew TripleDESCryptoServiceProvider
    ElseIf optAES.Checked = TrueThenReturnNew RijndaelManaged
    ElseIf optRC2.Checked = TrueThenReturnNew RC2CryptoServiceProvider
    EndIfEndFunction' Функция, возвращающая имя файла для шифрованияPrivateFunction GetFileName() AsStringIf Text = ""ThenDoIf dlgS.ShowDialog = DialogResult.OK Then
          Text = dlgS.FileName
          Return dlgS.FileName
        Else
          MsgBox("You must create or select a file!", _
            MsgBoxStyle.Exclamation)
        EndIfLoopElseReturn Text
    EndIfEndFunction' Функция, преобразующая строку в массив байтовPrivateFunction ToBytes(ByVal s AsString) AsByte()
    Dim enc AsNew UnicodeEncoding
    Return enc.GetBytes(s)
  EndFunction' Функция, создающая интерфейс-шифраторPrivateFunction CreateEnc(ByVal AlgType As SymmetricAlgorithm) _
    As ICryptoTransform
    If (TypeOf AlgType Is DESCryptoServiceProvider) Or _
      (TypeOf AlgType Is RC2CryptoServiceProvider) ThenReturn AlgType.CreateEncryptor(Key, IV)
    ElseIfTypeOf AlgType Is TripleDESCryptoServiceProvider ThenReturn AlgType.CreateEncryptor(TripleKey, TripleIV)
    ElseIfTypeOf AlgType Is RijndaelManaged ThenReturn AlgType.CreateEncryptor(aesKey, aesIV)
    EndIfEndFunction' Функция, создающая интерфейс-дешифраторPrivateFunction CreateDec(ByVal AlgType As SymmetricAlgorithm) _ 
    As ICryptoTransform
    If (TypeOf AlgType Is DESCryptoServiceProvider) Or _
      (TypeOf AlgType Is RC2CryptoServiceProvider) ThenReturn AlgType.CreateDecryptor(Key, IV)
    ElseIfTypeOf AlgType Is TripleDESCryptoServiceProvider ThenReturn AlgType.CreateDecryptor(TripleKey, TripleIV)
    ElseIfTypeOf AlgType Is RijndaelManaged ThenReturn AlgType.CreateDecryptor(aesKey, aesIV)
    EndIfEndFunction
#End Region
EndClass

Получился довольно громоздкий код. Давайте попробуем разобраться в нём и уловить наиболее важные моменты. В самом начале объявлены 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 AsNew FileStream(GetFileName, FileMode.Create)
    Dim CrStream AsNew CryptoStream(fs, Cryptor, CryptoStreamMode.Write)

    ' Получаем массив байтовDim b() AsByte = ToBytes(txt1.Text)
    ' Шифруем
    CrStream.Write(b, 0, b.Length)
    ' Закрываем поток
    CrStream.Close()
    fs.Close()
    fs = Nothing' Создаём читатель потока
    fs = New FileStream(GetFileName, FileMode.Open)
    Dim sr AsNew StreamReader(fs, Encoding.Default)
    ' Показываем результат
    txt2.Text = sr.ReadToEnd
    ' Закрываем потоки
    sr.Close()
    fs.Close()
  EndSub

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

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

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

Как видно из листинга, функция имеет тип 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 AsString) AsByte()
  Dim enc AsNew UnicodeEncoding
  Return enc.GetBytes(s)
EndFunction

Свойство 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 AsNew FileStream(GetFileName, FileMode.Open)
  Dim CrStream AsNew CryptoStream(fs, Cryptor, CryptoStreamMode.Read)
  Dim sr AsNew BinaryReader(CrStream)
  ' Служит для определения размера файлаDim f AsNew FileInfo(GetFileName)
  Dim enc AsNew UnicodeEncoding

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

Как видите, начало процедуры (зона объявления переменных) аналогично началу в процедуре шифрования. Только при инициализации интерфейса трансформации используется не функция 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

PublicClass WebForm1
  Inherits System.Web.UI.Page

#Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> _
    PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

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

  
  Dim enc AsNew UnicodeEncoding

  PrivateSub btnEnc_Click(ByVal sender AsObject, _
    ByVal e As System.EventArgs) Handles btnEnc.Click
  ' Генерируем пару из открытого и секретного ключейDim RSAcsp AsNew 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)
  EndSub' Функция шифрования/дешифрованияPrivateFunction RSACrypt(ByVal DataToEncrypt() AsByte, _
    ByVal RSAKey As RSAParameters, ByVal Action As KindOfAction) AsStringDim RSA AsNew RSACryptoServiceProvider

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

С первого взгляда видно, что код реализации асимметричного метода намного короче, чем код симметричного. Одной из причин столь малых габаритов является то, что нет нужды инициализировать множество объектов, связывая их друг с другом. В этом примере используется один-единственный класс – 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() AsByte, _
  ByVal fOAEP AsBoolean) AsByte()

Первый параметр этого метода принимает массив байтов для шифрования, а второй активизирует или деактивизирует Дополнение Оптимального Асимметричного Шифрования (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 AsNew 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()
  EndSub' Процедура, отображающая информацию о ключахSub ShowKeyInfo(ByVal KeyInfo As RSAParameters)
    Dim enc AsNew UnicodeEncoding
    OnErrorResumeNext

    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))
  EndSubEndModule

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

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

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

          Public
          Overrides
          Sub FromXmlString(ByVal xmlString AsString)
PublicOverridesFunction ToXmlString(_
ByVal includePrivateParameters AsBoolean) AsString

Метод 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

PublicClass WebForm1
  Inherits System.Web.UI.Page

  ProtectedWithEvents txt As System.Web.UI.HtmlControls.HtmlTextArea
  ProtectedWithEvents lblOriginalHash As System.Web.UI.WebControls.Label
  ProtectedWithEvents lblNewHash As System.Web.UI.WebControls.Label
  ProtectedWithEvents lblResult As System.Web.UI.WebControls.Label
  ProtectedWithEvents 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()> PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

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

  PrivateSub btn_Click(ByVal sender AsObject, _
    ByVal e As System.EventArgs) Handles btn.Click
    Dim i AsIntegerDim NewHash() AsByteDim same AsBoolean = 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 = FalseExitForEndIfNextIf same = TrueThen
      lblResult.ForeColor = Color.Green
      lblResult.Text = "Hash1 = Hash2"Else
      lblResult.ForeColor = Color.Red
      lblResult.Text = "Hash1 <> Hash2"EndIfEndSubPrivateSub Page_Load(ByVal sender AsObject, _
    ByVal e As System.EventArgs) HandlesMyBase.Load
    Const ORIGINAL_STRING AsString = "Hello World!"' Хэшируем строку 'Hello World!'
    OriginalHash = SHA384.ComputeHash(enc.GetBytes(ORIGINAL_STRING))
    lblOriginalHash.Text = enc.GetString(OriginalHash)
  EndSubEndClass

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


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


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

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

        Dim SHA384 AsNew SHA384Managed

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

        Dim SHA384 AsNew 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() AsByte) AsByte()
PublicFunction SignData(ByVal buffer() AsByte, _
  ByVal offset AsInteger, ByVal count AsInteger) AsByte()
PublicFunction SignData(ByVal inputStream As System.IO.Stream) AsByte()

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

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

Листинг 7.1: Sign1/default.aspx.vb
          Imports System.Security.Cryptography
Imports System.Text

PublicClass WebForm1
  Inherits System.Web.UI.Page

#Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

  PrivateSub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Load
    ' Исходный текстConst CLEAR_STRING AsString = "Hello, World!"Dim enc AsNew UnicodeEncoding
    ' Массивы байтDim clearText() AsByte, signText() AsByte' Создаём пару ключейDim DSA AsNew 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>")
    EndIfEndSubEndClass

Этот код сначала преобразует строку в массив байт с помощью объекта 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() AsByte, _
  ByVal str AsString) AsByte()

Этот метод имеет свои преимущества над методом 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

PublicClass WebForm1
  Inherits System.Web.UI.Page

#Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> _
  PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

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

  PrivateSub btn_Click(ByVal sender AsObject, _
    ByVal e As System.EventArgs) Handles btn.Click
    Dim SHA1 AsNew SHA1CryptoServiceProvider
    Dim DSA AsNew DSACryptoServiceProvider

    Dim hashText() AsByte, signText() AsByteDim enc AsNew 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>"EndIfEndSubEndClass

Код листинга 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>publicclass WebForm1 : System.Web.UI.Page
  {
    privatevoid Page_Load(object sender, System.EventArgs e)
    {
      // Исходный текстconststring 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
    overrideprotectedvoid 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>privatevoid 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>publicclass WebForm1 : System.Web.UI.Page
  {
    privatevoid Page_Load(object sender, System.EventArgs e)
    {
      // Исходная строкаconststring 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) + 
                "&nbsp;<font color='Green'>" +
                "Good</font><br>");
      else
        Response.Write("<b>DSA signature:</b> " + 
          enc.GetString(DSAsignText) + 
          "&nbsp;<font color='Red'>" +
          "Bad</font><br>");
      //RSA-------------if(RSAdeform.VerifySignature(RSAhashText, RSAsignText) == true)
        Response.Write("<b>RSA signature:</b> " + 
          enc.GetString(RSAsignText) + 
          "&nbsp;<font color='Green'>" +
          "Good</font>");
      else
        Response.Write("<b>RSA signature:</b> " + 
          enc.GetString(RSAsignText) + 
          "&nbsp;<font color='Red'>" +
          "Bad</font>");
    }

    #region Web Form Designer generated code
    overrideprotectedvoid 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>privatevoid 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

PublicClass WebForm1
  Inherits System.Web.UI.Page

#Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

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

  PrivateSub btn_Click(ByVal sender AsObject, _
    ByVal e As System.EventArgs) Handles btn.Click
    ' хэшируем
    lbl.Text = FormsAuthentication.HashPasswordForStoringInConfigFile( _
      txt.Value, DefAlg())
  EndSub' Функция, определяющая алгоритмPrivateFunction DefAlg() AsStringIf optList.Items(0).Selected = TrueThenReturn"SHA1"ElseReturn"MD5"EndIfEndFunctionEndClass

Как видите, код листинга 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

PublicClass login
    Inherits System.Web.UI.Page

#Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough()> _
    PrivateSub InitializeComponent()

    EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) HandlesMyBase.Init
      'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
      InitializeComponent()
    EndSub

#End Region

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

    PrivateSub btn_Click(ByVal sender AsObject, _
      ByVal e As System.EventArgs) Handles btn.Click
      If FormsAuthentication.Authenticate(txtName.Text, txtPassword.Text) Then
        FormsAuthentication.RedirectFromLoginPage(txtName.Text, False)
      Else
        lbl.Visible = TrueEndIfEndSubEndClass
Листинг 14.3: HashedCredentials/default.aspx.vb
      Imports System.Web.Security

PublicClass WebForm1
  Inherits System.Web.UI.Page

#Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent()

  EndSub'NOTE: The following placeholder declaration is required ' by the Web Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor.
    InitializeComponent()
  EndSub

#End Region

  PrivateSub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) HandlesMyBase.Load
    If User.Identity.IsAuthenticated Then
      Response.Write("<h3><i>Hello, " & User.Identity.Name & "</i></h3>")
    Else
      Response.Redirect("login.aspx")
    EndIfEndSubEndClass

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

Заключение

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


Эта статья опубликована в журнале RSDN Magazine #3-2004. Информацию о журнале можно найти здесь
    Сообщений 19    Оценка 30        Оценить