Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Лекция 6:

Работа с потоками данных

< Лекция 5 || Лекция 6: 12345678910111213

Упражнение 9. Защита данных с помощью потока CryptoStream

Часто встречаются ситуации, когда при хранении и передаче данных их защита является основополагающим требованием к приложению. Для защиты данных обычно применяют шифрование с секретным ключем. Одним из способов решения задачи шифрования является применение класса CryptoStream из пространства имен System.Security.Cryptography библиотеки mscorlib.dll. Этот класс служит оболочкой над другими потоками и выполняет криптографические операции над передаваемыми данными.

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

  1. SymmetricAlgorithm - симметричные алгоритмы, которые для шифрования и дешифрования информации используют один и тот же ключ. Производными от класса SymmetricAlgorithm являются тоже абстрактные классы:
    • DES
    • RC2
    • Rijndael
    • TripleDES
  2. AsymmetricAlgorithm - асимметричные алгоритмы, которые для шифрования и дешифрования применяют разные ключи. Асимметричные криптографические алгоритмы также называют инфраструктурой открытого ключа ( PKI - Public Key Infrastructure ) Производными от класса AsymmetricAlgorithm являются тоже абстрактные классы:
    • DSA
    • ECDiffieHellman
    • ECDsa
    • RSA
  3. HashAlgorithm - алгоритмы хеширования или дайджеста сообщения, преобразующие первоначальный текст любой длины в шифрованный текст фиксированной длины. Они выполняют односторонне шифрование, а это значит, что из полученного хешированием шифротекста нельзя восстановить первоначальный документ. Фиксированная длина хеша зависит от применяемого алгоритма и находится в пределах от 16 до 32 байтов. Производными от класса HashAlgorithm являются тоже абстрактные классы:
    • KeyedHashAlgorithm
    • MD5
    • RIPEMD160
    • SHA1
    • SHA256
    • SHA384
    • SHA512

Поскольку перечисленные классы являются абстрактными, то для создания экземплярных объектов они используют классы провайдеров ( Provider - поставщик) или менеджеров ( Manager - управленец).

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

Введение в симметричные алгоритмы .NET

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

Симметричные алгоритмы обычно намного быстрее асимметричных и они хорошо выполняют операции шифрования-дешифрования объемных файлов. Пространство имен System.Security.Cryptography поддерживает симметричные алгоритмы DES, RC2, Rijndael и TripleDES. Только у алгоритма Rijndael есть своя управляемая реализация, остальные поставляются из Microsoft Crypto API готовыми с помощью провайдеров.

Все классы симметричных алгоритмов наследуют от своего общего предка SymmetricAlgorithm, который предоставляет им ряд общих методов и свойств, некоторые из которых приведены в таблице

Таблица 19.9. Некоторые члены класса SymmetricAlgorithm
Член Описание
Key Свойство возвращает или устанавливает секретный ключ, представляющий собой байтовый массив ( bytes )
KeySize Свойство возвращает или устанавливает размер секретного ключа в битах ( bits )
LegalKeySize Свойство возвращает целые величины диапазона корректных размеров секретного ключа в битах: MinSize, MaxSize и допустимый для выбранного алгоритма шаг дискретизации SkipSize
IV Свойство возвращает или устанавливает байтовый вектор инициализации симметричного алгоритма ( Initialization Vector ). Поскольку алгоритмы прокручивают цепочку кодирования несколько раз, беря в качестве исходного результат предыдущего сдвига, то для самой первой итерации требуется начальная информация, которую и содержит IV
BlockSize Свойство возвращает или устанавливает размер блока данных в битах ( bits ), обрабатываемых за один проход. Длинные данные делятся на блоки установленного размера и если для последнего блока данных не хватает, он будет дополнен значениями по умолчанию
LegalBlockSize Свойство возвращает целые величины диапазона корректных размеров блока в битах: MinSize, MaxSize и допустимый для выбранного алгоритма шаг дискретизации SkipSize
Mode Устанавливает режим работы симметричного алгоритма и принимает значения от перечисления CipherMode:
  • CBC ( Cipher Block Chaining ) - сцепление блоков шифра. Каждый следующий блок исходного текста шифруется с учетом результатов шифрования предыдущего блока. Здесь для самого первого блока используется IV
  • ECB ( Electronic Codebook ) - электронная книга шифров. Режим шифрует каждый блок индивидуально. Это означает, что любые одинаковые блоки исходного текста, зашифрованные с тем же самым ключом, будут преобразованы в идентичные блоки шифрованного текста. Такой режим не очень устойчив к взлому, но быстрый и не требует IV
  • CFB ( Cipher Feedback ) - шифр с обратной связью. Режим делит каждый блок на маленькие разделы и обрабатывает их индивидуально со сдвигом, вместо того, чтобы обработать весь блок сразу
  • CTS ( Cipher Text Stealing ) - режим обрабатывает исходный текст любой длины и производит шифрованный текст той же длины
  • OFB ( Output Feedback ) - выходная обратная связь. Режим похож на CFB, но отличается способом заполнения сдвигового регистра
ICryptoTransform CreateEncryptor() Метод создает объект симметричного шифрования и возвращает ссылку на него. Принимает вектор ключа и вектор инициализации
ICryptoTransform CreateDecryptor() Метод создает объект симметричного дешифрования и возвращает ссылку на него. Принимает вектор ключа и вектор инициализации
void GenerateKey() Метод изменяет значение свойства Key случайным образом
void GenerateIV() Метод изменяет значение свойства IV случайным образом
bool ValidKeySize(int bitLength) Проверяет, подходит ли выбранный размер ключа для работы алгоритма

Рассмотрим работу семметричных алгоритмов на примерах.

Пример 1. Поток CryptoStream и шифрование в память

  • Создайте новое решение CryptoStream с проектом Demo1 так

  • Настройте форму в соответствии со следующей таблице свойств
Таблица 19.10.
Элемент Свойство Значение
Сама форма
Form Text Demo1 CryptoStream
Size 563; 438
StartPosition CenterScreen
MaximizeBox False
Метки и тектовые блоки для шифруемой информации
Label Text Source Text:
Location 95; 9
Label Text Encrypted Text:
Location 86; 128
Label Text Decrypted Text:
Location 85; 247
TextBox (Name) txtSourceData
Location 2; 31
Size 284; 88
Multiline True
Lines

Привет всем!

Пусть классики не волнуются:

это не тема по криптографии, это тема про поток CryptoStream

ScrollBars Vertical
TextBox (Name) txtEncryptedData
Location 2; 150
Size 284; 88
Multiline True
ScrollBars Vertical
TextBox (Name) txtDecryptedData
Location 2; 269
Size 284; 88
Multiline True
ScrollBars Vertical
Блок радиокнопок для выбора алгоритма шифрования
GroupBox Text SymmetricAlgorithm
Location 291; 9
Size 259; 136
RadioButton Checked True
Location 8; 16
Size 199; 21
Tag DES
Text DESCryptoServiceProvider
RadioButton Location 8; 40
Size 194; 21
Tag RC2
Text RC2CryptoServiceProvider
RadioButton Location 8; 64
Size 136; 21
Tag Rijndael
Text RijndaelManaged
RadioButton Location 8; 88
Size 231; 21
Tag TripleDES
Text TripleDESCryptoServiceProvider
RadioButton Location 8; 112
Size 175; 21
Tag SymmetricAlgorithm
Text DefaultServiceProvider
Блок с характеристиками выбранного алгоритма шифрования
GroupBox Text Properties SymmetricAlgorithm
Location 291; 151
Size 259; 206
Label Text Key:
Location 5; 26
Label Text IV:
Location 17; 63
Label Text KeySize (bits):
Location 5; 99
Label Text BlockSize:
Location 155; 100
Label Text LegalKeySizes:
Location 15; 137
Label Text LegalBlockSizes:
Location 5; 174
TextBox (Name) txtKey
Location 42; 23
Size 209; 22
TextBox (Name) txtIV
Location 42; 60
Size 209; 22
TextBox (Name) txtKeySize
Location 102; 97
Size 25; 22
TextBox (Name) txtBlockSize
Location 226; 98
Size 25; 22
TextBox (Name) txtLegalKeySizes
Location 124; 134
Size 127; 22
TextBox (Name) txtLegalBlockSizes
Location 124; 171
Size 128; 22
Кнопка для выполнения шифрования
Button (Name) btnExecute
Text Execute
Location 240; 368
  • Выделите все элементы формы командой Edit/Select All и замкните созданный интерфейс командой Format/Lock Controls
  • Выделите все текстовые блоки, кроме txtSourceData, и задайте для них
    • BorderStyle=FixedSingle
  • Выделите все элементы, кроме TextBox и GroupBox, и задайте для них
    • AutoSize=True

Созданный интерфейс должен получиться таким


  • Выделите все текстовые блоки, кроме txtSourceData, и создайте для них общие обработчики событий KeyDown и KeyPress для блокировки редактирования (имена обработчиков введите вручную)

  • Командой Project/Add Class добавьте новый класс с именем ExecuteCryptography.cs и заполните его следующим кодом (приводится полностью)
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
    
namespace Demo1
{
    class ExecuteCryptography
    {
        // Создаем кодировщик символов
        private UTF7Encoding utf7 = new UTF7Encoding();
    
        // Объявляем ссылки на шифровальщик,
        // на массивы Key и IV (Initialization Vector) 
        private SymmetricAlgorithm codec;
        private byte[] key;
        private byte[] initVector;// IV
    
        // Конструктор
        public ExecuteCryptography(SymmetricAlgorithm codec)
        {
            this.key = codec.Key;
            this.initVector = codec.IV;
            this.codec = codec;
        }
    
        public string Encrypt(string text)
        {
            byte[] input = utf7.GetBytes(text);
            byte[] output = Transform(input,
                            codec.CreateEncryptor(key, initVector));
            return Convert.ToBase64String(output);
        }
        public string Decrypt(string text)
        {
            byte[] input = Convert.FromBase64String(text);
            byte[] output = Transform(input,
                            codec.CreateDecryptor(key, initVector));
            return utf7.GetString(output);
        }
    
        private byte[] Transform(byte[] input, ICryptoTransform cryptoTransform)
        {
            // Создаем потоки
            MemoryStream memoryStream = // Поток памяти
                new MemoryStream();
            CryptoStream cryptoStream = // Шифропоток-оболочка
                new CryptoStream(memoryStream, 
                    cryptoTransform, CryptoStreamMode.Write);
    
            // Кодируем в поток памяти через шифропоток
            cryptoStream.Write(input, 0, input.Length);
            cryptoStream.FlushFinalBlock();
    
            // Преобразуем заполненный поток памяти в массив байтов
            byte[] result = memoryStream.ToArray();
    
            // Останавливаем потоки
            memoryStream.Close();
            cryptoStream.Close();
    
            // Возвращаем кодированное/раскодированное
            return result;
        }
    
        // Перегруженные методы
        public byte[] Encrypt(byte[] input)
        {
            return Transform(input,
                   codec.CreateEncryptor(key, initVector));
        }
        public byte[] Decrypt(byte[] input)
        {
            return Transform(input,
                   codec.CreateDecryptor(key, initVector));
        }
    }
}

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

MemoryStream memoryStream = // Поток памяти
                new MemoryStream();
            CryptoStream cryptoStream = // Шифропоток-оболочка
                new CryptoStream(memoryStream, 
                    cryptoTransform, CryptoStreamMode.Write);
            StreamWriter streamWriter = new StreamWriter(cryptoStream);
            streamWriter.Write(txtSourceData.Text);
            streamWriter.Flush();            
            cryptoStream.FlushFinalBlock();
  • Заполните файл Form1.cs следующим кодом (приводится полностью)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
    
// Дополнительные пространства имен
using System.Security.Cryptography;
using System.IO;
    
namespace Demo1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
    
            // Подписываемся на событие Click кнопки
            btnExecute.Click += btnExecute_Click;
        }
           
        // Обработчик кнопки 
        private void btnExecute_Click(object sender, EventArgs e)
        {
            // Создаем объект симметричного шифрования
            SymmetricAlgorithm codec = null;// Тип общего предка 
            switch (SelectedRadio().Trim())
            {
                case "DES":
                    codec = new DESCryptoServiceProvider();
                    break;
                case "RC2":
                    codec = new RC2CryptoServiceProvider();
                    break;
                case "Rijndael":
                    codec = new RijndaelManaged();
                    break;
                case "TripleDES":
                    codec = new TripleDESCryptoServiceProvider();
                    break;
                case "SymmetricAlgorithm":
                    codec = SymmetricAlgorithm.Create();
                    break;
                default:
                    MessageBox.Show("Выделите провайдер шифрования");
                    return;
            }
    
            // Создаем экземпляр нашего класса
            // и передаем ему выбранный шифровальшик
            ExecuteCryptography executeCryptography =
                new ExecuteCryptography(codec);
    
            // Шифруем текст
            string sourceData = txtSourceData.Text;
            string encryptedData = executeCryptography.Encrypt(sourceData);
            txtEncryptedData.Text = encryptedData;
    
            // Расшифровываем текст
            string decryptedData = executeCryptography.Decrypt(encryptedData);
            txtDecryptedData.Text = decryptedData;
    
            //////////////////////////////////////////////////
            // Показываем характеристики алгоритма шифрования
            //////////////////////////////////////////////////
            {
                txtKey.Text = Encoding.UTF7.GetString(codec.Key);// Key
                txtIV.Text = Encoding.UTF7.GetString(codec.IV);// Initialization Vector
    
                txtKeySize.Text = codec.KeySize.ToString();// KeySize
                StringBuilder sb = new StringBuilder();
                foreach (KeySizes sizes in codec.LegalKeySizes)
                {
                    sb.Append(sizes.MinSize.ToString() + "-" +
                        sizes.MaxSize.ToString() + 
                        " (" + sizes.SkipSize.ToString() + ") ");
                }
                txtLegalKeySizes.Text = sb.ToString();// LegalKeySizes
    
                txtBlockSize.Text = codec.BlockSize.ToString();// BlockSize
                sb = new StringBuilder();
                foreach (KeySizes sizes in codec.LegalBlockSizes)
                {
                    sb.Append(sizes.MinSize.ToString() + "-" +
                        sizes.MaxSize.ToString() + 
                        " (" + sizes.SkipSize.ToString() + ") ");
                }
                txtLegalBlockSizes.Text = sb.ToString();// LegalBlockSizes
            }
        }
    
        // Динамически ищем включенный RadioButton
        string SelectedRadio()
        {
            string tag = "";
            foreach (Control ctrl in groupBox1.Controls)
            {
                RadioButton radio = ctrl as RadioButton;// Приводим ссылку
                if (radio.Checked)
                {
                    tag = (String)radio.Tag;// Приводим ссылку
                    break;
                }
            }
    
            return tag;
        }
    
        // Блокировка редактирования всех целевых TextBox
        private void txtTarget_KeyDown(object sender, KeyEventArgs e)

        {
            e.Handled = true;
        }
        private void txtTarget_KeyPress(object sender, KeyPressEventArgs e)
        {
            e.Handled = true;
        }
    }
}
  • Запустите приложение и испытайте его работу - результат может быть таким

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

  • Разберитесь с кодом примера
< Лекция 5 || Лекция 6: 12345678910111213
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974