Разработка полноценных Windows-приложений
3. Шифрование и дешифрование текста
Создадим ресурс диалога для шифрования с идентификатором IDD_CIPHER_DIALOG. Добавим два текстовых поля (Edit Control), два флажка-переключателя (Check Box), кнопку (Button), индикатор сосотояния (Progress Bar) и групповое поле (Group Box). Шаблон получившегося диалога приведен на рисунке: рис. 5.5
Первое текстовое поле предназначено для ввода ключа шифрования, свойство Password должно иметь значение true. В групповое поле объединены элементы управления, отвечающие за вывод информации. Кнопка открывает диалог для указания пути к файлу в который будет сохранен шифротекст. Путь к файлу выводится во второе текстовое поле. Защитим его от изменения, установив свойство Read Only в значение true. Первый флажок-переключатель сигнализирует о необходимости записи конечного результата в файл. Второй-о необходимости вывода результата в основное текстовое поле программы. Добавим в приложение класс диалога, созданный на основе данного ресурса. Добавим также необходимые переменные. Класс CCipherDialog:
#pragma once
// CCipherDialog dialog
class CCipherDialog : public CDialog
{
DECLARE_DYNAMIC(CCipherDialog)
public:
CCipherDialog(CWnd* pParent = NULL); // standard constructor
virtual ~CCipherDialog();
// Dialog Data
enum { IDD = IDD_CIPHER_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public:
BOOL m_bCiphType; //Флаг указывает на то,какое действие необходимо
выполнить (шифрование/дешифрование)
CString m_sFilePathStr; //Переменная, связанная с текстовым полем,
в которое выводится путь файла для сохранения
CString m_sBufStr; //Переменная буфер
BOOL m_bToFile; //Переменная, связанная с первым флажком переключателем
BOOL m_bDisplay; //Переменная, связанная со вторым флажком переключателем
CString m_sKeyStr; //Переменная, связанная с первым текстовым полем,
в которое вводится ключ
int table[161]; //Таблица алфавита
int m_nTimer; //Счетчик таймера
int m_nCount; //Переменная индикатор хода процесса шифрования/дешифрования
bool m_bFinProc; //Флаг, сигнализирующий о том, прошел ли процесс до конца
public:
afx_msg void OnBnClickedButtonBrowse();
afx_msg void OnTimer(UINT_PTR nIDEvent);
protected:
virtual void OnCancel();
virtual void OnOK();
};Конструктор класса CCipherDialog:
CCipherDialog::CCipherDialog(CWnd* pParent /*=NULL*/)
: CDialog(CCipherDialog::IDD, pParent) //Инициализация переменных
, m_bCiphType(FALSE)
, m_sFilePathStr(_T(""))
, m_sBufStr(_T(""))
, m_bToFile(FALSE)
, m_bDisplay(FALSE)
, m_sKeyStr(_T(""))
, m_nTimer(0)
, m_nCount(0)
, m_bFinProc(true)
{
for(int i = 0; i < 95; i++) //Создание таблицы для шифрования символов
table[i] = i + 32; //Используются ASCII коды только печатных символов
for(int i = 95; i < 159; i++)
table[i] = i - 159;
table[159] = -88;
table[160] = -72;
}Функция DoDataExchenge:
void CCipherDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_FILEPATH, m_sFilePathStr);
DDX_Check(pDX, IDC_CHECK_TOFILE, m_bToFile);
DDX_Check(pDX, IDC_CHECK_TOCLIENT, m_bDisplay);
DDX_Text(pDX, IDC_EDIT_KEY, m_sKeyStr);
}Функция DoDataExchenge используется для создания связей между переменными и элементами управления. При добавлении переменных с помощью мастера, ее код генерируется автоматически.
Для связи диалога с основным окном программы создадим пункт меню Cipher и в нем два подпункта Cipher
Encipher… и Cipher
Decipher… и обработаем их. Для создания объектов класса CCipherDialog добавим в начало файла ChildView.h строку
#include "CipherDialog.h"
Обработчик пункта меню Cipher
Encipher…
void CChildView::OnCipherEncipher()
{
CCipherDialog dlg; //Создание диалога шифрования/дешифрования
dlg.m_bCiphType = TRUE; //Инициализация флага индикатора действия
(будем производить шифрование)
m_Rich.GetWindowTextA(dlg.m_sBufStr); //Копирование текста из текстового поля
в строковую переменную - буфер диалога
if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
if(dlg.m_bToFile) //Если установлен флаг сохранения результата шифрования/дешифрования
{
int nBuf; //количество символов в тексте
char *dinBuf; //буфер с текстом
if(dlg.m_sFilePathStr != "") //Если верно указан путь файла
{
CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записиси
nBuf = dlg.m_sBufStr.GetLength() + 1; //Записываем количество символов текста в переменную
dinBuf = new char [nBuf]; //Инициализируем буфер
strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); //Копируем данные из диалога в буфер
OutFile.Write(dinBuf,nBuf); //Записываем данные из буфера в файл
}
else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено");
//Если неверно указан путь к файлу,то сообщаем об ошибке
}
if(dlg.m_bDisplay) //Если установлен флаг вывода результатов на экран
{
m_Rich.Clear(); //Очищаем текстовое поле
m_Rich.SetWindowTextA(dlg.m_sBufStr); //Записываем результат шифрования/дешифрования в текстовое поле
}
}
}В данном пункте важен код, выделенный жирным шрифтом, остальное относится к выводу результатов шифрования/дешифрования на экран или их сохранения в файл. Для получения текста из элемента управления Rich Edit, используется метод GetWindowText(CString), аргументом ему передается строка, в которую будет записан текст. Для записи текста в текстовое поле, используется метод SetWindowText(CString).
Обработчик пункта меню Cipher
Decipher…
void CChildView::OnCipherDecipher()
{
CCipherDialog dlg;
m_Rich.GetWindowTextA(dlg.m_sBufStr);
if(dlg.DoModal() == IDOK)
{
if(dlg.m_bToFile)
{
int nBuf;
char *dinBuf;
if(dlg.m_sFilePathStr != "")
{
CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite);
nBuf = dlg.m_sBufStr.GetLength() + 1;
dinBuf = new char [nBuf];
strcpy_s(dinBuf,nBuf,dlg.m_sBufStr);
OutFile.Write(dinBuf,nBuf);
}
else
AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено");
}
if(dlg.m_bDisplay)
{
m_Rich.Clear();
m_Rich.SetWindowTextA(dlg.m_sBufStr);
}
}
}Также как и в предыдущем обработчике строки, важные в данном пункте, выделены жирным шрифтом. Единственное отличие данного обработчика от предыдущего состоит в том, что мы не инициализируем переменную dlg.m_bCiphType. Это говорит о том, что будет происходить дешифрование. Теперь реализуем алгоритм шифрования. Переопределим виртуальную функцию OnOk() класса CCipherDialog.
Переопределенный метод OnOk:
void CCipherDialog::OnOK()
{
MSG message; //Структура сообщение
UpdateData(true); //Обновляем данные
GetDlgItem(IDOK)->EnableWindow(FALSE); //Отключаем возможность повторного нажатия
кнопки Ok пока происходит шифрование
int nKeyLength = m_sKeyStr.GetLength(); //Инициализация переменной для хранения длины ключа
if(nKeyLength) //Если ключ введен
{
m_nTimer = (int)SetTimer(1,100,NULL); //Пускаем таймер с интервалом 100 милисекунд
CString tempstr; //Вспомогательная строка
for(m_nCount = 0; m_nCount < m_sBufStr.GetLength(); m_nCount++) //Пока не конец открытого текста
{
int buf_ascii = int(m_sBufStr[m_nCount]); //Шифруем или дешифруем по алгоритму
if(buf_ascii == 10 || buf_ascii == 13 || buf_ascii == 9) //Табуляции и переносы на новую строку не шифруем
{
tempstr += m_sBufStr[m_nCount];
continue;
}
int nTextChar = std::find(table,table+160,int(m_sBufStr[m_nCount])) - table;
int nKeyChar = std::find(table,table+160,int(m_sKeyStr[m_nCount % nKeyLength])) - table;
if(m_bCiphType) tempstr += char(table[(nTextChar + nKeyChar) % 161]);
else tempstr += char(table[(nTextChar + 161 - nKeyChar) % 161]);
if(::PeekMessageA(&message,NULL,0,0,PM_REMOVE)) //При этом на каждой иттерации
цикла обрабатываем сообщения
{
::TranslateMessage(&message);
::DispatchMessageA(&message);
}
}
if(m_bFinProc) //Если процесс полностью завершен
{
m_sBufStr = tempstr; //Записываем все полученные данные в буфер
KillTimer(m_nTimer); //Сбрасывем таймер
CDialog::OnOK(); //Закрываем диалог
}
else //Иначе
{
m_bFinProc = true; //Сбрасываем флаг завершения процедуры в начальное положение
m_nCount = 0; //Сбрасываем счетчик символов
CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Ининциализируем переменную
для управления индикатором хода процесса
pBar->SetPos(0); //Устанавливаем индикатор в начальное состояние
KillTimer(m_nTimer); //Сбрасываем таймер
}
}
else //Если ключ не введен
{
GetDlgItem(IDOK)->EnableWindow(TRUE); //Разблокируем кнопку Ok
AfxMessageBox("Введите ключ шифрования"); //Просим ввести ключ
}
}Обратим внимание на использовании в данном методе функций: PeekMessageA(&message,NULL,0,0,PM_REMOVE), TranslateMessage(&message) и DispatchMessageA(&message). Они позволяют обрабатывать сообщения на каждой иттерации цикла, создавая тем самым впечатление параллельности вычислений и реакции на действия пользователя. Функция SetTimer(1,100,NULL) запускает системный таймер, т.е. с интервалом, указанным во втором параметре данной функции, посылаются сообщения типа WM_TIMER.Первый аргумент функции - целое число, идентифицируещее таймер, третий - указатель на функцию, которая должна обрабатывать сообщение WM_TIMER. Если он равен NULL, то сообщение WM_TIMER посылается в общую очередь сообщений программы. Функция std::find(table,table+160,int(m_sBufStr[m_nCount])) - это алгоритм stl, который используется для поиска элементов в контейнерах (массивах). Добавим в класс CCipherDialog обработчик сообщения WM_TIMER, для того чтобы выводить информацию о ходе шифрования в индикатор состояния. Обработчик сообщения WM_TIMER:
void CCipherDialog::OnTimer(UINT_PTR nIDEvent)
{
CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH);
//Инициализируем переменную для управления индикатором процесса
if(m_sBufStr.GetLength()) //Если буфер не пуст
pBar->SetPos(m_nCount*100/m_sBufStr.GetLength()); //Устанавливаем позицию индикатора
процесса в соответствии с текущей позицией шифрования/дешифрования
CDialog::OnTimer(nIDEvent);
}Функция GetDlgItem(IDC_PROGRESS_CIPH) возвращает указатель на объект элемента управления диалога, идентификатор которого передается ей в качестве аргумента. Также необходимо переопределить функцию OnCancel() для данного диалога. Она прерывает процесс шифрования/дешифрования, если процесс начался, но не закрывает диалог. И закрывает диалог если процесс не запущен. Переопределенный метод OnCancel():
void CCipherDialog::OnCancel()
{
if(m_nCount) //Если идет процесс, то
{
GetDlgItem(IDOK)->EnableWindow(TRUE); //разблокируем кнопку Ok
m_nCount = m_sBufStr.GetLength(); //завершаем цикл шифрования, присваивая переменной счетчику конечное значение
m_bFinProc = false; //Указываем на то, что шифрование/дешифрование было прервано
}
else Cdialog::OnCancel(); //Иначе закрываем диалог
}Осталось обработать нажатие на кнопку Browse… Она должна открывать диалог для указания пути к файлу, в который будет выводиться результат шифрования/дешифрования. Обработчик кнопки Browse…
void CcipherDialog::OnBnClickedButtonBrowse()
{
Cstring strFilter; //Строка с поддерживаемыми форматами
Cstring strFileName; //Строка с путем и именем файла
Cstring strExtension; //Строка с расширением файла
UpdateData(true);//Обмен данными между элементами управления и переменными
strFilter = "Text file|*.txt||"; //Инициализация строки поддерживаемыми форматами
CfileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter);
//Создание диалога
if (dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
strFileName = dlg.m_ofn.lpstrFile; //Определяем путь и имя файла в который будем сохранять
if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввел расширение, то
{
switch (dlg.m_ofn.nFilterIndex) //В зависимости от того, какой из поддерживаемых форматов выбран
{
case 1: strExtension = "txt"; break; //Инициализируем строку с расширением
default: break;
}
strFileName = strFileName + '.' + strExtension; //Окончательно определяем строку
с именем путем и расширением
}
m_sFilePathStr = strFileName; //Записывае готовую строку с полным именем
в пременную член, связанную с текстовым полем
UpdateData(false); //Обновляем данные в элементах управления
в соответствии со связанными с ними переменными
}
}
