Złożoność ataku meet in the middle

Złożoność ataku meet in the middle
PA
  • Rejestracja:około 6 lat
  • Ostatnio:dzień
  • Postów:31
1

Witam,
Zastanawiam się w jaki sposób można osiągnąć naraz pesymistyczną złożność obliczeniową 2*2^56 i pesymistyczną złożność pamięciową 2^56 w przypadku ataku meet in the middle na dane zaszyfrowane dwukrotnie algorytmem DES. Dla przykładu załóżmy, że dysponuję dwoma parami tekstów jawnych i zaszyfrowanych, które mają te same klucze (dane są podane jako liczby heksadecymalne, klucze mają ustawione bity parzystości).

Tekst jawny 1:
1111111111111111

Tekst jawny 2:
2222222222222222

Tekst zaszyfrowany 1:
54C99F28781CED9F

Tekst zaszyfrowany 2:
F0E0C4236687BE79

Klucze do odgadnięcia:
ABABABABABABABAB
CDCDCDCDCDCDCDCD

Z tego co zrozumiałem czytając ten artykuł na wikipedii: https://pl.wikipedia.org/wiki/Meet_in_the_middle to należy najpierw przygotować tablicę, w której zaszyfruję tekst jawny 1111111111111111 wszystkimi możliwymi kluczami DES. Jej początek powinien wyglądać tak:

Klucz | Tekst zaszyfrowany

  • | -
    0101010101010101 | 89B07B35A1B3F47E
  • | -
    0101010101010102 | A52E042B7956BDBB
  • | -
    0101010101010104 | C6C93339DD7070DD
  • | -
    0101010101010107 | 0150F71195A68027
  • | -
    0101010101010108 | F734FB85C3B62799
  • | -
    010101010101010B | 53E9B7DC7D06E348
  • | -
    ... | ...
    Ilość wierszy wynosi 2^56.

Następnie próbuję odszyfrować tekst zaszyfrowany 54C99F28781CED9F po kolei każdym możliwym kluczem DES i wyszukać otrzymany (potencjalnie nieprawidłowy) tekst jawny w tablicy z poprzedniego kroku.

Próba #1: klucz 0101010101010101 daje 84CF6B79C6683406, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się niepowieść albo na etapie wyszukiwania, albo na etapie szyfrowania drugiego tekstu jawnego)
Próba #2: klucz 0101010101010102 daje 3295F05FC26482B6, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się niepowieść albo na etapie wyszukiwania, albo na etapie szyfrowania drugiego tekstu jawnego)
Próba #3: klucz 0101010101010104 daje D90DAB5658129502, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się niepowieść albo na etapie wyszukiwania, albo na etapie szyfrowania drugiego tekstu jawnego)
Próba #4: klucz 0101010101010107 daje FB06F0BE85BD8A6D, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się niepowieść albo na etapie wyszukiwania, albo na etapie szyfrowania drugiego tekstu jawnego)
Próba #5: klucz 0101010101010108 daje 585BFE568AA4A87F, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się niepowieść albo na etapie wyszukiwania, albo na etapie szyfrowania drugiego tekstu jawnego)
Próba #6: klucz 010101010101010B daje 244140483E4437FB, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się niepowieść albo na etapie wyszukiwania, albo na etapie szyfrowania drugiego tekstu jawnego)
...
Próba #57873028282430311: klucz CDCDCDCDCDCDCDCD daje A202EE26F8A71460, wyszukuję go w tablicy z poprzedniego kroku, jeżeli znalazłem ten tekst to sprawdzam poprawność 2 kluczy szyfrując drugi tekst jawny (powinno się udać zarówno na etapie wyszukiwania, jak i na etapie szyfrowania drugiego tekstu jawnego)

I teraz zasadnicze pytanie: żeby szybko wyszukać tekst w tablicy, to mogę ją najpierw posortować i potem użyć wyszukiwania binarnego, ale wtedy złożoność obliczeniowa dla długości klucza n bitów wyniesie 2^nlog(2^n)=n2^n, złożoność pamięciowa wyniesie c2^56, gdzie c jest stałą wynoszącą 56+64=120 bitów, ale ta stała nie jest istotna. Dowiedziałem się, że można użyć tablicy z haszowaniem, żeby złożność czasowa wyniosła 22^56, ale czy wtedy złożność pamięciowa nadal będzie wynosić 2^56? Przez złożoność pamięciową mam na myśli, że dodatkowa ilość danych potrzebna do stworzenia tej struktury danych (względem prostej tablicy z kluczami i tekstami zaszyfrowanymi) jest stała i niezależna od długości klucza i może wynieść np. 1000000 bitów, ale jakbym użył innego algorytmu zamiast DES (np. IDEA o tej samej długości bloku, ale 128 bitowej długości klucza) to ta ilość nadal wynosiłaby 1000000 bitów. Jak można zrealizować taką tablicę z haszowaniem w pamięci?

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

ale czy wtedy złożność pamięciowa nadal będzie wynosić 2^56

Tak, bo hashmapa nie ma specjalnie wielkiego narzutu. W najbardziej trywialnej opcji to jest goła tablica.

Jak można zrealizować taką tablicę z haszowaniem w pamięci?

Najprościej? Jako tablicę! Trik hashmapy polega na tym, że indeks w tablicy jest wyliczany na podstawie funkcji hashujacej klucz. Tzn masz pewną funkcje h(x) która dla klucza zwraca indeks w tablicy są dane. Wtedy wyciagniecie danych z hashmapy to po prostu tablica[h(klucz)]. Oczywiście jest tu pewien problem, którym są konflikty, tzn trudno zrobić taką funkcje h która nie generuje nigdy konfliktów (dla dwóch kluczy zwróci to samo).

Jeśli szukasz implementacji łatwiejszej, to wygodniej zrobić tablicę list, tzn każdy element tablicy jest listą która przechowuje wszystkie wartości dla których funkcja h dała taki sam indeks.

Oczywiście praktycznie każdy język programowania ma wbudowane takie podstawowe struktury danych i nie trzeba ich pisać od zera.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
PA
  • Rejestracja:około 6 lat
  • Ostatnio:dzień
  • Postów:31
0

Ok, to piszę to, co zrozumiałem. W przypadku ataku meet in the middle gdzie szukam K1 i K2 z tego równania: C = E(E(P, K1), K2) gdzie C to znany tekst zaszyfrowany, E to funkcja szyfrująca DES, P to znany tekst jawny, a K1 i K2 to szukane klucze; przekształcam to równanie do takiej postaci: D(C, K2) = E(P, K1) teraz D to funkcja deszyfrująca DES. Następnie w pętli po wszystkich możliwych 56-bitowych kluczach (oznaczanych jako k_ i czyli i-ty klucz) wstawiam do tablicy z haszowaniem następującą wartość (format: {klucz_tablicy => wartość}) {E(P, k_ i) => k_ i}. Później w następnej pętli po wszystkich możliwych 56-bitowych kluczach (oznaczanych jako k_ j czyli j-ty klucz) obliczam D(C, k_ j) i odczytuję wartość k_ i z tab[h(D(C, K_ j))] (tutaj mam 1. problem: D(C, K_ j) może nie być równe żadnemu z E(P, k_ i) z poprzedniego kroku, wtedy powinnienem przejść do następnego klucza i nie kontynuować tej iteracji); mając k_ j i k_ i sprawdzam dodatkowy blok teksu jawnego (P') i zaszyfrowanego (C'): C' = E(E(P, k_ i), k_ j), jeżeli to równanie jest prawidłowe to kończę całość i zwracam klucze K1 = k_ i i K2 = k_ j. I teraz 2. problem mam taki: w jaki sposób skonstruować funkcję h? I czy da to się zrobić w locie w czasie lepszym niż n * log(n) gdzie n = 2^56 przy zachowaniu złożoności pamięciowej n ? (Taką złożoność dla całego algorytmu mogę osiągnąć np. przez heapsort, wtedy złożoność obliczeniowa całego procesu łamania podwójnego DES to n*log(n) a pamięciowa to n; a według wikipedii mogę mieć naraz czasową n i pamięciową n)

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1
  1. Nie do końca rozumiem twoją notacje, więc napiszmy to zwyczajnie kodem:
Kopiuj
def cpu_goes_brrrr(plaintext, ciphertext):
    mapa = {}
    for key in possible_keys():
        mapa[encrypt(plaintext,key)] = key

    for key in possible_keys():
        d = decrypt(ciphertext,key)
        if d in mapa:
            return key, mapa[d],

I voila, cały meet i the middle zrobiony. Czyli w mapie trzymasz sobie jako klucz wynik jednego deszyfrowania a jako wartość użyty klucz. Następnie w pętli dla wszystkich kluczy sprawdzasz czy zaszyfrowanie danych dało nam coś co mamy w mapie. Nie wiem co miałeś na myśli pisząc

wtedy powinnienem przejść do następnego klucza i nie kontynuować tej iteracji

w jaki sposób skonstruować funkcję h

Możesz po prostu zdać się na twórców języka w którym piszesz ;) Zwykle samo liczenie hasha jest zależne jedynie od rozmiaru wartości dla której tego hasha liczysz, a w twoim przypadku rozmiar bloku jest stały i bardzo mały. Trywialny przykład hasha ze stringa to np. potraktować bity tego stringa jako jednego długiego inta a potem zrobić modulo rozmiar tablicy i tyle. Czyli np. masz stringa AA czyli 0100000101000001 i jak potraktujesz to na pałe jako inta to masz 16705 i voila, mamy hasha.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
PA
  • Rejestracja:około 6 lat
  • Ostatnio:dzień
  • Postów:31
0

Dzięki za odpowiedź.
To pytanie było tylko teoretyczne, bo jakiś czas temu zacząłem sobie czytać o kryptografii i ten typ ataku na podwójne szyfrowanie danych tym samym algorytmem mnie po prostu zainteresował. I tak do praktycznego zastosowania raczej przez wiele lat nie wystarczy mi pamięci w komputerze :)

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

I tak do praktycznego zastosowania raczej przez wiele lat nie wystarczy mi pamięci w komputerze

Hmm no to już zależy od konkretnego przypadku. Meet-in-the-middle to jest pewna klasa ataków, niekoniecznie musisz mieć jakieś podwójne szyfrowanie! Na dobrą sprawę szukanie kolizji przez birthday paradox to jest dokładnie ten sam mechanizm. Pepełniłem kiedyś takie zadanie na CTFa:

Kopiuj
import random

from Crypto.Util.number import getStrongPrime


def main():
    secret_bits = 48
    challenges = [generate_challenge(secret_bits) for _ in range(10)]
    solvable = [(N, ct, M, is_solvable) for (N, ct, M, is_solvable) in challenges if is_solvable]
    assert len(solvable) > 0  # it's just a hint that it's a common property
    N, ct, M, is_solvable = solvable[0]
    print("N = " + str(int(N)))
    print("ct = " + str(int(ct)))
    # print("M = " + str(int(M))) # you wish!


def generate_challenge(secret_bits):
    N, e = generate_RSA_pubkey()
    M, is_solvable = generate_secret(secret_bits)
    ct = pow(M, e, N)
    return N, ct, M, is_solvable


def generate_RSA_pubkey():
    modulus_bitsize = 1024
    e = 65537
    p = getStrongPrime(modulus_bitsize // 2)
    q = getStrongPrime(modulus_bitsize // 2)
    N = p * q
    return N, e


def generate_secret(secret_bits):
    M = random.randint(2 ** (secret_bits - 1), 2 ** secret_bits - 1)
    import solver
    return M, solver.is_solvable(M, secret_bits)


main()

output:

Kopiuj
N = 153925444825266405404296016820285889603681378402101336589329464961842012940371297301469649536111734999742714528709986215141965333810224830951469172716482545603364835583008550352222409796386064187919756548219280351127895602093980834522234748558225565031789941976740702306994823666953834995809100560973279991349
ct = 49258828688785826524053919049564557666797485380495142303571636718918855493745914425713948834302673401076375934795749020812927013205536426034127582877462948222553013533585166583151597238498630324268224723834865042941120996943852347638817146251845274398870427359031601825221080729264001112118956450061653065411

Mozesz spróbować się z nim zmierzyć ;) Oczywiście fakt ze już wiesz że chodzi o jakieś meet-in-the-middle ułatwia mocno sprawę...


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
PA
Odpowiedź to m = 69316392688068 :) , trzeba było rozbić: m = a * b (a i b mają max. długość 24 bity) i policzyć wartość ((a ^ -e) mod n * ct) mod n w pętli do a = 2^24, a następnie policzyć b ^ e mod n od 2^23 w górę i sprawdzać czy ta wartość istnieje w tablicy i jeżeli tak to zwrócić b * znalezione a
Shalom
Potwierdzam ;) Jak widać nie trzeba było żadnego podwójnego szyfrowania żeby zrobić meet-in-the-middle ;)
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)