Visual Basic, .NET, ASP, VBScript
 

   
 

Родился в г. Новосибирске в 1982 году. Окончил физико-математическую школу и решил продолжить учёбу в этом же направлении. Сейчас учусь в НГТУ на АВТФ. Программированием начал заниматься классе эдак в 3-тьем, 4-том (1992-1993). В те времена был популярен ZX Spectrum. Изначально осваивал Qbasic, потом перешёл на VB 3.0 и т.д. до современного VB.NET. Никогда не сожалел о том, что выбрал родным именно этот язык.
Любимые темы в программировании: криптография, теория чисел, мат. алгоритмы, логические игры и системные утилиты.
Прослушивал лекции по теории программирования в НГУ и успешно прошёл несколько тестирований на сертификаты BrainBench и Microsoft. Сайт: http://yxine.km.ru

 
     
   
 

Введение

 

Статью на тему защиты хотелось написать уже давно. Стремление это подкреплялось ещё и тем, что эта тема начинает набирать популярность (уже две статьи, затрагивающие данную тематику, представлены для конкурса). Действительно, в современном мире термин “безопасность” приобретает всё более весомую значимость. Но он идёт вкупе с такими вещами как криптография, значение которой столь же немаловажно. Пугает то, что многие люди даже не догадываются, что это означает. Как ни прискорбно, но среди них зачастую попадаются студенты технических факультетов крупнейших университетов. А ведь именно они – оплот компьютерного мира завтрашнего дня. А компьютерные технологии сегодня могут менять мировоззрения всего общества!

Ну, ладно я уже немного отвлекаюсь…

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

 

Общие замечания и определения

 

Криптограф (криптоаналитик) – человек, занимающийся изучением, разработкой, созданием и анализом криптографических методов; анализом стойкости ключей, шифров; взломом шифров.

Исходное сообщение (открытый текст, сообщение, послание и т.п.) – чистый текст, который несёт некий логический смысл. Это может быть последовательность байт, надписи, слова, звуки, файлы и т.п.

Взломщик – человек или машина, ведущая взлом шифра каким-либо методом (прямым перебором или анализом свойств). Имеется в виду, что ключ неизвестен. Либо некто, знающий ключ и ведущий взлом для выяснения надёжности и стойкости шифра к атаке. Атака на шифр – анализ шифра и всевозможные попытки получить исходное сообщение.

Шифр – зашифрованное сообщение.

SNBEM-версия ключа (или чего-то ещё) – это означает, что ключ (или что-то ещё) зашифрован методом SNBEM, и мы говорим о результате этого действия.

Ключ – просто пароль. Он участвует в шифровке/дешифровке. Обычно его знает лишь один человек и бережёт как зеницу ока. Знание ключа – 100% разрушение стойкости шифра.

Цикличный ключ – ключ, который при недостаточной длине образуется дописыванием себя к своему концу. 2 3 -> 2 3 2 3

Шифрование – получение шифра из сообщения каким-либо преобразованием. Обычно при участии ключа.

Дешифровка (расшифровка) – обратное действие шифрованию.

Простое число – целое число P, превосходящее единицу и имеющее только два делителя: 1 и P. 2, 3, 5, 7 – простые числа. 6 – число не простое (составное), так как имеет в делителях так же 2 и 3. 6=1*2*3. Простое число невозможно представить через подобное произведение. Существует огромное количество методов проверки простоты числа. О них подробней можно прочитать здесь. Я же предложу самый простой метод – решето Эратосфена (правда, чуть-чуть модифицированное):

 

Public Function IsPrime(ByVal P As Long) As Boolean

    Dim Max As Long, F As Long

    IsPrime = False

    If P < 2 Then Exit Function

    If ((P And 1) = 0) Then

       IsPrime = (P = 2)

       Exit Function

    End If

    If ((P Mod 3) = 0) Then

       IsPrime = (P = 3)

       Exit Function

    End If

    Max = CLng(Int(Sqr(P)))

    For F = 5 To Max Step 6

        If (((P Mod F) = 0) Or ((P Mod (F + 2)) = 0)) Then Exit Function

    Next

    IsPrime = True

End Function

 

Аддитивная разбросанность - это показатель рассеивания (т.е. неопределённости ~ энтропии Шеннона) зашифрованного сообщения. Мультипликативная разбросанность аналогична, но имеет большую степень рассеивания. И вправду, сложение намного меньше увеличивает число, нежели умножение. Покажем суть на примере:

Сообщение: 0 2 2 1 1 2 2 3 1 2 3

Ключ: 6 3 7 2 3

При аддитивной разбросанности (складываем элемент сообщения с элементом ключа. По необходимости ключ циклично повторяем): 6 5 9 3 4 8 5 10 3 5 9

При мультипликативной (умножаем): 0 6 14 2 3 12 6 21 2 6 18

Получаем, что для аддитивной разбросанности диапазон равен [3..10] (в идеале - [0..10]), а для мультипликативной: [0..21]. Эти диапазоны показывают, в каких рамках нужно искать символы при взломе шифра. Согласитесь, что при мультипликативности существует в 2,1 раза больше вариантов! Следовательно, и искать в такую же степень раз сложней. Сложная разбросанность – это комбинации аддитивной и мультипликативной разбросанностей.

723 – магическое число криптографии. Есть математические доказательства, что для множества систем, которые могут оперировать лишь с целыми ключами из диапазона [0..1000], ключ 723 по усреднённой статистике будет проверяться самым последним.

Двусторонний метод шифрования (с обратным ходом) – метод, который содержит в себе процедуры для шифрования и расшифровки. Т.е. если x – исходное сообщение, а Fkey(x) – зашифрованное, то двусторонний метод содержит ещё функцию Gkey(), которая обладает свойством: Gkey(Fkey(x))=x для любого значения x. Функция Fkey(x) шифрует сообщение x ключом key, а Gkey(x) соответственно расшифровывает.

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

 

Ситуация 1

 

Ситуация описывает банковскую систему, которая не использует одностороннего шифрования данных клиента:

 

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

Процесс получения денег клиентом выглядит следующим образом: клиент сообщает служащему свой личный пароль. Служащий просто сверяет его с паролем в базе данных и лишь в случае совпадения выдаёт клиенту деньги. А теперь обратная сторона процесса: пусть служащий банка не чист на руку и решил присвоить деньги клиента. Ничто не мешает ему взять из базы данных требуемый пароль и произвести операции со счётом клиента. Когда клиент начнёт вопить, что он не снимал деньги, кто ему поверит? Никто. Служащему достаточно сказать, что клиент приходил, называл правильный пароль (вот он у меня на листике записан!) и полностью обналичил счёт.

 

Ситуация 2

 

Ситуация описывает банковскую систему, которая использует односторонний метод шифрования данных клиента:

 

            Всё выглядит также, с одним лишь отличием: пароль клиента хранится в базе данных в зашифрованном виде (односторонним методом!).

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

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

 

            Могу предположить, что кто-то из читателей вскрикнет – надул ты нас, батенька! Во втором случае служащий может украсть деньги! Ведь именно пароль из базы данных нужен для доступа к деньгам. Начхать нам на реальный пароль клиента! И будет прав такой умный читатель! В банковской системе реализация описанного процесса намного сложней и в ней участвуют ещё такие персонажи, например, как снимок времени, ЭВМ и т.д. Пример просто показывает суть системы.

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

            В банке служащий тоже может быть человеком или ЭВМ. Таким образом, сохранность наших секретов ложится на плечи сумрачного НЕКТО, который скрыт от нас пеленой тумана, но на чью порядочность, стабильность и защиту мы искренне надеемся, молимся и верим. Именно поэтому надо параллельно разработке защиты разрабатывать ещё защиту для защиты.

 

            Я попытался объяснить этот сложный вопрос как можно проще, но думаю, что у Вас всё равно есть некоторые сомнения, возможно. Критика принимается! Очень приятно вести дискуссию с людьми, интересующимися криптографией. А вот Вам ещё одна мысль: если юзер забыл пароль к своему e-мылу, он может получить его через систему напоминания пароля, указав некоторые данные (девичью фамилию матери, иною свою мыльницу и т.п.). Так, что же получается? Пароли хранятся в открытом виде (ну, по крайней мере, не зашифрованы односторонне)? Остаётся лишь гадать, что будет, если базу стянут…

 

SNBEM

 

SNBEM – Simple Numbers Based Encryption Method (метод шифрования основанный на простых числах). Я написал этот метод на ASP, когда создавал сайт для Инвестиционного Клуба, который требовал нетривиальной защиты паролей для клиентов. Система аутентификации должна работать не с реальными паролями, а с их шифрами. И в случае утери пароля, восстановить его будет невозможно – нужно создавать другой пароль.

Требуемая защита должна была соответствовать двум требованиям:

1.       Метод должен быть односторонним

2.       Быть стойким к взлому

И вот что вышло:

 

1      Function SNBEM(Text)

2            Dim I,J,K,L,P,R,T

3            T=Text

4            For P=1 To 2

5                   R=""

6                   L=Len(Text)

7                   For I=1 To L

8                          J=(L*I+723) Mod (L+I)

9                          For K=I To Len(Text)

10                                J=((J*Asc(Mid(Text,K,1))) Mod 256)

11                                Do While Not isPrime(J)

12                                      J=J+1

13                                Loop

14                         Next

15                         J=J Mod 256

16                         R=Chr(J) & R

17                  Next

18                  Text=R

19           Next

20           Text=T

21            SNBEM=R

22     End function

 

Комментарии к коду: шифруем сообщение Text (1). Оно передаётся в переменную T (3). Сообщение шифруется 2 раза – слева направо и справа налево. Это даст большую устойчивость к взлому (4 строка). В R будут хранится уже зашифрованные символы сообщения (5). Делаем сложную разбросанность, привлекая магическое число криптографии 723 (8 строка). В девятой строке мы усиливаем зависимость текущего шифруемого символа от последующих в сообщении. Применяем мультипликативную разбросанность (10). В цикле DoLoop как раз создаётся односторонность метода (11-14)! Выравниваем значение текущего шифруемого символа (15) и дописываем к началу выходного сообщения (16). Теперь отправляемся на второй круг (19). Всё (20-22)!

            Роман Ройтер в своей статье говорил о том, что в программе лучше не хранить пароль. Я согласен. Но можно хранить его SNBEM-версию! Т.е. вместо кода:

 

Key=inputbox(“Ваш пароль:”)

If key=”qwerty” then msgbox “Cool!” else msgbox “Bad key”

 

пишем

 

Key=snbem(inputbox(“Ваш пароль:”))

If key=”£Gé[1]/” then msgbox “Cool!” else msgbox “Bad key”

 

SNBEM превратит qwerty в £Gé[1]/. Это и пишем в проверке (лучше проверять не целиком, а побайтно). Реального пароля нет! Остаётся опасаться только перехвата проверки. Но об этом позже.

 

Одноразовый блокнот

 

Данный метод в совершенстве использует алчное стремление многих людей знать всё.

Он действительно даёт человеку такую возможность!

 

         По заявлению Брюса Шнейдера (всемирно известного криптолога и гуру), этот метод является идеальным (т.е. 100% стойким к взлому) и я разделяю его точку зрения. Каких бы сложностей ни воротили крупнейшие компании мира в разработке новых сверхстойких алгоритмов шифрования (MARS IBM’а чего только стоит), одноразовый блокнот умиляет своей простотой (всё гениальное – просто!) и лёгкостью исполнения. Обычного человека этот метод только рассмешит своим наполеонизмом (куда ж такой крохе тягаться лезть к DES!!! Слон и Моська одним словом) и банальной схожестью с шифрами простой замены. А вот криптоаналитик с мировым именем проснётся ночью в холодном поту, не приведи господи ему увидеть очертания этого бронированного чудовища во сне!

            Так чёго же побоится криптоаналитик, если случится ему расшифровывать шифр полученный одноразовым блокнотом? Простой пример. Возьмём лишь только могучий наш русский язык. Пусть взломщик перехватил 5-ти символьное зашифрованное сообщение и знает, что оно зашифровано одноразовым блокнотом и что это одно из слов русского языка. Начнём с того, с какой вероятностью он узнает истинное сообщение: я не знаю, сколько в русском языке 5-ти символьных слов, поэтому будем ограничиваться потолком. Всего в русском существует 39135393 различных 5-ти символьных сообщений (33^5). Значит, вероятность найти единственно верное – не меньше 1/39135393. Не так уж и плохо, скажет взломщик! А теперь раскроем один секрет нашему криптоаналитику: перехваченный им шифр со 100% вероятностью может быть любым из 39135393 русских пятибуквенных сообщений! Ну-ка, выбери правильное! Любое из пятибуквенных слов русского языка может быть получено в результате взлома!!! Народ ликует… занавес… Только знание правильного ключа даёт правильное сообщение. Так в чём же секрет метода. Да нет секрета!!! Вот он метод (приведено шифрование, для дешифровки нужно чуть-чуть подправить 7-ую строку):

 

Private Function OnceTimeNotepad(byval Text as string,byval Password as string) as string

Dim Res as string, I as long

Do while len(password)<len(text)

Password=password & password            создаём цикличный ключ

Loop

For i=1 to len(text)

Res=Res & chr(clng(asc(mid$(text,I,1)))+clng(asc(mid$(password,I,1))) mod 256)

next

oncetimenotepad=res

End function

 

10 строк ужаса, набранных мною по памяти на клавиатуре за 1 минуту 30 секунд! Теперь остаётся лишь совместить SNBEM и одноразовый блокнот, и криптоаналитики могут спокойно отправляться на пенсию!

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

 

Генерация ключей и случайных последовательностей

 

            Ясно, что ключ оказывает большое влияние на стойкость системы шифрования. Чем сложнее будет ключ, тем труднее взломщику подобрать его. Существуют огромные чёрные списки ключей, которые не рекомендуется использовать. Они дают понять, что в качестве ключа лучше не использовать последовательности символов, которые несут какой-либо логический смысл. Вот, например, плохой вариант ключа: alexsandrthatlivesinnovosibirsk. Хороший ключ: StU5_Nist67. Хотя он намного короче  первого ключа, подобрать его для взломщика намного сложней.

            Как получать стойкие ключи? Хороший вариант – генерировать его случайным образом. Только надо обратить внимание на эту самую случайность. Код

 

Randomize timer

Key=int(rnd(1)*123456789)

 

не даёт случайного номера! Есть зависимость от timer’а, а любая зависимость в случайной последовательности – очень плохая вещь. Выход один: использовать специальные алгоритмы генерации случайных последовательностей (о них великолепно написано в книге ‘Криптография от папируса до компьютера’), либо использовать общеизвестные случайные величины (некоторые их части). Рассмотрим второй случай:

            К общеизвестным случайным величинам относятся такие константы, как “Пи”=3.14… и “e”=2.58… У числа ∏ известны более 2 миллиардов знаков после запятой и учёные не обнаружили среди них никаких закономерностей. Поэтому можно использовать любую часть этого числа в качестве случайной последовательности. Возьмём 125 цифр начиная с миллионного разряда после запятой в записи числа ∏ и мы получим случайное 1000 битное число. Запись числа ∏ (её можно найти на математических сайтах) влезет на один компакт диск. Достаточно создать функцию для получения последовательности нужной длины:

 

Public Function GetRandomFromPi(ByVal Start As Long, ByVal Length As Long) As String

    Dim FF As Integer, Ret As String

    Ret = String$(Length, " ")

    FF = FreeFile

    Open "d:\pi.dat" For Binary As #FF

         Get #FF, Start, Ret

    Close #1

    GetRandomFromPi = Ret

End Function

 

Секреты внутренностей Вашей программы

 

         Поставим перед собой цель написать программу, которая будет работать лишь при правильно введённом пароле при запуске. Написать защитный механизм можно различными способами, но всегда будет возникать ряд проблем с реализацией “защиты” нашей защиты. Итак, всё по порядку: создадим самую простую программу на VB 6.0:

 

Option Explicit

Sub Main()

End Sub

 

Она состоит из одного модуля, все формы удалены. Откомпилируем в c:\vb\crypt.exe. В любом HEX-редакторе просмотрим содержимое полученного файла (я использую HEX Workshop 3.01, но подойдёт простой DN или FAR – дело вкуса):

 

         Из статистики видно, что 3516 байт из 16384’x являются пустыми. Их изменение не повлияет на работоспособность всего exe-файла. Т.е. диапазон байт [582..4090] может использоваться для наших нужд (но необходимо следить за этим диапазоном, так как его границы могут варьироваться!). Сюда можно записать ключ для шифрования, расшифровки, а лучше его SNBEM-версию! Итого мы получим 3516 байт * 8=28128 битовый ключ! Таким образом, он не будет явно присутствовать в коде и, следовательно, не будет занимать дополнительное место. Доступ к шифрованному ключу можно получить, побайтно считав его из exe-файла:

 

Dim FF As Integer, Key As String

Key = String$(5, " ")                   ‘ 5 – это длина пароля

FF = FreeFile

Open "c:\vb\crypt.exe" For Binary As #FF

Get #FF, 582, Key

Close #FF

 

         Выберем для нашей программы пароль. Пусть будет Yxine, хотя лучше брать что-то длинней. Об этом я уже писал. Вычисляем SNBEM для Yxine: побайтно это выглядит так: 41 157 79 101 149. Переводим в HEX: 29 9D 4F 65 95 и записываем в exe-файл, начиная с 582 байта (для этого дела лучше написать небольшую программку, так как после каждого компилирования ключ будет пропадать):

 

 

         Всё, ключ вшит, хотя реального пароля в программе нет! Считывать его мы уже научились. Остаётся самое слабое звено: проверка пароля.

            Хакер может изучить, как работает алгоритм шифрования и вся наша защита. Он может спокойно вычислить место, где мы храним наш шифрованный ключ. Пожалуйста! Хакер может вырвать кусок защиты (функции isPrime и SNBEM). Но так как SNEM не имеет способа к дешифровке, то эта лазейка для поиска ключа даст взломщику лишь возможность для тупого перебора все вариантов. Т.е. теперь хакер знает SNBEM нашего пароля и знает, как шифруется открытый ключ. Длина нашего пароля 5 символов. Получаем, что хакеру нужно перебрать 256^5 ключей (т.е. 1.099.511.627.776 вариантов). А если бы мы использовали все 3516 байт для ключа: 256^3516 вариантов!!! Перебор явно отпадает. Хакеру остаётся либо найти точку сравнения паролей и поменять условный переход на безусловный (очень простой вариант), либо полностью ликвидировать защитный алгоритм из кода программы (очень сложный вариант даже для бывалого хакера). Не будем рассматривать второй путь, ограничимся только первым. Дописываем в Main строку:

 

UserKey = InputBox("Введите пароль:")

 

SNBEM от UserKey пока не берём. Сверять Key и UserKey напрямую или какими-то модификациями строк не советую – это очень легко перехватить. Хорошим способом сверки паролей в данном случае является закрытый код. Т.е. алгоритм проверки правильности пароля должен быть зашифрован (OneTimeNotepad c нашим ключом в качестве пароля – отличный вариант). Шифр кода проверки можно записать в exe-файл после нашего SNBEM ключа, т.е. начиная с 587-ого байта.

Проверка правильности ключей должна быть вшита в этот алгоритм. Пример реализации интерпретации можно найти в исходниках RIPL. Сам алгоритм может выглядеть, например, так (я взял язык RIPL, хотя можете придумать что-нибудь своё J):

 

Program Check

Memory

String Key1,Key2,Bool

End Memory

Proc Main

Copy Key1,DirectDim(Key)

Copy Key2,DirectDim(UserKey)

Copy Bool,key1=Key2

If bool->endp

DirectCall Done

Endp:

End Proc

End Program

 

         Функция DirectDIM() вытаскивает значение переменной из VB-программы и передаёт его в RIPL-программу. DirectCall вызывает выполнение VB-процедуры в случае правильности пароля.

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

            Доведём дело до конца:

            Шифруем данный кусок программы одноразовым блокнотом, представляем всё это дело в байтах и переводим в 16-ричную систему счисления. Записываем всё это в наш exe-файл начиная с 587-го байта. Фрагмент теперь выглядит так:

 

 

         Защита готова! В программе нет нашего реального пароля (Yxine), есть только его SNBEM-версия, плюс при этом сам алгоритм проверки подлинности пароля зашифрован с помощью этого же пароля! Защита защиты в чистом виде! Полный код программы выглядит так:

 

Option explicit

Sub Main

Dim FF As Integer, Key As String, UserKey as string, Code as string

Key = String$(5, " ")

FF = FreeFile

Open "c:\vb\crypt.exe" For Binary As #FF

Get #FF, 582, Key

Close #FF

Userkey=inputbox(“Введите пароль:”)

Code=String$(200,” “)                   длина зашифрованного кода

FF = FreeFile

Open "c:\vb\crypt.exe" For Binary As #FF

Get #FF, 587, Code

Close #FF

‘ теперь осталось только интерпретировать программу, записанную в переменной Code. См. RIPL

End Sub

Sub Done()

       Msgbox “Пароль введён правильно!”

End Sub

 

Всего доброго!

yxine@mail.ru

http://www.yxine.km.ru

 
     

   
   
     
  VBNet рекомендует