Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - ASP.NET »

Обсудить на форуме Обсудить на форуме

Средства безопасности ASP.NET Часть 3 - Криптография
samples.zip (251,72 Kb)

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

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

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

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

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

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

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

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

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

Официально члены, классифицированные по принципу действия, принято назвать криптографическими примитивами (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 ключа: один из них считается секретным, а другой открытым. Оба эти ключа математически взаимосвязаны, а то, какой из них будет секретным, а какой открытым определяется пользователем. При этом нет никакой разницы между тем, кому из них достанется та или иная роль. Отличия появляются на практике, и выражены они полной противоположностью действий, иными словами, текст, зашифрованный открытым ключом, может быть расшифрован только секретным, и наоборот: зашифровав секретным ключом, расшифровать удастся только открытым.

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

В языке UML под термином актор понимается некое действующее лицо

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

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

Рис. 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.

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

  1. Смириться и использовать массивы байтов с потоками
  2. Найти удовлетворяющую ваши запросы альтернативу
  3. Придумать свой криптометод
  4. Отказаться от криптографии

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

 

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

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

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

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="AdvCrypter.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. Давайте рассмотрим его подробнее. Вначале создаётся экземпляр провайдера шифрования. Для каждого криптометода существует свой провайдер, потому их имена объявлены примерно в следующих форматах: <ИмяКриптометода>CryptoServiceProvider или <ИмяКриптометода>Managed. Например, DESCryptoServiceProvider, RSACryptoServiceProvider, RijndealManaged и др. После инициализации криптопровайдера создаётся переменная, реализующая интерфейс-шифратор; далее открывается файловый поток для записи данных в файл. После этого, инициализируется криптопоток, в аргументах которого задаётся конечный поток данных (в нашем случае - это файловый поток fs); способ криптографической трансформации, т. е. интрефейс-шифратор, и действие, которое необходимо выполнить с данными. Из возможных действий выделяются чтение и запись.

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

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

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

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

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

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

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

Рис. 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>
    

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

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

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

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

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

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

Предыдущий пример был самым простейшим: мы только зашифровали текст без передачи каких-либо дополнительных параметров. Но вспомним теорию: в ней было сказано, что для эффективной защиты на симметричные криптометоды нужно накладывать ключ и инициализационный вектор (далее 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
Repeat:
            If dlgS.ShowDialog = DialogResult.OK Then
                Text = dlgS.FileName
                Return dlgS.FileName
            Else
                MsgBox("You must create or select a file!", MsgBoxStyle.Exclamation)
                GoTo Repeat
            End If
        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´ов шифрования и дешифрования

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

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

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 для определения длины считываемого файла. Необходимость введения этого класса возникла из-за того, что для получения данных из файла используется класс BinaryReader. Его метод 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)

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

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

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

Для шифрования и дешифрования вызываются методы Encrypt и Decrypt объекта RSACryptoServiceProvider. Синтаксисы этих методов идентичны, они только выполняют противоположные действия. Остановимся на одном из них:

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

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

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

Теперь запустите созданное приложение и попробуйте что-нибудь зашифровать. В случае успеха вы увидите результат, подобный рис. 8.

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

Вспомните примечание в начале этого раздела: "RSA способен обработать до 43 байтов". Для того, чтобы это проверить, введите текст длиною более 43 символов и нажмите Encrypt and Decrypt. Должно появиться исключение, как на рис. 9:

Рис. 9 - Метод RSA способен обработать до 43 байтов

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

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

Рис. 10 - Структура 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 просто выводит всю доступную информацию.

Один только код пока мало о чём говорит, поэтому запустим приложение и посмотрим на результат. Из рис. 11 видно, что данные об открытом ключе содержатся в переменных 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, наоборот, генерирует строку с данными о ключах, причём можно указать, нужно ли экспортировать информацию о секретном ключе.

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

Хеширование

Напомним ещё раз, что хеш - это числовое значение фиксированной длины, которое является уникальным представлением исходных данных. Среда .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!", то хеши совпадут (рис.12), в противном случае - нет (рис. 13).

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

Рис. 13 - Хеши не совпали, значит данные были изменены.

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

Dim SHA384 As New SHA384Managed

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

Dim SHA384 As New MD5CryptoServiceProvider

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

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

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

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

Так же как и для всех предыдущих криптографических примитивов в инфраструктуре .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

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

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

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

Рис. 14 - Цифровая подпись подтверждена

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

- Листинг 7.2: 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>")

        ´ Проверяем
        signText(10) = 144
        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Новая строка выделена жирным шрифтом:

signText(10) = 144

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

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

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

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

Из-за того, что метод RSA работает только с хешами SHA1 и MD5, а DSA - лишь с SHA1, то даже не пытайтесь применять другие хеш-алгоритмы, например, SHA256 или SHA384, потому что вы, в конечном счёте, получите ошибку, и ничего работать не будет.

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

- Листинг 8.1: Sign1/DefAlg.aspx

<%@ Page Language="vb" AutoEventWireup="false" Inherits="Sign1.DefAlg"%>
<%@ Import Namespace="System.Security.Cryptography"%><html>
  <head>
    <title>DefAlg</title>
  </head>
  <body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
		<%			Dim DSA As New DSACryptoServiceProvider
			REsponse.Write(DSA.SignatureAlgorithm)
		%>    </form>
  </body>
</html>

Открыв эту страницу, вы увидите окно, подобное рис. 16:

Рис. 16 - Информация об алгоритме цифровой подписи

Из рис. 16 видно, что асимметричным алгоритмом является DSA, а хеш-алгоритмом - SHA1.

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 описывает всё происходящее:

- Листинг 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
	}
}

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

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

 

DSASignatureFormatter DSAform = new DSASignatureFormatter(DSA);

DSASignatureDeformatter DSAdeform = new DSASignatureDeformatter(DSA);

 

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

Теперь запустите приложение и сравните, как создаёт цифровую подпись совокупность алгоритмов DSA-SHA1 и RSA-MD5 (см. рис. 17).

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

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

В первой части статьи было обещано рассмотреть хранение имени пользователя и пароля в файле 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, который выполняет все необходимые без вмешательства разработчика.

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

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

Теперь разработаем приложение, использующее эти криптограммы. Для этого создайте новый 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, но уже не в открытом виде, а в хешированном.

Создаём свой криптометод

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

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

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

Перейдём к практике. Для этого создайте новое Web-приложение и воспользуйтесь кодом из листингов 15.1 и 15.2:

- Листинг 15.1: Mirror/default.aspx

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="Mirror.WebForm1"%><HTML>
	<HEAD>
		<title>Mirror</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="Mirror"/>
		</form>
	</body>
</HTML>

- Листинг 15.2: Mirror/default.aspx.vb

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

    Private Sub btn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btn.Click
        Dim i As Integer, c As Byte
        Dim sb As New StringBuilder

        For i = 1 To Len(txt.Value)
            ´ Извлекаем каждый символ из строки
            c = Asc(Mid(txt.Value, i, 1))
            ´ ШИфруем и добавляем в объект StringBuilder
            sb.Append(Chr(c Xor 255))
        Next

        ´ Возвращаем значение
        txt.Value = sb.ToString
    End Sub
End Class

Код листинга 15.2 очень короткий, и посути шифрование осуществляется всего в 1 строчке:

c Xor 255

Данное выражение равносильно следующему:

255 - c

Это означает, что одна функция выполняет как шифрование, так и дешифрование. Алгоритм потому и называется Mirror, т. е. зеркало, потому что он симметричен относительно 127-го символа шрифта.

Например, в шрифтах, поддерживающих кириллицу, 255-й символ - это буква "я". Теперь выполним операцию Xor 255, и этот символ примет индекс 0 (потому что 255 Xor 255 = 0). Повторим действия ещё раз: 0 Xor 255 = 255 - снова получаем 255-й символ, т. е. букву "я".

В коде листинга 15.2 был применён объект StringBuider, а не конкатенация строк, потому что с ростом размера строки растёт и время выполнения конкатенации строк, а объект StringBuilder при этом продолжает идти в том же темпе. Одним словом, StringBuilder значительно быстрее конкатенации.

Теперь самое время запустить приложение и криптировать какой-нибудь текст (рис. 19):

Рис. 19 - Строка "Hello, World!" зашифрована методом Mirror

Заключение

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


Может пригодится:


Автор: Сергей Бакланов
Прочитано: 13629
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (1)

Прислал: campingmap
спасибо

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру