Криптография и взлом шифров на Python [Эл Свейгарт] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

КРИПТОrРА»

1

D•bug

Opt1ons

W1ndcw

( v3 . 6 . 2 : 5 fd3Зb5 ,

" cop ;·r :.qht: " ,

" c ::-ed:. t з " о ::

.:•.:1

H•lp
S

2 0 :.. 7 , 0 4 : 1. 4 : 3 � ) (MSC

" : 1c�::i!le

v . : 9 � 'J 3 2

b.lt.

х

::'l�e l ) }

( ) " f o r rr.o r e 1 � f o rП'.a t. 1 0 � .

ln: 3 Col: 4

Ри с. J .

Окно IDLE

Например, перейдите в окно интерактивной оболочки и введите сразу
вслед за приглашением > > > следующую команду:
> > > print ( ' Bello , world ! ' )

Нажмите клавишу , и интерактивная оболочка немедленно от­
реагирует на это, выдав следующий текст:
He l l o , wo r l d !

Резюме
В былые времена взламывать шифры приходилось с помощью каран­
даша и бумаги. И хотя с появлением компьютеров многие классические
шифры стали уязвимыми для атак, их изучение по-прежнему представля­
ет интерес. Кроме того, написание программ криптоанализа, способных
взламывать подобные шифры, - отличный способ научиться программи­
ровать.
В главе 1 вы познакомитесь с простейшими криптографическими ин­
струментами, позволяющими шифровать и дешифровать сообщения, не
прибегая к помощи компьютеров.

Введе н ие

29

Ждем ваwих отзывов!
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше
мнение и хотим знать, что было сделано нами правильно, что можно было
сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам инте­
ресны любые ваши замечания в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать
нам электронное письмо либо просто посетить наш сайт и оставить свои
замечания там. Одним словом , любым удобным для вас способом дайте нам
знать, нравится ли вам эта книга, а также выскажите свое мнение о том , как
сделать наши книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги
и ее авторов, а также свой обратный адрес. Мы внимательно ознакомимся
с вашим мнением и обязательно учтем его при отборе и подготовке к изда­
нию новых книг.
Наши электронные адреса:

30

E-ma i l :

i n f o @ d i a l e k t i ka . c om

WWW:

h t t p : / / www . d i a l e k t i ka . c om

Введе н и е

И Н СТР У М Е НТЫ " Б УМАЖ НО Й"
КР И ПТОГР АФИ И
"

Джин ши фр ования В'ьtnущеи из бу т ъ�.л:ки ",
Яи Кум,

оrиователъ

WhatsApp

П режде чем прис туп ать к нап исанию шиф­
ровальных программ , дав айте рассмотрим,
как реализуется п роцесс шифро в ания и де­
шифрования с по м о щью карандаша и бумаги.
Это позволит вам луч ше п онять п рироду ш ифров и тех математичес ких и н с трументо в , ко торые
применяются для создания секретн ых соо б щени й . В данно й
главе вы узнаете о том, что такое криптография и чем коды от­
личаются от шифров. Затем мы воспользуемся простым шифром,
известным как шифр Цезаря, для шифрования и расшифровки бу­
мажных сообщений.
В это И







Что такое криптография
К оды и шифры
Ш ифр Цеза ря
Ш ифровал ь ные д и ски
К рипто графия и арифмет и ка
Двойное шифрован и е

главе • • •

Что такое криnтоrрафи•
С незапамятных времен все, кому требовалось обмениваться тайными
сведениями, например шпионы, военные, пираты , торговцы и диплома­
ты, прибегали к засекречиванию своих сообщений, чтобы тайны остава­
лись надежно скрыты от посторонних глаз. Криптография - это наука, ко­
торая изучает способы создания и применения секретных кодов. Чтобы
понять, как работают криптографические методы, рассмотрим следующие
два фрагмента текста.
nyr N .v Nwz5 uNz5 Ns662 0 N zO N З z2v

I N N 2 N uwv, N 9,vN N lvNrB N З zyN4vN

N yvNwz9vNz5 N 6 1 9 Nyvr9

N 6 GvvOz6nvN . 7NOyv4 N 4 zzvN N

yOQ N nvNwv tyNz

vyN, N N99zOzz6wzOy3vv2 6 9

Nw964 N 6 ! 9 N 5vzxys690, N .v N 2 z5 u­

w296vy N N rrNyGst.560N94Nu5y

ЗvNz N r Ny64v, N .vNt64415ztr vNz

rN5 nz5vv5t6v6 3zNr5.

N 6 N 6 yv90, N r5 u N z N svt64vO N

N 75sz6966N Nvw6 zuO wtNxs6t

yvN 7967v9 B N 6wN r3 3 Q N-m63 rz9v

4 9 N rN З Ny9 Nvzyl

Текст слева - секретное сообщение, которое было зашифровано, т.е. пре­
образовано в секретный код. Любому человеку, не знающему способа его
дешифрования, или расшифрод'Ки, оно кажется полной абракадаброй. Сооб­
щение справа - случайный набор символов, не имеющий никакого скры­
того смысла. Шифрование позволяет сохранить смысл сообщения в тайне
от тех, кому не известен способ его расшифровки, даже если сообщение
попадет им в руки. Шифрованное сообщение воспринимается посторонними как
случайнъtй набор букв, не несущий в себе никакого смысла.
Криптограф, или шифровалъщик, использует и изучает секретные коды.
Разумеется, секретные сообщения не всегда остаются секретными. Крипто­
аналитик, т.е. взломщик кодов, или хакер, способен взламывать и читать сооб­
щения , зашифрованные другими людьми. Цель книги - научить вас зашиф­
ровывать и расшифровывать сообщения с помощью различных методов. К
счастью, все те методы взлома, которые вы изучите, не настолько опасны,
чтобы у вас из-за них могли возникнуть проблемы с законом.

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

32

Гл ава 1

В начале XIX века развитие электрического телеграфа привело к созда­
нию известного кода, обеспечивавшего почти мгновенный обмен сообще­
ниями между континентами по проводам. Сообщения, отправляемые по
телеграфу, достигали своих адресатов гораздо быстрее, чем прежняя лоша­
диная почта, перевозившая мешки с письмами. Однако телеграф не позво­
лял отправлять сообщения в том же виде, в каком они были написаны на
бумаге, т.е. в виде последовательностей букв. По нему могли пересылать­
ся только два типа электрических импульсов: короткий, который назвали
"точка" , и длинный, который назвали "тире".
Для преобразования букв алфавита в электрические импульсы необхо­
димо располагать системой кодов, с помощью которой можно было бы пе­
реводить привычные для нас буквы на язык точек и тире. Процесс преобра­
зования букв алфавита в последовательности точек и тире для отправки по
телеграфу называется кодированием, а обратный процесс преобразования
электрических импульсов в буквы при получении сообщения декодирова­
нием. Способ, применяемый для кодирования и декодирования телеграф­
ных сообщений (а впоследствии и сообщений, передаваемых по радио) ,
был изобретен Сэмюэлем Морзе и Альфредом Вейлем и получил название
код Морзе, или азбука Морзе (табл. 1.1) .
-

Таблица 1 . 1 . Международная азбука Морзе

Л атинский

Код

СИМВОЛ

А
в
с
D
Е
F
G
н

·- · · ·
-·-·
-··

· ·-·
--·
• • • •
• •
· ---

к
L
м

-··-· ·

Л атинский
символ
N
о
о

Q

R
s

Код


v
w
х
у
z

1
2

·--·
--··-·
• • •

3
4
5
6
7

т
u

Цифра

· · · · · ·-- · · -

8
9
о

Код
· ---· · --· · ·-· · · ·• • • • •
- · · · ·
-- · · ·
--- · ·
---- ·
-----

- · ---· ·

И нструменты " бум аж ной " к ри птогр афии

33

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

Ш ифр Цеза ря
Первый из шифров, который мы изучим, - шифр Цезаря , названный
так в честь Юлия Цезаря , который пользовался им 2000 лет тому назад.
Хорошая новость состоит в том, что он прост и несложен для изучения. Но
есть и плохая новость: в силу простоты этого шифра криптоаналитику не
составит большого труда взломать его. Тем не менее ознакомление с ним
даст вам полезный опыт.
Шифр Цезаря основан на замене одной буквы другой после предва­
рительного смещения всего алфавита на определенное число позиций.
Юлий Цезарь заменял буквы в своих сообщениях путем смещения алфави­
та на три позиции и последующей замены каждой буквы соответствующей
буквой из смещенного алфавита.
Например, вместо каждой буквы 'N он подставлял букву D вместо ка­
ждой буквы 'В' - букву 'Е' и т.д. Если Цезарю нужно было сдвинуть букву,
находящуюся в конце алфавита, скажем , 'У' , то он возвращался в нача­
ло алфавита, смещаясь в целом на три позиции и подставляя букву ' В ' .
В этом разделе м ы будем шифровать сообщения вручную , применяя
шифр Цезаря .
'

1

Более подробную информацию об азбуке Морзе можно найти

h t t p s : / / ru . w i k i p e d i a . o r g / w i k i /Aзбyкa_Mop з e .

34

Гл ава 1

в

Википедии :

' ,

Чтобы упростить преобразование простого текста в зашифрованный с
помощью шифра Цезаря, мы будем использовать шифровалъный диск. Он со­
стоит из двух колец, каждое из которых разбито на 26 ячеек ( по числу букв
английского алфавита) . Внешнее кольцо представляет алфавит исходного
текста, а внутреннее - соответствующие буквы зашифрованного текста.
Буквы на внутреннем кольце пронумерованы числами от О до 25. Эти числа
определяют ключ шифрования , в данном случае - количество позиций , на
которое нужно перейти от буквы '/\ к соответствующей букве на внутрен­
нем кольце. Поскольку сдвиг выполняется по кругу, смещение с ключом,
значение которого превышает 25, продолжается с начала алфавита, соот­
ветственно, смещение на 26 позиций равносильно отсутствию сдвига, сме­
щение на 27 позиций - сдвигу на 1 позицию и т.д.
Виртуальный шифровальный диск доступен по адресу h t t p s : / /
i nventwithpython . corn/ ciphe rwhee l / (рис. 1.1) . Чтобы провернуть диск,
щелкните на нем один раз и перемещайте указатель мыши по кругу, пока не
будет достигнута нужная конфигурация. Повторный щелчок останавлива­
ет дальнейшее вращение диска.

S
А
о

Т
В
1

U
С
2

V
О
3

W
Е
4

Х
F
5

У
G
б

Z

7

8
I
8

Рис. J . J .

Cl1ck "-h�e! to rotate.
В С D Е F G Н I
J
К L М N О Р Q R
J
К L v \ О Р Q R 5 Т U V W Х У Z
9 10 11 12 1 3 14 1 5 16 1� 13 19 20 2 1 22 2 3 24 2 5

Онлайнов ый ш ифровальный диск

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

35

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

Шифрование '°о6щенп с помощью шнфровалыоrо диска
В качестве примера зашифруем сообщение "ТНЕ SECRET PASSWORD
IS ROSEB UD" с помощью онлайнового шифровального диска. Проверните
диск так, чтобы деления внутреннего и внешнего кругов совпадали. Обра­
тите внимание на изображение точки под буквой 'N. . Число на внутреннем
круге под этой точкой и есть ключ шифрования, который будет приме­
няться при данной конфигурации диска.
На рис. 1 . 1 таким числом является 8. Мы используем его в качестве клю­
ча для того, чтобы зашифровать сообщение (рис. 1 .2 ) .
т

н

Е

s

Е

с

R

Е

Т

р

А

s

s

w о

R

D

!
в

р

м

Рис. J .2.

А м к z м в

I

s

R о s

в

u

D

z w А м J

с

L

Е

1

х

I

А А Е

w z

L

Q А

Шифрование сообщения с помощью ш ифра Цезаря по ключу В

Найдите каждую букву сообщения на внешнем круге и замените ее со­
ответствующей буквой из внутреннего круга. В данном примере сообще­
ние начинается с буквы 'Т' (первая буква 'Т' во фразе "ТНЕ SECRET. " " ) ,
поэтому находим букву 'Т' н а внешнем круге , а затем соответствующую
ей букву на внутреннем круге , которой является буква ' В ' . Таким обра­
зом , в исходном сообщении буква 'Т' всегда будет заменяться буквой ' В ' .
( Если б ы в ы использовали другой ключ шифрования, т о каждая буква 'Т'
заменялась бы другой буквой . ) Следующая буква сообщения - ' Н ' , кото­
рая превращается в букву ' Р ' . Далее буква 'Е' превращается в букву ' М ' .
Каждая буква внешнего круга всегда шифруется одной и той ж е буквой
внутреннего круга. Как только вы найдете первую букву 'Т' в сообщении
"ТНЕ SECRET" . " и увидите, что она шифруется буквой ' В ' , можете заме­
нить буквой ' В ' все буквы 'Т' в сообщении с целью экономии времени ,
чтобы каждую букву приходилось искать н а шифровальном диске всего
лишь раз.
Завершив процесс шифрования всего исходного сообщения "ТНЕ
SECRET PASSWORD IS ROSEBUD", вы получите результирующее зашиф­
рованное сообщение "ВРМ AMKZMB XIAAEWZL QA ZWAМJCL'. Обратите
внимание на то, что небуквенные символы, в данном случае пробелы , оста­
ются неизменными.
36

Глава 1

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

Дешифрование t006щени1 ' помощью шифровальноrо дж1а
Чтобы дешифровать зашифрованный текст, начните с внутреннего круга
шифровального диска. Предположим , вы получили зашифрованный текст
"IWГ CTL EPHHLDGS ХН HLDGSUXHW". Вы
не сможете расшифровать его, пока не узнае­
те ключ (если только вы не криптоаналитик) .
К счастью, отправитель уже сообщил вам , что
в своих сообщениях он использует ключ 1 5 .
Конфигурация шифровального диска для это­
го ключа приведена на рис. 1 .3.
Установите букву 'N. на внешнем круге (по­
мечена точкой) напротив буквы на внутреннем
круге с номером 1 5 (буква 'Р' ) . Затем найдите
первую букву секретного сообщения на вну- Рис. J .3. Конфигурация ш иф­
треннем круге (буква 'Г ) и запишите букву, ко- ровальноrо диска для ключа 1 5
торая соответствует ей на внешнем круге (буква 'Т' ) . Вторая буква секретного сообщения , 'W' , дешифруется в букву ' Н ' .
Расшифровав все буквы зашифрованного текста, вы получите сообщение в
виде простого текста: "ТНЕ NEW PASSWORD IS SWORDFISH" (рис. 1 .4 ) .
I w т

с т

т н

N

Е

Рис. J .4.

L

Е w

Е

р н

р А S

н

L

D G

s w о

s

R D

х н

н

I

s w о R D

s

L D G s u х н w
F

I

s н

Де шифрован ие сообщения с помощью ш ифра Цезаря по ключу 1 5

Если использовать некорректный ключ, например 1 6 , то расшифрован­
ное сообщение превратится в бессмысленный набор символов "SGD MDV
OZRRVNQC HR".

Шифрование и дешифрование средсrвами арифмеrпи
Шифровальный диск - удобный инструмент для применения шифра
Цезаря, но аналогичные операции можно выполнять и в арифметическом

И нструменты " бумажной " криптогр афии

37

виде. Запишите буквы алфавита от 'N. до 'Z' и пронумеруйте их числами от
О до 25. Начните с нуля под 'N. , единицей под 'В' и т.д., пока не поставите
номер 25 под 'Z' (рис. 1 .5 ) .
А
о

В
1

С
2

D
3

Е

4

F

5

G
6

Н

7

Рис. J .5.

I

8

J

К

L

М

N

О

Р

Q

R

S

Т

U

V

W

Х

У

Z

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2 5

Нумерация ол ф овнто

чнсломн о т О д о 2 5

Полученную таблицу перекодировки букв в цифры можно использовать
для представления букв, что позволяет выполнять над буквами арифмети­
ческие операции. Например, если вы запишете слово "САТ'' цифрами 2, О и
1 9 , то сможете прибавить к каждой из них 3, получив в результате последо­
вательность 5, 3 и 22. Эти новые числа представляют буквы "FDW". Позже
мы сможем написать компьютерную программу, которая все сделает за нас.
Чтобы использовать арифметику для шифрования с помощью шифра
Цезаря , найдите число под буквой, которую хотите зашифровать, и при­
бавьте к ней номер ключа. Результирующая сумма и есть число, находяще­
еся под зашифрованной буквой. Зашифруем , например, текст "HELLO.
HOW ARE YOU?" с помощью ключа 13. ( В качестве ключа можно исполь­
зовать любое другое число от 1 до 25.) Прежде всего найдите число под
буквой ' Н ' , т.е. 7. Затем прибавьте 1 3 к этому числу: 7 + 1 3 = 20. Поскольку
число 20 находится под буквой 'U', то буква 'Н' превращается в букву ' U ' .
Точно так же, чтобы зашифровать букву 'Е' (4) , выполните сложение
4 + 13 = 1 7. Буква над 17 - 'R' , поэтому 'Е' преобразуется в 'R' и т.д.
Этот процесс отлично работает до тех пор, пока мы не достигнем бук­
вы ' О ' . Число под 'О' - 1 4. Но 1 4 + 1 3 = 27, а список чисел доходит лишь
до значения 25. Если сумма чисел буквы и ключа равна или превышает 26,
придется вычесть из нее 26. В данном случае 27 26 = 1. Буква над цифрой
1 - ' В ' , поэтому, если используется ключ 1 3 , буква 'О' превращается в 'В'.
Зашифровав подобным образом каждую букву сообщения, получим зашиф­
рованный текст "URYYВ. UBJ NER LBH?".
Чтобы расшифровать зашифрованный текст, следует вместо прибавле­
ния ключа к каждой букве использовать вычитание. Букве 'В' в зашифро­
ванном тексте соответствует число 1 . Результатом вычитания 1 3 из 1 будет
отрицательное число -1 2. Аналогично нашему правилу "вычитания 26",
если результат меньше нуля при дешифровании, то к нему нужно приба­
вить 26. Поскольку - 1 2 + 26 = 14, буква 'В' в зашифрованном тексте дешиф­
руется в букву ' О ' .
Как видите, чтобы воспользоваться шифром Цезаря, вовсе н е обяза­
тельно иметь шифровальный диск. Все, что нам для этого нужно, - каран­
даш , бумага и минимальные знания арифметики !
-

38

Глава 1

Почему не работает двойное wифрование
У кого-то из читателей может возникнуть мысль, что использование
двух разных ключей шифрования подряд позволит вдвое увеличить стой­
кость шифра. Однако в случае шифра Цезаря (и большинства других шиф­
ров) это не так. В действительности результат двойного шифрования будет
эквивалентен тому, который вы могли бы получить обычным способом с
помощью единственного ключа. Попробуем применить двойное шифрова­
ние, чтобы понять, почему так происходит.
Если вы зашифруете слово "КITTEN" с помощью ключа 3, то будете при­
бавлять 3 к кодам букв простого текста, и результирующим текстом будет
"NLWWНQ". Если после этого зашифровать слово "NLWWHQ" , только
теперь с ключом 4, то получим слово "RPAALU". Но аналогичного резуль­
тата можно достичь, если сразу зашифровать слово "КITTEN", используя
ключ 7.
Для большинства шифров многократное повторное шифрование не
приводит к повышению стойкости шифра. Более того, если зашифровать
простой текст с использованием двух ключей , сумма которых равна 26, то
получим зашифрованный текст, полностью совпадающий с исходным !

Рез1Оме
Шифр Цезаря и другие подобные ему шифры не одно столетие применя­
лись для шифрования секретной информации. Но если вы захотите вруч­
ную зашифровать длинное сообщение, например целую книгу, то на это у
вас может уйти несколько дней или недель. Тут нам на помощь и приходит
программирование. С шифрованием и дешифрованием больших объемов
текста компьютер способен справиться менее чем за секунду!
Чтобы использовать компьютер для шифрования информации, следу­
ет освоить понятный ему язык и научиться писать программы, т.е. наборы
инструкций, следуя которым компьютер будет делать то же самое, что сде­
лали бы мы. К счастью, изучить язык программирования наподобие Python
гораздо легче, чем , скажем, японский или испанский. Что касается знания
математики, то от вас потребуется лишь умение выполнять операции сло­
жения, вычитания и умножения.
В следующей главе вы узнаете о том , как применять интерактивную обо­
лочку Python для построчного изучения программного кода.

И нструменты " бум аж ной " к риптогр аф и и

39

Кон т рольн ые воп росы

Ответы на контрольные вопросы приведен ы в п риложении Б.
1.

Зашифруйте следующи е фразы и з кн иги Амброза Бирса "Словарь Сата н ы"
( "The Devil' s Dictioпary" ), используя ука занные ключи.
А.
Б.
В.

"AM B I D EXTROUS: ABLE ТО PICK WITH EQUAL S К I LL А R I GHT-HAN D
РОС КЕТ O R А LEFT. " (кл ю ч 4 ) .
" G U I LLOT I N E: А MACH I N E WH I C H MAKES А F R E N C H MAN S H R U G H I S
S H O U LDERS WITH GOOD REASON." ( кл ю ч 1 7) .
" I M PI ETY: YOU R I R R EVERENCE TOWARD М У D EITY." ( ключ 2 1 )
.

2.

Дешифруйте следующие зашифрованные фрагменты текста, используя у ка­
за нные ключ и.
А. "ZXAI: р R D H IJ BT H D BTIXBTH LDGCQN H RD I RWBTC хе PBTGXRP PCS
PBTGXR PCHXC H R D IAPCS. " ( ключ 1 5 ) .
Б. " MQTSWXSV: Е VMZEP EWТMVERX XSTYFPMG LS RSVW." ( ключ 4).

3.

Зашифруйте следующее предложение, используя кл ю ч О:

4.

"TH IS 1 5 А S I LLY EXAMPLE."
Н иже приведен ы пары исходных слов и их зашифрованных версий. Какие
кл ючи ис пользовал ись в каждом случае?
А.
Б.
В.

5.

ROS E B U D - LIMYVOX
УАМАМ ОТО - PRDRDFKF
ASTRONOMY - H ZAYVUVТF

Как будет выглядеть приведенное ниже предложение, зашифрованное с помо­
щью ключа 8, если дешифровать его с помощью кл ю ч а 9?
" U M M SVMM: CVKWU UWV XI BQMVKM QV XTIVVQVO 1 ZM DMVOM BPI B
QA EWZBP E PQTM . "

40

Глава 1

П РОГР АМ М И РОВ А Н И Е
В И НТЕ Р А КТ И ВНОЙ О БОЛОЧ КЕ
"А н али тическая машина Бэббиджа не претендует
ни на какие открытия. Она может делатъ лишъ то,
'ttтo мы способнъt приказатъ ей и спалнятъ ".
Ада Лавлейс, октябfrь 1842 г.

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







Операторы
Значения
Цел ые и вещественные числа
В ы ражения
Хранение значен и й в переменных
И зменение значени й п еременн ых

•••

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

П ростые арифмети че ские выр а жения
Для начала откройте IDLE (см. введение ) . На экране появится окно ин­
терактивной оболочки с подсказкой > > > и мерцающим курсором рядом с
ней. Введите 2 + 2 и нажмите клавишу (в некоторых системах это
может быть клавиша ) . Компьютер отреагирует на это выводом на
экран числа 4 (рис. 2. 1 ) .
_ф Python 3.6.Gal Shetf

file .Edit Shefl _:te!)ug Qptions �1ndow J:!efp

Python З . б . О аl (vЗ . б . Оа l : S Э 9 бdа 37 2 fЬО , Мау 17 2 0 1 6 , 1 6 : 2 1 : 3 9 ) [MSC v . 1 9 0 0
(1\МDб4 ) ] o n win 3 2
Туре " copyr iqht " , " credi t s " or " l icense { ) " for more in forma t.ion .
>>> 2 " 2
4

»>

1

ln: 5 Col: 4

Рис. 2. J .

Вввднтв 2

+ 2

в ннтерактивно й оболочке

В примере на рис. 2 . 1 :шак + сообщает компьютеру о том , что к числу 2
нужно прибавить число 2 . PytJюn умеет выполнять и другие арифметиче­
ские операции, такие как вычитание чисел ( - ) , умножение ( * ) и ( / ) . Когда
символы + , - , * и / встречаются в таком контексте, они называются опера­
торами, поскольку приказывают компьютеру выполнить ту или иную опе­
рацию над числами, между которыми стоят. Арифметические операторы
Pyth o n приведены в табл. 2. 1 . Числа, окружающие знаки операций , называ­
ются зна'Ченuями или операндами.

42

Глава 2

Табn и ца 2. 1 . Арифметические операторы Pyth on

Оператор

Оnе раци 11

+

Сложение
Вы чита ние
Ум ножение

*

/

Дел е н ие

Сама по себе строка 2 + 2 не является программой, это всего лишь
одиночная инструкция. Программа образуется множеством подобных ин­
струкций.

Целые и вещесrвенные чиUJа
В программировании такие значения, как 4 , О и 9 9 , называют цмъtми
числами. Значения с десятичной точкой ( З . 5 , 4 2 . 1 и 5 . О ) называют веще­
ственнъtми числами или числами с мавающей тачкой. В Pythoв число 5 - це­
лое, но если записать его как 5 . О , то оно станет вещественным.
Целые и вещественные числа - это типъt данных. Значение 4 2 - это
целочисленный тип данных ( i nt ) . Значение 7 . 5 - это вещественный тип
данных, которому соответствуют числа с плавающей точкой ( f 1 о а t ) .
Любое значение относится к тому или иному типу данных. Обычно для
определения типа данных числа достаточно взглянуть на то, как оно запи­
сано. Числа без десятичной точки относятся к типу i n t , а числа, содержа­
щие десятичную точку, - к типу f loa t . Поэтому 4 2 это тип i n t , тогда как
4 2 . О тип f l oa t . Позже вы узнаете и о других типах данных (таких, как
строки, о которых пойдет речь в главе 3 ) .
-

-

Выра•енп
Вы уже видели один из примеров того, как в Pythoв решаются матема­
тические задачи, но возможности языка в этом отношении гораздо шире.
Введите в интерактивной оболочке следующие математические команды,
каждый раз завершая ввод нажатием клавиши (рис. 2.2 ) .
о > » 2+2+2+2+2
10
>>> 8*6
48
8 » > 10-5+6
11

о >» 2 +

2

Програ ммиро ван ие в интер а кти в ной обол очке

43

Такие команды называются въtражения­
ми. Компьютеры способны решать миллио­
ны подобных задач в секунду. Выражения со­
Зн а ч е н ие _.,. 2 + 2 � З н а чение
1
1
1
стоят из значений (чисел) , объединенных
Выр ажение
операторами (математическими знаками).
Выражение может включать столько угод­
Рис. 2.2. Выражение состоит из
значений (2) и операторов (+).
но значений ( О ) , если они объединяются
операторами, причем в одном выражении
можно использовать операторы разных типов ( 8 ) . Между значениями и
операторами можно вставить любое количество пробелов ( С) ) . Но следите
за тем , чтобы выражения начинались у левого края строки без предшеству­
ющих им пробелов, поскольку отступ строки кода влияет на то, как Python
будет интерпретировать инструкции. Подробнее об использовании пробе­
лов в начале строк вы узнаете в разделе "Блоки" главы 4.
Опер атор

i

Rop1дor оnераqнй
Возможно, вы помните из курса школьной математики , что порядох
операций определяет очередность выполнения операций в пределах вы­
ражения . Например, умножение предшествует сложению. Результат вы­
числения выражения 2 + 4 * 3 равен 1 4 , поскольку сначала выполняется
умножение 4 * 3 , а затем к полученному результату прибавляется 2 . Оче­
редность операций можно изменить, используя круглые скобки. В выра­
жении ( 2 + 4 ) * 3 сначала выполняется сложение ( 2 + 4 ) , после чего
полученная сумма умножается на 3. Добавив круглые скобки , мы получили
в результате не 1 4 , а 1 8 . Понятие приоритета (старшинства) операций в
Python имеет тот же смысл , что и в математике. Сначала выполняются опе­
раторы в круглых скобках, затем операторы * и / в порядке их следования
слева направо и лишь после этого операторы + и - (также в направлении
слева направо) .

Вычжпенне выражений
Если компьютер выполнил операцию 1 О + 5 и выдал результат в виде
числа 1 5 , то говорят, что он вычислш� выражение. Вычисление выражения
сводит его всего к одному значению точно так же , как решение арифмети­
ческой задачи сводит ее к единственному числу: ответу.
Выражения 1 О + 5 и 1 О + 3 + 2 имеют одно и то же значение, поскольку
результатом вычисления каждого из них является число 1 5 . Выражениями
считаются даже одиночные числа: значением выражения 15 является 1 5 .

44

Глава 2

Python продолжает вычислять выражение до тех пор, пока оно не пре­
вратится в единственное значение, как показано ниже.
(5 - 1)

'



((7

/

1)

+

(3 - 1 ) )

4

*

( ( 7 + 1) / (3 - 1 ) )

4



((

'
8

)

/

4 .

((

8

)

/

4

*

+

(3 - 1) )

t

( 2 ))

4.0

'-Г
16 . О

В Pytlюn вычисление выражения начинается с наиболее глубоко распо­
ложенных круглых скобок в направлении слева направо. Даже если пары
скобок вложены друг в друга, заключенные в них части выражения вы­
числяются по тем же правилам , что и любое другое выражение. Поэтому,
когда Python сталкивается с выражением ( ( 7 + 1 ) / ( 3 - 1 ) ) , первым
вычисляется выражение, заключенное во внутреннюю пару скобок, распо­
ложенную слева, ( 7 + 1 ) , и только после этого вычисляется выражение
в скобках справа, ( 3 - 1 ) . Выражения во внешних скобках вычисляются
лишь после того, как каждое из выражений во внутренних скобках будет
приведено к единственному значению. Обратите внимание на то, что ре­
зультат деления представляет собой вещественное значение (с плавающей
точкой) . Наконец, когда больше не остается выражений, заключенных в
скобки, Python выполняет оставшуюся часть вычислений в порядке следо­
вания операторов.
В выражении должно быть не менее двух значений, связанных операто­
рами. Если ввести в интерактивной оболочке всего одно значение и опера­
тор, то будет выдано сообщение об ошибке.
>>> 5 +
Synt axE r r o r : i nva l id s yntax

Эта ошибка возникла потому, что 5 + не является выражением. В Python
ожидается , что у оператора + будет два операнда. Термин синтаксичеС1Сая
ошибка означает, что компьютер не смог понять смысл введенной вами ин­
струкции, которая оказалась некорректной. Не забывайте: программиро­
вание подразумевает не просто ин формирование компьютера о том , что
он должен сделать, но и знание того, как правильно составлять инструк­
ции , которым он должен следовать.

П рогр а ммиро ван ие в интер а кти в ной обол очке

45

Ошибки

-

это норма.Аьно!

Ош ибки в п рограммах не я вля ются п реступлением! Комп ьютер не сломается и з-за
того, что в ы введете код, вызывающий ошибку. Python п росто известит вас о ее воз­
н и кновен и и и вновь отобразит подсказку > > >, п редлагающую продол.жить ввод ко­
ма нд.
П ока вы не п риобретете достаточный опыт п рограммирования, смысл этих сообще­
н и й будет не сли ш ком понятен для вас. Одна ко вы всегда сможете "поrуглить" текст
сообщения и получить ссылки на веб-страницы, где описы вается при рода конкретных
ошибок. Н иже ука зан адрес веб-стра ницы, где содержится список наиболее часто
встречающихся сообщен и й об ошибках Python и раскрывается их смысл:
https : / / i nvent w i t hpython . com/Ы og / 2 0 1 2 / 0 7 / 0 9 /
1 6 - common-python- runt ime - e r ro r s -b e g i nne r s - f ind/

Сохранение эначений в nеременных
Программы часто нуждаются в сохранении значений для их последую­
щего использования. Значения можно сохранять в переменных с помощью
знака равенства =, который также называют оператором присваивания. На­
пример, чтобы сохранить значение 1 5 в переменной spam, введите в инте­
рактивной оболочке следующую команду:
> > > spaa

=

15

Переменную можно представить в виде ящика, в котором находит­
ся значение 1 5 ( рис. 2.3) . Имя переменной s pam - это надпись на ящике
( позволяющая отличать одну переменную от другой) , а значение перемен­
ной - находящаяся в нем записка.
Нажав клавишу , вы не увидите в ответ
ничего, кроме пустой строки. Раз не появилось
никаких сообщений об ошибках, можете быть
уверены в том , что инструкция была выполнена
успешно. Появление очередной подсказки > > >
сигнал о возможности ввода следующей инструк­
ции.
В данном случае команда, содержащая опера­
тор присваивания = , создает переменную spam и
сохраняет в ней значение 1 5 . В отличие от вы­
Рис. 2 . З. Переменн ы е мож­
ражений инструкции - это команды, которые не
но уnодобнть ящиком, спо­
собным хранить зночення
сводятся к вычислению какого-либо значения.
-

46

Глава 2

Они связаны с выполнением действий, по эт о му в дан но м случае в и нтерак
тивной оболочке ничего не отображается.
Понять, какие команды являются выражениями, а какие инструкци­
ями, бывает непросто. Достаточно запомни·1ъ, что инструкция, выполне­
ние которой дает в результате одно значение, является выражением. Все
остальные команды - это инструкции.
Инструкция присваивания начинается с имени переменной, за кото­
рым следует знак = , а за ним - выражение ( рис . 2.4) . Результат вычисления
этого выражения сохраняется в указанной переменной.
­

-

*

Опер а то р присваивания

И м я пере м енн о й ..._.

S pam

::::

1

В ыражение
1

10

+

5

1

И нструкция присваивания

Рис. 2.4.

Составные части 11нструкц1111 пр11сва11ван11я

Имейте в виду, что переменные хранят значения , а не выражения, кото­
рые им присваиваются. Так, если вы введете инструкцию spam = 10 + 5 ,
т о сначала вычисляется результат выражения 1 О + 5 , а затем значение 1 5
присваивается переменной spam, что можно проверить, введя в интерак­
тивной оболочке имя этой переменной.
>>> sраш = 1 0 + 5
> > > sраш
15

Переменная сама по себе является выражением , значением которого
будет значение, хранящееся в этой переменной. Отдельное значение тоже
является выражением, значение которого (:овпадает с ним самим.
» > 15
15

А сейчас интересный момент! Если вве с ти инструкцию spam
дет выдано целочисленное значение 2 0 .
> > > sраш

=

+

5,

то бу­

15

> > > spam + 5

20

Программирова ние в интер а кти в ной оболо чке

47

Как видите, переменные можно использовать в выражениях наравне со
значениями. Поскольку значение spam равно 1 5, вычисление выражения
s pam + 5 сводится к вычислению выражения 1 5 + 5 , что дает 2 О .

Иэменение эначениi переменных
Хранящееся в переменной значение можно изменить, введя еще одну ин­
струкцию присваивания. Например, введите показанные ниже команды.
>>>
о >>>
о 20
о >»
о »>
0 8

15
sраш
sраш + 5
=

sраш
3
sраш + 5
=

После ввода первой инструкции spam + 5 ( О ) вы получите в качестве
результата 2 0 ( б ) , поскольку ранее сохранили в переменной spam значе­
ние 1 5 . Но когда вы вводите инструкцию spam = 3 ( С) ) , то значение 1 5
перезаписывается значением 3 (рис. 2.5) . Если теперь вы вновь введете ин­
струкцию spam + 5 ( О ) , то результатом вычисления выражения будет уже
8 ( 0 ) , поскольку выражение spam + 5 вычисляется как 3 + 5 . Прежнее
значение, хранившееся в переменной spam, оказалось забыто.

Рис. 2.5.

Значенне, хранящееся в переменной

sрат,

заменяется значеннем

3

Значение, хранящееся в переменной spam, можно использовать даже
дЛЯ того, чтобы присвоить новое значение самой этой переменной.

48

Глава 2

> > > spam

=

> > > spam

=

15
spam + 5

> > > spam

20

Инструкция spam = s pam + 5 сообщает компьютеру следующее: "Но­
вое значение переменной spam равно текущему значению плюс пять". Пе­
ременной, указанной слева от знака =, присваивается значение, вычисля­
емое справа от него. Значение переменой s pam можно увеличивать на 5
несколько раз подряд.
> > > spam

=

> > > spam

=

> > > spam

=

> > > spam

=

15
spam + 5
spam + 5
spam + 5

> > > spam

30

Значение переменной spam изменяется всякий раз, когда выполняется
очередная инструкция s pam = s pam + 5 . Окончательным значением , со­
храненным в переменной s pam, становится 3 0 .

Имена переменных
Несмотря на то что компьютеру абсолютно безразлично, как вы назо­
вете свою переменную, вы должны взвешенно отнестись к тому, как ее
назвать. Назначая переменным имена, отражающие, какого типа данные
содержатся в этих переменных, вы облегчите себе и другим людям пони­
мание программы. Вы вправе присвоить своим переменным имена напо­
добие abrahamL i nco l n или mon key, даже если в действительности ваша
программа не имеет никакого отношения ни к Аврааму Линкольну, ни к
обезьянам , - компьютеру все равно. Но если вы вернетесь к программе
после длительного перерыва, то вам может оказаться трудно вспомнить,
каково назначение той или иной переменной.
Удачно подобранные имена переменных описывают тип содержащих­
ся в них данных. Представьте, что вы переехали в новый дом , а все ваше
движимое имущество хранится в ящиках, помеченных как "Вещи". Так вы
никогда ничего не найдете! В качестве типичных имен переменных в при­
мерах книги, а также в значительной части документации к Python, исполь­
зуются имена spam, e gg s , bacon и т.п. ( по мотивам скетча "Спам" британ­
ской комик-группы "Монти Пайтон" ) , но в своих программах старайтесь
давать переменным более описательные имена.
П рогр а ммиро ван и е в интер а ктив н ой обол оч ке

49

Имена переменных (как и все остальное в Python ) чувствительны к ре­
гистру букв. Чувствителъностъ к регистру (case sensitivity) означает, что оди­
наковые имена переменных, но записанные с использованием букв разно­
го регистра, считаются разными. Например , в Python sparn, S РАМ, Sparn и
s РАМ - четыре разные переменные. Они не являются взаимозаменяемыми
и могут содержать независимые значения.

Реэ1Оме
Когда же мы приступим к написанию программ шифрования? Совсем
скоро. Но прежде чем вы сможете взламывать шифры , следует изучить еще
несколько базовых понятий программирования , которые мы рассмотрим
в следующей главе.
В этой главе вы научились применять интерактивную оболочку для
выполнения простейших инструкций Python. Вы должны предоставлять
Python точные указания относительно того, что следует делать, поскольку
компьютеры понимают лишь очень простые инструкции. Вы узнали о то м ,
что Python способен вычислять выражения (т.е. сводить их к единственно­
му значению) и что выражения - это значения (такие, как 2 или 5 4 ) , объ­
единенные с помощью операторов (таких, как + или - ) . Вы также узнали о
том , как сохранять значения в переменных, чтобы впоследствии их можно
было использовать в программе.
Интерактивная оболочка - удобный инструмент для изучения Python ,
поскольку можно вводить по одной инструкции за раз и сразу же видеть
результат. В главе 3 мы начнем создавать программы , которые содержат
множество последовательно выполняющихся инструкций. Мы обсудим
ряд других базовых концепций, что позволит вам написать свою первую
программу!
.

50

Глава 2

Ко н т р ольные вопросы

Ответы на контроль ные в опрос ы п риведены в приложении Б.
1

.

2.

Как выгл ядит оператор делен ия: / или \ ?
Какое и з при в еде нных ниже значени й я вля ется цел ым, а какое - вещественным?
42

3 . 1 4 1 5 92

3.

Какие из при в еде нных н иже строк не я вл я ютс я в ыражени я ми?
4

х

10 + 2

3 * 7 + 1

2 +
42

2 + 2

sparn

4.

=

42

Если вв ести следующие команды в интерактивной о б олочке, то какие резуль­
таты ото б разятся на экране после вып олнени я строк О и О ?
sparn

=

S РАМ

=

20

О sparn + 2 0
О sparn

30

П рогра ммиро вание в интерактивной обол оч ке

51

СТРОКО В Ы Й ТИ П ДАН Н Ы Х И
НАП И САН И Е П РО ГРАМ М
"Еди нственн ъ�й способ выучи т,ь 'Новый язык
программирован ия - это писатъ программ ы 'На нем ".
Брайа- н Кернигаи, Депи ис Рит•щ
"Язъпс программирова ия С "
н

В главе 2 вы узнали достаточно много о
целых числах и арифметических операци·
ях. Но Python - это же не калькулятор. П о·
скольку криптография основана на обработке
текстовых значений путем преобразования простого текста в
зашифрованный и наоборот, в этой главе вы узнаете о том,
как хранить, комбинировать и выводить текст на экран. Вы
также напишете свою первую программу, которая будет при·
ветствовать пользователя словами "Hello , world ! " и предла·
гать ему ввести свое имя.

В это й главе ."


Строки
Конкатенация и репли ка ция строк
И ндексы и срезы
Функция p r i n t ( )
На п иса н ие п рограмм с помощью I D LE
Сохранение и вы полнение п рограмм в I DLE
Комментар и и
Функция i nput ( )









Исnоnьэование строковых зна ч ений дn• работы
с текстом
В Python небольшие фрагменты текста называются строковыми значе­
ниями или просто стр01'амu. Все наши программы шифрования и взлома
будуг работать со строковыми значениями, преобразуя простой текст на­
подобие ' One i f Ьу l and , two i f Ьу space ' в зашифрованный текст
наподобие ' Ь l r Jvs Jo ! Jyn l q , J7 0 2 Jvs Jo ! J б З nprM ' . Простой и зашифро­
ванный тексты будуr представлены в программе строковыми значениями,
и Python предлагает множество способов для манипулирования этими зна­
чениями.
Строковые значения можно сохранять в переменных точно так же, как
целые и вещественные числа. Когда вы вводите строку, заключайте ее в
одинарные кавычки ( ' ) , показывающие, где она начинается и где заканчи­
вается. Введите в интерактивной оболочке следующий код:
> > > spam

=

' hello '

Обрамляющие кавычки не являются частью строкового значения.
Python понимает, что ' he 1 1 о ' - это строка, а s parn переменная, посколь­
ку строки заключаются в одинарные кавычки, а имена переменных - нет.
Если вы введете spam, то увидите содержимое этой переменной (строку
' he l l o ' ) .
-

> > > spam

=

' hello '

> > > spam

' he l l o '

54

Глава З

Так происходит потому, что Python вычисляет значение переменной - в
данном случае строку ' he l l o ' . Строки могут содержать любой алфавит­
но-цифровой символ. Рассмотрим несколько примеров строк.
1 hello 1
' he l l o '
> > > ' KITTENS '
>>>

' K I T TENS '

>>>
1

1 1

1

1 7 apples , 14 oranqes , З lemons '
' 7 appl e s , 1 4 orange s , 3 l emons '
> > > ' Anythinq not pertaininq to elephants is irrelephant . •
' Anything not pert a i ning t o e l ephan t s i s i r r e l ephant . '
> > > ' 0* &#wY% * &0cfsdYO* &qfC%Y0* & % 3yc8r2 '
' O* & # wY % * & 0 c f s dYO* & g fC % Y0 * & % 3 yc 8 r 2 '

>>>

Обратите внимание на то, что строка ' ' не содержит никаких симво­
лов; между двумя кавычками ничего не было введено. Такие строки называ­
ются пустыми.

Конкаrенаqи• сrрок с помощью oneparopa

+

Можно сложить две строки и получить новую с помощью оператора + .
Такая операция называется конкатенацией. Введите в интерактивной обо­
лочке следующий текст.
' Bello , ' + • world ! •
' He l l o , world ! '

>>>

Руtlюн конкатенирует строки в тачности в том виде, в каком вы их пре­
доставляете, не вставляя между ними пробелы. Чтобы вставить пробел
между ' He l l o , ' и ' wo r l d ! ' , введите пробел в конце строки ' He l l o , ' , пе­
ред закрывающей кавычкой.
' Bello , • + • world ! •
' He l l o , wor l d ! '

>>>

В результате конкатенации двух строковых значений с помощью опе­
ратора + вы получаете новую строку ( ' He l l o , ' + ' wo r l d ! ' превраща­
ется в ' He l l o , wo r l d !' ) точно так же , как в результате сложения двух
целых чисел получается новое целочисленное значение ( 2 + 2 превра­
щается в 4 ) . Python понимает, что именно должен делать оператор + , ис­
ходя из типа данных соответствующих значений. Как вы узнали в главе 2 ,
Строко в ый тип дан н ых и написание прогр а мм

55

тип данных сообщает нам ( и компьютеру) , какого рода данные участвуют
в операции.
Оператор + может использоваться в выражении при условии соответ­
ствия типов данных. Если попытаться сложить строку с числом, будет по­
лучено сообщение об ошибке. Введите в интерактивной оболочке следую­
щие команды.
> > > ' Bello ' + 42
T raceback ( mo s t re cent ca l l l a s t ) :
F i l e " < s t din> " , l i ne 1 , i n
T ypeE rror : must Ье st r , n o t i nt
> > > ' Bello ' + ' 42 '
' He l l o 4 2 '

Первая команда вызывает ошибку, поскольку ' He l l o ' - строка, а 4 2
целое число. Однако во второй команде ' 4 2 ' - уже строка, которую Python
успешно конкатенирует.

-

Pennнraqи1 'rpor ' nомощыо oneparopa

*

Оператор * позволяет умножить строку на целое число, в результате
чего строка реплицируетс.я (т.е. повторяется) несколько раз, причем крат­
ность повторения определяется целочисленным множителем. Введите в
интерактивной оболочке следующие команды.
О > > > ' Bello ' * З
' He l l oHe l l oHe l l o '
> > > spam
' AЬcd.ef '
8 > > > spam
spam * З
> > > spam
' AЬcde fAЬcde fAЬcde f '
=

=

Чтобы реплицировать какую-либо строку, введите ее, затем оператор * ,
а вслед за ним - требуемое количество повторений строки ( О ) . Можно так­
же сохранить строку в переменной и проделать то же самое с ней ( 8 ) .
В главе 2 вы узнали о том, что результатом применения оператора * к
двум целым числам является их произведение. Однако попытка использо­
вать оператор * в отношении двух строковых значений приведет к ошибке.
> > > ' Bello ' * ' world ! '
T ra ceba c k ( mo s t re cent cal l l a s t ) :
F i l e " < s t di n > " , l i ne 1 , i n
TypeE rror : can ' t mu l t i p l y s e quence Ьу non- i nt o f t ype ' st r '

56

Гл ава З

На примере конкатенации и репликации строк мы видим , что операто­
ры в Python могут выполнять разные действия в зависимости от типов дан­
ных операндов. Оператор + реализует сложение и конкатенацию, а опера­
тор * - умножение и репликацию строк.

Получение fимаолов frporи ' помощью индеrtоа
В программах шифрования часто будет требоваться получить отдель­
ные символы строк, и это можно сделать с помощью индексации. Чтобы
обратиться к отдельному символу строки, добавьте в конце строкового
значения ( или имени переменной, содержащей строку) пару квадратных
скобок ( [ и ] ) , заключив в них число, которое называется индексом и задает
позицию интересующего вас символа в строке. В Python индексация начи­
нается с О, поэтому индексом первого символа строки будет О. Индекс 1
соответствует второму символу, индекс 2 - третьему и т.д.
Введите в интерактивной оболочке следующие команды.
> > > Spa.JD
' Bello '
> > > spa.JD [ O ]
=

1

н

1

> > > spa.JD [ 1 ]
'е'

> > > spa.JD [ 2 ]
l'
1

Обратите внимание на то, что результатом вы­
числения выражения spam [ О ] является строко­
о 1 2 3 4
И ндексы :
вое значение ' Н ' , поскольку Н - первый символ
строки ' He l l o ' , а нумерация индексов начинает- Рис. 3. 1 . Строка ' He l l o '
н ее индексы
ся с О, а не с 1 (рис. 3. 1 ) .
Индексацию можно применить к переменной,
содержащей строковое значение, как это было сделано в предыдущем примере, или к самому строковому значению.
» > ' Zophie ' [ 2 ]
'р'

Значением выражения ' Z ophi e ' [ 2 ] является строковое значение ' р ' .
Строка ' р ' ведет себя как любое другое строковое значение, и ее можно
сохранить в переменной. Введите в интерактивной оболочке следующие
команды.

Строко в ый тип дан ных и на п и сание п рогра мм

57

> > > e99s • ' Zophie ' [ 2 )
> » e99s
'

р'

Если ввести индекс , значение которого слишком велико для данной
строки, Python выдаст сообщение об ошибке " s t r ing i ndex out o f
range " .
» > ' Hello ' ( 1 0 )
T r aceba c k

( mo s t r e c e n t ca l l l a s t ) :

l i n e 1 , i n
I ndexErro r : s t r i n g i ndex o u t o f range
Fi l e " < s t d i n> " ,

В данном случае ошибка обусловлена тем , что делается попытка исполь­
зовать индекс 1 0 в отношении строки ' He l l o ' , которая содержит всего 5
символов.

Оtрицатепьн ые индексы

Отриц атм:ь нъtе индексъе начинаются с конца строки и отсчитываются в
обратном направлении. Отрицательному индексу -1 соответствует послед­
ний символ строки, -2 - это индекс предпоследнего символа строки и т.д.
(рис. 3.2) .
Введите в интерактивной оболочке следующие команды.
> > > ' Bello ' ( - 1 )
'о'

> > > ' Hello ' ( - 2 )
11'

> > > ' Hello ' ( - 3 )
'1'

> > > ' Hello ' ( - 4 )
' е '

> > > ' Hello ' ( - 5 )
'Н'

> > > ' Hello ' ( 0 )
'Н'

С трока :
И н дексы :

'

1 1 l l 1 1'
Н е 1 1 о
- 5 -4 - 3 - 2 - 1

Рис. 3 . 2. Строка ' He l l o '
и ее отрицательные нндексы

58

Гла ва 3

Обратите внимание на то, что индексы -5 и О
указывают на один и тот же символ. Чаще всего
вы будете использовать положительные индек­
сы, но иногда все же удобнее работать с отрица­
тельными.

Попучен 11е HICIOJIЬlllX CllMIOJIOI аро111 с ROMOIЦЬIO срезов

Если хотите извлечь из строки более одного символа, используйте срезы
вместо индексов. В случае среза (slice) также указывается пара квадратных
скобок ( [ и ] ) , но теперь в них задается не одно целочисленное значение,
а два. Эти два значения разделяются двоеточием ( : ) и сообщают Python
индексы первого и последнего символов среза. Введите в интерактивной
оболочке следующую команду.
' Bowdy ' [ 0 : 3 )
' How '

>>>

Строка, которая возвращается в результате вычисления среза, начи­
нается с позиции, указанной первым индексом, и продолжается до пози­
ции, указанной вторым индексом, но не включает ее. В строке ' Howdy '
индексу О соответствует символ ' Н ' , а индексу 3 - символ ' d ' . Посколь­
ку срез доходит до второго индекса, но не включает его, значением среза
' Howdy ' [ О : З ] становится строка ' How ' .
Введите в интерактивной оболочке следующие команды.
' Bello ,
' He l l o '
> > > ' Bello ,
' wo r ld ! '
> > > ' Bello ,
' wo r l d '
> > > ' Bello ,
>>>

' r '

world ! ' [ 0 : 5 ]
world ! ' [ 7 : 13 ]
world ! ' [ - 6 : - 1 )
world ! ' [ 7 : 13 ] [ 2 ]

Обратите внимание на то, что в выражении ' He l l o , wo r l d ! ' ( 7 : 1 3 ]
[ 2 ] сначала вычисляется промежуточный результат в виде ' wo r l d ! ' [ 2 ] ,
вычисление которого дает окончательный результат в виде строкового
значения ' r ' .
В отличие от индексов использование слишком больших индексов в
срезе никогда не приводит к ошибке. В этом случае будет возвращаться ре­
зультат, соответствующий наиболее широкому из всех возможных соответ­
ствий срезу.
' Bello ' [ 0 : 99 9 ]
' He l l o '
> > > ' Bello ' [ 2 : 9 9 9 )
' l lo '
> > > ' Bello ' [ 1 0 0 0 : 2 0 0 0 ]
1 '

>>>

Строко в ый тип дан н ых и на п исание програ мм

59

Выражение ' Не 1 1 о ' [ 1 О О О : 2 О О О ] возвращает пустую строку, поскольку
индекс 1 ООО указывает на позицию за пределами конца строки, а раз так, то
отсутствуют символы , которые могли бы войти в данный срез. И хотя здесь
это не продемонстрировано, срезы также можно сохранять в переменных.
Опускани е инде ксо в в ср е з ах

Если опустить первый индекс в обозначении среза, Python автома­
тически использует для него значение О. Выражения ' Howdy ' [ О : 3 ] и
' Howdy ' [ : 3 ] возвращают одну и ту же строку.
• вowdy ' [ : З ]
' How '
> > > ' Bowdy ' [ 0 : 3 ]
' How '
>>>

Если опустить второй индекс, Python автоматически выберет оставшую­
ся часть строки, начиная с первого индекса.
' Bowdy ' [ 2 : ]
' wdy '

>>>

Индексы можно опускать по-разному. Введите в интерактивной оболоч­
ке следующие команды .
> > > myName = ' Zophie the Fat Cat '
»> myName [ - 7 : ]

' Fat Cat '
»> myName [ : 1 0 ]

' Z oph i e the '
»> myName [ 7 : ]

' t he Fat Cat '

Как видите, можно использовать даже отрицательные индексы. По­
скольку в первом примере -7 - начальный индекс, Python отсчитывает ин­
дексы с конца строки в обратном направлении, используя указанный ин­
декс в качестве начального. Затем он возвращает все символы , начиная с
данного индекса и до конца строки , поскольку второй индекс опущен.

Вывод значений с nомощьlО функции print ( )
Рассмотрим , как работает инструкция другого рода: функция p r i n t ( ) .
Введите в интерактивной оболочке следующие команды.

60

Глава З

> > > print ( ' Hello ! ' )
Hello !
» > print ( 4 2 )
42

Функции, такие как p r i n t ( ) , содержат код, предназначенный для ре­
шения конкретной задачи. В данном случае это вывод значений на экран.
В Python имеется множество полезных функций, благодаря которым вы
сэкономите уйму времени. Вызов функции означает выполнение содержа­
щегося в ней кода.
В данном примере в функцию p r i n t ( ) передается значение, указанное
в круглых скобках, и она выводит его на экран. Значения , которые переда­
ются функции при ее вызове, называются аргументами. Когда вы начнете
писать программы , вы будете использовать функцию p r i n t ( ) для отобра­
жения текста на экране.
В функцию p r i n t ( ) можно передать не одиночное значение, а выра­
жение. Такое возможно, потому что значением, фактически переданным
в функцию, будет результат вычисления этого выражения. Введите в ин­
терактивной оболочке выражение, конкатенирующее строки , в качестве
аргумента.
> > > spam
' Al '
> > > print ( ' Bello , ' + spam)
H e l l o , Al
=

Выражение ' He l l o , ' + sраm возвращает значение ' He l l o , ' + ' Al ' ,
результатом вычисления которого является строка ' He l l o , Al ' . Именно
эта строка и передается в функцию p r i n t ( ) при ее вызове.

Вывод экранированных симвоnов
Иногда строка содержит символы, сбивающие Python с толку. Напри­
мер, если в строке встретится одинарная кавычка, то вы получите сооб­
щение об ошибке, поскольку Python посчитает, что она обозначает конец
строки, и последующий текст - некорректный код, а вовсе не остаток стро­
ки. Введите в интерактивной оболочке следующий код, чтобы увидеть, как
все происходит в реальности.
> > > print ( ' Al ' s cat is named Zophie . ' )
S ynt axEr ror : i nva l i d s yntax

Строко в ый тип данн ых и написание програ мм

61

Чтобы вставить одинарную кавычку в строку, следует экранироватъ ее.
Экра нированнъtй символ (escape character) это символ, которому предше­
ствует обратная косая черта, например \ t , \ n или \ ' . Косая черта сообща­
ет Python о том, что следующий за ней символ имеет особый смысл . Введи­
те следующий код в интерактивной оболочке.
-

> > > print ( ' Al \ ' s cat is ruuaed Zophie . )
Al ' s cat i s named Z ophie .
'

Теперь Python понимает, что одинарная кавычка - часть строки, а не
признак ее конца.
В табл. 3 . 1 перечислено несколько экранированных символов, поддер­
живаемых в Python.
Табn и ца 3 . 1 . Экранированные символ ы

Э кранированный
символ

Отображаемый результат

\\

Об р атная косая ч е рта
Оди на р на я кав ыч ка

\'
\"

Дво й на я ка в ычка

\n

С имвол ново й ст р оки

\t

Та б уля ци я

Экранированному символу всегда предшествует обратная косая черта.
Если вы захотите включить в строку сам символ обратной косой черты,
то недостаточно указать только его, поскольку следующий за ним символ
будет интерпретироваться Python как экранированный. Например, следу­
ющая команда не будет корректно работать.
>» print ( ' I t is а green\ teal color . ' )
I t i s а green
e a l co l o r .

Здесь ' t ' в ' t e a l ' идентифицируется как экранированный символ, по­
скольку ему предшествует косая черта. Экранированный символ \ t имити­
рует нажатие клавиши на клавиатуре.
Введите вместо этого следующий код.
>>> print ( ' I t is а green\ \ teal color . ' )
I t i s а green \ t e a l c o l o r .

62

Глава З

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

Одинарные и двойные кавычки
Строки не обязательно заключать в одинарные кавычки. Вместо них
можно использовать и двойные кавычки. Следующие команды выводят
один и тот же текст.
>>> print ( ' Hello , world ! ' )
He l l o , w o r l d !
> > > print ( " Bello , world ! " )
He l l o , world !

Однако смешивать одинарные и двойные кавычки нельзя. ('..ледующая
команда вызывает ошибку.
> > > print ( ' Hello , world ! " )
S yntaxE r r o r : EOL wh i l e s cann i ng s t ring l i t e r a l

В книге используются в основном одинарные кавычки , поскольку их не­
много проще вводить, хотя для Python это совершенно безразлично.
Точно так же, как для вставки одинарной кавычки в строку, заключен­
ную в одинарные кавычки, следует использовать экранированный символ
\ ' , понадобится использовать экранированный символ \ " для вставки
двойных кавычек в строку, ограниченную двойными кавычками. Рассмо­
трим, например, следующие две команды.
>>> print ( ' Al\ ' s cat i s
Al ' s cat i s Z ophi e . She
> > > print ( " Zophie said ,
Z oph i e s a i d , " I сап s a y

Zophie . She says , "Меоw . 11 1 )
s ays , "Meow . "
\ " I can say thinqs o the r than ' Меоw ' you know . \ 11 11 )
t h i n g s other than ' Meow ' you know . "

В экранировании двойных кавычек в строке, ограниченной одинарны­
ми кавычками , и одинарных кавычек в строке, огр ан ич енной двойными
кавычками, нет необходимости. Интерпретатор Python понимает, что
если строка начинается с кавычек одного типа, то кавычки друго го типа не
могут служить признаком ее окончания.

Строко в ый тип да нных и написание прогр а мм

63

Написание nроrрамм в редакторе файnов IDLE
До сих пор вы вводили инструкции по одной за раз в окне интерактив­
ной оболочки. Но когда вы пишете программы , вы вводите сразу несколь­
ко инструкций и запускаете на выполнение их все, не дожидаясь каждый
раз ввода очередной инструкции. Пришло время и вам написать свою пер­
вую программу!
Приложение, которое предоставляет интерактивную оболочку, называ­
ется IDLE (lntegrated Developшent and Learning Environшent интегриро­
ванная среда разработки) . Помимо интерактивной оболочки в IDLE также
есть редактор файлов, который мы сейчас откроем.
Выберите в верхней части окна оболочки Python пункты меню File c::::>
N ew Wi nd ow , чтобы открыть новое пустое окно редактора файлов, в кото­
ром вы сможете ввести код программы (рис. 3.3) . В правом нижнем углу
этого окна отображаются номера строки и столбца текущей позиции кур­
сора.
-

е \Jntitkd
1 ilc

l·dit

fomыl

ltur1

Optior>\

Winduw

1 klp

l ra: l

Рис. 3.3.

Coh O

Окно редактора файлов с текущей позицией курсора в столбце О строки 1

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

Исходный код nроrраммы 11Hello, world!"
По давно сложившейся традиции первой программой, с написания ко­
торой новички начинают изучение языка, является программа, которая
выводит на экран текст " He l l o , world ! " . Мы создадим эту программу,
введя соответствующий текст в новом окне редактора файлов. Мы называ64

Глава 3

ем этот текст исходным кодом программы, поскольку он содержит инструк­
ции, которым будет следовать Python.
Исходный код программы "Hello, world! " доступен для загрузки на сайте
издательства (см. введение) . Если вы столкнетесь с ошибками при вводе
кода, сравните свой код с приведенным в книге, используя онлайн-утили­
ту D i f f (описана в следующем разделе) . Не забывайте о том, что вводить
номера строк не следует; они указаны в книге исключительно для удобства
ссылок на соответствующие строки кода при его описании.
hello.py
1.

# Thi s prog ram says he l l o and a s ks for my name .
p r i nt ( ' He l l o , wo r l d ! ' )
3 . p r i nt ( ' What i s your name ? ' )
4 . myName = i nput ( )
5 . p r i nt ( ' I t i s good t o meet you , ' + myName )

2.

В IDLE инструкции различного типа выделяются разными цветами.
Когда вы введете весь код, окно должно выглядеть примерно так, как по­
казано на рис. 3.4.
f ilo

fdit

1 orm.rt

Ruro

Option'

Wirodow

l lclp

t Th1!: prcg::am 5ау� he l l o ar.d a � k� far my г.�е .

print { He J.. i.. c,

print ( ' Wha": .... в
п;.уNt ()

)

cu:r !".а. е ? )
1

+ myName)

l n: 6

Рис. Э .4.

Cul: O

Окно редактора файлов по завер ш ении ввода кода

Проверка nравиnьности исходноrо кода с nомощьlО
онnайн -утиnиты D iff
Несмотря на то что можно скопировать и вставить текст программы
hello.py или загрузить его на сайте книги , лучше ввести его вручную. Так вы
ближе познакомитесь с исходным кодом программы. Однако в процессе
ввода текста в файловом редакторе есть риск допустить ошибки.
Чтобы сравнить введенный вами код с кодом, используемым в книге,
используйте онлайн-утилиту D i f f (рис. 3.5) . Скопируйте текст своей про­
граммы , а затем перейдите по адресу h t tp : / / i nventwi t hpython . c om/
ha с k i ng / d i f f / . Выберите программу hello.ру в списке слева, вставьте свой
код в текстовое поле справа и щелкните на кнопке Com pare . Утилита поСтроко в ый тип данн ых и напи сан и е прогр а мм

65

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

.... � Thls program says hello and asks fof my name
pr>nl(Нelowotldr)
pmt('Wtlat rs your neme?')
rтryNamв = !nput()
print(11 1S OCJod to meet you " �1

Th� Вook's Program

Vour

Prooram

Рис. 3.5.

Онлайн-утилита

Di ff

Исnоnьзова ние I DLE дnя nocneдy10щero достуnа
к nроrрамме
В процессе написания программы ее можно сохранить, чтобы вернуть­
ся к ней впоследствии, особенно если речь идет о вводе очень больших
программ. В IDLE имеется возможность сохранять и открывать програм­
мы точно так же, как в текстовом редакторе можно сохранять и открывать
документы .

Сохранение nроrраммы
Введя исходный код, сохраните его, чтобы вам не пришлось вводить его
заново всякий раз, когда вы захотите выполнить программу. Выберите пун­
кты меню Fi l e � Save As в верхней части окна редактора файлов, чтобы от­
крыть диалоговое окно Сохран ить как (рис. 3.6 ) . Введите hello . ру в поле
И мя файла и щелкните на кнопке Сохранить.
Почаще сохраняйте свои программы в процессе их ввода, чтобы не по­
терять часть работы в случае сбоя компьютера или случайного выхода из
IDLE. Для сохранения файла можно также использовать комбинацию кла­
виш (Windows/Linux) или ( rnacOS ) .

66

Глава З

Cr•rfy

1

Albert

1 11: 9

Рис. 3.7.

Col: �

Приблизительно так вы глядит окно интерактивной оболочки
во время выполнения программы "Hello, world! "

Строко в ый тип данн ых и на п исание прогр а мм

67

После того как вы нажмете клавишу , программа должна попри­
ветствовать вас (как пользователя данной программы ) по имени. Примите
поздравления : вы написали свою первую программу! Теперь вы начинаю­
щий программист. (Если хотите, можете вновь выполнить программу, по­
вторно нажав клавишу . )
Если вместо этого в ы получите сообщение о б ошибке наподобие при­
веденного ниже, то это будет означать, что вы выполняете программу, ис­
пользуя версию Python 2, а не Python 3.
He l l o , world !
What i s your name ?
AlЬert
Traceback ( mo s t re cent ca l l l a st ) :
Fi l e " С : / Python2 7 / he l l o . ру " , l i ne 4 , in
myName
i nput ( )
F i l e " < s t r i ng> " , l i ne 1 , i n
Name E r r o r : name ' Albert ' i s n o t de f i n e d
=

Ошибка обусловлена вызовом функции input ( ) , которая ведет себя
по-разному в версиях Python 2 и 3. Прежде чем продолжать, установите вер­
сию Python 3, выполнив инструкции, которые приводились во введении.

Опrрыrие nроrраммы, коюрую вы со.хранили ранее
Закройте редактор файлов, щелкнув н а кнопке х в правом верхнем углу
окна. Чтобы повторно загрузить сохраненную программу, выберите пун­
кты меню Fi leq Qpen и укажите в отк рывшемся окне файл hello.py, после
чего щелкните на кнопке Открыть. Сохраненная вами программа hello.py
должна открыться в окне редактора файлов.

Как работает nроrрамма 11Hello, world!"
Каждая строка программы "Hello , world ! " - это инструкция , сообщаю­
щая интерпретатору Python точный порядок действий, которые он должен
выполнить. Компьютерная программа во многом напоминает кулинарный
рецепт: сделать сначала это, затем то, и так до тех пор, пока не будет вы­
полнен каждый пункт.
Все инструкции выполняются по очереди с самого начала программы
и далее вниз по списку. Выполнение начинается с первой строки кода, по­
степенно перемещаясь к концу. Однако порядок выполнения инструкций
может отклоняться от прямолинейного, о чем пойдет речь в главе 4.
П роанализируем программу "Hello, world ! " строка за строкой, чтобы по­
нять ее работу, начиная со строки 1 .

68

Глава З

Комменrарии
Любой текст, следующий за символом решетки ( # ) , является коммента­
рием.
1 . # Th i s program s a y s he l l o and a s ks f o r my name .

Комментарии предназначены не для компьютера, а для программиста.
Компьютер иг н орирует комментарии. Они напоминают вам о том , для
чего предназначена программа, или сообщают тем , кто может изучать ис­
ходный код, что именно он делает.
Обычно программисты помещают комментарии в начале программы в
качестве вступительного пояснения. Программа IDLE выделяет коммента­
рии красным цветом, чтобы сделать их более заметными. Иногда програм­
мисты помещают символ # в начале строки кода для того, чтобы временно
отключить ее при тестировании программы. На жаргоне это называется
"закомментировать" код, и подобная мера может оказаться полезной в тех
случаях, когда вы пытаетесь разобраться в том, почему программа не рабо­
тает. Впоследствии , когда вам вновь понадобится данная строка, вы сможе­
те удалить символ # .

Вывод 'оо6щениi дл1 nолuоваrел1
Следующие две команды отображают сообщения , предназначенные
для пользователя, с помощью функции p r i nt ( ) . Функция - это своего
рода мини-программа в вашей программе. Огромное преимущество функ­
ций состоит в том , что они скрывают свой код. Нам достаточно знать
лишь, что именно делает функция , а не как она это делает. Например , мы
знаем , что функция p r i nt ( ) отображает текст на экране , но как именно
это происходит, от нас скрыто. Вызов функции - это команда, которая
указывает программе на необходимость выполнить код внутри указанной
функции.
Строка 2 в файле hello.py - это вызов функции p r i nt ( ) (в скобках указа­
на строка, которая должна быть выведена на экран ) . Строка 3 - еще один
вызов функции p r i n t ( ) . На этот раз программа выводит текст ' What i s
your narne ? ' .
2.

3 .

p r i nt ( ' He l l o , wo r l d ! ' )
p r i n t ( ' What i s your name ? ' )

Мы добавляем скобки после имени функции, чтобы было понятно, что
речь идет о функции p r i n t ( ) , а не о переменной p r i nt . Круглые скобки
Строко в ый тип данн ых и напи са ние прогр а мм

69

говорят Python о том , что мы вызываем функцию, аналогично тому, как
кавычки в записи ' 4 2 ' говорят о том , что мы используем строку ' 4 2 ' , а не
число 4 2 .

Ввод данных nо.пьэоваrелем
Строка 4 содержит инструкцию присваивания , в которой в переменную
(myName ) записывается результат вызова функции i nput ( ) :
4.

myName = i nput ( )

Когда вызывается функция i nput ( ) , программа ожидает, что пользова­
тель введет какой-то текст и нажмет клавишу . Введенная пользова­
телем текстовая строка (имя пользователя ) становится строковым значе­
нием, сохраняемым в переменной myName .
Как и в случае выражений , результатом вызова функции является
единственное значение . Это значение называется возвращаемым. В дан­
ном случае возвращаемым значением функции i nput ( ) является введен­
ная пользователем строка, которой должно стать имя пользователя . Если
пользователь введет имя Albe r t , то вызов функции i nput ( ) вернет стро­
ку ' Albe r t ' .
В отличие от функции p r i n t ( ) , функция i nput ( ) не нуждается в аргу­
ментах, и именно поэтому мы вызываем ее, оставляя скобки пустыми.
Последняя строка кода программы hello.py - это еще один вызов функ­
ции p r i nt ( ) .
5 . p r i nt ( ' I t i s good t o meet you ,

'

+

myName )

В строке 5 внутри вызова функции p r i n t ( ) мы используем оператор +
для конкатенации (объединения ) строки ' I t i s good t o mee t you , ' и
строки, сохраненной в переменной myName , т.е. имени, введенного в про­
грамму пользователем . Благодаря этому программа приветствует пользова­
теля, обращаясь к нему по имени.

Завершение nроrраммы
После выполнения последней строки программа завершает работу. При
этом компьютер "забывает", т. е . удаляет из памяти, все переменные, в том
числе и строку, сохраненную в переменной myName . Если вы вновь запусти­
те программу и введете другое имя, то именно оно и будет выведено про­
граммой .

70

Глава З

He l l o , wor l d !
What is your narne ?
Zophie
It i s good to rne et you , Z ophi e

Помните, что компьютер выполняет строго те действия, которые вы
запрограммировали для него. В данной программе он запрашивает ваше
имя, позволяет ввести строку, а затем отображает на экране строку, кото­
рую вы только что ввели.
Однако компьютеры не способны к рассуждениям. Программе безраз­
лично, введете ли вы свое имя , имя другого человека или вообще какую-ли­
бо несуразицу. Вы можете ввести любой текст, какой только захотите, и
компьютер безропотно примет его.
He l l o , wo r l d !
What i s your narne ?
qreat
It i s good to rne e t you , great

Рез1Оме
Чтобы писать программы, необходимо владеть языком общения с ком­
пьютером. Вы уже узнали немного о том , как это делается , в главе 2, а сей­
час даже смогли объединить несколько инструкций на языке Python в го­
товую программу, которая запрашивает имя пользователя и приветствует
его.
В этой главе вы познакомились с несколькими новыми способами мани­
пулирования строками, такими как использование оператора + для конка­
тенации строк. Вы также научились применять индексы и срезы для созда­
ния новых строк на основе имеющихся .
Последующие программы в книге будут намного более сложными, од­
нако каждая их строка будет объясняться . У вас всегда есть возможность
ввести инструкцию в интерактивной оболочке, чтобы понять, что в дей­
ствительности она делает, прежде чем включать ее в программу.
В следующей главе мы приступим к написанию первой программы шиф­
рования на основе обратного шифра.

Строко в ый ти п данн ых и написание прогр а мм

71

Конт рольн ые вопросы

Ответы на контрольные воп росы при ведены в п риложении Б.

1.

Если выполн ить инструкцию п рисваивания spam
следующие строки кода?
spam + s pam
spam * 3

2.

+

=

' C a t s ' , то что выведут

spam

Что вы ведут следующие строки кода?
p r int ( " De a r Al i c e , \nHow a r e you ? \ n S i nc e re l y , \ nBob " )
p r i nt ( ' He l l o ' + ' He l l o ' )

3.

Если выполнить инструкцию п рисваивания spam
' Four s core a n d s even
ye a r s is e i gh t y s even y e a r s . ' , то что выведет каждая из следующих
строк кода?
=

p r i nt
p r i nt
p r i nt
p r i nt
p r i nt
print
p r i nt

( spam [ 5 ] )
( spam [ - 3 ] )
( spam [ 0 : 4 ] + spam [ 5 ] )
( sp arn [ - 3 : - 1 ] )
( spam [ : 1 0 ] )
( sparn [ - 5 : ] )
( spam [ : ] )

4.

В каком окне отображается подсказка >>>: в окне и нтерактивной оболочки
или в окне редактора файлов?

5.

Что вы ведет сл едующая строка кода?
# p r i nt ( ' He l l o , world ! ' )

72

Глава З

О Б РАТН Ы Й Ш И Ф Р
"Разве наше воспитание готовит нас 'К тахим
вращениям
? Разве наши захонъt им потахают ? Могли
из
бы они остатъся шзамеченнъtМu в стране. . . где все люди
добров олъно подглядъtвают друг за дfrугом ? "
Джейн Остин, "Нортенгерское аббатство "

Обратный шифр зашифровывает сооб­
щение, выводя его в обратном порядке , по­
этому зашифрованной версией текста "Hello ,
world ! " будет " ! dlrow , olleH". Чтобы дешифровать зашифрованное сообщение и получить исход­
ный текст, необходимо просто обратить его. Операции шиф­
рования и дешифрования выполняются с помощью одного и
того же алгоритма.

Од н ако такой обрат н ый ши ф р является слабым , и получить простой
текст из заши ф рованной версии не составит никакого труда. Одного взгля­
да н а заши ф рованный текст достаточно для того, чтобы определить, что
это всего лишь сообщение, записанное в обратном порядке:
. оге ътаmи'Чорn онжол сен тед-уб мав , тскет йы ннаворфишаз и отэ
ътох ,ремирпаН
В то же время объяснить, как работает программа обратного ши ф рова­
ия
н , будет н етруд н о, в связи с чем мы и используем обрат н ый ши ф р в каче­
стве ос н овы для создан ия н ашей первой программы ши ф рования .

В это м главе


.••

Фун кция l e n ( )
Цикл whi l e
Б улев ти п данн ых
О п ер ато ры сра вн е н ия
Условия
Блоки







Исход нь1й код nроrраммы Reverse Cipher
Выберите в IDLE пункты меню File� New Wi ndow для создания нового
окна редактора файлов. Введите приведенный ниже код (естественно , без
номеров строк) , сохраните его в файле reverseCipher.py и выполните, нажав
клавишу .
reverseCiphвr.py

1.
2.
3.
4.
5.

# Пр о грамма обра т н о г о шифр о в а ни я
# https : / / www . no s t a r ch . com/ c r a c k i ngcode s / ( B S D L i ce n s e d )
me s s age = ' Three can keep
t rans l at e d = ' '

а

s e cret , i f two o f them a r e dead . '

6.

7, i
l e n ( me s sage ) - 1
8 . whi l e i >= О :
9.
t rans l at e d
t ra n s l a t e d
10 .
i
i - 1
11 .
1 2 . p r i nt ( t rans l a t ed )
=

+

me s s age [ i ]

=

П робный эаnуск nроrраммы Reverse Cipher
Выполнив программу reverseCipher.py, вы должны получить следующий
результат:
. daed e r a meht fo owt f i

, terces а

p e e k nac e e rhT

Чтобы дешифровать это сообще н ие, скопируйте текст . daed e r a me h
t fo owt f i , t e rce s а p e e k nac e e rhT в буфер обмена, выделив сооб­
щение и нажав комбинацию клавиш (Windows/Linux) или
74

Глава 4

( macOS ) . Затем вставьте скопированный текст, используя комбинацию
клавиш (Windows/Linux) или (macOS) , в качестве строко­
вого значения, сохраняемого в переменной me s s ag e в строке 4. Проследи­
те за тем, чтобы при этом не были случайно удалены одинарные кавычки,
ограничивающие строку. Обновленная строка 4 должна выглядеть так (из­
менение выделено полужирным шрифтом ) :
4 . me s s age = 1 . daed era шeht fo owt fi , terces а peek nac eerhT 1

Теперь, когда вы запустите программу rnJerseCipher.py, она выведет исход­
ное сообщение:
Three сап keep а s e c re t , if two of t hem are dead .

Ввод комментариев и установка nеременных
Первые две строки программы reverseCipher.py это комментарии, опи­
сывающие назначение программы и содержащие адрес веб-сайта, на кото­
ром ее можно найти.
-

1 . # Программа обратного шифрова ни я
2 . # https : / / www . пo s t a rch . com/ crac kiпgcode s / ( BS D L i ceпsed )

Фраза B S D L i c e n s e d означает, что любой человек может свободно
копировать и изменять эту программу при условии, что будет указано ав­
торство исходной версии программы (в данном случае во второй строке
указан адрес веб-сайта книги) . Я хочу, чтобы исходный файл содержал эту
информацию. Тогда при загрузке программы из Интернета пользователи
будут знать, где находится ее источник. Они также будут знать, что это про­
грамма с открытым исходным кодом , а значит, ее можно свободно распро­
странять.
Строка 3 пустая , и Python игнорирует ее. В строке 4 хранится сообще­
ние, которое мы хотим зашифровать и сохранить в переменной me s s ag e .
-

4 . me s s a ge = 1 Three с а п keep а s e c re t , i f t w o o f t hem a r e dead . 1

Всякий раз, когда мы хотим зашифровать или дешифровать новое сооб­
щение, нам достаточно ввести его в строке 4.
Переменная t ra n s l a t ed в строке 5 это переменная, в которой про­
грамма сохраняет обращенную строку.
-

5 . t raпs l a t e d = 1 1

Обратный шифр

75

Вначале перемен н ая t r a n s l a t e d содержит пустую строку. (Не забывай­
те о том , что пустая строка представляет собой две одинарные кавычки, а
не одну двойную . )

Оnредеnение дnины строки
Строка 7 - инструкция присваиван ия , сохраняющая зн ачение в пере­
менной i .
7 . i = l e n ( me s s a g e ) - 1

Выражением , которое вычисляется и значе н ие которого присваивается
переменной, является l e n ( me s s a g e ) - 1 . Первая часть этого выраже н ия,
l e n ( me s s age ) , представляет собой вызов функции l e n ( ) , которая получа­
ет строковый аргумент, подобно функции p r i n t ( ) , и возвращает целочис­
ленное значение , равное количеству символов в строке (т.е. длину строки) .
В дан ном случае м ы передаем функции l e n ( ) переменную me s s ag e , поэ­
тому вызов l e n ( me s s a ge ) возвращает количество символов в сообщении,
сохран енном в me s s a g e .
Давайте поэкспериме н тируем с функцией l e n ( ) . Введите в интерактив­
ной оболочке следующие команды.
> > > len ( ' Hello ' )
5
> > > len ( ' ' )
о

> > > spam
' Al '
> > > len ( spam)
2
> > > len ( ' Hello , ' +
13
=

1

1

+ ' world ! ' )

Значения , возвращаемые функцией l e n ( ) , говорят нам о том, что стро­
ка ' H e l l o ' содержит пять символов, а пустая строка - нуль символов. Если
сохран ить строку ' Al ' в переме нн ой и передать эту перемен ную функции
l e n ( ) , то она вернет значение 2 . Если передать функции l e n ( ) выраже­
ние ' He l l o , ' + ' ' + ' wo r l d ! ' , то она вер н ет 1 3 . Это объяс н яется тем,
что значе н ием выражения ' He l l o , ' + ' ' + ' wo r l d ! ' является строка
' H e l l o , w o r l d ! ' , которая содержит 1 3 символов (пробел и восклицатель­
ный знак считаются символами) .
Теперь, когда вы понимаете, как работает фун кция l e n ( ) , вернемся к
строке 7 программы reverseCipher.py. В ней определяется индекс последнего
символа сообщения путем вычитания 1 из возвращаемого зн ачения вызова

76

Гл ава 4

l e n ( me s s age ) . Мы должны вычесть 1 , поскольку индексация ведется от
нуля, и , например, в строке ' He l l o ' индексы изменяются от О до 4. Полу­
ченное целочисленное значение сохраняется в переменной i .

З накомство с цикnом whi le
Строка 8 - это инструкция Python, н азываемая циклом whi l e .
8.

whi l e i >= О :

Цикл wh i l e состоит из четырех частей (рис. 4. 1 ) .
Ключевое слово while

L�

Условие

/

while i >= о :
translated
i

=

i

-

1

=

Двоеточие

translated

+

message [ 1 ]

Блок кода
Рис. 4 . J .

Составные части цикла

wh i l e

Условие - это выражение, используемое в инструкции whi l e . Блок кода
в цикле wh i l e будет выполняться до тех пор, пока удовлетворяется указан­
ное условие.
Чтобы понять, как работают циклы wh i l e , необходимо сначала узнать
кое-что о булевых значениях, операторах сравнения и блоках.

Булев rнп данных
Булев тип даннъtх имеет только два возможных значения: T rue (истина)
или Fa l s e (ложь) . Эти значения чувствительны к регистру (вы всегда долж­
ны использовать прописные буквы Т и F , оставляя все остальные строчны­
ми) . Они не являются строковыми значениями, поэтому заключать слова
T rue и Fa l s e в кавычки не следует.
Поработайте с булевыми значениями, введя следующие инструкции в
интерактивной оболочке.
>>> spam = True
> > > spam
True
> > > spam = Fal se

Обратн ый шифр

77

> > > sраш
Fal s e

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

Операторы tрааненп
Взгляните на выражение, указанное в строке 8 программы reverseCipher.frY
после ключевого слова whi l e .
8.

wh i l e i > = О :

Это выражение ( i >= О ) содержит два значения (переменная i и цело­
численное значение О ) , соединенные знаком >=, который является операто­
ром "больше чем или равно". Оператор >= один из операторов сравнения.
Мы используем операторы сравнения для того, чтобы сравнить два зна­
чения и получить ответ в виде булева значения T rue или Fa l s e . Операто­
ры сравнения приведены в табл. 4. 1 .
-

Таблица 4. 1 . Опе раторы сравнения

О ператор

Выnоnн •ема• оnераци•

<

Меньше чем

>

Бол ьше чем

=

Больше чем или равно
Р а вно

!=

Н е равно

Введите следующие выражения в интерактивной оболочке, чтобы уви­
деть, какие значения они возвращают.
»> о < 6
T rue
»> 6 < о
Fal s e
» > 50 < 1 0 . 5
Fal s e
>» 10 . 5 < 11 . З
T rue
» > 1 0 < 10
Fa l s e

78

Гл ава 4

Выражение О < 6 возвращает булево значение T rue , поскольку число О
меньше числа 6 . Но поскольку число 6 не меньше числа О , то выражение
6 < О возвращает Fa l s e . Выражение 5 0 < 1 0 . 5 равно Fa l s e , поскольку
5 0 не меньше, чем 1 0 , 5 . Выражение 1 0 < 1 1 . 3 в о з в ращ ает T ru e , п осколь­
ку 1 0 , 5 меньше, чем 1 1 , 3 .
Обратимся к выражению 1 0 < 1 0 . Оно равно Fa l s e , поскольку число
1 0 не меньше числа 1 0 . Эти числа строго ра вн ы (Если бы Алиса была того
же роста, что и Боб, то вы не могли бы сказать, что Алиса ниже Боба. Это
утверждение было бы ложным . )
Введите другие выражения, используя операторы = (больше чем или равно) .
.

>>> 10
T rue
>>> 1 0
True
>>> 10
Fa l s e
»> 20
T rue

= 2 0

Обратите внимание на то , что 1 0 всегда предшествуют знаку = .
Далее введите в интерактивной оболочке несколько выражений, в кото­
рых используются операторы сравнения == (равно) и ! = ( не равно) , чтобы
увидеть, как они работают.
>>> 10
10
T rue
»> 10
11
Fa l s e
> > > 1 1 == 1 0
Fa l s e
>>> 10 ! = 10
Fa l s e
>» 10 ! = 11
T rue
-

Для целых чисел эти операторы работают так, как ожидалось. С равн е
ние равных между собой целых чисел с по м о щ ь ю оператора == дает T rue ,
а неравных - F a l s e . При сравнении таких чисел с п о м о щью оператора ! =
мы получаем прямо противоположн ые результаты.

­

Обратны й шифр

79

Аналогичным образом работает и сравнение строк.
-

> > > ' Bello '
' Bello '
T rue
>>> ' Bello '
' GoodЬye '
Fa l s e
' ВELLO '
> > > ' Bello '
Fal s e
> > > ' GoodЬye ' ! = ' Bello '
T rue
=

=

Регистр букв имеет значение для Python, поэтому строковые значения, в
которых регистры букв совпадают не полностью , не являются одной и той
же строкой. Например, строки ' He l l o ' и ' HELLO ' разные, поэтому резуль­
татом их сравнения с помощью оператора == будет F a l s e .
Обратите внимание н а различие между оператором присваивания ( = ) и
оператором проверки равенства (== ) . Одиночный знак равенства исполь­
зуется для присваивания значения переменной, тогда как двойной ( = = ) для проверки равенства двух значений. Если вы спрашиваете Python , рав­
ны ли два значения , используйте оператор ==. Если же вы приказываете
Python установить для переменной определенное значение , то используй­
те оператор = .
В Python строковые и целочисленные значения всегда считаются раз­
ными и никогда не могут быть равны. Например, введите в интерактивной
оболочке следующие команды.
>>> 42
Fal s e
> > > 42
Fa l s e
>>> 10
T rue

=

-

=

' Bello '
' 42 '

10 . 0

Несмотря на кажущуюся схожесть, целое число 4 2 и строка ' 4 2 ' не счи­
таются равными, поскольку строка - это не то же самое, что число. В то же
время целые и вещественные числа могут быть равными, поскольку и то,
и то - числа.
Когда вы работаете с операторами сравнения , помните, что результатом
вычисления каждого выражения всегда будет значение T rue или Fa l s e .

80

Глава 4

&поrи
Бл01С это одна или несколько строк кода, сгруппированных пугем соз­
дания для них отступа одной и той же минимальной величины ( определяе­
мой количеством пробелов перед началом строки ) .
Первая строка блока выделяется отступом величиной в четыре пробе­
ла. Любая последующая строка с отступом не менее четырех пробелов яв­
ляется частью блока. Если строка выделяется дополнительными четырьмя
пробелами (так что общее количество пробелов в ее начале становится
равным восьми ) , то это означает начало нового блока внугри первого бло­
ка. Блок заканчивается , когда встречается строка кода с отступом той же
величины, что и перед началом блока.
Рассмотрим некий воображаемый код ( не имеет значения , что за код,
поскольку нас интересует лишь отступ каждой строки) . Чтобы упростить
подсчет количества пробелов в отступах, отобразим их в виде жирных точек.
-

1 . codecodecode
2.

З.
4.
5.

6.
7.

• • • • codecodecode
• •

" codecodecode

# О пробелов отступа
# 4 п робела отступа

# 4 пробела отступа

• • • • • • • • codecodecode # 8 пробелов отступа
• • • • codecodecode

# 4 пробела отступа

• • • • codecodecode

# 4 пробела отступа

8 . codecodecode

# О пробелов отступа

Как видите, в строке 1 отступы отсугствуют; таким образом, количество
пробелов перед строкой кода равно нулю. Однако отступ строки 2 равен
четырем пробелам . Поскольку этот отступ больше отступа предыдущей
строки, мы знаем, что здесь начинается новый блок. Отступ строки 3 так­
же равен четырем пробелам , откуда следует, что она является продолжени­
ем блока.
Строка 4 имеет еще больший отступ (восемь пробелов) , что означает
начало нового блока. Этот блок находится внугри другого блока. В Python
одни блоки могуг н аходиться в других блоках.
В строке 5 отступ уменьшился до четырех пробелов, указывая на окон­
чание блока, находящегося на предыдущей строке. Строка 4 является един­
ственной строкой этого блока. Поскольку строка 5 имеет тот же отступ ,
что и блок, занимающий строки 2 и 3 , она все еще является частью перво­
начального внешнего блока, хоть и не является частью блока, занимающе­
го строку 4.

Обрат ный шифр

81

Строка 6 пустая , поэтому мы игнорируем ее, так как она не оказывает
на блоки никакого влияния.
Отступ строки 7 равен четырем пробелам , поэтому мы знаем , что блок,
начавшийся в строке 2, продолжается и в строке 7.
Строка 8 имеет нулевой отступ , что меньше отступа предыдущей стро­
ки. Это уменьшение величины отступа говорит нам о том , что предыдущий
блок, который начался в строке 2, закончился .
В этом коде имеются два блока. Первый блок занимает строки 2-7. Второй блок состоит только из строки 4 (и находится внутри другого блока) .
-

П р и мечан и е

Величин а отсту пов, в ы деля ющих бл аки, не обязате.лъно должн а бы тъ крат­
ной четырем пробе.лам . Вы впр аве в ы биратъ для этого любое коли'Чество про­
бе.л ов, тогд а к ак исполъзов ание чет ырех пробе.л ов явл.яетrя всего лишъ обще­
при ня т ым согл ашением .

Инсrрукцп while
Рассмотрим полную инструкцию wh i l e , начинающуюся в строке 8 про­
граммы reverseCipher. ру.
8.

wh i l e i >= О :
9.
t rans l at e d
t ra n s l a t e d + me s s age [ i ]
10 .
l
i = i
11 .
1 2 . p r int ( t rans l at ed )
=

-

Инструкция whi l e сообщает Python о том , что сначала необходимо
проверить условие в строке 8, т.е. i >= О . Инструкцию wh i l e i >= О :
можно трактовать следующим образом: "Пока переменная i больше или
равна нулю, выполнять код следующего блока" . Если условие равно T ru e ,
программа входит в блок, следующий за инструкцией wh i l e . Проан ализи­
ровав отступы , вы увидите , что этот блок включает строки 9 и 1 0 . Дости�
нув конца блока, программа возвращается к инструкции whi l e в строке 8 и
вновь проверяет условие. Если оно по-прежнему равно True , выполнение
переходит в начало блока, и блок вновь выполняется .
Если условие цикла wh i l e равно Fa l s e , программа пропускает код бло­
ка, и выполнение передается первой строке, непосредственно следующей
за этим блоком (т.е. строке 1 2 ) .

82

Глава 4

11Н ращивание " trpo•и
а
Итак, сначала переменная i в строке 7 устанавливается равной длине
сообщения минус 1 , после чего цикл wh i l e в строке 8 продолжает выпол­
нение инструкций, входящих в состав следующего блока, до тех пор пока
условие i >= О не станет равно Fa l s e .
7 . i = len ( me s s age )
1
wh i l e i >= О :
9.
t ra n s l a t e d
t ra n s lated + me s s age [ i ]
1
i = i
10 .
11 .
1 2 . print ( t rans l ated )
-

8.

-

Строка 9 - этоинструкция присваивания, которая сохраняет значение в
переменной t r a n s l a t ed. Этим сохраняемым значением является текущее
значение переменной t r an s l a t ed, конкатенируемое с символом, позиция
которого в сообщении определяется индексом i . В результате строковое
значение, сохраняемое в переменной trans l a ted, "наращивается" по од­
ному символу за раз до тех пор, пока не будет зашифрована вся строка.
Строка 10 - это тоже инструкция присваивания, в соответствии с ко­
торой сначала из текущего значения i вычитается 1 (эта операция назы­
вается декрементом) , после чего полученный результат становится новым
значением i .
Далее следует строка 1 2 ( пустая строка 1 1 иг н орируется ) , н о посколь­
ку она имеет меньший (нулевой ) отступ, то Python знает, что это означает
завершение блока цикла whi l e . Поэтому, вместо того чтобы выполнить
строку 1 2, программа возвращается в строку 8, в которой вновь проверяет­
ся , соблюдается ли условие продолжения цикла. Если результат проверки
условия равен T rue , то это означает повторное выполнение инструкций
блока (строки 9 и 1 0 ) . Так будет продолжаться до тех пор, пока условие не
станет равно Fa l s e (т.е. значение i стало меньше О ) , и только в этом слу­
чае программа перейдет к первой строке, непосредственно следующей за
блоком (строка 1 2 ) .
Детально проанализируем работу цикла, чтобы понять, сколько раз бу­
дет выполнен код блока. Начальным значением переменной i является
индекс последнего символа сообщения, а начальным значением перемен­
ной trans l a ted - пycтaя строка. Затем в цикле к концу этой пустой стро­
ки присоединяется значение me s s age [ i ] (а это последний символ сообще­
ния, поскольку значением i является последний индекс) .
После этого значение i декрементируется (т.е. уменьшается) на 1 , а это
означает, что теперь значением me s s age [ i ] будет предпоследний символ
Обратн ый шифр

83

сообщения . Таким образом , по мере перемещения i как индекса от кон­
ца строки сообщения к ее началу строка me s s age [ i ] добавляется в конец
строки t rans l ated. Именно так и получается , что символы строки сооб­
щения сохраняются в переменной t rans l a t e d в обратном порядке. Когда
i в конечном счете станет равно - 1 (это произойдет, когда в сообщении
будет достигнут индекс о ) , условие цикла станет равно Fa l s e , после чего
выполнение перейдет к строке 1 2 .
1 2 . p r int ( t ra n s l a t e d )

В строке 12 (последняя в программе) мы выводим содержимое пере­
менной t r a n s l a t e d (т.е. строку ' . d a e d e r a me h t fo o w t f i , t e r c e s а p e e k
n a c e e rhT ' ) на экран . Благодаря этому пользователь сможет увидеть, что
собой представляет обращенная строка.
Если вам все еще не удается понять, каким образом код, выполняющийся
в цикле wh i l e , обращает строку, добавьте в блок цикла следующую новую
строку (выделена полужирным шрифтом ) .
8

wh i l e i > = О :
t ra n s l at e d = t ra n s l a t e d + me s s age [ i ]
print ( ' i iв ' , i , ' , 1118 & &aqe [ i ] i в ' , шes saqe [ i ] ,
' , tranвlated is ' , translated)
11 .
i
i - 1
12 .
1 3 . p r i nt ( t ra n s l a t e d )
.

9.
10 .

=

В строке 1 0 на экран выводятся значения i , me s s a g e [ i ] и t r a n s l a t e d
при каждом прохождении цикла (т.е. н а каждой его итерации ) . Н а этот
раз мы используем вместо конкатенации строк нечто новое. Запятые
сообщают функции p r i n t ( ) о том , что мы выводим шесть различных
элементов , поэтому функция добавляет пробелы между ними. Теперь, за­
пустив программу, вы сможете наблюдать за процессом "наращивания"
строки, хранящейся в переменной t r a n s l a t e d . Вывод будет выглядеть
примерно так.
i
i
i
i
i
i
i
i
i
i

is
is
is
is
is
is
is
is
is
is

84

48
me s s age [ i ]
4 7 , me s s age [ i ]
me s s age [ i ]
46
45
me s s a g e [ i ]
4 4 , me s s age [ i ]
43
me s s age [ i ]
42
me s s age [ i ]
41
me s s age [ i ]
40
me s s age [ i ]
39
me s s age [ i ]

Глава 4

is
is
is
is
is
is
is
is
is
is

, translated
ct , t r a n s l a t e d
а , t ra n s l a t e d
е , trans lated
d , trans lated
trans lated
е , trans lated
r , trans lated
а , translated
, t ra n s l a t e d

is
is
is
is
is
is
is
is
is
is

.d
. da
. dae
. ctaed
. daed
. ctaed
. ctaed
. daed
. daed

е
er
era
era

is т , t ra n s l a t e d i s . daed e r a т

i is 38

me s s ag e [ i ]

i is 37

me s s ag e [ i ]

is е ,

i is 36

me s s age [ i ]

i s h , t ra n s l ated is

. daed e r a meh

i is 35

me s s ag e [ i ]

is t ,

t ra n s l ated is

. daed e r a meht

t r a n s l a t e d i s . daed e r a те

i is 34

me s s age [ i ]

is

t r a n s l at e d is

. daed e r a meht

i is 33

me s s age [ i ]

is f

t ra n s l ated i s

. daed e r a meht f

i i s 32

me s s ag e [ i ]

i s о , t ra n s l ated i s . daed e r a meht fo

i is 3 1

me s s ag e [ i ]

is

t ra n s l a ted i s . daed e r a meht fo

i is 30

me s s age [ i ]

is о ,

t ra n s l ated is

i is 2 9

me s s a g e [ i ]

is w , t ra n s l ated i s . daed e r a meht fo ow

. daed e r a meht fo о

i is 28

me s s age [ i ]

is t , t ra n s l at e d is

. da e d e r a meht fo owt

i is 27

me s s ag e [ i ]

is

t ra n s l ated is

. daed era meht fo owt

i is 26

me s s ag e [ i ]

is f

t ra n s l ated is

. daed e r a me h t fo owt f

i is 25

me s s age [ i ]

is i ,

t ra n s l ated is

. daed e r a meht fo owt fi

i is 24

me s s a g e [ i ]

is

t ra n s l ated i s . daed e r a meht fo owt f i

i is 23

me s s age [ i ]

is ,

i is 22

me s s age [ i ]

i s t , t r a n s l ated i s

. daed e r a meht fo o w t fi , t

i is 21

me s s a g e [ i ] i s е , t ra n s l ated i s . daed e r a meht fo owt fi , t e

i is 20

me s s age [ i ]

i s r , t ra n s l ated is

. daed e r a meht fo owt

i is 19

me s s ag e [ i ]

is с ,

t r a n s l a t e d i s . daed e r a meht fo owt fi , t e r c

i is 18

me s s age [ i ]

i s е , t ra n s l ated i s

i is 17

me s s ag e [ i ]

is s ,

t r a n s l a ted i s . daed e r a meht fo o w t fi , t e rc e s

i is 16

me s s age [ i ]

is

t r a n s l a t e d i s . daed e r a meht fo o w t fi , t e r c e s

i is 15

me s s age [ i ]

i s а , t ra n s l ated i s

i is 14

me s s ag e [ i ]

is

i is 1 3

me s s age [ i ]

i is 12

me s s age [ i ]

i is 11
i is 10

, transl ated i s

. daed e r a m e h t fo o w t f i

fi , t e r

. daed e r a m e h t fo o w t fi , t e r c e

. daed e r a meht fo o w t f i , t e r c e s а

t ra n s l a t e d i s . daed e r a meht fo owt

fi , t e r c e s а

i s р , t ra n s l at e d is

. daed e r a meht fo owt

me s s age [ i ]

is е , t r a n s l ated is

. daed e ra meht fo owt fi , t e r c e s а рее

me s s age [ i ]

is k

t ra n s l ated i s . daed e r a meht fo owt

i is 9

me s s age [ i ]

is

i is 8

me s s age [ i ]

i s n , transl ated i s

i is 7

me s s a g e [ i ]

is а ,

i is 6

me s s age [ i ]

i i s 5 , me s s a g e [ i ]

fi , t e r c e s а р

i s е , t r a n s l a t e d i s . daed e r a meht fo owt fi , t e r c e s а ре

t r a n s l a t ed i s

fi , t e r c e s а p e e k

. daed e r a meh t fo o w t f i , t e r c e s а p e e k

. daed e r a m e h t fo owt f i , t e r c e s а pee k n

t r a n s l a t e d i s . da e d e r a meht fo owt

is с ,

fi , t e rces а pee k na

transl ated i s

fi

is

t r a n s l ated is

is е ,

. d aed e r a meh t fo owt

, t e rc e s а pee k nac

. da e d e r a meht fo owt f i , t e r c e s а p e e k n a c

t r a n s l a t e d i s . daed e r a me h t fo o w t f i , t e rces а pe e k nac е

i is 4

me s s a g e [ i ]

i is 3

mes s age [ i ]

i s е , transl ated i s

i is 2

message [ i ]

is r ,

t r a n s l a t ed i s

i is 1

me s s a g e [ i ]

is h

t r a n s l a t e d is

is т

. da e d e r a meht fo owt fi , t e r ces а pee k nac eerh

me s s a g e [ i ]

t r a n s l a ted i s

. da e d e r a me h t fo owt

i is О

. daed e r a meht fo owt

fi , t e r c e s а pe e k nac е е

. daed e r a m e h t fo o w t f i , t e r c e s а pee k nac e e r

fi , t e r c e s а pee k nac e e r h т

Строка вывода " i i s 4 8 , me s s a g e [ i ] i s . , t ra n s l a t e d is "
показывает, чему равны значения i , me s s age [ i ] и t ra n s l a t e d после до­
бавления строки me s s a g e [ i ] в конец строки t r a n s l a t ed, но до декремен­
тирования i . Как видите, при первом проходе через цикл значение i уста­
навливается равным 4 8 , поэтому mе s s аgе [ i ] ( т.е. me s s age [ 4 8 ] ) представ­
ляет собой строку ' . ' . Переменная t rans l at e d вначале содержит пустую
строку, но после присоединения к ней значения me s s a g e [ i ] в строке 9 она
становится равна ' . ' .
Наследующей итерации цикла выводится текст " i i s 4 7 , me s s age [ i ]
i s d , t ran s l at e d i s . d " . Мы видим, что значение i было декремен­
тировано с 4 8 до 4 7 , поэтому теперь me s s age [ i ] - это me s s age [ 4 7 ] , т.е.
Об р атный шифр

85

строка ' d ' (вторая буква ' d ' в слове ' dead ' ) . Эта буква ' d ' добавляется в
конец строки, хранящейся в переменной t ra n s l a t ed, которая теперь со­
держит значение ' . d ' .
Мы имеем возможность наглядно убедиться в том , как строка, храняща­
яся в переменной t r an s l ated, медленно "наращивается" , постепенно пре­
вращаясь из пустой строки в обращенную строку сообщения.

Усовер ш енствование nроrраммы за с ч ет
исnоnьэования функции input ( )
Все программы, приведенные в книге, построены таким образом, что
как шифруемые, так и дешифруемые строки вводятся непосредственно в
исходном коде с помощью инструкций присваивания. Такой подход удобен
в процессе разработки программы, но вы ведь не думаете , что пользова­
телям будет удобно каждый раз самостоятельно вносить изменения в ис­
ходный код? Чтобы упростить применение программы и сделать ее более
удобной для пользователей , можете изменить соответствующие инструк­
ции присваивания , включив в них вызов функции i nput ( ) . При этом мож­
но включить в функцию i nput ( ) подсказку, предлагающую пользователю
ввести строку, подлежащую шифрованию. Например, строку 4 программы
reverseCipher.py можно заменить следующим образом:
4 . rne s s age = input ( Enter шes saqe :


•)

Когда вы запустите программу, она выведет на экран эту подсказку и бу­
дет ожидать, пока пользователь не введет сообщение. Введенное пользова­
телем сообщение станет тем строковым значением, которое сохранится в
переменной me s s ag e . Теперь, выполняя программу, вы сможете ввести лю­
бую строку, которая будет зашифрована программой и выведена на экран ,
как показано ниже.
E nt e r me s sage : Bello , world !
! dl row , o l l eH

Реэ1Оме
Мы только что завершили создание второй программы, которая преоб­
разует предоставленную ей строку в новую строку, используя такие прие­
мы, рассмотренные в главе 3, как индексирование и конкатенация строк.
В этой программе важную роль играет функция l e n ( ) , которая получает

86

Глава 4

строковый аргумент и возвращает целое число, показывающее, сколько
символов содержит данная строка.
Вы также узнали о булевом типе данных, который поддерживает только
два возможных значения : T rue и Fa l s e . Операторы сравнения == , ! = , < ,
>, = могут сравнивать два выражения и возвращать результат в виде
булева значения.
Условия - это выражения , содержащие операторы сравнения и воз­
вращающие булевы значения . Они используются в циклах whi l e , кото­
рые выполняют блок кода, следующий за инструкцией whi l e , до тех пор
пока условие не станет равно Fa l s e . Блок состоит из строк кода, выде­
ленных отступом одной и той же величины , и может включать вложен­
ные блоки .
Теперь, когда вы уже знаете достаточно много о том , как манипулиро­
вать текстом, мы можем приступать к созданию программ, способных вза­
имодействовать с пользователем . Это очень важно, поскольку текст - ос­
новной вид информации, позволяющий пользователю взаимодействовать
с компьютером.

Кон тр ольн ые воп р осы

Ответы на контрольные вопросы при ведены в п риложении Б.

1.

Что выведет на экра н следующая инструкция ?
p r i nt { l en { ' He l l o ' ) + l en { ' He l l o ' ) )

2.

Что выведет на экра н следующи й код?
i = о
wh i l e i < 3 :
p r i nt { ' He l l o ' )
i = i + 1

3.

А что вы ведет такой код?
i = о
spam = ' He l l o '
wh i l e i < 5 :
spam = s pam + spam [ i ]
i = i + 1
print { s pam )

О б ратный шифр

87

4.

А та кой?
i = о
wh i l e i < 4 :
wh i l e i < 6 :
i = i + 2
print ( i )

88

Глава 4

Ш И Ф Р Ц ЕЗАРЯ
"Болъиюй брсип следит за тобой ".
Джо-рдж

Оруэлл, "1 984 "

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

В это м гАаве ...







Инструкция imp o r t
Константы
Цикл f o r
И н струкции i f, else и el i f
Операторы in и n ot in
Строковый метод f ind ()

Исходнь1й код nроrраммы Caes ar Cipher
Введите приведенный ниже код в редакторе файлов и сохраните его в
файле caesarCiphe:r.py. Затем загрузите модуль pype:rclip.py, доступный по адре­
су https : / / i nventw i t hpytho n . com/pype r c l ip . py, и поместите его в
тот же каталог, в котором находится файл caesarCipher.py. Данный модуль
будет импортироваться программой caesarCipher.py; подробнее об этом мы
поговорим в разделе "Импорт модулей и установка переменных".
Когда файлы будут готовы , нажмите клавишу , чтобы запустить про­
грамму. Если столкнетесь с ошибками или другими проблемами, сравните
свой код с кодом , приведенным в книге, воспользовавшись онлайн-утили­
той Diff пo адресу h t t p s : / / i nventwi thpython . com / c r a c king / di f f / .
caesarCipher.py

1.
2.
3.
4.
5.
6.
7.
8.
9.
10 .
11 .
12 .
13.
14 .
15 .
16.

#

#

Шифр Це заря
https : / /www . no s t a rch . com / c r a c k i ngcode s / ( BS D L i ce n s e d )

import pype r c l ip
# Строка , подлежащая шифрованию/дешифрованию
me s s age
' Th i s i s my se cret me s s age . '
=

#

Ключ шифров а ни я / дешифрования
key = 1 3
# Установка режима работы : шифрование или дешифрование
mode = ' encrypt '
# зада т ь ' encrypt ' или ' de c r ypt '
# Все символы , кот орые могут быть зашифрованы
SYMBOLS = ' ABCDE FGH I JKLMNOP Q RSTUVWXY Z abcde fgh i j klmnopqrst uvwxyz
1 2 3 4 5 67 8 90 ! ? . 1

17 .
1 8 . # Переменная , храняща я зашифрованную/ дешифрованную форму сообщения
1 9 . t ra n s l a t e d = 1 1
20 .
2 1 . for s ymЬ o l in me s s age :
22 .
# Примечание : шифров а т ь / дешифровать можно толь ко симв олы ,
включе нные в строку SYMBOLS
23 .
i f s ymЬo l in S YMBOLS :
24 .
s ymЬo l i ndex
SYMBOLS . f ind ( s ymЬo l )
25 .
26.
# Выполнит ь шифро в а ние / дешифрование
27 .
if mode = = ' e ncrypt ' :
28 .
t ra n s l at e d i ndex
s ymЬ o l i ndex + ke y
29.
e l i f mode
' de c r ypt ' :
30 .
t raпs l at e d i ndex
s ymЬ o l i ndex - key
=

=

==

=

90

Гла ва 5

31 .
# Обработать " завертывание " , е сли необходимо
32 .
i f t rans l a t e d i ndex > = l e n ( SYMBOLS } :
33 .
t rans l a t e d i ndex = t r a n s l a t e d ! ndex - l e n ( SYMBOL S )
34 .
e l i f t ra n s l a t ed! ndex < О :
35 .
t ra n s l a t e d !ndex = t rans l a t e d i ndex + l e n ( SYMBOL S }
36 .
37 .
t ra n s l a t e d = t ra n s l a t e d + SYMBOLS [ t rans l a t e d ! ndex ]
38 .
39 .
e l se :
# Присоединить символ бе з шифрования/ дешифрования
40 .
t rans l at e d = t r a n s l a t e d + s ymЬ o l
41.
42 .
4 3 . # Вывод преобра зованной строки
4 4 . p r i nt ( t ra n s l a t e d }
4 5 . p ype r c l ip . copy ( t rans l a t e d }

Пример выnоnнени• nроrраммь1 Caesar Cipher
Выполнив программу caesarCipher.py, вы должны получить следующий ре­
зультат:
guv 6 Jv 6Jz ! J б r p 5 r 7 J z r 6 6nt rM

Это строка 1 Thi s i s my s e c r e t me s s age . 1 , зашифрованная шифром
Цезаря с ключом 1 3. Программа шифрования , которую вы только что вы­
полнили, автоматически копирует зашифрованную строку в буфер обмена,
чтобы ее можно было вставить в электронное письмо или текстовый файл.
Таким образом , вы сможете легко переслать зашифрованный текст друго­
му человеку.
При выполнении программы возможно появление следующего сообще­
ния об ошибке.
T raceba c k ( mo s t recent c a l l l a st } :
Fi l e " C : \ ca e s a rC iphe r . py " , l ine 4 , i n
impo rt pype r c l ip
Import E r r o r : No modu l e named pype r c l ip

Если вы столкнетесь с таким сообщением, то это, вероятно, будет озна­
чать, что модуль pyperclip.py не был помещен в нужную папку. Если же файл
pyperclip.py действительно находится в одной папке с файлом caesarCipher.py,
но модуль почему-то не работает, закомментируйте код в строках 4 и 45 (со­
держащих ссылки на модуль pype r c l ip ) программы caesarCipher.py, поме­
стив перед ними символ # . Это заставит Python игнорировать код, который
зависит от модуля pyperclip.py, и позволит программе успешно выполниться.
Шифр Цеза ря

91

Учтите, что если вы закомментируете указанные строки, то зашифрован­
ный или дешифрованный текст не будет скопирован в буфер обмена в кон­
це программы. Точно так же вы сможете отключать код модуля pype r c l i p
в программах, с которыми м ы будем работать в последующих главах.
Чтобы дешифровать сообщение, достаточно вставить выведенный текст
в качестве нового значения , сохраняемого в переменной me s s a g e в стро­
ке 7. Также необходимо изменить инструкцию присваивания в строке 1 3
таким образом , чтобы в переменной mo de сохранилась строка ' de c r yp t ' .
6.
7.
8.
9.
10 .
11 .
12 .
13 .

# Строка , подлежа щ ая шифрованию/ дешифров анию
me s s a g e
' guv6Jv6Jz ! J6rp5r7Jzr6 6ntrM '
=

#

Ключ шифровани я / дешифрования
key
13
=

# Установка режима работы : шифрование или дешифрование
mode
' decrypt '
# зада т ь ' e ncrypt ' или ' de c r ypt '
=

Теперь, выполнив программу, вы получите следующий результат:
T h i s i s my s e c ret me s s age .

Импорт модуnей и установка переменных
Несмотря на то что Python включает множество встроенных функций,
некоторые функции хранятся в отдельных программах, называемых моду­
лями. Модуль - это программа Python, содержащая дополнительные функ­
ции , которые могут использоваться в других программах. Модули импор­
тируются с помощью инструкции impo rt , в которой указывается имя им­
портируемого модуля.
Инструкция импорта содержится в строке 4.
1 . # Шифр Цезаря
2 . # https : / / www . n o s t a rch . com/ c r a c ki ngcode s / ( BS D L i c e n s e d )
3.
4 . import pype r c l i p

В данном случае мы импортируем модуль pype r c l ip, что дает нам
возможность впоследствии вызвать в программе функцию pype r c l i p .
сору ( ) , которая будет автоматически копировать строки в буфер обмена,
откуда их будет удобно вставлять в другие программы.
В нескольких следующих строках программы caesarCipher.py устанавлива­
ются значения трех переменных.
92

Глава 5

6.
7.
8.
9.
10 .
11 .
12 .
13 .

# Строка , подлежащая шифро ванию/дешифрова нию
me s s a ge = ' Th i s i s my s e cret me s s age . '
#

Ключ шифров а ния / дешифрования
key = 1 3

# Установка режима работы : шифрование или дешифрование
mode = ' encrypt '
# задать ' encrypt ' или ' de c ryp t '

В переменной me s s age сохраняется строка, которую необходимо за­
шифровать или дешифровать, а в переменной key - целочисленное зна­
чение ключа шифрования. В перемен ной mode хранится строка, опреде­
ляющая режим работы программы: строка ' encrypt ' задает шифрование
сообщения , строка ' de c r ypt ' - дешифрование.

Ко нстанты и переменные
Констан ты - это переменные, значения которых не должны изменять­
ся во время работы программы. Например, нашей программе шифрова­
н ия нужна строка, содержащая все допустимые символы, которые могуг
быть зашифрованы с помощью шифра Цезаря . Поскольку данная строка
не должна изменяться, мы сохраняем ее в константе SYMBOL S в строке 1 6 .
1 5 . # В с е симв олы , кот орые могут быть зашифров аны
1 6 . S YMBOLS = ' ABCDEFGHI JKLMNOPQRS TUVWXYZ abcde fgh i j klmnopq r s t uvwxy z
1 2 3 4 5 67 8 90 ! ? .
1

Символ - это общий термин, употребляемый в криптографии по отноше­
нию к одиночному знаку, который можно зашифровать или дешифровать.
Сuмволън ы й набор - это совокупность всех возможных символов, которые
можно шифровать или дешифровать с помощью определенного шифра.
Поскольку символьный набор используется в программе неоднократно и
мы не хотим вводить его заново каждый раз, когда он понадобится (это
чревато ошибками) , мы сохраняем весь символь н ый набор в константе
SYMBOL S , которую задаем один раз.
Обратите вниман ие на то, что все буквы в имени SYМBOLS - прописные,
что является общепринятым соглашением для имен констант. И хотя ни­
что не мешает нам изменить значение переменной SYMBOL S , использова­
ние в ее имени исключительно прописных букв служит напоминанием о
том , что ее значение не должно меняться.
Как и в случае любого другого общепринятого соглашения, мы не обяза­
н ы строго следовать данному правилу. Однако его соблюдение упрощает
Шифр Цезар я

93

другим программистам понимание того, как используются подобные пере­
менные. (Это поможет даже вам, если вы вернетесь к работе с программой
после длительного перерыва. )
В строке 1 9 программа сохраняет пустую строку в переменной
t ra n s l ated, которая впоследствии будет использоваться для хранения за­
шифрованного или дешифрованного сообщения .
1 8 . # Сохранить зашифрованную/дешифрованную форму сообщ ения
1 9 . t rans l a t ed = ' '

Как и в случае программы обратного шифрования, рассмотренной в гла­
ве 4, к моменту завершения работы программы переменная t r a n s l ated бу­
дет содержать полное зашифрованное (или дешифрованное) сообщение.
Но ее начальным значением является пустая строка.

Цикn fоr
В строке 2 1 мы начинаем цикл f o r .
2 1 . f o r s ymЬ o l i n me s sage :

Вспомните, что выполнение цикла whi l e продолжается до тех пор, пока
условие цикла равно T rue . Цикл f o r имеет несколько иное назначение и
не содержит условия , в отличие от цикла whi l e . Вместо этого он проходит
по строке или набору значений. На рис. 5. 1 представлены шесть элементов
этого цикла.
Имя переменной
"""'" """'' •�

'�

""� /�

""-�

Ключевое слово in

for symbol in message :
инструкции

ро
содержащая строку
С. о > > ' hello hello ' . find ( ' e ' )
1

Строковый метод f i nd ( ) напоминает более специализированную вер­
сию оператора i n . Он сообщает не только о том, содержится ли одна стро­
ка в другой , но и том, начиная с какой именно позиции она встречается.

Шифрование и деwифрование симвоnов
Теперь, когда мы разобрались в том , как работают инструкции i f , е 1 i f
и e l s e , а также оператор i n и строковый метод f i nd ( ) , вам будет проще
понять, как работает оставшаяся часть программы шифрования на основе
шифра Цезаря.
Программа способна шифровать и дешифровать только символы, вхо­
дящие в состав заданного символьного набора.
23.
24 .

i f s ymЬ o l i n SYMBOLS :
s ymЬ o l i ndex = SYMBOLS . f ind ( s ymЬo l )

Поэтому, прежде чем выполнить код в строке 24, программа должна
определить, содержится ли символ в поддерживаемом наборе. После этого
она определяет индекс строки S YMBOLS , начиная с которого располагается
строка s ymЬo l . Индекс, возвращаемый методом f i n d ( ) , сохраняется в пе­
ременной s ymЬo l i nd e x .
Располагая индексом текущего символа, сохраненным в переменной
s ymЬ o l i nd e x , мы можем применить к нему операции шифрования/
дешифрования. Шифр Цезаря добавляет значение ключа к индексу симво­
ла при его шифровании или вычитает значение ключа из индекса симво­
ла при его дешифровании. Полученное значение сохраняется в перемен­
ной t r a n s l a t e d i nd e x , поскольку оно будет служить индексом в строке
SYМBOLS , соответствующим преобразованному символу.
26.
27 .
28 .
29.
30 .

#

Выполнит ь шифровани е / дешифров ание
if mode == ' encrypt ' :
t ra n s l a t e d i ndex = s ymЬo l i ndex + key
e l i f mode == ' de c rypt ' :
t ra n s l a t e d i ndex = s ymЬo l i ndex - key

Ш ифр Цезаря

1 01

Переменная mode содержит строку, которая сообщает программе, что
именно необходимо сделать с сообщением: зашифровать или дешифро­
вать. Если переменная равна ' e n c rypt ' , то условие в строке 27 равно T rue ,
что приведет к выполнению строки 28, которая прибавит значение ключа
к значению s ymbo l i ndex (блок, следующий за инструкцией e l i f , будет
пропущен) . В противном случае, если переменная mode равна ' de c r ypt ' ,
будет выполнена строка 30, которая вычтет значение ключа.

06pa6onra 6Jаверrыван•• " fммвопыоrо на6ора
Когда мы реализовывали шифр Цезаря с помощью карандаша и бумаги в
главе 1 , то в некоторых случаях добавление или вычитание ключа приводи­
ло к значениям, превышающим или равным размеру символьного набора
или даже меньшим нуля. В подобных случаях мы должны добавлять или
вычитать длину символьного набора, чтобы автоматически возвращать­
ся к началу или концу символьного набора. С этой целью мы используем
вызов l e n ( SYМBOL S ) , который возвращает значение 6 6 , т.е. длину строки
S YМBOLS. Обработка "завертывания" осуществляется в строках 33-36.
#

Обработать " завертывание " , е сли необходимо
i f t r a n s l a t e d i ndex >= l e n { S YMBOLS ) :
t ra n s l a t e d i ndex = t ra n s l a t e d i ndex - l e n ( S YМBOLS )
e l i f t ran s l at e d i ndex < О :
t ra n s l a t e d i ndex = t ra n s l a t e d i ndex + l e n { SYMBOL S )

32 .
33 .
34 .
35 .
36 .

Если значение переменной t r a n s l a t e d i ndex превышает или равно 6 6 ,
то условие в строке 3 3 оказывается истинным, и выполняется строка 34
(тогда как инструкция е 1 i f в строке 35 пропускается) . Вычитание длины
строки SYMBOLS из значения t ra n s l a t e d i ndex переводит индекс пере­
менной обратно в начало строки S YМBOLS. В противном случае Python про­
веряет, не меньше ли нуля значение переменной t r a n s l a t e d i ndex . Если
это так, то выполняется строка 36, и переменная t ra n s l a t e d i ndex "завер­
тывается" вокруг конца строки SYМBOL S .
Возможно, вас удивляет, почему м ы использовали вызов l e n ( S YМBOLS ) ,
а не непосредственно значение 6 6 . Это позволяет нам добавлять или уда­
лять символы из набора SYМBOLS , не нарушая работу остальной части про­
граммы.
Теперь, когда в переменной t ra n s l a t e d i ndex хранится индекс преоб­
разованного символа, мы можем получить сам символ с помощью выра­
же н ия S YMBOLS [ t ra n s l a t e d i ndex ] . В строке 38 этот зашифрованный/
дешифрованный символ добавляется в конец строки t ra n s l a t e d путем
конкатенации строк:
1 02

Глава 5

38 .

t ra n s l a t e d = t ra n s l a t e d

+

SYMBOLS [ t rans l a t e d i ndex ]

В конечном счете строка t r an s l at e d будет содержать полное зашифро­
ванное или дешифрованное сообщение.

06pa6or•a символо1, не вuюченных в символьный на6ор
Строка me s s age может содержать символы , отсутствующие в строке
SYMBOLS . Эти символы не входят в символьный набор программы шифро­
вания и не могут быть зашифрованы или дешифрованы. Вместо этого они
будут непосредственно присоединяться к строке t rans l ated, что и проис­
ходит в строках 39-4 1 .
39.
40.
41.

else :
# Присоединит ь символ бе з шифровани я / дешифрования
t ra n s l a t e d = t rans l a t e d + s ymЬol

Инструкция e l s e в строке 39 выделена отступом величиной четыре
пробела. Взглянув на расположенные выше строки, вы увидите, что эта
строка имеет тот же отступ , что и строка 23. Несмотря на то что между эти­
ми двумя инструкциями i f и e l s e находится большое количество строк
кода, весь этот код принадлежит к одному блоку.
Если условие инструкции i f в строке 23 равно Fa l s e , соответствующий
блок игнорируется, и выполнение передается блоку инструкции e l s e , на­
чинающемуся в строке 4 1 . Этот блок состоит всего лишь из одной инструк­
ции, в которой символ присоединяется без какого-либо изменения в конец
строки t rans l a t ed. В результате символы, не входящие в заданный сим­
вольный набор, такие как ' % ' или ' ( ' , добавляются в строку t ra n s l a t ed,
не подвергаясь шифрованию или дешифрованию.

Вывод и коnирование nреобраэованной строки
Строка 43 не имеет отступа, а это означает, что она является первой
строкой, следующей за блоком , который начинается в строке 2 1 (блок цик­
ла f o r ) . К тому времени, когда выполнение программы достигнет стро­
ки 44, цикл обработает все символы строки сообщения, т.е. зашифрует
( или дешифрует) каждый из них и добавит в строку t r an s l ated.
4 3 . # Вывод пре обра зованной строки
4 4 . p r i nt ( t r ans l a t e d )
4 5 . pype rcl ip . copy ( t rans l a t e d )

Ши фр Цезар я

1 03

В строке 44 преобразованная строка выводится на экран с помощью
функции p r i n t ( ) . Обратите внимание на то, что это единственный вызов
функции p r i nt ( ) во всей программе. Компьютер выполняет большой объ­
ем работы , шифруя каждую букву сообщения , а также обрабатывая "завер­
тывание" строк и небуквенные символы. Однако пользователь не должен
всего этого знать, ему достаточно видеть только окончательную строку, со­
храненную в переменной t ran s l a t ed.
В строке 45 вызывается функция сору ( ) , которая получает один стро­
ковый аргумент и копирует его в буфер обмена. Поскольку сору ( ) - это
функция из модуля pype r c l i p , мы должны сообщить об этом Python ,
поместив префикс pype r c l i p . перед именем функции. Если вы введете
с ору ( t r an s l a t e d ) вместо pype r c l ip . с ору ( t ran s l a t e d ) , то Python вы­
даст сообщение об ошибке, поскольку не сможет найти такую функцию.
Сообщение об ошибке появится и в том случае, если вы забудете исполь­
зовать инструкцию import pype r c l ip (строка 4) , прежде чем пытаться
выполнить вызов pype rc l i p . сору ( ) .
Мы полностью проанализировали программу шифрования на основе
шифра Цезаря. Когда вы запустите ее, обратите внимание на то, что ком­
пьютер проделывает всю сложную работу менее чем за секунду. Даже если
вы сохраните в переменной me s s age очень длинную строку, компьютер
справится с шифрованием или дешифрованием сообщения не более чем
за пару секунд. Сравните это с теми несколькими минутами, которые пона­
добились бы вам для того, чтобы воспользоваться шифровальным диском.
Но наша программа еще и автоматически копирует зашифрованный текст
в буфер обмена, чтобы пользователь мог просто вставить его в электрон­
ное письмо и отправить кому-нибудь.

Ш ифрование друrих симвоnов
Одной из проблем реализованной нами программы шифрования явля­
ется то, что она не умеет шифровать символы , не входящие в заданный
символьный набор. Например, если вы зашифруете строку ' Ве sure t o
b r i ng t h e $ $ $ . ' с помощью ключа 20, то сообщение будет преобразовано
в строку ' VyQ ?A ! yQ . 9Qv ! 3 8 1 Q . 2 yQ$ $ $ T ' . Здесь не скрывается тот факт,
что в сообщении упоминаются доллары ( $ $ $ } . Однако программу можно
видоизменить таким образом , чтобы она справлялась и с другими симво­
лами.
Изменив строку, сохраняемую в константе SYMBOLS , и включив в нее
дополнительные символы, мы добьемся того, что программа будет шиф­
ровать эти символы тоже, поскольку теперь вычисление условия i f
1 04

Глава 5

s ymb o l i n S YMBOLS в строке 23 будет давать результат T ru e . Значением
s ymbo l i ndex будет индекс символа s ymb o l в этой новой, увеличенной

строке S YMBOL S . Операция "завертывания" должна будет добавлять и вы­
читать количество символов, соответствующее новой строке, но это уже
учтено, поскольку мы использовали вызов l e n ( S YМBOL S ) , а не ввели 6 6
непосредственно в коде.
В частности, строку 1 6 можно переписать так:
SYMBOLS = ' AВCDE FGH I JKLМNOPQRSTUVWXY Z abcde f gh i j klmnopq r s tuvwx y z l 2 3 4 5 6 7 8 9 0
! ? , - @ J1 $ % л & * ( } -+ - = [ ] { } 1 ; : < > ' / '


Имейте в виду, что, для того чтобы все это работало, шифрование и де­
шифрование сообщения должны выполняться с использованием одного и
того же символьного набора.

Реэ1Оме
Вы уже прочитали несколько глав книги и изучили ряд концепций
программирования , но теперь у вас есть программа, которая реализует
тайный шифр. И что более важно, вы понимаете, как работает такая про­
грамма.
В Python модули - это программы , которые содержат полезные функ­
ции. Чтобы задействовать расширенные функции, сначала их нужно им­
портировать с помощью инструкции imp o r t . Чтобы вызвать функцию, со­
держащуюся в модуле, поместите имя модуля вместе с оператором-точкой
перед именем функции: модуль . функция ( ) .
В соответствии с общепринятым соглашением константы записывают­
ся в коде с использованием прописных букв. Предполагается, что значе­
ния этих переменных не должны изменяться программой (хотя ничто не
запрещает программистам это делать) . Константы полезны тем, что позво­
ляют присваивать имена конкретным значениям.
Методы - это функции, присоединяемые к значению определенного
типа данных. Строковый метод f i nd ( ) возвращает целое число, указыва­
ющее позицию переданного методу строкового аргумента в строке, для ко­
торой он вызывается.
Вы изучили ряд программных конструкций, позволяющих упрамять
тем , какие строки кода и сколько раз должны быть выполнены. Цикл f o r
проходит по всем символам строкового значения , записывая в перемен­
ную цикла каждый следующий символ в качестве нового значения на оче­
редной итерации. Инструкции i f , e l i f и e l s e выполняют блоки кода на
основании того, какое значение имеет заданное условие: T rue или Fa l s e .
Шифр Цезаря

1 05

Операторы i n и n o t i n проверяют, содержится ли одна строка в дру­
гой, возвращая соответственно значение T rue или Fa l s e .
Знание программирования позволит вам выполнять шифрование или
дешифрование сообщений с помощью шифра Цезаря, используя язык, по­
нятный компьютеру. И коль скоро компьютер понимает, какие действия
он должен выполнить, он делает это гораздо быстрее человека, причем не
допуская ошибок (если только ошибки не вкрались в саму программу) . Не­
смотря на необычайную полезность шифра Цезаря, оказывается , что его
нетрудно взломать тому, кто знаком с программированием. В главе 6 вы
воспользуетесь полученными навыками, чтобы написать программу взло­
ма ш ифра Цезаря. Это позволит вам читать тексты, зашифрованные други­
ми людьми. Итак, нам предстоит научиться взламывать шифры !

Контр ольны е воп р ос ы

Отв ет ы на контрольные вопросы п риведены в приложении Б.
1.

Исполь зу йте про гра мму caesarCipher.py для шифрования следующих сооб­
щени й с помощью указанн ых ключей.
А.

' " You can s how Ы а сk is wh i t e Ь у a r g ument , " s a i d
" b u t you w i l l neve r convi nce me . " ' ( кл юч 8 ) .

Filby,

Б.
2.

З.

4.

' 1 2 3 4 5 6 7 8 9 0 ' ( ключ 2 1 ) .

Исполь зуйте п ро г рамму caesarCipher. py для дешифро ва н ия следующих за­
ши фрованны х сообщений с помощью указанных ключей.
А.

' Kv ? uqwp f u ? rncwu kdn g ? gp qw i j B ' (ключ 2 ) .

Б.

' XC B S w 8 8 S 1 8 A l S 2 S B 4 1 S E . 8 z S EwAS 5 0 D 5 A 5 x 8 1 V ' ( ключ 2 2 ) .

Какая инструкция Pythoп будет импортировать модуль waterme/on.py?
Что будут вы водить на экран следу ющие фрагме нты кода?
А.
spam = ' f o o '
for i i n spam :
s pam = s pam + i
p r i nt ( spam)

1 06

Глава 5

Б.
if 10 < 5 :
p r i nt ( ' He l l o ' )
e l i f Fa l s e :
p r i nt ( ' Al i ce ' )
elif 5 ! = 5 :
p r i nt ( ' ВоЬ ' )
else :

p r i nt ( ' GoodЬye ' )

в.
print ( ' f ' not i n ' foo ' )

г.
print ( ' foo ' in ' f ' )

Д.
p r i n t ( ' he l l o ' . find ( 1 00 1 ) )

Шифр Цезар я

1 07

ВЗЛО М Ш И Ф РА Ц Е ЗАР Я М ЕТОДО М
Г Р У БОЙ СИЛЫ
"Арабскш ученые. . . изобрели криптоанализ, науку
расшифровки сообщ ений без знания к.11:ю1ш ".
Сайм он Сингх, "Книга шифров "

Мы можем взломать шифр Цезаря , ис­
пользуя криптоаналитическую методику под
названием метод грубой силъt (brute force) .
Он предполагает дешифрование сообщения
путем полного перебора всех · возможных значе­
ний ключей. Ничто не мешает криптоаналитику взять какой-то
один ключ, применить его для дешифрования зашифрованно­
го текста, проанализировать результат и, если расшифровать
секретное сообщение не удалось, перейти к использованию
следующего ключа. Учитывая высокую эффективность метода
грубой силы в отношении взлома шифра Цезаря, вам не следу­
ет применять данный шифр на практике для шифрования се­
кретных сообщений.
В идеальном случае зашифрованный текст никогда не должен попадать
в руки посторонних. Однако при н цип Керхгоффс а (названный так в честь
криптографа Огюста Керкгоффса, жившего в XIX веке) утверждает, что
шифр должен сохранять свою способность быть секретным даже тогда,
когда любому человеку известно, как он работает, и зашифрованный текст
оказывается в руках тех, для кого он не предназначен . В ХХ веке этот прин­
цип был сформулирован математиком Клодом Шенноном в виде следую-

щего постулата (максима Шеннона) : "Враг знает систему". Частью шифра,
сохраняющей секретность сообщения, является ключ, а в случае шифра
Цезаря эту информацию можно легко раскрыть.

В э то м





главе . . •

Пр и нци п Керкгоффса и максима Ше ннона
Метод г рубой силы
Функци я range ( )
Ф орматиро в ание строк

Исходный код nроrраммы Caesar Hacker
Откройте в файловом редакторе новое окно, выбрав пункты меню
Fileq New File. Введите в этом окне приведенный ниже код и сохраните его

в файле ca,esarHacker.frY.
Когда файл будет готов, нажмите клавишу , чтобы запустить про­
грамму. Если столкнетесь с ошибками или другими проблемами, сравните
свой код с кодом, приведенным в книге, воспользовавшись онлайн-утили­
той Diff пo адресу h t t p s : / / i nve ntwi thpython . com/ c r a c k i n g / d i f f / .
caesarHacker.py

1.
2.
3.
4.
5.

# Программа в злома шифра Цезаря
# h t t p s : / / www . no s t a rch . com/ c ra ckingcode s / ( BS D L i ce n s ed )
me s s age
SYМBOLS

1 guv6Jv6Jz ! J6 r p 5 r 7 J z r 6 6nt rM 1
1 ABCDE FGH I JKLМNOPQRSTUVWXYZ abcde f gh i j klmn opqrst uvwxyz
1 2 3 4 5 67 8 90 ! ? . 1

6.
7 . # Цикл по в сем в о зможным значениям ключа
8 . for key i n range ( l e n ( S YMBOLS ) ) :
9.
# Важно присвоить пустую строку переменной t rans l a t e d ,
10 .
# ч тобы очистить ее от значения из предыдущей итерации
1 1
t ra n s l a t ed
11 .
12 .
13.
# Ос т ал ь ная ч а ст ь программы почти н е изменила с ь
14 .
15 .
# Цикл по в с ем символам сообщения
16.
f o r s ymЬol i n me s sage :
i f s ymЬ o l i n SYMBOLS :
17 .
18 .
s ymЬo l i ndex
S YMBOLS . f i nd ( s ymЬ o l )
=

=

1 10

Глава 6

19 .
20 .
21 .
22 .
23 .
24 .
25 .
26.
27 .
28 .
29.
30 .
31 .
32 .
33 .

t rans l at e d i ndex

=

s ymЬ o l i n dex

- ke y

# Обработка " заворачивания "
i f t rans l at e d i ndex < О :
t ra n s l a t e d i ndex = t r a n s l a t e d i nd e x +

l e n ( S YMBOL S )

# Присоединить дешифрованный символ
t rans l a t e d = t ra n s l a t e d + SYMBOLS [ t r a n s l a t e d i ndex ]
else :
# Присоединить символ без шифрования/ дешифрования
t rans l a t e d = t ra n s l at e d + s ymЬ o l
# Отобразить каждый во зможный вари а нт ра сшифровки
print ( ' Ke y # % s : % s ' % ( ke y , t rans l a t e d ) )

Обратите внимание на то, что значительная часть программы совпа­
дает с исходной программой шифрования на осн ове шифра Цезаря. Это
объясняется тем , что программа взлома шифра Цезаря использует те же
действия для дешифрования сообщений.

Пример вь1nоnнени11 nроrраммы Caesar Hacker
Выполнив программу взлома шифра Цезаря , вы должны получить при­
веденный ниже результат. Программа взлам ывает зашифрованный текст
guv 6 Jv 6 J z ! J б rp 5 r 7 J z r 6 6 n t rM, поочередно и с пользуя все 66 возможн ых
значений ключей.
Ке у
Кеу
Ке у
Ке у
Ке у
- -

guv 6 Jv 6 J z ! J 6 rp 5 r 7 J z r 6 6nt rM
ftu5 I u 5 I y I 5 qo 4 q 6 I yq5 5ms qL
e s t 4 Ht 4 Hx O H 4 pn 3 p 5 Hxp 4 4 1 rpK
d r s 3 G s 3Gw 9 G 3 om2 o 4 Gwo 3 3 kqoJ
cqr2 Fr2 Fv8 F 2 n l l n 3 Fvn2 2 j pn I

пропущено

Кеу
Кеу
Ке у
Ке у
Кеу
- -

#0 :
#1 :
#2 :
#3 :
#4 :
#11 :
#12 :
#13 :
#14 :
#15 :

пропуще но

Ке у
Ке у
Ке у
Ке у
Кеу

# 61 :
# 62 :
# 63 :
#64 :
# 65 :

- -

Vj ku ? ku ? o l ?uget gv? oguuci g B
U i j t ! j t ! n z ! t fds fu ! n f t tbh fA
T h i s i s my s e cret me s s age .
Sghr O h r O l x O rdbqds O l drrZ fd?
Rfgq9gq 9 kw9qcap c r 9 kcqqYe c !
- -

l z l 0 1 05СО wu0w ! 05w s ywR
kyz 0 N z 0N 4 BN 0 vt 9v N 4 v 0 0 rxvQ
j xy 9My9M3AМ 9 u s 8 uOM3u9 9qwuP
iwx 8 Lx8 L 2 . L8 t r 7 t 9 L2 t 8 8pvt0
hvw 7 Kw7 K l ?K7 s q 6 s 8 Kl s 7 7 ousN

Взл ом шифра Цезаря методом г рубой сил ы

111

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

Установка переменных
В программе взлома создается переменная me s s ag e для хранения за­
шифрованной строки, подлежащей дешифрованию. Константа SYMBOL S
содержит полный набор символов, поддерживаемых программой шифро­
вания.
1.
2.
3.
4.
5.

# Программа в злома шифра Цезаря
# h t t p s : / / www . n o s t a r ch . com/ c r a c k i ngcode s / ( BS D L i ce n s ed )
mes sage = ' guv 6 Jv 6 J z ! Jб rp 5 r 7 J z r 6 6nt rM '
S YМBOLS = ' ABCDE FGH I JKLМNOPQRSTUVWXY Z abcde fgh i j klmnopqrst uvwxy z
1 2 3 4 5 67 8 90 ! ? .
1

Строка SYMBOL S должна быть такой же, как и константа SYМBOLS в про­
грамме шифрования, применявшейся для шифрования текста, который
мы пытаемся дешифровать. Если это условие не будет соблюдено, програм­
ма взлома не сработает. Обратите внимание на то, что в этой строке между
символами О и ! содержится одиночный пробел.

Орrаниэаци11 цикпа с nомощьlО функции range ( )
В строке 8 начинается цикл f o r , который проходит не по строке, а по
значению, возвращаемому функцией range ( )
.

7 . # Цикл по в сем в о зможным значе ниям ключа
8 . f o r key in range ( l e n ( S YМBOLS ) ) :

Функция r ange ( ) имеет один целочисленный аргумент и возвращает зна­
чение типа range (диапазон) . Значения этого типа могут использоваться в
циклах для выполнения определенного количества итераций. Рассмотрим
несколько примеров. Введите в интерактивной оболочке следующий код.
> > > for i in range ( З ) :
print ( ' Bello ' )

Hello
Hello
Hello

1 12

Глава б

Цикл f o r выполняется три раза, поскольку мы передали функции
range ( ) число 3 .
Функция range ( ) будет присваивать переменной цикла целочисленные
значения в диапазоне от О до значения аргумента ( но не включая его) . На­
пример , введите в интерактивной оболочке следующий код.
> > > for i in range ( б ) :

print ( i )

о
1
2
3
4
5

Здесь переменной i присваиваются значения от О до (но не включая
его) 6 , аналогично тому, как это делается в строке 8 программы caesarHacker.
ру. В этой строке переменной key присваиваются значения от О до (но не
включая его) 6 6 . Вместо жесткого кодирования значения 66 мы исполь­
зуем результат, возвращаемый вызовом l e n ( SYMBOLS ) , чтобы программа
могла нормально работать даже в случае изменения строки SYМBOL S .
Когда программа впервые проходит через этот цикл, переменная key
устанавливается равной О, и зашифрованный текст сообщения дешифрует­
ся с ключом О . (Разумеется, если О не является исходным ключом , то сооб­
щение будет дешифровано в бессмыслицу. ) Код в цикле f o r , занимающий
строки с 9 по 3 1 , аналогичен коду исходной программы шифрования на
основе шифра Цезаря и выполняет всю работу по дешифрованию. На сле­
дующей итерации в строке 8 цикла f o r ключ устанавливается равным 1 .
Хоть мы и н е используем в программе эту возможность, функции
r ange ( ) разрешается передавать не один аргумент, а два. Первый аргумент
задает начало диапазона, второй - его конец (который сам не включается в
диапазон) . Аргументы разделяются запятой.
> > > for i i n range ( 2 ,

6) :

print ( i )

2
3
4
5

Переменная i будет иметь значения от 2 до 5 (но не включая 6 ) .

В зл ом шифра Цезаря методом груб ой си л ы

113

Дешифрование сообщения
В теле цикла программа добавляет дешифрованный текст в конец
строки, хранящейся в переменной t ran s l a t e d . В строке 1 1 значение
t r a n s l a t e d устанавливается равным пустой строке.
7 . # Цикл по всем в озможным значениям ключа
8 . f o r key in range ( l en ( SYМBOLS ) ) :
9.
# Важно присвоить пустую строку переменной t ra n s l a t e d ,
10 .
# чтобы очистит ь ее от значения и з предьщущей итерации
t rans l ated = ' '
11 .

Очень важно то, что в начале цикла f o r мы каждый раз переустанав­
ливаем переменную t ra n s l a t e d в пустую строку. Если этого не делать, то
текст, дешифрованный с помощью текущего ключа, будет добавлен к хра­
нящемуся в переменной t ra n s l a t e d дешифрованному тексту, оставшему­
ся после выполнения предыдущей итерации цикла.
Строки с 1 6 по 30 почти совпадают с аналогичными строками програм­
мы шифрования на основе шифра Цезаря , рассмотренной в главе 5, но
они немного проще, поскольку предназначены только для дешифрова­
ния текста.
13 .
14 .
15 .
16 .
17 .
18 .

# О с т аль ная ч а с т ь программы почти не изме нилась
# Цикл по всем символам сообще ния
f o r s yrnЬ o l i n rne s s age :
i f s yrnЬ o l in SYMBOLS :
s yrnЬ o l i ndex = SYMBOLS . f i nd ( s yrnЬol )

В строке 1 6 мы организуем цикл по всем символам зашифрованной
строки, хранящейся в переменной me s s a g e . На каждой итерации цикла в
строке 1 7 проверяется , входит ли текущий символ в набор, хранящийся
в константе SYMBOL S , и, если это так, символ дешифруется. В строке 1 8 с
помощью метода f i nd ( ) мы находим индекс символа в строке S YMBOLSи
сохраняем его в переменной s ymbo l i ndex.
Затем мы вычитаем значение ключа из переменной s ymЬo l i ndex в
строке 1 9.
t ra n s l a t e d i ndex = s yrnЬo l i ndex - key

19.
20 .
21 .
22 .
23 .

1 14

# О бработка заворачивания
i f t rans l a t e d i ndex < О :
t ra n s l a t e d i ndex = t r a n s l a t e d i ndex

Гл ава б

+

l e n ( SYMBOLS )

В результате такого вычитания значение t rаns l аtеdindех может оказать­
ся меньшим нуля, что потребует от нас обработать "заворачивание" строки,
хранящейся в константе SYМBOLS , для определения позиции, соответствую­
щей дешифрованному символу. Необходимость этой операции проверяется
в строке 22, и если значение t r an s l atedindex меньше нуля, то в строке 23 к
нему прибавляется 6 6 (т.е. значение, возвращаемое вызовом len ( SYМBOLS ) )
Дешифрованный символ определяется выражением
S YMBOLS
[ t ra n s l a t e d i ndex ] . В строке 26 этот символ добавляется в конец строки ,
хранящейся в переменной t r a n s l ated.
.

25 .
26 .
27 .
28 .
29 .
30 .

# Присоеди ни т ь дешифрова н ный символ
translated
t rans l at e d + S YMBOL S [ t ra n s l a t e d i ndex ]
=

else :
# Присоединит ь симв ол б е з шифрования / дешифрования
t ra n s l a t e d = t ra n s l a t e d + s ymЬol

В строке 30 символ, не найденный в символьном наборе S YMBOL S , до­
бавляется в неизменном виде в конец строки, хранящейся в переменной
t ran s l a t ed.

Исnоnьэование строковоrо форматировани11 дn11
ото6ра:жени11 кn1Оча и деwифрованных сообщений
Несмотря на то что в строке 33 содержится лишь один вызов функции
p r i n t ( ) , эта функция будет выполняться множество раз, поскольку она
вызывается на каждой итерации цикла f o r , создаваемого в строке 8.
32 .
33 .

# Отобразить ка ждый в озможный вариант расшифровки
p r i nt ( ' Key # % s : % s ' % ( ke y , t r a n s l a t ed } }

Аргументом функции p r i n t ( ) является строковое значение, в котором
используется страковое фарматирование ( интерпо.ляцu.я) . Параметр % s служит
для включения одной строки в другую. Первые символы % s в строке заменя­
ются первым из значений, заключенных в круглые скобки в конце строки.
Введите следующие инструкции в интерактивной оболочке.
> > > ' Bello % s ! ' % ( ' world ' )
' He l l o wor l d ! '
> > > ' Bello ' + ' world ' + ' ! '
' He l l o wor l d ! '
> > > ' Тhе % s ate the % s that ate the % s . ' % ( ' dog ' , • cat ' , ' rat ' )
' The dog a t e t he cat that a t e the rat . '

В зл ом шифра Цезаря методом груб ой сил ы

1 15

В данном примере первая строка, ' wo r l d ' , вставляется в строку ' H e l l o
! ' вместо символов % s . Это работает так, словно в ы конкатенируете
часть строки , предшествующую символам % s , с интерполированной стро­
кой и частью строки , следующей за символами % s . Если вы интерполируе­
те несколько строк, то они последовательно подставляются вместо соот­
ветствующих символов % s .
Строковое форматирование иногда проще применять, чем конкатена­
цию строк с помощью оператора + , особенно в случае длинных строк. Кро­
ме того, в отличие от конкатенации можно вставлять в строку, например,
целые числа. Введите в интерактивной оболочке следующие инструкции.
%s

> > > ' % s had % s pies . ' % ( ' Alice ' , 4 2 )
' Al i ce h a d 4 2 p i e s . '
> > > ' Alice ' + ' had ' + 42 + ' pies . '
T raceback ( mo s t re cent ca l l l a s t ) :
F i l e " < s t d i n > " , l i ne 1 , in
T ype E r ro r : Can ' t conve rt ' i nt ' ob j e ct t o s t r imp l i c i t l y

Целое число 4 2 без проблем вставляется в строку, если использовать ин­
терполяцию, но попытка конкатенировать его приводит к ошибке.
В строке 33 программы caesarHacker.py строковое форматирование при­
меняется для создания строки , содержащей значения обеих переменных,
ke y и t ra n s la ted. Поскольку в переменной key хранится целочисленное
значение, мы используем строковое форматирование для того, чтобы вста­
вить это значение в строку, передаваемую функции p r i n t ( ) .

Реэ1Оме
Критической уязвимостью шифра Цезаря является то, что количество
возможных ключей , которые можно использовать для шифрования , огра­
ничено. Любой компьютер сможет легко выполнить дешифрование с каж­
дым из 66 возможных ключей , и криптоаналитику понадобится всего лишь
несколько секунд для того, чтобы выбрать вариант, соответствующий ис­
ходному незашифрованному тексту. Чтобы повысить степень секретности
сообщения , мы должны использовать шифр , допускающий большее коли­
чество возможных ключей шифрования. Такую секретность может обеспе­
чить перестановочный шифр , который обсуждается в главе 7.

1 16

Глава 6

Конт рол ь н ы м воп р ос

От в еты на контрольные в опросы при в едены в приложении Б .
1.

В злома йте следующи й заш ифро ванны й текст, дешифруя по одно й строке за
раз, п оскол ьку строки заш ифрованы с испол ь зованием разных кл юч ей . Н е
за будьте о нео бходи мости экра ни ро в ания кавычек лю б о го в ида.
qe F I P ? eGSe ECNN S ,
ScoOMXXcoP S Z IWoQI ,
avn l l o l yD4 l ' y l Dohww 6Dh z D j huDi l ,
z . GM ? . cEQc . 7 0 с . 7 КсКМКНА9АGFК,
?MFYp 2 p P JJUp Z S I JWpRdpMFY ,
ZqH8 s l 5 HtqHTH4 s 3 l yvH5 z H 5 sp H 4 t p H z qH l H 3 1 5 K
Z fb i , ! t i f ! xpvme ! qspcbcmz ! fbu ! n fA

В зл ом шифра Цеза ря методом груб ой си л ы

1 17

Ш И Ф РО ВАН И Е С П ОМ О Щ ЬЮ
П Е РЕСТАН О ВОЧ Н О ГО Ш ИФ РА
" Утверждение о том, •tmo вас ·не должны
волноватъ проблемы конфиденциалмюсти
лич ной информации, поскол'Dку вам нечего
скръ�ватъ, равносилъно утверждению о том,
•tmo вас не должны волнова:тъ проблемы свободъt
слова, поск олъку вам нечего ска,затъ ".
Эдвард

Сн ау ден, 2015 г.

Шифр Цезаря не является стойким; ком­
пьютеру ничего не стоит перебрать методом
грубой силы все 66 возможных ключей. С другой стороны , перестановочный шифр труднее
поддается взлому методом грубой силы, поскольку количество
возможных ключей зависит от длины сообщения . Существует
множество различных типов перестановочных шифров, вклю­
чая шифр изгороди, шифр маршрутной перестано8'Ки, перестановоч­
ный шифр Мышковского и прерывистый перестановочный шифр.
В этой главе обсуждается простой перестановочный шифр
шифр вертикалъной перестановки.
-

В это м












главе . • •

Создание функци й с помощью и н струкци и de f
Аргументы и параметры
Область видимости переменн ых
Фун кция ma i n ( )
Сп иски
Сходство списков и строк
Вложенн ые сп иски
Составные операторы присваивания ( += , -= , * = , / = I
Строковы й метод j o i n ( )
Возвращаемы е значения и инструкция r e t u rn
П еременная _n a m e _

Как работает nерестановочН111 Й wифр
Если в подстановочном шифре одни символы заменяются другими, то
в перестановочном шифре символы остаются теми же, но переставляют­
ся так, чтобы сообщение было невозможно прочитать. Поскольку каждый
ключ создает свой способ упорядочения, или перестановки, символов,
криптоаналитик не знает, как нужно переупорядочить шифротекст, чтобы
восстановить исходное сообщение.
Чтобы зашифровать сообщение с помощью перестановочного шифра,
выполните следующие действия.
1.

Определите количество символов в сообщении и длину ключа.
2. Нарисуйте строку ячеек, количество которых равно длине ключа
( например, 8 ячеек для ключа 8 ) .
3. Начните заполнять ячейки слева направо, вводя в каждую ячейку по
одному символу.
4. Если ячейки исчерпаны, но символы все еще остаются, нарисуйте
дополнительную строку ячеек.
5. Введя последний символ, заштрихуйте неиспользованные ячейки в
последней строке.
6. Начав с левой верхней ячейки и продвигаясь вниз по каждому столб­
цу, выпишите символы. Достигнув конца столбца, переходите к сле­
дующему столбцу справа. Заштрихованные ячейки пропускайте. По­
лученный текст и будет шифротекстом.
1 20

Глава 7

Чтобы увидеть, как это работает на практике, мы зашифруем сообще­
ние вручную, а затем напишем программу на Python.

Шифрование соо6щенu вручную
Прежде чем приступить к написанию кода, зашифруем сообщение
"Сошшоn sense is not so сошшоn . " , используя лишь карандаш и бумагу. Дли­
на этого сообщения, включая пробелы и знаки пунктуации, составляет 30
символов. В качестве ключа для этого примера мы используем число 8.
Возможные значения ключей лежат в диапазоне от 2 до половины длины
сообщения , т.е. 1 5 . Чем больше длина сообщения, тем больше возможных
значений ключей. В случае шифрования целой книги с использованием
вертикального перестановочного шифра количество возможных ключей
исчислялось бы тысячами.
Первое, что необходимо сделать, - это нарисовать восемь (в соответ­
ствии со значением ключа) ячеек, расположенных в ряд (рис. 7. 1 ) .

Рис. 7. 1 .

Количество ячеек в первой строке определяется значением ключа

На втором шаге записываем шифруемое сообщение в ячейки, помещая
в каждую ячейку по одному символу (рис. 7 . 2 ) . Не забывайте о том , что про­
белы тоже считаются символами (в данном случае они представлены сим­
волом · ) .
с

Рис. 7.2.

о

m

m

о

n



s

Заполните каждую ячейку одним символом, включая пробелы

У вас всего восемь ячеек, а сообщение содержит 30 символов. Исчер­
пав ячейки, нарисуйте еще один ряд ячеек под предыдущим. Продолжайте
этот процесс до тех пор, пока не запишете все сообщение ( рис. 7.3) .
Заштрихуйте две ячейки в последнем ряду как напоминание о том , что
их следует игнорировать. Шифротекст состоит из букв, которые читают­
ся , начиная с левой верхней ячейки, вниз по столбцу. Как показано на ри­
сунке, первый столбец содержит буквы С, е, п и о. Достигнув последнего
ряда в данном столбце, перейдите к верхнему ряду следующего столбца
справа. Следующими символами являются о, п, о, т. Игнорируйте заштри­
хованные ячейки.

Ш и фрован ие с помощью переста но вочного шифра

1 21

1 -й

2-й

3- й

4- й

5-й

6- й

7-й

8-й

с

о

m

m

о

n



s

е

n

s

е



i

s



n

о

1



s

о



с

о

m

m

о

n

Ри с. 7. 3. Добавляйте ряды ячеек до

тех пор, пока не запи ш ете все сообщение

Мы получаем шифротекст в виде фразы "Cenoonommstmme оо snnio.
s s с", которая достаточно запутана для того, чтобы кому-то, глядя на нее,
удалось восстановить исхощюе сообщение.

СоJДанне nроrраммы шифро1ани1
Чтобы создать программу шифрования , мы должны транслировать
шаги описанной выше "бумажно-карандашной" процедуры в код на языке
Python. Вернемся к рассмотрению того, как следует зашифровать строку
' Coпunon s e n s e i s not so coпunon . ' , используя ключ 8. Для Python пози­
ция символа в строке определяется его числовым индексом, поэтому доба­
вим индексы каждой буквы сообщения в соответствующие ячейки (рис. 7.4) .
(Не забывайте о том, что отсчет индексов начинается с О, а не с 1 . )
1 -й
с
о
е

8
n

16
о

24

2-й

3-й

4- й

о

m

m

1

n

9
о

17
m

25

2
$

3
е

10

1 1

1
18

19

m

26



о

27

Ри с. 7. 4. Добавьте в каждую

5-й
о

6-й
n

7-й


4

5

6



i
13

14

12
s

20
n

28

о

21

s



22

8- й
s

7


15
с

23

29

ячейку числовые индексы, начиная с О

Мы видим, что в первом столбце содержатся символы с индексами О ,
8, 1 6 и 24 ( ' С ' , ' е ' , ' n ' и ' о ' ) . Следующий столбец содержит символы с
индексами 1 , 9, 1 7 и 25 ( ' о ' , ' n ' , ' о ' и ' m ' ) . Обратите внимание на возни­
кающую закономсрнщ:ть: п-й
New File. Введите в этом окне приведенный ниже код и сохраните его в
файле transpositionEncrypt.py. Не забудьте поместить модуль pyperclip.py в тот
же каталог, в котором сохранили файл. Затем выполните программу, нажав
клавишу .

Ш ифрование с помощью переста новочного шифра

1 23

transposition Encrypt.py

1.
2.
3.
4.
5.
6.
7.

# Программа шифрования на основе пере становочного шифра
# ht t p s : / / www . no s t a rch . com/ cracki ngcode s / ( B S D L i ce n s e d )
irnport pyper c l ip

d e f rna i n ( ) :
' Cornrnon s e n s e i s not s o cornrnon . '
rnyMe s s age
rnyKey = В
в.
9.
encryptMe s s ag e { rnyKe y , rnyMe s s age )
c iphe rtext
10 .
11 .
# Отобразить з ашифрованную строку , хранящуюся в переменной
12 .
# ciphertext , вставив после н е е символ 1
на случай , е сли
13 .
# в конце зашифрованного сообщения имеют с я пробелы
14 .
p r int ( ciphertext + ' 1 ' )
15 .
16.
# Скопировать зашифрованную строку в буфер обмена
17 .
pype r c l ip . copy { c i phe r t ext )
18 .
19.
20 .
2 1 . de f encryptMe s s a g e ( ke y , rne s s a ge ) :
# Каждая строка в списке ciphertext представляет столбец таблицы
22 .
c iphe r t ext = [ ' ' ] * key
23.
24 .
# Цикл по в сем столбцам в списке c iphertext
25 .
f o r c o l urnn i n range { ke y ) :
26.
current i ndex = c o l urnn
27 .
28 .
# Цикл , пока значение current i ndex не превысит длину сообщения
29.
whi l e current i ndex < l e n ( rne s s a ge ) :
30 .
# Поме стить в конец т е кущего столбца в списке ciphert ext
31 .
32 .
# символ сообщения с индексом cur rent i ndex
c iphe rtext [ co l urnn ] += rne s s ag e [ cu r rent i ndex ]
33 .
34 .
35 .
# Увеличит ь значение cur r e nt i ndex
current i ndex += ke y
36 .
37 .
# Возврат списка c iphe rtext в виде единой строки
38 .
return ' ' . j o i n { c i phe r t ext )
39 .
40.
41 .
4 2 . # Е сли файл t ranspos i t i onEnc rypt . py выполняе т с я как программа
4 3 . # ( а не импортируе тся как модуль ) , выз в а т ь функцию rna i n { )
rna i n
44 . if
narne
45.
rna i n { )
'

'

1 24

Глава 7

·

'

Пример выnо11нени11 nроrраммы Transpo s i tion
Encrypt
Выполнив программу transpositionEпcrypt.py, вы должны получить следую­
щий результат:
Cenoonomrnstmrne оо snnio . s s c l

Символ вертикальной черты ( 1 ) обозначает конец шифротекста в том
случае, если строка завершается пробелами. Сам шифротекст (без вер­
тикальной черты в конце) также копируется в буфер обмена, чтобы его,
например, можно было переслать по электронной почте. Если вы хотите
зашифровать другое сообщение или использовать другой ключ, измените
значения, присваиваемые переменным myMe s s age и myKey в строках 7 и 8.
После этого вновь запустите программу.

Соэдание собственн ых функций с nомощьlО
инструкции def
После импорта модуля pype r c l ip мы используем инструкцию de f для
создания пользовательской функции ma i n ( ) в строке 6.
1.
2.
3.
4.
5.
6.
7.
8.

# Программа шифрования на основе пере стан овочного шифра
# ht t p s : / /www . no s t arch . com/ cracki ngcodes / ( BS D L i c e n s e d )
import pype rcl ip
de f ma i n ( ) :
myMe s s age
myKey = 8

' Comrnon s e n s e i s not s o comrnon . '

Инструкция de f означает, что вы создаете, или определяете, новую функ­
цию, которая будет вызываться далее в программе. Блок кода, следующий
за инструкцией de f, - это и есть тот код, который будет выполняться при
вызове функции. Когда вы вызываете функцию, выполнение передается
блоку, следующему за инструкцией de f.
Как вы узнали в главе 3 , у некоторых функций есть аргументъt - значе­
ния , которые передаются в функцию. Например, в функцию p r i n t ( ) мо­
жет передаваться строковое значение в качестве аргумента, указываемого
в круглых скобках. Определяя функцию с аргументами, вы помещаете одно
или несколько имен переменных в круглые скобки в инструкции d e f . Эти
переменные называются параметрами. В нашем случае функция ma i n ( ) не
Ш и фрование с помощью перестанов очного шифр а

1 25

имеет параметров, следовательно, она не получает никаких аргументов
при вызове. Если попытаться вызвать функцию, указав аргументы в коли­
честве, большем или меньшем , чем количество параметров, заданное в
определении функции , то Python выдаст сообщение об ошибке.

Определение функции ' nарамеrрами
Давайте создадим функцию с одним параметром и вызовем ее. Открой­
те в редакторе файлов новое окно и введите следующий код.
helloFunction.py
О
б

8
О
0

0

8

de f he l l o ( name ) :
p r i nt ( ' He l l o , ' + name )
p r i nt ( ' St a rt . ' )
he l l o ( ' Al i ce ' )
p r i nt ( ' Ca l l the funct ion a g a i n : ' )
h e l l o ( ' Bob ' )
p r i nt ( ' Done . ' )

Сохраните эту программу в файле helloFunction.py и запустите ее, нажав
клавишу . Результат должен выглядеть так, как показано ниже.
Start .
H e l l o , Al i c e
Ca l l the fun c t i o n a g a i n :
H e l l o , ВоЬ
Done .

Когда вы запускаете программу helloFunction.py, она начинает выполнять­
ся с первой строки. Но инструкция de f ( О ) всего лишь определяет функ­
цию he l l o ( ) с одним параметром , каковым является переменная n ame .
Программа не выполняет блок ( 8 ) , следующий за инструкцией de f , - это
происходит только при вызове функции. Сначала выполняется инструк­
ция p r i n t ( ' S t a r t . ' ) ( О ) , и именно поэтому первой строкой, которую
выведет программа, будет строка ' S t a r t . ' .
За инструкцией p r i n t ( ' S t a rt . ' ) следует первый вызов функции
he l l o ( ) . Выполнение передается первой строке в блоке ( 8 ) функции
he l l o ( ) . Строка ' Al i ce ' передается в качестве аргумента и присваивает­
ся параметру narne . Этот вызов функции выводит на экран строку ' He l l o ,
Al i ce ' .
Когда программа достигает конца блока инструкции de f, выполнение воз­
вращается в строку ( 8 ) , содержащую исходный вызов функции, и передается
следующей инструкции ( 0 ) , выводящей текст ' Cal l the funct ion again : ' .
1 26

Глава 7

Далее следует второй вызов функции he l l o ( ) ( 0 ) . Программа вновь
возвращается к определению функции he l l o ( ) ( О ) и еще раз выполняет
ее код, отображая на экране текст ' He l l o , В оЬ ' . После выхода из функ­
ции программа переходит к следующей строке , содержащей инструкцию
p r i n t ( ' Done . ' ) ( 8 ) . Это последняя строка, на которой работа програм­
мы завершается .

Н1менение nарамеrров cra1w1aerc1 лишь внуrрн функции
Введите в интерактивной оболочке приведенный ниже код. Здесь опре­
деляется , а затем вызывается функция func ( ) . О б ратите внимание на то,
что для завершения блока инструкции de f n интерактивной оболочке сле­
дует ввести пустую строку после ра r arn = 4 2 .
> > > def func (paraш) :
раrаш

=

42

>>> spam = ' Hello '
» > func ( sраш)
» > print ( spaш)
He l l o

Функция f u n c ( ) получает параметр p a r am и устанавливает его значе­
ние равным 4 2 . Код вне этой функции создает переменную s pam и присва­
ивает ей строковое значение, после чего вы:Jывает функцию с аргументом
sparn и выводит значение аргумента.
Функция p r i nt ( ) в последней строке выведет строку ' He l l o ' , а не
число 4 2 . Когда функция func ( ) вызывается с переменной spam в каче­
стве аргумента, хранящееся в этой переменной значение копируется и
присваивается переменной param. Любые изменения переменной pa rarn
в теле функции не приведут к изменению значения переменной s p am. (Ис­
ключением из этого правила являются случаи , когда в качестве аргумента
функции передается список или словарь, о чем будет рассказано в разделе
"Списковые переменные используют ссылки" главы 9 . )
Каждый раз, когда вызывается функция , создастся ЛО'Калъная областъ ви­
димости. Переменные и параметры , созданные во время вызова функции,
существуют только в этой локальной области видимости и называются ло­
калъны.ми. Область видимости можно представить n виде контейнера, вну­
три которого существуют переменные. Когда происходит возврат из функ­
ции , локальная область видимости уничтожается , и информация о локаль­
ных переменных, которые хранились в ней , теряется .

Ш ифро ван ие с помощью перестано в очно го шифра

1 27

Переменные, созданные вне любой функции, существуют в глобалъной
области видимости и называются гл обалъными. Когда происходит выход из
программы, глобальная область видимости прекращает существование, и
информация обо всех переменных программы теряется. (Все переменные
в программах обратного шифрования и шифрования на основе шифра Це­
заря, рассмотренных в главах 4 и 5, были глобальными. )
Переменная должна быть либо локальной , либо глобальной и н е может
быть одновременно локальной и глобальной. Две различные перемен­
ные могут иметь одинаковые имена при условии, что они принадлежат к
разным областям видимости. Они по-прежнему считаются двумя разны­
ми переменными аналогично тому, как Мэйн-стрит в Сан-Франциско и
Мэйн-стрит в Бирмингеме - совершенно разные улицы .
Важно понимать, что значение аргумента, передаваемое при вызове
функции, копируется в параметр. Поэтому, даже если параметр изменя­
ется , значение переменной, послужившей аргументом , остается неизмен­
ным.

Оаределение фун1r1t1111 .main ()
В строках 6-8 программы transpositionEncrypt.frY содержится определение
функции ma i n ( ) , которая, будучи вызванной, устанавливает значения пе­
ременных mуМе s s аgе и mуКеу.
6 . d e f ma i n ( } :
myMe s sage
7.
myKey = 8
8.

' Common s e n s e i s not so common . '

Остальные программы в книге также содержат функцию ma i n ( ) . Объяс­
нение причин того, почему мы используем функцию ma i n ( ) , будет дано в
конце главы, а пока что вам достаточно знать, что функция ma i n ( ) всег­
да вызывается сразу же после запуска любой из программ, описанных в
книге.
Строки 7 и 8 первые две строки блока кода, определяющего функцию
rna i n ( ) . В этих строках в переменных myMe s s age и myKe y сохраняется
простое текстовое сообщение, которое необходимо зашифровать, и ключ
шифрования. Строка 9 пустая, но все еще является частью блока и отде­
ляет строки 7 и 8 от строки 1 0 для улучшения читаемости кода. В строке 10
переменной c iphe r t e x t присваивается зашифрованное сообщение путем
вызова функции с двумя аргументами:
-

-

10 .

1 28

c iphertext = encryptMe s s a g e ( myKe y , myMe s sage )

Глава 7

Код, выполняющий фактическое шифрование, содержится в функции
encryptMe s s a g e ( ) , определяемой далее, в строке 2 1 . Эта функция име­
ет два аргумента: целочисленное значение ключа и строка сообщения ,
подлежащего шифрованию. В данном случае мы передаем переменные
myMe s s age и myKey, определенные перед этим в строках 7 и 8. Когда функ­
ция вызывается с несколькими аргументами, они разделяются запятой.
Функция e n c ryp tMe s s a g e ( ) возвращает строку зашифрованного
текста.
Шифротекст сообщения выводится на экран в строке 15 и копируется в
буфер обмена в строке 1 8 .
12 .
13 .
14 .
15 .
16.
17 .
18 .

# Отобразить зашифрованную строку , хран ящуюся в переменной
# c iphe rtext , вставив после не е символ 1
на случай , е сли
# в конце за ши фрованно го сообще ния имеют ся пробелы
print ( c i phertext + ' 1 ' )
'

'

# Скопировать зашифрованную строку в буфер обме на
pyp e rc l i p . copy ( c iphertext )

После сообщения программа выводит символ ' 1 ' , чтобы пользователь
мог видеть любые возможные пробелы в конце шифротекста.
Строка 18 - последняя в функции ma i n ( ) . Как только она завершается ,
выполнение передается строке, которая следует за строкой вызова функ­
ции mа in ( ) .

Передача кn1Оча и сообще ния в качестве
арrументов
Переменные key и me s s age , указанные в скобках в строке 2 1 , - это па­
раметры функции.
2 1 . de f encryptMe s sage ( ke y , me s s age ) :

Когда функция encryptMe s s age ( ) вызывается в строке 1 0 , ей пере­
даются два аргумента (значения, сохраненные в переменных myKey и
myMe s sage ) . Эти значения присваиваются параметрам key и me s s a ge при
передаче выполнения в функцию.
Возможно, вас удивляет, почему мы ввели параметры key и me s s ag e ,
если у нас уже имеются переменные myKey и myMe s s a g e , определенные в
функции ma i n ( ) . Это требуется по той причине, что переменные myKe y и
myMe s s age принадлежат к локальной области видимости функции ma i n ( )
и не могут использоваться вне ее.
Ш ифро ван ие с помощью переста но в очного шифр а

1 29

Сnисковый тиn данных
В строке 23 программы transpositionEncrypt.fry появляется новый тип дан­
ных: спиаж.
22 .
23 .

# Каждая строка в списке c iphe rtext представляе т столбец т аблицы
c iphe rtext = [ ' ' ] * key

Прежде чем двигаться дальше, необходимо узнать о том , как работают
списки и что можно делать с их помощью. Список может содержать раз­
личные значения. Аналогично тому, как строки начинаются и заканчива­
ются кавычками, список начинается с открывающей квадратной скобки
( [ ) и заканчивается парной скобкой ( ] ) . Значения , сохраненные в списке,
указываются между этими скобками. Если список содержит несколько зна­
чений , то они разделяются запятыми.
Чтобы увидеть список в действии, введите в интерактивной оболочке
следующий код.
> > > animals = [ ' aardvark ' , ' anteater ' , ' antelope ' ,
> > > animals
[ ' aa rdva r k ' , ' ante at e r ' , ' ant e l ope ' , ' a lbert ' ]

' alЬert ' )

Переменная anima l s хранит значение в виде списка, содержащего че­
тыре строки. Индивидуальные значения , находящиеся в списке, называют
элементами списка. Списки идеально подходят для тех случаев, когда в од­
ной переменной необходимо сохранить несколько значений.
Многие операции, поддерживаемые для строк, допустимы и в случае
списков. Например, индексация и взятие срезов в списках реализованы
точно так же, как и в отношении строк. Разница лишь в том, что, напри­
мер, вместо обращения к отдельным символам , как в случае строк, индек­
сация списков позволяет обращаться к их отдельным элементам. Введите в
интерактивной оболочке следующие команды.
> > > aniшals = [ ' aardvark ' ,
» > aniшal s ( 0 )
' aardva r k '
»> aniшals [ l )
' an t e at e r '
»> aniшals ( 2 )
' ant e l op e '
f) > » aniшals ( 1 : 3 )
[ ' an t e a t e r ' , ' ant e l ope ' ]
О

1 30

Глава 7

' anteater ' ,

' antelope ' ,

' alЬert ' )

Учитывайте, что первый индекс равен О, а не 1 ( О ) . Аналогично тому,
как применение среза к строке позволяет получить новую строку, являю­
щуюся частью исходной, применение среза в отношении списка позволяет
создать на его основе список меньшего размера. И помните о том, что если
указан второй индекс, то срез доходит до элемента с этим индексом, но не
включает его ( 8 ) .
Цикл f o r может проходить п о элементам списка точно так же, как по
символам строки. Значением , сохраняемым в переменной цикла f o r , яв­
ляется одиночный элемент из списка. Введите в интерактивной оболочке
следующий код.
>>> for spaa in [ ' aardvark ' , ' anteater ' , ' alЬert ' ] :
print ( ' For dinner we are cooking
+ sраш.)
1

Fo r dinner we are cooking a a rdva r k
For dinner we a r e cooking a nt e a t e r
For dinner we a r e cooking a lbert

На каждой итерации цикла переменной s pam присваивается новое зна­
чение из списка, начиная с элемента с индексом О и до конца списка.

Изменение uеменrов сnж•о
Элементы списка можно изменять с помощью обычной инструкции
присваивания, указывая нужный индекс. Введите в интерактивной оболоч­
ке следующие команды.
> > > ani.Jllal s = [ ' aardvark ' , ' anteater ' , ' alЬert ' ]
= 9999
> > > ani.Jlla l s
б [ ' aa rdva r k ' , ' anteat e r ' , 9 9 9 9 ]

О > » ani.Jllal s ( 2 )

Чтобы изменить третий элемент списка anima l s , м ы используем индек­
сацию в виде выражения a n ima l s ( 2 ) , а затем с помощью оператора при­
сваивания меняем значение с ' a lb e r t ' на 9 9 9 9 ( О ) . Если затем проверить
содержимое списка, то значения ' a lb e r t ' в нем уже не будет ( 8 ) .

Ш ифрова н ие с помощью n ереста н о в оч н ого шифра

131

З амен а с и м воАов в ст ро ках

Несм отря на возможность перен азначения элементов спис ка, не получится сде­
лать то же самое в строке. В ведите в и нтеракти вной оболоч ке следующий код:
> > > ' Bello world ! ' [ б ] = ' Х '

Н а экране появится следующее сообщение об ошибке.
T raceba c k ( mo s t recent ca l l l a s t ) :
F i l e , l i ne 1 , in
' He l l o wo r l d ! ' [ 6 ] = ' Х '
T ypeE r r o r : ' st r ' obj ect doe s not s uppo rt i t em a s s i grunent

П ричиной поя вления это й ошибки я вляется то, что Pythoп не позволяет использо­
вать операторы присв а и вания в отношении индексир ован ных строковых зна чений.
Чтобы и зменить символ в строке, необходимо создать новую строку с помощью
сре зов . Введите в интеракти вно й оболочке следующие кома нд ы.
> > > spaJD = ' Rello world ! '
> > > spaJD = spam [ : б ] + ' Х ' + spaJD [ 7 : ]
> > > spaJD
' He l l o Xorld ! '

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

Вnо•енные ,"и,ки
Списки могуг содержать другие списки. Введите в интерактивной обо­
лочке следующие команды.
> > > spam = [ [ ' doq ' , ' cat ' ] , [ 1 , 2 , 3 ] ]
» > spam [ O ]
[ ' dog ' , ' cat ' ]
> » spam [ O ] [ 0 ]
' dog '
» > spam [ O ] [ 1 ]
' cat '
» > spam [ l ] [ О ]

1 32

Глава 7

1
»> spa.ш [ l ] [ 1 ]
2

Значением выражения s pam [ О ] является список [ ' dog ' , ' с а t ' ] , об­
ладающий собственными индексами. Использование двойных индексов
в квадратных скобках в выражении spam [ О ] [ О ] указывает на то, что мы
извлекаем первый элемент из первого списка: spam [ О ] дает нам список
[ ' dog ' , ' са t ' ] , а [ ' dog ' , ' с а t ' ] [ О ] строку ' dog ' .
-

Применение фунrqи11 l en () 11 oneparopa in к tnжкам
Ранее мы применяли функцию l e n ( ) для определения количества сим­
волов в строке ( т.е. длины строки) . Функция l e n ( ) работает и в отноше­
нии списков, возвращая целое число, соответствующее количеству элемен­
тов списка.
Введите в интерактивной оболочке следующий код.
> > > aniшals
[ 1 aardvark 1 , • anteater ' , ' antelope ' , ' alЬert ' ]
»> len (aniшal s )
4
=

Мы также применяли операторы i n и not i n для проверки вхождения
одной строки в другую. Оператор in также позволяет проверить, встреча­
ется ли в списке заданное значение, тогда как оператор not i n , наоборот,
позволяет удостовериться в том , что заданного значения нет в списке. Вве­
дите в интерактивной оболочке следующие команды.
>>> anilllal s
[ ' aardvark ' , ' anteater ' ,
> > > ' anteater ' in anillla l s
T rue
>>> ' anteater ' not in aniшals
Fa l s e
О > > > ' anteat ' i n anillla l s
Fa l s e
& > > > ' anteat ' i n anillla l s [ l ]
T rue
>>> ' delicious spain ' in aniшals
Fa l s e
=

' antelope ' ,

' alЬert ' ]

Почему выражение О возвращает Fa l s e , а выражение @
T rue?
Вспомните, что anima l s это список, тогда как вычисление выражения
anima l s [ 1 ] дает строку ' anteat e r ' . Результатом выражения О является
значение Fa l s e , поскольку строка ' an t e a t ' не входит в список a n ima l s .
-

-

Ш и фрование с помощью переста новочного шифра

1 33

В то же время результат выражения 8 равен T rue, поскольку a n ima l s [ 1 ]
это строка ' anteate r ' , в которой встречается строка ' an t e a t ' .
Аналогично тому, как пустая пара кавычек представляет пустую строку,
пустая пара квадратных скобок представляет пустой список. Введите в ин­
терактивной оболочке следующий код.

-

» > animals = [ ]
> » len ( animals )
о

Список a n ima l s - пустой, поэтому его длина равна нулю.

Конrаrенаqн• н реnлпаqн• tnжroв ' помощью oneparopoв



*

Операторы + и * позволяют конкатенировать и реплицировать не толь­
ко строки, но и списки. Введите в интерактивной оболочке следующие ко­
манды.
>>> [ ' hello ' ] + [ ' world ' ]
[ ' he l l o ' , ' wo r l d ' ]
> > > [ ' hello ' ] * 5
[ ' he l l o ' , ' he l l o ' , ' he l l o ' ,

' he l l o ' ,

' he l l o ' ]

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

Аnrоритм wифровани• с nомощьlО
nерестановочноrо wифра
В нашем алгоритме шифрования мы используем списки для создания
шифротекста. Вернемся к коду программы transpositionEncrypt.fry. Как мы
уже видели, в строке 23 переменная c i phe rtext представляет собой спи­
сок пустых строк.
22 .
23 .

11 Каждая с трока в списке c iphe rtext представляет столбец таблицы
c iphe rt ext = [ ' ' ] * key

Каждый элемент списка c i phe rtext представляет столбец таблицы
перестановочного шифра. Поскольку количество столбцов определяется
значением ключа, можно реплицировать список с одним пустым строко­
вым значением, умножив его на ключ. Именно таким способом в строке 23
формируется список, содержащий нужное количество пустых строк. Эле1 34

Глава 7

ментам списка будуг присваиваться все символы, которые попадают в один
столбец таблицы. В результате мы получим список строк, представляющих
каждый столбец. Поскольку индексы списка отсчитываются от нуля, индек­
сация столбцов также должна начинаться с нуля. Таким образом, элемент
ciphe rtext [ О ] представляет крайний слева столбец, ciphe r t e x t [ 1 ]
столбец справа от него и т.д.
Чтобы увидеть, как это работает, вернемся к рассмотренной ранее сетке
для примера "Common sense is not so common. " , в которой добавленные
сверху номера столбцов соответствуют индексам списка (рис. 7.6 ) .
-

о

2

3

4

5

6

7

с

о

m

m

о

n



s

е

n

s

е



i

s



n

о

1



s

о



с

о

m

m

о

n

Рис. 7.6.

Пример сетки сообщения с индексами списка для каждого столбца

Если мы вручную присвоим строковые значения переменной ciphe rtext
для этой сетки , то она примет следующий вид.
> > > ciphertext

=

[ ' Ceno ' , ' оnош • ,
' s ' , 's с' ]

• шs tш ' ,

• ше о ' ,

' о sn ' ,

' nio . ' ,

>>> ciphertext [ O ]
' Ceno '

Следующим шагом является добавление текста в каждый элемент спи­
ска c iphe r t e x t , как мы только что сделали вручную, но на этот раз про­
граммным способом.
25.
26.
27 .

Цикл по всем с т олбцам в списке ciphert ext
for column i n range ( ke y ) :
current l ndex = co lumn
#

Цикл f o r в строке 26 проходит по всем столбцам, а переменная c o l umn
содержит целочисленное значение, которое будет использоваться для
индексации списка ciphe r t e x t . На первой итерации цикла переменная
со 1 umn содержит О , на второй итерации
1, затем 2 и т.д. Каждый раз
мы получаем индекс строки, к которой впоследствии хотим обратиться,
используя выражение c i phe rtext [ co l umn ] .
-

-

Ш ифро ван ие с помощью перестаново ч ного шифра

1 35

Таким образом, в переменной curre n t i ndex хранится индекс строки
сообщения, которая требуется программе на текущей итерации цикла f o r .
Н а каждой итерации этого цикла в строке 27 переменной current i ndex
присваивается значение, соответствующее номеру столбца. Далее мы соз­
дадим шифротекст, конкатенируя перемешанные части сообщения по од­
ному символу за раз.

Составные операторы прис ваивания
До сих пор мы выполняли конкатенацию и сложение, используя опе­
ратор + , который прибавлял к переменной новое значение. Но зачастую
нам нужно, чтобы новое значение переменной базировалось на ее текущем
значении, поэтому переменная становилась частью выражения, которое
вычисляется и присваивается ей самой, как в приведенном ниже примере.
> > > spam
40
> > > spam
spam + 2
» > print ( spam)
42
=
=

Но есть и другие способы манипулирования значениями переменных
на основе их текущих значений. Например, это можно делать с помощью
составны х операторов присваивания. Инструкция s parn + = 2 , в которой ис­
пользуется составной оператор присваивания + = , равносильна инструк­
ции sparn = sparn + 2 . Это всего лишь более короткий способ записи
данной операции. Оператор + = реализует сложение целых чисел, а также
конкатенацию строк и списков. В табл. 7 . 1 приведены примеры составных
операторов присваивания и эквивалентные им простые операции.
Таблица 7. 1 . Соста вные операторы присваивания

Составной оператор присваивани•

Э квивалентна• операци•

s p am + = 4 2

s p am

s p am

+

42

42

s p am

s p am - 4 2

s p am * = 4 2

s p am

s p am * 4 2

s p am / = 4 2

s pam

s p am / 4 2

s p am

Мы будем использовать составные операторы присваивания для конка­
тенации символов в шифротекст.

1 36

Глава 7

Перемещение текущеrо индекса no строке
сообщения
Переменная current i ndex хранит индекс следующего символа стро­
ки сообщения, который будет конкатенироваться с элементом списка
ciphe r t e x t . На каждой итерации цикла wh i l e (строка 30) к переменной
cu r rent i ndex прибавляется переменная key, позволяющая выбрать дру­
гой символ сообщения, а на каждой итерации цикла f o r (строка 26) для
переменной current i ndex устанавливается новое значение переменной
c o l urnn.
Чтобы зашифровать строку, хранящуюся в переменной rne s s a g e , мы
должны взять первый символ сообщения , т.е. ' С ' , и поместить его в пер­
вый элемент списка c iphe r t ext . После этого следует пропустить восемь
символов сообщения (поскольку значение переменной key равно 8 ) и
конкатенировать соответствующий символ , т.е. ' е ' , с первым элементом
шифротекста. Мы должны продолжать пропускать символы в соответ­
ствии со значением ключа и конкатенировать соответствующие символы
до тех пор, пока не достигнем конца сообщения. В результате будет созда­
на строка ' Ceno ' являющаяся первым столбцом шифротекста. Затем мы
должны повторить эти действия, начиная со второго символа сообщения,
для создания второго столбца.
В цикле f o r , который начинается в строке 26, содержится цикл wh i l e ,
начинающийся в строке 30. В этом цикле м ы находим подходящий символ
сообщения и конкатенируем его для создания текущего столбца. Цикл вы­
полняется до тех пор, пока значение переменной current i ndex остается
меньше длины сообщения.
,

29.
30 .
31 .
32 .
33 .
34 .
35 .
36 .

# Цикл , пока значение current ! ndex не превысит длину сообщения
wh i l e current ! ndex < l e n ( me s s a g e } :
# Поме сти т ь в конец те кущего с толбца в списке c i phert ext
# символ сообщения с инде ксом current ! ndex
c i phertext [ co l umn ] + = me s s age [ current ! ndex ]
# Увеличи т ь значение current ! ndex
current ! ndex += ke y

Для каждого столбца цикл wh i l e проходит по символам исходного сооб­
щения , выбирая их с интервалом key путем добавления ключа к значению
current i ndex. На первой итерации цикла f o r (в строке 27) значение пе­
ременной current i ndex устанавливается равным значению переменной
c o l urnn , которая первоначально равна нулю.

Ш ифро ва н ие с помощью переста но во чного шифра

1 37

Как показано на рис. 7. 7, значением выраженияmе s s аgе [ current i ndex ]
на первой итерации я вляется первый символ сообщения. В строке 33 со­
держащийся в позиции me s s a g e [ current i ndex ] символ конкатениру­
ется со строкой ciphertext [ c o l umn ] , давая начало первому столбцу. В
конце каждой итерации цикла wh i l e (строка 36) к значению переменной
current i ndex прибавляется значение переменной key (т.е. 8) . Таким об­
разом, на первой итерации в столбец добавляется символ me s s a g e [ О ] ,
на второй - me s s age [ 8 ] , на третьей me s s age [ l б ] и на четвертой me s s a g e [ 2 4 ] .
1 -й

+
с

о

m

m

о

n

о

1

2

э

4

5

Рис. 7.7.

s

6

7

+

4- й

3- й

2-й



n

8

9

$

е

1

1
1

о

i
1

2

1

3

s

1

4

+
n

1

5

1

6

о

1

7

1
1

8

1

9

s

о

2
о

2

1

с

2
2

2
3

+

о

m

m

о

n

2
4

2
5

2
6

2
7

2
8

2
9

Стрвлкамн указан ь1 снмволы сообщення, извлекаемые с помощью выражения

messa ge [ c u rren t in dex ] на первой нтерацнн цнкпа for, коrда значение
пврвмвнн о й col umn установлено равным О

Пока значение c u r r e n t i ndex меньше длины строки me s s a g e , мы
продолжаем конкатенацию символов me s s age [ current i ndex ] до конца
элемента с индексом c o l urnn в списке ciphe r t e x t . Как только значение
curren t i ndex превысит длину сообщения , цикл whi l e завершится и про­
должится цикл f o r . Поскольку в блоке цикла f o r отсутствует какой-либо
другой код после цикла whi l e , начинается очередная итерация цикла f o r ,
н а которой значение переменной c o l umn становится равным 1 . Это же
значение записывается в переменную current i ndex.
Теперь, когда на каждой итерации цикла whi l e значение переменной
current i ndex будет увеличиваться на 8 в строке 36, индексы будут после­
довательно принимать значения 1 , 9 , 1 7 и 2 5 (рис. 7.8 ) .
6-й

5-й


+

с

о

m

m

о

n

о

1

2

3

4

5

Рис. 7. 8.

6

s

е

7

8

+
n

9

8-й

7- й

s

е

1

1
1

о

i
1
2

1

3

s

1

4

n

1

5

1

6

+
о

1

7

t
1

8

1

9

s

о

2
о

2

1

2
2

+

с

о

m

m

о

n

2
3

2
4

2
5

2
6

2
7

2
8

Стрвлкамн указаны снмволы сообщення, нзвлекаемые с помощью выражения

messa ge [ c u rren t in dex ] на второй итерацнн цнкпа for, коrда значенне
пврвмвнн ой col umn установлено равным 1

1 38

2
9

Глава 7

После конкатенации символов me s s age [ 1 ] , me s s age [ 9 ] , me s s age [ 1 7 ]
и me s sa g e [ 2 5 ] в элементе c iphe rtext [ 1 ] они образуют строку ' onom ' .
Это второй столбец таблицы.
Когда цикл f o r завершит проход по оставшимся столбцам, список
c i p h e r t e x t будет содержать элементы [ ' Ceno ' ,
' onom ' ,
' ms tm ' ,
' те о ' , ' о s n ' , ' ni o . ' , ' s ' , ' s с ' ] . Получив список строк, мы
должны объединить их для создания одной строки, содержащей весь шиф­
ротекст: ' Cenoonomms tmme о о s n n i o . s s с ' .

Строковый метод j oin ( )
Метод j o i n ( ) используется в строке 39 для конкатенации отдельных
строк, содержащихся в списке c i phe r text , в одну строку. Он вызывается
для строкового значения и получает в качестве аргумента список строк.
Возвращаемая строка будет содержать все элементы списка, разделенные
строкой, для которой был вызван метод j o i n ( ) . ( Если предполагается кон­
катенация строк, то строка-разделитель должна быть пустой. ) Введите в
интерактивной оболочке следующие команды.
>>>

' са ts ' ,
egqs
[ ' doqs '
. j oin ( eqqs )
' do g scat smoo s e '
' . j oin ( egqs )
б >»
' do g s , cat s , moo s e '
8 > » ' XYZ ' . j oin (egqs )
' do g sXYZ cat sXY Zmoo s e '
О >»

=

'



,

' moose ' ]

'

,

Когда метод j o i n ( ) вызывается для пустой строки ( О ) , вы получаете
простую конкатенацию строк списка e g g s без каких-либо других строк
между ними. Но иногда требуется разделять элементы списка для нагляд­
ности , что мы и сделали ( 8 ) , вызвав метод j о i n ( ) для строки ' , ' . Благо­
даря этому между элементами списка вставляется строка ' , ' . Между эле­
ментами списка можно вставить любую строку ( 8 ) .

Возвращаемые значения и инструкция return
Результатом вызова функции ( или метода) всегда является значение,
называемое возвращаемым. Когда вы создаете собственную функцию с по­
мощью инструкции de f , инструкция return сообщает Python , что собой
представляет возвращаемое значение функции.

Ш ифро ва ние с помощью n ерестановочного шифра

1 39

38 .
39 .

# Возврат списка c iphe rtext в виде единой строки
returп ' ' . j o i п ( c i phertext )

В строке 39 метод j о i n ( ) вызывается для пустой строки и получает в
качестве аргумента переменную c iphert e x t , в результате чего строки спи­
ска c i phe rtext объединяются в одну строку.

Пример ижrрукqии re turn
Инструкция return записывается в виде ключевого слова return, за
которым следует возвращаемое значение. Вместо фиксированного значе­
ния можно указать выражение, как это сделано в строке 39. В таком случае
возвращаемым значением является результат вычисления данного выра­
жения. Откройте новое окно в редакторе файлов, введите приведенный
ниже текст программы, сохраните его в файле addNumbers.py и запустите
программу, нажав клавишу .
addNumbers. py

1 . de f addNumbe r s ( a , Ь ) :
return а + Ь
2.
3.
4 . p r i nt ( addNumЬe r s ( 2 , 4 0 ) )

Выполнив программу addNumbers.py, вы должны получить следующий ре­
зультат:
42

Это объясняется тем, что результатом вызова функции addNumЬe r s ( 2 ,
4 0 ) в строке 4 является число 42. Инструкция return в строке 2 функции
addNumbe r s ( ) вычисляет выражение а + Ь, а затем возвращает получен­
ный результат.

8011par 1аwифрованноrо wифporeкtra
В программе transpositionEncrypt.py инструкция r e t u r n в функции
e n cryp tMe s s age ( ) возвращаетстроковое значение, которое создается пу­
тем объединения всех строк, являющихся элементами списка c iphe r t e x t .
Если в переменной c iphe rtext содержится список [ ' Ceno ' , ' onom ' ,
' ms tm ' , ' те о ' , ' о s n ' , ' n i o . ' , ' s ' , ' s с ' ] , то метод j o i n ( )
сформирует строку ' Cenoonomms tmme о о s n n i o . s s с ' . Именно эта
строка, являющаяся результатом работы программы шифрования, возвра­
щается функцией encryptMe s s a g e ( ) .
1 40

Глава 7

Функции обладают тем замечательным преимуществом , что програм­
мисту достаточно знать лишь, для чего именно предназначена функция, и
вовсе не обязательно знать, как работает ее код. Программист может быть
уверен в том, что если он вызовет функцию encryp tMe s sage ( ) и передаст
ей целое число и строку в качестве значений параметров ke y и rne s s ag e , то
она вычислит зашифрованную строку. Он не обязан знать что-либо о том,
как именно достигается результат, точно так же, как вы , не имея ни малей­
шего понятия о том, как работает код функции p r i n t ( ) , знаете, что если
передать ей строку, то она выведет ее на экран.

Переменная

name

-

-

Программу шифрования на основе перестановочного шифра можно
превратить в модуль с помощью простого трюка, включающего использо­
вание функции rna i n ( ) и переменной _narne_.
Когда вы запускаете программу на языке Python , переменной _narne_
(обратите внимание на префикс и суффикс в виде двойного подчеркива­
ния ) автоматически, еще до того как выполнится первая строка програм­
мы, присваивается строковое значение ' _rna i n_ ' (и вновь двойное под­
черкивание до и после rna in ) .
В самом конце файла (и, что более важно, после всех инструкций de f )
можно проверить, содержит ли переменная _narne_ строку ' _rna i n_ ' .
Если это действительно так, то вызывается функция rna i n ( ) .
Фактически инструкция i f в строке 44 является одной из первых, вы­
полняемых при запуске программы (сначала выполняются инструкции
import и de f ) .
4 2 . # Е сли файл t ranspo s i t i onEncrypt . py выполняет с я как программа
4 3 . # ( а не импортируе тся как модуль ) , выз в а т ь функцию ma in ( )
44 . if
name
ma i n
ma i n ( )
45 .
'

·

Подобная организация кода объясняется тем, что Python автоматически
устанавливает для переменной _nаmе_ значение ' _ma in_ ' только в том
случае, если файл запускается как основная программа. В случае же импорта
файла другой программой на языке Python для переменной _name_ будет
установлено строковое значение ' t ranspo s i t i onEncrypt ' . Аналогично
тому как программа импортирует модуль pype r c l i p , чтобы можно было
вызывать содержащиеся в нем функции, другим программам может понадо­
биться импортировать модуль transpositionEncrypt.py, чтобы можно было вы­
звать его функцию encryptMe s s age ( ) , не запуская функцию ma in ( ) .
Ш и фрование с помощью перестановочного шифра

1 41

Когда выполняется инструкция irnp o r t , интерпретатор Python ищет
файл модуля, присоединяя расширение .frY к имени файла (вот почему ин­
струкция irnport pype r c l ip импортирует файл pyperclip.frY) . Еще до того,
как программа начнет выполняться, переменной _narne_ присваивает­
ся имя файла без расширения .ру. Именно так наша программа выясня­
ет, выполняется ли она в качестве основной программы или импортиру­
ется в виде модуля другой программой. ( Мы будем импортировать файл
traпspositioпEпcrypt.frY в виде модуля в главе 9.)
При импорте модуля t ra n s po s i t i onEncrypt выполнятся все содер­
жащиеся в нем инструкции de f , и в результате будет определена функция
encryptMe s s age ( ) . А вот функция rna i n ( ) не будет вызвана, поэтому код
шифрования строки ' C ornrnon s e n s e i s not s o cornrnon . ' с ключом 8 не
будет выполнен.
Именно по этой причине код, реализующий шифрование строки
rnyMe s s a g e с помощью ключа rnyKey, помещен в тело функции (которой
по общепринятому соглашению обычно присваивается имя rna i n ( ) ) . Он
не будет выполнен, если файл traпspositioпEпcrypt.frY импортируется другими
программами, в то же время программы по-прежнему смогут вызывать его
функцию encryptMe s s age ( ) . Это обеспечивает повторное использование
кода данной функции другими программами.
П р и мечан и е

Полезнъtй способ изучитъ работу программ ы - выполнитъ ее пошагово,
отслеживая промежутичнъ�е резулътаты. Вы сможете проследитъ за ра­
ботой функции, выводящей приветствие, и программ ъ� шифрования на
основе перестановичного шифра, восполъзовавшисъ трассировщиками по
адресу h t tps : //go o . gl /KzXa Rr (helloFипctioп.frY) и h t tps : //goo . gl /
Ь5п Те и (transpositioпEпcrypt.frY). Трассировщик в наглядной форме предста­
вит вам резулътатъt въ�полнения каждой сmрО'Ки кода.

Реэ1Оме
В этой главе мы изучили новые функции, операторы и типы данных,
что позволило нам реализовать более сложные алгоритмы обработки дан­
ных. Программа шифрования на основе перестановочного шифра слож­
нее, чем программа шифрования на основе шифра Цезаря, рассмотренная
в главе 6, зато перестановочный шифр намного более стойкий. Главное помнить, что залог успешного понимания кода - умение пошагово анали­
зировать его, как это делает интерпретатор Python.

1 42

Глава 7

Программный код можно группировать в блоки , называемые функ­
циями, которые создаются с помощью инструкции de f . Функция может
иметь параметры , значения которым передаются в виде аргументов
вызова. Параметры - это локальные пе р е ме н ные . Переменные, созда­
ваемые вне функций, являются глобальными. Локальные переменные
отличаются от глобальных, даже если их имена совпадают. То же самое
относится и к локальным переменным р аз н ы х ф ункций, имеющим оди­
наковые имена.
Списки могут хранить множество значений , в том числе другие списки.
Многие операции, привычные для строк ( такие, как индексация , извлече­
ние срезов и использование функции l e n ( ) ) , также применимы и к спи­
скам. Метод j o i n ( ) позволяет объединять элементы списка, содержащего
строки, в одну строку.
Составные операторы присваивания о б е > > spam

=

> > > cheese
> > > spam

=

42
spam
100

=

> > > spam

100
> > > cheese

42

М ы присваиваем значение 4 2 переменной spam, а затем копируем зна­
чение s pam и присваиваем его переменной chee s e . Если впоследствии мы
заменим значение s pam значением 1 0 0 , то это новое число не повлияет на
значение chee s e , поскольку spam и che e s e разные переменные, кото­
рые хранят разные значения .
Однако списки работают иначе. Присваивая список переменной, мы в
реальности присваиваем ей ссылку на этот список. Попробуем во всем ра­
зобраться. Введите в интерактивной оболочке следующие инструкции.
-

о > > > spam
[0, 1 , 2, 3, 4, 5)
spam
f) > > > cheese
' Hello ! '
С) > > > cheese [ l ]
> > > spam
[ О , ' He l l o ! ' , 2 , 3 , 4 , 5 ]
> > > cheese
[ О , ' He l l o ! ' , 2 , 3 , 4 5 ]
=

=

=

/

Поведение этого кода может показаться вам несколько странным. Мы
меняем лишь список chee s e , однако это приводит к одновременному из­
менению списка spam.
Когда мы создаем список ( О ) , мы присваиваем ссылку на него пере­
менной s pam. Однако следующая инструкция ( @ ) копирует в переменную
che e s e лишь ссылку на список, а не сами значения списка. Это означает,
что значения, хранящиеся в переменных s pam и chee s e , теперь указывают
на один и тот же список. Существует лишь один базовый список, поскольку
собственно список даже не копировался. В результате, если мы изменяем

1 68

Глава 9

элемент che e s e [ 1 ] ( � ) , то изменяем тот же список, на который ссылается
переменная spam.
Вспомните о том, что переменные можно уподобить ящикам, содержа­
щим значения. Однако списковые переменные фактически не содержат
сами списки - они содержат ссылки на них. (Ссылки основаны на внутрен­
них идентификаторах Python, но об этом можно не думать. ) На рис 9 . 1 по­
казано, что происходит, когда список присваивается переменной spam.
ID :

[о,

Рис. 9. J .

57207444
1,

2,

з,

4, 5 ]

Инструкция spam
[ О , 1 , 2 , 3 , 4 , 5 ] сохраняет
в переменно й не сам список, а ссылку на него
=

Затем ссылка, сохраненная в переменной spam, копируется в перемен­
ную chee s e (рис. 9.2 ) . При этом в переменной chee s e создается и сохраня­
ется не новый список, а ссылка на него.

ID:

[О,

Рис. 9.2.

Инструкция

ch e e s e

=

spam

57207444

1,

2,

З,

4, 5]

копирует ссылку на список, а не сам список

Н а писа н ие тесто в

1 69

Если мы изменим список, на который ссылается переменная chee s e , то
список, на который ссылается переменная spam, тоже изменится, посколь­
ку обе переменные ссылаются на один и тот же список (рис. 9.3) .

ID :

572.07444

[о, ' Hello ' ,

Рис. 9.3.

Инструкция

ch e e s e

2., з,

4, 5 ]

[1 }
' Hel l o ! ' изменяет список, н а который
ссьтаются обе переменные
=

Несмотря на то что с технической точки зрения переменные Python со­
держат ссылки на спИ>>

' Bello world ! ' . endswith ( ' world ' )

Fa l s e

Строковые значения должны в точности совпадать. Обратите внима­
ние на то, что отсутствие восклицательного знака в строке ' wo r l d ' ( 8 )
приводит к тому, что метод endswith ( ) возвращает значение Fa l s e .

ИtnольJованне trроковы.х меrодо1 1 11роrрамме
Как уже подчеркивалось, мы хотим, чтобы программа принимала лю­
бой ввод, начинающийся с буквы ' С ' , независимо от ее регистра. То есть
мы хотим , чтобы содержимое файла заменялось, если пользователь вводит
' с ' , ' co n t i nue ' , ' С ' или любую другую строку, начинающуюся с буквы
' С ' . Для повышения гибкости программы мы используем методы l owe r ( )
и s t a r t swi t h ( ) .
19.

# Если выходной файл суще ствуе т , зада т ь поль зователю в опрос

20 .

i f o s . pa t h . ex i s t s ( output F i l e name ) :

21 .

p r int ( ' Th i s w i l l ove rw r i t e t h e f i l e % s .
( Q ) uit ? '

22 .

re sponse = i nput ( ' >

23 .

i f not re spon s e . l owe r ( ) . s t a r t s w i t h ( ' с ' ) :

24 .

( C ) ont i nue o r

% { output F i l ename ) )
' )

sys . exit ( )

В строке 23 мы получаем первую букву строки и с помощью метода
s t a rt swi th ( ) проверяем, является ли она буквой ' С ' . Этот метод чув­
ствителен к регистру символов и проверяет совпадение с буквой ' с ' в
нижнем регистре, поэтому мы используем метод l owe r ( ) для изменения
регистра строки. Если пользователь введет ответ, не начинающийся с бук­
вы ' С ' , то метод s t a r t s w i t h ( ) вернет значение Fa l s e , в результате чего
условие в инструкции i f станет равно T rue (благодаря оператору not ) , и
вызов функции s y s . e x i t ( ) приведет к завершению работы программы.
Фактически пользователь даже не обязан вводить ' Q ' для выхода из про­
граммы: любая строка, не начинающаяся с буквы ' С ' , приведет к вызову
функции s y s . e x i t ( ) и выходу из программы.
Шифрование и дешифрование фай л о в

1 87

Чтение входноrо файnа
В строке 27 мы начинаем применять методы файлового объекта, кото­
рые обсуждались в начале главы.
26.
27 .
28 .
29.

Прочита т ь сообщение из входного файла
f i l eObj = open ( i nput Fi l ename )
cont ent = f i l e Obj . read ( )
f i l eOb j . c l o s e ( )
#

30 .
31 .

p r i nt ( ' % s i ng . . . ' % ( myMode . t i t l e { ) ) )

В строках 27-29 файл, имя которого хранится в переменной i nput­
Fi l ename , открывается , его содержимое читается в переменную content,
а затем файл закрывается. Когда содержимое файла прочитано, в стро­
ке 3 1 выводится сообщение, извещающее пользователя о начале процесса
шифрования или дешифрования. Поскольку переменная myMode содержит
строку ' en c rypt ' или ' de crypt ' , вызов строкового метода t i t l e ( ) дела­
ет первую букву этой строки прописной. Сама строка вставляется в выраже­
ние ' % s i ng . . . ' , которое будет отображаться либо как ' Encrypt ing . . . ' ,
либо как ' De c rypt ing . . . ' .

Измерение затрат времени на шифрование и
дешифрование
Шифрование или дешифрование целого файла может занять намного
больше времени по сравнению с короткой строкой. Пользователю полез­
но знать, как долго длится этот процесс. Для измерения длительности опе­
раций можно использовать функции модуля t ime .

Модуль time и функqп time . time ()
Функция t ime . t ime ( ) возвращает текущее время в виде вещественно­
го числа, которое выражает количество секунд, прошедших с полуночи
1 января 1 970 года. Этот момент называют эпохой Unix. Чтобы увидеть, как
работает данная функция , введите в интерактивной оболочке следующие
инструкции.
> > > import time
» > time . time ( )
1 5 4 0 9 4 4 000 . 7 1 97 928
» > time . time ( )
1 5 4 0 9 4 4 00 3 . 4 8 1 7 97 2

1 88

Глава 1 0

Поскольку функция t ime . t ime ( ) возвращает вещественное значение,
оно может выводиться с точностью до миллисекунды ( 1 / 1 ООО секунды) .
Разумеется, числа, отображаемые функцией t ime . t ime ( ) , зависят от того,
в какой момент времени она вызывается , а их непосредственная интерпре­
тация несколько затруднительна. С первого взгляда вовсе не очевидно, что
значению 1 5 4 0 9 4 4 0 0 0 . 7 1 9 7 9 2 8 соответствует примерное время 1 5:00,
приходящееся на вторник, 30 октября 20 1 8 года. В то же время функция
t ime . t ime ( ) очень удобна для определения количества секунд, прошед­
ших между двумя ее вызовами. Мы используем эту функцию для определе­
ния того, как долго выполняется программа.
Например , если вычесть приведенные в предыдущем листинге веще­
ственные значения , то можно узнать, сколько времени ушло на ввод вто­
рой инструкции в интерактивной оболочке.
> > > 1 5 4 0 9 4 4 0 0 3 . 4 8 1 7 972 - 1 5 4 0 9 4 4 0 0 0 . 7 1 9 7 9 2 8

2 . 7 62 0 0 4 3 7 5 4 577 637

Если вам нужно написать код, работающий со значениями даты и време­
ни, ознакомьтесь с описанием модуля dat e t ime , доступным по следующе­
му адресу:
https : / / automat e t hebo r i n g s t uf f . com/ chapt e r 1 5 /

Ис1опь1ование функqии time . time ( ) в 1роrрамме
В строке 34 функция t ime . t ime ( ) возвращает текущее время , которое
сохраняется в переменной s t a rt T ime . В строках 35-38 вызывается функ­
ция encryptMe s s age ( ) или dec ryptMe s s a g e ( ) , в зависимости от того,
какая строка сохранена в переменной myMode: ' encrypt ' или ' de c rypt ' .
33 .
34 .
35 .
36 .
37 .
38 .
39 .
40 .

# Измерить , как долго длится шифрование / дешифрование
st artT ime = time . t ime ( )
i f rnyMode == ' encrypt ' :
translated = t r anspos i t i onEncrypt . encryptMe s s age ( rnyKe y , content )
e l i f rnyMode == ' dec rypt ' :
t ranslated = t ranspo s i t i onDecrypt . decryptMe s sage ( rnyKe y , cont ent )
totalT irne = round ( t irne . t irne ( ) - s t a rtT irne , 2 )
print ( ' % s ion t irne : % s seconds ' % ( rnyMode . t i t l e ( ) , totalTirne ) )

В строке 39 функция t ime . t ime ( ) вызывается вновь, и время , сохранен­
ное в переменной s t a r t T ime , вычитается из текущего времени. Результат
представляет количество секунд, прошедших между двумя вызовами функ­
ции t ime . t ime ( ) . Выражение t ime . t ime ( ) - s t a r t T ime передается
функции round ( ) , которая округляет его до двух десятичных знаков после
Шифро ва ние и дешифрование файлов

1 89

запятой, поскольку нашей программе не нужна миллисекундная точность.
Полученный результат сохраняется в переменной t o t a l T ime . В строке 40
выводится режим работы программы и сообщается количество времени ,
затраченное программой н а шифрование или дешифрование файла.

Запись в выходной файn
На данном этапе зашифрованное ( или дешифрованное) содержимое
файла хранится в переменной t r a n s l a ted. Однако по завершении работы
программы эта переменная будет утеряна, поэтому необходимо сохранить
содержащуюся в ней строку в файле. В строках 43-45 открывается новый
файл (функции open ( ) передается аргумент ' w ' ) , а затем вызывается ме­
тод wri te ( ) файлового объекта.
# Запис а т ь тран слиров анное сообщение в выходной файл
= ope n ( output F i l en ame , ' w ' )

42 .
43.

output F i l eOb j

44 .

output F i l eOb j . wr i t e ( t ra n s l a t e d )

45.

output F i l eObj . c l o s e ( )

В строках 4 7 и 48 выводятся завершающие сообщения и указывается
имя файла, в котором был сохранен результирующий текст.
47 .

p r i n t ( ' Done % s i ng % s

( % s cha ract e r s ) . '

%

( myMode ,

i nput F i l e name ,

l e n ( c o n t e nt ) ) )
48.

p r i n t ( ' % s ed f i l e i s % s . '

%

( myMode . t i t l e ( ) ,

Строка 48 - последняя в функции ma i n ( )

output F i l ename ) )

.

Вызов функции main ( )
В строках 53 и 54 (которые выполняются после инструкции de f ) вызы­
вается функция ma i n ( ) , если программа выполняется , а не импортируется
в виде модуля.
51 .

# Е сли программа t ranspo s i t i onCiphe r Fi l e . py выполняе т с я

52 .

#

53 .

if

54 .

( а не имп ортируе т с я к а к модул ь ) ,
name

ma i n

'

выз в а т ь функцию ma in ( )

·

ma i n ( )

Об этом подробно рассказывалось в главе 7.

1 90

Глава 1 0

Реэ1Оме
В программе transpositionFileCipher.py ш�пользовались методы open ( ) ,
read ( ) , w r i te ( ) и c l o s e ( ) , которые позволили нам шифровать большие
текстовые файлы, хранящиеся на жестком диске. Вы узнали о том, как про­
верить существование файла с помощью функции o s . path . exi s t s ( ) . Са­
мое главное, что у нас появилась возможность расширить сферу примене­
ния наших программ, импортируя их функции в другие программы.
Вы также изучили ряд полезных строковых методов, упрощающих ввод
данных пользователем , и узнали о том, как использовать модуль t ime для
измерения быстродействия программы.
В отличие от шифра Цезаря , количество возможных ключей в случае пе­
рестановочного шифра слишком велико для того, чтобы можно было ор­
ганизовать атаку методом грубой силы. Но если написать программу, рас­
познающую текст на естественном языке (в отличие от строк, содержащих
бессмысленный текст) , то компьютер сможет исследовать результат тысяч
попыток дешифрования и определить, какой ключ позволяет успешно рас­
шифровать сообщение. Об этом мы поговорим в главе 1 1 .

Кон т рольные во просы

Ответы на контрольные вопросы приведены в п р иложении Б.

1.

Как правильно: o s . e x i s t s ( ) или o s . p a t h . e x i s t s ( ) ?

2.

Когда началась эпоха Uпix?

3.

Ка ким будет результат вычисления следующих вы ражений?
' Foobar ' . s t a r t s w i t h ( ' Foo ' )
' Fo o ' . s t a r t s w i t h ( ' Fooba r ' )
' Foobar ' . s t a r t swi th ( ' foo ' )
' ba r ' . endswi th ( ' Fooba r ' )
' Foobar ' . endswi t h ( ' ba r ' )
' The qu i c k brown fox j umped ove r the ye l l ow l a z y dog . ' . t i t l e ( )

Ши фрование и де шифро ва ние фай л о в

1 91

П РОГРАМ М Н О Е РАС П О З НАВАН И Е
АН ГЛИ Й С КИ Х СЛО В
"Дед проuзкосит ·что-то еще боме длттое и сложное.
И тут Уотерхауз (вот ока - стихи.я криптоакалитика,
когда кужно кайти смысл среди кажущейся бессвязности,
а нейроккая сетъ в голове 1tутко улавливает uзбъtтlJlmостъ
сигкала) покимает, ч то дед говорит по-англий ск и с
силъmtм местmtм акцектом ".
Нил Стивенсон, "Криптоммикок "

В предыдущ ей главе мы использовали пе­

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

Если компьютер деши ф рует сообщение, используя неверный ключ, то
результирующий текст представляет собой "мусор " , а не текст на англий­
ском языке. Можно написать такую программу, чтобы она распознавала
слова естественного языка. Тогда, если программа обнаружит, что был
использован неверный ключ, она сможет автоматически п е рейти к сле­
дующему ключу. Как только будет найден ключ , результат применения ко­
торого содержит слова на английском языке, программа с ооб щ ит об этом

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

В это м









rАаве • • •

Словарный тип да нных
Метод s p l i t ( )
Значение None
Ош ибки деления на нул ь
Функции f l oa t ( ) , i n t ( ) и s t r ( ) и целочисленное деление
Сп исковый метод append ( )
Аргументы по умолчанию
Р асчет процентов

Может пи комnь1Отер п онимать анrnийский язык?
Компьютер не способен понимать английский язык, во всяком случае,
он понимает его не так, как мы, люди. Компьютеры не разбираются в ма­
тематике, шахматах или политике, так же, как часы не знают, когда у нас
обед. Компьютеры всего лишь выполняют инструкции одну за другой.
А вот сами эти инструкции способны имитировать сложное поведение,
позволяющее решать математические задачи , выигрывать шахматные пар­
тии и предсказывать результаты выборов.
В идеале нам нужна функция (назовем ее i s Eng l i s h ( ) ) , которой можно
передать строку и получить в ответ True, если строка представляет собой
текст на английском языке, или Fal s e , если строка содержит текстовый "му­
сор". Рассмотрим в качестве примера предложение на английском языке и
"мусорный" текст и попытаемся выявить в них какие-'Го закономерности.
Robo t s are your f r i ends . Except for RX- 6 8 6 . She w i l l t ry t o eat you .
a i -p e y е . xrx ne augur i i rl 6 Rt i yt fhubE 6d h r S e i t B . . ow e o . t e l y o o s E s

t

Обратите внимание на то, что английский текст состоит из слов, ко­
торые можно найти в словаре, чего нельзя сказать о "мусоре". Поскольку
слова обычно разделяются пробелами, один из способов проверки может
заключаться в том, чтобы разбить сообщение по пробелам на строки и
проверить, встречаются ли эти строки в словаре. Для разбивки строк на
подстроки предназначен строковый метод spl i t ( ) , который способен
определять границы слов по пробелам . Далее можно сравнить каждую под1 94

Глава 1 1

строку с кажд ым словом словаря, используя длинную инструкцию i f , как
показано ниже.
i f word ==
word

' aa rdva r k '

' abandoned '

o r w o rd ==

o r word == '

' abacus '

o r word ==

abb revi a t e '

' abandon '

o r word ==

or

' abb revi at i o n ' o r

wo rd == ' abdome n ' o r . . .

Подобное решение, конечно, допустимо, но навряд ли мы будем так де­
лать, поскольку это слишком утомительно. К счастью, можно воспользо­
ваться файлом английского словаря, который представляет собой текстовый
файл, содержащий почти все слова английского языка. Этот файл имеет­
ся в материалах книги, так что нам остается только написать функцию
i s Engl i s h ( ) , проверяющую вхождение подстрок сообщения в словарь.
Учитывайте, что это не официальный орфографический словарь. В нем
содержатся далеко не все слова. Кроме того, в сообщении могут использо­
ваться разного рода обозначения, такие как RX-686. Также исходный текст
может быть составлен на другом языке, но мы ограничимся только англий­
ским.
Таким образом, функция i s E ng l i s h ( ) не является абсолютно надеж­
ной, но если бол'Ьшинство слов в строковом аргументе - английские, то су­
ществует высокая вероятность того, что он представляет собой текст на
английском языке. Вероятность дешифровать шифротекст неверным клю­
чом и при этом получить текст на английском языке очень невелика.
Файл словаря (содержащ ий более 45 ООО слов) доступен для загрузки на
сайте издательства (см. введение) . В нем слова, записанные прописными
буквами, располагаются по одному в строке. Открыв его, вы увидите сле­
дующее.
AARHUS
AARON
АВАВА
АВАСК
ABAFT
AВANDON
AВAN DONED
AВAN DON ING
AВAN DONMENT
AВAN DONS
- -

опуще н о

- -

Наша функция i s E ng l i s h ( ) будет разбивать дешифрованную строку
на подстроки и проверять, встречается ли каждая из них в файле словаря.
Если определенное число подстрок являются английскими словами, мы
П рограммное распозн аван ие ан гл и йских сл о в

1 95

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

И сходнь1й код модуnя Detect Engli sh

Откройте в редакторе файлов новое окно, выбрав пункты меню Fi leq
New Fi le. Введите приведенный ниже код и сохраните его в файле
detectEnglish.py. Убедитесь, что файл dictionary. txt находится в том же самом
каталоге, иначе программа не будет работать. Запустите программу, н ажав
клавишу .
detectEn9/ish.py

1 . # Модуль распознав ания английского языка
2 . # ht t p s : / /www . no s t a rch . com / c r a c k i ngcode s / ( BS D L i c e n s e d )
3.

# Чтобы исполь зовать данный модул ь , в в едите следующий код :
5. #
irnport det e c t E ng l i sh
6. #
detectEng l i sh . i s Engl i s h ( s orne S t ring )
# в о звращает T ru e или Fa l s e
7 . # В каталоге про граммы должен суще ствов ать файл d i ct i onary . t xt ,
8 . # содержащий по одному слову в строке . Файл доступен для загруз ки
9 . # на сайте издател ь с т в а ( см . введение ) .
1 0 . U P PE RLETTERS = ' ABCDE FGH I JKLMNOPQRSTUVWXYZ '
1 1 . LETTERS AND S PACE = U P PERLETTERS + U P PERLETTERS . l owe r ( ) + ' \ t \ n '
12 .
1 3 . de f loadDict ionary ( ) :
14 .
di ct i onaryFi l e
open ( ' d i c t i onary . t xt ' )
e ng l i shWords
{}
15 .
for word i n d i ct i onaryFi l e . re ad ( ) . sp l i t ( ' \ n ' ) :
16.
engl i s hWords [ wo rd ] = None
17 .
dict i onaryFi l e . c l o s e ( )
18 .
return eng l i s hWords
19.
20 .
2 1 . ENGL I S H WORDS
l oadDi ct i onary ( )
22 .
23 .
2 4 . de f getEng l i s hCount ( rne s s a ge ) :
25 .
rne s s age = rne s s age . uppe r ( )
26.
rne s s age = rernoveNonLet t e r s (me s sage )
27 .
po s s iЬleWords = rne s s age . sp l i t ( )
28 .
[] :
i f p o s s i Ь l eWords
29.
30 .
return О . О # слова о т сут ствуют , по э т ому возвращаем О . О
31 .
rnatches = О
32 .
f o r word i n p o s s iЬl eWords :
33 .
4.

=

=

=

==

1 96

Глава 1 1

34 .
35 .
36.
37 .
38 .
3 9 . def
4О .
41.
42 .
43.
44 .
45.
46.
4 7 . def
48 .
49.
50 .
51 .
52 .
53 .
54 .
55 .

i f word i n ENGL I S H WORDS :
mat c h e s + = 1
return f l oat ( mat c he s ) / l e n ( po s s iЫ eWords )

removeNonLet t e r s ( me s sage ) :
lettersOnly = [ ]
for s ymЬ o l i n me s s age :
i f s ymЬo l i n LETTERS_AND S PACE :
l e t t e rsOnl y . append ( s ymЬ o l )
return ' ' . j o i n ( l e t t e r s On l y )

i sE ng l i sh ( me s s a g e , wo rdPercentage=2 0 , l e t t e r Pe r centage=8 5 ) :
# По умолчанию 2 0 % слов должны быть в файле сло в аря ,
# а 8 5 % симв олов сообщения должны быт ь буквами или
# пробелами ( а не знаками препинания или числами )
wordsMatch = getEng l i shCount ( me s s a g e ) * 1 0 0 >= wordPe rcentage
numLet t e r s = l e n ( removeNonLe t t e r s ( me s sage ) )
me s s ageLett e r s Pe rcentage = f l oat ( numLet t e r s ) / l e n ( me s s a g e ) * 1 0 0
l e t t e rsMatch = me s s ageLet t e r s Pe r centage >= l e t t e rPer centage
return wordsMatch and l e t t e r sMat ch

П рименение модуn11 Detect Engl i sh
Программа detectEnglish.py не будет выполняться сама по себе. Вместо
этого другие программы шифрования будут импортировать ее, чтобы мож·
но было вызвать функцию de t e c t E ng l i s h . i s E ng l i s h ( ) , которая возвра·
щает T rue , если строка определяется как текст на английском языке. Вот
почему в файле detectEnglish.py нет функции ma i n ( ) . Другие функции, вклю­
ченные в файл detectEnglish.py, являются вспомогательными и вызываются
функцией i s E ng l i s h ( ) . То, что мы сделаем в этой главе, позволит любой
программе импортировать модуль de t e c t E ng l i s h с помощью инструкции
import и использовать содержащиеся в нем функции.
Вы также сможете использовать этот модуль в интерактивной оболоч­
ке, для того чтобы проверить, представляет ли собой отдельная строка
текст на английском языке.
» > iшport detectEnglish
>>> detectEnglish . isEngli sh ( ' I s thi s sentence English text? ' )

T rue

В этом примере функция определила, что строка ' I s th i s s e ntence
E ng l i sh t ex t ? ' действительно представляет текст на английском языке,
и поэтому вернула значение True.
П рогр а ммное р аспоз н ава н ие а нгл ийских сл о в

1 97

Укаэани• no исnоnьэовани1О модуn• и установка
констант
Обратимся к первой части программы detectEng/,ish.frY.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10 .
11 .

# Модуль распознавания английского языка
# https : / / www . n o s t a rch . com/ cracki ngcode s / ( BS D L i cense d )
# Ч т обы испол ь з о в а т ь данный модул ь , в в едит е следующий код :
import det e c t E ng l i sh
#
#
det e c t E ng l i sh . i sEng l i sh ( s omeS t r ing )
# возвращае т T rue или Fa l s e
# В каталоге программы должен существовать файл d i ct i onary . txt ,
# содержащий по одному слову в строке . Файл доступен для загрузки
# на сайте изда тель ства ( см . введение ) .
U PPERLETTERS = ' ABCDE FGHI JKLМNOPQRSTUVWXY Z '
LETTERS AN D S PACE = U P PERLETTERS + U P PERLETTERS . l ower ( ) + ' \ t \ n '

Первые девять строк кода - комментарии с описанием того, как исполь­
зовать данный м одуль. Они напоминают, что модуль будет работать толь­
ко в том случае, если файл dictionary. txt находится в том же каталоге, что и
файл detectEnglish.frY.
В строках 1 О и 1 1 устанавливается несколько констант, имена которых
записаны в верхнем регистре. Как вам уже известно из главы 5, констан­
ты - это переменные, значения которых не изменяются после того, как
были установлены. U P P E RLETTERS - это константа, содержащая 26 букв
алфавита в верхнем регистре. Она создана для удобства. С ее помощью
устанавливается константа LETTE RS_AND_S PAC E , которая содержит все
буквы алфавита в верхнем и нижнем регистре, а также символы пробе­
ла, табуляции и новой строки. Вместо того чтобы вводить все символы в
верхнем и нижнем регистре, мы конкатенируем строку U P PE RLETTERS с
самой собой , вызывая строковый метод U P PE RLETTERS . l ow e r ( ) . Си м во­
лы табуляции и новой строки представляются экранированными симво­
лами \ t и \ n .

Сnоварный тиn данных
Прежде чем продолжить рассмотрение остальной части программы
detectEnglish.frY, необходимо познакомиться со словарным типом данных,
чтобы понять, как преобразовать текст, содержащийся в файле, в строку.
Словарнъtй тип даннъtх ( не следует путать с файлом словаря) может хранить
множество значений, как и список. Но в списках мы извлекаем элементы
с помощью целочисленных индексов, например s pam [ 4 2 ] . А в словаре до­
ступ к каждому элементу осуществляется по ключу. Если индексами списка
1 98

Глава 1 1

могуг служить только целые числа, то ключом словаря может быть как це­
лое число, так и строка, например spam [ ' he l l o ' ] или s pam [ 4 2 ] . Слова­
ри позволяют организовать более гибкое хранение данных и не хранить
элементы строго по порядку. Вместо квадратных скобок, используемых в
списках, словари создаются с помощью фигурных скобок. Пустой словарь
обозначается как { } .
П р имечан и е

Имейте в виду, ч то файл'Ы словарей и слов арнъtе значения - совершенно раз­
Н'Ые nо'НЯтия, хотъ и наз'Ываются похожим образом. Словаръ Python может
содержатъ множество значений, а файл словаря - это текстовъtй файл, со­
держащий английские слова.

Элементы словаря представляются парами "ключ: значение", в кото­
рых ключи и значения разделены двоеточиями. Пары "ключ: значение"
разделяются запятыми. Чтобы извлечь значение из словаря , необходимо
указать ключ в квадратных скобках, как при индексации списка. Введите в
интерактивной оболочке следующие инструкции.
>>> spam

=

{ ' keyl ' :

' Тhis is а value ' ,

' key2 ' : 42 }

> > > spam [ ' keyl ' ]

' Th i s i s а value '

Сначала мы создаем словарь spam с двумя парами "ключ: значение", а
затем обращаемся к значению, связанному со строковым ключом ' key l ' .
В словарях, как и в списках, можно хранить любые типы данных.
Обратите внимание на то, что, как и в случае списков, в переменных
хранятся не сами словари, а ссылки на них. В следующем примере исполь­
зуются две переменные, содержащие ссылки на один и тот же словарь.
>>> spam

=

> > > egqs

=

{ ' hello ' : 42 }
spam
> > > egqs [ ' hello ' ]
99
> > > egqs
{ ' he l l o ' : 9 9 }
> > > spam
{ ' he l lo ' : 9 9 }


В первой строке создается словарь s p am, содержащий только одну
пару "ключ: значение". В нем хранится целочисленное значение 4 2 ,
ассоциированное со строковым ключом ' he l l o ' . Во второй строке
словарь присваивается другой переменной, e gg s . После этого можно
использовать переменную e g g s для того, чтобы изменить значение в
П рогра ммное р аспозн ава ние англ ийских сл о в

1 99

словаре, ассоциированное со строковым ключом ' h e l l o ' , сделав его
равным 9 9 . Теперь обе переменные, e g g s и s p am, должны возвращать одну
и ту же словарную пару с обновленным значением.

Различие между 'поаар•ми и ,"и,1rами
Между словарями и списками немало общего, но существуют и важные
различия.
• Элементы словаря не упорядочены. Не существует первого или по­
следнего элемента словаря, как это имеет место в списках.
• Словари нельзя конкатенировать с помощью оператора + . �ели не­
обходимо добавить новый элемент, используйте индекс с новым клю­
чом , например foo [ ' а newkey ' ] = ' а s t r ing ' .
• В списках могут использоваться только целочисленные индексы,
значения которых изменяются от нуля до длины списка минус один ,
тогда как в словарях можно использовать любой ключ. Если словарь
хранится в переменной sparn, то можно сохранить значение в эле­
менте [ 3 ] даже в отсутствие элементов s parn [ О ] , sparn [ 1 ] и sparn [ 2 ] .

До6авпение и ИJменение меменrоа uoaap•
Можно добавлять и изменять значения в словаре, используя ключ слова­
ря в качестве индекса. Чтобы увидеть, как это работает, введите в интерак­
тивной оболочке следующие инструкции.
> > > spam

=

( 42 :

' hello ' }

> > > print ( spam. [ 42 ] )

he l l o
> > > spam. [ 42 ]
' goodЬye '
> > > print ( spam. [ 42 ] )
goodЬye
=

В этом примере в словаре хранится строковое значение ' he l l o ' , свя­
занное с ключо м 4 2 . Мы можем присвоить новое строковое значение
' goodbye ' этому же ключу, используя инструкцию s parn [ 4 2 ] = ' go odbye ' .
Присваивание нового значения существующему ключу словаря приводит к
замене исходного значения, ассоциированного с этим ключом. Например,
если мы обратимся к словарю с помощью ключа 4 2 , то получим связанное
с ним новое значение.
И точно так же, как списки могут содержать другие списки, словари
тоже могут содержать другие словари ( или списки) . Чтобы проверить это,
введите в интерактивной оболочке следующие инструкции.
200

Глава 1 1

> > > foo
{ ' fiz z ' : { ' nаше ' : ' Al ' ,
' cow ' ] }
>>> foo [ ' fi z z ' ]
{ ' age ' : 1 4 4 , ' name ' : ' Al ' }
> > > foo [ ' fi z z ' ] [ ' nаше ' ]
' Al '
>>> foo [ ' moo ' ]
[ ' а ' , ' brown ' , ' cow ' ]
»> foo [ ' moo ' ] [ l ]
' brown '
=

' age ' : 1 4 4 } ,

' moo ' :

[ 'а' ,

' brown ' ,

Здесь создается словарь f o o , содержащий два ключа: ' f i z z ' и ' rno o ' .
Ключу ' f i z z ' соответствует другой словарь, а ключу ' rno o ' - список.
(Не забывайте о том , что значения в словарях не хранятся в определен­
ном порядке. Именно по этой причине пары "ключ: значение" при вводе
foo [ ' fi z z ' ] отображаются не в том порядке, в каком вы их вводили.)
Чтобы извлечь значение из словаря, вложенного в другой словарь, сначала
необходимо указать в квадратных скобках ключ большего набора данных,
т.е. ключ ' f i z z ' в данном примере. Затем следует вновь использовать
квадратные скобки и ввести ключ ' narne ' , соответствующий вложенному
строковому элементу ' Al ' , который нужно извлечь.

Применение фун•qии len () • �повар•м
Функция l e n ( ) позволяет определить количество элементов в списке
или количество символов в строке. Она же позволяет узнать количество
элементов в словаре. Введите в интерактивной оболочке следующие ин­
струкции.
>>> spam
{}
> > > len ( spam)
=

о

>>>
>>>
>>>
>>>
3

spam [ ' nаше ' ]
spam [ ' pet ' ]
spam [ ' age ' ]
len ( spam)

=
=
=

' Al '
' Zophie the cat '
89

В первой строке создается пустой словарь sparn. Функция l e n ( ) кор­
ректно сообщает длину этого словаря, равную нулю. Но после того как мы
включаем в словарь три значения, ' Al ' , ' Z oph i e the cat ' и 8 9 , она воз­
вращает 3 , соответствующее трем парам "ключ: значение", которые были
только что занесены в словарь.

П рогра ммное распозн аван ие англ ийских сл о в

20 1

Применение oaeparopa iп 1 спо1ар1м
С помощью 011ератора i n можно проверить существование определен­
ного ключа в словаре. Важно помнить о том , что оператор i n проверяет
ключи, а не значения. Введите в интерактивной оболочке следующие ин­
струкции.
>>> eqqs
{ ' foo ' : ' llli lk ' , ' bar ' :
> > > ' foo ' in eqqs
T rue
>>> ' llli l k ' in egqs
Fa l s e
> > > ' Ьlah Ыаh Ыаh ' i n eqqs
Fa l s e
> > > ' Ьlah Ыаh Ыаh ' not in eggs
T rue


' bread ' }

Мы создаем словарь eggs с несколькими парами "ключ: значение", а за­
тем с помощью оператора i n проверяем, какие ключи существуют в слова­
ре. Строка ' foo ' является ключом в словаре egg s , поэтому возвращается
T rue. В двух последующих случаях мы получаем Fa l s e , поскольку строка
' mi l k ' является значением , а не ключом, а строки ' Ы аh Ы а h Ы аh '
вообще нет в словаре. Оператор not i n тоже поддерживается , что под­
тверждает последняя команда.

Поиск апеменrо1 1 U1овар1х 1ы1оп111еп1 6ысrрее, чем 1 с1ис1ах
Представьте, что вы создали в интерактивной оболочке следующие спи­
сок и словарь.
>>> listVal
[ ' sраш ' , • eggs ' , ' bacon ' ]
> > > dictiona:r:yVal = { ' sраш ' : О , ' eggs ' : О ,


' bacon ' : О }

Pytlюn может найти строку ' bacon ' в словаре di c t i o naryVa l немного
быстрее, чем в списке l i s tVa l . Так происходит потому, что в случае спи­
ска Pythoп должен начинать поиск с самого начала, поочередно просма­
тривая элементы до тех пор, пока не найдет нужный. Если список очень
большой, придется просмотреть множество элементов, что отнимет нема­
ло времени.
Но словарь, называемый также хеш-таблицей, сразу же сообщает, где
именно в компьютерной памяти хранится значение для пары "ключ: значе­
ние" , чем и объясняется тот факт, что элементы словаря не упорядочены.
Каким бы ни был размер словаря , для нахождения любого элемента требу­
ется одно и то же количество времени.
202

Глава 1 1

При поиске в коротких списках и словарях различие в скорости едва
заметно. Однако в нашем модуле de t e c t E ng l i sh количество элементов бу­
дет исчисляться десятками тысяч, и выражение word i n ENGL I SH_WORDS
будет многократно вычисляться при вызовах функции i s Eng l i sh ( ) . В слу­
чае большого количества элементов использование словаря существенно
ускоряет работу программы.

Иtnолwование QJIUOI ror '° UIОвар•ми
Цикл f o r может применяться для итерирования по ключам словаря
аналогично тому, как это происходит в списках. Введите в интерактивной
оболочке следующий код.
> > > sраш
{ ' nаше ' : ' Al. ' ,
> > > for k in sраш :
print ( k , spam [ k ] )
=

' age ' : 9 9 }

Age 9 9
name Al

Цикл начинается с ключевого слова for. Далее задается переменная
цикла k, а с помощью ключевого слова in мы указываем, что хотим ите­
рировать по ключам словаря spam. Инструкция завершается двоеточием,
за которым следует тело цикла. Функция p r i n t ( k , spam [ k ] ) отображает
текущий ключ словаря вместе с соответствующим значением.

Реаnиэаци• файnа сnовар•
Вернемся к программе detectEnglish.frY и настроим файл словаря. Файл
словаря хранится на жестком диске, и нам необходимо загрузить его в
строковом виде. С этой целью мы создадим вспомогательную функцию
loadDi c t i onary ( ) .
1 3 . de f l o adD i c t i ona ry ( ) :
dict i onaryFi l e = open ( ' d i c t i ona r y . txt ' )
14 .
eng l i shWords = { }
15 .

Прежде всего мы получаем объект файла словаря, вызывая функцию
open ( ) и передавая ей строку ' d i c t i onary . txt ' в качестве имени файла.
Затем создается переменная e ng l i shWords в виде пустого словаря.
Слова, хранящиеся в файле английского словаря , будут скопированы в
словарь Python. Для хранения строк можно было бы использовать список,
П рогр а ммное р а спознавание а н гл и йских сл о в 203

но мы выбрали именно словарь, поскольку оператор i n работает со слова­
рями быстрее, чем со списками.
Сейчас мы познакомимся со строковым методом s p l i t ( ) , который слу­
жит для разбивки файла словаря на отдельные подстроки.

Меrод spli t ()
Строковый метод s p l i t ( ) разбивает переданную ему строку по пробе­
лам , возвращая список из нескольких строк. Чтобы увидеть, как это рабо­
тает, введите в интерактивной оболочке следующую инструкцию.
just served us Nutella . ' . split ( )
> > > ' Му very energetic шother
[ ' Му ' , ' ve ry ' , ' energet i c ' , ' mot h e r ' , ' j u s t ' , ' s e rved ' , ' us ' , ' Nut e l l a . ' ]

Результатом будет список, состоящий из восьми строк, - по одной
строке для каждого из слов в исходной строке. Пробелы, даже если их не­
сколько, опускаются . Методу s p l i t ( ) можно передать необязательный
аргумент, позволяющий задать непробельную строку, по которой следует
осуществлять разбивку. Введите в интерактивной оболочке следующую ин­
струкцию.
> > > ' helloXXXw orldXXXhowxxxareXXyou? ' . spli t ( ' ХХХ ' )
[ ' he l l o ' , ' wo r l d ' , ' how ' , ' a reXXyou? ' J

Обратите внимание на то, что данная строка вообще не содержит про­
белов. Вызов s p l i t ( ' ХХХ ' ) разбивает исходную строку на подстроки в тех
местах, где встречается группа символов ' ХХХ ' , в результате чего мы полу­
чаем список из четырех строк. Последняя часть строки, ' a reXXyou ? ' , не
разбивается , поскольку ' ХХ ' - не то же самое, что ' ХХХ ' .

Ра16и1ка файла словар• на оrдельные слова
Вернемся к файлу detectEnglish.py и рассмотрим , как осуществляется раз­
бивка строки, хранящейся в файле словаря, с сохранением каждого слова
в виде ключа.
16.
17 .

f o r word i n d i c t i onaryF i l e . read ( ) . sp l i t ( ' \ n ' ) :
engl i shWords [ word ] = None

Проанализируем строку 16. Переменная d i c t i onaryFi l e хранит объ­
ект открытого файла. Метод d i c t i onaryFi l e . read ( ) осуществляет чте­
ние всего файла и возвращает его в виде одной длинной строки. Затем мы
вызываем метод s p l i t ( ) для разбивки этой длинной строки по символам
204 Глава 1 1

новой строки. Поскольку в файле словаря слова содержатся по одному в
строке, в результате разбивки по символам новой строки возвращается
список, состоящий из всех слов словаря.
Создаваемый в строке 1 6 цикл for проходит по словам и сохраняет
каждое из них в ключе словаря. А поскольку значения , ассоциируемые с
ключами ( мы используем словарный тип данных) нас не интересуют, мы
сохраняем для каждого ключа значение None.
None - это служебное значение типа NoneTyp e , которое присваивается
переменной для того, чтобы показать отсутствие реального значения. В то
время как булев тип данных имеет только два значения (Т rue и Fa 1 s е ) , тип
NoneType имеет всего одно значение: None . Оно всегда записывается без
кавычек и начинается с прописной буквы .
Предположим, например, что у вас имеется переменная qu i zAnswe r ,
предназначенная для хранения ответов н а вопросы викторины , кото­
рые пользователь дает в форме "да - нет". Если пользователь пропуска­
ет вопрос и не отвечает на него, то имеет смысл назначить переменной
qu i zAnswer значение None в качестве значения по умолчанию, а не значе­
ние T rue или Fa l s e . В противном случае может сложиться впечатление,
что пользователь ответил на вопрос правильно или неправильно. Анало­
гичным образом при выходе из функции по достижении ее конца, а не в
результате выполнения инструкции return, автоматически возвращается
значение None , поскольку сама функция ничего не возвращает.
В строке 1 7 переменная wo rd служит текущим ключом в словаре
eng l i s hWord, а значением ключа становится None.

Во1враr данных в виде (повар•
По завершении цикла for словарь eng l i s hWords будет содержать де­
сятки тысяч слов. На данном этапе мы закрываем объект файла, поскольку
его содержимое уже прочитано, и возвращаем словарь eng l i s hWords .
18 .
19.

d i ct i onaryFi l e . c l o s e { )
return e n g l i shWo rds

После этого вызывается функция loadD i c t i onary ( ) и возвращаемый
ею словарь сохраняется в переменной ENGL I SH _WORD S .
2 1 . ENGL I SH_WORDS

=

loadD i ct i onary { )

Функция l oadDi c t i onary ( ) вызывается еще до того, как начнет вы­
полняться остальной код модуля de tectEng l i s h , но Python должен вы­
полнить инструкцию de f l o adD i c t i onary ( ) до того, как мы сможем ее
П рогр а ммное распозн аван ие ан гл ийских сл о в 205

вызвать. Именно по этой причине инструкция присваивания располагает­
ся после определения функции loadD i c t i onary ( ) .

Подсчет коnичества анrnийских сnов в сообщении
В строках 24-27 программы создается функция g e t E ng l i s hCount ( ) ,
которая имеет строковый аргумент и возвращает вещественное значение,
определяющее долю распознанных английских слов по отношению к об­
щему количеству слов в сообщении. Мыпредставим это отношение в виде
значения в интервале от 0,0 до 1 ,0. Значению 0,0 соответствует отсутствие
английских слов в сообщении, тогда как значение 1 ,0 означает, что все
слова в сообщении - английские. На основании этого значения функция
i s Eng l i s h ( ) будет возвращать значение T rue или Fa l s e .
2 4 . de f
25 .
26.
27 .

getEngl i shCount ( me s s a ge ) :
me s s age = me s sa g e . uppe r ( )
me s s age = removeNonLe t t e r s ( m e s s a g e )
pos s i Ь l eWords = me s sage . sp l i t ( )

В функции сначала создается список слов, содержащихся в строке со­
общения. В строке 25 символы строки преобразуются в верхний регистр.
В строке 26 с помощью функции removeNonLe t t e r s ( ) из строки удаля­
ются все небуквенные символы, такие как числа и знаки препинания (о
том, как работает эта функция, вы узнаете далее) . Наконец, в строке 27
метод s p l i t ( ) разбивает строку на слова и сохраняет их в переменной
pos s iЬ l eWo r d s .
Например, если функции getEng l i s hCount ( ) была передана стро­
ка ' He l l o t h e r e . How a r e you ? ' , то после выполнения строк 25-27
список po s s i Ь l eWords будет содержать [ ' HELLO ' , ' THERE ' , ' HOW ' ,
' ARE ' , ' YOU ' ] .
Если в строке сообщения содержатся цифры, например ' 1 2 3 4 5 ' , то
функция r emoveNonLe t t e r s ( ) вернет пустую строку, и метод s p l i t ( )
сформирует пустой список. В данной программе пустой список эквивален­
тен отсутствию слов на английском языке, т.е. случаю, когда их количество
равно нулю, что может повлечь за собой ошибку деления на нуль.

Оши6rа делени• на нуль
Чтобы вернуть вещественное значение в интервале от 0 , 0 до 1 ,0 , мы
делим количество слов в переменной p o s s i Ь l eW o rd s , распознанных как
английские, на общее количество слов, сохраненных в этой переменной.
Несмотря на простоту данной операции, следует убедиться в том , что
206

Глава 1 1

список, хранящийся в переменной po s s i Ь l eWords , не является пустым.
Если он пуст, то общее количество слов в с п ис ке p o s s i Ь l eWords равно
нулю.
Поскольку в арифметике операция делен и я на нуль не имеет смысла,
попытка деления на нуль в Python приводит к возникновению ошибки.
Чтобы проверить это, введите в интерактивной оболочке следующую ин­
струкцию.
>» 4 2 / о
T raceback ( mo s t recent ca l l l a st ) :
Fi l e " " , l i ne 1 , i n
42 / о
Z e ro D i v i s i on E r ro r : div i s i on Ь у z e r o

Как видите, при делении 42 на О возникает ошибка Z e ro D i vi s i on E r r o r
и выводится сообщение о том , что произошло. В о избежание проблем сле­
дует убедиться в том , что список po s s i Ы eWords не является пустым.
В строке 29 проверяется, является ли список p o s s i Ь l eWords пустым, а
в строке 30 возвращается значение О . О , е сл и список не содержит ни одно­
го слова.
i f p o s s iЬ l eWords == [ ] :
return О . О # слова о т сутс твуют , поэ тому возвращаем О . О

29.
30 .

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

Счиrаем анrпиifкие fnoвa
Чтобы получить отношение количества английских слов к общему чис­
лу слов, мы будем делить количество слов в переменной po s s iЫ eWord s ,
распознанных как английские, н а общее число слов, хранящихся в этой
переменной. Таким образом, нужно подсчитать, сколько в сообщении ан­
глийских слов. В строке 32 для счетчика mа t сhе s устанавливается нулевое
значение. В строке 33 организуется цикл f o r , в котором мы проходим по
всем словам в списке po s s iЬ l eWords и проверяем существование каждого
из них в словаре ENGL I S H_WORDS . Если слово содержится в словаре, значе­
ние счетчика mа t сhе s инкрементируется в строке 35.
32 .
33 .
34 .
35 .

matches = О
f o r word in p o s s i Ь l eWords :
i f word i n ENGL I S H WORDS :
mat ch e s += 1

П рогр аммн ое р а спозн а в а ние а н гл ийских сл ов

207

По завершении цикла f o r найденное количество английских слов со­
храняется в переменной rna tche s . Не забывайте о том, что качество рабо­
ты модуля d e t e c t Eng l i s h зависит от точности и полноты файла словаря.
Если какое-то слово отсутствует в файле словаря , то оно не будет учиты­
ваться как английское, даже если является таковым. И наоборот, если в
словаре допущена орфографическая ошибка, то слова, не являющиеся ан­
глийскими, могут быть учтены как таковые.
На данном этапе количество слов в списке po s s i Ь l eWo r d s , распознан­
ных как английские, и общее количество слов, хранящихся в этом списке,
представлены целыми числами. Чтобы получить вещественное число в ин­
тервале от 0,0 до 1 ,0 при делении этих двух чисел, мы должны сделать одно
из них вещественным.

ФункQJrн :floa t () , int () и s tr () и целочжленное деление
Рассмотрим, как превратить целое число в вещественное, поскольку для
нахождения искомого отношения мы должны разделить одно число на дру­
гое, а они оба являются целыми числами. В Python 3 деление всегда выпол­
няется одним и тем же способом независимо от типов значений, тогда как
в Python 2 в тех случаях, когда оба значения являются целыми, выполняет­
ся целочисленное деление. Поскольку пользователи могут импортировать
модуль detectEnglish.frY, пользуясь версией Python 2, мы должны передать
хотя бы одно из чисел функции f l oa t ( ) чтобы получить результат де­
ления в виде вещественного числа. Это будет гарантировать выполнение
обычного деления независимо от используемой версии Python. Так обеспе­
чивается обратная совместимостъ с предыдущими версиями.
Рассмотрим ряд других функций преобразования , хоть они и не будут
использоваться в программе. Функция int ( ) возвращает целочисленную
версию своего аргумента, а функция s t r ( ) преобразует аргумент строку.
Чтобы увидеть, как они работают, введите в интерактивной оболочке сле­
дующие инструкции.
,

> » float ( 4 2 )

42 . О
» > int ( 42 . 0 )

42
» > int ( 42 . 7 )

42
> > > int ( ' 42 ' )

42
» > s tr ( 4 2 )

' 42 '
» > s tr ( 4 2 . 7 )

' 42 . 7 '

208

Глава 1 1

Как видите, функция f l o a t ( ) превращает целое число 4 2 в веществен­
ное. Функция i n t ( ) превращает вещественные числа 4 2 . О и 4 2 . 7 в целые,
отбрасывая их дробные части, и может превратить строку ' 4 2 ' в целое
число. Функция s t r ( ) преобразует числовые значения в строковые. Эти
функции полезны , если нужно получить значение, эквивалентное другому
типу данных.

Нахождение доли анrлиi,•их 'лов в 'оо6щенп
Чтобы найти отношение количества английских слов к общему количе­
ству слов, мы делим полученное значение счетчика ma t ch e s на общее ко­
личество слов, хранящихся в переменной p o s s i Ь l eWo r d s . В строке 36 для
деления этих двух чисел используется оператор / .
36 .

return f l o a t ( ma t che s ) / l e n ( po s s i Ь l eWords )

Когда мы передаем функции f l o a t ( ) целочисленное значение счетчи­
ка mat ch e s , она возвращает вещественный эквивалент этого числа, кото­
рый делится на длину списка po s s iЬ l eWo rd s .
Инструкция return f l oa t ( ma t che s ) / l e n ( po s s i Ь l eWo rd s ) может
стать причиной возникновения ошибки деления на нуль в том случае, когда
вызов l e n ( po s s iЫ eWo rds ) возвращает нуль. В свою очередь, это возмож­
но только тогда, когда список p o s s iЬ l eWo rds оказывается пустым. Однако
в строках 29 и 30 этот случай специально проверяется , и если список пуст,
то возвращается значение О . О . Если переменная p o s s iЫ eWo rds пустая ,
то выполнение программы никогда не идет далее строки 30, поэтому мы
можем быть уверены в том, что строка 36 не станет причиной возникнове­
ния ошибки Z e roDi vi s i onE r ro r .

Удаnение небуквеннь1х симвоnов
Некоторые символы, например числа или знаки препинания, будут пре­
пятствовать нормальной работе нашей программы, поскольку слова не бу­
дут выглядеть в точности так, как они сохранены в файле словаря. Напри­
мер, если последним словом в сообщении является ' you . ' и мы не удалим
точку в конце слова, то оно не будет воспринято как английское, посколь­
ку ' you ' записано без точки в файле словаря. Чтобы избежать подобного
рода случаев неправильной интерпретации , числа и знаки препинания не­
обходимо удалять.
Чтобы удалить из строки любые числа и знаки препинания, функция
getEngl i s hCount ( ) , которую мы только что рассмотрели, вызывает для
нее функцию removeNonLe t t e r s ( ) .
П рогр а ммное распозн авание а н гл ийских сл о в 209

3 9 . d e f removeNonLet t e r s ( me s s a ge } :
4О .
l e t t e rsOn l y = [ ]
41 .
f o r s ymЬ o l i n me s s age :
i f s ymЬo l in LETTERS AND S PACE :
42 .
l e t t e r sOnl y . append ( s ymЬo l }
43.

В строке 40 создается пустой список, а в строке 4 1 начинается цикл f o r ,
в котором перебираются все символы в аргументе me s s a g e . В цикле про­
веряется , встречается ли текущий символ в строке LETTERS_AND_S PAC E .
Если символ является числом или знаком препинания , значит, о н отсуг­
ствует в строке LETTERS _AND_ S PACE и не будет добавлен в список. Если же
символ встречается в этой строке, то он добавляется в список с помощью
метода append ( ) , который мы сейчас рассмотрим.

Меrод append () '""""
Когда мы добавляем значение в конец списка, мы говорим, что оно при­
соединяется к списку. Эта операция встречается в Python настолько часто,
что для нее предусмотрен метод append ( ) , который имеет единственный
аргумент, присоединяемый к концу списка. Введите в интерактивной обо­
лочке следующие инструкции.
> > > eqqs
[]
> > > eqqs . append ( ' hovercraft ' )
> > > eqqs
[ ' hove r c r a ft ' ]
> > > eqqs . append ( ' eels ' )
> > > eqqs
[ ' hov e r c r a ft ' , ' ee l s ' ]
=

Создав пустой список egg s , мы можем вызвать метод e g g s .
append ( ' hove r c ra f t ' ) , чтобы добавить в список строку ' hove r c r a f t ' .
В результате возвращается единственное значение, сохраненное в этом
списке, т.е. ' hove r c r a f t ' . Если мы вновь используем метод append ( )
для добавления строки ' ee l s ' в конец списка, то возвращается стро­
ка ' hove r c r a f t ' , за которой следует строка ' e e l s ' . Точно так же ме­
тод append ( ) можно использовать для добавления элементов в список
l e t t e r sOnly, который мы создали ранее. Именно это и делает вызов
l e t t e r sOn l y . append ( s ymb o l ) в строке 43 цикла f o r .

210

Глава 1 1

СоJДание 'fPOIИ, 06ъед11н•юасеi 6у1вы
По завершении цикла f o r список l e t t e r s On l y будет содержать список
всех буквенных и пробельных символов из оригинальной строки me s s a g e .
Поскольку список, состоящий и з односимвольных строк, н е очень полезен
для обнаружения английских слов, в строке 44 строки символов, хранящи­
еся в списке l e t t e rsOnly, объединяются в одну возвращаемую строку.
44 .

return ' ' . j oi n ( l e t t e r s On l y )

Чтобы конкатенировать список элементов l e t t e r sOnly в одну боль­
шую строку, мы вызываем строковый метод j o i n ( ) для пустой строки ' ' .
В результате будет получена объединенная строка без разделителей. По­
сле этого результирующее строковое значение возвращается функцией
removeNonLe t t e r s ( ) .

Расnоэнавание анrnийских сnов
Попытки дешифровать сообщение с использованием неверного ключа
будут часто приводить к получению намного большего количества небук­
венных и непробельных символов, чем в типичном сообщении на англий­
ском языке. Кроме того, получаемые слова часто будут представлять собой
случайные сочетания символов, отсутствующие в словаре английских слов.
Оба этих момента проверяются в функции i s E ng l i s h ( ) .
4 7 . de f
48 .
49.
50 .

i sEngl i sh ( me s s a g e , wordPercentage=2 0 , l e t t e r P e r centage=8 5 ) :
# По умолча нию 2 0 % сло в должны быть в файле словаря ,
# а 8 5 % символо в сообщения должны быть буквами или
# пробелами ( а н е знаками препинания или числами )

В строке 47 создается функция i s Engl i s h ( ) , которая получает строко­
вый аргумент и возвращает Т rue , если строка представляет собой текст
на английском языке, и Fa l s e в противном случае. Функция имеет три
параметра: me s s a g e , wordPercentage= 2 0 и l e t t e r P e rcentage= 8 5 . Пер­
вый параметр содержит строку, подлежащую проверке, тогда как второй и
третий параметры задают значения по умолчанию для процентных долей
слов и букв, которые должна содержать строка, чтобы считаться текстом
на английском языке. ( Процент это число в интервале от О до 1 00, кото­
рое характеризует долю определенных элементов по отношению к общему
количеству элементов. ) Использование аргументов по умолчанию и расчет
процентов обсуждаются в следующих разделах.
-

П рогр аммное распознавание ан гл и йских сл о в 2 1 1

Иfnолuование арrуменrов во умолчанию
Иногда оказывается , что функции почти всегда передается одно и то же
значение. Вместо того чтобы указывать это значение при каждом вызове,
можно задать аргумент по умолчанию в инструкции de f .
В строке 4 7 определение функции включает три параметра, причем
для параметров wordPercentage и l e t t e r Percentage заданы значения
по умолчанию: 2 0 и 8 5 соответственно. Функция i s Eng l i sh ( ) может
быть вызвана с указанием от одного до трех аргументов. Если параметры
wordPercentage и l e t t e r Pe rcentage не предоставлены , то значениями
этих параметров будут их аргументы по умолчанию.
Аргументы по умолчанию определяют, какую процентную долю в стро­
ке me s s ag e должны составлять реальные английские слова, чтобы функ­
ция i s Eng l i s h ( ) определила данное сообщение как состоящее из англий­
ских слов, и какую процентную долю должны составлять буквы и пробелы,
а не числа и знаки препинания. Например, если функция i s Eng l i s h ( )
вызывается толI>КО с одним аргументом , то будут использованы значе­
ния по умолчанию для параметров word Percentage (целое число 2 0 ) и
l e t t e r Pe r centage ( целое число 8 5 ) , т.е. 20% строк должны быть пред­
ставлены английскими словами и 85% строк - буквами и пробелами. Эти
значения подходят для распознавания текстов на английском языке в боль­
шинстве случаев, но, возможно, вы захотите испытать и другие комбина­
ции аргументов, когда функция i s Eng l i s h ( ) нуждается в использовании
более слабых или более строгих пороговых значений. В подобных ситуа­
циях нужно лишь передать значения для параметров wordPer centage и
l e t t e r P e r c e n t a g e , а не использовать значения по умолчанию. В табл. 1 1 . 1
представлены различные варианты вызова функции i s Eng l i s h ( ) и их эк­
виваленты.
Табп и ца 1 1 . 1 . Вызов функции

с

указанием и без указания арrументов по умолчанию

Вызов функции

Экв и ваnент

i s E ng l i s h ( ' H e l l o ' }

i s Eng l i s h ( ' He l l o ' ,

20,

85 )

i s Eng l i s h ( ' He l l o ' ,

50 ,

85)

i s Eng l i s h ( ' He l l o ' ,

50 ,

60 )

i s Eng l i s h ( ' H e l l o ' ,

20,

60 )

i s Eng l i s h ( ' H e l l o ' ,

50)

i s Engl i s h ( ' He l l o ' ,

50,

i s Engl i s h ( ' He l l o ' ,

l e t t e r P e r ce n t a g e= 6 0 }

60)

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

21 2

Глава 1 1

Вычисление npoqeнrнoi доли
Как только программе становятся известны процентные доли, которые
следует использовать в качестве критериев, она должна вычислить реаль­
ные процентные доли для строки me s s a g e . Например, строка ' He l l o cat
MOOS E f sdkl ewp i n ' содержит пять "слов" , но только три из них являют­
ся реальными английскими словами. Чтобы вычислить процентную долю
английских слов в этой строке, мы делим их количество на общее количе­
ство слов и умножаем результат на 1 00. Процентная доля английских слов
в данной строке равна 3 / 5 * 1 00, что составляет 60% . Другие примеры
вычисления процентных долей приведены в табл. 1 1 .2.
Табпица 1 1 .2. Вычисление процентной доли английских слов

Коп ичество
ан rп ийски х
спов

Общее
копи ч ество
спов

Доп я
ан rп ийски х
сп о в

*

1 00

3

5

0, 6

*

1 00

0, 6

*

6

10

=

Про центная
доп я

60

1 00

=

60
60
36,78

300

500

0, 6

*

1 00

=

32

87

0, 3 6 78

*

1 00

=

87

87

1 ,0

*

1 00

=

о

10

о

*

1 00

=

1 00
о

Значение процентной доли всегда будет заключено в пределах от О (от­
сутствие английских слов) до 1 00 (все слова - английские ) . Наша функция
i s E ng l i s h ( ) будет рассматривать строку как состоящую из английских
слов, если по крайней мере 20% слов включены в словарь и 85% символов
являются буквами или пробелами. Это означает, что сообщение все равно
будет считаться текстом, состоящим из английских слов, даже если файл
словаря не совершенен или некоторые слова в сообщении имеют другое
написание, чем то, которое зафиксировано в словаре.
В строке 5 1 процентная доля распознанных английских слов вычисляет­
ся путем передачи аргумента mе s s аgе функции g e t E ng l i s hCount ( ) кото­
рая возвращает вещественное значение в интервале от О . О до 1 . О .
,

51 .

wordsMatc h = getEngl i shCount ( me s s age ) * 1 0 0 >= word P e r c e ntage

Процентная доля получается умножением этого вещественного чис­
ла на 1 0 0 . Если результат равен или превышает значение параметра
wo rdPer cent a g e , то в переменной wordsMatch сохраняется значение
П рогр а ммное р а спознава ние а н гл ийских сл о в

213

T rue. (Вспомните, что оператор сравнения >= вычисляет булево выраже­
ние . ) В противном случае переменная wordsMatch будет равна Fa l s e .
Процентная доля буквенных символов в строке me s s age вычисляется
в строках 52-54 путем деления количества буквенных символов на общее
количество символов в сообщении.
52 .
53 .
54 .

numLe t t e r s = l e n ( r emoveNonL e t t e r s ( me s s a ge ) )
m e s s a g e Let t e r s Percentage = float ( numLe t t e r s ) / l e n ( me s s a g e ) * 1 0 0
l e t t e r sMat ch = me s s ageLett e r s Percentage > = l e t t e r Pe rcentage

Ранее в программе мы создали функцию removeNonLe t t e r s ( ) , пред­
назначенную для нахождения всех буквенных и пробельных симво­
лов в строке, поэтому нам достаточно вызвать ее. В строке 52 функция
r emoveNonLe t t e r s ( me s s age ) возвращает строку, содержащую только
буквенные и пробельные символы из аргумента me s s a g e . Передача этой
строки функции l e n ( ) позволяет получить общее количество буквенных
и пробельных символов в строке me s s a g e , которое мы сохраняем в виде
целого числа в переменной numLe t t e r s .
Процентная доля букв определяется в строке 5 3 путем деления веще­
ственной версии целого числа, сохраненного в переменной numLe t te r s ,
н а результат вызова l e n ( me s s age ) . Значение, возвращаемое функцией
l e n ( me s s a ge ) , будет равно общему количеству символов в строке mе s s а g е .
Как уже обсуждалось, функция f l oat ( ) нужна для того, чтобы гарантиро­
вать, что в строке 53 выполняется обычное, а не целочисленное деление,
если модуль detectEng l i sh подключается к программе, которая выполня­
ется под управлением Python 2.
В строке 54 проверяется, превышает ли процентная доля, хранящаяся
в переменной me s s ageLe t t e r s Percentage , значение параметра l e t t e r­
P e r c e n t a g e . Результатом вычисления этого выражения будет булево зна­
чение, которое сохраняется в переменной l e t t e r sMat ch.
Мы хотим, чтобы функция i s E ng l i s h ( ) возвращала значение T rue
только в том случае, если обе переменные , wordsMa t ch и l e t t e rsMa t ch,
равны T rue. В строке 55 эти значения комбинируются с помощью опера­
тора аnd.
55 .

return wordsMa t ch and l e t t e r sMatch

Если обе переменные равны True, то функция i s Engl i s h ( ) признает
сообщение как текст на английском языке и возвращает T rue. В противном
случае возвращается значение Fa l s e .

214

Глава 1 1

Реэ1Оме
Программа шифрования/ дешифрования файлов с помощью переста­
новочного шифра является улучшенным решением по сравнению с анало­
гичной программой для работы с шифром Цезаря , поскольку она может
иметь сотни и даже тысячи возможных ключей, а не 26. Несмотря на то что
для компьютера не составляет труда расшифровать сообщение с тысячами
возможных ключей, нам нужно написать код, который определяет, являет­
ся ли строка текстом на английском языке, что может свидетельствовать о
правильном дешифровании сообщения.
В этой главе мы написали программу для распознавания английских
слов на основе текстового файла словаря. Словарь полезен по той причи­
не, что может содержать множество значений, как и список. Однако, в от­
личие от списка, в качестве индексов в словарях можно использовать стро­
ковые значения ключей, а не только целые числа. Большинство операций,
которые можно выполнять в отношении списка, такие как определение
длины с помощью функции l e n ( ) или использование операторов in и not
in, поддерживается также и для словарей. Однако в случае большого коли­
чества элементов оператор i n выполняется для словаря гораздо быстрее,
чем для списка. Для нас это особенно полезно, поскольку наш словарь со­
держит тысячи значений , которые нужно быстро просматривать.
В этой главе также были рассмотрены метод s p l i t ( ) , позволяющий
разбивать строку на список строк, и тип данных NoneType , имеющий толь­
ко одно значение: None . Это значение позволяет показать факт отсутствия
значения.
Вы узнали о том, как избежать ошибок деления на нуль при использова­
нии оператора / , как преобразовать значение в другой тип данных с помо­
щью функций int ( ) , f l oa t ( ) и s t r ( ) и как использоват ь метод append ( )
для добавления значения в конец списка.
Создавая определение функции , можно предоставит ь для некоторых
параметров аргументы по умолчанию. Если при вызове функции этим па­
раметрам не передаются аргументы, то программа использует значения
аргументов по умолчанию.
В главе 12 вы узнаете о том , как применить распознавание английских
слов для взлома перестановочного шифра.

П рогр а ммное р а спо знаван ие а нгл и йских сл о в

21 5

Кон т р оАьные воп росы

Ответы на контрольные вопросы при ведены в приложении Б .
1.

Что выведет следующи й код?
spam = { ' name ' : ' Al ' }
p r i nt ( spam [ ' name ' ] )

2.

Что вы ведет следующий код?
s pam = { ' e gg s ' : ' bacon ' }
p r int ( ' bacon ' in spam)

3.

Н апишите ци кл f o r дл я вывода значений, содержащихся в следующем
словаре.
spam

4.

=

{ ' name ' :

' Z ophi e ' ,

' spe c i e s ' :

' cat ' ,

' age ' : 8 }

Что выведет следующий код?
p r i nt ( ' He l l o , wo r l d ! ' . sp l i t ( ) )

5.

Что вы ведет следующий код?
de f s pam ( e g g s = 4 2 ) :
p r i nt ( eggs )
spam ( )
spam ( ' He l l o ' )

6.

Ка ков процент слов английского языка в следующем п редложении?
"Whe t h e r i t ' s f l obul l l a r i n the m i nd t o quar f a l o g the s l ings
and a r rows o f out rageous guuuuuuuuur . "

216

Глава 1 1

ВЗЛО М П Е Р ЕСТАН О ВО Ч НО ГО Ш И Ф РА
"РО'Н Ривест, один, из тех, кто придумал RSA, полагает, •tто
огран,ичен,ие криптографии окажется безрассудством.
По его мн,ен,ию, плохо без разбора запрещатъ техн,ологии
толъко потому, что н,екоторые преступн,и·ки могут
исполъзоватъ их в свсrих целях ".
Саймон, Син,гх, "Кн,ига шифров "

В этой главе мы применим метод грубой

силы для взлома перестановочного шифра.
Из тысяч потенциальных ключей корректным
может быть только тот, который приводит к по­
лучению понятного текста (в книге предполагается текст на
английском языке ) . Используя модуль detectEnglish.frY (см. гла­
ву 1 1 ) , программа взлома перестановочного шифра поможет
нам найти корректный ключ.
В это м



главе ...

М ногострочн ы й текст с тройными кавычками
Строковый метод s t r i p ( )

Исходнь1 й код nроrраммы Transpo si tion Backer

Откройте в редакторе файлов новое окно , выбрав пункты меню
File � New File. Введите в этом окне приведенный ниже код и сохраните

его в файле transpositionHacker.py. Как всегда, убедитесь в том , что модули
pyperclip.py и transpositionDecrypt.py (см. главу 8) , а также модуль detectEnglish.py
и файл dictionary. txt (см. главу 1 1 ) находятся в том же каталоге, что и файл
transpositionHacker.py. Запустите программу, нажав клавишу .
transpositionHacker.py
1.

# Про грамма в зл ома пере с т а но в о ч ного

2.

# h t t p s : / / www . n o s t a r ch . com/ c r a c k i n g code s /

шифра
( B S D Li censed )

3.
4.

imp o r t p yp e r c l i p ,

de t e c t E ng l i s h ,

t r an s po s i t i onDe c r ypt

5.
6.

de f m a i n ( ) :

7.

# Приведенный ниже т е кст

8.

# из ф айла приме ра

9.

myMe s s a ge
na

=

( см .

можно скопировать
введение )

" " "AaKo o s o e D e 5 b 5 s n ma r e n o o r a ' l h l r r c e e y

inde i t n uhor e t rm au i e u

eu a r i s f a t t

v

е me a l e f e d h s pprngAnl n o e ( c - o r ) a l a t r lw

one dt e s i l h e t cdba .

е

enlh

e r N e 2 grnanw , f o r wn l b s ya apor t E . n o

v

t t g e t u rmudg , t f l l e l

n c g H t n i e c e t r gmn oa ус r , i e a a

toesa-

е

о еЬ

ng l om , A i n

n i t i a i cynh r C s aemi e - s p

a 0m8 2 e l w s h c n t h

e kh

g a e c npeut a a i e e t gn i odh s o d ro hAe s nr s f c e g r t N C s L c Ь l 7 rn8 aEhe i de i k f r
a B e r c a e u t h l l n r s h i cw s g e t r i eb r ua i s s s

d iorr . " " "

10 .
11 .

h a c ke dMe s s a ge

=

h a c kT r a n s p o s i t i on ( rnyMe s s a ge )

12 .
13 .
14 .
15 .

i f h a c kedMe s s a ge

==

None :

p r i n t ( ' Fa i l e d to h a c k e n c r yp t i on . ' )
else :

16 .

p r i n t ( ' C o p y i n g h a c k e d rne s s a g e to c l ipboa r d : ' )

17 .

p r i n t ( h a c k e dMe s s a g e )

18 .

pype r c l i p . copy ( ha c k e dMe s s a g e )

19 .
20 .
21 .
22 .

de f h a c k T r a n s po s i t i o n ( me s s a g e ) :
p r i n t ( ' Ha c k i n g . , , ' )

23 .
24 .

# Выполнение программы на Python

25 .

# нажав < C t r l + C >

26.

print ( ' ( Pr e s s C t r l - C

( W i ndow s )

или

можно в любой моме нт прервать ,
( rna c OS и L i nux )

< C t r l + D>

( on W i ndow s )

or C t r l - D

( on rnacOS a n d L inux )

to q u i t a t a n y t i me . ) ' )
27 .
28 .

# П е р е б ор в с е х в о зможных

29.

for key i n range ( l ,

30 .

print ( ' Trying key # % s .

31 .

21 8

ключей ме тодом грубой силы

l e n ( me s s a ge ) ) :

Глава 1 2

..' %

( ke y ) )

32 .
33 .
34 .
35 .
36.
37 .
38 .
39.
40.
41.
42 .
43.
44 .
45.
46.
47.
4 8 . if
49.

=

de c r yp t e d T e x t

t r an s po s i t i o n De c r yp t . d e c r yptMe s s a g e ( ke y ,

me s s a g e )

i f d e t e c t E ng l i s h . i s E n g l i s h ( de c r yp t e dT ex t ) :

# Запросить

у поль зователя подтверждение корр е кт н о с ти ключ а

print ( )
p r i n t ( ' P o s s iЫ e e n c r yp t i on h a c k : ' )
p r i n t ( ' Ke y % s :

%s '

%

( ke y ,

d e c r yp t e dT e x t [ : l O O ] ) )

print ( )
p r i n t ( ' E n t e r D i f done ,
r e s po n s e

=

input (

anything e l s e t o con t inue h a c k i пg : ' )

'> ')

i f r e s p oп s e . s t r i p ( ) . uppe r ( ) . s t a r t s w i t h ( ' D ' ) :
r e t u r n de c r yp t e dT e x t
r e t u r n None
ma i n

пате

' ·

ma i n ( )

П р имер вь1nоnнени11 nроrраммы Transpos i tion

Backer
Выполнив программу transpositionHacker.py, вы должны получить следую­
щие результаты .
Hacking . . .
( Press Ctrl-C
T r yiпg key
T r yiпg key
T r ying key
T r ying key
T ryiпg key
Tryiпg key

#1 .
#2 .
#3 .
#4 .
#5 .
#6 .

( оп Wi ndows )

or C t r l - D ( оп ma cOS and Li пux )

t o qu i t a t any t i me . )

..
..
..
. .
..
..

Po s s iЬle eпcryptioп ha c k :
Кеу

6 : Augu s t a Ada Kiпg-Noe l , Couпt e s s o f Love l ace ( 1 0 DecemЬ e r 1 8 1 5 - 2 7
1 8 5 2 ) was а п Eпg l i sh ma t

NovemЬ e r

Ent e r D i f dопе ,

aпythiпg e l s e to coпt iпue hacking :

> D
Copyiпg hacked me s s age to c l ipboard :

( 1 0 DecemЬ e r 1 8 1 5 - 2 7 NovemЬer
1 8 5 2 ) was ап Eпgl i s h mathema t i ciaп апd wri t e r , chi e f l y known for he r wo r k

Augu s t a Ada Kiпg-Noe l , Couпte s s of Lovelace

о п Cha r l e s Babbage ' s e a r l y mechani cal geпe ral -purp o s e compu t e r , t h e Aпalytical
Епgiпе . Her not e s оп the engiпe include what i s re cogпi s e d as the f i r s t
algori thm inteпded to Ье c a r r i e d o u t Ьу а machine . A s а r e s u l t ,

s h e i s ofteп

rega rded as the first compu t e r prog ramme r .

Взл ом переста но вочного шифра

21 9

После проверки ключа #6 программа отображает отрывок дешифро­
ванного сообщения , чтобы получить от пользователя подтверждение пра­
вильности найденного ключа. В данном примере расшифрованная вер­
сия выглядит многообещающей. Если пользователь подтверждает вводом
D, что представленный отрывок дешифрован правильно, то программа
возвращает полную версию сообщения. Данное сообщение представляет
собой фрагмент биографии Ады Лавлейс (разработав в 1 842- 1 843 годах
алгоритм вычисления чисел Бернулли, она стала первым компьютерным
программистом в истории человечества) . Если расшифрованный вариант
оказывается ложным , пользователь может нажать любую другую клавишу,
и программа продолжит проверять другие ключи.
Вновь запустите программу, но на этот раз пропустите корректный
ключ, нажав любую другую клавишу, кроме D. Программа предположит, что
корректный ключ все еще не найден, и продолжит перебор всех возмож­
ных ключей методом грубой силы.
- -

o пyщe

Trying
Trying
Trying
Fa i l ed

нo

- -

ke y # 4 1 7 . . .
key # 4 1 8 . . .
ke y # 4 1 9 . . .
t o h a c k enc rypt i o n .

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

Импорт модуnей
Первые несколько строк кода информируют пользователя о возможно­
стях программы. В строке 4 импортируются несколько модулей, которые
были написаны нами или уже встречались в предыдущих главах: pyperclip.py,
detectEnglish.py и transpositionDecrypt.py.
1 . # Программа в злома перестановочного шифра
2 . # ht tps : / /www . no s t a rch . com/ c r a c k i ngcode s / ( BS D L i censed )
з.

4 . import pype r c l ip , det ec t E ng l i sh , t ranspo s i t i onDecrypt

Программа оказалась довольно короткой (менее 50 строк кода) , потому
что значительная ее часть реализована в других программах, которые мы
используем в качестве модулей .
220

Глава 1 2

Соэдание мноrострочноrо текста с nомощь1О
тройных кавычек
Переменная myMe s s a g e содержит шифротекст, который мы пытаемся
взломать. В строке 9 хранится строка, которая начинается и заканчивает­
ся тройными кавычками. Заметьте, насколько это длинная строка.
6.
7.

de f m a i n ( ) :
# При в е д е нный ниже т е к с т можно скопир о в а т ь

8 .

# и з ф а йла примера

9.

myMe s s a g e = " " " AaKo o s o e D e S b S s n ma r e n o o r a ' l h l r r c e e y е
па

в в е дение )

i nde i t n uho r e t rm au i e u v e r N e 2

eua r i s fa t t
one

( см .

dtes

е me a l e fedh s ppmgAn l n o e ( c - o r ) a l a t r

i l h e t cdba .

ncgHt nie

c e t r gmnoa

t t g e t u rmudg , t f l l e l v
ус r , i e a a

enlh

gma nw , f o rw n l b s y a a p o r t E . n o
lw о еЬ

n g l om , Ain

n i t i a i c ynhrC s a emi e - s p

t o e s a - е a 0m 8 2 e l w s h c n t h

e kh

g a e cnpeu t a a i e e t gn i o dh s o d ro hAe s n r s f c e g r t N C s L c Ь 1 7 rn 8 a E h e i de i k f r
aBercaeu

t h l l n r s h i c w s g e t r i e b r ua i s s s

d i o r r . 11 11 11

Текст, заключенный в тройные кавычки, называется многострач11:ы.м, по­
скольку он охватывает несколько строк и может содержать символы разры­
ва строки. Многострочные тексты удобны по той причине, что их можно
использовать для включения длинных строк в исходный код программы, а
также тем , что они не требуют экранирования встречающихся в них оди­
нарных и двойных кавычек. В качестве примера многострочного текста
введите в интерактивной оболочке следующие инструкции.
>>> spam = " " " Dear Alice ,
Why did you dres s up шу hamster in doll clothinq?
I look at Мr . Fuzz and think , " I know this was Alice ' s doinq . "
Sincerely ,
Brienne" " "
» > print ( spam)
Dear Alice ,
Why d i d you d r e s s

I

l o o k a t Mr .

up my hams t e r

Fu z z a n d t h i n k ,

"I

i n do l l

c l ot h i ng ?

know t h i s w a s Al i c e ' s d o i ng . "

S i n c e re l y ,
B r i e nn e

Обратите внимание на то, что этот текст занимает несколько строк.
Все, что следует за открывающими тройными кавычками, будет интерпре­
тироваться как часть строки до тех пор, пока программа снова не встретит
тройные кавычки. При создании многострочного текста допускается ис­
пользование либо трех двойных, либо трех одинарных кавычек.

Взл ом перестановочного ши фра 2 2 1

Отображение резуnьтатов взnома соо6щени•
Код взлома шифротекста помещен в функцию h a c kT ra n s po s i t i on ( ) ,
которая вызывается в строке 1 1 и будет определена в строке 2 1 . Эта функ­
ция имеет один аргумент: зашифрованное сообщение, которое мы пытаем­
ся взломать. Если функции удалось взломать шифротекст, она возвращает
строку дешифрованного текста, в противном случае возвращается значе­
ние Nоn е .
hac kedМe s sage = hackTranspo s i t i o n ( rnyMe s s age }

11 .
12 .
13 .
14 .
15 .
16.
17 .
18 .

i f h a ckedМe s s age == None :
print ( ' Fa i l ed to ha c k encrypt ion . ' }
else :
p r i nt ( ' Copying hacked rne s s age t o c l i pboard : ' }
p r i nt ( ha c kedMe s s age }
pyp e r c l ip . copy ( hackedМe s s age }

В строке 1 1 вызывается функция hackTranspo s i t io n ( ) которая воз­
вращает взломанное сообщение, если попытка оказалась удачной, или зна­
чение None. Результат сохраняется в переменной hac kedMe s s age.
В строках 1 3 и 1 4 программа проверяет значение переменной hac ked­
Me s s age и , если она равна None , сообщает пользователю о том , что взло­
мать шифротекст не удалось.
Следующие четыре строки определяют, что будет делать программа,
если функции удалось взломать шифротекст. В строке 1 7 выводится де­
шифрованное сообщение, а в строке 1 8 оно копируется в буфер обмена.
Но для того, чтобы этот код заработал, мы должны определить функцию
h a c kTranspo s i t i on ( ) , которая рассматривается ниже.
,

П оnучение взnоманноrо соо6щени•
Функция hackTranspo s i t i on ( ) начинается парой инструкций print ( ) .
2 1 . def hackT ranspo s i t ion (rne s s a ge ) :
22 .
p r i nt ( ' Hacking . . . ' }
23.
24 .
# Выполнение программы на Python можно в любой моме нт прерв а т ь ,
25 .
# нажав ( W i ndow s } или ( rnacOS и L i nux } :
26.
p r i nt ( ' ( Pr e s s C t r l - C ( on W indows } o r Ct r l - D ( on rnacOS and Li nux }
to qui t at any t irne . } ' }

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

Глава 1 2

Функция p r i n t ( ) в строке 26 сообщает пол ьзо вател ю о том , что он может
в любой момент выйти из программы, нажав комбинацию клавиш
(Windows) или (macOS и Linux ) . Ланная ко м би н ация позволяет
завершить работу любой программы на языке Pytlюn .
В следующих строках определяется ди ап аз о н возможных ключей, ко­
торые программа должна перебрать, пытаясь в зл омать перестановочный
шифр.
28 .
29.
30 .

# Пере бор всех в озможных ключей ме т одом грубой сил ы
f o r key i n range { l , l e n { me s s age ) ) :
p r i nt { ' T r ying ke y # % s . . . ' % { ke y ) )

Возможные ключи беругся из интервала от 1 до длины сообщения. Все
они перебираются в цикле f o r , который н ач инает ся в строке 29. В стро­
ке 30 программа отображает текущий ключ.
В строке 32 вызывается функция dec r y p t M e s s a ge ( ) из созданной нами
ранее программы transpositionDecrypt.py, которая получает дешифрованное
сообщение, соответствующее текущему тес тируемо му ключу, и сохраняет
его в переменной de c ryptedT e x t .
32 .

decryptedText

=

transpos i t i onDe c r ypt . de c r yptMe s s age { key , me s s a ge )

Дешифрованный текст, содержащийся в переменной de c rypt e dT e x t ,
будет представлять собой осмысленный текст н а английском языке только
в случае использования корректного ключа. В противном случае мы полу­
чим "мусор".
Далее строка, содержащаяся в переменной dec ryptedTex t , передается
функции d e t e c t Eng l i s h . i s E ng l i s h ( ) , которую мы создали в главе 1 1 .
Если функция определяет текст как англ и йс ки й , то программа выводит
фрагмент текста из переменной dec rypt edText и выдает запрос пользо­
вателю.
34 .
35 .
36.
37 .
38 .
39 .
40.
41.

i f dete ctEng l i sh . i sEng l i sh ( de c rypt edText ) :
# З апросить у поль зователя подтверждение корректности ключа
print ( )
print ( ' Po s s i Ы e encrypt ion hac k : ' )
print ( ' Key % s : % s ' % { ke y , de c r ypt edText [ : l O O ] ) )
print { )
print { ' Ent e r D i f done , anything e l s e t o cont i nue hac king : ' )
response
i nput ( ' > ' )
=

Тот факт, что функция d e t e c t E ng l i sh . i s Engl i s h ( ) вернула значение
Т rue, еще не означает, что программе удалщ:ь найти корректный ключ. Это
Взл ом перестано в очного шиф р а 223

может быть всего лишь ложноположительный результат в том смысле, что
программа распознала в качества английского текст, который в действи­
тельности является "мусором" . Чтобы пользователь мог это проверить,
программа выводит в строке 38 фрагмент текста. Для вывода первых 1 00
символов строки, сохраненной в переменной de c ryptedT e x t , использует­
ся срез de cryptedText [ : 1 0 0 ] .
В строке 4 1 программа приостанавливает работу, ожидая , пока пользо­
ватель не введет букву D или что-нибудь еще. Полученная строка сохраняет­
ся в переменной r e s p on s e .

Сrро•овый меrод s trip ()
Когда пользователь не придерживается строго инструкций , выводи­
мых для него программой, могуг возникать ошибки. Если программа
transpositionHacker.py предл агает пользователю ввести D в качестве под­
тверждения , то это означает, что никакой другой ответ не будет принят.
Рассмотрим, как можно использовать строковый метод s t r i p ( ) для того,
чтобы заставить программу принимать другие варианты ответа при усло­
вии , что они близки к D.
Метод s t r i p ( ) возвращает версию строки, из которой удалены любые
пробельные символы в начале и конце. К пробельным относятся символы
пробела, табуляции и новой строки. Чтобы увидеть, как это работает, вве­
дите в интерактивной оболочке следующие инструкции.
>>> '
Bello ' . strip ( )
' He l l o '
' . s trip ( )
> > > ' Rello
' He l l o '
>>> '
Bello World
' He l l o W o r l d '

' . s trip ( )

В этом примере метод s t r i p ( ) удаляет пробельные символы , нахо­
дящиеся в начале и в конце первых двух строк. Если строка наподобие
He l l o Wor l d
' содержит пробельные символы и в начале, и
в конце, то метод удалит их по обе стороны строки, но не удалит пробелы
между другими символами.
Метод s t r i p ( ) может также иметь строковый аргумент, определяю­
щий, какие символы , в отличие от пробельных, подлежат удалению из на­
чала и конца строки. В качестве примера введите в интерактивной оболоч­
ке следующие инструкции.

224 Глава 1 2

> > > ' aaaaaВELLOaa ' . s trip ( ' а ' )
' HE LLO '
> > > ' аЬаЬаВЕLLОЬаЬа ' . s trip ( ' аЬ ' )
' HELLO '
> > > ' аЬссаЬсЬасЬХУZаЬсХУZасссаЬ ' . s trip ( ' аЬс ' )
' XYZ abcXYZ '

В первых двух случаях передача строковых аргументов ' а ' и ' аЬ ' при­
водит к удалению этих символов в начале и в конце строки. В то же время
метод s t r i p ( ) не удаляет символы, находящиеся посередине строки. Как
показано в третьем примере, строка ' аЬс ' остается в строке ' XYZ abcXYZ ' .

Применение frpoкoвoro метода s trip ()
Вернемся к исходному коду программы. В строке 43 с помощью ин­
струкции i f задается условие, позволяющее пользователю вводить разные
варианты ответа.
43 .
44 .

i f response . st r i p ( ) . uppe r ( ) . s t a r t s w i t h ( ' D ' ) :
return dec ryptedText

Если бы это условие было сформулировано в виде r e s p o n s e == ' D ' , то
пользователь был бы строго обязан ввести именно D для завершения рабо­
ты программы. Например, если пользователь введет ' d ' , ' D ' или ' Done ' ,
то условие не сработает, и программа продолжит проверку других ключей
вместо того, чтобы вернуть взломанное сообщение.
Чтобы обойти эту проблему, из начала и конца строки , сохраненной в
переменной r e s p ons e , с помощью метода s t r i p ( ) удаляются пробелы.
Затем для строки, возвращаемой методом r e s p o n s e . s t r i p ( ) , вызыва­
ется метод u pp e r ( ) . Независимо от того , введет ли пользователь ' d '
или ' D ' , строка, возвращаемая методом u pp e r ( ) , всегда будет начинать­
ся с прописной буквы ' D ' . Это немного упрощает использование про­
граммы.
Чтобы пользователь мог ввести слово, которое начинается с буквы ' D ' ,
мы используем метод s t a rt swi t h ( ) , позволяющий проверить только пер­
вую букву слова. Например, если пользователь введет ' done ' в качестве
ответа, то пробелы будут удалены, а результирующая строка ' done ' будет
передана методу up p e r ( ) . После того как метод u pp e r ( ) переведет все
символы строки в верхний регистр, результирующая строка ' DONE ' будет
передана методу s t a r t s w i th ( ) , который вернет True, поскольку строка
начинается с символа ' D ' .

Взл ом переста но в очного шифра 225

Если пользователь подтверждает, что дешифрованная строка кор­
ректна, то функция h a c kT ra n spo s i t i on ( ) в строке 44 возвращает де­
шифрованный текст.

Нево1можн°'n. вuома tоо6щенп
Строка 46 выполняется по завершении цикла f o r , запущенного в стро­
ке 29:
return None

46.

Если программа достигла данной точки, значит, не была выполнена ин­
струкция re turn в строке 44, а это может произойти только в том случае,
если перебор всех ключей не позволил дешифровать сообщение. Тогда в
строке 46 возвращается значение None , указывающее на невозможность
взлома.

Вызов функции main ( )
В строках 48 и 49 вызывается функция ma i n ( ) , если программа была
запущена на выполнение, а не импортирована в виде модуля другой про­
граммой, использующей функцию hackTranspo s i t i on ( ) .
48 . if
49.

name
ma i n ( )

ma i n

'

·

Не забывайте о том, что значение переменной _nаmе_ устанавливает­
ся интерпретатором Python. Функция ma i n ( ) не будет вызвана, если про­
грамма transpositionHacker.py импортируется в виде модуля.

Реэ1Оме
Как и глава 6, эта глава оказалась короткой, потому что значительная
часть рассмотренного в ней кода уже реализована в написанных ранее про­
граммах. Наша программа взлома может использовать функции из других
программ, импортируя их в виде модулей.
Вы узнали о том, как применять тройные кавычки для включения тек­
стовых блоков, простирающихся на несколько строк в исходном коде. Вы
также узнали, насколько полезен может быть строковый метод s t r ip ( )
для удаления пробелов или других символов из начала или конца строки.
Наличие программы detectEnglish.py помогло нам сэкономить немало вре­
мени, которое пришлось бы потратить на самостоятельную проверку того,
226

Глава 1 2

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

Ко н т р ольные воп росы

Ответы на контрольные вопросы приведены в приложении Б.

1.

Какой результат вернет п ри веденное ниже выражение?
He l l o worl d ' . s t r i p ( )

2.

Какие символы я вляются пробел ьными?

3.

П очему вызов ' H e l l o wo r l d ' . s t r i p ( ' о ' ) возвращает строку, которая
по-прежнему содержит букву ' о ' ?

4.

П очему вызов ' xxxHe l l oxxx ' . s t r ip ( ' Х ' ) возвращает строку, которая
по-прежнему содержит букву ' х ' ?

Взл ом перестановочного шифра

227

АФФ И Н Н О Е Ш И Ф РОВАН И Е С П О М О Щ ЬЮ
М ОДУЛ Ь Н О Й АРИФ М ЕТИ КИ
''Люди векам.и nЪtm али сь доби ться приватиост и - ш.епотом.,
в темноте, за закръtтЪtмu дверями, с помощъю тайиых
рукопожат и й и посла-н:н иков с запечатттъ�ми конвертами.
Действуя по стари нке, м.ъ� ·не могли надежно хран и тъ свои
тайнъ�, а с помощъю электрон·нЪtх mеХ'нологи й - можем ".
эрик Хъюз, "Ман и фест ш.и фропаика " (1 993)

В этой главе рассматриваются мультипли­

кативный и аффинный шифры. Мультипли­
кативный шифр напоминает шифр Цезаря ,
только в качестве операции шифрования используется не сложение , а умножение. Аффин­
ный шифр представляет собой сочетание мультипликативного
шифра и шифра Цезаря , что повышает его стойкость и надеж­
ность.
Но сначала вы узнаете о модульной арифметике и наибольшем общем
делителе - двух математических концепциях, в которых следует разобрать­
ся для реализации аффинного шифра. На их основе мы создадим модуль
для обработки "завертываний" и поиска подходящих ключей для аффин­
ного шифра. Этот модуль будет задействован при разработке программы
аффинного шифрования в главе 1 4.

В это м








главе ...

М одул ьная арифметика
Оператор деления с остатком ( % )
Наибол ьши й общий делитель ( Н ОД)
Груп повое присваивание
Алгоритм Евклида для нахождения наибольшего общего делителя
Мульти пликати вный и аффи н н ый шифры
Расши рен ный алгоритм Евклида для нахождения модульных обращений

Модуnьна• арифметика
Модулъная арифметика - раздел математики, в котором целые числа
сравниваются по модулю некоторого натурального числа. Вычисляемые
остатки "вращаются" вокруг этого числа, подобно стрелке часов. Мы будем
использовать модульную арифметику для обработки "завертываний" в аф­
финном шифре. Рассмотрим , как это работает.
Представьте циферблат, снабженный только часо­
о
11
вой стрелкой , на котором вместо 1 2 часовуказано О.
2
10
(Если бы часы проектировались программистами,
клянусь, так и было бы. ) Если сейчас 3 часа, то что
9
покажет стрелка спустя 5 часов? Ответить на этот во­
прос не составит труда: 3 + 5 = 8, т.е. стрелка преодо­
леет 5 отметок и покажет 8 часов (рис. 1 3. 1 ) .
А если сейчас 1 О часов, то что покажет стрелка че­
Рис. JЗ. J . З часа +
рез 5 часов? Сложив два числа, получим 5 + 1 0 = 1 5 ,
5 часов В часов
н о н а часах нет такой отметки, там всего 1 2 часовых
делений. Чтобы определить время, мы выполняем
вычитание 1 5 - 12 3, что дает 3 часа. (Обычно мы
различаем время до и после полудня, но для модуль­
ной арифметики это не имеет значения . )
Чтобы проверить расчеты , вращайте часовую
стрелку
на 5 часов вперед, начиная с 1 О часов. Она
8
окажется на делении , которое соответствует 3 часам
7
5
6
(рис. 1 3.2) .
Если сейчас 1 О часов, то какое время будет через
Рис. JЗ.2. 1 О часов +
200 часов? Результат сложения 200 + 10 = 2 1 0 , что
5 часов 3 часа
намного больше 1 2. Поскольку один полный оборот
=

=

=

230

Гл ава 1 3

возвращает часовую стрелку в ее исходную позицию,
задачу можно решить, последовательно вычитая 1 2
(что соответствует одному полному обороту) до тех
пор, пока не получим число, меньшее 1 2. Выполнив
вычитание 2 1 0 - 1 2 , мы получаем число 1 98, которое
все еще больше, чем 1 2 , поэтому продолжаем вычи­
тать 1 2 , пока разность не станет меньше 1 2. В данном
случае окончательным ответом будет 6. Таким обра- Рис. JЗ.З. 1 О часов +
зом, если сейчас 1 О часов, то спустя 200 часов часовая
200 часов 6 часов
стрелка покажет 6 часов (рис. 1 3.3) .
Если хотите проверить расчеты, вращайте часовую стрелку на 200 часов
вперед, и вы убедитесь в том, что она остановится на отметке 6 часов. Но
нам гораздо проще заставить компьютер выполнить соответствующие вы­
числения с помощью оператора деления с остатком .
=

О ператор деnения с остатком
Для записи модульных выражений используют опер атор деления с остат­
(шodulo operator) , сокращенно mod. В Python оператор mod обозна­
чается символом процента ( % ) . Этот оператор возвращает остаток от де­
ления целых чисел. Например, 2 1 + 5 = 4 с остатком 1 , и 2 1 % 5 = 1 . Точно
так же 1 5 % 1 2 равно 3, аналогично тому, как 15 часов - это также 3 часа.
Чтобы увидеть, как работает оператор mod, введите в интерактивной обо­
лочке следующие выражения.
ком 1

-

>>> 2 1 % 5
1
> > > ( 1 0 + 2 0 0 ) % 12
6
>>> 10 % 10
о

>>> 2 0 % 1 0
о

Мы уже знаем, что если к времени 1 0 часов прибавить 200 часов, то
стрелка на 1 2-часовом циферблате покажет 6 часов. Соответственно, ре­
зультатом выражения ( 1 0 + 200) % 1 2 будет 6. Обратите внимание на то,
что применение оператора mod к двум числам , которые делятся без остат­
ка, дает О, как, например, в случае операций 1 0 % 1 0 или 20 % 1 0 .
1

Также существует неформальный термин

деление п о модулю, однако его использование н е рекоменду­
- Примеч. ред.

ется ( h t t p s : / / r u . w i k i p e d i a . о r g / w i k i / Д е л е н и е_с_о с т а т ком) .

Аффинное шифрование с помощью модул ьной ариф метики

23 1

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

Нахождение множитеnей дп• вычисnения
наи6оnьwеrо о6щеrо деnитеnя
Множите.ли это числа, произведение которых равно заданному числу.
Рассмотрим операцию 4 6 = 24. Здесь 4 и 6 - множители , на которые раз­
бивается число 24. Поскольку представленное в таком виде число делится
на любой из своих множителей нацело (без остатка} , их также называют
делителя.ми данного числа.
Число 24 можно разбить на множители разными способами :
8 . 3 = 24,
-

·

1 2 . 2 = 24,
24 . 1 = 24.
Поэтому множителями числа 24 являются числа 1 , 2, 3, 4, 6 , 8, 12 и 24.
Рассмотрим множители числа 30:
1 . 30 = 30,
2 . 1 5 = 30,
3 . 1 0 = 30,
5 . 6 = 30.
Таким образом , множителями числа 30 являются числа 1, 2 , 3, 5, 6, 10, 1 5
и 30. Имейте в виду, что любое число всегда будет иметь в качестве множи­
телей 1 и само себя , поскольку результат умножения 1 на число равен дан­
ному числу. Также обратите внимание на то, что списки множителей для
чисел 24 и 30 содержат общие множители 1 , 2, 3 и 6. Из этих множителей
наибольшим является число 6, поэтому 6 называется наиболъшим общим мно­
жителем, или, что встречается чаще, наиболъшим общим делителем (НОД ) ,
чисел 2 4 и 30.
Самый простой способ нахождения НОД двух чисел - визуализация их
множителей. Для этого мы воспользуемся счетнъtми палочками Кюизенера
брусочками, составленными из ряда квадратиков, количество которых
равно представляемому числу. Счетные палочки облегчают наглядную де­
монстрацию арифметических операций. Использование счетных палочек
Кюизенера для визуализации операций 3 + 2 = 5 и 5 3 = 1 5 проиллюстри­
ровано на рис. 1 3.4.
-

-

·

232

Глава 1 3

Длина составленных вместе палочек 3 и

2 равна длине палочки 5. С помощью счет­

ных палочек можно находить даже резуль­
тат умножения двух чисел , строя прямо­
угольник, сторонами которого являются
"'
палочки , соответствующие умножаемым l s :
: :
з
числам. Количество квадратиков в таком
Рис. JЗ.4. Представление операци й
прямоугольнике дает искомый результат.
сложения и умножения с помощью
Пусть палочка длиной 20 квадратиков
счетных палочек Кюизенера
представляет число 20. Тогда число является делителем 20, если некоторое количество соответствующих ему палочек укладывается вдоль палочки 20 вро­
вень с ней. Как показано на рис. 1 3 .5 , числа 4 и 1 0 являются делителями
20, поскольку путем составления соответствующих палочек в ряд можно
получить палочку длиной 20 квадратиков.
.

1 20 :
1 1 0:
1<
Рис. JЗ.5.

1 ·<

: :
1 1 0:
1< : : 1<

.

i
1
1

1 ,.;

Использование счетных палочек Кюизенера для иллюстрации того факта,
что числа 4 и 1 О являются делителями числа 20

В то же время числа 6 и 7 не являются делителями числа 20, поскольку
укладыванием соответствующих палочек в ряд не удается добиться того ,
чтобы края результирующей палочки и палочки , состоящей из 20 квадра­
тиков , совпадали (рис. 1 3.6 ) .

1 20 :
1<
1<

: :
1<
1< :

: :
1<
1< : :

:
:
1

'

i
!

1

1
1
1 Сл и шком м н ого!

'

Сл и шком мало!

Рис. JЗ.6.

Использование счетных палочек Кюизенера для иллюстрации того факта,
что числа 6 и 7 не являются делителями числа 20

Наибольшим общим делителем (НОД) двух палочек, т.е. двух представ­
ляемых ими чисел , является самая длинная из палочек, составлением кото­
рых можно уравнять длины обоих рядов (рис . 1 3. 7 ) .

Аффи нное шифрование с помощью модул ьной ариф метики

233

1

::·� � � � � � � i � � � � � � � i . � � � � � � � 1


1

Рис. JЗ. 7.

Исп ол ьзов ание счетных полочек Кюизенера для
д ем о нстрации над чисел 1 6 и 24

В этом примере ряд из па.тючек длиной 8 квадратиков можно выложить
вровень как с палочкой 24, так и с палочкой 1 6. Поэтому искомым НОД
является число 8.
Теперь можно написать функцию для нахождения НОД двух чисел.
Груnnовое nрисваивание

Функция gcd ( ) , которую мы собираемся написать, будет возвращать
НОД двух чисел. Но сначала рассмотрим полезный трюк, который называ­
ется группов'Ым присваиванием. С помощью этрго трюка в одной инструкции
присваивания можно задать значения сразу нескольким переменным. Вве­
дите в интерактивной оболочке следующие выражения .
> > > spam , egqs
> > > spam
42
> > > eqqs
' He l l o '
>>> а , Ь , с , d
>>> а
' Al i ce '
>>> d
' Da n i e l l e '

=



42 ,

1 Hello 1

[ ' Alice ' ,

' Brienne ' ,

' Carol ' ,

' Danielle ' ]

Переменные слева и сп р а11а от оператора присваивания = разделяют­
ся запять��и. Количество элементов в списке должно совпадать с количе­
ством переменных, указанных слева от оператора присваивания . В про­
тивном случае Pythoп выдаст сообщение об ошибке, указывающее на то ,
что необходимо предостави1ъ больше значений или же количество значе­
ний слишком велико.

234

Глава 1 3

Одно из основных применений группового присваивания - перестанов­
ка значений двух переменных. Введите в интерактивной оболочке следую­
щие инструкции.
>>> spam = ' hello '
> > > eqgs = ' goodЬye '
> > > spam , eqgs = eggs , spam
> > > spam
' goodbye '
> > > eggs
' he l l o '

Присвоив значение ' he l l o ' переменной spam и значение ' goodbye '
переменной egg s , мы затем меняем значения местами с помощью группо­
вого присваивания . Этот трюк с перестановкой значений будет применен
для реализации алгоритма Евклида, с помощью которого ищется НОД.

Апrоритм Евкпида дnя нахождения НОД
На первый взгляд, задача нахождения НОД кажется простой: для этого
нужно определить все множители обоих чисел, а затем взять наибольший
из них. Но в случае больших чисел все оказывается вовсе не таким легким.
Евклид, греческий математик, живший около 2000 лет тому назад, разра­
ботал короткий алгоритм нахождения НОД двух чисел с помощью модуль­
ной арифметики. Ниже приведен код функции gcd ( ) , которая реализует
этот алгоритм на языке Python и возвращает НОД двух чисел, а и Ь.
de f g c d ( а , Ь ) :
whi l e а ! = О :
а, Ь = Ь % а, а
return Ь

Функция gcd ( ) получает два числа, а и Ь, а затем выполняет групповое
присваивание в цикле для нахождения НОД. Работа функции gcd ( ) по на­
хождению НОД чисел 24 и 32 представлена на рис. 1 3.8.
Детальное рассмотрение того, как работает алгоритм Евклида, выходит
за рамки книги, но можете быть уверены в том , что эта функция возвраща­
ет НОД двух чисел , которые ей передаются в качестве аргументов. Если
вызвать ее в интерактивной оболочке, передав ей числа 2 4 и 3 2 в качестве
параметров а и Ь, то она вернет число 8 .
> » gcd ( 2 4 , 3 2 )
в

Аффинное шифрование с помощью модул ьной ариф метики

235

а,

Ь

=

Ь %

а,

Ь

=

32

а,

Ь

=

а,

ь

=

а,

Ь

=

а,

Ь

=

Ь

=

а,

%

а

24 , 24

--

В ычисление Ь mod а

24

--

Цикл продолжается, поскольку а ! = О

.___.
8

,

ь,--�·-·
%

24

а,

а

__

Груп повое присваивание
с перестановкой значений

% 8, 8

--

Вычисление Ь mod а

о

--

Цикл завершается , поскольку а = О

t

,

8

8

--

Окончательным значением Ь
является Н ОД

Рис. JЭ.8.

Схема работы функцнн g c d ( )

Огромным достоинством функции gcd ( ) является то, что она легко
справляется с большими числами.
> > > qcd ( 4 0 9 1 1 92 4 3 , 8 7 7 8 0 2 4 3 )
6837

Функция gcd ( ) пригодится нам при выборе допустимых ключей для
мультипликативного и аффинного шифров, о чем мы поговорим далее.

Как ра6ота1От му11ьтиn11икативнь1й и аффинный
шифры
В шифре Цезаря шифрование и дешифрование символов реализуется
путем их преобразования в числа, добавления или вычитания ключа и по­
следующего обратного преобразования новых чисел в символы.
В случае шифрования с помощью мулътипликативного шифра мы умно­
жаем индекс на ключ. Например , если мы шифруем букву 'Е' с помощью
ключа 3, то нужно найти ее индекс (4) и умножить его на ключ ( 3 ) , чтобы
получить индекс зашифрованной буквы ( 4 3 = 1 2 ) , которому соответствует
буква 'М' .
Если результат умножения превышает общее количество букв, то муль­
типликативный шифр сталкивается с той же проблемой "завертывания" ,
что и шифр Цезаря, но теперь мы можем использовать для решения про­
блемы оператор mod. Например, в шифре Цезаря переменная S YMBOLS со­
держала строку ' ABCDE FGH I JKLMNOPQRSTUVWXYZ abcde fghi j klmnopqr s tu
vwxy z l 2 3 4 5 6 7 8 9 0 ! ? . ' . Ниже приведена таблица, включающая несколь­
ко первых и последних символов строки SYMBOL S вместе с их индексами.
·

236 Глава 1 3

о
А

в

2

3

4

5

6

59

60

61

с

D

Е

F

G

8

9

о

62

63

64

65

?

Рассмотрим, во что превращаются символы в результате шифрования с
помощью ключа 1 7. Чтобы зашифровать символ 'F' с помощью этого клю­
ча, мы умножаем его индекс 5 на 1 7 и находим остаток от деления получен­
ного результата на 66, чтобы учесть "завертывание" 66-символьного набо­
ра. Результат операции ( 5 1 7 ) mod 66 равен 1 9 , а числу 1 9 соответствует
символ 'Т' . Поэтому в результате шифрования буквы 'F' с помощью мульти­
пликативного шифра и ключа 1 7 мы получаем букву 'Т' . Ниже приведены
две строки, представляющие все символы используемого алфавита и их за­
шифрованные версии. Символу в верхней строке соответствует символ в
нижней строке, имеющий тот же индекс.
·

' ABCDE FGH I JKLMNOPQRSTUVWXYZ abcde fgh i j klmnopqr s t uvwxy z l 2 3 4 5 67 8 9 0 ! ? . '
' ARi zCTk2 EVm4 GXo 6 I Z q 8 Kb s 0 Mdu ! O fw . QhyBS j l DU 1 3 FWn5HYp7 Ja r 9Lct Nev? Pgx '

Сравните этот результат с тем , который мы получили бы с помощью
шифра Цезаря , где символы шифруются путем простого сдвига:
' ABCDE FGH I JKLMNOPQRSTUVWXYZ abcde fghi j klmnopqr s t uvwxy z l 2 3 4 5 6 7 8 9 0 ! ? . '
' RSTUVWXY Z abcde fgh i j klmnopqr st uvwxyz l 2 3 4 5 6 7 8 9 0 ! ? . ABCDEFGH I JKLMNOPQ '

Как нетрудно заметить, мультипликативный шифр с ключом 1 7 обе­
спечивает более непредсказуемое распределение символов в зашифро­
ванной строке, поэтому его труднее взломать. Однако выбор ключей для
мультипликативного шифра требует внимательности , о чем мы погово­
рим далее.

Вы6ор доnусrимых мулынплнкаrивных ключей
Не всякое число годится на роль ключа мультипликативного шифра. На­
пример, выбрав 1 1 в качестве ключа, вы получите следующий результат:
' ABCDE FGH I JKLMNOPQRSTUVWXY Zabcde f gh i j klmnopqr s t uvwxyz l 2 3 4 5 6 7 8 9 0 ! ? . '
' ALWhs 4ALWhs 4 ALWhs 4ALWhs 4 ALWhs 4ALWh s 4 ALWhs 4ALWh s 4 ALWh s 4ALWh s 4 ALWhs 4 '

Такой ключ нам не подходит, поскольку все символы 'Х , 'G' и 'М' шифру­
ются одной и той же буквой: '!\. . Встретив в зашифрованном тексте символ
'Х , вы не будете знать, в какой символ он должен быть дешифрован. Анало­
гичная проблема возникает при шифровании букв 'S' , 'У' , 'е' , 'k' и др.
В мультипликативном шифре ключ и размер символьного набора долж­
ны быть взаимно простыми числами. Два числа называются взаимно про­
стыми, если их НОД равен 1 . Иными словами , они не должны иметь ни­
каких других общих множителей , кроме 1 . Например, числа numl и num2

Аффинное шифро ва ние с помощью модул ьной а рифметики

237

взаимно просты, если gcd ( numl , num2 ) == 1 , где numl - ключ, а num2
размер символьного набора. Поскольку в предыдущем примере 1 1 (ключ)
и 66 (размер символьного набора) имеют НОД , не равный 1 , то они не
являются взаимно простыми, откуда следует, что число 1 1 не может быть
использовано в качестве ключа мультипликативного шифра. Отметим, что
свойство взаимной простоты не требует, чтобы каждое из двух чисел было
простым.
В случае использования мультипликативного шифра очень важно знать,
как правильно применять модульную арифметику и функцию gcd ( ) . С по­
мощью функции gcd ( ) мы выясняем , являются ли два числа взаимно про­
стыми, так как это необходимо для определения того, пригодно ли число
для использования в качестве ключа мультипликативного шифра.
Для набора, состоящего из 66 символов, мультипликативный шифр мо­
жет иметь только 20 различных ключей , т.е. даже меньше, чем в случае
шифра Цезаря ! Но можно скомбинировать мультипликативный шифр с
шифром Цезаря и получить более мощный аффинный шифр, к обсужде­
нию которого мы сейчас перейдем .
-

Шифрование с 1омощью аффинноrо шифра
Одним из недостатков мультипликативного шифра является то, что
буква 'А:. всегда транслируется в букву 'А:. . Причина заключается в том, что
буква 'N имеет нулевой индекс, а умножение нуля на любое число всегда
дает нуль. От этой проблемы можно избавиться , добавив второй ключ для
шифрования с помощью шифра Цезаря после того, как будет выполнено
мультипликативное шифрование. Этот дополнительный шаг превращает
мультипликативный шифр в аффинный.
Аффинн'Ый шифр имеет два ключа: А и В . Ключ А это целое число, ко­
торое умножается на индекс буквы . К полученному произведению прибав­
ляется ключ В, после чего вычисляется остаток от деления полученной
суммы на 66, как это делается в оригинальном шифре Цезаря. Это озна­
чает, что аффинный ключ имеет в 66 раз больше возможных ключей, чем
мультипликативный. Это также гарантирует, что буква 'А:. не будет всегда
транслироваться в саму себя в шифротексте.
Процесс дешифрования аффинного шифра - это зеркальное отраже­
ние процесса шифрования (рис. 1 3.9) .
Для дешифрования аффинного шифра мы используем операции, об­
ратные по отношению к использованным при шифровании. Рассмотрим
процесс дешифрования и способ вычисления модульных обращений более
подробно.
-

238

Глава 1 3

Шифрование
Открытый

Умножение
на ключ А

текст

.,.. П рибавление .,.. Деление
ключа В

.,..

Шифротекст

с остатком
на размер
набора
символов

Дешиф ро в ан и е
Открытый .., Деление
текст

.., Умножение

с остатком

на модульное

на размер

обращение

набора

Шифротекст

.., Вычитание
ключа В

ключа А

символов

Рис. JЗ. 9.

Процессы шифрования и де ш ифрования с испол ьзов анием аффинного ш ифра

Дешифрование ' помощью аффинноrо шифра
В шифре Цезаря для шифрования используется онерация сложения , а
для дешифрования - операция вычитания . В аффинном шифре для шиф­
рования используется операция умножения. Логично предположить, что
для дешифрования аффинного шифра потребуется операция деления . Но
если вы попытаетесь это сделать, то увидите, что такой подход не работа­
ет. Для дешифрования аффинного шифра необходимо выполнить умноже­
ние на модульное обращение ключа. Эта операция является обращением
операции mod, которая применялась в процессе шифрования .
Модулъное обращение ( modular inverse) двух чисел представляется выра­
жением ( а i) % т = 1 , где i - модульное обращение, а а и т - два числа.
Например, модульным обращением 5 mod 7 будет некоторое число i, та­
кое , что ( 5 i) % 7 = 1 . Можно попытаться найти это число методом грубой
силы, выполнив следующие вычисления:
1 не является модульным обращением 5 шоd 7 , поскольку ( 5 1 ) % 7 = 5;
·

·

·

2 не является модульным обращением 5 шоd 7 , поскольку ( 5 2) % 7 = 3 ;
·

3 является модульным обращением 5 mo > ' НELLO ' . iaupper ( )
True
О » > ' HELLO WORLD 1 2 3 ' . i supper ( )
True
8 » > ' hello ' . islower ( )
True
>>> 1 12 3 1 . i aupper ( )
Fa l s e
> > > ' . ialo-r ( )
Fa l s e
'

2 8 2 Глава 1 6

Инструкция О возвращает значение True, поскольку строка ' HELLO
WORLD 1 2 3 ' содержит по крайней мере один символ в верхнем регистре и
не содержит букв в нижнем регистре. Наличие чисел в этой строке никак
не влияет на результат. Метод ' he l l o ' . i s l ower ( ) ( 8 ) возвращает значе­
ние T ru e , поскольку строка ' h e l l o ' содержит по крайней мере один сим­
вол в нижнем регистре и не содержит букв в верхнем регистре. В послед­
них двух случаях оба метода возвращают значение F а 1 s e , так как строки не
содержат букв.
Вернемся к рассмотрению функции t ra n s l a t eMe s s a g e ( ) , в которой
используется метод i s uppe r ( ) .

Сохранение perиtrpa 6укв ' помощью меrода isupper ()
В функции t r a n s l a t eMe s s a g e ( ) с помощью строкового метода
i s uppe r ( ) гарантируется, что преобразованные буквы сохранят исход­
ный регистр.
59.
60 .
61 .
62 .

i f s ymЬ o l . i s uppe r ( ) :
t rans l at e d += cha r s B [ s ym i nde x ] . upper ( )
else :
t ra n s l a t e d += cha r s B [ symi ndex ] . l ower ( )

В строке 59 проверяется , содержит ли переменная s ymbol букву в верх­
нем регистре. Если это действительно так, то в строке 60 символ, храня­
щийся в позиции cha r s B [ s ymi ndex ] , преобразуется в верхний регистр и
конкатенируется со строкой, хранящейся в переменной tran s l a ted. Если
же переменная s ymЬ o l содержит букву в нижнем регистре, то в строке 62
символ, хранящийся в позиции cha r s B [ s ymindex ] , преобразуется в ниж­
ний регистр и конкатенируется со строкой , хранящейся в переменной
t ra n s l ated.
Метод i s uppe r ( ) возвращает T rue , только когда строка содержит хотя
бы одну букву в верхнем регистре. Если в переменной s ymЬol окажется не­
буквенный символ, такой, например, как ' 5 ' или ' ? ' , то условие в строке 59
вернет значение Fa l s e , и вместо строки 60 выполнится строка 62. В этом
случае метод l owe r ( ) не окажет никакого влияния на строку, так как в ней
отсутствуют буквы. Метод просто вернет исходный небуквенный символ.
Таким образом, в строке 62 учитывается возможное наличие в строке
s ymbol символов в нижнем регистре, а также небуквенных символов.
Отступ строки 63 указывает на то, что данная инструкция e l s e относит­
ся к инструкции i f s ymbo l . upp e r ( ) in cha r sA : в строке 56, поэтому
строка 65 выполняется в том случае, если символ s ymЬ o l не встречается в
строке LETTERS .
П р о гр а мм и ро ва н ие простого подста но в очного шифра

283

else :
# Символ отсутствует в наборе LETTERS ; просто добавляем его
t r a n s l at e d += syrnЬol

63 .
64 .
65 .

Мы не можем зашифровать или дешифровать такой символ, поэтому
просто присоединяем его к строке t ran s l a t e d в неизменном виде.
В строке 67 функция t rans l a t eMe s s age ( ) возвращает значение, хра­
нящееся в переменной t rans l ated, которая содержит зашифрованное
или дешифрованное сообщение:
return t ra n s l a t e d

67 .

Далее мы узнаем, как сгенерировать корректный ключ для простого
подстановочного шифра с помощью функции getRandornKey ( ) .

rенерирование сnучайных кn1Очей
Вводить строку ключа, содержащую все символы алфавита, - не слишком
приятное занятие. Справиться с этим помогает функция getRandornKey ( ) ,
которая возвращает подходящий ключ. В строках 7 1 -73 осуществляется
случайное перемешивание символов в константе LETTERS .
7 0 . de f
71 .
72 .
73 .

g e t RandomKey ( ) :
key = l i st ( LETTERS )
random . shuff l e ( ke y )
return " . j o i n ( ke y )

Пр и меч а н и е

Если хотите пош�тъ, к ак осуществляется случ айное перемешив а ние симво­
лов строки с помощъю методов l i s t () , ra n dom . sh u ffl e ( ) и j o i n () ,
прачитайте раздел "Случ айное перемешив а ние строки " в гл аве 9.

Чтобы задействовать функцию getRandornKey ( ) , измените строку 1 1
следующим образом:
11 .

myKey = qetRandomКey ( )

Поскольку используемый ключ отображается на экране в строке 20, вы
увидите, какой ключ вернула функция g e t RandornKey ( ) .

284

Глава 1 6

Вызов функции main ( )
Функция ma i n ( ) вызывается только в том случае, если файл simpkSuЬ­
Cipher.py был запущен как программа, а не импортируется в виде модуля дру­
гой программой.
76 . if
77 .

n ame
ma i n ( )

ma i n

'

·

Реэ1Оме
В этой главе вы узнали о том , как использовать списковый метод s o r t ( )
для упорядочения элементов списка и как сравнить два упорядоченных
списка между собой. Вы также познакомились со строковыми методами
i s uppe r ( ) и i s l owe r ( ) , позволяющими проверить, содержит ли строка
одни только символы верхнего или нижнего регистра. Еще вы узнали о так
называемых функциях-обертках, которые служат для вызова других функ­
ций , обычно с той или иной модификацией аргументов.
Количество возможных ключей простого подстановочного шифра на­
столько велико, что его невозможно взломать методом грубой силы. Это
делает его неуязвимым для методик взлома, которые мы применяли в от­
ношении других шифров. Тем не менее в главе 1 7 вы узнаете, как взломать
простой подстановочный шифр. Вместо простого перебора всех возмож­
ных ключей методом грубой силы мы используем более изощренный и
сложный алгоритм.

Ко н тр ольны е вопросы

Ответы на контрольные воп росы при ведены в п р иложении Б.
1.

П очему взлом п ростого подстановочного шифра методом грубой силы невоз­
можен даже с помощью сверхмощного компьютера?

2.

Что будет содержать переменная spam после выполнения следующего кода?
spam = [ 4 , 6 , 2 , 8 ]
spam . sort ( )

3.
4.

Что такое фун кция-обертка?

5.
6.

Что возвращает метод ' HELLO 1 2 3 ' . i s uppe r ( ) ?
Что возвращает метод ' 1 2 3 ' . i s l owe r ( ) ?

Что возвращает метод ' he l l o ' i s l owe r ( ) ?


П рогр а ммиро вание простого подстановочного шифра 285

ВЗЛО М П РОС Т ОГО
П ОДСТАНО ВОЧ Н ОГО Ш И Ф Р А
Шифроваиие - осиовной споr.об за�14итъt прива:rrт осrпи.
Шифроваиш в су щ иосrпи делает и11.формацию 11 едосту rт ой
ширтсой обществmиот�u.. За·1шн·ы, запрещающие
испол'ЬЗовшние криптографии, беr.сuл·ь·н-ы за пределами
гртщ·1s ·кои·кретного государства ".
"

,

,

.

Эрик Хъюз, "Манифест шифропа·нка " ( 1 993)
В главе 1 б вы узнали о невозможности

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




•••

Шаблоны слов, слова-ка ндидаты и ва риа нты дешифрования букв
Регулярные вы ражения
Метод s ub ( ) регул я рных вы ражений

Дешифрование с исnоnьэованием сnоварных
wа6nонов
Атака методом грубой силы предполагает простой перебор всех воз­
можных ключей. Если ключ подобран корректно, то результат дешифрова­
ния будет представлять собой осмысленный текст. Однако путем предвари­
тельного анализа шифротекста можно значительно сократить количество
потенциальных ключей, подлежащих тестированию, а иногда даже опре­
делить полный или частичный ключ.
Предположим, что исходный текст в основном состоит из английских
слов, включенных в словарь наподобие того, который мы использовали в
главе 1 1 . Шифротекст, естественно, не будет содержать подобных слов, но
в нем все равно будут встречаться группы букв, разделенных пробелами ,
подобно словам в предложениях. В отношении таких групп мы употребля­
ем термин шифрослово. В подстановочном шифре каждой букве алфавита
соответствует ровно одна уникальная зашифрованная буква. Такие бук­
вы мы называем шифробуквами. Поскольку каждая буква исходного текста
шифруется одной конкретной шифробуквой и в этой версии шифра мы
не трогаем пробелы, в исходном тексте и шифротексте будут встречаться
одни и те же шаблоны слов.
Например, сообщению "MISSISSIPPI SPILr может соответствовать шиф­
ротекст "RJBBJBBJXXJ BXJHH". В обоих случаях первое слово содержит
одинаковое количество букв. То же самое относится и ко второму слову.
Исходный текст и шифротекст характеризуются одним и тем же шаблоном
(т.е. закономерностью) расположения букв и пробелов. Обратите внима­
ние на то, что повторяющиеся буквы сообщения встречаются в том же ко­
личестве и в тех же позициях шифротекста.
Таким образом , можно смело предположить, что каждому шифрослову
соответствует некое слово, входящее в словарь английского языка, и их ша­
блоны должны совпадать. Следовательно, если бы мы смогли найти в сло­
варе слово, в которое дешифруется данное шифрослово, то это указало бы
нам, как дешифровать каждую его шифробукву. И если с помощью данной
методики нам удастся восстановить достаточное число шифробукв, то мы
сможем полностью дешифровать сообщение.

Поиtк 111а6понов спов
В качестве примера исследуем шаблон шифрослова "HGHHU". В нем
видны определенные закономерности, которые должны быть свойствен­
ны и соответствующему слову в исходном тексте. Оба слова должны иметь
следующие общие характеристики:
288

Глава 1 7

1 ) длина слова составляет пять букв;
2) первая , третья и четвертая буквы слова должны быть одинаковыми;
3) слово должно содержать три разные буквы : первую, вторую и пятую.
Задумаемся над тем, какие английские слова укладываются в этот шаблон. Одним из слов, удовлетворяющих описанному критерию, является
слово рирру, состоящее из пяти букв ( ' Р ' , ' U ' , ' Р ' , ' Р ' , 'У' ) , три из которых
разные ( ' Р ' , ' U ' , 'У' ) , причем их позиции соответствуют шаблону ( ' Р ' - пер­
вая, третья и четвертая, 'U' вторая и 'У' - пятая ) . Слова тотту, ЬоЬЬу,
lиUs и паппу тоже подпадают под это описание. Все они, равно как и любое
другое слово из файла словаря, удовлетворяющее заданным критериям, яв­
ляются воз м ожными вариантами дешифрования слова "HGHHU".
Чтобы представить шаблон слова в форме, понятной ко м пьютеру, мы
преобразуем его в набор чисел, разделенных точками. Эти числа указыва­
ют на повторяемость букв в шаблоне.
Составить шаблон слова несложно: первой букве ставится в соответ­
ствие число О, а первому вхождению любой другой буквы - следующее
число, на единицу превышающее предыдущее использованное. Напри­
мер, шаблоном слова cat будет 0 . 1 .2, а шаблоном слова classification
0. 1 .2.3.3.4.5.4.0.2.6.4.7.8.
В случае простого подстановочного шифра не имеет значения, какой
именно ключ используется для шифрования: слово исходного текста и со­
ответствующее ему шифрослово всегда будут подчиняться одному и тому же
шаблону. Если шаблон шифрослова "HGHHU" - 0. 1 .0.0.2, значит, исходное
слово имеет такой же шаблон.
-

-

Поиск во1можных варианrов дешифровано 6укв
Чтобы дешифровать шифрослово "HGHHU" , в файле словаря нужно
найти все слова, соответствующие шаблону 0. 1 .0.0.2. Мы будем называть
такие слова кандидатами для данного шифрослова. Вот как, например , вы­
глядит список кандидатов для шаблона "HGHHU":
• рирру;

тотту;

ЬоЬЬу;
• lиlls,

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

289

дешифрования для всех шифробукв, а затем определить фактические бук­
вы исходного текста, действуя методом исключения . В табл. 1 7 . 1 приведе­
ны варианты дешифрования шаблона "HGHHU".
Табл и ца 1 7. 1 . Варианты дешифрования шаблона " H G H H U "

Шифробук вы

н

G

н

н

u

Вар и а нты деwифровани•

р

u

р

р

у

м

о

м

м

у

в

о

в

в

у

L

u

L

L

s

N

А

N

N

у

Вот что мы получаем на основе табл. 1 7. 1 .
1 . Для буквы ' Н ' имеются следующие варианты дешифрования: ' Р ' ,
'М ' , ' В ' , ' I..: и N
2. Для буквы 'G' имеются следующие варианты дешифрования: 'U' , 'О'
и 'А' .
3. Для буквы 'U' имеются следующие варианты дешифрования: 'У' и 'S' .
4. Для всех остальных шифробукв, кроме 'Н' , 'G' и ' U ' , в данном при­
мере нет вариантов дешифрования .
'

'

.

В результате формируется дешифрова.лъный словаръ, в котором буквам ал­
фавита ставятся в соответствие потенциальные варианты дешифрования.
По мере накопления зашифрованных сообщений мы будем находить ва­
рианты дешифрования для каждой буквы алфавита, но в данном примере
наш шифротекст включает лишь шифробуквы 'Н' , 'G' и ' U ' , а варианты
дешифрования для остальных шифробукв не определены.
Обратите внимание на то, что буква 'U' имеет только два варианта де­
шифрования ( 'У' и 'S' ) ввиду перекрывания слов-кандидатов, многие из
которых заканчиваются буквой 'У' . Чем болъше перекрывание, тем менъше ва­
риантов дешифрования и тем леZ'Че вычислитъ, в какую букву должна бытъ дешиф­
рована та или иная шифробуква.
Чтобы перевести табл. 1 7 . 1 в код на языке Python , представим дешифро­
вальный словарь значениями словарного типа (пары "ключ: значение" для
букв ' Н ' , ' G ' и ' U ' выделены полужирным шрифтом ) .
{ 'А' : [ ] , ' В ' :
'А' ] , ' Н ' : [ ' Р '
'М' : [] , 'N' : [
'U' : [ 'У' , ' S ' ]

290

Глава 1 7

[ ] , ' С ' : [ ] , ' D ' : [ ] , ' Е ' : [ ] , ' F ' : [ ] , 'G' : [ 'U' , '0' ,
, 'М' , 'В ' , 'L' , 'N' ] , ' I ' : [ ] , ' J ' : [ ] , ' К ' : [ ] , ' L ' : [ ] ,
) , 'О' : [] , 'Р' : [] , 'Q' : [] , 'R' : [] , 'S' : [] , 'Т' : [] ,
, 'V' : [] , 'W' : [] , 'Х' : [] , 'У' : [ ] , ' Z ' : [] )

Этот словарь включает 26 пар "ключ: значение": по одному ключу для
каждой буквы алфавита и список вариантов дешифрования для каждой
буквы. На данный момент в словаре содержатся варианты дешифрования
для букв ' Н ' , ' G ' и ' U ' . Всем остальным ключам соответствуют значения в
виде пустых списков, [ ] , поскольку варианты дешифрования для них пока
не определены.
Если нам удастся свести количество вариантов дешифрования для не­
которой шифробуквы всего лишь к одной букве за счет перекрывания де­
шифровальных словарей, то мы сможем расшифровать данную букву. Даже
если нам не удастся добиться этого для всех 26 шифробукв, может оказать­
ся так, что, взломав значительную часть шифробукв, мы будем в состоянии
расшифровать большую часть шифротекста.
Теперь, когда вы познакомились с основной терминологией, перейдем
к рассмотрению алгоритма взлома.

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

Определите шаблоны для всех шифрослов в тексте.
2. Найдите слова-кандидаты, в которые может быть расшифровано
каждое шифрослово.
3. Для каждого шифрослова создайте словарь, представляющий вари­
анты дешифрования для каждой шифробуквы.
4. Объедините словари в единую структуру, которую мы назовем пере­
сечением словарей.
5. Удалите из пересечения буквы, для которых удалось установить, ка­
ким шифробуквам они соответствуют.
б. Дешифруйте шифротекст с помощью установленных шифробукв.
Чем больше слов в шифротексте, тем выше вероятность перекрывания
словарей и тем меньше остается вариантов дешифрования для каждой
шифробуквы. Это означает, что чем длиннее сообщение, зашифрованное с помо­
щъю простого подстановочного шифра, те.м легче его взломатъ.
Прежде чем рассматривать исходный код программы, обсудим , как
упростить выполнение первых двух этапов описанной выше процедуры
взлома. Мы используем тот же файл словаря , что и в главе 1 1 . Кроме того,
нам понадобится вспомогательный модуль wordPatterns.frY для получения
упорядоченного списка шаблонов по каждому слову из словаря.
Взл ом простого подста но вочного шифра

29 1

Модуnь Word Patterns
Чтобы получить шаблоны слов для каждого слова в словаре dictioпary. txt,
загрузите файл makeWordPatterns.py, доступный на сайте издательства (см.
введение ) . Убедитесь в том , что оба этих файла находятся в той же папке,
что и программа simpleSиbHacker.py, рассматриваемая в этой главе.
Программа makeWordPatterns.py содержит функцию g e tWo r d P a t t e r n ( ) ,
которая получает строку ( например, ' puppy ' ) и возвращает ее шаблон ( на­
пример, ' О . 1 . О О . 2 ' ) . Когда вы запустите программу, она должна создать
модуль Python wordPatterns.py, который включает единственную инструк­
цию присваивания , занимающую более 43 ООО строк кода!
.

a l l P a t t e rn s
{ ' 0 . 0 . 1 ' : [ ' EEL ' ] ,
' О . О . 1 . 2 ' : [ ' EELS ' , ' OO Z E ' ]
' 0 . 0 . 1 . 2 . 0 ' : [ ' EE R I E ' ] ,
' О . 0 . 1 . 2 . 3 ' : [ ' AARON ' , ' LLOYD ' ,
=

,

- - опущено - -

' OO Z E D ' ] ,

Переменная a l l P a t t e rns содержит словарь, в котором ключами явля­
ются строки шаблонов слов, а значениями - английские слова, соответ­
ствующие данному шаблону. Например, чтобы найти все cJioвa, соответ­
ствующие шаблону 0. 1 .2. 1 .3.4.5.4.6.7.8, введите в интерактивной оболочке
следующие инструкции.
> > > iшport wordPatterns
> > > wordPatterns . allPatterns [ ' 0 . 1 . 2 . 1 . З . 4 . 5 . 4 . 6 . 7 . 8 ' ]
[ ' BENE F I C IARY ' , ' HOMOGENE I T Y ' , ' MOTORCYCLE S ' ]

В словаре a l l Pa t t e rn s значением ключа ' 0 . 1 . 2 . 1 . 3 . 4 . 5 . 4 . 6 . 7 . 8 '
является список [ ' BENE F I C IARY ' , ' HOMOGENE ITY ' , ' MOTORCYCLES ' ] ,
который содержит три слова, соответствующих данному шаблону.
П р и мечан и е

Если при попъtтке uмпортироватъ модулъ wordPa t t e rn s в интерактив­
ной оболачке въt получите сообщение об ошибке Modu 1 eNo t Fo u n dEr ror, вве­
дите следующие инструкции.
>» iшport sys
> > > sys . path . append ( 1 .-ня_nаn.11:• ' )

В кшчестве имени папки укажите каталог, в котоfюм сохранен файл
wordPatterns.py. Тем самым въt сообщите интерпретатору, в какой папке сле­
дует искатъ недостающие модули.
292

Глава 1 7

Исходный код nроrраммы Simple SuЬs ti tution
Backer
Откройте в редакторе файлов новое окно, выбрав пункты меню File�
N ew File. Введите в этом окне приведенный ниже код и сохраните его в фай­
ле simpleSubHacker.py. Убедитесь в том , что файлы pyperclip.py, simpleSubCipher.
ру и wordPatterns.py находятся в той же самой папке. Запустите программу,
нажав клавишу .
simpleSubHacker.py

1 . # Программа взлома простого подстановочного шифра
2 . # https : / /www . no s ta rch . com/ crackingcode s / ( BS D L i censed )
3.
4 . import o s , re , сору , pypercl i p , s imp l e S ubCiphe r , wordPatterns ,
ma keWo rdPa t t e rns
5.
6.
7.
8.
9.
1 0 . LETTERS = ' ABC DE FGHI JKLМNOPQRSTUVWXYZ '
1 1 . nonLe t t e rsOrSpace Pattern = re . compi l e ( ' [ лA- Z \ s ] ' )
12 .
1 3 . de f ma i n ( ) :
14 .
me s s age = ' S y 1 nlx sr рууасао 1 ylwj e i swi upar lul sxrj i s r
sxrj sxwj r , ia e smm rwct j sxs za s j wmpramh , l x o txma rr j i a
aqsoaxwa s r pqace i amnsxu , i a e smm cayt ra j p fams aqa s j . S y ,
р х j ia p j i a c i l xo , i a s r рууа сао rpna j i sxu e i swi l yypcor 1
c a l rpx урс lwj sxu sx lwwpco l xwa j p i s r sxrj sxwj r , i a e smm
l wwabj s j aqax рх j i a rmsui j a rj aqs oaxwa . Jia pcsusx ру
nhj i r s r agbml sxao sx j i sr e l h . - Facj c lxo Ct rramm '
15.
# Определяем варианты дешифрования шифротекста
16.
print ( ' Ha c k i ng . . . ' )
17 .
l e t t e rMapping = hackS imp l e Sub ( me s sage )
18 .
19.
# Выводим резул ь т а ты
20.
print ( ' Mapping : ' )
21.
print ( l e t t e rMapp i ng )
22 .
p r i nt ( )
23.
print ( ' Original ciphe rtext : ' )
24 .
print ( me s s age )
25.
print ( )
26.
print ( ' Copying hacked me s s age to cl ipboa rd : ' )
27 .
28 .
hackedМe s s age = decryptWi thCiphe r l e t t e rMapping ( me s sage ,
l e t t e rMapp i ng )

Взл ом простого подста но вочного шифра 293

29.
30 .
31 .
32 .
3 3 . de f
34 .
35 .

pype r c l i p . copy ( hac kedМe s s age )
print ( ha c kedМe s sage )

g e t B l ankCiphe r l e t t e rMapping ( ) :
# Возвраща е т пустой дешифр о в аль ный словарь
return { ' А ' : [ ) , ' В ' : [ ) , ' С ' : [ ] , ' D ' : [ ] , ' Е '
' F' : [ ) , 'G' : [) , 'Н' : [ ] , , I , : [ ] , ' J' : [ ] ,
'L' : [ ) , 'М' : [) , 'N ' : [ ] , 'О' : [ ) , 'Р' : [] ,
'R' : [) , , S' : [] , 'Т' : [] , 'U' : [] , 'V' : [] ,
'Х' : [] , 'У' : [] , ' Z ' : [) }

: [) ,
'К' : [] ,

'Q' :

[] ,

'W' :

[],

36 .
37 .
3 8 . de f addLe t t e rsToMapp i ng ( l e t te rMapping , c iphe rwo rd, candidate ) :
39.
# Параме тр l e t t e rMapping - э т о дешифров ал ь ный сло в арь ,
40.
# обраба тываемый данной функцией .
41.
# Параме тр c iphe rwo rd - э т о строко в о е значение шифрослов а .
# Параме тр candida t e - э т о англий ское сло в о -кандидат ,
42 .
43 .
# в которое може т быт ь дешифровано данное шифросло в о .
44 .
45.
# Функци я добавляет в дешифроваль ный словарь
46.
# буквы слов а - кандида та в кач е с т в е вариа н т о в
47 .
# дешифрования шифробукв .
48.
49.
50 .
f o r i in range ( l en ( c ipherword ) ) :
51 .
i f candidat e [ i ] not in l e t t e rMapping [ c iphe rword [ i ] ] :
52 .
l e t t e rMapping [ c ipherword [ i ] J . append ( candi dat e [ i ) )
53 .
54 .
55 .
5 6 . de f i n t e r s e ctMapping s ( mapA , mapB } :
57 .
# Чтобы построить пересечение слов арей , создаем пустой словарь и
58 .
# добавляем в него толь ко варианты, суще ствующие в ОБОИХ сло в арях
59 .
i n t e r s e c t edМapping
getBlankCiphe r l e t t e rMapp i ng ( )
60 .
f o r l e t t e r in LETTERS :
61 .
62 .
# Пустой список означает " в озможна любая букв а " ;
63 .
# в э т ом случае копируем список целиком
64 .
if mapA [ l e t t e r ]
[] :
65 .
i n t e r s e ctedМapping [ l e t t e r ]
copy . deepcopy ( mapB [ l e t t e r ] )
66 .
e l i f mapB [ l et t e r ] =
[] :
67 .
i n t e r s e ctedМapping [ l e t t e r ]
copy . deepcopy ( mapA [ l e t t e r ] )
68 .
else :
69 .
# Е сли буква из словаря mapA [ l e t t e r ] суще ствует в словаре
70 .
# mapB [ l e t t e r ] , добавляем ее в i n t e r s e ctedМapping [ l e t t e r ]
71 .
f o r mappedLe t t e r in mapA [ l e t t e r ] :
72 .
i f mappedLe t t e r in mapB [ l e t t e r ] :
=

==

=

294 Глава 1 7

73.
i n t e r s e ctedМapping [ l e t t e r ] . append ( mappedLet t e r )
74 .
75 .
return intersect edМappi ng
76.
77 .
7 8 . de f remove SolvedLet t e r s FromМapping ( l e t t e rMapping ) :
79.
# Шифробуквы, транслируемые толь ко в одну букву, считаются
во.
# дешифрованными , и соответст вующие буквы могут быть удалены из
81.
# осталь ных списков . Например , е сли ' А ' транслируе тся в [ ' М ' , ' N ' ] ,
82 .
# а ' В ' - в [ ' N ' J , то мы знаем, что ' В ' соответствуе т ' N ' , поэтому
83.
# ' N ' можно удалить из списка для ' А ' . Это также означа е т , что ' А '
84 .
# транслируе тся в [ ' М ' ] . А поскольку ' А ' теперь соответствует
85.
# един ственной букве , можно удалить ' М ' из осталь ных списков
86.
# ( вот почему словарь сокращается в цикле ) .
87 .
l oopAgain = T rue
88 .
whi l e loopAgai n :
89.
# Сначала предполагаем, что цикл не буде т выполнен повторно
90 .
91 .
loopAgain = Fa l s e
92 .
# sol vedLe t t e r s - список букв в верхнем регистре , имеющих
93 .
# единственное соответствие в словаре l e t t e rMappi ng
94 .
s o lvedLe t t e r s = [ ]
95 .
for ciphe r l e t t e r i n LETTERS :
96 .
i f l e n ( le t t e rMappi ng [ c iphe r l e t t e r ] ) == 1 :
97 .
solvedLe t t e r s . append ( l e t t e rMapping [ c iphe r l et t e r ] ( 0 ) )
98 .
99 .
# Если буква установлена , то она не може т быть вариантом
100 .
# дешифрования для другой шифробуквы, поэтому она должна
101 .
# быть удалена из других списков
102 .
for ciphe r l e t t e r in LETTERS :
103 .
for s in s o l vedLe t t e r s :
104 .
i f l e n ( l et t e rMapping [ ci phe r l e t t e r ] ) ! = 1 and s i n
105.
l e t t e rMapp i ng [ ciphe r l e t t e r ] :
106.
l e t t e rMapping [ c iphe r l e t t e r ] . remove ( s )
107 .
i f len ( l e t t e rMapp ing [ ciphe r l e t te r ] ) == 1 :
# Дешифрована новая букв а , продолжаем цикл
108 .
l oopAgain = T rue
109 .
return l e t t e rMapping
110 .
111 .
112 .
1 1 3 . de f hackS imp l e S ub ( me s sage ) :
i nt e r s ect edМap = getBlankCiphe r l e t t e rMapping ( )
114 .
ciphe rwo rdL i s t = nonLe t t e rsOrSpace Pattern . sub ( ' ' ,
115.
me s s age . upper ( ) ) . sp l i t ( )
for ciphe rword in ciphe rwordL i s t :
116 .
# Получить новый дешифровал ь ный словарь для каждого шифрослова
117 .
candidateMap = getBlankCiphe r l e t t e rMappi ng ( )
118 .

Взл ом простого подста но вочного шифра 295

119 .
120 .
121 .
122 .
123 .
124 .
125 .
126 .
12 7 .
128 .
12 9 .

wordPat t e rn
makeWordPatterns . getWordPat t e rn ( cipherword )
i f wordPa t t e rn not in wordPat t e rns . a l l Pat t e rns :
cont i nue # этого слова нет в словаре , продолжаем
=

# Добавить буквы каждого слова-кандидата в словарь
for candi date in wordPatterns . a l l Pa t t e rns [ wordPa t t e rn ] :
addLe t t e r sToMappi ng ( candidateMap , c iphe rword, candida t e )
# Найти пересечение нового словаря с существующим пересечением
intersectedМap
intersectMapp i ngs ( in t e r s e ctedМap ,
candidateMap )
=

130 .
131 .
132 .
133 .
134 .
1 3 5 . de f
136 .
137 .
138 .
139 .
140 .
141 .
142 .
143 .
144 .
145 .
146.
147 .
148 .
149 .
150 .
151 .
1 52 .
153 .
154 .
155 . if
156 .

# Удалить решенные буквы из других списков
return remove S o l vedLe t t e r s FromМapp i ng ( intersect edМap )

decryptWi thCiphe r l e t t e rMapping ( ciphertext , l e t t e rMapping ) :
# Возвращает строку шифро текста , дешифрованную с помощью словаря
# шифробукв , в которой неоднозначности заменены подчеркиваниями
# Сначала создаем ключ на основе словаря l e t t e rMapp i ng
key
[ ' х ' ] * l e n ( LETTERS )
for ciphe r l e t t e r i n LETTERS :
i f l e n ( l e t t e rMapping [ ci phe r l e t t e r ] )
1:
# Е сли имеется тол ь ко одна буква , добавляем ее в ключ
keyi ndex = LETTERS . fi nd ( l e t t e rMappi ng [ ciphe r l e t t e r ] ( 0 ) )
key [ keyi ndex ] = ciphe r l e t t e r
else :
c iphe rt ext
ciphe rtext . replace ( ciphe r l e t t e r . l ower ( ) , ' ' )
ciphe rt ext
c i phertext . replace ( ciphe r l e t t e r . upper ( ) , ' ' )
key
' ' . j o i n ( ke y )
=

==

=

# Дешифруем шифротекст с помощью созданного ключа
return simp l e S ubCiphe r . de cryptMes sage ( ke y , ciphertext )

name
ma in ( )

ma in

1 .

Пример выnоnнени11 nроrраммь1 Simple
SuЬsti tution Hacker
Когда вы запустите программу, она попытается взломать ши ф ротекст,
хранящийся в переменной me s s age. Вы должны получить следующий ре­
зультат.

296

Глава 1 7

Hacking . . .
Mapp i ng :
( 'А' : [ ' Е '
[ 'В' , 'Р' ]
'I' : ['Н' ]
[ ' D' ] , ' Р'
[ ' G' ] , 'V'

] , 'В' : [ 'У' , ' Р' , 'В' ] , 'С' : [ 'R' ] , 'D' : [] ,
, 'G' : [ 'В' , 'Q', 'Х' , 'Р' , 'У' ] , 'Н' : [ ' Р' ,
, ' J' : [ 'Т ' ] , 'К' : [ ] , 'L' : [ 'А' ] , 'М' : [ 'L' ]
: [ 'О' ] , 'Q' : [ 'V' ] , 'R' : [ ' S ' ] , ' S ' : [ ' ! ' ] ,
: [] , 'W' : [ 'С' ] , 'Х' : [ 'N' ] , 'У' : [ ' F' ] , 'Z'

' Е ' : [ 'W' ] , ' F' :
'У' , 'К' , 'Х' , 'В' ] ,
, 'N' : [ 'М' ] , 'О' :
'Т' : [ 'U' ] , 'U' :
: ['Z' ] }

O r i g i na l ciphertext :
Sy 1 nlx s r рууасао 1 ylwj e i swi upa r l u l s x r j i s r s x r j sxwj r , i a e smm
rwct j sxs za s j wrnpramh , ! х о t xmar r j i a aqsoaxwa s r pqa c e i amnsxu , i a e smm
cayt ra j p fams a qa s j . S y , р х j ia p j iac i lxo , i a s r рууасао rpna j i sxu e i sw i
lyypcor 1 ca l rpx у р с lwj sxu sx lwwpcolxwa j p i s r s x r j sxwj r , i a e s mm lwwabj
sj aqax рх j ia rms ui j a r j aqs oaxwa . Jia pcsusx ру nhj i r sr agbml sxao sx j i s r
e l h . - Fa c j c lxo Ctr ramm
Copying hacked me s s age t o cl ipboard :
I f а man i s o f f e red а fact which goes aga i n s t h i s i n s t inct s , he w i l l
s c rut i n i z e i t c l o s e l_ , and unle s s t he evidence i s overwhe lmi ng , he wi l l
refuse t o _e l i eve i t . I f , o n the other hand , h e i s o f fe red s ome t hing
whi ch a f fords а reason for act ing i n accordance t o h i s inst inct s , he
wi l l ассе t i t even on the s l ight e s t ev idence . The o r i g i n o f m ths i s
е l a ined i n t h i s w a . - e rt rand Rus s e l l

Исследуем код программы более подробно.

Импорт модуnей и настройка констант
Рассмотрим начальные строки программы. В строке 4 импортируются
семь модулей - больше , чем в любой из программ, которые мы обсуждали
до этого. В строке 1 0 в глобальной переменной LETTERS сохраняется сим­
вольный набор, состоящий из букв алфавита в верхнем регистре.
1 . # Программа в злома простого подстановочного шифра
2 . # https : / / www . no s t arch . com/ c r a c k i ngcode s / ( BS D L i censed )
3.
4 . import o s , re , сор у , pype r c l i p , s impl eS ubCipher , wordPat t er n s ,
ma keWordPa t t e rn s

- - опущено - -

1 0 . LETTERS = ' AВCDE FGH I JKLMNOPQRSTUVWXY Z '

Модуль re содержит функции для работы с регулярными выражениями,
с помощью которых можно выполнять сложные манипуляции со строка­
ми. Познакомимся с регулярными выражениями поближе.

Взлом простого подста но в очного шифра 297

Поиск симвоnов с nомощыо реrуnярнь1х выражений
Регулярные выражения - это строковые шаблоны, соответствующие
определенным поисковым строкам. Например, шаблон ' [ лA- Z \ s ] ' в
строке 1 1 - это регулярное выражение, которое сообщает Python о том,
что необходимо найти любой символ, не являющийся буквой от 'А:. до 'Z' в
верхнем регистре или пробельным символом (таким, как пробел, табуля­
ция или символ новой стршш ) .
1 1 . nonLe t t e r s O r S p a c e P a t t e r n

=

re . comp i l e ( ' [ лA- Z \ s ] ' )

Функция re . cornp i l e ( ) создает об'Ое'/Сm шаблона регулярного выражения
(далее для краткости - об'Ое'/Сm шаблона) , с которым могут работать дру­
гие функции модуля r e . Мы будем использовать этот объект для удале­
ния любых небуквенных символов из шифротекста в разделе "Функция
ha c kS irnp l e Sub ( ) .
С помощью регулярных выражений можно выполнять самые разноо­
бразные манипуляции (:О > > buildinq = ' '
> > > for с in ' Bello world ! ' :
buildinq += с
> > > print (buildinq)

В данном случае в цикле перебираются все символы строки ' He l l o
w o r l d ! ' , которые конкатенируются со строкой, хранящейся в перемен­
ной bui l d i n g . По завершении цикла переменная b u i l d i ng будет содер­
жать всю исходную строку.
Несмотря на кажущуюся простоту, данная методика реализуется в Python
слишком неэффективно. Намного быстрее начать с пустого списка и при326

Гл ава 1 8

менять к нему метод append ( ) . Когда список будет построен, его можно бу­
дет преобразовать в строку помощью метода j o i n ( ) . Приведенный ниже
код дает тот же результат, что и в предыдущем примере, но гораздо бы­
стрее.
> » building = [ ]
>>> for с in ' Hello world ! ' :
building . append ( c )
> > > building = 1 1 . j oin (building)
> > > print (building)

Применение списков вместо строк позволяет значительно ускорить ра­
боту программы. Разницу в быстродействии можно замерить с помощью
функции t ime . t ime ( ) . Откройте в редакторе файлов новое окно и введи­
те в нем следующий код.
string Test.py
import t ime
s t a r t T ime = t ime . t ime ( )
for t r i a l i n range ( 1 0 0 0 0 ) :
bui lding = ' '
for i i n range ( l O O O O ) :
bui lding += ' х '
p r i nt ( ' S t r i ng conca t e na t i on :

( t ime . t ime ( ) - s t a rt T i me ) )

s t a r t T ime = t ime . t ime ( )
for t r i a l i n range ( l O O O O ) :
bui lding = [ ]
for i i n range ( l O O O O ) :
bui l ding . append ( ' х ' )
bui lding = ' . j o i n ( bu i l di ng )
p r i nt ( ' L i s t appendi ng :

( t ime . t ime ( ) - s t a r t T ime ) )

'

Сохраните программу в файле stringТest.frY и запустите ее. Результаты бу­
дут примерно такими.
S t r i ng concatenat i o n :
L i s t app e nd i ng :

4 0 . 3 1 7 07 0 9 60 9 9 8 5 3 5
1 0 . 4 8 8 2 1 90227 508 5 4

В переменную s t a r t T ime записывается текущее время, после чего вы­
полняется цикл, в котором к строке присоединяется 1 О ООО символов пу­
тем конкатенации. По завершении цикла программа сообщает общее вре­
мя, затраченное на эту операцию. Далее в переменную s t a r t T ime вновь
Програ ммиро вание шифр а Виженер а 327

записывается текущее время , и выполняется цикл, в котором создается
строка той же длины, но путем присоединения элементов списка. В конце
программа тоже сообщает время , затраченное на эту операцию. На ком­
пьютере автора на создание 1 О ООО строк длиной 1 О ООО символов каждая
ушло примерно 40 секунд, тогда как на выполнение той же задачи с помо­
щью списков - всего 1 О секунд. Таким образом, если в программе создается
большое количество строк, использование списков позволит существенно
ускорить ее работу.
В дальнейшем мы будем создавать строки в программах именно с помо­
щью списковых методов.

Ш ифрование и деwифрование соо6щени•
Поскольку код шифрования и дешифрования почти одинаков, мы созда­
дим две функции-обертки, enc ryptMe s s age ( ) и decryptMe s sage ( ) , кото­
рые будут вызывать функцию t ra n s l a t eMe s s age ( ) , содержащую сам код.
2 6 . def encr yptMe s s age ( ke y , me s s age )
27 .
ret urn t r a n s l a t eMe s s age ( ke y ,
28 .
29.
3 0 . d e f dec ryptMe s s age ( ke y , me s s age )
ret urn t ra n s l a t eMe s sage ( ke y ,
31 .

:
me s s a g e ,

' encrypt ' )

me s s a g e ,

' de c rypt ' )

:

Функция t rans l a t eMe s s a ge ( ) создает зашифрованную (дешифрован­
ную) строку символ за символом, сохраняя их в списке t r a n s l ated, содер­
жимое которого может быть объединено, когда строка будет готова.
3 4 . de f
35 .
36.
37 .
38 .

t rans l at eMe s s a g e ( ke y , me s s age , mode ) :
t rans l at e d
[]
# хранит з ашифров анное / дешифрованное сообщение
ke y i ndex = О
key = key . upp e r ( )

Не забывайте о том , что шифр Виженера - это уже знакомый вам шифр
Цезаря, за исключением того, что ключ, применяемый к букве, зависит от
ее позиции в сообщении. Начальным значением переменной key i ndex, с
помощью которой отслеживается текущий подключ, является О , поскольку
для шифрования/ дешифрования первого символа сообщения использует­
ся буква key [ О ] .
В программе предполагается, что ключ содержит только буквы в верх­
нем регистре. Чтобы гарантировать допустимость ключа, в строке 38 к
строке ключа применяется метод uppe r ( ) .
328

Глава 1 8

Остальной код функции t rans l a t eMe s s age ( ) напоминает код, кото­
рый применялся для реализации шифра Цезаря.
40 .
41 .
42 .
43.
44 .
45 .
46.

for s ymЬol i n rne s sage : # цикл по символам строки rnes s age
nurn = LETTERS . fi nd ( s ymЬ o l . uppe r ( ) )
i f nurn ! = - 1 : # s ymЬo l . upper ( ) найден в строке LETTERS
if rnode == ' encrypt ' :
nurn += LETTERS . f i nd ( ke y [ keyi ndex ] ) # доба вить в случае
шифрования
e l i f rnode == ' de crypt ' :
nurn = LETTERS . f i nd ( ke y [ key ! ndex ] ) # вычесть в случае
дешифрования
-

В цикле f o r , который начинается в строке 40, символы сообщения при­
сваиваются переменной s ymЬ o l на каждой итерации цикла. В строке 4 1
определяется индекс текущего символа, переведенного в верхний регистр,
в строке LETTERS , в результате чего буква преобразуется в число.
Если значение nurn не равно - 1 , то это означает, что символ в верхнем
регистре был найден в строке LETTERS (т.е. данный символ является бук­
вой ) . Переменная key i ndex позволяет отслеживать текущий подключ, ко­
торый является результатом вычисления выражения key [ keyI ndex ] .
Конечно, это всего лишь однобуквенная строка. Чтобы преобразовать
подключ в число, нужно найти индекс данной буквы в строке LETTERS. За­
тем это число суммируется (в случае шифрования) с числовым кодом сим­
вола в строке 44 или вычитается (в случае дешифрования) из него в стро­
ке 46.
При работе с шифром Цезаря мы проверяли, не является ли новое
значение nurn отрицательным (в этом случае мы прибавляли к нему число
l e n ( LETTERS ) ) или же превышающим значение l e n ( LETTERS ) (в таком
случае мы вычитали из него число l e n ( LETTERS ) ) . Это позволяет учесть
случаи "завертывания".
Однако существует более простой способ. Те же самые вычисления мож­
но уместить в одной строке кода, разделив значение nurn на l e n ( LETTERS )
с остатком:
48 .

nurn % = l e n ( LETTERS )

# обработка " завертыв ания "

Например, если бы значение nurn было равно - 8 , то к нему нужно было
бы прибавить 2 6 (т.е. значение l e n ( LETTERS ) ) , чтобы получить 1 8 , но
это же число является результатом операции - 8 % 2 6. Или же, если бы
значение nurn было равно 3 1 , то из него требовалось бы вычесть 2 6 , что­
бы получить 5 , но результатом операции 3 1 % 2 6 тоже является число 5 .
П рогра мм и рование шифра Виженера 329

Операция деления с остатком в строке 48 справляется с обоими случаями
"завертывания".
Зашифрованным (или дешифрованным) символом является буква
LETTERS [ num ] . Но мы хотим , чтобы регистр полученного символа совпа­
дал с регистром исходного символа.
50 .
51 .
52 .
53 .
54 .

# Доба вить зашифрованный /дешифрованный символ в конец
if s yrnЬ o l . i s uppe r ( ) :
t rans l a t e d . append ( LETTERS [ num ] )
e l i f s yrnЬ o l . i s l owe r ( ) :
t ra n s l a t e d . append ( LETTERS [ num ] . l owe r ( ) )

Поэтому, если переменная s ymЬo l содержит букву в верхнем регистре,
условие в строке 5 1 выполняется, и тогда в строке 52 символ LETTERS [ num ]
присоединяется к списку t r a n s l ated, поскольку все символы в строке
LETTERS уже переведены в в е рх н ий регистр.
Если же в переменной s ymbo l хранится буква в нижнем регистре, то
выполняется условие в ст ро ке 53, и тогда в строке 54 к списку t rans l a t e d
присоединяется буква L E T T E R S [ num ] , преобразованная е е в нижний ре­
гистр. Тем самым мы обеспечиваем согласование регистра букв в исходном
и зашифрованном (дешифрованном) сообщениях.
После преобразования символа мы хотим убедиться в том, что на следу­
ющей итерации цикла f o r будет выбран очередной подключ. Для этого в
строке 56 значение keyi ndex увеличивается на единицу.
ke y i ndex += 1
i f key i ndex
ke yi ndex

56.
57 .
58 .

==
"

# перейти к следующей букве ключа
l e n ( ke y ) :
О

Но если в данный момент уже задействован последний из подключей,
то значение keyi ndex станет равно длине ключа. Это условие проверяется
в строке 57, и если оно с обл юдается то в строке 58 значение key i ndex
сбрасывается в О , чтобы выражение key [ ke y i ndex ] вновь указывало на
первый подключ.
Выделение отступом и н ст рукции e l s e в строке 59 указывает на то, что
она связана с инструкцией i f в строке 42.
,

59 .
60 .
61 .

330

else :
# Присоединит ь символ беэ шифрования / дешифро вания
t ra n s l a t e d . append ( s ymЬo l )

Глава 1 8

Код в строке 6 1 выполняется в том случае, если символ не был обнару­
жен в строке LETT ERS. Это происходит тогда, когда символом, хранящим­
ся в переменной s ymb o l , является число или знак препинания, например
' 5 ' или ' ? ' . В таком случае в строке 61 символ присоединяется к списку
t rans l ated в неизменном виде.
Завершив формирование списка t ra n s l a t e d , мы вызываем метод
j o i n ( ) для пустой строки, чтобы получить в результате объединенную
строку:
return ' ' . j o i n ( t rans l a t e d )

63 .

Функция возвращает полную строку зашифрованного или дешифрован­
ного сообщения.

Вызов функции main ( )
Программа завершается строками 68 и 69.
68 . if
69 .

name
ma i n ( )

ma i n

'

·

Функция ma i n ( ) выполняется лишь в том случае, если программа
была запущена непосредственно, а не импортируется в виде модуля дру­
гой программой, которой требуются ее функции encryptMe s s age ( ) и
de c ryptMe s s age ( ) .

Рез1Оме
Шифр Виженера не намного сложнее шифра Цезаря, одного из первых
шифров, с которым вы познакомились. Внеся всего несколько изменений
в шифр Цезаря, мы создали шифр, количество возможных ключей которо­
го делает невозможным взлом методом грубой силы.
Шифр Виженера неуязвим для атаки с использованием словарных ша­
блонов, которая применялась в нашей программе взлома простого подста­
новочного шифра. В течение сотен лет "неподдающийся" шифр Виженера
оберегал секреты конфиденциальных сообщений, но в конечном счете и
он не устоял. В главах 1 9 и 20 вы познакомитесь с методами частотного ана­
лиза, используя которые можно взломать шифр Виженера.

П рогр а мм ирование шифра Виженера 33 1

Кон трольн ы е воп р о сы

Ответы на контрольные вопросы приведены в приложении Б.

1.

К а кому шифру а налогичен шифр Виженера, если не считать того, что в нем
используется несколько ключей вместо одного?

2.

Скол ько возможных вариантов существует для ключа шифра Виженера дли­
ной 1 О символов?
А.
Б.
В.
Г.

З.

332

Сотни.
Тысяч и.
Миллионы.
Более триллиона.

К ка кому типу шифров относится шифр В иженера?

Глава 1 8

ЧАСТОТН Ы Й АНАЛИ З
"Умение интуитивно находитъ закономерности в
хаосе бесполезно без погружения в сам хаос. Если там
действителъно естъ закономерности, сознание их nО'Ка
не видит. Но теперъ, когда буквъ� прошли перед глазами
и бъt.ли вътисанъ� на бумаге, в работу 8'КЛ'/Q1./,ается
подсознание" . "
Нил Стивенсон, "Криптономикон "

В этой главе вы узнаете о том , как опреде­
лить частотность каждой буквы английского
алфавита в тексте. З атем вы сравните эти данные с частотностью букв в шифротексте , что даст
важную информацию для взлома шифра. П роцесс определения
того , насколько часто буквы появляются в открытых текстах и
шифротекстах , называется 'Частотный анализ. Изучение часто�
ного анализа является важным шагом на пути к взлому шифра
Виженера , чем мы займемся в главе 20.
В э то м





rАаве ...

Частотность букв и буквенный ряд ETAOI N
Аргументы key и r e ve r s e метода s o r t ( )
П ередача функци й как значен и й вместо вызова функций
Преобразование словарей в списки с по м ощью методов keys ( ) , va l u e s ( )
и i t ems ( )

Анаnиэ частотности букв в тексте
Если подбрасывать монету множество раз, то примерно в половине слу­
чаев выпадет "орел", а в половине - "решка". Таким образом, частота выпа­
дения "орла" и "решки" примерно одинакова. Эту частоту можно предста­
вить в процентном отношении, разделив количество наступивших собы­
тий (например, сколько раз выпал "орел") на количество попыток (сколь­
ко раз мы подбросили монету) и умножив результат на 1 00. Мы многое мо­
жем узнать о монете по частоте выпадения "орла" и "решки": правильно ли
сбалансированы ее стороны и не является ли она поддельной, например с
"орлом" на обеих сторонах.
Точно так же мы можем многое узнать о шифротексте, изучая частот­
ность встречающихся в нем букв. Одни буквы английского алфавита встре­
чаются чаще других. Например, в английских словах чаще всего встреча­
ются буквы 'Е', 'Т' , 'lt и ' О ' , тогда как буквы 'J' , 'Х' , ' Q' и 'Z' встречаются
реже. Эти различия в частотности мы используем для взлома сообщений,
зашифрованных с помощью шифра Виженера.
На рис. 1 9. 1 в графическом виде представлены данные о частотности
букв в английском языке. Эта диаграмма была построена на основе тек­
стов, взятых из книг, газет и других источников.
0, 1 4

0, 1 2

0, 1 0

0,08

0,06

0,04

0,02

A B C D E F G H

Рис. J 9. J .

334

Глава 1 9

1

J

K L M N O P Q R S T U V W X Y Z

Частотность букв в типи ч ных текстах на ан гл ийском языке

Расположив буквы в порядке убывания частотности , мы увидим , что
чаще всего встречается буква 'Е', за ней следует буква 'Т' , затем буква 'N. и
т.д. (рис. 1 9.2) .
0, 1 4

0, 1 2

0, 1 0

0,08

0,06

0,04

0,02

Е Т А О 1

Рис. 19.2.

N S Н R D L С U MW F G У Р В V К J

Х Q Z

Буквы, расположенные в порядке убывания их частотности
в типичных текстах на анrлийск ом языке

Шесть наиболее часто встречающихся букв в английском языке
ETAOIN. А вот полный список букв, расположенных в порядке убывания
их частотности: ETAOINSHRDLCUMWFGYPBVКJXQZ.
Вспомните, что в перестановочном шифре сообщения шифруются пу­
тем расположения букв исходного сообщения в другом порядке. Это озна­
чает, что частотность букв в шифротексте не будет отличаться от их ча­
стотности в исходном тексте. Например, в ши ф ротексте перестановочно­
го шифра буквы ' Е ' , 'Т' и 'N. должны встр е чат 1.с я чаще, чем буквы 'Х' , 'Q'
и 'Z'.
Точно так же буквы , которые чаще всего встречаются в ши фротекс тах ,
зашифрованных с помощью шифра Цезаря или п ро ст о ю нодстановочного
шифра, скорее всего, были получены шифрованием таких наиболее часто
используемых букв, как 'Е' , 'Т' или 'N.. В то же вре м я бук вы , которые встре­
чаются в шифротексте намного реже других, скорее всего, были получены
шифрованием букв 'Х' , 'Q' и 'Z' исходного текста.
-

Ча стотный анализ 335

Чрезвычайная полезность частотного анализа для взлома шифра Ви­
женера объясняется тем, что он позволяет применять метод грубой силы
для взлома по одному подIUiючу за раз. Например, если сообщение было
зашифровано с помощью IUiючa "PIZZA" , то для нахождения сразу всего
IUiючa нам потребовался бы полный перебор 265, или 1 1 88 1 376 IUiючей.
Но для взлома методом грубой силы только одного из пяти подIUiючей нам
нужно испробовать лишь 26 возможных вариантов. Применение аналогич­
ной процедуры в отношении каждого из пяти подIUiючей означает, что нам
придется испытать всего-навсего 26 5, или 1 30 подIUiючей.
В случае IUiючa "PIZZA" каждая пятая буква сообщения, начиная с первой,
будет шифроваться с помощью подIUiюча 'Р' , каждая пятая буква, начиная со
второй, - буквой '1' и т.д. Мы можем применить метод грубой силы для опре­
деления первого подIUiюча, дешифруя каждую пятую букву шифротекста
при помощи всех 26 возможных подIUiючей. Тогда мы обнаружили бы, что
использование буквы 'Р' в качестве первого подIUiюча приводит к дешиф­
рованным буквам, которые соответствуют частотности букв в текстах на ан­
глийском языке в большей степени, чем в случае использования остальных
25 возможных подIUiючей. Это стало бы убедительным аргументом в пользу
того, что именно буква 'Р' является первым подIUiючом. Действуя в том же
духе в отношении других подIUiючей, можно получить весь IUIЮЧ .
·

Частотное соответствие букв
Для нахождения частотности букв в сообщении мы применим алгоритм,
который сортирует буквы в строке в порядке убывания их частотности. С
помощью этой упорядоченной строки будет вычисляться величина, кото­
рая в данной книге называется оценкой 'Частотного соответствия ( frequency
match score) . Она показывает, насколько частотность букв в строке совпа­
дает с частотностью букв в английском языке.
Чтобы рассчитать оценку частотного соответствия , мы присваиваем
ей нулевое начальное значение, а затем увеличиваем ее на единицу всякий
раз, когда одна из наиболее употребительных букв английского алфавита
( 'Е ' , 'Т' , 'N. , ' О ' , '1' , ' N ' ) появляется среди шести наиболее часто встречаю­
щихся букв в шифротексте. Мы также добавляем к оценке единицу всякий
раз, когда одна из наименее употребительных букв английского алфавита
( 'V' , 'К' , :J ' , 'Х' , 'Q' , 'Z' ) появляется среди шести наименее часто встречаю­
щихся букв в шифротексте.
Оценка частотного соответствия для строки может иметь значения от О
(абсолютное несоответствие) до 1 2 (полное соответствие). Знание данной
оценки дает важную информацию о характере исходного текста.

336

Глава 1 9

Вычииrение оqенки чatJoпtoro сооrвеmвп 6укв дл• npocroro
nодсrановочноrо шифра
Для вычисления оценки частотного соответствия букв в сообщении ,
зашифрованном с помощью простого подстановочного шифра, мы будем
использовать следующий шифротекст.
Sy 1 n l x s r рууа сао 1 ylwj e i swi upa r l u l s x r j i s r s x r j s xwj r , ia e smrn
rwct j sx s z a s j wrnpramh , lxo t xma r r j i a aqs oaxwa s r pqace i amn s xu , i a e smrn
caytra j p fams aqa s j . S y , рх j i a p j i a c i l xo , ia s r рууаса о rpna j i sxu
e i swi l yypcor 1 c a l rpx урс lwj sxu sx lwwpco l xwa jp i s r s x r j sxwj r , ia
e smrn lwwabj sj aqax р х j i a rmsu i j a r j aqsoaxwa . Jia pcsusx ру nh j i r s r
agbml sxao s x j i s r e l h . - acj c l x o C t r ramrn

Если мы подсчитаем частотность каждой буквы в этом шифротексте и
расположим буквы в порядке убывания частотности, то получим следую­
щую последовательность: ASRXJILPWМCYOUEQNTHBFZGKVD. Чаще все­
го в шифротексте встречается буква 'А: , на втором месте - буква 'S' и т.д.
вплоть до буквы 'D' , которая встречается реже всех остальных букв.
Из шести наиболее часто встречающихся букв в этом примере ( 'А: , 'S' ,
'R' , 'Х' , Т и '1' ) две буквы ( 'А:. и '1') также входят в список шести наиболее
употребительных букв английского языка, которыми являются 'Е', 'Т' , 'А: ,
'О' , ' 1 ' и 'N' . Соответственно м ы добавляем к оценке частотного соответ­
ствия два балла.
Шестью наименее часто встречающимися буквами в шифротексте яв­
ляются буквы 'F' , 'Z' , 'G' , 'К' , 'V' и 'D' . Три из них ( ' Z ' , 'К' и 'V' ) появля­
ются в списке наименее часто встречающихся букв английского языка
( 'V' , 'К' , Т , 'Х' , 'Q' и 'Z' ) . Поэтому мы увеличиваем оценку еще на три
балла. На основании упорядоченности букв шифротекста по частотности
(ASRXJILPWМCYOUEQNTHBFZGKVD) мы получаем суммарную оценку
частотного соответствия , равную 5 (рис . 1 9.3) .
� S R X J ! L PWMCYOU E QNTH B F �G KVD

.._ 5 соответствий

E T AO I N S H R D L C U MW F G Y P B V K J X Q Z
Наибольшая
частот н ость

Рис. J 9.3.

Игн ор и роват ь
средние

14

Наименьшая
частотн ость

Вычисление оценки ч астотного соответствия букв
для простого nодстановочного ш ифра

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

337

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

Вычжпенне оqено чacrorнoro '0011ert11п 6укв дп• 1ере'1ановочноrо
шифра
Давайте вычислим оценку частотного соответствия для букв шифротек­
ста, полученного с помощью перестановочного шифра.
" I r c a s cwui l uhnv iwu e t nh , o sgaa i ce t i peeeee s l nat s f i e t g i t i t t yneceni s l . е
fo f fnc i s l t n sn о а yrs sd oni s l i , 1 e r g l e i t rh fmwfrogot n , l s t c o f i i t .
аеа we s n , l nc ее w , l e i h eeehoer ros i o l e r s nh nl oahs t s i l a s v i h t v f e h
rt i r a id that n i e . im e i -dlmf i t h s z o ns i s ehroe , a i ehcdsanah i e c gv gyedsB
onsdihs o c n i n c e t h i T i t x eRneahg i n r е t e om fbi o t d n
a f fc a h i e c e s d d !ее
n t a c s cwevhtdhnhp iwru "

Ряд букв, расположенных в порядке убывания частотности, в данном
шифротексте выглядит так: EISNTНAOCLRFDGWVМUYВPZXQJК. Буква
'Е' встречается наиболее часто, на втором месте - буква '1' и т.д.
Четыре буквы , которые встречаются в этом шифротексте чаще других
( 'Е' , '1' , 'N' и 'Т' ) , также входят в список букв, наиболее часто встречаю­
щихся в типичных текстах на английском языке (ETAOIN ) . Точно так же
пять наименее часто встречающихся букв в шифротексте ( 'Z ' , 'Х' , 'Q' , 'J' и
' К' ) одновременно входят в список VКJXQZ, в результате чего суммарная
оценка частотного соответствия достигает значения 9 (рис. 1 9.4) .
E I S N T H A O C L R F DG W V M U V B P Z X Q J K

.._ 9 соответствий

E TAO I N S H R D L C U MW F GV P B V K J XQ Z
Наибольшая
частотн ость

Рис. 1 9.4.

Игн ор и ров ать
с редние 1 4

Наименьшая
ча стотн ост ь

Вычисление оценки частотного соответствия
букв для перестановочноrо ш ифра

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

338

Глава 1 9

"'"олыование чааоrноrо аналиэа в и�учае шифра Виженера
Пытаясь взломать шифр Виженера, мы должны дешифровать каждый
из подключей по отдельности. Это означает, что мы не можем полагаться
на метод обнаружения английских слов, поскольку не в состоянии дешиф­
ровать достаточную часть сообщения с помощью только одного подключа.
Вместо этого мы дешифруем буквы , зашифрованные с помощью одно­
го подключа, и выполняем частотный анализ, чтобы определить, какой из
вариантов дешифрования приводит к частотности, наиболее близкой к ча­
стотности букв в типичных текстах на английском языке. Другими слова­
ми, мы должны выяснить, какой из вариантов дешифрования характеризу­
ется наиболее высокой оценкой частотного соответствия букв. Это служит
хорошим показателем того, что мы нашли правильный подключ.
Описанный процесс повторяется для второго, третьего, четвертого и
пятого подключей. На данном этапе предположение о том, что длина клю­
ча составляет пять букв, является всего лишь догадкой. (В главе 20 вы узна­
ете об использовании метода Касиски для определения длины ключа.) По­
скольку в шифре Виженера.для каждого подключа существует 26 вариантов
дешифрования (число букв в алфавите) , то в случае пятибуквенного ключа
компьютер должен перепробовать 26 + 26 + 26 + 26 + 26, т.е. 1 56 вариантов
дешифрования. Это намного меньше, чем если бы испытывались все воз­
можные комбинации подключей, общее количество которых составляет
1 1 88 1 376 (26 . 26 . 26 . 26 . 26) !
Взлом шифра Виженера включает ряд дополнительных этапов, рассмо­
тренных в главе 20, в которой мы напишем собственно программу взлома.
А пока что мы создадим модуль частотного анализа, включающий следую­
щие вспомогательные функции.





qetLetterCount ( )

. Получает строковый аргумент и возвращает сло­
варь, содержащий счетчики частотности каждой буквы в строке.
qetFrequencyOrder ( ) . Получает строковый аргумент и возвращает
строку из 26 букв алфавита, расположенных в порядке убывания ча­
стотности.
enqlishFreqМatchScore ( ) . Получает строковый аргумент и возвра­
щает целое число в интервале от О до 1 2, определяющее оценку ча­
стотного соответствия букв.

Ча стотн ый анализ 339

Исходный код nроrраммь1 Frequency Analysi s
Откройте в редакторе файлов новое окно, выбрав пункты меню File�
New File. Введите в этом окне приведенный ниже код и сохраните его в
файле JreqAnalysis.py. Запустите программу, нажав клавишу .
freqAna lysis.py

1.
2.
3.
4.
5.
6.
7.
8.
9.
10 .

# О пределение ч а с т о т н о сти букв
# ht t p s : / / www . n o s t a rch . com/ c r a c k i ngcode s / ( B S D L i c e n s e d )
ETAO I N
LETTERS

=
=

' ETAO I N S HRDLCUМW FGY PBVKJXQ Z '
' ABCDE FGH I JKLМN O PQRSTUVWXY Z '

de f g e t L e t t e rCount ( rne s s a g e ) :

# В о з враща е т словарь , ключами которого я в л яют с я буквы ,
# а з н а ч е н и ями - ч а с т о т н о с т ь каждой буквы в с т роке rne s s a g e
l e t t e rCount = { ' А ' : О , ' В ' : О , ' С ' : О , ' D ' : О , ' Е ' : О , ' F ' : О ,
' G ' : О, ' Н ' : О, , I 1 : О, ' J' : О, ' К' : О, ' L ' : О, 'М' : О, 'N' : О,
' О' : о, ' Р ' : О, ' Q ' : О, ' R ' : О, ' S ' : О, ' Т ' : О, ' U ' : О, 'V' : О,
'W' : О, 'Х' : О, 'У' : О, 1 z 1 : О}

11 .
for l e t t e r in rne s s age . upp e r ( ) :
12 .
13 .
i f l e t t e r in LETTERS :
14 .
l e t t e rCount [ l e tt e r ] += 1
15 .
16.
return l e t t e rCount
17 .
18 .
1 9 . de f g e t i t ernAt i ndex Z e r o ( i t erns ) :
r e t u r n i t erns [ O ]
20 .
21 .
22 .
2 3 . de f g e t F r e quencyOrde r ( rne s s ag e ) :
24 .
# В о з враща е т с т р о ку букв алф а ви т а , р а с положе нных в п ор ядке
25 .
# убыв ания их ч а с т о т н о с т и в строке rne s s a g e .
26.
27 .
# В о - п е р вых , п олуч аем словарь ч а с т о т н о сти букв
28 .
l e t t e rT o F r e q
g e t Le t t e rCount ( rne s s a g e )
29.
30 .
# В о - в торых , с о з д а ем словарь счетчиков ч а с т о т н о с т и
31 .
# со списком букв по каждому счет чику
freqToLe t t e r = { }
32 .
for l e t t e r in LETTERS :
33 .
34 .
i f l e t t e rT o F r e q [ l e t t e r ] not i n f r e qToLet t e r :
35 .
freqToLet t e r [ l e t t e rToFreq [ l e t t e r ] ] = [ le t t e r ]
else :
36.
37 .
freqToLet t e r [ l e t t e rToFreq [ l e t t e r ] ] . appen d ( l e t t e r )
38 .
39.
# В - т р е т ь их , и зм е н я ем порядок букв в каждом списке на
=

340 Глава 1 9

40.
# обратный порядку " ETAO I N " и пре вращаем списки в строки
for f r e q i n f r e qToLe t t e r :
41.
42 .
f r e qToLet t e r [ fre q ] . s o r t ( key=ETAO I N . f i nd , reve r s e =True )
43 .
f r e qToLet t e r [ fre q ] = 1 1 . j o i n ( f r e qToLett e r [ f r e q ] )
44 .
45.
# В- че твертых , преобразуем словарь f reqToLe t t e r в список
46.
# кортежей ( ключ , значение ) и сортируем е го
freqPa i r s = l i s t ( freqToLe t t e r . i t ems ( ) )
47 .
freqPa i r s . s o r t ( key=g e t i t emAt i nde x Z e r o , reve r s e=T rue )
48 .
49.
# В-пятых , после того как буквы были упорядочены по част отности ,
50 .
# и звлекаем все буквы для формирования о кончатель ной строки
51 .
f r e qOrde r = [ ]
52 .
f o r freqPa i r i n freqPa i r s :
53 .
f r e qOrde r . append ( freqPa i r [ l ] )
54 .
55 .
return 1 1 . j o i n ( fr e qOrde r )
56.
57 .
58 .
5 9 . def e ng l i shFreqMa t chS core ( me s s a ge ) :
# Возвраща е т оценку частотного соответствия для строки
60 .
# me s sage . Совпаде н ия проверяют ся по шести наиболее
61 .
# и наименее часто в с тречающимся буквам в строке
62 .
# и в английском языке в целом .
63 .
64 .
freqOrde r = get FrequencyOrde r ( me s s a g e )
65 .
66.
mat chScore = О
67 .
# Число совпадений для ше сти наиболее часто в стречающихся букв
68 .
for c ommonLet t e r i n E TAO I N [ : 6 ] :
69 .
i f commonLet t e r i n f re q0rde r [ : 6 ] :
70 .
mat ch S c o r e += 1
71 .
# Число совпадений для шести наименее часто в стречающихс я букв
72 .
for uncommonLe t t e r i n ETAOIN [ - 6 : ] :
73 .
i f uncommonLet t e r i n fr e qOrde r [ - 6 : ] :
74 .
mat ch S c o r e += 1
75.
76.
return mat ch S c o r e
77 .

Сохранение букв аnфавита в nор•дке ETAOI N
В строке 4 создается переменная E TAO I N , в которой сохраняются 26
букв алфавита, расположенных в порядке убывания их частотности.
1 . # Определение частотности букв
2 . # h t t p s : / / www . no s t arch . c om/ c r a c k i ngcode s / ( BS D L i censed )
3.
4 . E TAO I N = 1 ETAO I N S HRDLCUMWFGY PBVKJXQZ 1

Ча стотный а н ал из 34 1

Разумеется, не все тексты на английском языке отражают именно такое
частотное распределение. Не составит труда найти книгу, в которой, ска­
жем, буква 'Z' встречается чаще буквы 'Q' . Например, в романе "Гадсби"
американского писателя Эрнеста Винсента Райта буква 'Е' вообще не ис­
пользуется , что приводит к необычному распределению частотности букв.
Однако в большинстве случаев, включая и наш модуль, порядок ETAOIN
будет соблюдаться достаточно точно.
Кроме того, для некоторых функций, входящих в состав нашего модуля,
понадобится также строка, которая содержит все буквы в верхнем реги­
стре, расположенные в алфавитном порядке. Для этого в строке 5 создает­
ся константа LETT ERS.
5 . LETTERS = ' ABCDE FGH I JKLMNOPQRSTUVWXYZ '

Константа LETTERS служит той же цели, что и константа S YМBOLS в пре­
дыдущих программах: она позволяет установить соответствие между буква­
ми и целочисленными индексами.
Теперь рассмотрим, как функция getLet t e r s Coun t ( ) подсчитывает ча­
стотность каждой буквы в заданной строке.

П одсчет 6укв в сообщении
Функция getLe t t e rCount ( ) получает строку me s s age и возвращает
словарь, ключами которого являются буквы, а значениями - количество
появлений каждой буквы в строке.
В строке 1 0 создается словарь l e t t e rCount , для всех ключей которого
установлены нулевые начальные значения.
7 . def getLet t e rCount ( me s sage ) :
8.
# Возвраща е т словарь , ключами которого являют ся буквы ,
9.
# а значениями - число появлений каждой буквы в строке
10 .
l e t t e rCount = { ' А ' : о , ' В ' : О , ' С ' : О , ' D ' : О , ' Е ' : О ,
' G ' : О, ' Н ' : О, , I , : О, ' J' : О, ' К' : О, ' L' : О, 'М' :
' О ' : О, ' Р ' : О, ' Q ' : О, ' R ' : О, ' S ' : О, ' Т ' : О, ' U ' :
' W ' : О, ' Х ' : О, ' У ' : О, 1 z
0}
'

me s s age
' F' : О,
О, ' N' : О,
О,

'V' :

О,

:

Проверяя каждый символ сообщения в цикле f o r (строка 1 2 ) , м ы инкре­
ментируем значения ключей до тех пор, пока они не будут представлять
частотность каждой буквы.
12 .
13 .
14 .

342

f o r l e t t e r in me s s age . upp e r ( ) :
i f l e t t e r i n LETTERS :
l e t t e rCount [ l e t t e r ] + = 1

Гла ва 1 9

В цикле for мы проходим по всем символам строки me s s ag e , преобра­
зованным в верхний регистр. Текущий символ записывается в перемен­
ную l e t t e r . В строке 1 3 мы проверяем, содержится ли символ в строке
LETTERS , поскольку счетчики небуквенных символов нас не интересуют.
Если буква встречается в строке LETTERS , в строке 1 4 инкрементируется
значение l e t t e rCount [ l e t te r ] .
По завершении цикла for функция g e t Le t t e rCount ( ) возвращает сло­
варь l e t t e rCoun t , содержащий счетчики появлений каждой буквы в cтpo­
кe me s s ag e .
16.

return l e t t e rCount

В этой главе мы будем использовать следующую строку (https : / /
e n . wi kiped i a . o r g / w i ki /Al a n_Tu r i ng ) .
" " "Alan Mat h i son T u r i ng was а B r i t i s h mathemat i c i an , l o g i c i a n ,
cryptana l y s t , and compu t e r s c i e nt i st . Не wa s h i g h l y i n f luent i a l i n
t h e deve l opment o f compute r s c i ence , providing а forma l i sat i on o f the
concept s o f " a l g o r i t hm" and " comput a t i o n " w i t h the Turing ma chine .
Turing i s w i d e l y cons idered t o Ье the father o f compu t e r s c i ence and
a rt i f i c i a l i n t e l l i g e nce . Dur i ng World War I I , Turing worked for t he
Gove rnment Code and Cyph e r S chool ( GCCS ) at B l e t ch l e y P a r k , B r i t a i n ' s
codebre a k i ng centre . For а t ime he was head o f Hut 8 , the s e c t i o n
re spons iЬle for Ge rman nava l cryptana l ys i s . Не dev i s e d а numЬ e r o f
t e chnique s f o r b r e a k i ng Ge rman c iphe r s , i nc luding t h e method o f the
ЬоmЬе , an e l e c t r ome chani ca l ma chi ne that cou ld f i nd s e t t i ng s f o r the
E n i gma mach i n e . Aft e r the war he wor ked at the Nat i o n a l Phys i c a l
Laborat o r y , whe re he created o n e o f t he f i r s t de s i gns for а s t o red­
program compute r , the АСЕ . In 1 9 4 8 Turing j o i ned Мах Newman ' s C omput i ng
Labo ratory at Manche s t e r Unive r s i t y , whe re he a s s i s t e d in the devel opment
o f the Manche s t e r comput e r s and became interested i n mathema t i c a l
b i o l ogy . Не wrot e а pape r o n t h e chemi c a l ba s i s o f mo rphogene s i s , and
predicted o s c i l l at i ng chemi c a l react i o n s such as the B e l ou s ov- Z habo t i n s ky
react i o n , whi ch were f i r s t obse rved i n the 1 9 6 0 s . T u r i ng ' s homo s e xua l i t y
r e s u l t e d i n а crimi n a l p r o s e cut i o n i n 1 9 5 2 , when homo s exual act s we re
s t i l l i l l e g a l i n the U n i t e d K i ngdom . Не accepted t r e atment w i t h fema l e
hormone s ( chemi c a l c a s t rat i o n ) a s a n a l t e rnat ive t o p r i s on . Tur i ng d i e d
i n 1 9 5 4 , j us t over two weeks b e f o r e h i s 4 2 nd bi rthday , from cyanide
p o i s on i n g . An i nque s t det e rmi ned that his death was s u i c i de ; his mot he r
and s ome others be l i eved h i s de ath was accident a l . On 1 0 SeptemЬe r 2 0 0 9 ,
f o l l owing a n I nt e rnet campa i gn , B r i t i sh P r ime M i n i s t e r Gordon Brown made
an o f f i c i a l puЫ i c apo l o g y o n beha l f of the Bri t i sh gove rnment f o r " t he
appa l l ing way he was t re a t e d . " As o f Мау 2 0 1 2 а p r i v a t e memЬ e r ' s Ь i l l was
b e f o r e the House o f Lords wh i ch wou l d grant Turing а s t atutory pa rdon i f
enacted . " " "

Частотный а н ализ 343

Для этой строки, содержащей 1 35 вхождений буквы 'А:. , 30 вхождений
буквы ' В ' и т.д. , функция g e t Le t t e rCount ( ) возвращает следующий сло­
варь.
{ 'А'
'I' :
'Q' :
'У' :

: 135, ' В ' : 30, ' С ' : 7 4 , ' D ' : 5 8 , ' Е ' : 196, ' F ' : 37 , ' G ' : 39, ' Н ' : 87 ,
1 3 9 , ' J ' : 2 , ' К ' : 8 , ' L ' : 62 , ' М ' : 5 8 , ' N ' : 1 2 2 , ' О ' : 1 1 3 , ' Р ' : 3 6 ,
2, ' R ' : 106, ' S ' : 89, ' Т ' : 140, ' U ' : 37, 'V' : 14, 'W' : 30, ' Х ' : 3,
21, ' Z ' : 1)

Поnучение nepвoro эnемента кортежа
Функция g e t i t emAt i ndex Z e r o ( ) в строке 1 9 возвращает элемент кор­
тежа с индексом О.
1 9 . d e f get i t emAt inde x Z e r o ( i t ems } :
ret urn i t ems [ O ]
20 .

Эта функция будет передаваться методу s o r t ( ) для сортировки списка
букв по частотности. Более подробно этот вопрос будет рассмотрен в раз­
деле "Преобразование элементов словаря в сортируемый список" .

Уnорядочение 6укв, встречаt0щихся в сообщении,
в соответствии с частотностьlО
Функция getFrequencyOrde r ( ) получает строку me s s age и возвраща­
ет строку, которая содержит 26 букв алфавита в верхнем регистре, распо­
ложенных в порядке убывания их частотности в сообщении. Если строка
me s s age представляет собой осмысленный текст, а не случайное скопле­
ние букв, то, вероятнее всего, она будет аналогична, если не идентична,
строке, содержащейся в константе ETAO I N . Эта функция выполняет основ­
ную часть работы по вычислению оценки частотного соответствия, кото­
рую мы используем в программе взлома шифра Виженера в главе 20.
Например , если передать функции g e t FrequencyOrde r ( ) строку
" " "Al a n Ma t h i s o n T u r i n g
" " " , то она вернет строку ' ET IANOR­
SHCLMDGFU PBWYVKXQJZ ' , поскольку в исходной строке чаще всего встреча­
ется буква ' Е ' , второе место занимает буква ' Т ' , затем ' 1 ' , 'А:. и т.д.
Алгоритм работы функции get Frequen cyOrde r ( ) можно представить
в виде следующей последовательности действий:
1 ) подсчет числа букв в строке;
2) создание словаря счетчиков частотности со списком букв по каждо­
му счетчику;
.

344

Глава 1 9

.

.

сортировка списков букв в порядке, обратном порядку ETAOIN;
преобразование этих данных в список кортежей;
преобразование списка кортежей в окончательную строку, возвра­
щаемую функцией get FrequencyOrde r ( ) .
Рассмотрим каждый из этапов по отдельности.

3)
4)
5)

Подсчет 6укв с nомощью функqии getLe t terCoun t ()

На первом этапе функция get FrequencyOrde r ( ) вызывает функцию
getLette rCount ( ) в строке 28, передавая ей параметр me s s a g e для по­
лучения словаря l e t t e rT o Freq, содержащего счетчики по каждой букве
сообщения.
2 3 . def get FrequencyOrder ( me s s a ge ) :
24 .
# Возвраща ет строку букв алфавита , ра сположенных в порядке
25 .
# убывания их частотно сти в параметре me s s age
26.
# Во-первых , получаем словарь частотности букв
27 .
l e t t e rToFreq
get Let t e rCount ( me s s age )
28 .
=

Если передать этой функции строку 11 11 11Alan Mathison Tur ing . . . " 11 11 ,
то в строке 28 в переменную l e t t e rT o F r e q будет записан следующий сло­
варь.
{ 'А'
'I' :
' N ' :
'W' :

: 135, ' С '
139, ' Н ' :
122 , ' Q ' :
30, ' V ' :

: 74, ' В ' : 30, ' Е ' : 196,
87 , ' К ' : 8 , ' J ' : 2 , ' М ' :
2, ' Р ' : 36, ' S ' : 89, ' R'
14, 'У' : 21, 'Х' : 3, ' Z ' :

' D ' : 5 8 , ' G ' : 3 9 , ' F ' : 37 ,
5 8 , ' L ' : 62 , ' О ' : 1 1 3 ,
: 106, ' U ' : 37 , ' Т ' : 1 4 0 ,
1}

СоJДание словар• счеrчпов часrоrвосrи со сnиаrами 6укв
На втором этапе функция g e t FrequencyOrde r ( ) создает словарь f r e q­
ToLe t t e r , ключами которого являются счетчики частотности, а значени­
ями - списки букв, соответствующих счетчикам частотности. В то время
как словарь l e t t e rToFreq сопоставляет буквенные ключи значениям ча­
стотности, словарь f reqToLe t t e r сопоставляет значения частотности
спискам букв, поэтому необходимо поменять местами ключи и значения в
словаре l e t t e rT o Freq, так как несколько букв могут иметь одну и ту же ча­
стотность. В данном примере у букв ' В ' и ' W ' одинаковые счетчики ( 30 ) ,
и м ы создаем для них словарь наподобие { 3 О : [ ' В ' , ' W ' ] } поскольку
ключи словаря должны быть уникальными.
В строке 32 сначала создается пустой словарь.
,

Частотн ый а н ал из

345

30 .
31 .
32 .
33 .
34 .
35 .
36.
37 .

# Во- в торых , со з даем сло варь счетчиков частотности
# со списком букв по каждому сче тчику
f r e qToLet t e r
{}
f o r l e t t e r i n LETTERS :
i f l e t t e rToFreq [ l et t e r ] not i n f r e qToLet t e r :
freqToLet t e r [ l et t e rToFreq [ l e t t er ] ] = [ l e t t e r ]
else :
freqToLet t e r [ l e t t e rT o F r e q [ l e t t e r ] ] . append ( l e t t e r )
=

В строке 33 начинается цикл по всем буквам строки LET T E RS . Инструк­
ция i f в строке 34 проверяет, содержится ли показатель частотности бук­
вы, т.е. l e t t e rT o F r e q [ l e t t e r ] , в виде ключа в словаре f r e qToLe t t e r .
Если это н е так, т о в строке 3 5 ключ добавляется в словарь, а в качестве
спискового значения задает» def spam ( ) :
print ( ' Bello ! ' )
» > spam ( )
He l l o !
> > > eggs = spam
» > eggs ( )
Hello !

В данном случае мы объявляем функцию spam ( ) , которая выводит на
экран строку ' He l l o ! ' . Но это также означает, что в переменной sраm хра­
нится определение функции. Затем мы копируем функцию, сохраненную в
переменной spam, в переменную e gg s . Теперь мы можем вызывать функ­
цию e g g s ( ) точно так же, как и функцию spam ( ) ! Обратите внимание на
то, что скобки после имени s p am в инструкции присваивания не ставятся.
Если бы они стояли, то в результате была бы вызвана функция s pam ( ) , а
возвращенное ею значение было бы присвоено переменной e gg s .
Поскольку функции трактуются как значения , их можно передавать в ка­
честве аргументов при вызове других функций. Введите в интерактивной
оболочке следующий код.
> > > de f doИath ( func) :
return func ( l O , 5 )
> > > de f adding ( a , Ь ) :
return а + Ь
> > > de f suЬtracting ( a , Ь ) :
return а
Ь
-

348

Глава 1 9

О

» > dомаth ( addin9)
15
> > > doМa.th ( suЬtractin9)
5

Здесь мы объявляем три функции: doMath ( ) , adding ( ) и suЬt ract ing ( ) .
Передавая функцию, хранящуюся в переменной addi ng, функции
doMath ( ) ( о ) , мы тем самым присваиваем значение переменной adding
параметру func , и поэтому вызов func ( 1 О ,
5 ) эквивалентен вызову
функции adding ( ) с аргументами 1 0 и 5 . Вот почему функция doMa t h
( adding ) возвращает значение 1 5 . Точно так же , когда м ы передаем пе­
ременную s uЬt r a c t i ng функции doMa t h ( ) в качестве аргумента, вызов
doMath ( s uЬt rac t ing ) возвращает значение 5 , поскольку func ( l O , 5 ) это то же самое, что и suЬt r a c t i ng ( 1 0 , 5 ) .

Переда ч а функции методу sort ( )
Передача функции (или другого метода) методу s o r t ( ) позволяет реа­
лизовать нестандартный вариант сортировки. Обычно метод s o r t ( ) со­
ртирует элементы списка по алфавиту.
> > > spam = [ ' С ' ,
» > spam . sort ( )
> > > spam
[ 'А' , 'В' , ' С' ]

'В' ,

'А' ]

Но если передать функцию (или метод) в качестве аргумента key, то
элементы списка сортируются в соответствии со значениями, возвращае­
мыми этой функцией при передаче ей каждого из элементов списка. На­
пример, методу s o rt ( ) можно передать строковый метод ETAO I N . f i nd ( )
в качестве аргумента key.
> > > ETAOIN
' ETAOINSНRDLCUМWFGYPBVКJXQZ '
> > > spam . sort ( key=ETAOIN . find)
> > > spam
[ 'А' , 'С' , ' В' ]
=

При получении аргумента ETAO I N . f i nd метод s o rt ( ) не выполняет
сортировку в алфавитном порядке. Вместо этого он сначала вызывает ме­
тод f i nd ( ) для каждого элемента списка, и в результате вызовы ETAO I N .
f i nd ( ' А ' ) , ETAO I N . f i nd ( ' в ' ) и ETAO I N . f i nd ( ' с ' ) возвращают индек­
сы 2 , 1 9 и 1 1 , соответствующие позиции каждой буквы в строке ETAO I N .
Для сортировки элементов списка spam метод s o rt ( ) использует н е исходЧа стотный анализ 349

ные строки ' А ' , ' В ' и ' С ' , а полученные индексы. Именно поэтому строки
' А ' , ' В ' и ' С ' после сортировки располагаются в очередности ' А ' , ' С ' и
' В ' , отражающей порядок их появления в ряду ETAO I N .

Обращение mис101 букв с nомощью метода sort ( )
Чтобы выполнить сортировку букв в порядке, обратном порядку
ETAOIN, необходимо сначала отсортировать их на основании строки
ETAOIN, присвоив аргументу key метода s or t ( ) метод ETAO I N . f i nd. По­
сле того как этот метод вызывается для всех букв и находит их индексы,
метод sort ( ) выполняет сортировку букв на основании их числовых ин­
дексов.
Обычно метод s o r t ( ) сортирует элементы списка в алфавитном или
числовом порядке по возрастанию. Чтобы выполнить сортировку по убыва­
нию, необходимо присвоить аргументу reve r s e метода s o r t ( ) значение
T rue.
Это выполняется в строке 42.
39 .
40.
41 .
42 .
43.

# В - тре т ь и х , изменяем порядок букв в каждом списке на
# обратный порядку " ETAO I N " и пре вращаем списки в строки
f o r f r e q in f r e qToLe t t e r :
f r e qToLe t t e r [ f re q ] . sort ( key=ETAO I N . f i nd , reverse=True )
f r e qToLe t t e r [ f r e q ] = ' ' . j o i n ( f r e qToLett e r [ f r e q ] )

Вспомните, что на данном этапе freqToLe t t e r - это словарь, ключами
которого являются целочисленные значения счетчиков, а значениями списки букв. Мы сортируем строковые значения, соответствующие ключу
freq, а не сам словарь f r e qT oLe t t e r . Словари нельзя сортировать, по­
скольку и них нет порядка элементов: не существует первой или последней
пары "ключ: значение", в отличие от элементов списка.
Использовав строку 11 1 1 11Al a n Mathi s on Turing . . . 11 11 11 в качестве зна­
чения переменной f r e qToLe t t e r , мы получим следующий вид словаря по
завершении цикла.
{ 1 : ' Z ' , 2 : ' QJ ' , 3 : ' Х ' , 1 3 5 : ' А ' , 8 : ' К ' , 1 3 9 : ' I ' , 1 4 0 : ' Т ' , 1 4 : ' V ' ,
2 1 : ' У ' , 3 0 : ' BW ' , 3 6 : ' Р ' , 3 7 : ' FU ' , 3 9 : ' G ' , 5 8 : ' MD ' , 6 2 : ' L ' , 1 9 6 :
' Е ' , 74 : ' С ' , 87 : ' Н ' , 8 9 : ' S ' , 106 : ' R ' , 113 : ' О ' , 122 : ' N ' }

Обратите внимание нато, что строки при ключах 30, 37 и 58 отсорти­
рованы в порядке, обратном порядку ETAOIN. До начала цикла эти пары
"ключ: значение" выглядели так: { 3 О : [ ' В ' , ' W ' ] , З 7 : [ ' F ' , ' u ' ] ,
5 8 : [ ' D ' , ' М ' ] , . . . } . По окончании цикла они приобретают следую­
щий вид: { 3 О : ' BW ' , 3 7 : ' FU ' , 5 8 : ' MD ' ,
}.


350

Глава 1 9





Вызов метода j o i n ( ) в строке 43 превращает список букв в одиночную
строку. Например , элемент f r e qToLe t t e r [ 3 0 ] содержит список [ ' В ' ,
' W ' ] , который превращается в строку ' BW ' .

Сорrиров•а t11ж1tов tловар• 110 чаfJОТНОfJИ
Четвертым этапом , выполняемым функцией g e t FrequencyOrde r ( ) ,
является сортировка строк, хранящихся в словаре f reqToLe t t e r , по ча­
стотности и преобразование этих строк в список. Имейте в виду, что в силу
неупорядоченности пар "ключ: значение" в слоиарях список всех ключей
или всех значений словаря будет представлять собой список элементов,
расположенных в случайном порядке. Отсюда следует, что этот список так­
же нуждается в сортировке.

Исnо111азовани е с11овар н ых методов keys ( ) , value s ( ) и i tems ( )
Каждый из методов keys ( ) , va lue s ( ) и i t ems ( ) словаря преобразует
отдельные его элементы в типы данных, не являющиеся словарями. После
того как словарь преобразован в другой тип данных , его можно преобразо­
вать в список с помощью функции 1 i s t ( ) .
В качестве примера введите в интерактивной оболочке следующие ин­
струкции .
> > > sраш. = { ' cats ' : 1 0 , ' doqs ' : З , ' mice ' : 3 )
» > sраш. . keys О
di ct_keys ( [ ' mi ce ' , ' ca t s ' , ' do gs ' ] )
> > > list ( spaш. . keys ( ) )
[ ' mi c e ' , ' ca t s ' , ' do g s ' ]
> > > list ( spaш. . values ( ) )
[3, 10, 3]

Для получения списка всех ключей словаря применяется метод ke y s ( ) ,
возвращающий объект d i c t _ key s , который затем можно передать функ­
ции l i s t ( ) . Аналогичный метод va lue s ( ) во:шращает объект d i ct_
va l ue s . В приведенных выше примерах мы нолучили с помощью этих ме­
тодов списки ключей и значений словаря соответственно.
Если нужно одновременно получить и ключи, и значения словаря , исполь­
зуйте метод i t ems ( ) , возвращающий объект di ct_i t ems , который превра­
щает пары "ключ: значение" в кортежи. Далее этот объект можно передать
функции 1 i s t ( ) . Введите в интерактивной оболочке следующий код.
> > > sраш. = { • cats • : 1 0 , ' doqs ' : З , ' mice ' : 3 )
> > > list ( spaш. . iteas ( ) )
[ ( ' mi ce ' , 3 ) , ( ' ca t s ' , 1 0 ) , ( ' dog s ' , 3 ) ]

Ча стот ный а н ал из 3 5 1

С помощью вызовов i terns ( ) и 1 i s t ( ) мы преобразуем пары "ключ:
значение" словаря в список кортежей. Это именно то, что необходимо
предпринять в отношении словаря f r e qT o Le t t e r для того, чтобы можно
было отсортировать буквенные строки по частотности.

Прео6разова н11е мементов с.повар� в сортируемыii сп исок
В словаре f re qT oL e t t e r ключами являются целочисленные счетчики
частотности букв, а значениями - однобуквенные строки. Чтобы отсорти­
ровать строки по частотности, мы вызываем метод i t ems ( ) и функцию
l i s t ( ) для создания списка кортежей "ключ: значение". В строке 47 спи­
сок кортежей сохраняется в переменной freqPa i r s .
45.
46.
47 .

# В-четвертых , преобра зуем словарь f reqToLe t t e r в список
# кортежей ( ключ , значение ) и сортируем его
freqPa i rs = l i s t ( freqToLe t t e r . i t erns ( ) )

В строке 48 мы передаем методу s o r t ( ) функцию g e t l t ernAt i ndex­
Z e r o ( ) , которая была создана ранее.
48 .

freqPa i r s . s o r t ( key=get l t ernAt l ndex Z e r o , reve r s e=T rue )

Функция g e t i t ernAt i ndex Z e r o ( ) получает первый элемент кортежа,
которым в данном случае является целочисленный счетчик частотности.
Это означает, что элементы списка f r e q Pa i r s сортируются по числовым
значениям счетчиков частотности. В строке 48 мы передаем методу s or t ( )
значение T ru e в качестве аргумента reve r s e , поэтому кортежи сортируют­
ся по убыванию счетчиков частотности.
Взяв строку " " "Al an Mat h i s o n T u r i n g . . . " " " в качестве примера,
мы должны получить после выполнения строки 48 следующий список
f r e q Pa i r s .
[ (196, ' Е ' ) , (140, ' Т ' ) , (139,
( 10 6 , ' R ' ) , ( 8 9 , ' S ' ) , ( 8 7 , ' Н '
( 3 9 , ' G ' ) , ( 3 7 , ' FU ' ) , ( 3 6 , ' Р '
( 8 , ' к ' ) , ( 3 , ' х ' ) , ( 2 , ' QJ ) ,
'

' I ' ) , ( 13 5 , ' А ' ) , ( 12 2 , ' N ' ) , (113, ' 0 ' ) ,
) , ( 7 4 , ' С ' ) , ( 6 2 , ' L ' ) , ( 5 8 , ' MD ' ) ,
) , ( 3 0 , ' BW ' ) , ( 2 1 , ' У ' ) , ( 1 4 , ' V ' ) ,
(1, ' z' ) ]

Теперь переменная f r e q P a i r s содержит сцисок кортежей, располо­
женных в порядке убывания частотности букв. В каждом кортеже первым
значением является целое число, представляющее собой счетчик частот­
ности, а вторым - строка, содержащая буквы с соответствующей частот­
ностью.

352

Глава 1 9

СоJДание (nжка опорrированных 6укв
Пятый этап, выполняемый функцией g e t FrequencyOrder ( ) , - созда­
ние списка всех строк на основе отсортированного списка кортежей, хра­
нящегося в переменной f r e q Pa i r s . Мы хотим получить единую строку, в
которой буквы расположены по убыванию частотности, поэтому нам не
нужны целочисленные значения , хранящиеся в кортежах. Сначала пере­
менной f r e qOrder присваивается пустой список в строке 52, а затем в ци­
кле f o r в конец списка f reqOrde r присоединяется строка с индексом 1 из
каждого кортежа, хранящегося в переменной f r e q Pa i r s .
50 .
51 .
52 .
53 .
54 .

# В- пятых , после того как буквы б ыл и упорядочены по частотности ,
# извле каем все буквы для формирования о кончатель ной строки
freqOrde r = [ )
f o r freqPa i r i n freqPa i r s :
freqOrde r . append ( f reqPa i r [ l ] )

По завершении цикла переменная f r e qOrde r должна содержать список
[ , Е , , , Т , , , I , , , А , , , N , , , О , , , R , , , s , , , Н , , , С , , , L , , , MD , ,
, G , , , FU , , , р , , , BW , , , у , , , V , , , К , , , Х , , , QJ , , , z , ] .
Далее элементы списка f r e qOrde r объединяются в строку с помощью
метода j o i n ( ) :
56 .

return ' ' . j o i n ( f r e qOrde r )

11 11 11 функция
В примере со строкой 11 1 1 11 Alan Ma t h i son Turing
getFrequencyOrde r ( ) возвращает строку ' ET IANORSHCLMDGFU PBWYVKXQJZ ' .
Таким образом, в данном примере чаще всего встречается буква Е на вто­
ром месте - буква 'Т' , на третьем - буква '1' и т.д.
Получив частотное распределение букв в виде строки, мы можем срав­
нить его с аналогичным распределением для типичных текстов на англий­
ском языке ( ' ETAO I NSHRDLCUМWFGYPBVKJXQ Z ' ) , чтобы оценить степень их
близости.






'

' ,

Вычисnение оценки частотноrо соответствия букв
в сообщении
Функция eng l i s hFreqMa t chScore ( ) получает строку me s s ag e и воз­
вращает целое число в интервале от О до 1 2 , являющееся оценкой частот­
ного соответствия для данной строки. Чем выше эта оценка, тем больше
частотность букв сообщения соответствует частотности букв в типичных
текстах на английском языке .
Ча стотный анализ

353

5 9 . de f
60 .
61 .
62 .
63 .
64 .
65 .

eng l i shFreqMa t chScore ( me s s a ge ) :
# Возвраща е т оценку часто тного соотв е т ствия для строки
# me s s age . Сов падения проверяются по шести наиболе е
# и наименее часто в с тречающимся буквам в строке
# и в англий ском языке в целом .
f reqOrder = get FrequencyOrde r ( me s s age )

Первое, что необходимо сделать для вычисления оценки частотного
соответствия букв, - это упорядочить буквы в соответствии с их частот­
ностью, что и делается в строке 65 с помощью функции g e t Frequency­
Order ( ) . Полученная последовательность сохраняется в виде строки в пе­
ременной f r e qOrde r.
Переменная mat c h S c o r e инициализируется значением О в строке 67
и инкрементируется в цикле for (строка 69) . В этом цикле первые шесть
букв строки ETAO I N сравниваются с первыми шестью буквами строки
f r e qOrde r, и за каждую общую букву начисляется дополнительный балл.
67 .
68 .
69 .
70 .
71 .

mat ch S core = О
# Число соот в е т с т вий для шести наиболее часто в с тречающих ся букв
f o r commonLet t e r i n ETAO I N [ : 6 ] :
i f commonLet t e r i n f r e q0rde r [ : 6 ] :
mat chScore += 1

Вспомните о том , что срез [ : 6 ] - это то же самое, что срез [ О : 6 ] , по­
этому в строках 69 и 70 извлекаются первые шесть букв строк ETAOIN и
f r e qOrder соответственно. Если любая из букв 'Е' , 'Т' , 'Х , ' О ' , '1' или 'N'
также встречается среди первых шести букв строки freqOrde r , то условие
в строке 70 становится равно T rue, и тогда в строке 71 инкрементируется
значение mat ch S c o r e .
Строки 73-75 аналогичны строкам 69-7 1 , з а исключением того, что в
данном случае проверяется наличие последних шести букв строки ETAO I N
( 'V' , 'К' , '] ' , 'Х' , Q и 'Z' ) среди последних шести букв строки f r e qOrde r и
соответствующим образом инкрементируется значение mat c h S c o r e .
'

72 .
73 .
74 .
75 .

'

# Число соот в е т с т вий для шести наиме н е е часто в с тречающихс я букв
f o r uncommonLe t t e r i n ETAO IN [ - 6 : ] :
i f uncommonL e t t e r i n f r e qOrde r [ - 6 : ] :
mat chScore += 1

В строке 77 функция возвращает целое число, содержащееся в перемен­
ной ma t c h S c o r e :

354 Гла ва 1 9

77 .

ret urn mat ch S c o r e

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

Рез1Оме
В этой главе вы узнали о том , как применять функцию s o r t ( ) для сорти­
ровки списков в алфавитном или числовом порядке и как использовать ее
аргументы reve r s e и ke y для изменения параметров сортировки. Мы рас­
смотрели, как преобразовывать словари в списки с помощью словарных
методов key s ( ) , va l u e s ( ) и i t ems ( ) . Вы также узнали о том, что функ­
цию можно передать в качестве аргумента другой функций.
В главе 20 мы воспользуемся написанным в этой главе модулем частотно­
го анализа для взлома шифра Виженера.

Кон т р ол ьные вопросы

Ответы на контрольные вопросы приведен ы в приложении Б.

1.

Что такое частотный анализ?

2.

Какие шесть букв чаще всего встречаются в текстах на а н глийском языке?

3.

Что будет содержать переменная

s p am

после вы полнения следующего кода?

spam = [ 4 , 6 , 2 , 8 ]
spam . s o rt ( reve r s e=True )

4.

Есл и переменная

s p am содержит словарь,

то ка к получить список его ключей?

Ча стотный а н ал из 355

ВЗЛО М Ш ИФ РА В И Ж Е Н Е Р А
"Неприкосиовенностъ частной жи:ти - неотимлемое право
1tе.1tовека и обяза:телъное условие того, 'Чтобъ � он мог житъ с
1�увством самоуважен ия и собствен·ного достоинства ".
Брюс Шнашр, криптограф, 2006 г.

Существуют два метода взлома шифра Ви­
женера. Один из них перебор по словарю, или
словарная атаха (dictionary attack) , предпола­
гаю щий полный перебор всех слов из файла
словаря в качестве ключа. Э то сработает только
в том случае, если искомый ключ является английским словом,
например "RAVEN" или "DESK" . Второй , более сложный метод,
открытый в XIX веке математиком Чарльзом Бэббиджем, дает
результат, даже если ключ представляет собой случайный набор
букв, такой как "VUWFE" или "PNFJ". В этой главе мы напишем
программы для взлома шифра Виженера обоими методами.
-

В это м







rАаве ...

П еребор по словарю
Метод Касиски
Н ахожде н и е множителей
Ти п да нных s e t и фун кция s e t ( )
Сп исковый метод e xt e nd ( )
Функция i t e rt oo l s . p rodu c t ( )

Исnоnьэование nеребора no cnoвaplO дп• вэnома
wифра Виженера методом rру6ой сипы
Сначала мы используем метод перебора по словарю для взлома шифра
Виженера. Файл словаря dictionary. txt (доступный на сайте издательства; см.
введение) насчитывает примерно 45 ООО английских слов. На компьютере
автора дешифрование сообщения размером с длинный абзац с использо­
ванием каждого из этих слов в качестве ключа заняло менее пяти минут.
Это означает, что в тех случаях, когда шифротекст зашифрован с помощью
английского слова, он уязвим для перебора по словарю. Рассмотрим исход­
ный код программы , реализующей перебор по словарю для взлома шифра
Виженера.

Исходный код nроrраммы Vigenere Dictionary
Hacker
Откройте в редакторе файлов новое окно, выбрав пункты меню Fileq
New File. Введите в этом окне приведенный ниже код и сохраните его в
файле vigenereDictionaryHacker.py. Убедитесь в том , что файлы detectEngUsh.py,
vigenereCiphcr.py и pyperclip.py находятся в той же самой папке. Запустите про­
грамму, нажав клавишу .
vigenere. Dictionary. Hacker.py

1 # Взлом шифра Виженера ме тодом перебора по словарю
2 # https : / / www . no s t a rch . com/ crackingcode s / ( BS D L i c e n s e d )
3.
4 . import det e ct E ng l i s h , vigenereC iphe r , pypercl ip
5.
6 . def ma i n ( ) :

358

Глава 20

7.
8.
9.
10 .
11 .
12 .
13 .
14 .
15 .
16.
17 .
1 8 . de f
19.
20 .
21 .
22 .
23 .
24 .
25 .
26.
27 .
28 .
29.
30 .
31 .
32 .
33 .
34 .
35 .
36 .
37 .
38 . if
39 .

c iphe rtext = " " " T z x i sn z eccj x kg nfq l o l mys bbqq I l x c z . " " "
hac kedМe s sage = hackVigene r e Dict i onary ( ciphe rtext )
i f hac kedМe s sage ! = None :
p r i nt ( ' Copying hacked me s s age t o c l ipboard : ' )
p r i nt ( hacke dМe s sage )
pyp e rc l ip . copy ( ha c kedMe s sage )
else :
p r i nt ( ' Fa i l e d to hack encrypt i o n . ' )

hackVigene r e D i c t i onary ( ciphe rtext ) :
f o = open ( ' dict i onary . txt ' )
words = fo . read l i n e s ( )
fo . c l o s e ( )
for word in words :
word = word . s t r ip ( )
# удалить завершающий символ новой с троки
decryptedText = v i gene reCiphe r . de cryptMe s s a g e ( wo r d , c iphe rtext )
i f det e ctEngl i sh . i sEng l i sh ( de c r ypt edText , wordPe rcentage= 4 0 ) :
# Спросить у поль зовател я , найден ли ключ дешифрования
print ( )
p r i nt ( ' Po s s iЬ l e encrypt ion b re a k : ' )
p r i nt ( ' Ke y ' + s t r ( word ) + : ' + dec ryptedText [ : l O O ] )
p r i nt ( )
p r i nt ( ' Ent e r D for done , o r j us t pre s s E nt e r t o cont i nue
breaki ng : ' )
response = i nput ( ' > ' )
'

i f respons e . upper ( ) . st a rt sw i t h ( ' D ' ) :
return decrypt edText
пате
ma i n ( )

ma i n

1

.

П ример выnоnнения nроrраммь1 Vigenere
Dictionary Hacker
Выполнив программу, вы должны получить следующий результат.
Pos s iЫ e encr ypt i o n bre a k :
Ке у ASTROLOGY : The r e c l yecre t s c r k not the qnks I t e l l .
Ent e r D for done , o r j us t p r e s s Ent e r t o cont i nue b r e a k i ng :
>
P o s s iЬ l e encrypt i on bre a k :

Взл ом шифр а В иженера 359

Кеу ASTRONOMY : The r e a l s e cret s are not the o n e s I t e l l .
Ent e r D f o r done , or j u st pre s s Ent er t o cont i nue brea king :
> d
Copying h a c ked rne s s age t o c l ipboard :
The r e a l s e cret s a r e not the ones I t e l l .

Первый ключ, предложенный программой (AS TROLOGY) , не подходит,
поэтому пользователь нажимает клавишу , позволяя программе вы­
полняться до тех пор, пока не будет найден корректный ключ (AS TRONOMY ) .

О структуре nроrраммы
Исходный код программы vigenereDictionaryHacker.py в основном содер­
жит уже знакомые нам инструкции , поэтому мы не будем рассматривать его
построчно. Общий алгоритм таков: функция h a c kV i g e ne r e D i ct i onary ( )
пытается использовать каждое слово из файла словаря в качестве ключа
для дешифрования шифротекста, и если результат дешифрования пред­
ставляет собой осмысленный текст на английском языке (в соответствии
с критериями модуля de t e c t E ng l i s h ) , то она выводит его на экран и
предлагает пользователю завершить процесс дешифрования или продол­
жить его.
Обратите внимание на вызов метода readl ine s ( ) для файлового объек­
та, возвращаемого функцией open ( ) :
20 .

words = fo . read l i n e s ( )

В отличие от метода read ( ) , который возвращает все содержимое фай­
ла в виде одной строки , метод readl i n e s ( ) возвращает список, каждый
элемент которого является строкой из файла. Поскольку каждая строка
словаря содержит по одному слову, переменная words будет содержать спи­
сок всех английских слов от Aarhus до Zurich.
Остальная часть функции (строки 23-36) аналогична коду, который ис­
пользовался в программе взлома перестановочного шифра, рассмотрен­
ной в главе 1 2 . В цикле f o r мы перебираем все слова, содержащиеся в спи­
ске word s , дешифруем шифротекст с использованием очередного слова в
качестве ключа, а затем вызываем функцию detectEng l i s h . i s E ng l i sh ( )
для проверки того, что результат представляет собой осмысленный текст
на английском языке.
Теперь рассмотрим , как взломать шифр Виженера даже в том случае ,
когда ключом служит случайный набор букв, а не слово из словаря .

360

Глава 20

Исnоnьзование метода Касиски дпя оnредеnения
дnины кn1Оча
Метод Касиски предназначен для определения длины ключа шифра Ви­
женера. Это дает возможность применить частотный анализ для независи­
мого взлома каждого из подключей. Первым человеком , которому удалось
взломать шифр Виженера, был Чарльз Бэббидж, но об этом никто не узнал,
так как он не опубликовал свои результаты . Позже аналогичное открытие
сделал немецкий математик Фридрих Касиски, в честь которого и был на­
зван метод. Рассмотрим этапы алгоритма, которые будут реализованы на­
шей программой взлома шифра Виженера.

Нахождение nовrор•ющихс• сеrменrов
В соответствии с методом Касиски первое, что необходимо сделать, это найти в шифротексте повторяющиеся сегменты, содержащие по
крайней мере три буквы. Такие последовательности могут быть повто­
рениями одних и тех же наборов символов в исходном тексте, зашифро­
ванных с помощью одних и тех же подключей. Например, если зашифро­
вать открытый текст "ТНЕ САТ IS OUT OF ТНЕ BAG" с помощью ключа
"SPILLTHEBEANS", то получим следующее:
TНECAT I S OUTOFTНEBAG
SPILLTHEBEAN SSPILLT
LWМNLMPW PYTBXLWММ L Z

Обратите внимание на то, что сочетание "LWМ" повторяется дважды.
Причина заключается в том, что группа букв "LWМ" в шифротексте - это
слово "ТНЕ" , зашифрованное с помощью одних и тех же букв ключа, "SPI",
поскольку так получилось, что ключ повторяется на втором слове "ТНЕ".
Количество букв от начала первого буквосочетания "LWM" до начала вто­
рого такого же буквосочетания , которое мы будем называть интервалом
повторения, равно 1 3. Это дает основания полагать, что длина ключа, при­
меняемого в данном шифротексте, составляет 1 3 букв. Таким образом , мы
выявили длину ключа пуrем простого анализа повторяющихся последова­
тельностей.
Однако в большинстве шифротекстов ключ не будет удобным образом
выравниваться по повторяющимся последовательностям букв, или же
ключ может встречаться между повторяющимися последовательностями
более одного раза, т.е. интервал повторения будет кратным длине ключа,
а не равным ей. Чтобы попытаться найти пуrи решения этой проблемы,

Взл ом шифра В иженер а 36 1

рассмотрим более длинный пример, в котором нам не известно, что собой
представляет ключ.
Если удалить из шифротекста "PPQCA XQVEKGYBNKMAZU YВNGBAL
JON 1 TSZM JYIM. VRA(; VOHT VRAU С TКSG.DDWUO XITLAZU VAW
RAZ С VКВ QP IWPOU" все небуквенные символы, то он примет вид стро­
ки, приведенной на рис. 20. 1 . На этом рисунке также выделены повторяю­
щиеся последовательности - "VM' , "AZU" и "YBN" - и указано количество
букв между каждой парой пщ�ледовательностей .

PPQCAXQVEKG '

r

МAZU

8 букв
GBALJONIТSZМJYr

24 буквы

OvoнOcткsGODWUOXIТLAZUVдvEzcvкBQPIWPOU
3 2 буквы

Рис. 20. J. Повторяющиеся

последовательностн в ш нфротексте

В этом примере существует несколько возможных вариантов длины клю­
ча. Следующий шаг метода Касиски заключается в вычислении множите­
лей всех интервалов повторения для сужения круга возможных вариантов.

Определение множиrепеii 1 инrервапах no1ropeнu
В нашем случае промежутки между последовательностями, т.е. интерва­
лы повторения , равны 8, 8, 24, 32 и 48. Но для нас важны не они , а их мно­
жители.
Чтобы понять, почему это так, рассмотрим сообщение "THEDOGAN­
DTHECAT" (табл. 20. 1 ) и попытаемся зашифровать его с помощью девяти­
буквенного ключа ABCПEFGHI, а также трехбуквенного ключа XYZ. Каж­
дый из этих ключей повторяется на протяжении всей длины сообщения .
Табnица 20. 1 . Шифрование сообщения "TH E DOGAN DTH ECAT" двумя различными

ключами
Ш ифро1 ани е кл ючом
д8CDEFG H I

Ш ифровани е ключом XYZ

Исходн ое сообщени е

TНE DOGAN DTHECAТ

THE DOGAN DTHECAT

Ключ {повторяющийся!

AВC DE FG Н IAВC DE F

XYZXY Z X Y ZXYZX Y Z

Ш и фротекст

T I GG S L G U L T I GF E Y

QFDAM FXLCQFD Z Y S

Как и ожидалось, два ключа приводят к двум разным шифротекстам.
Разумеется, ни исходное сообщение, ни ключ не будут известны взломщи362

Глава 20

ку шифра, но он заметит, что в шифротексте "TIGGSLGULПGFEY" после­
довательность "TIG" встречается при значениях индекса О и 9. Поскольку
9 - О = 9 , интервал повторения для данной последовательности равен 9, а
это может свидетельствовать о том, что истинный ключ является девяти­
буквенным , и в рассматриваемом случае он именно такой.
Однако и в шифротексте "QFDAМFXLCQFDZYS" имеется своя повторя­
ющаяся последовательность, "QFD" , которая встречается при значениях
индекса О и 9. Интервал повторения здесь тоже равен 9, и это, на первый
взгляд, указывает на то, что ключ, использованный для получения данного
шифротекста, насчитывает девять букв. Однако мы знаем , что в действи­
тельности ключ содержит всего три буквы: "XYZ".
Повторяющиеся последовательности встречаются тогда, когда одни и
те же буквы в исходном сообщении ( "ТНЕ" в нашем примере) шифруются
одними и теми же буквами ключа (в данном примере это "АВС" и "XYZ " ) ,
что происходит в том случае, если аналогичные группы в сообщении и
ключе "выравниваются" и шифруются в одну и ту же последовательность.
Подобное выравнивание может встречаться в позициях, кратных длине
реального ключа (таких, как 3, 6, 9, 1 2 и т.д. ) , и именно поэтому трехбук­
венный ключ может порождать последовательности с интервалом повто­
рения , равным 9.
Таким образом , возможная длина ключа может быть обусловлена не
только интервалом повторения , но и его множителями. Множителями
числа 9 являются числа 9, 3 и 1 . Поэтому, если вы нашли последовательно­
сти с интервалом повторения 9, то должны рассмотреть два возможных ва­
рианта длины ключа: 9 и 3. Ключ длиной 1 можно игнорировать, поскольку
шифр Виженера с ключом такой длины сводится к шифру Цезаря.
Второй этап метода Касиски включает нахождение множителей каждо­
го из интервалов повторения (см. рис. 20. 1 и табл. 20.2) .
Табл и ца 20.2. Множител и выявленных интервалов повторения

Инте рвап п овто рени •

Множи тепи

8

2, 4 , 8
2, 4 , 6, 8, 1 2, 2 4
2, 4 , 8, 1 6
2, 4 , 6, 8, 1 2, 2 4 , 4 8

24
32
48

Числа 8, 8, 24, 32 и 48 в совокупности имеют следующие множители: 2,
2, 2 , 2, 4, 4, 4, 4, 6, 6, 8, 8, 8, 8, 12, 12, 1 6, 24, 24 и 48.

Взлом шифра В иженера 363

С наибольшей вероятностью ключами окажутся чаще всего встречаю­
щиеся множители. Поскольку в данном примере чаще остальных встреча­
ются множители 2, 4 и 8, то именно они являются наиболее вероятными
вариантами длины ключа Виженера.

Получение •аждой n·й 6у�rвы сrро•и
Теперь, когда нам известны возможные значения длины ключа, мы мо­
жем воспользоваться этой информацией для дешифрования сообщения по
одному подключу за раз. Предположим, что длина ключа в нашем примере
равна 4. Если нам не удастся взломать шифротекст, мы попытаемся исполь­
зовать ключи длиной 2 или 8.
Поскольку ключ применяется циклично, использование ключа длиной
4 означает, что каждая четвертая буква открытого текста шифруется пер­
вым подключом , каждая четвертая буква исходного текста, начиная со вто­
рой, шифруется вторым подключом и т.д. Мы сформируем строки из букв
шифротекста, шифруемых одним и тем же подключом. Сначала мы опре­
делим, какой будет каждая четвертая буква строки, если начинать с разных
букв, а затем объединим все такие буквы в одну строку. В рассматриваемых
примерах каждая четвертая буква выделяется полужирным шрифтом.
Идентифицируем каждую четвертую букву, начиная с первой:
PPQCAXQVEKGYBNKМAZUYBNGВALJONI T S ZMJYIМVRAGVOHTVRAUCTKSGDDWUOXI TIAZUVAVVRAZCV
КВQPIWPOU

Далее находим каждую четвертую букву, начиная со второй:
PPQCAXQVEKGYBNКМAZUYBNGBALJONITS ZMJYIМVRAGVOHТVRAUCTKSGDDWUOXITLAZUVAVVRAZCV
КВQPIWPOU

Затем проделываем то же самое, начиная с третьей и четвертой буквы,
пока не исчерпаем всю проверяемую длину ключа. В табл. 20.3 приведены
объединенные строки , составленные из букв, выделенных полужирным
шрифтом на каждой итерации.
Таблица 20.3. Строки, составленные из каждой ч етвертой буквы
Н а ч ало отс ч ета

С трока

П ервая буква

PAE BABANZIAHAKDXAAA K I U

Вторая буква

PXKNZN LI M M GTUSWIZVZBW

Третья буква

QQGKUGJTJVVVCG UTUVCQ P

Ч етвертая буква

CVYMYBOSYRO RTDO LVRVPO

364

Глава 20

Применение чatrorнoro анапиэа дn• 1W1ома rаждоrо nодuюча
Если мы угадали длину ключа, то каждая из четырех строк, созданных
нами в предыдущем разделе, должна быть зашифрована с помощью од­
ного ключа. Это означает, что если строка, зашифрованная корректным
ключом, подвергается частотному анализу, то дешифрованные буквы, ве­
роятнее всего, будут иметь высокую оценку частотного соответствия. Рас­
смотрим, как это работает, используя в качестве примера первую строку:
PAEBABAN Z IAHAKDXAAAKI U .
Прежде всего , дешифруем строку 2 6 раз (по одному разу для каждого
из 26 возможных подключей) , используя функцию vigene reCiphe r .
de c ryptMe s s age ( ) (см. главу 1 8 ) . Затем протестируем каждую дешифро­
ванную строку, используя функцию частотного анализа для английского
языка f r e qAna l ys i s . eng l i s h FreqMa t c h S c o re ( ) (см. главу 1 9 ) . Введите в
интерактивной оболочке следующий код.
> > > i111po r t freqAnalysis , viqenereCipher
> > > for suЬkey in ' AВCDEFGHJIJКLМNOPQRSТUVWXYZ ' :
decrypteclМessaqe = viqenereCipher . decryptмes saqe ( suЬkey ,
' PAEВAВANZ IAВAIФXAAAКI U ' )
print ( suЬkey , decrypteclМessaqe ,
freqAnalysis . enqlishFreqМatchScore (decrypteclМessaqe) )
А PAEBABAN Z IAHAКDXAAAK I U 2
В OZ DAZAZMYH Z G Z JCWZ Z Z JHT 1
- -

о пуще но

- -

Результаты представлены в табл. 20.4.
Табnица 20.4. Оценки частотного соответствия для каждого варианта

деш ифрова ния
Деш ифрование

Оце нка частотноrо
соответстви 11

'А'

' PAEBABAN Z I AHAK DXAAAK I U '

2

'В'

' OZ DAZAZMYH Z G Z JCWZ Z Z JHT '

'С'

' NYC Z Y Z YLX G Y F Y I BVYYY I G S '

1

'О'

' MXBYXYXKWFXEXHAUXXXHFR '

о

'Е'

' LWAXWXWJVEWDWG Z T WWWGEQ '

' F'

' KVZ WVWV I U DVCV F Y S VVVFDP '

'G'

' JU YVUVUHTCUBUEXRUUUECO '

П одкn юч

о

Взл ом шифр а В иженер а 365

Окан:ча:ние табл. 20. 4
Деwифрование

Оценка частотноrо
с оответстви•

'Н'

' I TXUTUTGS BTAT DWQT T T DBN '

1

,I,

' H S WT S T S FRAS Z S CVP S S S CAM '

2

' J'

' GRVS RSREQZ RYRBUORRRB Z L '

о

'К'

' FQURQRQD P YQXQATNQQQAYK '

'L'

' E PTQPQPCOXPWP Z S MP P P ZX J '

'М'

' DO S P O POBNWOVOYRLOOOYW I '

'N'

' CNRONONAМVNUNXQKNNNXVH '

'О'

' BMQNMNMZLUMTMWP JМММWUG '

П одкn �оч

о
2

'Р'

' ALPMLMLYKTL SLVO I L LLVT F '

'Q'

' Z KOLKLKX JS KRKUNHKKKU S E '

'R'

' Y JNKJKJWI RJQJTMG JJJTRD '

'S'

' X I MJI J I VH Q I P I S L F I I I S Q C '

'Т'

' WHL I H I HUGPHOHRKEHHHRPB '

'U'

' VGKHGHGT FOGNGQJDGGGQOA '

'V'

' U FJGFG F S E N FMF P I C F F F PN Z '

1

'W'

' T E I FE FE RDMELEOH B E E E OMY '

2

'Х'

' S DHE DE DQCL DKDNGADDDNLX '

2

'У'

' RCGDCDCPBKCJCMF Z CCCMKW '

о

' QB FCBCBOAJB I BL E YBBBLJV '

о

1z1

о

Подключи, которые приводят к получению вариантов дешифрования
с наибольшей оценкой , являются самыми вероятными кандидатами на
роль реального ключа. В табл. 20.4 подключи ' А ' , ' I ' , ' N ' , ' W ' и ' Х ' дают
наивысшую оценку частотного соответствия для первой строки. Обратите
внимание на то, что оценки в целом имеют низкие значения, поскольку
объем шифротекста не позволяет получить достаточно длинную тестовую
строку, но для данного примера этого вполне достаточно.
Следующий шаг заключается в повторении этого процесса для осталь­
ных трех строк с целью выявления наиболее вероятных вариантов под­
ключей. Окончательные результаты приведены в табл. 20.5.
Поскольку для первой строки существуют пять возможных подключей,
для второй - два, для третьей - один и для четвертой - пять, общее коли­
чество возможных комбинаций составляет 50 ( 5 2 1 5 ) . Другими слова·

366

Глав а 20

·

·

ми, нам нужно перебрать 50 возможных ключей. Это намного лучше, чем
полный перебор 26 26 26 26 (или 456 976) возможных ключей, к кото­
рому пришлось бы прибегнуть, если бы мы не сузили список кандидатов.
Разница становится еще более существенной в случае ключей Виженера
большей длины.
·

·

·

Таблица 20.5 . Наиболее вероятные подкл юч и

дnя

тести р уемых стр ок

Наиболее 1еро1тные

Строка wифротекста

ПОАКЛ IО Ч И

PAE BABANZIAHAKDXAМKI U

A, l, N, W, X

PXKNZN LI M M GTU SWIZVZBW

1, z

QQG K U GJTJVWCG UTUVCQ P

с

CVYMYBOSY R O RTDOLVRVPO

К, N , R , V, У

Перебор во1можных 110Дl(Лючеi меrодом rpy6oi силw
Метод грубой силы предполагает полный перебор всех возможных комбинаций подключей. Все 50 возможных комбинаций приведены ниже.
AICK

l lC K

NICK

WICK

XICK

AICN

llCN

NICN

WICN

XICN

AICR

llCR

NICR

WICR

XICR

AICV

l lCV

N I CV

WICV

XICV

AICY

l lCY

N I CY

WICY

XICY

AZCK

IZCK

NZCK

WZC K

XZCK

AZC N

IZCN

NZCN

WZC N

XZCN

AZC R

IZCR

NZCR

WZC R

XZCR

AZCV

IZCV

NZCV

wzcv

xzcv

AZCY

I ZCY

NZCY

WZCY

XZCY

Завершающим этапом алгоритма, реализуемого нашей программой
взлома шифра Виженера, будет тестирование всех 50 возможных вариан­
тов дешифрования на полном шифротекстс, чтобы увидеть, какие из них
приводят к получению осмысленного текста на английском языке. В ходе
этого процесса мы должны обнаружить, что ключом для дешифрования
шифротекста "PPQCA XQVEKG"." является WICК.

Взлом шифра Виженера 367

Исходный код nроrраммы Viqenere Backer
Откройте в редакторе файлов новое окно, выбрав пункты меню Fileq
N ew. Введите в этом окне приведенный ниже код и сохраните его в файле
vigenereHacker.frY. Убедитесь в том, что файлы detectEngUsh.frY, freqAnalysis.frY,
vigenereCipher.frY и frYperclip.frY находятся в той же самой папке. Запустите про­
грамму, нажав клавишу .
Шифротекст, заданный в строке 1 7 программы, неудобно вводить вруч­
ную. Чтобы избежать возможных опечаток, скопируйте его из файла ис­
ходного кода, доступного на сайте издательства (см. введение ) . Для выяв­
ления возможных различий между текстом вашей программы и текстом
программы, приведенным в книге, воспользуйтесь онлайн-утилитой d i f f
(http : / / i nventwi t hpython . com/hacking / di f f / ) .
vigenere. Hacker.py

1.
2.
3.
4.
5.
6.
7.
В.
9.
10.
11.
12 .
13.
14 .
15.
16.
17 .

# Программа в злома шифра Виженера
# https : / / www . nostarch . com/ crackingcode s / ( BS D L i censed )
import i t e rt oo l s , re
import vi gene reCiphe r , pyp e r c l ip , freqAna l ys i s , detectEng l i s h
LETTERS = ' ABCDEFGHI JKLМNOPQRSTUVWXYZ '
# ограничение длины проверяемых ключей
МАХ КЕУ LENGTH = 1 6
NUM_MOST_FREQ_LETTERS = 4 # ограничение количества букв на подключ
# T rue - отключе ние вывода
S I LENT MODE
Fa l s e
NONLETTERS PATTERN
re . comp i l e ( ' ( ЛА- Z ] ' )
=

=

de f ma i n ( ) :
# Этот шифротекст можно скопировать из
# файла исходного кода ( см . введение )
ciphertext
" " " Adi z Avt z qeci Tmzubb wsa т Pmi l qev ha lpqavt a kuo i ,
lgouqda f , kdmkt svmzt s l , i z r xoexgh z r kkus i t aa f . V z wsa twbhdg
uba lппnzhdad qz
=

- -

18 .
19.
20.
21.
22 .
23.
24 .
25.
26.
27 .

368

опуще но - -

a zmtmd ' g widt ion bwna f z t zm T cpsw wr Z j rva i vdcz e a i gd y zmЬo
Tmzubb а kbmhptg z k dvrvwz wa e f iohzd . " " "
hac kedMe s s age = hackVigenere ( ciphertext )
i f hackedМe s s age ! = None :
print ( ' Copying hac ked me s s age to cl ipboard : ' )
print ( hac kedMe s s age )
pype rcl i p . copy ( hackedМe s s age )
else :
print ( ' Fa i l e d t o hack encryp t i o n . ' )

Гла ва 20

2 8 . de f
29.
30 .
31 .
32 .
33 .
34 .
35 .
36 .
37 .
38 .
39 .
40.
41.
42 .
43 .
44.
45.
46.
47 .
48 .
49.
50 .
51 .
52 .
53 .
54 .
55 .
5 6 . de f
57 .
58 .
59 .
60 .
61 .
62 .
63 .
64 .
65 .
66 .
67 .
68 .
69 .
70 .
71 .
72 .
73 .
74 .
75 .
76.

fi ndRepe a t S e quence s Spacings ( me s s age ) :
# Находит в сообщении любые 3 - , 4 - и 5-буквенные повторяющиеся
# последо ватель ности . Возвращает словарь , в котором ключи - это
# последо ватель ности , а значения - списки интервалов повторения .
# Исполь зуем регулярное выражение для удаления небуквенных символов
me s s age
NONLETTERS _ PATTERN . sub ( ' ' , me s s age . upper ( ) )
=

# Получение списка последов атель ностей , найде нных в сообщении
s e qSpacings
{}
# ключи - последовательности , з н ачения списки интервалов повторения
for s eqLen i n range ( 3 , 6 ) :
for s e qStart in range ( l en ( me s sage ) - s e qLen ) :
# Получение очередной последователь ности
seq
me s sage [ s eqStart : seqStart + s eqLen ]
=

# Поиск э той последователь ности в осталь ной части сообщения
for i i n range ( s eqStart + s e qLen , l e n ( me s sage ) - seqLen ) :
i f me s sage [ i : i + s e qLen ]
seq :
# Найде на повторяюща яся последовател ь ность
if s e q not i n s e qSpacing s :
s eqSpacings [ se q ]
[ ] # пустой список
==

=

# Добавить интервал пов торения между исходной
# и повторившейся последователь ностями
s e qSpacings [ se q ] . append ( i - seqStart )
return s e qSpaci ngs

getUseful Factors ( num) :
# Возвращает список поле зных множителей параметра num . Полезными
# считаются множители от 2 до МAX_KEY_LENGT H . Например , вызов
# getUseful Fact o r s ( 1 4 4 ) вернет [ 2 , 3 , 4 , 6, 8 , 9, 1 2 , 1 6 ] .
i f num < 2 :
return [ ]
factors

=

[]

# числа , меньшие 2 , не имеют поле зных множителей
# список найденных множителей

# При поиске множителей необходимо проверять лишь целые
# числа вплоть до МАХ КЕУ LENGTH
for i i n range ( 2 , МAX_KEY_LENGTH + 1 ) : # множитель 1 бе споле зен
if num % i
О:
facto rs . append ( i )
otherFactor
int ( num / i )
i f othe r Factor < МАХ КЕУ LENGTH + 1 and otherFactor ! 1 :
factors . append ( othe r Fact or )
return l i s t ( set ( factors ) ) # удаляем дублика ты
==

=

=

Взл ом шифр а Виж енер а 369

7 7 . de f get i t emAt i ndexOne ( x ) :
78 .
return x [ l ]
79.
80 .
8 1 . de f getMo s tComrnonFacto rs ( s eqFactors ) :
82 .
# Во-первых , подсчитываем повторы множителей в словаре seqFactors
83 .
factorCoun t s = { } # ключ - множитель ; значение - число пов торений
84 .
85 .
# Ключи словаря s e qFactors - э т о цепочки букв , а значения - списки
86.
# множителей интервалов п о в т орения . Словарь выглядит примерно так :
87 .
# { ' GFD ' : [ 2 , 3 , 4 , 6 , 9 , 1 2 , 1 8 , 2 3 , 3 6 , 4 6 , 6 9 , 92 , 1 3 8 , 2 0 7 ] ,
' ALW ' : [ 2 , 3 , 4 , 6 , . . . ] , . . . }
88 .
for s e q in s eqFact o r s :
89.
factorLi s t = seqFact o r s [ se q ]
for f a c t o r i n factorLi s t :
90 .
91 .
i f factor not in factorCount s :
factorCount s [ factor ] = О
92 .
facto rCount s [ fact o r ] += 1
93 .
94 .
95 .
# В о - в торых , объединяем множители и сче тчики повторений в кортежи
96 .
# и со здаем список т а ких кортежей , чтобы их можно был о сортировать
97 .
factors ByCount = [ ]
98 .
for factor i n factorCount s :
# Исключаем множи т ели , которые больше , чем МАХ КЕУ LENGTH
99 .
if factor ) ,

166 .

# по оценкам : ч е м выше ,

1 67 .

# eng l i shFreqМa t chScore ( )

1 68 .

freqScores = [ ]

169.

for pos s iЬ leKey i n LETTERS :

170 .

de c ryp t edText

=

[ ( < буква > ,
. . .

] . Список сортируе т с я

тем лучше . См . коммент арии к функции
в модуле f r e qAna l ys i s . py .

vigene reCiphe r . decr yptMe s s age ( po s s i Ь l e Ke y ,

nthLe t t e r s )
171 .

keyAndFreqMat chTuple =

( po s s i Ь l e K e y ,

f r e qAna l ys i s . engl i s h FreqMat chScore ( decryptedText ) )
172 .

freqScore s . append ( keyAndFreqMat chTup l e )

Взл ом шифра Виже н ера 37 1

173 .

# Сортир о в ка по оценкам частотного с о о т в е тствия

174 .

freqScores . sort { key=get i t ernAt i ndexOne , reverse=True )

175 .
176 .

a l l FreqS core s . append { freqS core s [ : NUM_MOST_FREQ_LETTERS ] )

177 .
178 .
17 9 .
180 .
181 .
182 .
183 .
184 .

i f not S I LENT MODE :
for i i n range { l en { al l FreqS cores ) ) :
# Исполь зуем i + 1 , чтобы первая буква не считалась 0 - й
print ( ' Po s s iЫ e letters for letter %s o f the key : ' %
( i + 1 ) , end= ' ' )
for freqScore i n a l l FreqS core s [ i ] :
p r i nt ( ' % s ' % freqS core [ O J , end= ' ' )
print ( ) # переход на новую строку

185 .
186 .
187 .

# Про в еряем все комбинации наибол е е вероятных букв

188 .

for i ndexe s i n i t e rtool s . product ( range { NUM_MOS T_FREQ_LETTERS ) ,
repeat=mo s t L i ke l yKeyLength ) :
# Создаем в озможный ключ из букв в списке a l l FreqS cores
p o s s iЬ l e Ke y = ' '
for i in range ( mo s t L i ke l yKeyLength ) :
pos s i Ы eKey += al l FreqScore s [ i ] [ i ndex e s [ i ] ] ( 0 )

189 .
190 .
191 .
192 .
1 93 .
194 .
195 .

# для каждой по зиции в ключ е

i f not S I LENТ MODE :
print { ' Att emp t i ng with key : % s ' % { p o s s i Ы eKey ) )

196 .
197 .

decryptedText = vigenereCiphe r . decryptMe s s age ( po s s iЬleKey,
ciphe rtextUp )

198 .
199 .

i f det e ctEngl i sh . i sEng l i sh { de c ryp t edText ) :

200 .

# Задаем исходный регистр букв во в зломанном mифротексте

201 .

o r i gCa s e = [ ]
for i i n range { l en ( ciphe rtext ) ) :
i f ciphe rtext [ i ] . i supper ( ) :
o r i gCa se . append ( de cryptedText [ i ] . uppe r { ) )
else :
o r i gCase . append ( de cryptedText [ i ] . l owe r { ) )
dec ryptedText = ' ' . j o i n ( o r i gCa s e )

202 .
203 .
204 .
205 .
206 .
207 .
208 .
209.

# Спросить у поль зовател я ,

210 .

print ( ' Pos s i Ы e encryp t i o n hac k with key % s : ' %
( po s s i Ы eKey ) )
print ( decrypt edText [ : 2 0 0 ] ) # выводим первые 2 0 0 симв олов
print ( )
p r i nt ( ' Ent e r D i f done , anything e l s e t o conti nue
hacking : ' )
response = i nput ( ' > ' )

211 .
212 .
213 .
214 .

найден ли ключ дешифрования

215 .

i f response . s t rip { ) . uppe r ( ) . s t art swith { ' D ' ) :
re turn de cryptedText

216.
217 .

372

Глава 20

218 .
219.
220 .

# Получит ь о смысленный текст не удалось ,

поэ т ому воз вращаем None

return None

221 .
222 .
2 2 3 . de f hackVigenere ( ciphe rtext ) :
224 .
# Прежде в с е г о , необходимо применить ме тод Касиски
225.
# для выяснения в о зможной длины ключа
226 .
227 .
228 .
229.
230 .
231 .
2 32 .
233 .
234 .
235.

236 .
237 .
238 .

a l l Li ke l yKeyLengths = kas i s ki Examination ( ciphertext )
i f not S I LENT MODE :
keyLengt hS t r = 1 1
for keyLength i n a l l L i ke l yKe yLengths :
keyLengthS t r += 1 % s 1 % ( keyLength )
print ( 1 Ka s i s ki exami nation resul t s say the mos t l i ke l y
k e y l engths are : 1 + keyLengthS t r + 1 \ n 1 )
hackedМe s s age = None
for keyLength i n a l l L i ke l yKe yLengths :
i f not S I LENT MODE :
print ( 1 At t empt i ng ha c k with key l ength % s
( % s po s s iЬ l e keys ) . . . 1 % ( keyLeng t h ,
NUM_MOST_FREQ_LETTERS * * keyLength ) )
hackedMe s sage = att emptHackWi thKeyLength ( ciphe rtext , keyLengt h )
i f hackedМe s s age ! = None :
break

239.
240.
241.
242 .
243.
244 .
245.
246.
247 .
248 .
249.

250 .
251 .
252 .
253 .

# Е сли ни один из найденных с помощь ю ме тода Касиски вариантов
# длины ключа не сработал , начать атаку ме т одом грубой силы

i f hackedМe s s age == None :
i f not S I LENT MODE :
print ( 1 UnaЫe to hack me s sage with l i ke l y key l e ngth ( s ) .
Brut e - forcing key length . . . 1 )
for keyLength i n range ( l , МAX_KEY_LENGTH + 1 ) :
# Не перепроверять длину ключ а ,
ме т одом Ка сиски

уже опробо ванную

i f keyLength not in a l l L i ke l yKeyLengths :
I f not S I LENT MODE :
print ( 1 At t empting hack with key length % s
( % s po s s iЫ e keys ) . . . 1 % ( keyLeng t h ,
NUM_MOST_FREQ_LETTERS * * keyLength ) )
hackedМe s sage = attemptHackWi thKeyLength ( ciphe r t ext ,
keyLength )
i f hackedМe s sage ! = None :
break
return hackedМe s s age

254 .
255 .
2 5 6 . # Е сли файл vi genereHacker . py выполняется как про грамма
2 5 7 . # ( а не импор тирует с я как модуль ) ,
ma i n ' :
258 . if
name
259 .
ma i n ( )

выз в а т ь функцию mai n ( ) :

Взл о м шифра Виже н ер а 3 7 3

Пример выnо.nнени• nроrраммы Vigenere Hacker
Выполнив программу, вы должны получить примерно следующие ре­
зультаты .
Kas i s ki exarninat ion r e s u l t s s a y the rnos t l i ke l y key l engths a r e : 3 2 6 4
12 8 9 1 6 5 11 10 15 7 14 13
Atternpting hack with
Pos s i Ы e l e t t e r s for
Poss iЫe l e t t e r s for
P o s s i Ы e l e t t e r s for
At ternpt ing with key :
Atternpting with ke y :
- - oпyщeнo - -

Atternpting with key :
Atternpting with key :
Atternpting hack with
Pos s iЬ l e l e t t e r s for
Pos s iЫ e l e t t e r s for
Atternpting with key :
Atternpting with ke y :
- - опущено - -

k e y l ength 3 ( 6 4 pos s i Ы e keys ) . . .
l e t t e r 1 of the key : А 1 М Е
l e t t e r 2 of the key : S N О С
l e t t e r 3 of the key : V I Z В
ASV
AS I
ECZ
ЕСВ

key l ength 2 ( 1 6 pos s i Ы e keys ) . . .
l e t t e r 1 o f the key : О А Е Z
l e t t e r 2 of the key : М S I D
ОМ

OS

Atternpting with key : Z I
Atternpting with key : Z D
Atternpting h a c k w i t h key l ength 6 ( 4 0 9 6 pos s iЬ l e keys ) . . .
Poss iЫe l e t t e r s for l e t t e r 1 o f the key : А Е О Р
Pos s iЫ e l e t t e r s for l e t t e r 2 o f the key : S D G Н
Pos s i Ы e l e t t e r s f o r l e t t e r 3 o f the key : I V Х В
Pos s iЬ l e l e t t e r s for l e t t e r 4 o f the key : м z Q А
Pos s i Ы e l e t t e r s f o r l e t t e r 5 of the key : о в Z А
P o s s i Ы e l e t t e r s for l e t t e r 6 o f the key : V I к z
Atternpting with key : AS I MOV
Pos s i Ы e encryption hack with key ASIMOV :
Alan Mat h i s on Turing was а B r i t i sh rnatherna t i c i a n , l o g i c i a n , cryptana lys t , and
cornput e r s c i e nt i s t . Не was highly influenti a l in the deve loprnent of cornputer
s c i e nce , providing а forrna l i s a t i on of the con
Enter D if done , anything e l s e to continue hacking :
> d
Copying hacked rne s s a g e to cli pboa rd :
Alan Math i s on Turing was а B r i t i s h rnatherna t i cian , l o g i c i a n , cryptana l ys t , and
cornput e r s c i ent i s t . Не was highly influen t i a l in the deve l oprnen t o f cornput e r
- - опущено - -

374 Глава 20

Импорт модуnей и функция main ( )
Рассмотрим исходный код программы для взлома ши фра Виженера.
Данная программа импортирует различные модули, в том числе и новый
модуль i t e r t oo l s , о котором вскоре будет рассказано более подробно.
1.
2.
3.
4.
5.

# Программа в злома шифра Виженера
# https : / /www . n o s t a rch . com/ cracki ngcode s / ( BS D L i ce n s e d )
import i t e r t oo l s , re
import vigenereCiphe r , pype rcl i p , f r e qAna l ys i s , det e ct E ng l i sh

В строках 7-1 1 задается несколько констант, о назначении которых мы
поговорим, когда будем обсуждать код, в котором они используются .
Функция ma i n ( ) напоминает аналогичные функции предыдущих про­
грамм взлома.
1 4 . de f
15 .
16 .
17 .

ma i n ( ) :
# Этот шифротекст можно скопировать из
# файла исходного кода ( см . введение )
ciphertext = " " " Ad i z Avt z q e c i Tmzubb w s a т Pmi lqev ha lpqavt a kuo i ,
l g ouqda f , kdmkt s vmzt s l , i z r xoexgh z r kkus i t a a f . V z w s a twbhdg
ubalmmzhdad qz
- -

18 .
19.
20 .
21 .
22 .
23 .
24 .
25 .

o nyщe нo

- -

a zmtmd ' g widt i o n bwna f z t zm T cpsw w r Z j rva ivdcz e a i gd y zmЬo
Tmzubb а kbmhptg z k dvrvwz wa e fi o h z d . '""'
hackec!Мe s sage = hackV i genere ( c iphe rt ext )
i f hackec!Мe s s age ! = None :
print ( ' Copyi ng hacked me s s age t o c l ipb oa rd : ' )
p r i n t ( hac kedMe s s age )
pyp e r c l ip . copy ( ha c kec!Мe s sage )
else :
print ( ' Fa i l ed t o h a c k encrypt i on . ' )

Ши фротекст передается функции h a c kVigenere ( ) , которая возвраща­
ет либо деши фрованную строку, если попытка взлома оказалась успешной,
либо значение None в случае неудачи . В случае успеха программа выводит
взломанное сообщение на экран и копирует его в буфер обмена.

Нахождение nовторя1Ощихся nосnедоватеnьностей
Функция f i ndRepe a t S e quenc e s Sp a c i n g s ( ) реализует первый этап ме­
тода Касиски, обнаруживая все повторяющиеся последовательности букв
в строке сообщения и вычисляя длину интервалов между ними.
Взл ом шифра Виженера 375

2 8 . de f f i ndRepeatS equence s Spacings ( me s s a ge ) :
33 .
34 .
35 .
36 .
37 .

- - опущено - -

# Исполь зуем ре гулярное выражение для удаления небуквенных симв олов
me s s age
NONLETTERS PATTERN . sub ( ' ' , me s s age . upper ( ) )
_
=

# Получение списка последов атель ностей , найденных в сообщении
s e qSpacings = { ) # ключи - последователь но сти , значения списки интервалов пов торения

В строке 34 сообщение преобразуется в верхний регистр, и с помощью
метода s ub ( ) из него удаляются любые небуквенные символы.
Словарь s e qSpa c i ng s , создаваемый в строке 37, предназначен для хра­
нения повторяющихся последовательностей в качестве ключей. Значени­
ями словаря являются списки целочисленных значений, соответствующих
количеству букв между повторными вхождениями данной последователь­
ности. Например, если в параметре me s s age будет передана наша тестовая
' , то функция f i ndRep e a t S e quence Spa c i n g s ( ) вер­
строка ' P PQCAXQV
нет словарь { ' VRA ' : [ 8 , 2 4 , 3 2 ] , ' AZU ' : [ 4 8 ] , ' YBN ' : [ 8 ] } .
В цикле f o r , который начинается в строке 38, перебираются все после­
довательности, встречающиеся в строке, и выявляются повторы.
.

38 .
39.
40 .
41 .

.

.

f o r s e qLen i n range ( 3 , 6 ) :
f o r s e qS t a rt i n range ( l en ( me s s a g e ) - s eqLen ) :
# Получение очередной последователь н о с ти
seq
me s s age [ s e q S t a r t : s e q S t a r t + s e qLen ]
=

На первой итерации цикла ищутся повторяющиеся последовательно­
сти , которые содержат ровно три буквы. На следующей итерации ищут­
ся последовательности из четырех букв и т.д. Изменяя границы диапазо­
на ( 3 , 6 ) в строке 38, вы сможете управлять тем , последовательности
какой длины будет проверять программа. В то же время в большинстве
шифротекстов вполне можно ограничиться поиском повторяющихся по­
следовательностей длиной три , четыре и пять букв. Дело в том , что, с
одной стороны , такие последовательности достаточно длинные для того,
чтобы не считать повторения случайными, а с другой стороны - доста­
точно короткие, что увеличивает вероятность их повторений. Текущая
длина последовательности , проверяемая в цикле f o r , хранится в пере­
менной s e qLen.
Во вложенном цикле f o r , который начинается в строке 39, с помо­
щью срезов из строки me s s age извлекаются все возможные подстроки
длиной s e qLen. Начало среза хранится в переменной s e q S t a r t . Напри­
мер, если переменная s e qLen равна 3, а параметр me s s age содержит
376

Глава 20

строку ' P PQCAXQ ' , то на первой итерации
0123456
Ин дексы
mes sage р р о с A X Q
( s e q S t a r t = О ) извлекаются первые три
seqStart
о
РРО
символа строки , и мы получаем подстроку
р с
seqStart
1
seqStart
2
QCA
' P PQ ' . Следующим значением s e q S t a r t бу­
seqStart
3
САХ
дет 1 , и мы извлекаем подстроку ' PQC ' . Эти
seqStart
4
AXQ
действия повторяются для всех последую­
Рис. 20.2. З ночення
щих индексов, пока не будут извлечены попеременной seq взовнснмостн
следние три символа строки, для которых
от значення seqS t a r t
индекс начала среза определяется выраже­
нием l e n ( me s s a g e ) - s eqLe n. В результате
мы получаем последовательности, приведенные на рис. 20.2.
Цикл выполняется для всех индексов вплотьдо l е n ( me s s a g e ) - s e qLen,
а текущий индекс начала среза присваивается переменной s e q S t a r t .
В строке 4 1 полученный срез записывается в переменную s eq.
В строке 44 начинается еще один вложенный цикл for, в котором в
строке сообщения ищутся повторные вхождения данного среза.
=

=

=
=

=

43.
44 .
45.

# Поиск этой последователь ности в осталь ной части сообщения
for i in range ( s eqStart + s eqLen , l e n ( me s sage ) - s eqLen ) :
i f me s s age [ i : i + s eqLe n ] == s e q :

Переменная i поочередно принимает значения индексов всех возмож­
ных последовательностей длиной s eqLen, содержащихся в строке те s s a g e .
Индексы изменяются в диапазоне от s e q S t a r t + s e qLen (позиция сразу
за концом текущей последовательности s eq) и до l e n ( me s s a g e ) - s eqLe n
(последний индекс, при котором еще может быть обнаружена последова­
тельность длиной s e qLen) . Например, если параметр me s s a g e содержит
строку ' P PQCAXQVEKGYBNKМAZUYBN ' , переменная s e q S t a r t равна 1 1 , а пе­
ременная s eqLe n равна 3 , то в строке 41 в переменную s e q будет записано
значение ' YBN ' , и в цикле f o r сообщение будет просматриваться начиная
с индекса 1 4 .
Выражение me s s age [ i : i + s eqLe n ] в строке 45 возвращает подстроку,
которая сравнивается с переменной s eq, чтобы выяснить, не является ли
она ее повторением. Если обнаружен повтор, то в строках 46-52 вычисля­
ется интервал повторения, который добавляется в словарь s eqSpa c i n g s .
Н а первой итерации цикла переменная s e q в строке 4 5 сравнивается с
подстрокой ' КМА ' , на второй - с подстрокой ' МАZ ' , затем - с подстрокой
' AZ U ' и т.д. Когда переменная i достигнет значения 1 9 , в строке 45 обна­
ружится , что подстрока ' YBN ' совпадает с переменной s e q , и тогда будут
выполнены строки с 46 по 52.

Взл ом шифра Виженер а 377

Найдена повт оряюща яся последовател ь но с т ь
i f s e q n o t i n s e qSpacing s :
s eqSpacings [ s e q J = [ ]
# пустой список

46.
47 .
48 .
49.
50 .
51 .
52 .

#

# Добавить интервал повторения между исходной
# и повторившейс я п о следовател ь н о с т ями
seqSpacings [ s e q ] . append ( i - s e q S t a rt )

В строке 47 проверяется , содержится ли подстрока s e q в качестве клю­
ча в словаре seqSpa c i ng s . Если такого ключа еще нет, то в строке 48 он
добавляется в словарь, а его значением становится пустой список.
Интервал между последовательностью me s s a ge [ i : i + s e qLe n ] и ис­
ходной последовательностью me s s age [ s e q S t a r t : s eq S t a r t + seqLe n ]
равен разности i - s e q S t a r t . Обратите внимание н а то, что i и
s e q S t a r t это начальные индексы срезов, указанные перед двоеточием.
Следовательно, выражение i - s e q S t a r t дает нам количество букв, разде­
ляющих обе последовательности, и мы присоединяем это значение к спи­
ску, хранящемуся в позиции s e qSpa c i n g s [ s e q ] .
По завершении всех циклов словарь s e qSpa c i n g s будет содержать все
повторяющиеся последовательности длиной 3, 4 и 5 букв, а также значения
всех интервалов между ними. Словарь s eqSpac i n g s возвращается функци­
ей f i ndRepe a t S e quence s S p a c i n g s ( ) в строке 53:
-

53 .

return s e qSpa cings

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

Вь1чисn е ни е мно•итеnей интерваnов повторения
Вспомните, что следующий :·пап метода Касиски заключается в нахож­
дении множителей интервалов повторения. Мы ищем множители, выра­
жаемые числами в диапазоне от 2 до МАХ_ КЕУ_LENGTH. Для этого предна­
значена функция getU s e fu l Fa c t o r s ( ) , которая получает параметр num
и возвращает список, содержащий только множители, удовлетворяющие
этому критерию.
5 6 . de f get U s e fu l Fa c t o r s ( num ) :
61 .

378

- - опущено - -

i f num < 2 :

Глава 20

62 .
63 .
64 .

return [ ]
factors = [ ]

# числа , мень шие 2 , не имеют поле з ных множителей
# спи со к найде нных множителей

В строке 61 проверяется особая ситуация, когда значение nurn меньше 2.
В таком случае в строке 62 возвращается пустой список, поскольку пара­
метр nurn не имеет полезных множителей.
Если же значение nurn больше 2, то необходимо вычислить все множите­
ли nurn и сохранить их в виде списка. В строке 64 создается пустой список
fact o r s , предназначенный для хранения таких множителей.
В цикле f o r , который начинается в строке 68, перебираются целые
числа в диапазоне от 2 до МАХ_ КЕУ_ LENGTH включительно. Вспомните, что
функция range ( ) возвращает диапазон, не включающий верхнее гранич­
ное значение, поэтому мы передаем ей МAX_KEY_LENGTH + 1 в качестве
второго аргумента. Цикл предназначен для нахождения всех множителей
числа num.
68 .
69 .

70 .
71 .

for i i n range ( 2 , МAX_KEY_LENGTH + 1 ) :
i f num % i == О :
fact o r s . append ( i }
othe r Fa c t o r = int ( nurn / i }

# множитель 1 бе споле зен

В строке 69 проверяется равенство выражения nurn % i нулю. Если это
условие выполняется , то мы знаем, что nurn делится на i без остатка, а зна­
чит, i является множителем nurn. В таком случае в строке 70 значение i при­
соединяется к списку множителей, хранящемуся в переменной facto r s .
Поскольку частное nurn / i тоже является множителем числа nurn, его це­
лочисленная форма сохраняется в переменной o t h e r F a c t o r в строке 7 1 .
( Вспомните, что результатом операции / всегда будет число с плавающей
точкой. Например, выражение 2 1 / 7 возвращает не целое число 3 , а ве­
щественное число 3 . О . ) Если результирующее значение равно 1 , то оно не
включается в список f a ct o r s ; этот случай проверяется в строке 72.
72 .
73.

i f othe r Fact o r < МАХ КЕУ LENGTH + 1 and othe r Fa c t o r ! = 1 :
f a ct o r s . append ( otherFact o r )

Мы исключаем значение 1 по той причине, что в случае ключа длиной 1
шифр Виженера ничем не отличается от шифра Цезаря.

Удuение ду6лааrов t помощью фупqии set ()
В методе Касиски мы должны определить наиболее часто встречающий­
ся множитель интервалов повторения, поскольку длина ключа Виженера,
Взл ом шифр а В иженера 379

скорее всего, будет равна именно ему. Но прежде чем мы сможем присту­
пить к анализу частотности каждого множителя, необходимо воспользо­
ваться функцией s e t ( ) для удаления дубликатов множителей из списка
fact o r s . Например, если передать функции g e t U s e f u l Fa c t o r s ( ) аргу­
мент 9 , то проверка 9 % 3 == О вернет значение True, и к списку facto r s
будет присоединено как значение i , так и значение i n t ( num / i ) . Н о оба
выражения равны 3 , поэтому 3 будет включено в список дважды. Во избе­
жание появления дубликатов мы передаем список функции s e t ( ) , которая
преобразует список в множество. Тип данных s e t аналогичен типу данных
l i s t , за исключением того, что множество может включать лишь уникаль­
ные значения.
Передав список функции s e t ( ) , мы получим множество, содержащее
лишь уникальные элементы. В то же время , если передать множество функ­
ции l i s t ( ) , мы преобразуем его в список. Чтобы убедиться в этом, введи­
те в интерактивной оболочке следующие инструкции.
> » set ( [ 1 , 2 , З, З , 4] )
{1, 2, 3, 4 }
> > > spam = list ( set ( [ 2 , 2 , 2 ,
> > > spam
[ 2 , ' cat s ' ]

' cats ' , 2 , 2 ] ) )

Преобразование списка в множество приводит к удалению из него ду­
бликатов. После обратного преобразования множества в список послед­
ний тоже не будет содержать повторяющихся элементов.

Удаление ду6лирующ11хt• мно•иrепеi и tорrировка t11иt1a
В строке 74 список f a c t o r s передается функции s e t ( ) для удаления ду­
бликатов:
74 .

return l i s t ( s et ( fact o rs ) )

# удаляем дубликаты

Функция get i t emAt i ndexOne ( ) (строка 77) почти идентична функции
g e t i t emAt i ndex Z e r o ( ) пpoгpaммы freqAnalysis.py, которую мы написали в
главе 19.
7 7 . de f get i t emAt i ndexOne ( x ) :
return x [ l ]
78 .

Впоследствии эта функция будет передаваться методу s o r t ( ) для сорти­
ровки списка на основании элемента с индексом 1 .

380

Глава 20

Нахождение наи6олее часrо всrречающи.хс• множителей
Для нахождения наиболее часто встречающихся множителей, являю­
щихся самыми вероятными значениями длины ключа, нам понадобится
функция g e tMo s tCommonFa c t o r s ( ) , определяе � ая в строке 8 1 .
8 1 . de f getMo s t CommonFactors ( se qFact ors ) :
# Во- первых , подсчитываем повторы множителей в словаре s eqFact o r s
82 .
83.
factorCount s
{ }
# ключ - множи тель ; значение - число повт оре ний
=

Параметр s e qF a c t o r s - это словарь, созданный с помощью функ­
ции k a s i s k i E x ami n a t i o n ( ) , которую мы рассмотрим далее. В дан­
ном словаре ключами служат цепочки букв, а значениями - списки
множителей интервалов повторения , возвращаемые функцией f i nd­
Repeat S e quence s Sp a c i n g s ( ) . Например, переменная s e qFa c t o r s может
содержать словарь следующего вида.
{ ' V RA ' : [ 8 , 2 , 4 , 2 , 3 , 4 , 6 , 8 , 1 2 , 1 6 , 8 , 2 , 4 ) ,
1 2 , 1 6 , 2 4 ] , ' YBN ' : [ 8 , 2 , 4 ] }

' AZ U ' :

[2, 3, 4, 6, 8,

Функция getMo s t C ommonFa c t o r s ( ) сортирует множители, содержащи­
еся в словаре s e qFa c t o r s , в порядке убывания частотности и возвраща­
ет список кортежей, включающих два целочисленных элемента. Первым
элементом кортежа является множитель, вторым - количество вхождений
этого множителя в словаре s e qFacto r s .
Например, функция getMo s tCommon Fa c t o r s ( ) может вернуть такой
список.
[ (3, 556) ,
( 1 6 , 1 05 ) ,
( 13 , 52 ) ]

( 2 , 54 1 ) , ( 6 , 5 2 9 ) , ( 4 , 33 1 ) , ( 12 , 32 5 ) , ( 8 , 1 7 1 ) , ( 9 , 1 5 6 ) ,
( 5 , 98 ) , ( 1 1 , 8 6 ) , ( 1 0 , 8 4 ) , ( 1 5 , 8 4 ) , ( 7 , 8 3 ) , ( 1 4 , 68 ) ,

Как следует из этого списка, в словаре s e q Fa c t o r s , который был пере­
дан функции getMo s tCommonF a c t o r s ( ) , множитель 3 встречается 556 раз,
множитель 2 - 54 1 раз, множитель 6 - 529 раз и т.д. Заметьте, что первым
в списке появляется множитель 3, поскольку он встречается чаще других.
Множитель 13 встречается реже, чем любой другой множитель, потому он
оказался последним в списке.
В функции getMo s t CommonFa c t o r s ( ) сначала создается пустой словарь
f a c t o rCount s (строка 83) , который будет использоваться для хранения
счетчиков числа вхождений каждого множителя. В данном словаре ключа­
ми будут множители , а значениями - счетчики этих множителей.

Взл ом шифр а Виженер а 38 1

В цикле f o r , который начинается в строке 88, функция проходит по
всем последовательностям , содержащимся в словаре s eqFa c to r s , сохра­
няя каждую из них в переменной s e q на каждой итерации. Список множи­
телей, содержащийся в словаре s eqFact o r s для последовательности s eq,
сохраняется в переменной f a c t o rL i s t (строка 89) .
88 .
89.
90 .
91 .
92 .
93 .

f o r s e q in s e qFact o r s :
fact orL i s t = s e q F a c t o r s [ se q ]
for fact o r i n factorLi s t :
i f fact o r not i n facto rCount s :
fact o rCount s [ fa cto r ]
О
fact orCount s [ fa c t o r ] += 1
=

Все множители из этого списка просматриваются во вложенном цикле
f o r , который начинается в строке 90. Если множитель не существует в ка­
честве ключа в словаре f a c t o rCount s , то он добавляется в него в строке 92
со значением О. В противном случае значение, соответствующее данному
ключу в словаре factorCount s , инкрементируется в строке 93.
Далее необходимо отсортировать множители, содержащиеся в словаре
factorCount s , в соответствии со значениями их счетчиков. Но посколь­
ку понятие упорядоченности неприменимо к словарям, мы должны пре­
образовать словарь в список кортежей, кажд ый из которых содержит два
целочисленных элемента. (Аналогичное преобразование мы применяли в
функции g e t F requencyOrde r ( ) пpoгpaммыfrqAnalaysis.py в главе 1 9 . ) Спи­
сок будет храниться в переменной f a c t o r s ByCoun t , которая инициализи­
руется в строке 97.
97 .
98 .
99 .
100 .
101 .
102 .
103 .

f a c t o r s ByCount = [ ]
for factor i n facto rCount s :
# Исключаем множители , которые больше , чем МАХ КЕУ LENGTH
i f fact o r > > spam
> > > eqgs
[1, 2 , З)
> > > spam . append (eqqs )
> > > spam
[ ' cat ' , ' dog ' , ' mouse ' , [ 1 , 2 , 3 ] ]
=

=

В отличие от этого метод extend ( ) добавляет в конец списка каждый из
элементов списка, переданного ему в качестве аргумента. Введите в инте­
рактивной оболочке следующие инструкции.
> > > spam
[ ' cat ' , ' dog ' , • шouse ' )
>>> eqqs
[1 , 2 , З]
> > > spam . extend (eqqs )
> > > spam
[ ' cat ' , ' dog ' , ' mous e ' , 1 , 2 , 3 ]
=
=

Как видите, все значения из списка egg s ( 1 , 2 и 3 } присоединяются к
списку sparn как отдельные элементы.

Ра�111ирение сповар1 repea tedSeqSpacings
Словарь repeatedSeqSpac i n g s сопоставляет последовательности строк
со списками целочисленных интервалов повторения, но фактически нам
нужен словарь, в котором последовательности строк сопоставляются со
списками множителей данных интервалов. (Чтобы понять, почему это так,
см. раздел "Определение множителей в интервалах повторения".) Данная
задача решается в строках 1 1 8- 1 22.
118 .
119.
120 .
121 .
122 .

s eqFacto r s = { }
for s e q in repeatedSeqSpacings :
s e qFact o r s [ s e q ] = [ ]
for spa c i n g in repe at edSeqSpacings [ s e q ] :
s eqFact o r s [ se q ] . ex t e nd ( getU s e fu l Fa c t o r s ( spa cing ) )

В строке 1 1 8 создается пустой словарь s eqFact o r s . В цикле f o r , кото­
рый начинается в строке 1 1 9 , программа проходит по всем ключам словаря
repea t e dS e q Spa c i ng s , представляющим собой строковые последователь­
ности. В строке 1 20 для каждого ключа создается пустой список, который
будет храниться в словаре s eq Fa c t o r s .
В о вложенном цикле f o r (строки 1 2 1 - 1 22) программа проходит п о всем
интервалам повторения, передавая каждый из них функции getU s e fu l­
Facto r s ( ) . Каждый элемент списка, возвращаемый этой функцией, добав­
ляется в качестве значения ключа s eqFact o r s [ s e q ] с помощью метода
ext end ( ) . По завершении циклов переменная s eqFacto r s будет содер384

Глава 20

жать словарь, сопоставляющий строковые последовательности со списка­
ми множителей интервалов повторения. Благодаря этому мы получаем ин­
формацию не только об интервалах повторения , но и об их множителях.
В строке 1 25 словарь s eq F a c t o r s передается функции g e tMo s tCommon­
Fact o r s ( ) , возвращающей список кортежей из двух целых чисел, первое
из которых представляет множитель, а второе - количество появлений
этого множителя в словаре s eqFacto r s . Данный список сохраняется в пе­
ременной facto r s ByCount .
1 2 5 . f a c t o r s ByCount = getMo s tComrnonFact o r s ( s e qFact o r s )

Но нам все-таки нужно, чтобы функция kas i s kiExami n a t i o n ( ) возвра­
щала список множителей, а не список кортежей. Поскольку множители
хранятся в первых элементах кортежей списка f a c t o r s ByCoun t , мы долж­
ны извлечь их из кортежей и сформировать из них отдельный список.

ИJвле'lение множителей п t11жка :factorsByCoun t
В строках 1 30- 1 34 список множителей сохраняется в переменной
a l lL i ke l yKeyLeng t h s .
130 .
131 .
132 .
133 .
134 .

a l l L i ke l yKeyLengths = [ ]
for t wo i ntTup l e in factors ByCount :
a l l L i ke l yKeyLengths . append ( t wo i ntTup le [ O ] )
r e t urn a l l L i ke l yKeyLengths

В цикле f o r , который начинается в строке 1 3 1 , программа проходит по
всем кортежам списка f a c t o r s ByCount и добавляет элемент кортежа с ин­
дексом О в конец списка a l l L i ke l yKeyLeng th s . По завершении цикла пе­
ременная a l l L i ke l yKeyLengths будет содержать все целочисленные мно­
жители из словаря f a c t o r s ByCoun t , которые возвращаются в виде списка
функцией ka s i s kiExami n a t i o n ( ) .
Теперь мы можем определить наиболее вероятную длину ключа, с
помощью которого было зашифровано сообщение, но нам еще нужно
иметь возможность выделить из сообщения буквы, зашифрованные од­
ним и тем же подключом. Вспомните: в случае шифрования сообщения
' THEDOGANDTHECAT ' ключом ' XYZ ' подключ ' Х ' будет применяться для
шифрования букв сообщения с индексами О, 3, 6 , 9 и 1 2. Поскольку буквы
исходного сообщения шифруются одним и тем же подключом ( ' Х ' ) , ча­
стотность этих букв в дешифрованном тексте должна быть близка к частот­
ности букв в типичных английских текстах. Данную информацию можно
задействовать для нахождения подключа.
Взл ом шифра Виженера 385

Поnучение букв, эаwифрованных одним
и тем же nодкn1Очом
Чтобы извлечь из шифротекста буквы, зашифрованные одним и тем же
подключом, необходимо написать функцию, которая создает строку, ис­
пользуя 1-е, 2-е или n-e буквы сообщения. Первое, что должна сделать такая
функция, получив начальный индекс , длину ключа и сообщение, - это уда­
лить из сообщения небуквенные символы , используя объект регулярного
выражения и метод s ub ( ) (строка 1 45 ) .
П р и мечан и е

Регулярные выражения обсуждались в главе 1 7.

Строка, содержащая лишь буквы, сохраняется в виде нового значения
переменной me s s a g e .
1 3 7 . d e f getNt hSub ke y s Le t t e r s ( nt h , keyLe ngt h , me s s age ) :
145 .

- - опущено - -

me s s age = NONLETTERS_PATTERN . sub ( ' ' , me s s age )

Далее мы формируем список, присоединяя к нему буквы, а затем исполь­
зуем метод j o i n ( ) для объединения элементов списка в одну строку.
147 .
148 .
149.
150 .
151 .
152 .

1
i = nth
[]
l e t t e rs
whi l e i < l e n ( me s s a ge ) :
l e t t e r s . append ( me s s age [ i ] )
i += keyLength
return ' ' . j o i n ( l e t t e r s )
-

Переменная i задает индекс той буквы в строке me s s a g e , которую тре­
буется присоединить к списку l e t t e r s . Начальным значением перемен­
ной i является n th
1 (строка 14 7) , а начальным значением переменной
l e t t e r s - пустой список (строка 1 48 ) .
Цикл whi l e , начинающийся в строке 1 49, продолжает выполняться до
тех пор, пока значение i остается меньшим, чем длина строки mе s s аg е . На
каждой итерации цикла буква me s s age [ i ] добавляется в список l e t t e r s .
Затем значение i увеличивается н а keyLength в строке 1 5 1 , что позволяет
перейти к следующей букве подключа.
По завершении цикла однобуквенные строки , содержащиеся в списке
l e t t e r s , объединяются в одну строку, и эта строка возвращается функци­
ей getNthSubke y s Le t t e r s ( ) .
-

386

Глава 20

Теперь, когда мы научились извлекать буквы, зашифрованные од­
ним и тем же подключом, мы сможем использовать функцию getNth­
Subkey s L e t t e r s ( ) для того, чтобы попытаться дешифровать сообщение
с помощью ключей наиболее вероятной длины.

Попытки деwифровани• с nомощьlО кn1Очей
веро•тной Д11 и ны
Вспомните о том, что функция ka s i s kiExaminat i on ( ) возвращает не
гарантированно верную длину ключа Виженера, а список, включающий
несколько возможных вариантов, отсортированных в порядке убывания
их вероятности. Если длина ключа была определена неверно, програм­
ма попытается использовать ключ другой длины. Эту работу выполняет
функция а t t emp t H a c kWi thKeyLength ( ) , которая получает шифротекст и
предполагаемую длину ключа. В случае успеха функция возвращает строку
взломанного сообщения, а в случае неудачи - значение None.
Процедура взлома применяется к буквам, преобразованным в верхний
регистр, но мы хотим, чтобы любая дешифрованная строка возвращалась
с соблюдением исходного регистра, а для этого нужно сохранить строку
в ее первоначальном виде. Чтобы обеспечить это, мы сохраняем строку
шифротекста, буквы которой преобразованы в верхний регистр, в пере­
менной ciphertextUp (строка 1 57 ) .
1 5 5 . de f a t t empt H a c kW i t hKeyLength ( ciphe rt ext , mo s t L i k e l yKeyLengt h ) :
156 .
# Определяем наиболее вероятные буквы для каждого подключа
c iphert extUp = ciphertext . uppe r ( )
157 .

Исходя из предположения, что значение mo s tL i ke l yKeyLength пред­
ставляет собой корректную длину ключа, программа взлома вызывает
функцию getNthSubke y s Le t t e r s ( ) для каждого подключа, а затем при­
меняет метод грубой силы, выполняя полный перебор всех 26 возможных
букв для нахождения той из них, которая приводит к получению дешифро­
ванного текста с частотностью, наиболее близко соответствующей частот­
ности букв в типичных английских текстах.
Сначала в строке 1 60 создается пустой список a l l Fr e q S co r e s , в ко­
тором будут храниться оценки частотного соответствия, возвращаемые
функцией f r e qAna l ys i s . e n g l i s hFreqMa t ch S c o re ( ) .
160 .

161 .
1 62 .

a l l FreqS c o r e s = [ ]
f o r nth i n range ( l , mo s t L i ke l yKe yLength + 1 ) :
nthLe t t e r s = g etNthSub keysLet t e r s ( nt h , mos t L i ke l yKeyLength ,
c iphert e xtUp )

Взл ом шифр а В иженер а 387

Переменная nth в цикле f o r , который начинается в строке 1 6 1 , поо­
чередно принимает значения от 1 до mo s t L i ke l yKeyLength. Вспомните,
что диапазон , возвращаемый функцией range ( ) , не включает верхнюю
границу, задаваемую вторым аргументом. В связи с этим мы увеличиваем
верхнюю границу диапазона на 1 , чтобы охватить значение mo s t L i ke l y­
KeyLength.
Буквы п-го подключа возвращаются функцией g e t N t h Sub k e y s ­
Le t t e r s ( ) в строке 1 62.
Далее необходимо дешифровать буквы п-го подключа, используя все
26 возможных подключей, чтобы увидеть, какой из них приводит к ча­
стотности букв, близкой к частотности в типичных английских текстах.
Список оценок частотного соответствия будет храниться в переменной
f r e q S c o r e s , которая инициализируется пустым списком в строке 1 68. В
цикле f o r , который начинается в строке 1 69, программа проходит по всем
26 буквам строки LETTERS .
1 68 .
169 .
170 .

f r e qScores = [ J
for pos s iЬ l e Key in LETTERS :
de crypt edText
vi genereC iphe r . de cryptMe s sage ( po s s iЬleKe y ,
nthLet t e r s )
=

Значение po s s iЫ eKey используется для дешифрования шифротекста
путем вызова функции vigenereC iphe r . decryptMe s s a g e ( ) в строке 1 70.
Подключ, содержащийся в переменной po s s i Ь l e Key, является однобук­
венным , тогда как строка nthLe t t e r s формируется только из тех букв
шифротекста, которые могут быть зашифрованы данным подключом , если
программа правильно определила длину ключа.
Далее дешифрованный текст передается функции f r e qAna l y s i s .
eng l i s hFreqMa t ch S c o r e ( ) , чтобы проверить, насколько близко частот­
ность букв в строке dec rypt edText соответствует частотности букв в ти­
пичных английских текстах. Вспомните из главы 1 9 , что данная функция
возвращает целое число в интервале от О до 1 2 , причем чем больше это
значение, тем ближе соответствие.
В строке 1 7 1 полученная оценка частотного соответствия и под­
ключ , использованный для дешифрования, заносятся в переменную
keyAndFreqMa t chTup l e . В строке 1 72 этот кортеж добавляется в конец
списка freqScore s .
171 .

keyAndFre qMat chTup l e
( po s s i Ь l e Ke y ,
f r e qAna lys i s . e ng l i shFreqMat chS c o r e ( de c ryp t e dText ) )
freqScore s . append ( keyAndFreqMat chTupl e )
=

172 .

388

Глава 20

Когда цикл f o r , начатый в строке 169, завершится, список f reqScore s
будет содержать 26 кортежей, представляющих собой пары "ключ, оценка
частотного соответствия" - по одному кортежу для каждого из 26 подклю­
чей. Список должен быть отсортирован таким образом , чтобы первыми
шли кортежи с наибольшими оценками. Это означает, что кортежи необ­
ходимо отсортировать по значениям с индексом 1 и в обратном порядке
( по убыванию ) .
М ы применяем к списку f r e q S c o r e s метод s o rt ( ) , передавая ему функ­
цию ge t i t ernAt i ndexOne ( ) в качестве аргумента key. (Отсутствие круглых
скобок после имени функции означает, что она не вызывается, а передает­
ся как значение. ) Аргумент reve r s e равен T ru e , что означает сортировку
по убыванию.
174 .

freq S c o r e s . s ort ( key=get i t emAt i ndexOne , reve r s e =T rue )

В строке 9 для константы NUM_мо s т _ FREQ_ LETT E RS было задано значе­
ние 4 . После того как кортежи, содержащиеся в списке f reqScore s , были
отсортированы по убыванию, в строке 1 76 в переменную a l l FreqScore s
добавляется список, содержащий только первые четыре кортежа, т.е. кор­
тежи с четырьмя наиболее высокими оценками частотного соответствия.
Таким образом, элемент a l l FreqSco res [ О ] содержит оценки для первого
подключа, элемент a l l FreqS core s [ 1 ] - оценки для второго подключа и т.д.
176 .

a l l FreqS core s . append ( f r e qS co r e s [ : NUM_MOST_FREQ_LETTERS ] )

Когда цикл f o r , начатый в строке 1 6 1 , завершится, переменная
a l l FreqSco r e s будет содержать списки в количестве, равном mo s t ­
L i ke l yKeyLength. Например, если параметр mo s t L i ke l yKeyLeng th ра­
вен 3 , то переменная a l l FreqScore s будет включать три списка. Первый
список содержит кортежи для первых четырех букв с наивысшими оцен­
ками частотного соответствия , относящихся к первому подключу полного
ключа шифра Виженера. Второй список содержит аналогичные кортежи,
относящиеся ко второму подключу полного ключа шифра Виженера, и т.д.
Первоначально, если бы мы хотели применить метод грубой силы к
полному ключу шифра Виженера, то количество возможных ключей было
бы равно 26 в степени, равной длине ключа. Например, в случае ключа
ROS EBUD длиной 7 букв мы имели бы 26 7 , или 8 03 1 8 1 0 1 76, возможных
ключей.
Однако проверка частотного соответствия букв помогает определить че­
тыре наиболее вероятные буквы для каждого подключа. Применительно к
примеру с ключом ROSEBUD это означает, что необходимо проверить только
В зл ом шифр а В иженер а 389

47 , или 1 6 384, возможных ключей, что является огромным шагом вперед по
сравнению с прежним их количеством, составляющим около 8 млрд!

Арrуменr еnd фунrцин prin t ()
Следующее, что необходимо сделать, - вывести информацию для поль­
зователя. Для этого мы воспользуемся функцией p r i nt ( ) , но передадим ей
необязательный аргумент, с которым ранее не сталкивались. Всякий раз,
когда вызывается функция p r i nt ( ) , она выводит на экран переданную ей
строку вместе с завершающим символом новой строки. Мы можем отме­
нить переход на новую строку и вывести другую информацию в текущей
строке, указав аргумент end при вызове функции p r i nt ( ) . Чтобы увидеть,
как это работает, введите в интерактивной оболочке показанный ниже код.
> > > def printStuff ( ) :
о " .
print ( ' Bello ' , end= ' \n ' )
print ( ' Bowdy ' , end= ' ' )
б " .
8 " .
print ( ' Greetinqs ' , end= ' XYZ ' )
print ( ' GoodЬye ' )
»> printStuff ( )
He l l o
H owdyG r e e t i пgsXY ZGoodbye

Передача аргумента e nd= ' \ n ' ( О ) эквивалентна обычному вызову функ­
ции. Однако передача аргумента e nd= ' ' ( б ) или end= ' XY Z ' ( О ) приводит
к замене символа новой строки, поэтому последующие вызовы функции
p r i nt ( ) отображают результаты в текущей строке.

3any(r nроrраммы в 11111хом п режиме нлн ' выводом информации
ДЛI DОЛЬ3ОВаrеп1
На данном этапе мы хотим знать, какие буквы являются четырьмя наи­
более вероятными кандидатами для каждого из подключей. Если ранее в
программе для константы S I LENT _MODE было установлено значение Fa l se ,
то содержимое переменной a l l FreqS c o r e s будет выведено на экран.
178 .
179 .
180 .
181 .
182 .
183 .
184 .

390

i f поt S I LENT MODE :
for i iп raпge ( l e п ( a l l FreqScores ) ) :
# Исполь зуем i + 1 , чтобы первая буква не считалась 0 -й
p r i пt ( ' Po s s i Ы e l e t t e r s f o r l e t t e r % s of the key : ' %
( i + 1 ) , епd= ' ' )
f o r freqScore i п a l l FreqS c o r e s [ i ] :
p r iпt ( ' % s ' % freqScore [ O J , end= " )
p r i пt ( )
# пере ход на новую строку

Глава 20

Если же для константы S I LENT_MODE было установлено значение T rue,
то блок инструкции i f будет пропущен.
На данном этапе нам удалось уменьшить количество возможных под­
ключей до уровня , позволяющего применить к ним метод грубой силы. Да­
лее вы узнаете о том , каким образом можно сгенерировать все возможные
комбинации подключей с помощью функции i t e r t o o l s . product ( ) для
их последующего перебора методом грубой силы.

Нахождение воJможных rом6инаqиi nодrпючеi
Теперь, когда у нас имеются все возможные подключи, мы должны объ­
единить их для формирования целого ключа. Проблема в том , что даже
наиболее вероятные буквы могут оказаться некорректными. В реальности
корректной может быть вторая или третья буква из числа наиболее вероят­
ных. Это означает, что мы не можем ограничиться простым объединением
наиболее вероятных букв каждого подключа в единый ключ. Для нахожде­
ния истинного ключа мы должны испытать все возможные комбинации.
Для тестирования всех возможных комбинаций подключей в програм­
ме vigenereHacker.py применяется функция i t e r t oo l s . product ( ) .

Функци я i tertools . product ( )
Функция i t e r t o o l s . product ( ) создает все возможные комбинации
элементов списка или значений схожего типа, такого как строка или кор­
теж. Подобную комбинацию элементов называют декартовым произведением,
что и дало название функции. Данная функция возвращает объект, кото­
рый можно преобразовать в список, передав его функции l i s t ( ) . Чтобы
увидеть, как это работает, введите в интерактивной оболочке следующий
код.
> > > iшport itertools
>>> itertools . product { ' AВC ' , repeat=4 )
О < i t e r t o o l s . product ob j e ct at О х 02 С 4 0 1 7 0 >
» > list { itertools . product { ' AВC ' , repeat=4 ) )
[ ( 'А' , 'А' , 'А' , 'А' ) , ( 'А' , 'А' , 'А' , ' В ' ) ,
( 'А' , 'А' , ' В ' , 'А' ) , ( 'А' , 'А' , ' В ' , ' В' ) ,
' С ' , 'А' ) , ( 'А' , 'А' , ' С ' , ' В ' )
( 'А' , 'А' '
( 'А' , ' В ' ' 'А' , 'А' ) , ( 'А' , ' В ' , 'А' , ' В ' ) ,
( 'А' , 'В' ' ' В ' , 'А' ) , ( 'А' , ' В ' , 'В' , 'В' ) ,
о пущ е но
( 'С' , 'В' ' 'С' , 'В' ) , ( 'С' , 'В' , 'С' ' 'С' ) ,
( ' С ' , 'С' , 1А' , ' В' ) , ( 'С ' , ' С ' , 'А' , 'С' ) ,
( 'С' , 'С' , 'В' , 'В' ) , ( 'С' , 'С' ' •в• , 'С' ) ,
( 'С' , 'С' ' 'С' , 'В' ) , ( 'С' , 'С' ' 'С' ' 'С' ) ]
- -

( 'А' , 'А' , 'А' , ' С ' )
( 'А' , 'А' , ' В ' , ' С ' )
, ( 'А' , 'А' , ' С ' , 'С'
( 'А' , ' В' , 'А' , 'С ' )

,
,
),
,

- -

( 'С',
( 'С',
( 'С' ,

'С' ,
'С' '
'С' ,

'А' ,
'В' '
'С' '

'А' ) ,
'А' ) ,
'А' ) ,

Взл ом шифра Виженера 39 1

Получив строку ' АБС ' и целое число 4 в качестве аргумента repe a t ,
функция i t e r t oo l s . p roduct ( ) возвращает объект декартового произ­
ведения ( О ) , который после преобразования в список содержит кортежи
из четырех значений, представляющих возможные комбинации букв ' А ' ,
' в ' и ' с ' . В конечном итоге мы получаем список, содержащий в общей
сложности 34 , или 8 1 , кортеж.
Также допускается передавать в функцию i t e r t o o l s . p roduct ( ) спи­
ски и им подобные значения, например объекты range , возвращаемые
функцией range ( ) . Чтобы увидеть это на конкретном примере, введите в
интерактивной оболочке следующий код.
>>> iшport itertools
>>> list ( itertool s . product ( range ( 8 ) , repeat=S ) )
( (0, О, О, О, 0) , (0, О, О, О, 1) , (0, О, О, О, 2 ) , (0,
( 0 , О , О , О , 4 ) , ( 0 , О , О, О, 5 ) , ( 0, О , О , О , 6) , ( 0 ,
(0, О, О, 1, 0) , (0, О, О, 1, 1 ) , (0, О, О, 1, 2) , (0,
(0, О, О, 1, 4) ,
- - опущено - -

(7, 7, 7, 6, 6) '
(7, 7, 7, 7, 2) ,
(7, 7, 7, 7, 6) '

(7' 7, 7, 6, 7 ) ,
(7, 7, 7, 7, 3) '
(7, 7, 7, 7, 7) ]

(7, 7, 7, 7, 0) ,
(7' 7, 7, 7, 4 ) ,

О,
О,
О,

О, О, 3) ,
О, О, 7 ) ,
О, 1, 3) ,

(7, 7, 7, 7, 1 ) ,
(7, 7, 7, 7, 5) ,

Получив объект диапазона, создаваемый вызовом r ange ( 8 ) , а также
число 5 в качестве аргумента repe a t , функция i t e rt o o l s . p roduct ( ) воз­
вращает список, элементами которого являются кортежи из пяти значе­
ний, представляющих собой целые числа в диапазоне от О до 7 .
Мы не можем просто передать список потенциальных подключей в
функцию i t e rt oo l s . p roduct ( ) , так как она создает комбинации одних и
тех же значений, а для каждого из подключей, вероятнее всего, будут суще­
ствовать разные варианты потенциальных букв. Вместо этого, поскольку
наши подключи хранятся в виде кортежей в списке a l l FreqScore s , мы
будем получать доступ к буквам с помощью индексов, имеющих значения
от О до желаемого количества букв минус 1 . Мы знаем, что количество
букв в каждом кортеже равно NUM_MOS T _ FREQ_ LETTERS , поскольку именно
это значение задавалось для каждого кортежа в строке 1 76. Соответствен­
но, константу NUM_MOS T_FREQ_LETTERS нужно будет передать функции
i t e r t o o l s . product ( ) . Вместе с ней мы передадим также вероятную дли­
ну ключа в качестве второго аргумента, чтобы длина создаваемых корте­
жей была равна длине потенциального ключа.
Например, если мы хотим испытать только первые четыре наиболее
вероятные буквы каждого подключа ( их количество определяется кон­
стантой NUM_MOS T_FREQ_LETTERS ) , в то время как сам ключ , вероятнее
392

Глава 20

всего, состоит из пяти букв, то первым значением, созданным функцией
i t e r t o o l s . product ( ) , будет кортеж ( О , О , О , О , О ) . Следующими зна­
чениями будут кортежи ( О , О , О , О , 1 ) , затем ( О , О , О , О , 2 ) и т.д. до
тех пор, пока не будет создан кортеж ( 3 , 3 , 3 , 3 , 3 ) . Каждое из пяти
целочисленных значений кортежа представляет собой индекс элемента в
списке a l l FreqScore s .
Доступ к nод1U11Оча м, хран 1щ1мс1

в сп и ске allFreqScores
Значением переменной a l l FreqS c o r e s является список наиболее ве­
роятных букв каждого подключа вместе с их оценками частотного соответ­
ствия. Чтобы увидеть, как это выглядит, создайте гипотетический список
a l l FreqS c o r e s в IDLE. Например, в случае шестибуквенного ключа, для
которого мы нашли четыре наиболее вероятные буквы каждого из подклю­
чей , список может иметь следующий вид.
> > > allFreqScores = [ [ ( ' А ' , 9 ) ,

( 'Е' ,
( ' D ' , 4) , ( ' G' , 4) , ( ' В ' , 4) ] , [ ( ' ! ' ,
[ ( 'И' , 10) , ( ' Z ' , 5) , ( ' Q ' , 4) , ( ' А' ,
( 'А' , 3) ] , [ ( ' V' , 10) , ( ' ! ' , 5) , ( ' К'

5) , ( ' 0 ' , 4 ) , ( ' Р ' , 4) ] , [ ( ' S ' , 10) ,
11) , ( 'V' , 4) , ( 'Х ' , 4) , ( ' В ' , 3) ] ,
3) ] , [ ( ' 0 ' , 11) , ( ' В ' , 4 ) , ( ' Z ' , 4 ) ,
, 5) , ( ' Z ' , 4) ] ]

Список кажется слишком сложным, но мы можем добраться до кон­
кретных значений с помощью индексов. Если обратиться к списку
a l l FreqS c o r e s по индексу элемента, то мы получим список кортежей воз­
можных букв для этого подключа и их оценки частотного соответствия.
Например, элемент a l l FreqScores [ О ] содержит список кортежей для
первого подключа вместе с оценками для каждого потенциального под­
ключа, элемент a l l FreqSco r e s [ 1 ] - аналогичные данные для второго
подключа и т.д.
> > > allFreqScores [ O ]

[ ( 'А' , 9) ,

( 'Е' , 5) ,

( 'О' , 4) ,

( 'Р' , 4) ]

> > > allFreqScores [ l ]

[ ( ' S ' , 10) ,

( ' D' , 4 ) ,

( 'G' , 4) ,

( 'Н' , 4) ]

Можно также получить доступ к отдельным кортежам возможных букв
для каждого подключа, добавив дополнительный индекс. Например, обра­
тившись к элементу a l l Fre q S c o r e s [ 1 ] [ О ] , мы получим кортеж, включаю­
щий наиболее вероятную букву второго подключа и ее оценку частотного
соответствия. Обратившись к элементу а 1 1 FreqS c o re s [ 1 ] [ 1 ] , получим
кортеж, включающий вторую наиболее вероятную букву того же подклю­
ча, и т.д.

В зл ом шифра В иженер а 393

> > > allFreqScore s [ l ] [ O ]

( 1 s 1 , 10)
> > > allFreqScore1 [ l ] [ l ]

( ' D' , 4)

Поскольку эти значения являются кортежами , для получения самой
буквы без оценки ее частотного соответствия потребуется обратить­
ся к первому элементу кортежа. Таком образом , для доступа к наиболее
вероятной букве второго подключа мы должны использовать элемент
a l l FreqS core s [ 1 ] [ О ] [ О ] , для доступа ко второй наиболее вероятной
букве этого же подключа - элемент a l l F r e q S c o r e s [ 1 ] [ 1 ] [ О ] и т.д.
> > > allFreqScores [ l ] [ O ] [ O ]
1s1
> > > allFreqScores [ l ] [ 1 ] [ 0 ]

'D'

Разобравшись с доступом к потенциальным подключам, содержащимся
в списке a l l FreqS c o re s , мы должны скомбинировать их для нахождения
потенциальных ключей.
Соsдани е 1ом6ина ци i nоД1U1ючеi
фун кции i tertools

с

nомощью

. produat ( )

Каждый из кортежей, созданных с помощью функции i t e r t o o l s .
p roduct ( ) , представляет один ключ, причем позиция в кортеже соот­
ветствует первому индексу, по которому мы получаем доступ к списку
a l l FreqScore s , а целые числа в кортеже представляют второй индекс в
списке a l l FreqScore s .
Поскольку ранее м ы установили для константы NUM_мо s т _ FREQ_
LETTERS значение 4 , вызов i t e r t o o l s . p roduct ( range ( NUM_MOST _ FREQ_
LETTERS ) , repea t=rno s t L i ke l yKeyLeng t h ) в строке 1 88 означает, что
для каждого значения переменной index e s в цикле f o r мы получаем кор­
теж целых чисел (от О до 3 ) , представляющих четыре наиболее вероятные
буквы для каждого подключа.
188 .
189 .
190 .
191 .
1 92 .

394

for inde xe s i n i t e r t o o l s . product ( range ( NUM_MOST_FREQ_LETTERS ) ,
repea t =mo s t L i ke l yKeyLengt h ) :
# Со здаем в о зможный ключ из букв в списке a l l FreqScores
pos s i Ы e Key = ' '
fo r i in range ( mo s t L i ke l yKeyLengt h ) :
p o s s iЫeKey += a l l FreqScore s [ i ] [ i ndexes [ i ] ] [ О ]

Гла ва 20

Мы конструируем полные ключи Виженера, используя переменную
i ndexe s , которая на каждой итерации получает значение в виде одного
кортежа, созданного с помощью функции i t e r t oo l s . product ( ) . Ключ
инициализируется пустой строкой в строке 1 90, а во вложенном цикле f o r
(строки 1 9 1 и 1 92) программа проходит п о целым числам в диапазоне от О
до значения mo s t L i ke l yKeyLeng th (но не включая его) для каждого кор­
тежа, формируя ключ.
Поскольку значение переменной i изменяется на каждой итерации
цикла, значение i ndexe s [ i ] становится индексом кортежа, который мы
выбираем в элементе a l l FreqScore s [ i ] . Именно поэтому, обращаясь к
элементу a l l FreqS core s [ i ] [ i ndexe s [ i ] ] , мы получаем нужный кортеж.
Далее необходимо обратиться к его элементу с индексом О , чтобы полу­
чить букву подключа.
Если для константы S I LENT _MODE установлено значение Fa l s e , то в
строке 1 95 выводится сформированный ключ.
194 .
1 95 .

i f not S I LENT MODE :
print ( ' At t emp t i ng wi th key : % s ' % ( po s s iЬ l e K e y ) )

Теперь, когда в нашем распоряжении имеется полный ключ Виженера,
мы можем дешифровать шифротекст (строки 1 97-208) и проверить, по­
лучен ли в результате осмысленный текст на английском языке. В случае
положительного исхода такой проверки программа выводит дешифрован­
ный текст на экран, чтобы дать пользователю возможность принять окон­
чательное решение.

Вывод дешифрованноrо re•'ra ' �охранением •оррепноrо perи'rpa 6рв
Поскольку переменная decryptedText содержит буквы в верхнем реги­
стре, мы создаем новую строку путем добавления в список o r i g C a s e букв
из строки decrypte dT e x t , предварительно преобразуя их в нужный ре­
гистр (строки 20 1 -207) .
1 97 .
1 98 .
199.
200 .
201 .
202 .
203 .
204 .

decryptedText
vigenereCiphe r . decryptMe s sage ( po s s iЬ l eKey ,
c iphe rt extUp )
i f detectEngl i s h . i s E ng l i sh ( decryptedText ) :
# З адаем исходный регистр букв в о взломанном шифротексте
o r i gC a s e = [ ]
f o r i i n range ( l e n ( c iphe rt e xt ) ) :
i f c iphe rtext [ i ] . i s upper ( ) :
o r i g C a s e . append ( dec rypt edText [ i ] . uppe r ( ) )

В зл ом шифра В иженер а 395

205 .
206 .
207 .

else :
o r i gC a s e . append ( decrypt edText [ i ] . l owe r ( ) )
decrypt edText = ' ' . j o i n ( o rigCas e )

Цикл f o r , начинающийся в строке 202, проходит по индексам строки
c i ph e r t e x t , которая, в отличие от строки ciphe rtextUp , сохраняет ис­
ходную версию регистра букв шифротекста. Если c i phe rtext [ i ] - буква
в верхнем регистре, то буква dec rypt edText [ i ] преобразуется в верх­
ний регистр и добавляется в список o r i gCa s e . В противном случае буква
de c ryptedText [ i ] добавляется в список o r i gC a s e без изменений. По
окончании цикла элементы списка o r i gC a s e объединяются (строка 207) в
одну строку decryp t edTex t .
В следующем фрагменте результат дешифрования отображается на
экране, чтобы пользователь мог проверить, действительно ли найден ис­
тинный ключ.
p r i nt ( ' P o s s i Ы e encrypt i o n hack w i t h key % s : ' %
( po s s i Ы e Key ) )
p r i nt ( decrypt edText [ : 2 0 0 ] )
# выводим первые 2 0 0 символов
print ( )
p r i nt ( ' E n t e r D i f done , anyt hing e l s e t o cont i nue
hacking : ' )
response = i nput ( ' > ' )

210 .
211 .
212 .
213 .
214 .
215 .
216.
217 .

i f re sponse . s t r i p ( ) . uppe r ( ) . s t a r t swi t h ( ' D ' ) :
return decrypt edText

Дешифрованный текст в корректном регистре выводится на экран, что­
бы пользователь мог подтвердить, что является ли он осмысленным тек­
стом на английском языке. Если пользователь вводит букву ' о ' , то функ­
ция возвращает строку decryptedText.
В противном случае, если ни один из дешифрованных вариантов не был
принят пользователем, попытка взлома считается неудачной, и функция
возвращает значение None:
220 .

return None

П опучение вэпоманноrо сообщения
Все созданные нами функции будут вызываться в функции h a c k­
Vigenere ( ) , которая получает строку шифротекста в качестве аргумен­
та и возвращает взломанное сообщение (если взлом оказался успешным)
или значение None (если попытка взлома оказалась неудачной ) . Вначале
396

Глава 20

мы определяем возможные варианты длины ключа с помощью функции
ka s i s k i Examina t i on ( ) .
2 2 3 . de f
224 .
225 .
22 6 .

hackVigenere ( c iphe r te xt ) :
# Прежде всего , необ ходимо применить ме тод Касиски
# для выяснения возможной длины ключа
a l l L i ke l yKe yLengths = ka s i s ki Examinat ion ( ciphert ext )

Список возможных вариантов выводится на экран, если для константы
S I LENT_MODE было установлено значение Fa l s e .
227 .
228 .
229 .
230 .
231 .

i f not S I LENT MODE :
keyLengthS t r
for keyLength in a l l L i ke l yKeyLengt h s :
keyLengt h S t r += ' % s ' % ( ke yLengt h )
print ( ' Ka s i s ki exami nat i o n r e s u l t s s a y the mo st l i ke l y
k e y l engths a r e : ' + keyLengthSt r + ' \ n ' )
=

'

'

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

Выход ИJ цикла, еtли найден nоrенqиальный uюч
Мы хотим , чтобы цикл тестирования длины ключа продолжался до
тех пор, пока не будет найдена корректная длина ключа. Если функция
a t t empt H a c kW i thKeyLeng th ( ) сумела подобрать ключ, то цикл принуди­
тельно завершается с помощью инструкции b re a k.
По аналогии с тем, как инструкция cont i nue применяется в теле цикла
для возврата в его начало, инструкция b re a k предназначена для немедлен­
ного выхода из цикла. Встретив данную инструкцию, программа немедлен­
но переходит к выполнению строки, стоящей сразу за телом цикла. Мы
прервем цикл , когда программа найдет корректный ключ , подтвержден­
ный пользователем.
232 .
233 .
234 .
235 .

236 .
237 .
238 .

hac kedМe s s age = None
for keyLength in a l l L i ke l yKeyLengths :
i f not S I LENT MODE :
print ( ' At tempting ha ck with key l ength % s
( % s po s s iЫ e keys ) . . . ' % ( keyLength ,
NUM_MOST_FREQ_LETTERS * * keyLength ) )
hackedMe s s age
att emptHackW i t hKeyLength ( ciphertext , keyLength )
i f hac kedMe s s age ! = None :
break
=

Взл ом шифра В и жен ера 397

В строке 236 для каждой возможной длины ключа вызывается функция
a t t empt H a c kWithKeyLength ( ) . Если она не вернула значение None, то
взлом считается успешным, и программа выходит из цикла в строке 238.

fесrиро•ние •ех кrальных •рианrов длины uюча меrодом
rpy6oi силы
Если попытки взлома с использованием всех возможных вариантов дли­
ны ключа, возвращаемых функцией kas i s kiExami n a t i on ( ) , оказались
неудачными, то в строке 242 переменная h a c kedMe s s age окажется равна
None. В этом случае проверяются все остальные варианты длины ключа
вплоть до МАХ _ КЕУ _LENGTH. Другими словами, если с помощью метода Ка­
сиски не удалось вычислить корректную длину ключа, то мы выполняем
полный перебор вариантов в цикле f o r (строка 245 ) .
242 .
243 .
244 .
245 .
246.
247 .
248 .
249 .

250 .
251 .
252 .

i f hac kedМe s sage == None :
i f not S I LENT MODE :
p r i nt ( ' UnaЬl e t o hack me s s a g e w i t h l i ke l y key l e ngth ( s ) .
Brut e - f o r c i ng key l ength . . . ' )
for keyLength i n range ( l , МAX_KEY_LENGTH + 1 ) :
# Не nерепроверять длину ключа , уже опробов анную
ме тодом Каси ски
i f keyLength not i n a l l L i ke l yKeyLengths :
i f not S I LENT MODE :
p r i nt ( ' Att empt ing hack w i t h key l ength % s ( % s
p o s s i Ы e keys ) . . . ' % ( ke yLengt h ,
NUM_MOST_FREQ_LETTERS * * keyLength ) )
hac kedMe s s age = a t t empt H a c kW i t hKeyLeng t h ( c iphe rt ext ,
keyLengt h )
i f hac kedМe s s age ! = None :
break

В строке 245 начинается цикл for, в котором для каждого значения
переменной keyLength (имеющей значения от 1 до МАХ_ КЕУ _LENGTH )
вызывается функция a t t empt H a c kWi thKeyLe ngth ( ) при условии , что
это значение не содержится в списке a l l L i ke l yKeyLengt h s . Причина за­
ключается в том, что значения длины ключа, содержащиеся в переменной
a l l L i ke l yKeyLeng th s , ранее уже были проверены (строки 233-238) .
Наконец, значение h a c kedМe s s age возвращается в строке 253:
,

253 .

398

return hac kedМe s sage

Глава 20

Вызов функции main ( )
Функция ma i n ( ) будет вызвана в том случае, сели программа была за­
пущена непосредственно, а не импортируется в виде модуля другой про­
граммой.
2 5 6 . # Е сли файл v i g e n e r e H a c ke r . py выполняет с я к а к п р о грамма
2 5 7 . # ( а не импортируется как модуль ) , выз в а т ь функцию ma i n ( )
ma i n ' ·
258 . if
name
259 .
ma i n ( )

:

На этом рассмотрение программы взлома шифра Виженера можно счи­
тать завершенным. Будет ли взлом успешным , зависит от характеристик
шифротекста. Чем ближе частотность букв исходного текста к частотно­
сти букв типичных английских текстов и чем длиннее исходный текст, тем
выше вероятность успеха.

Изменение значений констант, исnоnыуе м111х
в nроrрамме
Если программа взлома не справилась с задачей, то можно попытаться
изменить ряд настроек. На работу программы ока.1ыва10т влияние три кон­
станты, которые задаются в строках 8- 10.
8 . МАХ КЕУ LENGT H = 1 6
9 . NUM_MOST_FREQ_LETTERS
1 0 . S I LENTMODE = Fa l s e

4

# ограничение длины п р о в е р яемых ключей
# ограничение количества букв на подключ
# T rue - о т ключение вывода

Если длина ключа шифра Виженера превышает значение константы
МAX_KEY_LENGTH (строка 8 ) , то программе взлома заведомо не удастся
определить корректный ключ. В случае неудачи попробуйте увеличить это
значение и повторно выполнить программу.
Попытки взлома с использованием коротких некорректных ключей за­
нимают мало времени. Но если для константы МAX_KEY_LENGTH установ­
лено слишком большое значение и функция ka s i s ki Exami na t i o n ( ) оши­
бочно решит проверять ключи чрезмерно большой длины, то она может
выполняться часами или даже месяцами, пытаясь в:шомать шифротекст с
помощью ключей ошибочной длины.
Чтобы этого избежать, константа NUM_MOST_FREQ_LETTERS (строка 9)
ограничивает возможное количество букв, проверяемых для каждого
подключа. С увеличением данного значения программа взлома будет те­
стировать намного большее количество ключей. Это может потребоВзлом ши ф р а Виже нер а

399

ваться, если оценки, полученные с помощью функции f re qAna l ys i s .
eng l i shFreqMat chScore ( ) , оказались неточными, но учитывайте, что
работа программы существенно замедлится. А установка значения 2 6 для
константы NUM_мо s т _ FREQ_ LETTERS полностью исключит сокращение
числа возможных букв для каждого подключа!
Уменьшение значений констант МАХ КЕУ LENGTH и NUM_MOST_FREQ_
LETTERS ускоряет работу программы, но снижает вероятность успешного
взлома шифра, тогда как их увеличение замедляет выполнение, но повы­
шает вероятность успеха.
Наконец, для увеличения быстродействия программы можно устано­
вить для константы S I LENT _MODE значение True, чтобы программа не тра­
тила понапрасну время на вывод информации на экран. Бывает так, что
компьютер сам по себе работает очень быстро, но отображение сообще­
ний на экране проходит относительно медленно. Недостатком отказа от
вывода информации на экран является то, что вам ничего не будет извест­
но о ходе процесса, и вы узнаете о результате только после того, как про­
грамма полностью завершит свою работу.

Резюме
Взлом шифра Виженера требует соблюдения определенной последова­
тельности действий. Кроме того, многие компоненты программы взлома
могут не сработать. Например, ключ шифра Виженера, использованный
для шифрования текста, может иметь длину, превышающую значение
МАХ_ КЕУ_ LENGTH, или же функция , выдающая оценку частотного соответ­
ствия, может получить неточные результаты, поскольку частотность букв в
открытом тексте отличается от типичной. Кроме того, открытый текст мо­
жет содержать слишком много слов, которые отсутствуют в файле словаря,
и функция i s E ng l i s h ( ) не распознает его как английский текст.
Проверяя различные причины, по которым программа взло м а не сра­
ботала, вы сможете вносить в код соответствующие изменения для обра­
ботки подобных случаев. Тем не м енее представленный в книге вариант
программы взлома отлично спрамяется с задачей снижения количества
возможных ключей, исчисляемых миллиардами или триллионами, всего
лишь до нескольких тысяч.
Вместе с тем существует одна методика, которая делает взлом шифра Ви­
женера математически невозможным, каким бы мощным ни был компью­
тер и какой бы изощренной ни была програм ма взлома. Об этой м етодике,
получившей название однора,зовый шифроблакнот, речь пойдет в главе 2 1 .

400

Глава 20

Ко н тр ольные воп росы

Ответы на контрольные вопросы при ведены в п риложе нии Б.

1.

Что такое перебор по словарю?

2.

Какую информацию о шифротексте предоставляет метод Касиски?

3.

Какие два и зменения происходят при п реобразован и и списка в множество с
помощью функции s e t ( ) ?

4.

П еременная spam п редставляет собой сп исок [ ' с а t ' , ' dog ' , ' mous е ' ,
' dog ' ] , содержащи й четыре элемента. Сколько элементов будет содержать
список, возвращаемый функцией l i s t ( s e t ( sp am ) ) ?

5.

Что вы ведет следующий код?
print ( ' He l l o ' , e nd= ' ' )
print ( ' Wo r l d ' )

Взл ом шифра В и женер а 40 1

О Д Н О РА ЗОВ Ы Й Ш ИФ РО БЛОКНОТ
"Я тысячу ptlЗ

пракручШJал это в голове, - прои:тосит
Уотерхауз, - и вижу единственное объяснение: они
переводят свои сообщения в болъшие двоuчные числа
и комбинируют их с другими болъшuми двоu'Чными
'Числами - скорее всего, однарtlЗовыми шифроблакнотами .
Тогда тъt ничего ·не добьешъся, - говорит Алан . ОднарtlЗовъtй ши фроблакнот взломатъ нелъзя ".
Нил СтШJенсон, "Криптономикон "

В этой главе вы узнаете о шифре , который
невозможно взломать, каким бы мо щным ни
был компьютер , сколько бы времени вы ни
тратили на взлом и каким бы гениальным хакером вы ни были . Речь идет о так называемом од­
норазовом шифjюблакноте (one-time pad cipher) . К счастью, нам
не придется писать для него новую программу! П рограмма для
работы с шифром Виженера, которую мы создали в главе 1 8 ,
позволяет реализовать этот шифр без внесения в нее каких-ли­
бо изменений. Одн ако шифрование с помо щью одноразового
блокнота настолько неудобно для регулярного применения,
что к нему в основном прибегают лишь с целью кодирования
сооб щений особой важности.

В это м



главе • . •

Не поддающийся взло му одноразовый шифроблокнот
Двухразовый шифроблокнот как экви валент шифра Виженера

Не nодда1Ощийся вэnому однораэовый
wифробnокнот
Одноразовый шифроблокнот - это шифр Виженера, который приобре­
тает абсолютную криптографическую стойкость, если ключ удовлетворяет
следующим критериям:
1 ) длина ключа совпадает с длиной открытого сообщения;
2) символы ключа выбираются абсолютно случайным образом;
3) ключ используется всего один раз и больше не применяется ни к
каким другим сообщениям.
Придерживаясь этих трех правил , можно сделать зашифрованное со­
общение неуязвимым для любых видов криптоанализа. Такой ключ невоз­
можно взломать, даже располагая неограниченными вычислительными
ресурсами.
Свое название шифр получил благодаря тому, что обычно его ключи за­
писывали в блокноте. После использования ключа верхний лист блокнота
отрывали, чтобы перейти к следующему ключу. Как правило, в блокноте
сразу записывали большое количество ключей, помечая ключи конкретны­
ми датами , а сам блокнот передавали из рук в руки. Например, если зашиф­
рованное сообщение было получено 3 1 октября, то следовало пролистать
блокнот и найти ключ, соответствующий этой дате.

Выра1ни1Оние длины IUIIO'fa no ра1меру (Оо6щенп
Чтобы понять, почему шифротекст, зашифрованный с помощью одно­
разового шифроблокнота, невозможно взломать, порассуждаем над тем ,
что делает обычный шифр Виженера уязвимым для атак. Вспомните, что в
основе нашей программы взлома шифра Виженера лежит частотный ана­
лиз. Но если размер ключа равен размеру сообщения, то подключ каждой
буквы открытого тек > len ( SYМВOLS )
66
> > > SYМВOLS [ O ]
'А'
> > > SУМВОLS [ З О ]
'е'
=

Символы в наборе можно представить их целочисленными и нде кс ами.
Но нам нужно каким-то образом объединить эти небольшие числа в одно
большое число, представляющее блок.
Для создания блока мы умножаем индекс символа в наборе на размер
символьного набора, возводимый в постоянно увеличивающуюся степень.
Сумма всех таких чисел и будет блоком. В качестве примера рассмотрим
объединение небольших чисел в один большой бл ок для строки ' Howdy ' .
Целое число для блока начинается с О , а размер набора составляет 66
символов. Введите в интерактивной оболочке следующие инструкции .
> > > Ыockinteger
> > > len ( SYМВOLS )
66

=

О

Первый символ в строке ' Howdy ' -

'Н'

. Найдем его индекс.

> » SYМВOLS . index ( ' И ' )
7

Поскольку это начальный символ сообщения , мы умножаем его индекс в
символьном наборе на 66° (в Python операт ор возведения в ст еп е нь - * * ) , в
результате чего получаем значение 7 , которое прибавляется к блоку.

П рограмма шифро вания с открытым ключом 447

>>> 7 * (бб * * О )
7
> > > Ыock.Inteqer = Ыock.Integer + 7

Далее находим аналогичный индекс для ' о ' - второго символа в строке
' Howdy ' . Так как это второй символ в сообщении, умножаем его индекс в
символьном наборе на 66 1 , а не на 66°, и прибавляем результат к блоку.
> > > S!ИВOLS . index ( 1 о • )
40
> > > Ыockinteger += 4 0 * ( б б * * 1 )
> > > Ыock.Integer
2 64 7

Теперь целочисленное значение блока равно 2 6 4 7 . Мы можем сокра­
тить процесс нахождения индекса каждого символа, используя одну строку
кода.
> > > Ыockinteger += S!ИВOLS . index ( ' w ' ) * ( len ( S!ИВOLS ) * * 2 )
> » Ыock.Integer += S!ИВOLS . index ( ' d ' ) * ( len ( S!ИВOLS ) * * З )
> > > Ыock.Integer += S!ИВOLS . index ( ' y ' ) * ( len ( S!ИВOLS ) * * 4 )
> > > Ыockinteger
9 57 2 8 5 9 1 9

В результате кодирования строки ' Howdy ' в один большой целочис­
ленный блок мы получаем целое число 957 285 9 1 9, уникальным образом
представляющее эту строку. Продолжая возводить число 66 во все большую
степень, мы можем сформировать большое целое число для замещения
строки любой длины вплоть до размера блока. Например, блок 277 98 1
представляет строку ' 4 2 ! ' , а блок 1 О 627 1 06 1 69 278 065 987 48 1 042 235
655 809 080 528 - строку ' I n amed my с а t Z op h i e . ' .
В связи с тем что размер нашего блока равен 1 69, в одном блоке можно
зашифровать не более 1 69 символов. Если длина сообщения, которое мы
хотим закодировать, превышает 1 69 символов, придется создавать допол­
нительные блоки. В программе puhlicKeyCipher.py блоки будут разделяться
запятыми, чтобы пользователь видел, где заканчивается один блок и начи­
нается следующий.
В табл. 24. 1 приведен пример сообщения , разбитого на блоки, и показа­
ны целые числа каждого блока. В блоке может храниться не более 1 69 сим­
волов сообщения .

448

Глава 24

Табn. 24. 1 . Разбивка сообщения на блоки
Цеnое число блока

Сообщение

1 -й блок
Alan M athison
( 1 69 с имволов) Turing was а British
crypta na lyst a n d
computer scientist. Не
was hig h ly influential
in the development
of computer science
and provided а
formalisation of

3 0 1 3 8 1 03 3 8 1 2002765 8 1 206 1 1 1 663 3 2 2 7
0 1 5 9047 1 547608 3 2 6 1 5 2 5 95 4 3 1 3 9 1 5 75 7
97 1 407078 3 74 8 5 0 8 9 8 5 2 6592 8 606 1 3 956
4 8 6 5 77 1 2 40 1 2 6484 806 1 468979996 8 7 1 1
065 2544896 1 5 5 8 6402 77994456848 1 07 1
5 8 4 2 3 1 6206595 2 6 3 3 2464 2 5 9 8 59569 8 76
2 77 1 963 1 46093 9 2 5 6595 6 8 8 769305 9 8 2 9
1 540 1 2 9 2 3 4 1 45 946645 1 1 3 73 0 9 3 5 2 60 8 7
3 5 4 3 2 1 666 1 3 77362 346098640 3 8 1 1 0994
8 5 3 9 2 4 8 2 698

2-й блок
the concepts of
( 1 69 символов) algorith m and
computation with the
Turing machine. Turing
is widely considered
to Ье the father of
computer science and
artificial intelligence .
During W

1 1 0689078092 2 1 47455 2 1 5 9 3 5 0 8 0 1 95 6 3
4 3 73 1 3 2 6 8 0 1 0 2 708 1 92 7 1 3 65 1 48408547
5402 67775 2 79 1 95 8075 8 72 2 72 0 2 6708 70
2 6 3 40702 8 1 1 0970955 5 76 1 00 8 5 8 4 1 3 768
1 9 1 902 2 5 2 5 8 0 3 244269 1 476944762 1 742
5 73 3 3 90 2 1 48064 1 07269 8 7 1 669093 6550
045 770 1 42 80 2 9042445 2 4 7 1 1 75 1 4 3 5 049
1 1 73 9 8 9 8 6044 8 3 8 79 1 5 973 1 507893 7 1 94
8 60 1 1 2 5 7479 8 0 1 65 8 756445 2 79245 1 5 67
1 5 8 6 3 3 4863 1

orld War 1 1 he worked
for t h e Government
Code and Cypher
School a t Bletchley
Pa rk.

1 5 8 3 67975496 1 60 1 9 1 442 895 2 4472 1 75 8
3 697875 8 37635 974 8 64 1 2 80475094 3 905
655 902 2 73 2 095 9 1 807729054 1 944 8 5 9 8 0
905 3 2 8 69 1 5 7642 2 8 3 2 6 8 8 74950952 7709
9 3 5 74 1 799076979034

3-й блок
( 8 2 символ а )

В данном примере 420-символьное сообщение состоит из двух блоков по
1 69 символов каждый и одного блока, содержащего оставшиеся 82 символа.

Арифмеrика шифро111нп и дешифро1ани1 с оrкрыrым ключом
Теперь, когда вы знаете, как преобразовывать символы в целочислен­
ные блоки, рассмотрим математические операции, лежащие в основе
шифрования блоков с помощью открытого ключа.
Вот так выглядят уравнения , описывающие процесс шифрования с от­
крытым ключом:
C = M' mod n,
M = C d mod n.
П рограмма шифро вания с открыты м ключом 449

Первое уравнение используется для шифрования каждого целочислен­
ного блока, второе - для дешифрования. М - это целочисленный блок со­
общения , С - целочисленный блок шифротекста. Числа е и п образуют от­
крытый ключ, а числа d и п - закрытый ключ. Вспомните о том, что любой
человек, в том числе и криптоаналитик, имеет свободный доступ к откры­
тому ключу ( е, п) .
Мы создаем зашифрованное сообщение путем возведения каждого це­
лочисленного блока в степень е, как это делалось в предыдущем разделе,
и деления полученного результата по модулю п. Это дает нам целое число,
представляющее зашифрованный блок С. Объединение всех блоков позво­
ляет получить зашифрованное сообщение.
Предположим , например, что необходимо зашифровать пятисимволь­
ную строку ' Howdy ' и отправить ее Алисе. После преобразования в це­
лочисленный блок сообщение принимает следующий вид: [ 9 5 7 2 8 5 9 1 9 ]
(данное сообщение целиком умещается в одном блоке, поэтому список со­
держит всего одно значение) . Открытый ключ Алисы имеет длину 64 бита.
Этого недостаточно для того, чтобы считать его надежным, но в нашем
примере так проще и нагляднее. Числа п и е равны 1 1 6 284 564 958 604 3 1 5
258 674 9 1 8 1 42 848 8 3 1 759 и 1 3 805 220 545 6 5 1 593 223 соответственно.
(В случае 1 024-битовых ключей эти числа были бы намного больше. )
Шифруя сообщение, мы вычисляем результат операции
( 957 285 9 1 9 1 3 805 220 51 5 65 1 593 2 23 ) %
1 1 6 284 564 958 604 3 1 5 258 674 9 1 8 1 42 848 831 759
путем передачи этих чисел функции pow ( ) .
> > > pow ( 95 7 2 8 5 9 1 9 , 1 3 8 0522054 5 6 5 1 5 93223 ,
1 1 62 8 4 5 6 4 9 5 8 6 0 4 3152 5 8 6 7 4 9 1 8 1 42 8 4 8 8 3 1 7 5 9 )
4 3 92 4 8 0 7 6 4 1 5 7 4 60 2 9 6 9 3 3 4 1 7 6 5 0 5 1 1 8 7 7 5 1 8 6

Для быстрого вычисления столь большой степени функция pow ( ) выпол­
няет возведение в степенъ по модулю. Вычисление выражения ( 9 5 7 2 8 5 9 1 9 * *
1 3 8 0 5 2 2 0 5 4 5 6 5 1 5 9 32 2 3 ) % 1 1 62 8 4 5 6 4 95 8 60 4 3 1 5 25 8 67 4 9 1 8 1 4 2 8 4 8 8 3 1 7 5 9
обычным способом дало бы тот же самый результат, но длилось бы не­
сколько часов. Функция pow ( ) возвращает целочисленный блок, представ­
ляющий зашифрованное сообщение.
Чтобы дешифровать зашифрованное сообщение, его получатель, владе­
ющий закрытым ключом ( d, п} , должен возвести целочисленное значение
каждого блока в степень d и выполнить деление по модулю п. Декодирова­
ние всех дешифрованных блоков в символы с их последующим объедине­
нием позволяет получить исходное сообщение.

450

Глава 24

Пусть, например, Алиса пытается расшифровать целочисленный блок
43 924 807 641 574 602 969 334 1 76 505 1 1 8 775 1 86. Компоненты п ее откры­
того и закрытого ключей совпадают, а компонента d закрытого ключа рав­
на 72 424 475 949 690 1 45 396 970 707 764 378 340 583. Для дешифрования
необходимо выполнить следующую операцию.
>>> pow ( 4 3 9 2 4 8 0 7 6 4 1 5 7 4 6 0 2 9 6 9 3 3 4 1 7 65 0 5 1 1 8 7 75 1 8 6 ,
7 2 4 2 4 4 7 5 9 4 9 6 9 0 1 453969 7 0 7 0 7 7 6 4 3 7 8 3 4 0 58 3 ,
1 1 62 84 5 6 4 9 5 8 6 0 431525 8 6 7 4 9 1 8 1 4 2 8 4 8 8 3 1 7 5 9 )
957 2 8 5 9 1 9

Преобразовав целочисленный блок 9 5 7 2 8 5 9 1 9 в строку, м ы получим
' Howdy ' , т.е. исходное сообщение. Сейчас вы узнаете, как преобразовать
целочисленный блок в строку.

Прео6раэование 6ло•а в crpoq
Чтобы дешифровать блок в исходный целочисленный блок, прежде все­
го необходимо преобразовать его в небольшие целые числа, каждое из ко­
торых соответствует исходному символу. Процесс начинается с последне­
го символа, добавленного в блок. Для вычисления чисел, соответствующих
отдельным символам, используется деление с округлением вниз и деление
с остатком.
Вспомните, что в предыдущем примере целочисленное значение блока
для строки ' Howdy ' было равно 9 5 7 2 8 5 9 1 9. Исходное сообщение содер­
жит пять символов, а значит, индекс последнего символа в сообщении ра­
вен 4. Размер символьного набора сообщения равен 66. Чтобы определить
индекс последнего символа строки сообщения в символьном наборе, мы
делим число 957 285 9 1 9 на 664 с округлением вниз, получая 50. Для окру­
гления вниз можно использовать оператор целочисленного деления ( / / ) .
В символьном наборе элементом с индексом 5 0 ( S YMBOLS [ 5 0 ) ) будет сим­
вол ' у ' , который действительно является последним символом в строке
' Howdy ' .
В интерактивной оболочке эти вычисления можно выполнить с помо­
щью следующих инструкций.
> > > Ьlockinteger = 957285 9 1 9
> > > SYМВOLS = ' AВCDEFGBI.na.мNOPQRSТOVИXYZaЬcdef'qhijklшnopqrstuvwxyz
1234567890 ! ? . '
> > > Ыockinteger // ( 6 6 * * 4 )
50
» > SYМВOLS [ 5 0 ]
у
'

'

П рогр а мм а шифрования с открытым ключом 45 1

Следующий шаг - получение остатка от деления целочисленного значе­
ния блока на 661 , что дает нам очередной целочисленный блок. Результат
операции 957 285 9 1 9 % 661 равен 8 549 1 1 9, что соответствует целочис­
ленному блоку для строки ' Howd ' . Последний символ этого блока можно
определить путем деления на 663 с округлением вниз. Для этого введите в
интерактивной оболочке следующие инструкции.
> > > Ыockinteqer = 8 5 4 9 1 1 9
> > > SIМВOLS [Ьlockinteqer / / ( len ( SIМВOLS ) * * 3 ) ]
'd'

Итак, в этом блоке последним символом оказался ' d ' , и преобразован­
ная строка приобретает вид ' dy ' . Удалить этот символ из целочисленного
значения блока можно тем же способом , что и раньше.
> > > Ыockinteqer
> > > Ыockinteqer
2 1 1735

=

Ыockinteqer % (len ( SIМВOLS) * * З )

Число 2 1 1 7 3 5 - это блок для строки ' How ' . Продолжая в том же духе,
мы сможем восстановить всю строку.
> > > SIМВOLS [Ьlockinteqer / / ( len ( SIМВOLS ) * * 2 ) ]
' w '

>>>
>>>
'о'
>>>
>>>
'Н'

Ыockinteqer = Ьlockinteqer % (len ( SIМВOLS) * * 2 )
SIМВOLS [Ьlockinteqer / / ( len ( SYМВOLS ) * * 1 ) ]
Ыockinteqer = Ьlockinteqer % (len ( SIМВOLS) * * 1 )
SYМВOLS [Ьlockinteqer / / ( len ( SYМВOLS ) * * 0 ) ]

Теперь вы знаете, как извлечь символы строки ' Howdy ' из целочислен­
ного блока 9 5 7 2 8 5 9 1 9 .

почему непы1 11помаrь соо6щение, 1ашифро1анное ' вомощью
onrpыroro ключа
Различные типы криптографических атак, которые мы применяли ра­
нее, оказываются бессильными против шифрования с открытым ключом ,
если оно корректно реализовано. Вот лишь несколько причин.
1 . Атака методом грубой силы н е работает ввиду слишком большого
количества возможных ключей, подлежащих перебору.
2. Перебор по словарю не работает, поскольку ключи основаны на
числах, а не на словах.
452

Глава 24

3.

4.

Атака с использованием шаблонов слов не работает, поскольку
одно и то же слово исходного текста может быть зашифровано по­
разному, в зависимости от того, где именно оно встречается в блоке.
Частотный анализ не работает, поскольку каждый зашифрованный
блок соответствует целой группе символов, что делает невозможным
использованием счетчиков вхождений отдельных символов.

Поскольку открытый ключ ( е, п ) общедоступен , в случае перехвата шиф­
ротекста крипотаналитику будут известны числа е, п и С. Но, не зная число
d, математически невозможно вычислить М, т.е. исходное сообщение.
Вспомните из главы 23, что е - это число, взаимно простое с числом
(р - 1 ) ( q - 1 ) , а d представляет собой модульное обращение чисел е и
(p - l ) ( q - 1 ) . В главе 1 3 вы узнали о том , что модульное обращение двух чи­
сел вычисляется путем нахождения числа i, удовлетворяющего уравнению
( а · i) % т = 1, где а и т входят в выражение а шоd т. Таким образом, крипто­
аналитику известно, что d- это модульное обращение е шоd (р - 1 ) ( q - 1 ) , и
он мог бы получить полный ключ дешифрования , решив уравнение ( е d )
шоd (р - 1 ) ( q - 1 ) = 1 , но дело в том , что не существует способа определить,
что собой представляет произведение (р - 1 ) ( q - 1 ) .
Размеры ключей известны из файла открытого ключа, поэтому криптоа­
налитик изначально знает, что р и q меньше числа 2 1021 , а е - число, взаимно
простое с произведением (p - l ) ( q - 1 ) . Однако е обладает этим свойством в
отношении множества чисел, поэтому нахождение всех возможных произ­
ведений (p - l ) ( q - 1 ) чисел из диапазона от О до 2 1021 - слишком масштабная
задача для того, чтобы ее можно было решить методом грубой силы.
Несмотря на недостаточность данных для взлома шифра, криптоанали­
тик может получить определенную подсказку благодаря открытому ключу,
который состоит из двух чисел ( е, п) . Нам известно, что п = р q, поскольку
именно так мы вычисляли п, когда создавали открытый и закрытый ключи
в главе 23. А поскольку р и q - простые числа, то для заданного п существу­
ют ровно два числа, которые могут быть компонентами р и q.
Вспомните, что простое число не имеет никаких других делителей , кро­
ме единицы и самого себя . Следовательно, если перемножить два простых
числа, то делителями полученного произведения будут единица, само это
произведение и два искомых простых числа.
Таким образом, все, что требуется сделать для взлома шифра с откры­
тым ключом, - это найти множители числа п. Количество возможных ва­
риантов, предположительно, не так уж и велико, ведь нам известно, что
существуют всего два простых числа, произведение которых равно п.
Определив числа р и q, мы сможем вычислить произведение (р - 1 ) ( q - 1 ) и
использовать его для нахождения числа d. Процедура кажется не слишком
·

·

Програ мм а шифрован ия с открытым ключом 453

сложной. Для выполнения необходимых расчетов мы воспользуемся функ­
цией i s Pr ime ( ) из программы pnmeNит.frY, которую написали в главе 22.
Функцию i s P r ime ( ) можно видоизменить таким образом, чтобы она
возвращала первые из найденных множителей, поскольку нам известно,
что для п могут существовать лишь два делителя, отличных от единицы и
самого числа п.
def i s Pr ime ( num ) :
# Возвраща е т ( p , q ) , где р и q - делители num .
# Пров еряем, дели т с я ли num на какое -либо и з
# чисел , ме ньши х квадратного корня из num
f o r i in range ( 2 , i nt ( math . sqrt ( num ) ) + 1 ) :
i f num % i == О :
return ( i , num / i )
return None # делители не найдены , значи т , num - простое число

Если бы мы захотели написать программу для взлома шифра с открытым
ключом, то могли бы просто вызывать эту функцию, передавать ей число
п ( которое можно взять из файла открытого ключа) и ждать, пока она не
найдет множители р и q. Далее можно было бы вычислить произведение
(р - 1 ) ( q 1 ) , а затем - модульное обращение е шоd (р - 1 ) ( q 1 ) , получив
тем самым ключ дешифрования d. После этого не составило бы труда вы­
числить М, т.е. исходный текст.
Есть лишь одна проблема. Вспомните, что п представляет собой число,
для записи которого требуется около 600 цифр. Функция math . s qr t ( ) не
в состоянии обрабатывать столь большие числа, поэтому она выдаст сооб­
щение об ошибке. Но даже если бы она поддерживала такие числа, цикл
f o r выполнялся бы оченъ долго. Компьютеру не хватило бы и 5 миллиардов
лет непрерывной работы, настолько велики эти числа!
Именно этим и объясняется стойкость шифров с открытым ключом:
с математической точки зрения никакого обходного пути для нахождения делите­
лей болъших чисел не существует. Нет ничего проще, чем взять два простых
числа р и q и перемножить их для получения числа п. В то же время крайне
трудоемко решить обратную задачу: найти для достаточно большого числа
п простые делители р и q. Если взять небольшое число, скажем, 1 5 , то легко
определить, что его можно разложить на множители 5 и 3. Но ситуация
кардинальным образом изменится, если попытаться разложить на множи­
тели такое, например, число, как 1 78 565 887 643 607 245 654 502 737. Вот
почему шифры с открытым ключом практически невозможно взломать.
-

454 Глава 24

-

Исходный код nроrраммь1 PuЬl ic Кеу Cipher
Откройте в редакторе файлов новое окно, выбрав пункты меню Fileq
New Fi le. Введите в этом окне приведенный ниже код и сохраните его в
файле puЫicKeyCipher.py.
puЬ/icKeyCipher.py

1.
2.
3.
4.
5.
6.
7.
8.
9.
10 .

# Шифрование с открытым ключом
# https : / /www . пos t a rch . com/ crackiпgcode s / ( BS D L i ce п s e d )
irnport s ys , rnath
# От крытьм и закрытьм ключи для э той программы создают ся
# программой rna ke PuЬ l i c Privat eKeys . py . Программа должна
# выполнят ь с я в той же папке , в которой находят ся файлы ключей .
S YМBOLS
' ABCDE FGH I JKLMNOPQRSTUVWXYZ abcde fghi j klrnпopqrstuvwxyz
1 2 3 4 5 67 8 9 0 ! ? . '

11 .
1 2 . de f
13 .
14 .
15 .
16.
17 .
18 .
19 .

20 .
21 .
22 .
23 .
24 .
25 .
26.
27 .
28 .
29 .
30 .
31 .
32 .
33 .
34 .

=

rna iп ( ) :
# Проверяем , что необходимо сделать : записа т ь зашифрованное
# сообщение в файл или дешифро вать сообщение , прочитанное из файла
f i l e пarne
' eпcrypted_fi l e . txt ' # файл для чтения / записи
rnode
' eпcrypt ' # зада т ь ' eпcrypt ' или ' de crypt '
=

=

i f rnode
' eпcrypt ' :
rne s sage
' Jourпa l i s t s bel oпg iп the gut t e r because that i s
whe re t h e rul i пg c l a s s e s throw t he i r gui l t y s e c r et s . Gera l d
P r i e s t l a пd . T h e Fouпd i пg Fat hers gave t h e f r e e p re s s the
prot ect i oп i t rnus t have t o bare the s e c r e t s o f gove rnrneпt
апd iпforrn the peopl e . Hugo B l a ck . '
pubKeyFi l eпarne
' al_swe iga rt_pubkey . txt '
priпt ( ' Eпcryp t i п g апd w r i t iпg t o % s . . . ' % ( fi l eпarne ) )
eпcrypt edText
eпcryptAпdWr i t e T o Fi l e ( f i l eпarne ,
puЬKeyFi l eпarne , rne ssage )
==

=

=

=

p r i пt ( ' Eпcrypted t ext : ' )
priпt ( eпcrypt edText )
e l i f rnode
' decrypt ' :
privKeyFi l eпarne
' a l_swe i ga rt_pr ivkey . t xt '
priпt ( ' Readiпg frorn % s апd decrypt iпg . . . ' % ( f i l eпarne ) )
de crypt edText
readFrornF i l eAndDe crypt ( f i l eпarne ,
p rivKeyF i l e пarne )
==

=

=

priпt ( ' Dec rypted text : ' )
priпt ( decrypt edText )

П рогр а мм а шифрован ия с открытым ключом 455

35 .
3 6 . de f getBlocks FromText ( me s s age , ЫockS i z e ) :
# Преобра зуе т строку сообщения в список целочисленных блоков
37 .
for chara c t e r in me s s a g e :
38 .
i f charact e r not i n S YMBOLS :
39 .
p r i nt ( 1 E RROR : The s ymЬol s e t does not have t h e
40.
cha ract e r % s 1 % ( charact e r ) )
s y s . exit ( )
41 .
Ьlockints = [ ]
42 .
for ЫockStart i n range ( O , l e n ( me s s age ) , Ь l o c kS i z e ) :
43.
# Вычисляем целочисленный блок для текущей группы символов
44 .
Ы oc k i nt = О
45.
for i i n range ( Ьl o c kS t art , min ( ЬlockStart + Ы o c kS i ze ,
46.
l e n ( me s s a ge ) ) ) :
Ь l o c k i nt += ( SYМBOLS . index ( me s sage [ i ] ) ) * ( l en ( SYMBOLS ) * *
47 .
( i % Ы o c kS i ze ) )
48 .
Ы o c k i nt s . append ( Ь l oc k i nt )
return Ь l o c k i n t s
49.
50 .
51 .
5 2 . de f getText FromВlocks ( Ьl o c k i nt s , me s s ageLengt h , Ь l o c kS i z e ) :
# Преобразует список целочисленных блоков в с троку исходного
53 .
# сообщения . Необходимо задать длину исходног о сообщени я , чтобы
54 .
# корректно преобразовать последний блок .
55 .
me s sage = [ ]
56 .
f o r Ы o c k i nt i n Ы o c k i nt s :
57 .
ЫockМ e s sage = [ ]
58 .
f o r i i n range ( Ьl o c kS i ze - 1 , - 1 , - 1 ) :
59 .
i f l e n ( me s sage ) + i < me s s ageLengt h :
60 .
# Декодирование строки сообщения для нужного числа
61 .
# символов ( задается переменной Ь l o c kS i z e ) из блока
62 .
63 .
char i ndex
Ы oc k i nt / / ( len ( S YMBOLS ) * * i )
Ь l o c ki n t = Ьl o c k i nt % ( l e n ( S YМBOL S ) * * i )
64 .
Ь l o c kМe s sage . i nsert ( O , SYМBOLS [ ch a r i ndex ] )
65 .
me s s a g e . ext end ( Ь l ockМe s sage )
66 .
return 1 1 . j o i n ( me s s age )
67 .
68 .
69 .
7 0 . de f encryptMe s sage ( me s s age , key , Ы o c kS i ze ) :
71 .
# Преобразует строку сообщения в список целочисленных блоков
72 .
# и шифрует каждый бло к . Для шифрования нужен ОТКРЫТЫЙ ключ .
encryptedBlocks = [ ]
73 .
n , е = key
74 .
75 .
76.
for Ыосk i n getBlocks FromText (me s s age , Ы o c kS i ze ) :
77 .
# шифротекст = сообщение л е mod n
encrypt edB l o c ks . append ( pow ( Ь l o c k , е , n ) )
78 .
return encrypt edB l o c ks
79.
80 .
=

456

Глава 24

81 .
8 2 . def decryptMe s sage ( encrypte dB l ocks , me s s ageLength , key , Ы o c kS i z e ) :
83 .
# Дешифрует список зашифрованных блоков в строку сообщения ,
84 .
# длину которого необходимо зада т ь , чтобы корректно дешифровать
85 .
# последний блок . Для дешифров ания нужен ЗАКРЫТЫЙ ключ .
86.
decryptedB l o c ks
[]
87 .
n, d
key
88 .
for Ы ос k i n encryp t e dB l oc ks :
89.
# сообщение
шифротекст
d mod n
90 .
decryptedBlocks . append ( pow ( Ь l o c k , d , n ) )
91 .
return getText FromВ l o c ks ( decrypt e dBl ocks , me s s a geLengt h ,
Ь l o c kS i z e )
92 .
93 .
9 4 . de f readKeyFi l e ( keyF i lename ) :
95 .
# Считывает из указанного файла о ткрытый или
# закрытый ключ в виде кортежа ( n , e ) или ( n , d )
96 .
97 .
fo = open ( keyFi l e name )
content = fo . read ( )
98 .
fo . c l o s e ( )
99 .
keyS i z e , n , E o r D
cont ent . sp l i t ( ' , ' )
100 .
101 .
return ( i nt ( ke yS i ze ) , i nt ( n ) , int ( Eo r D ) )
102 .
103 .
1 0 4 . de f encryptAndWr i t eToFi l e ( me s s age Fi l e name , keyFi l e name , mes s age ,
Ь l o c kS i z e=None ) :
# Считывает ключ из файла , шифруе т сообщение и сохраняет его
105 .
# в выходном файле , возвращая зашифрованную строку
106 .
keyS i z e , n , е
readKeyFi l e ( keyFi l ename )
107 .
108 .
i f Ь l o c kS i z e
None :
109 .
# Если параметр Ы o c kS i z e не зада н , сделать его максималь но
допустимым для т е кущей длины ключа и размера набора символов
Ы o c kS i ze
int ( math . l og ( 2 * * keyS i z e , l e n ( SYMBOLS ) ) )
110 .
# Проверяем, достаточно ли длины ключа для данного ра змера блока
111 .
i f not ( math . l o g ( 2 * * keyS i ze , l e n ( S YМBOLS ) ) >= Ь l o c kS i z e ) :
112 .
s y s . exi t ( ' ERROR : B l o c k s i z e i s t o o l arge f o r the key and
113 .
symЬol s e t s i ze . Did уои speci fy the correct key f i l e
and encrypt e d f i l e ? ' J
# Шифруем сообщение
114 .
encryptedBlocks = encryptMe s s age ( me s s age , ( n , е ) , Ы o c kS i ze )
115 .
11 6 .
# Преобразование бол ь ших целочисленных значений в единую строку
117 .
for i in range ( le n ( encr yptedB locks ) ) :
118 .
e ncrypt edBlocks [ i ] = s t r ( encrypt edBlocks [ i ] )
119.
encrypt edCont ent
' , ' . j o i n ( encrypte dBlocks )
120 .
121 .
122 .
# Запись зашифрованной строки в выходной файл
encrypt edCont ent
' % s_% s_% s ' % ( le n ( me s sage ) , Ы o c kS i ze ,
123 .
encryptedCont ent )
=

=

=

л

=

=

==

=

=

=

П рогра мма шифр о ва н и я с открытым ключом 457

124 .
125 .
12 6 .
127 .
128 .
12 9 .
130 .
1 3 1 . de f
132 .
133 .
134 .
135 .
136 .
137 .
138 .
139 .
140.
141 .
142 .
143 .
144 .
145 .
14 6 .

147 .
148 .
14 9 .
150 .
151 .
152 .
153 .
154 .

fo = open ( me s sage F i l ename , ' w ' )
fo . w r i t e ( encrypt edCont ent )
fo . c l o s e ( )
# Возвращаем зашифро ванную строку
return encrypt edCont ent

readFromF i l eAndDe crypt ( me s s age F i l ename , keyFi l ename ) :
# Считыв а е т ключ из ф а йл а , считывает зашифрованное сообщение из
# в х одно г о файла и деши фрует е го , возвращая дешифрованную строку
keyS i z e , n , d
readKeyFi l e ( ke yFi l ename )
=

# Считываем р а змер сообщения и зашифрованное сообщение из файла
fo = open ( me s s a g e F i l ename )
content = fo . read ( )
content . sp l i t ( ' ' )
me s s ageLengt h , Ь l o c kS i ze , encrypt edМe s sage
me s sageLength
int ( me s sa geLengt h )
Ь l o c kS i ze = int ( Ь l o c kS i ze )
=

# Проверяем , достаточно ли длины ключа для данного размера блока
if not ( math . l og ( 2 * * keyS i z e , l e n ( SYMBOLS ) ) > = Ы o c kS i z e ) :
s y s . e x i t ( ' ERROR : B l o c k s i z e i s too l a rge for t he key and
s ymЬ o l set s i z e . Did you spe c i fy the correct ke y f i l e
and encrypted f i l e ? ' )
# П ре о бр а з уе м зашифрованное сообщение в целочисленные блоки
encrypt edB l o c ks
[]
for Ыос k i n encrypt edМe s s age . sp l i t ( ' , ' ) :
encryptedBlocks . append ( int ( Ь l o c k ) )
=

# Дешифруем бол ь шие целочисленные блоки
return dec ryptMe s sage ( encryptedB l o c ks , mes s ageLeng t h ,
Ь l o c kS i z e )

(n, d) ,

155 .
156 .
1 5 7 . # Е сли файл puЬ l i cKeyCiphe r . py выполняется как программа
1 5 8 . # ( а не импортируе т с я ка к модуль ) , вызвать функцию ma i n ( )
159 . if
narne
-rna i n- ' :
rna i n ( )
1 60 .

П ример выnоnнения

nроrраммь1 PuЬlic Кеу Cipher

Давайте попробуем выполнить программу puЫicKeyCipher.py и зашифро­
вать секретное сообщение. Чтобы отправить нужному человеку секретное
сообщение , зашифрованное с помощью этой программы , получите файл
открытого ключа данного человека и поместите его в одну папку с файлом
программы.
458

Глава 24

Прежде чем приступить к шифрованию сообщения, убедитесь в том,
что в строке 1 6 для переменной rnode установлено значение ' en crypt ' .
Обновите переменную rne s s age в строке 1 9 , задав в ней текст сообщения.
Укажите для переменной pubKeyFi l enarne в строке 20 имя файла откры­
того ключа. В переменной f i l enarne (строка 1 5 ) хранится имя выходно­
го файла, в который должен быть записан шифротекст. В строке 22 пе­
ременные f i l enarne , pubKeyF i l e narne и rne s s age передаются функции
encryptAndWri t e T o F i l e ( ) , которая шифрует сообщение и сохраняет его
в файле.
Запустив программу, вы должны получить примерно следующие резуль­
таты.
Encrypt ing and wr i t i ng t o encryp t e d_f i l e . txt . . .
Encrypte d text :
2 5 8 1 6 9 4 5 1 0 8 4 5 1 5 2 4 9 0 7 1 3 8 2 3 6 8 5 9 8 1 6 0 3 9 4 8 3 7 2 1 2 1 9 4 7 5 9 0 7 5 9 0 2 3 7 9 0 3 9 1 8 2 3 92 37 7 6 8
6 4 3 6 9 9 4 8 5 6 660301 3 2 3 1 5 7 2 5 37 2 4 9 7 8 02 2 8 6 1 7 02 0 98 3 2 4 4 2 7 7 38 2 8 4 2 2 5 5 3 0 18 6 2 1 33 8 0 1 8 8
8 8 0 5 7 7 3 2 9 6 3 4 8 3 3 9 2 2 98 9 0 8 9 0 4 6 4 9 6 9 5 5 6 9 3 7 7 97 0 7 2 4 3 4 3 1 4 9 1 6 5 2 2 8 3 9 6 92 2 7 7 0 3 4 5 7 9 4 6 3
5 9 4 7 1 38 4 3 5 5 9 8 98 4 1 8 9307 2 3 4 65008 8 68 98507 4 4 3 6 1 2 62707 1 2 9 9 7 1 7 8 2 4 0 7 6 1 0 4 50208047
9 2 7 1 2 9 68 7 8 4 1 6 2 1 7 3 4 7 7 6 9 6 5 7 0 1 8 2 7 7 9 1 8 4 9 0 2 9 7 2 1 5 7 8 5 7 5 9 2 5 7 2 9 0 8 5 5 8 1 2 2 2 1 0 8 8 9 0 7 0 1 6
9 0 4 9 8 3 0 2 5 5 4 2 1 7 4 4 7 1 60 6 4 9 4 7 7 9 6 7 3 6 0 1 5 3 1 0 0 8 9 1 5 5 8 7 6 2 3 4 2 7 7 8 8 3 3 8 1 3 4 5 2 4 7 3 5 3 6 8 0 6 2 4
5 8 5 6 2 9 67 2 9 3 97 0 9 5 5 7 0 1 6 1 07 2 7 5 4 6 9 3 8 8 2 8 4 5 1 2 4 1 9 2 5 6 8 4 0 9 4 8 3 7 3 7 2 3 3 4 9 7 3 0 4 0 8 7 9 6 9 6 2 4
0 4 3 5 1 6 1 5 8 2 2 1 68 94 5 4 1 4 8 0 9 60207 3 8 7 5 4 656357 1 4 07 4 7 7 2 4 65708 958 607 6 9 5 4 7 9 1 2 2 8 0 9 4 9
8 5 8 5 6 6 2 7 8 5 0 6 4 7 5 1 2 5 4 2 3 5 4 8 9 9 6 8 7 3 8 3 4 67 9 5 6 4 9 llJ. 2 5 3 3 8 4 3 3 3 6 9 7 5 1 1 5 5 3 9 7 6 1 3 3 2 2 5 0 4 0 2
6 9 9 8 6 8 8 3 5 1 5 0 62 3 0 1 7 5 8 2 4 3 8 1 1 68 4 00 4 9 2 3 6 0 8 3 5 7 37 4 1 8 1 7 6 4 5 9 3 3 37 1 9 4 5 64 5 3 1 3 3 6 58 4 7 6
2 7 1 1 7 6 0 3 5 2 4 8 5 9 7 0 2 1 97 2 3 1 6 4 5 4 5 2 6 5 4 5 0 6 9 4 5 2 8 3 8 7 6 6 3 8 7 5 9 9 8 3 9 3 4 0 5 4 2 4 0 6 6 8 7 7 7 2 1 1 3 5
5 1 1 3 1 3 4 5 4 2 5 2 5 8 97 3 3 97 1 9 62 2 1 9 0 1 6 0 6 6 6 1 4 9 7 8 3 9 0 37 8 6 1 1 1 7 5 9 6 4 4 5 6 7 7 3 6 6 9 8 6 0 9 4 2 9 5 4 5
605901 7 1 4 33908 2 5 4 2 7 2 5 0 1 5 1 4 053098 5 6 8 5 1 1 7 2 32 5 9 8 77 8 1 7 65 4 5 638 1 4 1 4 036570104353
8 5 92 4 4 6 6 0 0 9 1 9 1 0 3 9 1 0 9 9 6 2 1 0 2 8 1 9 2 1 7 7 4 1 5 1 9 6 1 5 6 4 6 9 9 7 2 9 7 7 3 0 5 2 1 2 6 7 6 2 9 37 4 6 8 2 7 0 0 2 9
8 3 2 3 1 4 6 6 8 2 4 0 6 9 3 2 3 0 0 3 2 1 4 1 0 97 3 1 2 5 5 6 4 0 0 6 2 9 9 6 1 5 1 8 63 5 7 9 9 4 7 8 6 5 2 1 9 6 07 2 3 1 6 4 2 4 9 1 8 6
4 8 7 8 7 5 5 5 6 3 1 6 3 3 9 4 2 4 9 4 8 9 7 5 8 0 4 6 6 0 9 2 3 6 1 6 6 8 2 7 6 7 2 4 2 9 4 8 2 9 6 3 0 1 67 8 3 1 2 0 4 1 8 2 8 9 3 4 4 7 3 7
8 6 8 2 4 8 0 9 3 0 8 1 2 2 3 5 6 1 3 3 5 3 9 8 2 5 0 4 8 8 8 0 8 1 4 0 6 3 3 8 9 0 5 7 1 92 4 9 2 9 3 9 6 5 1 1 9 9 5 3 7 3 1 0 6 3 5 2 8 0 3 7 1

Программа записывает шифротекст в файл encrypted_JUe. txt. Это резуль­
тат шифрования строки, хранящейся в переменной rne s s age (строка 1 9 ) .
Поскольку используемый вами открытый ключ наверняка будет отличать­
ся от моего, ваши результаты будут другими, но формат выходной строки
останется тем же. Как нетрудно увидеть, шифротекст разбит на два блока,
т.е. два больших целых числа, разделенных запятой. Число 2 5 8 в начале
шифротекста соответствует длине исходного сообщения. Далее идет сим­
вол подчеркивания и число 1 6 9 , определяющее размер блока.
Чтобы дешифровать это сообщение, задайте для переменной rnode зна­
чение ' dec rypt ' и вновь запустите программу. Убедитесь в том, что в стро­
ке 28 в качестве значения переменной p r i vKey F i l en arne указано имя соот­
ветствующего файла закрытого ключа и что этот файл, равно как и файл
П рогра мма шифрова н и я с от крытым кл ючом 459

encrypted_file. txt, находится в том же каталоге, что и файл puЫicKeyCipher.py.
На этот раз программа дешифрует зашифрованное сообщение, хранящее­
ся в файле encrypted_file. txt, и результат будет примерно таким.
Reading f r om encrypt ed_f i l e . txt and de crypt ing . . .
Decrypt ed text :
Journa l i s t s b e l ong in the gut t e r because that i s whe re the r u l i n g c l a s s e s
t h row the i r gu i l t y s e c ret s . G e r a l d P r i e s t l and . T h e Found i ng Fat h e r s gave
the free pre s s the prot ect ion it mus t have to bare the s e c ret s o f
gove rnment and inform the peop l e . Hugo B l a c k .

Учтите, что программа puЫicKeyCipher.py способна шифровать и дешиф­
ровать только простые текстовые файлы.
Теперь перейдем к более тщательному рассмотрению исходного кода
программы .

Начаnо nроrраммь1
В шифрах с открытым ключом используются числа, поэтому мы пре­
образуем строку сообщения в целое число. Оно вычисляется на основа­
нии индексов символов в наборе, который хранится в константе SYMBOLS
(строка 1 0 ) .
1.
2.
3.
4.
5.
6.
7.
8.
9.
10 .

# Шифрование с от крытым ключом
# ht t p s : / / www . no s t a rch . com/ cr a ckingcode s / ( BS D L i censed )
import s y s , math
# От крытый и закрытый ключи для этой программы создаются
# программой ma ke PuЬ l i c P r ivat eKeys . py . Программа должна
# выполнят ь ся в т ой же папке , в которой находят ся файлы ключей .
SYMBOLS = ' ABCDE FGH I JKLMNOPQRST UVWXY Z abcdefgh i j klmnopqr s t uvwxyz
1 2 3 4 5 67 8 90 ! ? .
1

Как nроrрамма оnредеnяет, что ей деnать:
wифровать иnи деwифровать
Чтобы программа puЬlicKeyCipher.py могла определить, какую именно
операцию - шифрование или дешифрование - следует выполнить и какой
файл ключа следует использовать, мы сохраняем определенные значения
в соответствующих переменных, которые задаются в функции ma i n ( )
.

460

Глава 24

1 2 . de f
13 .
14 .
15 .
16.

ma i n ( ) :
# Проверяем, что необ ходимо сделать : записат ь зашифрованное
# сообщение в файл или дешифроват ь сообщение , прочитанное из файла
f i l ename
' encrypted_f i l e . txt ' # файл для чтения / записи
mode
' encrypt ' # задат ь ' encrypt ' или ' de crypt '
=

=

Если в строке 1 6 для переменной mode задано значение ' encryp t ' ,
то программа зашифрует сообщение и запишет его в файл, имя которо­
го содержится в переменной f i l ename . Если же переменная mode равна
' de c rypt ' , то программа прочитает содержимое зашифрованного файла
(его имя берется из переменной f i l e name ) и дешифрует его.
Строки 1 8-25 определяют порядок действий в случае шифрования.
18 .
19.

20 .
21 .
22 .
23.
24 .
25 .

i f mode == ' encrypt ' :
me s sage
' Journa l i s t s b e l ong i n the gut t e r because that i s
whe re the rul i ng c l a s s e s t h row the i r gui l t y s e cre t s . G e r a l d
P r i e s t l and . The Founding Fathe r s g a v e t h e f r e e pre s s t he
pro te ct ion it mus t have t o bare the s e cret s o f government
and i n f o rm the peop l e . Hugo B l a ck . '
pubKe yFi l ename
' a l_swe igart_pub ke y . txt '
p r i nt ( ' Encrypt ing and w r i t i ng t o % s . . . ' % ( f i l ename ) )
enc rypt edText = encryptAndW r i t e T o Fi l e ( f i l ename ,
pubKe y F i l ename , me s sage )
=

=

p r i nt ( ' Encrypted text : ' )
p r i nt ( enc rypt edText )

Переменная me s s age (строка 1 9 ) содержит текст, подлежащий шифро­
ванию, а переменная pubKeyF i l ename (строка 20) задает имя файла откры­
того ключа (в данном примере это файл al_sweigart_pubkey. txt) . Не забывайте
о том, что переменная me s s a g e может содержать лишь символы , которые
входят в состав используемого символьного набора, хранящегося в перемен­
ной SУМВОLS . В строке 22 вызывается функция еnсrурtАndWr i tеТо F i l е ( ) ,
которая шифрует сообщение с помощью открытого ключа и записывает
его в файл, задаваемый переменной f i l ename .
Строки 27-33 определяют порядок действий, когда переменная mode со­
держит строку ' de c rypt ' . В этом случае вместо того, чтобы шифровать
сообщение , программа считывает содержимое файла закрытого ключа,
имя которого задано в переменной p r ivKeyFi l ename (строка 28 ) .
27 .
28 .
29.

e l i f mode
' de c rypt ' :
privKeyFi l ename = ' a l_swe i g a r t pr ivkey . t xt '
p r i nt ( ' Reading f r om % s and decrypt ing . . . ' % ( fi l ename ) )
==

П рогр а мм а шифро ван ия с открытым кл ючом 46 1

30 .
31 .
32 .
33 .

decrypt e dT e xt = readFromF i l eAndDec r ypt ( f i l ename ,
p r ivKe yF i l ename )
p r i nt ( ' De c r yp t e d t ext : ' )
print ( decrypt edText )

Далее мы передаем переменные f i l ename и p r i vKeyFi l ename функции
readFromF i l eAndDecrypt ( ) , которая возвращает дешифрованное сооб­
щение, сохраняемое в переменной decryptedText (строка 30) . В стро­
ке 33 дешифрованное сообщение выводится на экран, и на этом работа
программы завершается.
Теперь рассмотрим, как реализуются основные этапы шифрования с от­
крытым ключом и , в частности, как сообщения преобразуются в блоки.

П реобразование строк в бnоки с nомощь1О функции
getвlocksFromText ( )
Функция g e t B l o c k s FromT e x t ( ) преобразует сообщение в 1 28-байто­
вые блоки. Она получает строку сообщения и размер блока в качестве па­
раметров и возвращает список блоков, т.е. список больших целых чисел,
замещающих сообщение.
3 6 . de f g e t B l o c k s F romText ( m e s s a g e , Ы o c kS i z e ) :
# Преобразует строку сообщения в список целочисленных блоков
37 .
for cha ract e r i n me s sage :
38 .
i f cha ra c t e r not i n SYMBOLS :
39 .
p r i nt ( ' ERROR : The s ymЬ o l s e t doe s not have t he
40 .
cha r a c t e r % s ' % ( charact e r ) )
sys . exit ( )
41 .

Код в строках 38-4 1 позволяет убедиться в том , что строка me s s age
содержит лишь символы из набора, хранящегося в переменной SYMBOL S .
Параметр Ы oc kS i z e необязательный, и в нем может быть указан любой
размер блока. Приступая к созданию блоков, мы прежде всего преобразуем
строку в байты.
Блок создается путем объединения всех индексов символьного набора,
соответствующих символам сообщения, в одно большое число, как это де­
лалось в разделе "Преобразование строки в блок". Для хранения блоков
создается список Ы o c k i n t s (строка 42) .
42 .

462

Ы o c kint s = [ ]

Глава 24

Мы хотим , чтобы размер каждого блока составлял Ы o c kS i z e байт, но
если сообщение не делится равномерно на блоки такого размера, то ко­
личество символов в последнем блоке будет меньше, чем Ы o c kS i z e . Для
обработки подобных ситуаций мы используем функцию min ( )
.

Функции min () и .шах ()
Функция min ( ) возвращает наименьший из > > min ( 1 3 , 32 , 1 3 , 1 5 , 1 7 , 3 9 )
13

Аргументом функции min ( ) также может быть список или кортеж. Что­
бы убедиться в этом , введите в интерактивной оболочке следующие ин­
струкции.
>>> min ( [ 3 1 , 2 6 , 2 0 , 1 3 , 12 , 3 6 ) )
12
> > > spam
( 1 0 , 37 , 3 7 , 43 , 3 )
» > min ( spam)
3
=

В данном случае функция min ( ) возвращает наименьший из элементов
списка или кортежа. Противоположная ей функция max ( ) возвращает наи­
больший из своих аргументов.
>>> шах ( 1 8 , 1 5 , 22 , 3 0 , 3 1 , 3 4 )
34

Теперь вернемся к программе и рассмотрим, как с помощью функции
mi n ( ) последний блок усекается до нужно1·0 размера.

Сохранение 6локов в переменной Ыockin t
В цикле f o r , который начинается в строке 43, создаются целые числа
для каждого блока. В качестве значения переменной Ы o c kS t a r t устанав­
ливается индекс первого символа создаваемого блока.
43.
44 .
45.
46.

for Ы o c k S t a r t i n range ( O , l e n ( me s s a ge ) , Ы o c kS i z e ) :
# Вычисляем целочисленный бло к дл� т е куще й г р упп ы символов
Ьlock!nt = О
for i i n range ( Ь l ockS ta r t , m i n ( Ь l o c k S t a r t + Ь l o c kS i z e ,
l e n ( me s s age ) ) ) :

П рогр а мм а шифровани я с открытым ключом 463

Для хранения создаваемого блока предназначена переменная b l o c k I n t ,
первоначально равная О (строка 45) . В о вложенном цикле f o r , который
начинается в строке 46, переменная i последовательно становится равной
индексам всех символов сообщения, включаемых в блок. Индексы стартуют
от значения Ы o c kS t a rt и достигают значения Ы o c kS t a rt + Ы o c k S i z e
либо l e n ( me s s a g e ) , в зависимости от того, что меньше. Функция min ( ) в
строке 46 возвращает наименьшее из двух значений.
Второй аргумент функции range ( ) в строке 46 должен быть наимень­
шим из значений Ы o c k S t a r t + Ы o c kS i z e и l e n ( me s s ag e ) , поскольку
каждый блок всегда состоит из 1 28 символов ( или другого фиксированного
значения, задаваемого переменной Ь 1 ос kS i z е ) , за исключением последнего
блока. Последний блок может содержать ровно 1 28 символов, но гораздо
вероятнее, что их будет меньше. В таком случае мы хотим ограничить пе­
ременную цикла i значением l e n ( me s s age ) , поскольку оно соответствует
индексу последнего символа в сообщении.
Определив диапазон символов, составляющих блок, мы выполняем
арифметическую операцию для преобразования символов в одно большое
число. Вспомните, как в разделе "Преобразование строки в блок" мы со­
здавали большое целое число путем умножения индекса каждого символа в
символьном наборе на 66"1и1ек'-"'"""иа ( 66 - это длина строки SYMBOLS ) . В про­
грамме мы умножаем значение SYMBOLS . i ndex ( me s s a ge [ i ] ) (целочис­
ленный индекс символа в наборе) на выражение ( 1 en ( S YМBOLS ) * * ( i %
Ы o c kS i z e ) ) для каждого символа, и прибавляем результат к значению
Ыockint.
47 .

Ы o c k i nt += ( SYМBOLS . i ndex ( rne s s age [ i ] } } * ( l e n ( S YMBOLS } * *
( i % Ы o c kS i z e } }

Мы хотим , чтобы показателем степени служил индекс относительно на­
чала текущего бл()'l(,а, который всегда будет изменяться от нуля до Ь 1 ос kS i z е .
М ы н е можем напрямую использовать переменную i в качестве показа­
теля степени индекс_символа, поскольку она индексирует всю строку со­
общения, проходя значения от О до l e n ( me s s age ) . В таком случае мы
получили бы индекс, намного больший, чем 6 6 . Выполнив деление i по
модулю Ы o c kS i z e , мы получаем индекс относительно начала блока; имен­
но поэтому в строке 47 выполняется операция l e n ( SYMBOLS ) * * ( i %
Ы o c kS i z e ) , а не просто l e n ( SYMBOL S ) * * i .
П о завершении вложенного цикла м ы получаем целочисленное зна­
чение блока. В строке 48 это значение добавляется в список Ы o c k i nt s .
Н а следующей итерации основного цикла, начатого в строке 43, вычисля­
ется следующий блок сообщения.
464

Глава 24

48 .
49.

Ы o c k i nt s . append ( Ь l oc k i nt )
return Ы o c k i n t s

В итоге мы получаем список целочисленных значений блоков,
Ы о с kint s , который возвращается функцией в строке 49.
На данном этапе мы преобразовали всю строку сообщения в целочис­
ленные блоки, но необходимо понять, как выполнить обратное преобра­
зование целочисленных блоков в исходное сообщение в процессе дешиф­
рования.
"

Дешифрование 6nоков с помощью фун кции
getTextFromВlocks ( )
Функция getText FromB l o c k s ( ) решает задачу, противоположную той,
которую решает функция g e t B l o c k s F romT ext ( ) . Она имеет параметр
Ыockint s , содержащийсписок целочисленных блоков, параметр me s s age­
Length, задающий длину сообщения, и параметр Ы o c kS i z e , определяю­
щий размер блока, и возвращает строку, соответствующую данному спи­
ску блоков. Параметр me s s ageLength нужен для того, чтобы функция
могла обработать последний блок в тех случаях, когда он содержит менее
Ь l o c kS i z e символов. Этот процесс был описан в разделе "Преобразова­
ние блока в строку" .
5 2 . de f
53 .
54 .
55 .
56.

g e t Text FromB l o c ks { Ь lo c k i nt s , me s sa g e Lengt h , Ы o c kS i z e ) :
# Преобразует список целочисленных блоков в строку исходного
# сообщения . Необходимо задат ь длину исходного сообщения , чтобы
# корре ктно преобра зоват ь последний бло к .
me s sage = [ ]

Список me s s a g e , создаваемый в строке 56 , будет хранить все симво­
лы, получаемые в результате обработки списка целочисленных блоков
Ыockint s .
В цикле f o r , который начинается в строке 57, м ы проходим по всем це­
лочисленным блокам , хранящимся в списке Ы o c k i nt s . В строках 58-65
вычисляются буквы, закодированные в текущем блоке.
57 .
58 .
59.

f o r Ь l o c k i nt in Ь l o c k i nt s :
Ы o c kМe s s a ge = [ ]
for i i n range { Ь l o c kS i z e

-

1 , -1, -1 ) :

Функция getText FromB l o c ks ( ) разбивает блок на Ы o c kS i z e целых
чисел, каждое из которых соответствует индексу символа в символьном
Прогр а мм а шифрования с откр ытым ключом 465

наборе. Мы должны извлекать эти индексы из переменной Ы o c k i nt в об­
ратном порядке, поскольку в процессе шифрования сообщения мы начи­
нали с наименьших показателей степени (66°, 66 1 , 66 2 и т.д. ) , тогда как в
процессе дешифрования операции деления и деления с остатком должны
выполняться сначала для наибольших показателей. Вот почему перемен­
ная вложенного цикла f o r , начинающегося в строке 59, имеет начальное
значение Ы o c kS i z e
1 , а затем уменьшается на 1 на каждой итерации ,
пока н е станет равна О.
Прежде чем преобразовать индекс в символ, необходимо убедиться в
том, что в процессе декодирования блоков мы не вышли за пределы со­
общения. Это делается путем проверки того, что количество преобразо­
ванных на данный момент символов, т.е. l e n ( me s s age ) + i , все еще не
превышает значение me s s a geLength (строка 60) .
-

i f l e n ( me s s age ) + i < me s s ageLength :
# Де кодиров ание строки сообщения для нужного числа
# символ ов ( задается переменной Ы o c kS i z e ) и з блока
cha r i ndex = Ы o c k i nt // ( l e n ( SYMBOLS ) * * i )
Ы o c k i nt = Ы o c k i nt % ( l en ( S YMBOLS ) * * i )

60 .
61 .
62 .
63 .
64 .

Для извлечения символов из блока мы следуем процедуре, описанной в
разделе "Преобразование блока в строку". Каждый символ добавляется в
список Ы o c kМe s s a ge. В процессе шифрования блоков порядок следова­
ния символов фактически меняется на противоположный, поэтому деко­
дированный символ нельзя присоединить в конец списка Ы o c kMe s s a g e .
Вместо этого символ вставляется в начало списка с помощью метода
insert ( ) .

И(DОЛЫОIОНИе (llШIOIOIO меrода insert ()
Списковый метод append ( ) добавляет значение в конец списка, тогда
как метод i n s e r t ( ) позволяет вставить значение в лю бую позицию списка.
В качестве аргументов метод i n s e r t ( ) получает целочисленный индекс
позиции вставки и добавляемое значение. Чтобы увидеть, как это работа­
ет, введите в интерактивной оболочке следующие инструкции.
> > > sраш
[2 , 4 , б , 8]
> > > spaш . insert ( O , ' hello ' )
> > > sраш
[ ' he l l o ' , 2 , 4 , 6 , 8 )
> > > spaш . insert ( 2 , ' world ' )
> > > sраш
[ ' he l l o ' , 2 , ' wo r l d ' , 4 , 6 , 8 )
=

466

Глава 24

В этом примере мы создаем список spam, а затем вставляем в него стро­
ку ' he l l o ' , делая ее первым элементом списка (индекс О ) . Как видите,
элемент можно вставить в позицию с любым допустимым индексом, в том
числе 2.

06ъединенне меменrов ,••,., .шessage в одну 'rpoay
Мы используем вычисленное значение cha r l ndex в качестве индекса в
строке SYМBOL S , чтобы извлечь из нее нужный символ, который добавля­
ется в начало списка Ы o c kМe s s ag e .
65 .
66 .
67 .

Ы o c kМ e s s a g e . i n s e r t ( O , SYMBOLS [ cha r l ndex ] )
me s sa g e . ext end ( Ь l o c kМe s s age )
ret urn ' ' . j oi n ( me s s a ge )

В конце каждой итерации основного цикла все символы текущего
блока добавляются в список me s s a g e (строка 66) . По завершении цикла
функция g e t T e x t F romB l o c k s ( ) возвращает список, преобразованный в
строку.

Функци• encryptмes s age ( )
Функция encryptMe s s a g e ( ) шифрует каждый блок, используя строку
исходного сообщения , а также кортеж открытого ключа из двух целых чи­
сел, создаваемый функцией readKe y F i l e ( ) , которую мы напишем далее.
Функция возвращает список зашифрованных блоков.
7 0 . def encryptMe s s age ( me s s a ge , ke y , Ь l o c kS i z e ) :
И Преобразует строку сообщения в список целочисленных блоков
71 .
И и шифрует каждый блок . Для шифрования нужен ОТКРЫТЫЙ ключ .
72 .
73 .
e n c r yptedB l ocks = [ ]
74 .
key
n, е
=

В строке 73 создается список encrypt e dB l o c k s , в котором будут хра­
ниться зашифрованные блоки. В строке 7 4 два целых числа, хранящихся в
кортеже key, присваиваются переменным n и е . Далее реализуется ариф­
метика шифрования с открытым ключом.
Над каждым блоком исходного сообщения выполняются определенные
арифметические операции, в результате которых мы получаем целое чис­
ло, представляющее собой зашифрованный блок. В частности, мы возво­
дим целочисленное значение блока в степень е и выполняем деление по
модулю n, применяя функцию pow ( Ь l o c k , е , n ) (строка 78) .
Програ мм а шифрования с откр ытым ключом 467

76.
77 .
78 .
79.

for Ы о с k i n g e t B l o c k s FromText ( me s s age , Ь l o c kS i z e } :
# шифротекст = сообщение л е mod n
encrypt edBlocks . append ( pow ( Ь l o c k , е , n } }
return encrypt edB l o c k s

Полученный зашифрованный блок добавляется в список encrypted­
Blocks .

Функция decryptмes sage ( )
Функция dec ryptMe s s age ( ) дешифрует список блоков и возвращает
дешифрованную строку сообщения. В качестве аргументов ей передается
список зашифрованных блоков, длина сообщения , кортеж закрытого клю­
ча и размер блока.
В строке 86 создается список decryptedB l o c k s , предназначенный для
хранения дешифрованных блоков, а в строке 87 мы применяем трюк с
групповым присваиванием для записи двух целочисленных составляющих
кортежа ключа в переменные n и d.
8 2 . de f
83 .
84 .
85 .
86.
87 .

dec ryptMe s s a g e ( encr ypt edB l oc k s , me s s ageLengt h , k e y , Ы o c kS i z e } :
# Дешифрует список зашифрованных блоков в строку сообще ния ,
# длину которого необходимо задат ь , чтобы корректно дешифровать
# последний блок . Для дешифрования нужен ЗАКРЫТЫЙ ключ .
decrypt e d B l o c k s = [ ]
n , d = key

Арифметика дешифрования блоков та же, что и в случае шифрования,
за исключением того, что целочисленное значение блока возводится не в
степень е , а в степень d (строка 90) .
88 .
89.
90 .

for Ы о с k i n encrypt edB l ocks :
# сообщение = шифротекст л d mod n
decrypt edBlocks . append ( pow ( Ь l o c k , d , n } )

Список дешифрованных блоков вместе с параметрами me s s ageLength
и Ы o c kS i z e передается функции getText FromB l o c ks ( ) , и результат воз­
вращается в виде дешифрованного текста.
91 .

return getText FromB l o c k s ( de c r ypt edB l o c k s , me s sa ge Le n gt h ,
Ь l o c kS i ze }

Теперь следует рассмотреть, каким образом функция readKeyFi l e ( )
считывает открытый и закрытый ключи из файлов и создает кортежи, ко­
торые передаются функциям enc ryptMe s s a ge ( ) и decryptMe s s age ( ) .
468

Глава 24

Чтение открытоrо и эакрытоrо кn1Очей иэ
соответству1Ощих файnов
Функция re adKeyFi l e ( ) считывает числовые значения ключей из фай­
лов, создаваемых программой makePuЫicPrivateKcys.py (см. главу 23) . Имя
файла задается параметром keyF i l e name . Файл должен находиться в той
же папке, что и программа puЫicKcyCipher.py.
В строках 97-99 функция открывает файл , загружает его содержимое в
строковом виде в переменную cont ent , после чего закрывает файл.
9 4 . def re adKe y F i l e ( keyF i l ename ) :
95 .
# Считыв а е т из указанного файла от крытый или
# закрытый ключ в виде кортежа ( n , e ) или ( n , d )
96 .
fo = open ( key F i l e name )
97 .
content = fo . read ( )
98 .
fo . c l o s e ( )
99 .
keyS i z e , n , Eo r D = cont e nt . spl i t ( ' , ' )
100 .
r e t u rn ( i nt ( ke yS i ze ) , i nt ( n ) , int ( Eo r D ) )
101 .

В файле ключа хранится размер ключа в байтах, число n, а также чис­
ло е либо d, в зависимости от типа ключа (открытый или закрытый ) . Как
было показано в предыдущей главе, эти значения записываются в строко­
вом виде и разделяются запятыми, что позволяет извлечь их с помощью
строкового метода s p l i t ( ) , задав запятую в качестве разделителя. Возвра­
щаемый методом s p l i t ( ) список содержит три элемента, которые поме­
щаются в переменные keyS i z e , n и E o r D с помощью инструкции группово­
го присваивания (строка 1 00).
Вспомните, что переменная cont ent представляет собой строку, поэ­
тому возвращаемые методом s p l i t ( ) элементы списка также будут стро­
ками. Чтобы преобразовать их в числа, мы применяем к ним функцию
i n t ( ) . В результате функция readKe y F i l e ( ) возвращает три целых числа:
i n t ( keyS i z e ) , i n t ( n ) и i n t ( E o r D ) .

Запись эаwифрованноrо сообщения в файn
Функция e n c r yp tAndWr i t e T o F i l e ( ) вызывает функцию e n c r ypt­
Me s s age ( ) для шифрования строки с помощью прочитанного ключа и соз­
дает файл, содержащий зашифрованное содержимое.
1 0 4 . de f encryptAndWr i t e T o F i l e ( me s s ag e F i l ename , keyFi le name , me s s age ,
Ы o c kS i z e=None ) :
# Считыв а е т ключ из файла , шифрует сообщение и со храняет его
105 .
# в выходном файле , возвраща я зашифрованную строку
106 .
keyS i z e , n , е = readKe yFi l e ( ke yFi l ename )
1 07 .

П рогр а мм а шифрования с открытым ключом 469

Функция имеет три строковых параметра: имя файла, в который не­
обходимо записать зашифрованное сообщение (me s s a ge F i l ename ) , имя
файла открытого ключа ( key F i l ename ) и сообщение, подлежащее шифро­
ванию (me s s a ge ) . Необязательный четвертый параметр Ы o c kS i z e задает
размер блока.
Первый шаг процесса шифрования - чтение значений keyS i z e , n и е из
файла ключа с помощью функции re adKeyFi l e ( ) (строка 1 07 ) .
Для параметра Ь l о с kS i z е предусмотрено значение по умолчанию Nоnе .
108 .
109 .
110 .

i f Ь l o c kS i ze
None :
# Если параметр Ь l o c kS i ze не задан , сдела т ь его максималь н о
допустимым для т екущей длины ключа и размера набора символов
Ь l o c kS i z e
i nt ( rnath . l og ( 2 * * keyS i z e , l e n ( SYMBOLS } } }
==

=

Если параметр Ы o c kS i z e не задан, то размер блока выбирается макси­
мально допустимым для заданной длины ключа и используемого набора
символов. Не забывайте о том, что должно выполняться условие 2д.1...на_"""""' >
раз.мер_набора_символовразw.р_блтш. Поэтому для определения предельного раз­
мера блока вызывается функция math . l o g ( ) , которая вычисляет логарифм
значения 2дмша_"""""' по основанию l e n ( SYМBOLS ) (строка 1 1 0 ) .
Арифметика шифрования с открытым ключом корректно работает
лишь в том случае, если длина ключа равна или превышает размер блока,
поэтому выполняемая в строке 1 1 2 проверка играет важную роль.
111 .
112 .
113 .

# Про в еряем, достаточно ли длины ключа для данного ра змера блока
i f not ( rnath . l og ( 2 * * keyS i z e , l e n ( SYMBOLS } ) > = Ь l o c kS i z e ) :
s y s . e x i t ( ' E RROR : B l o c k s i z e i s t o o l a rge for the key апd
s yrnЬ o l set s i z e . Did you s p e c i fy the correct key f i l e
a n d e n c rypt ed f i l e ? ' )

Если длина ключа слишком мала, выводится сообщение об ошибке и
программа завершает работу. В этом случае пользователь должен либо
уменьшить значение параметра Ы o c kS i z e , либо использовать ключ боль­
шей длины.
Теперь, когда у нас имеются компоненты ключа n и е , можно вызвать функ­
цию encryptMe s s age ( ) , которая возвращает список целочисленных блоков.
114 .
115 .

# Шифруем сообщение
encryptedB l o c ks
encryptMe s s a g e ( rne s s age ,
=

( n , е } , Ы o c kS i z e }

Функция encryptMe s s a g e ( ) ожидает, что ключ будет передан е й в виде
кортежа из двух целых чисел. Именно поэтому переменные n и е помеща­
ются в кортеж, который передается в качестве второго аргумента.
470

Глава 24

Далее мы преобразуем зашифрованные блоки в строку, которую можно
записать в файл. Все блоки объединяются в одну строку и разделяются запя­
тыми. Использовать для этого инструкцию ' , ' . j o i n ( encryptedB l o c ks )
напрямую нельзя, поскольку метод j o i n ( ) применим только к спискам со
строковыми значениями. На данный момент список encryptedB l o c k содер­
жит целые числа, следовательно, их необходимо преобразовать в строки.
1 17 .
118 .
119 .
120 .

# Преобразов ание больших целочисленных значений в единую строку
f o r i i n range ( l en ( encrypt e dB l o c ks ) ) :
encrypt edB l ocks [ i ] = s t r ( enc ryptedB l ocks [ i ] )
enc rypt edContent = ' , ' . j o i n ( encrypt edB l o c k s )

В цикле f o r , который начинается в строке 1 1 8, мы проходим по всем
индексам списка e n c ryptedB l o c ks , преобразуя целое число encrypted­
B l o c ks [ i ] в строку. По завершении цикла список encryptedB l o c ks будет
содержать строки , а не целые числа.
Только после этого список строк, содержащихся в переменной
enc ryptedB l o c ks , можно передать методу j o i n ( ) , который объединяет
их в строку, используя запятые в качестве разделителей. Объединенная
строка сохраняется в переменной encrypt edCont ent (строка 1 20 ) .
Кроме зашифрованных целочисленных блоков, в файл записываются
также длина сообщения и размер блока.
122 .
123 .

# Запись зашифрованной строки в выходной файл
enc ryptedContent = ' % s_% s_% s ' % ( l e n ( me s sage ) , Ы o c kS i z e ,
encryptedCont ent )

В строке 1 23 содержимое переменной enc ryptedContent преобразует­
ся таким образом, чтобы она включала размер сообщения в виде целого
числа ( l en ( me s s age ) ) , за которым следуют символ подчеркивания, раз­
мер блока ( Ы o c kS i z e ) , еще один символ подчеркивания и строка зашиф­
рованных блоков ( e n c rypt e dContent ) .
Последним этапом процесса шифрования становится запись содержи­
мого в файл. В строке 1 24 вызывается функция open ( ) , которой переда­
ется имя файла, хранящееся в параметре me s s a g e F i l e name . Учтите, что
если файл с таким именем уже существует, то он будет перезаписан.
124 .
125 .
126.
127 .
128 .

fo = ope n ( me s s age F i l ename , ' w ' )
fo . wr i t e ( encryptedCont ent )
fo . c l o s e ( )
# Возвращаем зашифрованную строку
ret urn encryptedCont ent

П рогр а мм а шифро ва ния с открытым ключом 47 1

Строка, содержащаяся в переменной encryptedCont ent , записывается
в файл посредством вызова метода wri te ( ) в строке 1 25. Далее файловый
объект f о закрывается (строка 1 26 ) .
Наконец, содержимое переменной enc ryptedContent возвращается
функцией e n cryptAndWri t e T o F i l e ( ) в строке 1 28. Это делается для того,
чтобы программа, вызвавшая данную функцию, могла, к примеру, вывести
строку на экран.
Теперь, когда вы узнали , как функция enc ryptAndWri teTo F i l e ( ) шиф­
рует строку сообщения и записывает результаты в файл, мы можем перей­
ти к рассмотрению того, как функция readFromF i l eAndDecrypt ( ) де­
шифрует зашифрованное сообщение.

Деwифрование содержимоrо файnа
Как и функция enc ryptAndWri teTo F i l e ( ) , функция readFromF i l e­
AndDec ryp t ( ) имеет параметры, задающие имя файла зашифрованного
сообщения и имя файла ключа. Убедитесь в том, что функции передается
имя файла закрытого, а не открытого ключа.
1 3 1 . de f
132 .
133 .
134 .

readFromF i l eAndDe crypt ( me s sage F i l e name , keyF i l ename ) :
# Считыв а е т ключ из файла , считыв а е т зашифрованное сообщ ение из
# в ходного файла и дешифрует его , в о з вра щ а я де шифрованную строку
keyS i z e , n , d = readKe yFi l e ( keyF i l ename )

Первым этапом процедуры дешифрования становится вызов функции
re adKeyF i l e ( ) для считывания значений переменных keyS i z e , n и d из
файла ключа (аналогично тому, как это происходило в процессе шифрова­
ния сообщения ) .
Второй этап - чтение содержимого файла. В строке 1 38 файл, заданный
в параметре me s s a g e F i l ename , открывается для чтения.
1 37 .
138 .
139 .
140 .
141 .
142 .

# Считываем ра змер сообщения и зашифрованное сооб щение из файла
fo = open ( me s sage F i l ename )
content = fo . read ( )
me s s ageLength , Ь l o c kS i z e , encryp t e dМe s sage
cont ent . sp l i t ( ' ' )
me s s ageLength = i nt ( me s s ageLength )
Ь l o c kS i ze = i nt ( Ь l oc kS i z e )

Метод read ( ) (строка 1 39) возвращает содержимое файла в виде одной
строки, которую вы увидели бы, если бы открыли файл в текстовом редак­
торе, таком как Блокнот или TextEdit, скопировали все его содержимое и
вставили в программу.

472

Глава 24

Вспомните, что формат зашифрованного файла - это три целых чис­
ла, разделенных символами подчеркивания. Они задают длину сообщения,
размер блока и собственно зашифрованные блоки. В строке 1 40 вызывает­
ся метод s p l i t ( ) , возвращающий указанные три значения в виде списка.
С помощью операции группового присваивания эти значения сохраняют­
ся в переменных me s s ageLength, Ы o c kS i z e и encryptedMe s s a g e соот­
ветственно.
Поскольку метод s p l i t ( ) возвращает строки, содержимое перемен­
ных me s s ageLength и Ы o c kS i z e преобразуется в целые числа с помощью
функции i n t ( ) (строки 1 4 1 и 1 42 ) .
В функции также проверяется, чтобы размер блока н е превышал длину
ключа (строка 1 45 ) .
144 .
145 .
14 6 .

# Проверяем, достаточно ли длины ключа для данного р а змера блока
i f not ( math . l og ( 2 * * keyS i z e , l e n ( S YMBOLS ) ) >= Ы o c kS i z e ) :
s y s . e x i t ( ' ERROR : B l o c k s i z e i s t o o l a rg e for the key and
s ymЬo l set s i z e . Did you s pe c i f y the correct key f i l e
and enc rypt ed f i l e ? ' )

Чаще всего это формальная проверка, ведь если бы размер блока был
слишком большим, то зашифрованный файл вообще не был бы создан.
Наиболее вероятной причиной ошибки может стать неверно заданный
файл закрытого ключа в параметре key F i l e name , а это означает, что сооб­
щение в любом случае было бы дешифровано некорректно.
Строка encryptedMe s s age содержит несколько блоков, разделенных
запятыми, которые мы преобразуем обратно в целые числа и сохраняем в
списке encryptedB l o c k s .
148 .
149 .
150 .
151 .

# Преобра зуем зашифрованное сообщение в целочисленные блоки
encrypt edB l ocks = [ ]
f o r Ы о с k i n encryp te dМe s sage . sp l i t ( ' , ' ) :
encrypt edB l o c ks . append ( i nt ( Ь l o c k ) )

В цикле f o r , который начинается в строке 1 50, мы проходим по
списку блоков, созданному путем вызова метода spl i t ( ) для строки
encrypt edMe s s a g e . Список содержит строковые значения блоков, ко­
торые преобразуются в целочисленный вид и добавляются в список
enc ryptedB l o c k (строка 1 5 1 ) . По завершении цикла список encrypted­
B l o c k s будет содержать целочисленные блоки зашифрованного сообще­
ния .
В строке 1 54 список encryptedB l o c k s передается функции decrypt­
Me s s age ( ) вмecтe c apгyмeнтoм me s s a geLength (длина сообщения) , закрыП рогр а мм а шифрования с открытым кл ючом 473

тым ключом (кортеж из двух целых чисел n и d ) и аргументом Ы o c kS i z e
(размер блока) .
153 .
154 .

целочисле нные блоки
return decryptMe s sa g e ( encrypt edB l o c ks , me s s ageLengt h ,
Ы o c kS i ze )
# Де шифруем бол ь шие

(n, d) ,

Возвращаемая функцией dе с r урtМе s s аgе ( ) строкадешифрованного со­
общения в свою очередь возвращается функцией readFi l eAndDecrypt ( ) .

Вызов функции main ( )
Наконец, если файл puЫicKeyCipher.py запускается как программа, а не
импортируется в виде модуля другой программой, то в строках 1 59 и 1 60
вызывается функция rna i n ( ) .
1 5 7 . # Е сли файл puЫ i cKeyCiphe r . py выполняетс я как программа
1 5 8 . # ( а не импор тируе т с я как модуль ) , выз в а т ь функцию ma i n ( )
ma i n
159 . if
name
ma i n ( )
160 .
'

·

Реэ1Оме
Примите поздрамения - вы дочитали книгу до конца! Главы под на­
званием "Взлом шифров с открытым ключом" не будет ввиду отсутствия
простых методик взлома, способных справиться с этой задачей в сроки,
которые не исчислялись бы триллионами лет.
В данной главе рассматривался упрощенный вариант RSA, и тем не ме­
нее это реальный шифр, применяемый в профессиональных криптогра­
фических приложениях. Например, когда вы регистрируетесь на сайте
или совершаете покунки в Интернете, подобные шифры позволяют защи­
щать пароли и номера кредитных карт от злоумышленников, которые мо­
гут перехватывать ваш сетевой трафик.
Несмотря на то что арифметика шифрования с открытым ключом везде
одна и та же, вам не следует исrюльзовать рассмотренную здесь программу
для защиты своих секретных файлов. Способы взлома подобных программ
достаточно сложные, и тем не менее они существуют. Например, из-за того
что случайные числа, генерируемые функцией random . rand i nt ( ) , не яв­
ляются истинно случайными и могут быть предсказаны, злоумышленник
способен выяснить, какие числа были выбраны в качестве простых чисел
для вашего закрытого ключа.

474 Глава 24

Все шифры , описанные в предыдущих главах, уязвимы для взлома и не
предназначены для практического применения. Вообще говоря, не стоит
пытаться писать собственный криптографический код для защиты своих
конфиденциальных данных, поскольку весьма вероятно, что в процессе
реализации программы вы допустите ряд трудно обнаруживаемых ошибок.
В результате злоумышленники и спецслужбы смогут воспользоваться эти­
ми уязвимостями для взлома ваших зашифрованных сообщений.
Шифр надежен лишь в том случае, если сообщение удается сохранить
в тайне, когда о шифре известно все, кроме вашего личного ключа. Нель­
зя рассчитывать на то, что криптоаналитик не имеет доступа к тому же
криптографическому приложению, которым пользуетесь вы, или что ему
неизвестно, какой шифр вы применили. Всегда исходите из того, что враг
знает систему!
Профессиональные программы шифрования пишутся специалистами в
области криптографии, которые годами изучали математические принци­
пы и слабые места различных шифров. Но даже в этом случае написанные
ими программы анализируются другими криптографами , которые пыта­
ются найти в них ошибки или потенциальные уязвимости. Вам тоже впол­
не по силам изучить все, что необходимо знать о криптосистемах, включая
соответствующий математический аппарат. Для этого не обязательно быть
самым крутым хакером, нужно лишь потратить достаточно времени на
овладение необходимым багажом знаний.
Надеюсь, книга послужит тем фундаментом , опираясь на который вы
станете отличным программистом. О программировании и криптографии
можно было бы рассказать гораздо больше , чем охвачено здесь, поэтому не
останавливайтесь на полпути! Настоятельно рекомендую прочитать книгу
Саймона Сингха "Книга шифров. Тайная история шифров и их расшиф­
ровки" (https : / /pda . coo l l ib . com/Ь / 2 3 3 5 4 5 / re ad) , в которой увлека­
тельно рассказывается об истории криптографии.
Желаю удачи !

Програ мма шифрования с открытым кл ючом 475

ОТЛАДКА КОДА PYT H O N

IDLE

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

Как работает отnадчик
Чтобы запустить отладчик IDLE, выберите в окне интерактивной обо­
лочки пункты меню Debu g q Debugger (Отладкаq Отладчик) . На экране
появится окно Debug Control (Управление отладкой) , приведенное на
рис. А. 1 .
Установите в этом окне флажки Stack (Стек) , Locals (Локальные пе­
ременные) , Source ( Исходный код) и Globals (Глобальные переменные) ,
чтобы отображать весь объем отладочной информации. Если окно Debug
Control открыто, то всякий раз, когда вы будете запускать программу из
окна редактора файлов, отладчик будет приостанавливать ее выполнение
перед первой инструкцией и выводить следующую информацию :





инструкция, которая должна быть выполнена;
список всех локальных переменных и их значений;
список всех глобальных переменных и их значений.
е U = О :
t rans l at e d = t rans l at e d + me s sage [ i ]
i = i
1
-

-

p r i nt ( t ra n s l a t e d )

Выберите в окне интерактивной оболочки пункты меню Debu g q
Debu gg er (ОтладкаQОтладчик) . Когда вы нажмете клавишу или выбе­
рете пункт R u n q R u n Module ( ВыполнитьQ Выполнить модуль) , отладчик
запустит программу и остановится на строке 4. Строки 1-3 в данной про­
грамме либо пустые, либо содержат комментарии, поэтому отладчик авто­
матически пропускает их. Отладчик всегда останавливается на той строке
кода, которая будет выполняться следующей. Окно Debu g Control примет
вид, показанный на рис. А.2.
Щелкните один раз на кнопке Over, чтобы выполнить строку 4, в ре­
зультате чего переменной me s s age будет присвоено значение ' Three can
keep а s e c r e t , if two of them are dead . ' . Теперь в разделе Globals
окна Debu g Control должна появиться переменная me s s age вместе со сво­
им значением.
Далее отладчик переходит к строке 5 , которая будет подсвечена в окне
редактора файлов (рис. А.3) .
Подсветка указывает на то, в каком именно месте программы находит­
ся в данный момент отладчик. Можете продолжить пошагово выполнять
программу, щелкая на кнопке Over. Если же хотите возобновить ее работу
в обычном режиме, щелкните на кнопке Go.

480

П рил ожение А

IJrh119 Co11trvl
Go

1

Stop

1

Ov.r

1 1 ()_uit
Out

1 Р'
w

revor

_file,_

'S:\\\\Oialektika\\\\Crac kC ".\\\)
file !·d� Г om1ot f\un Option' Wi11dow 1 lclp
1 Reve r з e C1pher

t h t t p s : / / www . no s t a r ch . corn/cr a c kir.Qcodeз
• :!: r � e car. keep а s � c re t. ,..
l e n (message )

-

tr;н1.s lated

translat�d +

О:

i

i

i

-

1

{ЗSD Licen�ed)

f t wto ::f then; а:е dead. . '

1

meвsagc { i ]

! 11: 5 Col: O

Рис. А .Э.

Отладчик остановился на строке 5

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

48 1

Задание точек останова
Можно задать точку останова на той или иной строке кода, и отладчик
будет приостанавливать программу всякий раз, когда выполнение дости­
гает этой строки. Точка останова позволяет быстро перейти к тому месту,
которое требует тщательной отладки.
Чтобы задать точку останова, щелкните правой кнопкой мыши на соот­
ветствующей строке в окне редактора файлов и выберите в контекстном
меню пункт Set B reakpoint (Задать точку останова) . Попробуйте, напри­
мер, сделать это в строке 9 (рис. А.4).
f ile

l·dit

forrr1o1t

lt

Window

1 tclp

t �ttpз : / /W'.+l'A' . г. o з t a r c.� . com/ cr ackingcode з

le� (me3sage )
i. >= О :

-

(BSD Lice�зed)

1

Parte
Ln: 9 Со� 4

Clear Brcakpoi11t

Рис. А.4.

Заданне точки о станова в строке 9

Строка, для которой задана точка останова, выделяется в окне редак­
тора файлов желтым цветом. Точек останова может быть сколько угодно.
Когда программа запускается под управлением отладчика, ее выполнение ,
как обычно, останавливается на первой инструкции. Но если щелкнуть на
кнопке Go, то программа будет выполняться до тех пор, пока не достигнет
ближайшей точки останова (рис. А.5 ) .
Далее можете щелкать на кнопках G o , Over, Step и Out, чтобы продол­
жить выполнение программы в соответствующем режиме. При каждом
щелчке на кнопке Go выполнение продолжится до тех пор, пока не встре­
тится очередная точка останова или не будет достигнут конец программы.
В нашем случае с каждым щелчком на кнопке Go строка tra n s l a ted, ото­
бражаемая в разделе Globals, будет увеличиваться по мере шифрования со­
общения.
Чтобы удалить точку останова, щелкните правой кнопкой мыши на со­
ответствующей строке в окне редактора файлов и выберите в контекстном
меню пункт Clear B reakpoint (Отменить точку останова) . Желтая подсвет­
ка строки исчезнет, и в будущем отладчик не будет останавливаться на этой
строке.
482

П ри л ожен ие А

(10 Step Ovcr Out t)uit
...
....__.
._ .
,__�_,__...._

.1

1

1 1

J:"



rrvcr О

'Ьdb0.runO, line 4;111: cxeilrkage._

None

-�ре > > math . ceil ( З . O )

ОТВЕТ: З
> > > math . floor ( З . 1 )

ОТВЕТ: З
»> round ( З . 1 )

ОТВЕТ: 3
>» round ( З . 5 )

ОТВЕТ: 4
> > > False and False

ОТВЕТ: Fa l s e
> > > False or False

ОТВЕТ: Fa l s e
> > > not not True

OTBET: T rue
3.

Составьте таблицы истинности для операторов and, o r и n o t .

О п ератор and
А

and

в

==

Реэупьтат

True
False

True

and

True

==

True

and

True

--

Fa lse

--

False

and

Fa lse

--

False

А

or

в

True

or

True

--

True

True

--

True
True
False

Fa lse
Fal se

and

О п ератор оr

True

or

==

Fa lse

or

Fa lse

--

Fa l se

or

False

--

Реэупьтат

От веты н а контрол ьны е в опросы 493

О п ератор not
not

А

not

True

not

False

4.

==

Резуnьтат

--

False
True

==

Какая из приведенных ниже инструкций корректна?
if
if
if
if

-- 1 ma i n
name
-- 1 name
ma i n
1
ma i n 1 .
name
1
-ma i n
-name 1

ОТВЕТ: i f

1 .
1 .

.

name

ma i n

' ·

rnaвa 9
1 . Если вы запустили приведенную ниже программу и она вывела на
экран число 8, то что будет выведено на экран , когда вы запустите
программу в следующий раз?
import random
random . s e e d ( 9 )
p r i nt ( random . randint ( l , 1 0 ) )

ОТВЕТ: 8 . (В результате задания одного и того же затравочного числа
будут генерироваться одни и те же псевдослучайные числа.)
2. Что выведет на экран следующая программа?
spam
(1, 2, 3]
eggs
spam
ham
eggs
ham [ O J
99
p r i nt ( ham
spam )
=

=

=

=

==

ОТВЕТ: Т ruе
3.

В каком модуле содержится функция deepcopy ( ) ?
ОТВЕТ: в модуле сору.

4.

Что выведет на экран следующая программа?
import сору
spam
[1, 2, 3]
eggs
copy . deepcopy ( spam )
ham
copy . de epcopy ( eggs )
ham [ O J
99
p r i nt ( ham
spam )
=

=

=

=

= =

ОТВЕТ: Fa l s e
494 При л оже ние Б

rnaвa 10
1 . Как правильно: o s . exi s t s ( ) или o s . p a t h . e x i s t s ( ) ?

ОТВЕТ: o s . path . exi s t s ( )
2.

Когда началась эпоха Unix?
ОТВЕТ: в полночь 1 января 1 970 года (среднее время по Гринвич у ) .

3.

Каким будет результат вычисления следующих выражений?
' Fooba r ' . s t a rt swi t h ( ' Foo ' )

ОТВЕТ: True
' Foo ' . s t art s w i t h ( ' Foob a r ' )

ОТВЕТ: Fa l s e
' Fooba r ' . s t a r t swi t h ( ' foo ' )

ОТВЕТ: Fa l s e
' ba r ' . e ndswi t h ( ' Foobar ' )

ОТВЕТ: Fa l s e
' Fooba r ' . endswi t h ( ' bar ' )

ОТВЕТ: T rue
' The qui c k brown f o x j urnped ove r the ye l low lazy dog . ' . t it l e ( )

ОТВЕТ: ' The Qu i c k B rown Fox
Dog . '

Jump e d Ove r T h e Ye l l ow

La z y

rnaвa 1 1
1 . Что выведет следующий код?
spam
{ ' name ' : ' Al ' }
p r i nt ( spam [ ' name ' ] )
=

ОТВЕТ: ' Al '
2. Что выведет следующий код?
spam
{ ' eggs ' : ' bacon ' }
pr int ( ' bacon ' i n spam)
=

ОТВЕТ: Fa l s e

Ответы на контрол ьные вопрос ы

495

3.

Напишите цикл for для вывода значений, содержащихся в следую­
щем словаре.
spam = { ' name ' :

' Z oph i e ' ,

' sp e c i e s ' :

' cat ' ,

' age ' : 8 }

ОТВЕТ:
f o r key i n spam :
p r i nt ( spam [ ke y ] )

4.

Что выведет следующий код?
p r i nt ( ' He l l o , wor ld ! ' . sp l i t ( ) )

ОТВЕТ: [ ' He l l o , ' ,
5.

' wo r l d ! ' ]

Что выведет следующий код?
de f spam ( eggs= 4 2 ) :
p r i nt ( eggs )
spam ( )
spam ( ' He l l o ' )

ОТВЕТ:
42
Hello

6.

Каков процент слов английского языка в следующем предложении?
" Whe t h e r i t ' s f l obu l l l a r in the mind to qua r f a l o g t he s l ings and
a r rows o f out rageous guuuuuuuuur . "

ОТВЕТ: 80% ( 1 2 из 15 слов: 1 2 / 1 5 = 0,8, т.е. 80% ) .

rnaвa 12
1.

Какой результат вернет приведенное ниже выражение?
H e l l o world ' . s t r i p ( )

ОТВЕТ: ' He l l o w o r l d '
2. Какие символы являются пробельными?

ОТВЕТ: символы пробела, табуляции и перехода на новую строку.
3.

Почему вызов ' He l l o w o r l d ' . s t r i p ( ' о ' ) возвращает строку, кото­
рая по-прежнему содержит букву ' о ' ?
ОТВЕТ: потому что метод s t r ip ( ) удаляет лишь символы, находящие­
ся в начале и в конце строки.

496

П риложен ие Б

4.

Почему вызов ' xxxHe l l oxxx ' . s t r i p (
рая по-прежнему содержит букву ' х ' ?

'Х' )

возвращает строку, кото­

ОТВЕТ: потому что метод s t r i p ( ' Х ' ) будет удалять только символы
' Х ' , но не ' х ' .

rnaвa 13
1.

Каковы результаты вычисления приведенных ниже выражений?
17 % 1 0 0 0

ОТВЕТ: 1 7
5 % 5

ОТВЕТ: О
2. Чему равен НОД чисел 1 0 и 1 5?

ОТВЕТ: 5 (поскольку 5
лятся как 1 0 , так и 1 5 ) .
3.

-

наибольшее из чисел , на которые нацело де­

Что будет содержать переменная spam после выполнения инструкции
spam , e g g s
' he l l o ' , ' wo r l d ' ?
=

ОТВЕТ: ' he l l o '
4.

НОД чисел 1 7 и 3 1 равен 1 . Означает ли это, что числа 17 и 31 являют­
ся взаимно простыми?
ОТВЕТ: да (числа называются взаимно простыми, если их НОД равен 1 ) .

5.

Почему числа 6 и 8 не являются взаимно простыми?
ОТВЕТ: потому что их НОД равен 2, а не 1 .

6.

Напишите формулу для модульного обращения выражения А mod С.
ОТВЕТ: модульным обращением i является число, удовлетворяющее
соотношению (А i) % С = 1 .
·

rnaвa 14
1.

Комбинацией каких двух шифров является аффинный шифр?
ОТВЕТ: мультипликативного и шифра сдвига (или шифра Цезаря) .

2. Что такое кортеж и чем он отличается от списка?

ОТВЕТ: кортеж - это тип данных, который , как и список, может со­
держать несколько значений. Однако , в отличие от списка, значения
кортежа не могут изменяться.
От веты н а контрол ьные в опросы 497

3.

Почему аффинный шифр является слабым, если ключ А равен 1 ?
ОТВЕТ: поскольку результатом умножения любого числа на 1 является
сам о число, это означает, что аффинный шифр будет кодировать лю­
бую букву той же буквой.

4.

Почему аффинный шифр является слабым, если ключ В равен О?
ОТВЕТ: поскольку результатом сложения любого числа с О является
само число, это означает, что аффинный шифр будет кодировать лю­
бую букву той же буквой.

Гnава 15
1 . Чему равно выражение 2 * * 5 ?

ОТВЕТ: 3 2
2.

Чему равно выражение 6 * * 2 ?
ОТВЕТ: 3 6

3.

Что выводит следующий код?
for i in range ( 5 ) :
i f i == 2 :
cont inue
p r i nt ( i )

ОТВЕТ:
о
1
3
4

4.

Выполняется ли функция ma i n ( ) , определенная в файле affineHacker.
ру, если другая программа импортирует модуль a f f ineHac ke r ?
ОТВЕТ: нет. (Если файл и м портируется другой програм м ой, то
переменной
name
авто матически присваивается значение
' a f f i neHa c ke r ' , поэтому функция ma in ( ) не вызывается.)

Гnава 16
1. Почему взлом простого подстановочного шифра методом 1·рубой
силы невозможен даже с помощью сверхмощного компьютера?

ОТВЕТ: даже для сам ого м ощного ко м пьютера количество возможных
ключей оказывается слишком больши м .
2. Что будет содержать переменная spam после выполнения следующего

кода?
498

П ри л ожение Б

spam = [ 4 , 6 , 2 , 8 ]
spam . s o r t ( )

ОТВЕТ: [ 2 , 4 , 6 , 8 J
3.

Что такое функция-обертка?
ОТВЕТ: функция-обертка вызывает другую функцию, передавая ей
свои аргументы, и возвращает значение, которое возвращает эта функ­
ция.

4.

Что возвращает метод ' he l l o ' . i s l owe r ( ) ?
ОТВЕТ: Т ruе

5.

Что возвращает метод ' HELLO 1 2 3 ' . i s uppe r ( ) ?
OTBET: True

6.

Что возвращает метод ' 1 2 3 ' . i s l owe r ( ) ?
ОТВЕТ: Fa l s e . (Чтобы метод i s l owe r ( ) вернул значение T ru e , стро­
ка должна содержать хотя бы одну букву в нижнем регистре и не содер­
жать букв в верхнем регистре. )

rnaвa 1 7
1.

Каким будет шаблон для слова "hello"?
ОТВЕТ: 0. 1 .2.2.3

2.

Имеют ли слова "шашшоth" и "goggles" общий шаблон?
ОТВЕТ: да (0. 1 .0.0.2.3.4) .

3.

Какое из слов - "Alleged" , "efficiently" или "poodle" - является возмож­
ным вариантом дешифрования шифрослова "РУУАСАО"?
ОТВЕТ: "Alleged" (строки "РУУАСАО" и "Alleged" имеют общий шаблон:
0. 1 . 1 .2.3.2.4) .

rnaвa 1 8
1.

Какому шифру аналогичен шифр Виженера, если не считать того, что
в нем используется несколько ключей вместо одного?
ОТВЕТ: шифр Виженера аналогичен шифру Цезаря. (Если выбрать для
шифра Виженера ключ длиной один символ, то он станет идентичным
шифру Цезаря.)

2. Сколько возможных вариантов существует для ключа шифра Вижене­

ра длиной 1 О символов?

От веты на контрол ьны е во п росы 499

А. Сотни.
Б. Тысячи.
В. Миллионы .
Г. Более триллиона.
ОТВЕТ: вариант г), т.е. более триллиона (точнее, количество возмож­
ных ключей равно 1 4 1 1 67 095 653 376) .
3.

К какому типу шифров относится шифр Виженера?
ОТВЕТ: шифр Виженера относится к полиалфавитным сдвиговым
шифрам , поскольку он представляет собой шифр сдвига (подобный
шифру Цезаря) , в котором используется несколько наборов подстано­
вочных символов.

rnaвa 19
1. Что такое частотный анализ?
ОТВЕТ: это раздел криптоанализа, связанный с подсчетом частотно­
сти букв в шифротексте.
2.

Какие шесть букв чаще всего встречаются в текстах на английском
языке?
ОТВЕТ: ETAOIN.

3.

Что будет содержать переменная spam после выполнения следующего
кода?
spam

=

[4 , 6, 2, 8]

spam . s o r t ( r e v e r s e =T ru e )

ОТВЕТ: [ 8 , 6 , 4 , 2 ]
4.

Если переменная spam содержит словарь, то как получить список его
ключей?
ОТВЕТ: l i s t ( spam . keys ( ) )

rnaвa 20
1 . Что такое перебор по словарю?
ОТВЕТ: это атака методом грубой силы, суть которой заключается
полном переборе слов из словаря в качестве возможных ключей.
2.

500

в

Какую информацию о шифротексте предоставляет метод Касиски?
ОТВЕТ: метод Касиски может помочь в определении длины ключа Ви­
женера, примененного в шифротексте.
При л ожение Б

3.

Какие два изменения происходят при преобразовании списка в множество с помощью функции s e t ( ) ?
ОТВЕТ: удаляются дубликаты значений, и теряется упорядоченность
значений (в отличие от списков эле м енты м ножеств не упорядочива­
ются) .

4.

Переменная spam представляет собой список [ ' ca t ' ,
' dog ' ,
' mous e ' , ' dog ' ] содержащий четыре элемента. Сколько элементов
будет содержать список, возвращаемый функцией l i s t ( s e t ( spam ) ) ?
,

OTBE'I: 3 (удаляется дубликат строки ' dog ' ) .
5.

Что выведет следующий код?
p r i nt ( ' He l l o ' , end= ' ' )
p r i nt ( ' Wo r l d ' )

OTBE'I: He l l oWo r l d (в одной строке) .

rnaвa 21
1.

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

2. Эквивалентом какого шифра является двухразовый шифроблокнот?

ОТВЕ'I: повторное при менение одноразового шифроблокнота эквива­
лентно шифрованию текста с помощью шифра Виженера.
3.

Если использовать ключ , длина которого в два раза превышает раз­
мер исходного сообщения, то станет ли шифрование с помощью од­
норазового шифроблокнота в два раза более безопасным?
ОТВЕТ: нет. Стойкость шифра останется той же, поскольку дополни­
тельные буквы ключа не используются.

rnaвa 22
1.

Сколько всего существует простых чисел?
OTBE'I: количество простых чисел бесконечно велико. Такого поня­
тия, как "наибольшее" простое число, не существует.

2.

Как называются целые числа, не являющиеся простыми?
ОТВЕТ: такие числа называются

состав11:ыми.

От веты на контрол ь ные в опросы

50 1

3.

Назовите два алгоритма нахождения простых чисел?
ОТВЕТ: м ожно назвать даже три: перебор делителей, тест Миллера Рабина и решето Эратосфена.

Гnава 23
1 . В чем разница между симметричными и асимметричными шифрами?
ОТВЕТ: в симметричном шифре один и тот же ключ используется как
для шифрования, так и для дешифрования сообщений. В асимметрич­
ном шифре для этих целей используются два разных ключа.
2.
3.

Алиса сгенерировала открытый и закрытый ключи. К сожалению,
впоследствии она потеряла закрытый ключ.
Смогут ли другие люди отправлять ей зашифрованные сообщения?
ОТВЕТ: да. Для отправки Алисе зашифрованных сообщений другие
люди с м огут использовать открытый ключ Алисы.

4.

Сможет ли она дешифровать сообщения, отправленные ей ранее?
ОТВЕТ: нет. Для дешифрования этих сообщений требуется знать ее за­
крытый ключ.

5.

Сможет ли она снабжать документы цифровой подписью?
OTBE'l: нет. Для создания цифровой подписи Алисе потребуется ее за­
крытый ключ.

6.

Смогут ли другие люди верифицировать подписанные ею ранее доку­
менты?
ОТВЕ'l: да. Для верификации документов, подписанных Алисой до по­
тери ею своего закрытого ключа, они смогут использовать открытый
ключ Алисы.

7.

Что такое ауrентификация и конфиденциальность? В чем разница
между ними?
OTBE'l: аутентификация - это процедура верификации личности, по­
зволяющая удостовериться в то м , что человек действительно тот, за
кого себя выдает. Конфиденциальность - это обеспечение недоступно­
сти инфор мации для посторонних лиц и предоставление доступа к ней
лишь те м , для кого она предназначена.

8.

Что такое невозможность отказа?
ОТВЕТ: это свойство цифровых подписей, гарантирующее, что вла­
делец подписи не сможет впоследствии отрицать факт подписания
доку м ента.

502

Приложение Б

П р е дм ет н ым указ ател ь
D
Diff, 24; 63

Е
ETAOIN, 333; 339

1
IDLE, 26; 62; 475

м
МIТМ, 43 1

Булев оператор, 1 5 1
Булев тип данных, 75

в
Вариант дешифрования, 287
Вероятно простое число, 42 1
Взаимно простые числа, 235; 437
Возведение в степень по модулю, 448
Возвращаемое значение, 1 37
Вызов функции, 59; 67
Выражение, 41

r
N

None, 203

р
РКI, 430

R
RSA, 429

А
Абсолютный путь, 1 79
Азбука Морзе, 3 1
Алгоритм Евклида, 233
Аргумент, 59; 123
по умолчанию, 21 О
Асимметричный шифр, 428
Атака
посредника, 43 1
словарная, 32 1
Аутентификация, 430
Аффинный шифр, 227; 236; 243
взлом, 260

Б
Бесконечный цикл, 255
Блок, 79; 444

Гибридная криптосистема, 442
Глобальная область видимости, 1 26
Глобальная переменная, 1 26
Глубокое копирование, 1 69
Групповое присваивание, 232
Гугол, 4 1 1

д
Двойное шифрование, 37
Двухразовый шифроблокнот, 405
Декартово произведение, 389
Декремент, 8 1
Деление, 1 48
на нуль, 204
с остатком, 229
целочисленное, 206; 239
Делитель, 230
Дешифровальный словарь, 288
Диапазон, 1 1 О

3
Закрытый ключ, 428
Затравка, 1 63
Значение, 40

и
Индекс, 55
отрицательный, 56
Инструкция, 44
break, 395
continue, 265
def, 1 2 3
elif, 96
else, 95
if, 94
import, 90
return, 1 37
Интегрированная среда разработки, 26; 62
Интерактивная оболочка, 26
Интервал повторения, 359
Интерполяция, 1 1 3
Интерпретатор, 25
Интерфейсная функция, 277
Инфраструктура открытых ключей, 430
Исходный код, 63

к
Кавычки, 6 1
одинарные, 52
тройные, 2 1 9
Кандидат, 287
Ключ
аффинного шифра, 236; 248
длина, 359; 402; 436
закрытый, 428
мультипликативного шифра, 235
открытый, 428; 444
простого подстановочного шифра, 270
секретный, 32; 409
случайный, 255; 404
шифра Виженера, 3 1 8
Код Морзе, 3 1
Комментарий, 67
Конкатенация, 53
списка, 1 32
Константа, 9 1
Конфиденциальность, 430
Кортеж, 249
Криптография, 30
Криптосистема

504

Предметн ый указател ь

гибридная, 442
с открытым ключом, 428; 444

л
Локальная область видимости, 125
Локальная переменная, 1 2 5

м
Максима Шеннона, 1 08; 249
Метод, 98
append(), 208; 325; 3 8 1
close(), 1 80
endswith(), 1 85
extend(), 382
fine(), 98
insert(), 464
islower(), 280
isupper(), 280
items(), 349
join(), 1 37; 325
keys(), 349
lower(), 1 84
read(), 1 8 1
readlines(), 358
replace(), 3 1 0
sort(), 275; 347
split(), 1 92; 202
startswith(), 1 84; 223
strip(), 222
sub(), 308
title(), 1 84
upper(), 1 84; 223
values(), 349
write(), 1 80
грубой силы, 1 07
Касиски, 359
Многострочный текст, 2 1 9
Множество, 378
Множитель, 230; 4 1 0
Модуль, 90
сору, 1 69
itertools, 389
math, 148; 4 1 7
os, 1 82
path, 1 82
random, 1 62

re, 295
secrets, 404
time, 1 86; 325
Модульная арифметика, 228
Модульное обращение, 237
Моноалфавитный шифр, 269
Мультипликативный шифр, 227; 234

н
над (наибольший общий делитель), 230
алгоритм Евклида,233
о
Область видимости, 125
Обратная совместимость, 206
Обратный шифр, 7 1
Одноразовый шифроблокнот, 40 1
Округление, 148
В Н И З , 449
Операнд, 40
Оператор, 40
., 98
* , 54; 1 32
* * , 263; 445
/, 148
//, 239; 449
%, 229
+ , 53; 1 32
and, 1 5 1
in, 97; 1 3 1 ; 200
mod, 229; 234
not, 1 53
not in, 97
or, 1 52
булев, 1 5 1
возведения в степень, 263; 445
деления с остатком, 229
приоритет, 42
присваивания, 44
составной, 1 34
сравнения, 76
целочисленного деления, 239
Открытый ключ, 428; 444
Отладка, 475
Оценка частотного соответствия, 334

п
Параметр, 1 2 3
Перебор
делителей, 4 1 5
по словарю, 32 1 ; 356
Переменная, 44
_пате_, 1 39
глобальная, 126
имя, 47
локальная, 125
Перестановочный шифр, 1 1 8; 1 32
взлом, 2 1 5
дешифрование, 143
частотный анализ, 336
Подключ, 3 1 8
Подстановочный шифр, 269
взлом, 285
частотный анализ, 335
Порядок операций, 42
Принцип Керкгоффса, 107
Присваивание, 232
Простое число, 4 1 0
Процент, 209
Псевдослучайное число, 1 62; 404
Пустая строка, 53

р
Регулярное выражение, 296; 308
Репликация
списка, 1 32
строк, 54
Решето Эратосфена, 4 1 8

с
Секретный ключ, 32; 409
Символ, 9 1
экранирование, 60
Символьный набор, 9 1
Симметричный шифр, 428; 44 1
Синтаксическая ошибка, 43
Словарная атака, 32 1
Словарь, 1 96; 298
Случайное число, 162; 404
Сортировка, 347

П редметный у казател ь 505

Составное число, 4 1 1
Список, 1 28; 1 66; 3 78
вложенный, 1 30
глубокое копирование, 1 69
длина, 1 3 1
конкатенация, 1 32
преобразование в строку, 325
репликация, 1 32
сортировка, 275
Сравнение, 76
Срез, 57
Ссылка, 1 66
передача, 1 68
Строка, 52
длина, 74
замена символов, 1 30
преобразование в список, 1 70
пустая, 53
репликация, 54
форматирование, 1 1 3
Счетные палочки Кюизенера, 230

т
Таблица истинности, 1 52
Текстовый файл, 1 76
Тест
Миллера - Рабина, 42 1
простоты, 4 1 1
Тестирование, 1 60
Тип данных, 4 1
булев, 75
кортеж, 249
Точка останова, 476; 480
Тройные кавычки, 2 1 9

ф
Файл
закрытие, 1 80
запись, 1 80; 188
открытие, 1 79
проверка существования, 1 82
текстовый, 1 76
чтение, 1 8 1 ; 1 86; 358
Факторизация, 41 1

506

Предмет ный указ ател ь

Функция, 59
append(), 290
ceil(), 148
choice(), 405
compile(), 296
deepcopy(), 1 69
exists(), 1 82
exit(), 1 72
float(), 149; 206
floor(), 1 48
input(), 68; 84
int(), 206
len(), 74; 1 3 1 ; 1 99
list(), 1 70; 349; 378
log(), 468
main(), 123; 1 26; 1 39
max(), 46 1
min{}, 46 1
open(), 1 79
pow(), 448
print(), 58; 67; 388
product(), 389
randbelow(), 404
randint(), 1 62
range(), 1 1 0
round(), 1 48
seed(), 1 62; 1 63
set{), 378
shuffie(), 1 69
sqrt(), 4 1 7
str(), 206
time(), 1 86; 325
возвращаемое значение, 68
интерфейсная, 277
обертка, 277
параметр, 1 2 3
передача п о значению, 346
создание, 1 2 3

х
Хеш-таблица, 200

ц
Целочисленное деление, 239
Цикл
for, 92; 20 1
while, 75; 93
бесконечный, 255
Цифровая подпись, 430

ч
Частотный анализ, 33 1 ; 363
Чувствительность к регистру, 48

m
Шаблон слова, 286
Шифр, 32
асимметричный, 428
аффинный, 227; 236; 243
взлом, 260
Виженера, 3 1 7
взлом, 355
частотный анализ, 337
моноалфавитный, 269

мультипликативный, 227; 234
обратный, 7 1
перестановочный, 1 1 8; 1 32
взлом, 2 1 5
дешифрование, 1 43
частотный анализ, 336
подстановочный, 269
взлом, 285
частотный анализ, 335
простой замены, 269
симметричный, 428
Цезаря, 32; 87
Шифроблокнот
двухразовый, 405
одноразовый, 40 1
Шифровальный диск, 33
Шифрослово, 286
Шифротекст, 32

э
Экранирование символов, 60
Элемент списка, 128
Эпоха Unix, 1 86

П ред м етный указател ь 507

АВТОМАТИЗАЦИЯ РУТИННЫХ
ЗАДАЧ С ПОМОЩЬЮ РУТНОN
практическое руководство
для начинающих
Эл Свейгарт

К н и г а нау ч и т вас испол ь з о ­
в а т ь Python д л я н а п и с а н и я п р о ­
гра м м , способн ы х в с ч и т а н н ы е
секунды сделать т о , на ч т о р а н ь ­
ше у вас уход и л и часы руч н о -

АВТ О МАТИЗАЦИЯ
РУТИННЫХ ЗАДАЧ
С ПОМ О ЩЬЮ PYTHON
nРАКТИЧЕСКОЕ РУКОВОДСТВО
ДЛR НА Ч ИН А Ю Щ И Х
ЭЛ С8ЕИГАРТ

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


поиск определенного тек­
ста в файле и л и в м н о ­
жестве файлов;



создание, обновление, пере­
мещение и переи менова­
ние файлов и папок;



поиск в И н тернете и загруз­



обновление и формат и р о в а н и е

ка о н л а й н-контента;
д а н н ы х в элект р о н н ы х табли­

www.williamspuЬlish ing.com

цах


Excel

л ю б ого размера;

разбиен ие, с л и я н ие, размет­
ка водя н ы м и знаками и ш и ф ­
рование РD F-документов;



рассылка н а п о м и н а н и й в виде
сообщен и й элект ронной почты
и л и текстовы х у в едомлен и й ;



I S B N 978- 5 -6040 7 24-2-4

заполнение о н л а й новы х ф о р м .

в продаже

РУТНОN
ДЛЯ ЧАЙНИКОВ
2-Е ИЗДАНИЕ

Джон Пол Мюллер

Python - это мощный язык
программирования , на котором
можно создавать самые разные
приложения, не зависящие
о т платформы. Он идеально
ПОДХОДИТ ДЛЯ НОВИЧКОВ,

особенно если нужно быстро
научиться программировать
и начать создавать реальные
проекты. Благодаря пошаговым
инструкциям, приведенным в
книге, вы сможете в краткие
сроки освоить основы языка.
Работая в среде Jupyter
Notebook, вы будете применять
принципы грамотного
программ ирования для создания
смешанного представления
кода, заметок, математических
уравнений и графиков.
Основные темы книги:

w w w. d i a l e k t i k a . c o m

I S B N : 9 7 8 - 5 - 9 0 7 1 44- 2 6 - 2



загрузка и установка Python;



использование командной
строки;



знакомство со cpeдoй jupyter
Notebook;



основы программирования на
Python;



создание коллекций
и списков;



взаимодействие с пакетами;



поиск и устранение ошибок.

в продаже

ИЗУЧАЕМ РУТНОN

ТОМ 1
5-Е ИЗДАНИЕ

Марк Лутц

llll­

O"REILLY

Марк. !Jтц

www.williamspuЬlishing.com

ISBN 9 7 8 - 5 -907 1 44- 5 2 - 1

С помощью этой п рактической
к н и ги вы получ ите всестороннее
и глубокое введение в основы языка
Python . Буду ч и основанн ы м на
популя рном учебном курсе Марка
Лутца, обновленное 5-е издание
книги поможет вам быстро
научиться писать эффективный
высококачественный код на Python .
Она я вляется идеал ьным способом
начать изучение Python, будь вы
новичок в програм м ирован и и
или профессиональн ы й
разработч и к програм м ного
обеспечен ия на дру гих языках .
Это простое и понятное у чебное
пособие, у ком плектованное
контрольными вопросам и,
у п ражнениями и полезн ы м и
иллюстрациями, позволит вам
освоить основы л и неек Python 3.Х
и 2.Х. Вы также ознакомитесь
с расш ирен н ы м и возможностям и
язы ка, полу чившими ш ирокое
распространение в коде Python .

в

п родаже

ИЗУЧАЕМ РУТНОN

ТОМ 2
5-Е ИЗДАНИЕ

Марк Лутц

l!!l­

O'REILLY

Марк_ !J!тц

www.williamspuЬlishing.com

I S B N 9 7 8 - 5 - 9 0 7 1 44- 5 3 - 8

С помощью этой п рактической
к н и ги вы пол у чите всесторон нее
и глубокое введен ие в основы языка
Python . Буду ч и основан н ы м на
попул я рном у •1ебном курсе Марка
Лутца, обновленное 5-е издан ие
к н и г и поможет вам б ыстро
нау ч иться п исать эффекти вный
высококачественный код на Python .
Она я вляется идеал ь н ы м способом
начать и зу чен ие Python, будь вы
новичок в програм м и ровани и
или профессионал ьн ы й
разработч и к п рограм м ного
обеспечения на дру гих языках.
Это простое и понятное у чебное
пособие, у ком плектован ное
контрол ь н ы м и вопросам и,
у пражнен и я м и и полез н ы м и
иллюстрац и я м и , позвол ит вам
освоить основы ли неек Py thon 3.Х
и 2.Х. Вы также ознаком итесь
с расш ирен н ы м и возможностя м и
язы ка, полу ч и вшим и ш ирокое
распространен ие в коде Python.

в

п родаже

РУТНОN
СПРАВОЧНИК

ПОЛНОЕ ОПИСАНИЕ ЯЗЫКА
3-Е ИЗДАНИЕ
Алекс Мартелли
Анна Рейвенскроф т
Стив Холден

Алекс Мартелли,
Анна Рейвенскрофт, Стив Холден

www.williamspuЫishing.com

ISBN 9 7 8 - 5 -6040 7 2 3 - 8 - 7

К н и га ох в ат ы в ает
ч р е з в ы ч а й но ш и рокий спектр
областей п рименения Python,
включая веб-п риложе н и я ,
сетевое п рогра м м и рование,
обработку Х М L-документов,
взаи модействие с базами
дан н ы х и высокоскорост н ы е
в ы ч исле н и я . Она станет
идеал ь н ы м подспорьем как
для тех, кто реш ил изу ч ить
Python, имея п редварител ь н ы й
о п ы т програм м и рован ия
н а других язы ках, так и для
тех, кто уже испол ьзует этот
язык в своих разработках.
Основные тем ы к н и ги :

си нтаксис Python , модули
стандартной библ иотеки
и пакеты расш и р е н и й ;

опера ц и и с файлам и ,
работа с текстом, базы
дан н ы х , м ногозадач ность
и обработка ч исловых
данных;

основы работы с сетя м и ,
и клиентские модул и
сетевы х п ротоколов;

модул и рас ш и р е н и я
Python, средст ва
п акетирования и
расп ространения
расш ирен и й , модулей
и п риложе н и й .
в

продаже

Н ауч итес ь п ро г р а м м и ровать н а Pyt h o n ,
созда в ая и в зл а м ы ва я ш и ф р ы , с п о м о щ ь ю

ОсновныЕ ТЕМЫ книги


н и й н а Pyt h o n

о�о р ы х п е рес ы л а ются секрет н ы е сооб­


я!



П р и м е н е н и е сл о в а ре й д л я б ы строй
п ро в е р к и того, содержит ли де ш и ф ро­
в а н ное сооб ще н и е осм ы сл е н н ы й текст
н а а н гл и й с ком я з ы ке ил и случ а й н ы й



Созда н и е тесто в, позвол я ю щ и х убе­
д иться в том, что код ш и фрова н ия и де­
ш и фрова н и я работает ко р р е ктно



П рогра м м и рова н и е (и взлом!) а фф и н ­
н о го ш и фра, в кото ром д л я ш и фрова­
ния сообще н ия п р и м е н я ется модул ь н а я

П осле з н а комства с о с н о в а м и п ро гр а м м и ­
рова н и я н а Pyt h o n в ы уз н а ете, ка к
созда вать, тести ровать и взл а м ы вать кл ас­
с и ч е с к и е ш и ф р ы , в кл ю ч а я п е реста н о в о ч ­

н а б о р букв

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

Созда н и е к р и птогра ф и ч еских п р и л оже­

а р и фмет и ка


Взл ом ш и фров методом грубой сил ы
и с п о м о щ ь ю ч а стот н о го а н ал иза

В каждой гл а ве п р и водится п ол н о ц е н ­
н а я п ро грамма с п о ш а го в ы м о п и с а н и е м
а л го р итма ее работ ы . П ро ч ита в к н и гу, в ы
н ауч ите с ь п ро г р а м м и ровать н а Pyt h o n
и сможете созда вать собстве н н ы е кри пто­
гра ф и ч ес к и е с и стем ы !

Об АВТОРЕ

Эл СвЕйГАJП

профессиональный разработчик,
автор множества книг по программированию,
включая бестселлер Автоматизация рутинных
задач с помощью Python. Многие его книги
доступны на сайте i nventwi thpython . c om.
-

I S B N 978-5-907203-02-0
1 9 1 4 8