Złam ten kod z Pythonem. Jak tworzyć, testować i łamać szyfry - ebook
Złam ten kod z Pythonem. Jak tworzyć, testować i łamać szyfry - ebook
Szyfrowanie do niedawna było wiązane z bezpieczeństwem publicznym. Najbezpieczniejsze implementacje podlegały takim samym rządowym regulacjom jak przemysł zbrojeniowy. Do dzisiaj rządy i różnego rodzaju służby dążą do uzyskania możliwości odczytywania zaszyfrowanych danych. Tymczasem silna kryptografia jest podstawą globalnej ekonomii, zapewnia codzienną ochronę milionom użytkowników i większości organizacji. A to nie wszystko. Algorytmy szyfrujące, ich implementacja czy programowe łamanie szyfrów to równocześnie fascynująca dziedzina wiedzy i pole do zabawy, ćwiczeń oraz eksperymentowania z programowaniem.
Ta książka jest przeznaczona dla osób, które nie umieją programować, ale chciałyby zapoznać się z kryptografią. Omówiono tu podstawowe koncepcje programowania w Pythonie, który dziś jest uważany za najlepszy język dla początkujących koderów. Pokazano, jak tworzyć, testować i łamać programy implementujące szyfry klasyczne, takie jak przestawieniowy i Vigenere'a, by stopniowo przejść do znacznie bardziej zaawansowanych zagadnień, w tym kryptografii klucza publicznego. Każdy program przedstawiono w postaci pełnego kodu źródłowego, wyjaśniono także wiersz po wierszu jego działanie. Dzięki tej książce można się zarówno nauczyć zasad kryptografii, jak i zdobyć umiejętności pisania kodu szyfrującego i deszyfrującego w Pythonie.
Znajdziesz tutaj między innymi:
- wprowadzenie do programowania w Pythonie: pętle, zmienne, kontrola przepływu działania programu
- omówienie technik szyfrowania stosowanych przed wynalezieniem komputerów
- różne algorytmy szyfrowania z wykorzystaniem Pythona
- testowanie programów szyfrujących i deszyfrujących
- szyfrowanie i deszyfrowanie plików
- łamanie szyfrów techniką brute force czy analiza częstotliwości
A teraz stwórz algorytm szyfru idealnego!
Spis treści
O autorze 4
O korektorach merytorycznych 4
Podziękowania 17
Wprowadzenie 19
- Kto powinien przeczytać tę książkę? 20
- Co znajdziesz w tej książce? 21
- Jak używać tej książki? 23
- Wpisywanie kodu źródłowego 23
- Sprawdzanie pod kątem błędów 23
- Konwencje zastosowane w książce 24
- Zasoby w internecie 24
- Pobieranie i instalowanie Pythona 24
- Instalacja Pythona w systemie Windows 24
- Instalacja Pythona w systemie macOS 25
- Instalacja Pythona w systemie Ubuntu 25
- Pobieranie pliku pyperclip.py 25
- Uruchamianie środowiska IDLE 26
- Podsumowanie 27
1. Papier jako narzędzie kryptograficzne 29
- Co to jest kryptografia? 30
- Kod a szyfr 30
- Szyfr Cezara 32
- Krążek szyfrowania 32
- Szyfrowanie wiadomości za pomocą krążka szyfrowania 33
- Deszyfrowanie za pomocą krążka szyfrowania 34
- Szyfrowanie i deszyfrowanie z użyciem arytmetyki 35
- Dlaczego podwójne szyfrowanie nie działa? 36
- Podsumowanie 36
2. Programowanie w powłoce interaktywnej 39
- Kilka prostych wyrażeń matematycznych 40
- Wartości całkowite i wartości zmiennoprzecinkowe 41
- Wyrażenia 41
- Kolejność wykonywania działań 42
- Obliczanie wartości wyrażeń 42
- Przechowywanie wartości w zmiennych 43
- Nadpisywanie zmiennej 45
- Nazwy zmiennych 46
- Podsumowanie 47
3. Ciągi tekstowe i tworzenie programów 49
- Praca z tekstem przy użyciu wartości w postaci ciągu tekstowego 50
- Konkatenacja ciągu tekstowego za pomocą operatora + 51
- Replikacja ciągu tekstowego przy użyciu operatora * 52
- Pobieranie znaków z ciągu tekstowego przy użyciu indeksów 53
- Wyświetlanie wartości za pomocą funkcji print() 56
- Wyświetlanie znaków sterujących 57
- Apostrof i cudzysłów 58
- Tworzenie programów w edytorze pliku IDLE 59
- Kod źródłowy programu typu Witaj, świecie! 59
- Sprawdzanie kodu źródłowego za pomocą narzędzia Online Diff Tool 61
- Użycie środowiska IDLE w celu późniejszego uzyskania dostępu do programu 62
- Zapisywanie programu 62
- Uruchamianie programu 63
- Otwieranie wcześniej zapisanych programów 64
- W jaki sposób działa program Witaj, świecie!? 64
- Komentarze 64
- Wyświetlanie wskazówek dla użytkownika 65
- Pobieranie danych wejściowych od użytkownika 65
- Zakończenie programu 66
- Podsumowanie 66
4. Szyfr odwrotny 69
- Kod źródłowy programu wykorzystującego szyfr odwrotny 70
- Przykładowe uruchomienie programu 70
- Definiowanie komentarzy i zmiennych 71
- Określanie długości ciągu tekstowego 72
- Wprowadzenie do pętli while 73
- Boolowski typ danych 73
- Operatory porównania 74
- Blok kodu 76
- Konstrukcja pętli while 77
- "Rośnięcie" ciągu tekstowego 78
- Usprawnianie programu za pomocą funkcji input() 81
- Podsumowanie 82
5. Szyfr Cezara 85
- Kod źródłowy programu wykorzystującego szyfr Cezara 86
- Przykładowe uruchomienie programu 87
- Importowanie modułu i przypisywanie zmiennych 88
- Stałe i zmienne 89
- Pętla for 90
- Przykład pętli for 90
- Pętla while będąca odpowiednikiem pętli for 91
- Konstrukcja if 92
- Przykład użycia polecenia if 92
- Polecenie else 92
- Polecenie elif 93
- Operatory in i not in 94
- Metoda find() 95
- Szyfrowanie i deszyfrowanie symboli 96
- Obsługa zawinięcia 97
- Obsługa symboli spoza zbioru symboli 98
- Wyświetlanie i kopiowanie skonwertowanego ciągu tekstowego 98
- Szyfrowanie innych symboli 99
- Podsumowanie 100
6. Łamanie szyfru Cezara za pomocą ataku brute force 103
- Kod źródłowy programu wykorzystującego szyfr odwrotny 104
- Przykładowe uruchomienie programu 105
- Definiowanie zmiennych 106
- Iteracja z użyciem funkcji range() 106
- Deszyfrowanie wiadomości 108
- Stosowanie formatowania ciągu tekstowego do wyświetlenia klucza i deszyfrowanej wiadomości 109
- Podsumowanie 110
7. Szyfrowanie za pomocą szyfru przestawieniowego 113
- Sposób działania szyfru przestawieniowego 113
- Ręczne szyfrowanie wiadomości 114
- Tworzenie programu szyfrującego 116
- Kod źródłowy programu wykorzystującego szyfr kolumnowy 117
- Przykładowe uruchomienie programu 118
- Samodzielne definiowanie funkcji za pomocą polecenia def 118
- Definiowanie funkcji pobierającej argumenty 119
- Zmiana parametru istniejącego tylko wewnątrz funkcji 120
- Definiowanie funkcji main() 121
- Przekazywanie klucza i wiadomości jako argumentów 122
- Typ danych listy 123
- Ponowne przypisywanie elementów na liście 124
- Lista list 125
- Stosowanie funkcji len() i operatora in z listą 126
- Konkatenacja listy i replikacja za pomocą operatorów + i * 127
- Algorytm szyfrowania przestawieniowego 127
- Rozszerzone operatory przypisania 128
- Iteracja currentIndex przez wiadomość 129
- Metoda join() 131
- Wartość zwrotna i polecenie return 132
- Przykład polecenia return 132
- Zwrot szyfrogramu 133
- Zmienna __name__ 133
- Podsumowanie 134
8. Deszyfrowanie wiadomości chronionej szyfrem przestawieniowym 137
- Łamanie szyfru przestawieniowego za pomocą kartki i ołówka 138
- Kod źródłowy programu deszyfrującego wiadomość chronioną szyfrem przestawieniowym 139
- Przykładowe uruchomienie programu 141
- Importowanie modułów i definiowanie funkcji main() 141
- Deszyfrowanie wiadomości za pomocą klucza 142
- Funkcje round(), math.ceil() i math.floor() 142
- Funkcja decryptMessage() 143
- Operatory boolowskie 145
- Dostosowywanie wartości zmiennych column i row 148
- Wywoływanie funkcji main() 150
- Podsumowanie 150
9. Tworzenie programu do testowania innych programów 153
- Kod źródłowy programu do testowania innych programów 154
- Przykładowe uruchomienie programu 155
- Importowanie modułów 156
- Generowanie liczb pseudolosowych 156
- Tworzenie losowo wybranego ciągu tekstowego 158
- Powielanie ciągu tekstowego losowo wybraną liczbę razy 158
- Zmienna listy używa odwołania 159
- Przekazywanie odwołania 162
- Stosowanie funkcji copy.deepcopy() do powielenia listy 162
- Funkcja random.shuffle() 163
- Losowe mieszanie ciągu tekstowego 163
- Testowanie poszczególnych wiadomości 164
- Sprawdzanie poprawności szyfrowania i zakończenie programu 165
- Wywoływanie funkcji main() 166
- Testowanie programu 166
- Podsumowanie 167
10. Szyfrowanie i deszyfrowanie plików 169
- Pliki zwykłego tekstu 170
- Kod źródłowy programu wykorzystującego szyfr przestawieniowy do szyfrowania pliku 170
- Przykładowe uruchomienie programu 171
- Praca z plikami 172
- Otwieranie pliku 172
- Zapisywanie i zamykanie pliku 173
- Odczyt danych z pliku 174
- Funkcja main() programu 175
- Sprawdzanie istnienia pliku 175
- Funkcja os.path.exists() 176
- Sprawdzanie za pomocą funkcji os.path.exists() istnienia pliku danych wejściowych 176
- Stosowanie metod ciągu tekstowego do zapewnienia większej elastyczności danych wejściowych 177
- Metody ciągu tekstowego upper(), lower() i title() 177
- Metody ciągu tekstowego startswith() i endswith() 177
- Stosowanie metod ciągu tekstowego w programie 178
- Odczyt pliku danych wejściowych 179
- Pomiar czasu operacji szyfrowania i deszyfrowania 179
- Moduł time i funkcja time.time() 179
- Stosowanie funkcji time.time() w programie 180
- Zapis danych wyjściowych do pliku 181
- Wywoływanie funkcji main() 181
- Podsumowanie 182
11. Programowe wykrywanie języka angielskiego 183
- Jak komputer może zrozumieć język angielski? 184
- Kod źródłowy modułu do wykrywania języka angielskiego 186
- Przykładowe uruchomienie programu 187
- Polecenia i definiowanie stałych 187
- Typ danych w postaci słownika 188
- Różnice między słownikiem i listą 189
- Dodawanie lub modyfikowanie elementów słownika 190
- Stosowanie funkcji len() ze słownikiem 191
- Stosowanie operatora in ze słownikiem 191
- Wyszukiwanie elementów w słowniku odbywa się szybciej niż na liście 192
- Stosowanie pętli for w słowniku 192
- Implementacja pliku słownika 193
- Metoda split() 193
- Podział słownika na poszczególne słowa 194
- Zwrot danych słownika 194
- Zliczanie liczby słów angielskich w wiadomości 195
- Błąd dzielenia przez zero 196
- Zliczanie dopasowań słów w języku angielskim 196
- Funkcje float(), int() i str() oraz dzielenie całkowite 197
- Określanie proporcji angielskich słów w wiadomości 198
- Usuwanie znaków innych niż litery 198
- Metoda append() typu listy 199
- Tworzenie ciągu tekstowego liter 200
- Wykrywanie słów angielskich 200
- Stosowanie argumentów domyślnych 200
- Obliczanie wartości procentowych 201
- Podsumowanie 203
12. Łamanie szyfru przestawieniowego 205
- Kod źródłowy programu umożliwiającego złamanie szyfru przestawieniowego 206
- Przykładowe uruchomienie programu 207
- Importowanie modułów 208
- Wielowierszowy ciąg tekstowy ujęty w potrójny cudzysłów 208
- Wyświetlanie wyniku deszyfrowania wiadomości 209
- Pobranie deszyfrowanej wiadomości 210
- Metoda strip() ciągu tekstowego 212
- Stosowanie metody strip() ciągu tekstowego 213
- Nieudana próba deszyfrowania wiadomości 213
- Wywoływanie funkcji main() 214
- Podsumowanie 214
13. Moduł arytmetyki modularnej dla szyfru afinicznego 215
- Arytmetyka modularna 216
- Operator reszty z dzielenia 217
- Wyszukiwanie dzielników do obliczenia największego wspólnego dzielnika 218
- Przypisanie wielokrotne 220
- Algorytm Euklidesa do wyszukiwania największego wspólnego dzielnika 221
- Sposób działania szyfrów multiplikatywnego i afinicznego 222
- Wybór poprawnego klucza multiplikatywnego 223
- Szyfrowanie z użyciem szyfru afinicznego 224
- Deszyfrowanie szyfru afinicznego 225
- Określanie odwrotności modularnej 226
- Operator dzielenia całkowitego 226
- Kod źródłowy modułu cryptomath 227
- Podsumowanie 228
14. Programowanie szyfru afinicznego 231
- Kod źródłowy programu wykorzystującego szyfr afiniczny 232
- Przykładowe uruchomienie programu 233
- Importowanie modułów i stałych oraz definiowanie funkcji main() 234
- Generowanie i weryfikowanie kluczy 236
- Typ danych w postaci krotki 236
- Sprawdzanie pod kątem słabych kluczy 237
- Ile kluczy może mieć szyfr afiniczny? 238
- Tworzenie funkcji szyfrującej 240
- Tworzenie funkcji deszyfrującej 241
- Generowanie losowych kluczy 242
- Wywoływanie funkcji main() 243
- Podsumowanie 244
15. Łamanie szyfru afinicznego 245
- Kod źródłowy programu umożliwiającego złamanie szyfru afinicznego 245
- Przykładowe uruchomienie programu 247
- Importowanie modułów i stałych oraz definiowanie funkcji main() 248
- Funkcja odpowiedzialna za złamanie szyfru afinicznego 249
- Operator wykładniczy 249
- Obliczanie całkowitej liczby kluczy, których można użyć 250
- Polecenie continue 251
- Stosowanie polecenia continue do pominięcia kodu 252
- Wywoływanie funkcji main() 253
- Podsumowanie 254
16. Programowanie prostego szyfru podstawieniowego 255
- Jak działa prosty szyfr podstawieniowy? 256
- Kod źródłowy programu wykorzystującego szyfr podstawieniowy 257
- Przykładowe uruchomienie programu 259
- Importowanie modułów i stałych oraz definiowanie funkcji main() 259
- Metoda sort() listy 261
- Funkcje opakowujące 262
- Funkcja translateMessage() 263
- Metody isupper() i islower() ciągu tekstowego 265
- Zachowywanie wielkości liter dzięki metodzie isupper() 266
- Generowanie losowego klucza 267
- Wywoływanie funkcji main() 268
- Podsumowanie 268
17. Łamanie prostego szyfru podstawieniowego 271
- Stosowanie wzorca słowa do deszyfrowania 272
- Znajdowanie wzorca słowa 272
- Wyszukiwanie potencjalnych liter odszyfrowujących 273
- Omówienie procesu łamania szyfru 275
- Moduł wzorca słowa 275
- Kod źródłowy programu wykorzystującego szyfr podstawieniowy 276
- Przykładowe uruchomienie programu 280
- Importowanie modułów i stałych 280
- Wyszukiwanie znaków za pomocą wyrażeń regularnych 281
- Konfigurowanie funkcji main() 281
- Wyświetlanie użytkownikowi wyniku operacji łamania szyfru 282
- Tworzenie mapowania szyfrogramu 283
- Tworzenie pustego mapowania 283
- Dodawanie liter do mapowania 283
- Łączenie dwóch mapowań 285
- W jaki sposób działają funkcje pomocnicze mapowania liter? 286
- Wyszukiwanie zdeszyfrowanych liter w mapowaniu 290
- Testowanie funkcji removeSolvedLettersFromMapping() 292
- Funkcja hackSimpleSub() 292
- Metoda replace() ciągu tekstowego 294
- Deszyfrowanie wiadomości 295
- Deszyfrowanie w powłoce interaktywnej 296
- Wywoływanie funkcji main() 297
- Podsumowanie 298
18. Programowanie szyfru Vigenere'a 299
- Stosowanie wielu liter kluczy w szyfrze Vigenere'a 300
- Dłuższe klucze szyfru Vigenere'a są znacznie bezpieczniejsze 302
- Wybór klucza uniemożliwiającego atak słownikowy 303
- Kod źródłowy programu wykorzystującego szyfr Vigenere'a 303
- Przykładowe uruchomienie programu 305
- Importowanie modułów i stałych oraz definiowanie funkcji main() 305
- Tworzenie ciągu tekstowego za pomocą procesu dołączania do listy 306
- Szyfrowanie i deszyfrowanie wiadomości 307
- Wywoływanie funkcji main() 310
- Podsumowanie 310
19. Analiza częstotliwości 313
- Analiza częstotliwości występowania liter w tekście 314
- Dopasowywanie częstotliwości występowania liter 316
- Obliczanie wyniku dopasowania częstotliwości dla prostego szyfru podstawieniowego 316
- Obliczanie wyniku dopasowania częstotliwości dla prostego szyfru przestawieniowego 317
- Stosowanie analizy częstotliwości do złamania szyfru Vigenere'a 318
- Kod źródłowy programu obliczającego wynik dopasowania częstotliwości 319
- Przechowywanie liter w kolejności ETAOIN 321
- Zliczanie liter w wiadomości 321
- Pobieranie pierwszego elementu składowego krotki 323
- Układanie liter według częstotliwości ich występowania w wiadomości 323
- Zliczanie liter za pomocą funkcji getLetterCount() 324
- Tworzenie słownika częstotliwości wystąpień i listy liter 324
- Sortowanie liter w odwrotnej kolejności ETAOIN 325
- Sortowanie list słownika według częstotliwości występowania 330
- Tworzenie listy sortowanych liter 332
- Obliczanie wyniku dopasowania częstotliwości dla wiadomości 332
- Podsumowanie 334
20. Łamanie szyfru Vigenere'a 335
- Atak słownikowy w celu złamania szyfru Vigenere'a metodą brute force 336
- Kod źródłowy programu umożliwiającego złamanie szyfru Vigenere'a za pomocą ataku słownikowego 336
- Przykładowe uruchomienie programu 337
- Informacje o programie do łamania szyfru Vigenere'a za pomocą ataku słownikowego 337
- Stosowanie metody Kasiskiego do ustalenia długości klucza 338
- Odszukanie powtarzających się sekwencji 338
- Pobieranie dzielników liczb określających odstępy 339
- Pobieranie każdej n-tej litery ciągu tekstowego 341
- Stosowanie analizy częstotliwości do złamania poszczególnych podkluczy 342
- Przeprowadzanie ataku brute force na możliwe klucze 344
- Kod źródłowy programu umożliwiającego złamanie szyfru Vigenere'a 344
- Przykładowe uruchomienie programu 349
- Importowanie modułów i definiowanie funkcji main() 350
- Wyszukiwanie powtarzających się sekwencji 351
- Obliczanie dzielników odstępów 354
- Usuwanie duplikatów za pomocą funkcji set() 355
- Usuwanie powtarzających się dzielników i sortowanie listy 355
- Wyszukiwanie najczęściej występujących dzielników 356
- Określanie prawdopodobnej długości klucza 358
- Metoda listy extend() 358
- Rozszerzanie słownika repeatedSeqSpacings 359
- Pobieranie dzielników z factorsByCount 360
- Pobieranie liter szyfrowanych za pomocą tego samego podklucza 360
- Próba deszyfrowania z użyciem potencjalnych długości klucza 362
- Argument w postaci słowa kluczowego key funkcji print() 364
- Uruchamianie programu w trybie cichym lub wyświetlania informacji użytkownikowi 365
- Wyszukiwanie możliwych kombinacji podkluczy 365
- Wyświetlanie deszyfrowanego tekstu z użyciem właściwej wielkości liter 369
- Zwrot deszyfrowanej wiadomości 370
- Opuszczanie pętli po znalezieniu potencjalnego klucza 371
- Atak brute force na wszystkie długości klucza 371
- Wywoływanie funkcji main() 372
- Modyfikowanie stałych programu 373
- Podsumowanie 373
21. Szyfr z kluczem jednorazowym 375
- Niemożliwy do złamania szyfr z kluczem jednorazowym 376
- Tworzenie klucza o długości odpowiadającej długości wiadomości 376
- Zapewnianie prawdziwej losowości klucza 378
- Dlaczego klucza jednorazowego można użyć tylko raz? 379
- Dlaczego dwukrotnie użyty klucz jednorazowy to szyfr Vigenere'a? 379
- Podsumowanie 380
22. Wyszukiwanie i generowanie liczb pierwszych 381
- Co to jest liczba pierwsza? 382
- Kod źródłowy modułu liczb pierwszych 384
- Przykładowe uruchomienie modułu 386
- Sposób działania algorytmu próbnego dzielenia 386
- Implementacja algorytmu próbnego dzielenia 388
- Sito Eratostenesa 389
- Generowanie liczb pierwszych za pomocą sita Eratostenesa 391
- Algorytm pierwszości Rabina-Millera 392
- Wyszukiwanie ogromnych liczb pierwszych 393
- Generowanie ogromnych liczb pierwszych 395
- Podsumowanie 395
23. Generowanie kluczy dla szyfru klucza publicznego 397
- Kryptografia klucza publicznego 398
- Problem z uwierzytelnieniem 400
- Podpis cyfrowy 400
- Uważaj na atak MITM 401
- Etapy generowania kluczy publicznego i prywatnego 402
- Kod źródłowy programu generującego klucze kryptografii klucza publicznego 403
- Przykładowe uruchomienie programu 404
- Tworzenie funkcji main() 406
- Generowanie kluczy za pomocą funkcji generateKey() 406
- Obliczanie wartości e 407
- Obliczanie wartości d 407
- Zwracanie kluczy 408
- Tworzenie plików kluczy za pomocą funkcji makeKeyFiles() 408
- Wywoływanie funkcji main() 410
- Hybrydowe systemy kryptograficzne 411
- Podsumowanie 411
24. Programowanie szyfru klucza publicznego 413
- Jak działa kryptografia klucza publicznego? 414
- Tworzenie bloku 414
- Konwersja ciągu tekstowego na blok 415
- Matematyka szyfrowania i deszyfrowania za pomocą kryptografii klucza publicznego 416
- Konwersja bloku na ciąg tekstowy 418
- Dlaczego nie można złamać szyfru wykorzystującego kryptografię klucza publicznego? 420
- Kod źródłowy programu wykorzystującego kryptografię klucza publicznego 421
- Przykładowe uruchomienie programu 425
- Konfiguracja programu 426
- Wybór trybu pracy programu 426
- Konwersja ciągu tekstowego na bloki za pomocą funkcji getBlocksFromText() 428
- Funkcje min() i max() 428
- Przechowywanie bloków w blockInt 429
- Stosowanie funkcji getTextFromBlocks() do deszyfrowania wiadomości 431
- Stosowanie metody insert() listy 432
- Łączenie listy message i tworzenie na jej podstawie jednego ciągu tekstowego 432
- Tworzenie funkcji encryptMessage() 433
- Tworzenie funkcji decryptMessage() 433
- Odczytywanie kluczy publicznego i prywatnego z ich plików 434
- Zapisywanie szyfrogramu do pliku 435
- Deszyfrowanie danych z pliku 437
- Wywoływanie funkcji main() 439
- Podsumowanie 439
A. Debugowanie kodu Pythona 441
- Na czym polega działanie debugera? 441
- Usuwanie błędów z programu wykorzystującego szyfr odwrotny 443
- Definiowanie punktu przerwania 445
- Podsumowanie 447
B. Odpowiedzi do ćwiczeń 449
- Rozdział 1. 449
- Rozdział 2. 450
- Rozdział 3. 451
- Rozdział 4. 452
- Rozdział 5. 453
- Rozdział 6. 454
- Rozdział 7. 455
- Rozdział 8. 457
- Rozdział 9. 459
- Rozdział 10. 459
- Rozdział 11. 460
- Rozdział 12. 461
- Rozdział 13. 462
- Rozdział 14. 462
- Rozdział 15. 463
- Rozdział 16. 463
- Rozdział 17. 464
- Rozdział 18. 464
- Rozdział 19. 465
- Rozdział 20. 465
- Rozdział 21. 466
- Rozdział 22. 466
- Rozdział 23. 466
Kategoria: | Hacking |
Zabezpieczenie: |
Watermark
|
ISBN: | 978-83-283-7496-6 |
Rozmiar pliku: | 4,8 MB |