Архив статей

Все статьи из текущего раздела

Криптография в С++

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

Важные письма шифровались еще во времена Цезаря. Шифры были несложные, и в наши дни даже ребенок справился бы с ними за пару минут. Например, в Древнем Риме шифром служил алфавит со сдвигом на три буквы (D вместо A и т.д.). К счастью, криптоалгоритм Цезаря сейчас никем не используется :). Теперь создана куча других, очень и очень сложных шифров, и нам предстоит не только разобраться в принципах их работы, но и реализовать один алгоритм в собственной программе.

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

Представь себе криптоалгоритм, с помощью которого ты шифруешь что-либо одним ключом, а расшифровываешь уже совсем другим. Такой шифр называется асимметричным. Ключ, которым шифруют, называют открытым ключом (public key). Почему его так называют? Потому что он раздается всем. Но даже имея открытый ключ, ты все равно не сможешь расшифровать информацию за разумное время, не имея второго ключа. Открытый ключ можно спокойно выкладывать у себя на домашней страничке и просить посылать тебе письма, зашифрованные им. Второй же ключ называется закрытым (private key). Именно с его помощью ты сможешь получить информацию, зашифрованную первым ключом. Типичный пример асимметричного шифра - это RSA, используемый в PGP. Основной задачей таких шифров является защита передаваемой информации.

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

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

Пишем шифровщик

Наша программа будет реализовывать, наверное, один из самых простых, но в то же время очень популярных алгоритмов шифрования - XOR (на самом деле шифрованием это назвать трудно, но некоторые кодеры любят использовать этот метод, хотя в серьезных продуктах такое вряд ли когда-нибудь встретится - прим. ред.). Название его говорит само за себя, XOR - это логический оператор "поразрядное исключающее ИЛИ". Именно с его помощью мы будем шифровать каждый символ файла с ключом - другим символом.

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

0 на 0 = 0

0 на 1 = 1

1 на 0 = 1

1 на 1 = 0

В Си оператор xor выглядит как знак "^" (без кавычек). Чтобы проксорить два числа b и с (любого типа, двоичные разряды оператор получит сам), необходимо написать a = b ^ c. Соответственно, имея два символа, символ начальный и символ-ключ, мы можем получить зашифрованный символ и записать его в символ начальный. Вот пример: sim = sim ^ key. В C++ такая строка упрощается до sim ^= key. Это простое выражение уже шифрует один символ с другим. Нашей же программе понадобится зашифровать целый файл с ключом любой длины (т.е. не одним символом), заданным тобой.

Коротко о проекциях файлов

Для открытия файла мы воспользуемся API функцией CreateFile, в первом параметре которой будет содержаться полное имя открываемого файла, например: "C:\\password.txt". Заметь, когда пишешь строку прямо в программе, символ "\" нужно заменить символом "\\", иначе компилятор не поймет и подумает, что это какой-то спецсимвол (например, "\n" - символ перевода каретки). Функция CreateFile вернет нам хэндл открытого для чтения и записи файла hFile, после чего это значение мы передадим функции GetFileSize для того, чтобы узнать размер файла. И только теперь, открыв файл и зная его размер, мы создадим его ПРОЕКЦИЮ.

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

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

Коротко об указателях

Если ты когда-нибудь видел хотя бы один исходник на C++, то мог заметить значок "*", который ставят перед объявлением некоторых переменных разного типа. Этот значок показывает, что данная переменная является указателем, т.е. переменной, адресующей данные в определенное место памяти. Например, я пишу:

char *str = "строка";

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

Шифруем

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

1) Указатель на шифруемую строку

2) Длину этой строки

3) Указатель на ключ

4) Длину ключа

Мощь синтаксиса языка С++ позволяет записать ВСЮ функцию шифрования массива символов ксором с ключом заданной тобой длины в ОДНУ СТРОКУ, не считая объявления функции. Вот как это будет выглядеть:

for(int i = 0,t = 0; i < szin; in[i++] ^= key[t++]) if(t >= szkey) t = 0;

Цикл for, ты, конечно, узнал, а вот назначение таких хитрых выражений я сейчас объясню. Легче всего это сделать, приведя аналогию с более простым циклом while.

for(выражение 1; выражение 2; выражение 3) оператор;

это то же самое, что:

выражение 1;

while(выражение 2){

оператор;

выражение 3;

}

Так станет понятнее, что же делает приведенный цикл. По началу он объявляет переменные i и t, затем начинает цикл, который будет длиться до тех пор, пока i не станет больше или равна szin (передаваемая в параметрах функции длина шифруемой строки). Далее в цикле ксорится i-ый элемент массива с t-ым элементом ключа. Потом оба счетчика i и t повышаются на один. При этом если счетчик t станет вдруг больше или равен длине ключа szkey (тоже передается в параметрах), то t обнулится. Таким образом этот несложный цикл ухитряется шифровать, а при повторном вызове расшифровывать строку с ключом любой длины.

Наша программа - консольное Win32-приложение. И запускаться, соответственно, будет из командной строки. При запуске она берет первое значение аргумента (argv[1]) за параметр, содержащий путь к файлу, и пытается его открыть, а второй аргумент (argv[2]) - шифрующий ключ. Например, чтобы зашифровать файл C:\password.txt, необходимо запустить программу следующим образом:

crypto.exe C:\password.txt ключ


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

Реализация сложных криптоалгоритмов

С простыми криптоалгоритмами мы разобрались, а как быть со сложными? Ведь для их реализации требуется знание не только языка, но и высшей математики. Так, например, для генерации ключа для алгоритма RSA требуется найти простое число длиной около 512 бит (в зависимости от стойкости криптоалгоритма), а это не так легко сделать. На написание программы, реализующей сложный криптоалгоритм, ушло бы очень много времени, и в одной статье это не поместится. Но здесь нам на помощь приходят криптопровайдеры. Криптопровайдер (далее КП) - это чаще всего просто библиотека готовых функций шифрования, расшифровки, генерации ключей для существующих криптоалгоритмов. Если поискать в Сети, таких провайдеров можно найти очень много, но удобнее всего использовать КП от фирмы Mircosoft. Он уже встроен в Windows и готов к употреблению. Набор функций, использующих Windows КП, называется CryptoAPI, и с его помощью ты можешь зашифровать все что угодно и практически любым алгоритмом. Единственный недостаток криптоапи - поставляется он без исходников, а это может вызвать недоверие. Но на этот случай можно воспользоваться набором очень качественно реализованных алгоритмов проекта www.openssl.org. Вот точный урл на документацию к нему: http://www.openssl.org/docs/crypto/crypto.html

Но я предпочитаю CryptoAPI. C сайта www.xakep.ru ты можешь скачать исходник программы, использующий криптоапи. Там же скачивай исходник с бинарником сегодняшней программы шифрования файла. И с подробными комментариями!


Break

Если возникнут какие-то вопросы, связанные с алгоритмами шифрования или нашей программой, пиши. Обязательно отвечу. Подробную информацию по апи функциям, приведенным в статье, и, кстати, по криптоапи, ты сможешь найти в MSDN (msdn.microsoft.com) на сайте или на диске, прилагающемся к Visual Studio (это неправда, обычно в палатках VS всегда сильно урезан, а MSDN покупается отдельно на 3-4 дисках - прим. ред.). Но я хочу тебя предупредить: в нашей стране, согласно указу №334 от 3 апреля 1995 года, производить и распространять любые шифрующие средства можно, только имея лицензию ФАПСИ. Соответственно, шифровать нельзя :). Поэтому пиши программы только для личного пользования и только в познавательных целях.

На этом все. Удачного компилирования.


//Мы будем использовать WinAPI

#include

//Код шифрующей функции

void code(char *in,int szin,char *key,int szkey){

for(int i = 0,t = 0; i < szin; in[i++] ^= key[t++]) if(t >= szkey) t = 0;

}

//точка входа

int main(int argc, char **argv){

//Открываем файл для чтения и записи

HANDLE hFile = CreateFile(argv[1],GENERIC_WRITE|GENERIC_READ,0,0,3,0,0);

//Получаем его размер

DWORD dwFileSize = GetFileSize(hFile,0);

//Создаем проекцию файла

HANDLE hFileMap = CreateFileMapping(hFile,0,4,0,dwFileSize,0);

//Получаем указатель на проекцию

char* cFile = (char*)MapViewOfFile(hFileMap,2,0,0,0);

//Шифруем всю проекцию

code(cFile,dwFileSize,argv[2],strlen(argv[2]));

//После использования файл и проекцию требуется закрыть

//Ставим EOF, закрываем файл и его проекцию

UnmapViewOfFile(cFile);

SetFilePointer(hFile,dwFileSize,0,0);

SetEndOfFile(hFile);

CloseHandle(hFileMap),CloseHandle(hFile);

return 0;

}


Сколько стоит строительный песок купить природный строительный песок pesok777.ru.