Libsodium – kryptografia w łatwiejszym wydaniu

Już kilka razy potrzebowałem użyć kryptografii do jakiegoś zadania. Zwykle przychodził z “pomocą” potworek o nazwie openssl, za pomocą którego można było wyczarować cuda. Problemem była jedynie składnia, którą trudno zapamiętać, bo “potworek” jest szyty na miarę profesjonalnych zastosowań.

API “potworka” jest jeszcze bardziej pokręcone i wymaga ogromnej wiedzy, już na sam początek, co dla niewprawionych w bojach z kryptografią może być i tak bardzo dużym wyzwaniem.

Niedawno, szukając jakiejś alternatywy dla wołania zewnętrznego polecenia openssl albo gpg, dla projektu libdinemic udało mi się znaleźć bibliotekę Sodium, która jest następcą innego, ponoć podobnego narzędzia NaCl. Biblioteka ta pozwala w bardzo przyjemny sposób ogarnąć podstawowe zadania związane z kryptografią:

  • Podpisy cyfrowe – generujące kawałek danych potwierdzający, że właściciel prywatnego klucza coś napisał
  • Szyfrowanie symetryczne – pozwalające zamienić ciąg ważnych dla nas danych w (prawie) bezużyteczne śmieci, możliwe do odszyfrowania jedynie po podaniu hasła
  • Szyfrowanie asymetryczne – pozwalające zaszyfrowanie komuś danych posiadając jedynie jego publiczny, znany wszystkim klucz. Dzięki temu tylko ta osoba ta może odszyfrować, korzystając z prywatnej części klucza
  • Funkcje skrótu SHA2
  • … i kilka innych fajnych narzędzi związanych z bezpieczeństwem

Jak się za to zabrać?

Będziesz potrzebował samą bibliotekę, jej pliki nagłówkowe, kompilator i edytor tekstu:

sudo apt-get install libsodium-dev gcc gedit

Aby skompilować coś, co zachwile stworzysz, dodaj opcję -lsodium do kompilatora:

gcc -o program -lsodium program.c

…lub skorzystaj z gotowego narzędzia typu make, qmake albo cmake.

Klucz prywatny i publiczny – po co to

Zanim przejdziemy do kolejnych kroków – kilka zdań o kryptografii asymetrycznej. Jak źle by to nie było omówione na studiach, warto wiedzieć co ona daje i wierzyć, że jeszcze nikt z NSA nie złamał jej zabezpieczeń (albo i nie wierzyć). W dużym uproszczeniu, kryptografia asymetryczna bazuje na parach kluczy (liczb), z których jedna, prywatna, to zwykle duża losowa liczba. W niektórych algorytmach musi to być liczba pierwsza. Druga liczba, publiczna jest obliczana na podstawie tej pierwszej, prywatnej.

Generowanie drugiej liczny jest wykonywane w taki sposób, aby operacja odwrotna była bardzo żmudna i długotrwała. Dobrym porównaniem jest mieszanie farbek. Jeśli znasz proporcje (Twój klucz prywatny), to możesz szybko wymieszać podstawowe kolory i otrzymać identyfikujący Cię kolor. Każda wiadomość pomaziana Twoim kolorem będzie automatycznie oznaczona jako nadana przez Ciebie, bo tylko Ty potrafisz zrobić taki kolor. Jeśli natomiast ktoś chciałby podrobić Twój kolor, to musiałby próbować różnych zestawień barw, co trwało by znacznie dłużej. Podobnie miało to miejsce z tajną recepturą KFC. Wszyscy próbowali ją podrabiać, ale do czasu jej wycieku oryginalny smak, bazujący na proporcjach kilkunastu przypraw pozostawał tylko w KFC.

O ile kolory czy proporcje przypraw można by próbować dość łatwo odtworzyć, bo nie ma aż tylu możliwości, to obliczenie prywatnego klucza kryptograficznego (proporcji przypraw) na podstawie klucza publicznego (smaku) może trwać zwykle latami na pojedynczym komputerze. Dla tego jest to uznawane za bezpieczny środek komunikacji, a dodatkowo daje możliwość udostępnienia wszystkim czegoś (smaku, koloru, publicznego klucza), który może zweryfikować czy to my lub umożliwić zaszyfrowanie nam wiadomości, którą odczytamy tylko my.

Podpisy cyfrowe

Pierwszym zadaniem z libsodium było dla mnie zakodowanie modułu umożliwiającego generowanie podpisów cyfrowych w libdinemic. Miało to uwierzytelnić każdą zmianę wykonywaną w rozproszonej bazie dla wielu hostów, bez korzystania z blokad i tym podobnych mechanizmów. W zamian miały być wykorzystane właśnie podpisy cyfrowe – tylko osoba posiadająca klucz prywatny przypisany do obiektu w bazie może go zaktualizować.

Jak wygenerować taki podpis za pomocą libsodium? Potrzebujemy wiadomość (update stanu bazy) oraz parę kluczy, prywatny i publiczny. Klucze można wygenerować jedną funkcją:

#include 
...

unsigned char sign_secret_key[crypto_sign_SECRETKEYBYTES];
unsigned char sign_public_key[crypto_sign_PUBLICKEYBYTES];

crypto_sign_keypair(sign_public_key, sign_secret_key);

crypto_sign_keypair zapisze do podanych zmiennych klucz prywatny (losowy ciąg danych) i odpowiadający mu klucz publiczny. Libsodium używa do tego algorytmów opartych o krzywe eliptyczne, przez co klucze są znacznie krótsze od tych znanych z RSA. Mając dwa klucze możemy wygenerować wiadomość i podpisać ją cyfrowo naszym prywatnym kluczem:

unsigned char sig[crypto_sign_BYTES];
unsigned char message[] = "Ala ma kota a kot ma Ale";

crypto_sign_detached(sig,
                     NULL,
                     message,
                     strlen(message),
                     sign_secret_key);

Podpis cyfrowy będzie zapisany do zmiennej sig, która ma stałą, określoną przez crypto_sign_BYTES długość.

W ten sposób możemy rozpowszechnić nasz publiczny klucz, za pomocą którego inne osoby lub programy będą mogły potwierdzić autentyczność naszej wiadomości. Aby zweryfikować podpisany ciąg znaków wystarczy sprawdzić:

char sig[crypto_sign_BYTES];
unsigned char *message;

sig = ... //pobieramy podpis
message = ... //pobieramy wiadomość

if (crypto_sign_verify_detached(sig,
                                message,
                                strlen(message),
                                sign_public_key) != 0) {
    // To jest fałszywy podpis!
} else {
    // Podpis się zgadza
}

Szyfrowanie danych

Szyfrowanie danych z libsodium wygląda podobnie, aczkolwiek wymaga użycia innego zestawu funkcji i algorytmów. Po pierwsze musimy wygenerować inny zestaw kluczy za pomocą innej funkcji – crpto_box_keypair:

unsigned char encryption_secret_key[crypto_box_SECRETKEYBYTES];
unsigned char encryption_public_key[crypto_box_PUBLICKEYBYTES];

crypto_box_keypair(encryption_public_key, encryption_secret_key);

Następnie możemy rozpowszechnić publiczny klucz. Szyfrowanie działa trochę inaczej – tutaj do wygenerowania danych nadawcy potrzebny jest publiczny klucz. Dzięki temu tylko odbiorca może odszyfrować swoją wiadomość. Odwrotnie było w przypadku podpisów – tylko nadawca mógł się podpisać pod wiadomością, a zweryfikować mógł ktokolwiek, używając jego klucza publicznego.

Do zaszyfrowania wiadomości będziemy potrzebowali dodatkowego obszaru pamięci, do którego libsodium zapisze zaszyfrowane dane:

unsigned char message[] = "Ala ma kota a kot nie ma Ali";
unsigned char *encrypted_message = new unsigned char[strlen(message) + crypto_box_SEALBYTES];

crypto_box_seal(encrypted_message,
                message,
                strlen(message),
                encryption_public_key);
// Wysylamy wiadomosc w swiat

Odbiorca wiadomości może ją odszyfrować posiadając swój prywatny klucz:

// Odebranie wiadomosci
...

unsigned char *decrypted_message = new unsigned char[encrypted_message_length];
int rc = crypto_box_seal_open(decrypted_message,
                              encrypted_message,
                              encrypted_message_length,
                              encryption_public_key,
                              encryption_secret_key);

if (rc != 0) {
    // nie udalo sie odszyfrowac wiadomosci
} else {
    // Mamy wiadomosc!
}

Do powyższych przykładów należy podejść czysto edukacyjnie i zwrócić uwagę zwłaszcza na funkcje strlen, które w przypadku danych binarnych mogą zwrócić niepoprawne wartości.

Leave a Reply