<jupyter_start><jupyter_text>Napredni operacijski sustavi  2. laboratorijska vježba - Digitalni potpis Zagreb, *04.05.2020.*  Izjava Tekstovi zadataka se koriste samo u edukativne svrhe, te njihova prava još uvijek pripadaju autorima. Tekstovi zadatka preuzeti su sa sljedećih poveznica: [generalno](http://www.zemris.fer.hr/predmeti/os2/), [vježba 2](http://www.zemris.fer.hr/predmeti/os2/kriptografija/digitalni_potpis.html). Također, bilo kakve izmjene su isključivo radi estetike, i ne mijenjaju intelektualnog vlasnika na mene ili bilo kog tko uređuje ovu datoteku.  Sadržaj- [Priprema](Priprema)- [Simetrična enkripcija](Simetrična-enkripcija)- [Asimetrična enkripcija](Asimetrična-enkripcija)- [Funkcije sažimanja](Funkcije-sažimanja)- [Digitalne omotnice](Digitalne-omotnice)- [Digitalni potpisi](Digitalni-potpisi)- [Digitalni pečati](Digitalni-pečati)  Priprema Kako bi uopće mogli pokrenuti ovu vježbu, potreban nam je modul za enkripciju **Cryptodome** (`pip install pycryptodome`). Koristi se **Python 3.6.10**, iako vjerojatno vježba radi na bilo kojem Pythonu verzije **3.6** ili više. Ukoliko imate problem s pokretanjem (Jupyter ne pronalazi `Crypto`), napravite ovo: ```pip3 install ipykernel --upgradepython3 -m ipykernel install --user``` Prvo ćemo učitati par modula koji će nam olakšavati stvari:<jupyter_code>from base64 import b64decode, b64encode
import json
from sys import stderr
from IPython.display import display, Markdown, Code<jupyter_output><empty_output><jupyter_text>Onda ćemo učitati 5 metoda koje smo implementirali (tj. obuhvatili klasama radi lakšeg korištenja):<jupyter_code>from symmetric import AES, DES3
from asymmetric import RSA
from hashes import SHA2, SHA3<jupyter_output><empty_output><jupyter_text>Zatim ćemo učitati sve komponente:<jupyter_code>from components import Envelope, Signature, Seal<jupyter_output><empty_output><jupyter_text>Sve primjere izvodit ćemo na poruci "Ana voli Milovana":<jupyter_code>message = "Ana voli Milovana"<jupyter_output><empty_output><jupyter_text>Simetrična enkripcija Na raspolaganju imamo dva algoritma:- AES- Triple DESDok **Triple DES** ima jednu veličinu ključa ($64$ bitova), moguće je odabrati $3$ veličine ključa za **AES**:- $128$ bita- $192$ bita- $256$ bitaTakođer, oba algoritma koriste jedan $5$ načina kriptiranja (zadani je **CBC**):- ECB- CBC- OFB- CFB- CTR  AES Možemo demonstrirati rad ovog algoritma za različite ključeve:<jupyter_code>aes_ = [AES(x) for x in (128, 192, 256)]<jupyter_output><empty_output><jupyter_text>Osim ovakve inicijalizacije, moguće je učitati i veličinu ključa u broju bajtova. Enkriptirajmo podatke koristeći svaki od ovih načina rada:<jupyter_code>aes_crypts = [x.encrypt(message) for x in aes_]
print("\n\n".join([x.decode("utf8") for x in aes_crypts]))<jupyter_output>5DhyVVf+th5Q8Q7rN7Ybv/nce8CDiNPjWeTTEC+pmeQ=

0I0DLn1LfdmeC53Ndw2B5VJJUhCTbWcmOJcCx0Mco5g=

RMID4dZqb5WEF/W6ms+eaY809Wco10g49ewdaqr37Yo=<jupyter_text>Možemo ih pokušati i dekriptirati:<jupyter_code>aes_decrypts = [x.decrypt(y) for x, y in zip(aes_, aes_crypts)]
print("\n\n".join([x.decode("utf8") for x in aes_decrypts]))<jupyter_output>Ana voli Milovana

Ana voli Milovana

Ana voli Milovana<jupyter_text>Kako imamo nekoliko načina kriptiranja, bilo bi dobro da prikažemo generirane podatke za svaki od tih tipova, na primjer na $128$-bitnim ključevima:<jupyter_code>aes_modes = ("ecb", "cbc", "ofb", "cfb", "ctr")

aes_m = [AES(128, x) for x in aes_modes]
aes_dicts = [x.save_to_dict() for x in aes_m]
aes_display_pairs = [(title, code) for title, code in zip([x.upper() for x in aes_modes],
                                                          [json.dumps(x, indent=2) for x in aes_dicts])]
for title, code in aes_display_pairs:
    display(Markdown(f"#### {title} način rada"))
    display(Code(code, language="json"))<jupyter_output><empty_output><jupyter_text>Triple DES Kako se ovdje radi o jednoj veličini ključa, demonstrirat ćemo ponašanje za različite načine kriptiranja:<jupyter_code>des_modes = ("ecb", "cbc", "ofb", "cfb", "ctr")

des_ = [DES3(x) for x in des_modes]
des_crypts = [x.encrypt(message) for x in des_]
print("\n\n".join([x.decode("utf8") for x in des_crypts]))<jupyter_output>Xcce3admnhjUp0aiJ2RCrJdivpybvV5b

m5NaEalbXAjAPCkaGaz4qgbVE8hcLu92

c6En2TUq/uf/ci6eTNafEurD3DM/lnDC

kFhXp4zePqBelAAmQSC6PhB4t0vaFxZc

dLoSuerayc21LdjyYRkZdnJVbSPMLqLX<jupyter_text>Kao i prije, ovo možemo dekriptirati:<jupyter_code>des_decrypts = [x.decrypt(y) for x, y in zip(des_, des_crypts)]
print("\n\n".join([x.decode("utf8") for x in des_decrypts]))<jupyter_output>Ana voli Milovana

Ana voli Milovana

Ana voli Milovana

Ana voli Milovana

Ana voli Milovana<jupyter_text>Konačno, usporedit ćemo dobivene ključeve:<jupyter_code>des_dicts = [x.save_to_dict() for x in des_]
des_display_pairs = [(title, code) for title, code in zip([x.upper() for x in des_modes],
                                                          [json.dumps(x, indent=2) for x in des_dicts])]
for title, code in des_display_pairs:
    display(Markdown(f"#### {title} način rada"))
    display(Code(code, language="json"))<jupyter_output><empty_output><jupyter_text>Asimetrična enkripcija Od ovih algoritama, implementirali smo samo **RSA**. Dostupni su nam ključevi veličina $1024$, $2048$ i $3072$ bita, kako definira FIPS, a zadana je veličina ključa od $1024$ bita. Veličina javnog eksponenta je proizvoljna (zadano je $65537$).  RSA Pogledajmo algoritam po ključevima:<jupyter_code>rsa_key_lengths = (1024, 2048, 3072)

rsa_ = [RSA(x) for x in rsa_key_lengths]<jupyter_output><empty_output><jupyter_text>Kao i prije, možemo koristeći ključeve enkriptirati našu poruku:<jupyter_code>rsa_crypts = [x.encrypt(message) for x in rsa_]
print("\n\n".join([b64encode(x).decode("utf8") for x in rsa_crypts]))<jupyter_output><KEY>

<KEY>

<KEY>[...]<jupyter_text>Kao i prije, možemo ih dekriptirati:<jupyter_code>rsa_decrypts = [x.decrypt(y) for x, y in zip(rsa_, rsa_crypts)]
print("\n\n".join([x.decode("utf8") for x in rsa_decrypts]))<jupyter_output>Ana voli Milovana

Ana voli Milovana

Ana voli Milovana<jupyter_text>Naposljetku, možemo pogledati kako izgledaju zapisi ovih ključeva:<jupyter_code>rsa_dicts = [x.save_full_to_dict() for x in rsa_]
rsa_display_pairs = [(title, code) for title, code in zip([str(x) for x in rsa_key_lengths],
                                                          [json.dumps(x, indent=2) for x in rsa_dicts])]
for title, code in rsa_display_pairs:
    display(Markdown(f"#### {title}-bitni ključ"))
    display(Code(code, language="json"))<jupyter_output><empty_output><jupyter_text>**Dodatno**: Dostupno je i spremanje samo privatnog ključa, tj. zapis bez `priv_exp` kroz metode `save_public_to_dict()` i `save_public_to_file()`.  Funkcije sažimanja U sklopu ove vježbe, implementirana su 2 algoritma za sažetke: **SHA-2** i **SHA-3**.Svaki od njih može raditi s 4 duljine:- $224$ bita- $256$ bita- $384$ bita- $512$ bita  SHA-2 Kao i prije, možemo napraviti instance sa sve $4$ duljine:<jupyter_code>sha2_lengths = (224, 256, 384, 512)

sha2_ = [SHA2(x) for x in sha2_lengths]<jupyter_output><empty_output><jupyter_text>Sad možemo sažeti našu poruku na sva $4$ načina:<jupyter_code>print("\n\n".join(x.hash_now(message).hex() for x in sha2_))<jupyter_output>d71d3ceb381e4a2139d5fee16f47ce1b2a01093a4cfd935956f74a7d

301a86f512742aefa145d718fb11a713c98ae62579b862fea2c94046f35a678e

4cf14d16378565669bfd2a65f0b91a434c5d07b5b83252d5741d870093727adf687dc5513a31bb6e2ae4ddde6d77db87

d11a7f5a49f77205dd6493a609b3ca0d44e119153b53fc58e16f455ec0d8839fe5b0b2a81a834ac6f6c3c9123094a5ca746fbc1adb77fc1f576f49ef2579e7fd<jupyter_text>SHA-3 Ponovit ćemo postupak kao i za prethodni algoritam:<jupyter_code>sha3_lengths = (224, 256, 384, 512)

sha3_ = [SHA3(x) for x in sha3_lengths]
print("\n\n".join(x.hash_now(message).hex() for x in sha3_))<jupyter_output>c1d3d11a552870ec03c618c0f4bdbd7e7d4113eaf96d3aa8a5094680

2564cc6e6a127fb50ea460394a08fc917f1d041e8c0d29841ba084f21da3cb77

b63b3a4e4f4f9626a8f533b9e6370f4e848c4e855acb57b3abc4fa4964d61a593fad4b996534858fe698325f98d5ef27

e1a53a38adf6a9d332746e15b523496fbd3fed9cdfdc064841e1b8d6ee1d5048902a3a8a3350f954777cbcb9ed1ba8924f3304bbf28fb699a7e6d97c2e714ed1<jupyter_text>Digitalne omotnice Digitalna omotnica predstavlja neki dokument čiji sadržaj enkriptiramo nekom simetričnom enkripcijom, a potom tajni ključ te enkripcije enkriptiramo javnim ključem primatelja. Zatim šaljemo sljedeće podatke:- metoda kriptiranja podataka- inicijalizacijski vektor za ECB, CBC i OFB načine rada- enkriptirani podaci- enkriptirani tajni ključRadi reprezentativnosti, mi šaljemo nešto više podataka (iako je to nepotrebno). Za simetričnu enkripciju možemo birati algoritme **AES** i **Triple DES**. Za asimetričnu enkripciju imamo jedino **RSA**. Pa omotajmo našu poruku u digitalnu omotnicu:<jupyter_code>env_algorithms = ("AES", "Triple DES")

env_ = [Envelope(x(), RSA()) for x in (AES, DES3)]
env_dicts = [x.envelop_to_dict(message) for x in env_]
env_display_pairs = [(title, code) for title, code in zip([str(x) for x in env_algorithms],
                                                          [json.dumps(x, indent=2) for x in env_dicts])]
for title, code in env_display_pairs:
    display(Markdown(f"#### Algoritam {title}"))
    display(Code(code, language="json"))<jupyter_output><empty_output><jupyter_text>Možemo i pročitati ove omotnice:<jupyter_code>print("\n\n".join([Envelope.open_from_dict(x, y.secret_key_cipher).decode("utf8")
                   for x, y in zip(env_dicts, env_)]))<jupyter_output>Ana voli Milovana

Ana voli Milovana<jupyter_text>Digitalni potpisi Digitalni potpisi su poruke sažete nekim algoritmom sažimanja, a potom enkriptiranje javnim ključem primatelja. U našem konkretnom slučaju to znači da ćemo poruku prvo sažeti **SHA-2** ili **SHA-3**, a potom je enkriptirati **RSA** ključem. Kako primatelj mora usporediti sažetak poruke nakon što dekriptira potpis, uz potpis mu šaljemo i poruku. Ovo ćemo istestirati na oba algoritma sažimanja, za, recimo, $512$-bitne sažetke (i zadani $1024$-bitni **RSA** ključ):<jupyter_code>sig_classes = (SHA2, SHA3)
sig_names = ("SHA-2", "SHA-3")

sig_ = [Signature(x(512), RSA()) for x in sig_classes]
sig_dicts = [x.sign_to_dict(message) for x in sig_]
sig_display_pairs = [(title, code) for title, code in zip([str(x) for x in sig_names],
                                                          [json.dumps(x, indent=2) for x in sig_dicts])]
for title, code in sig_display_pairs:
    display(Markdown(f"#### Uz {title}-512 sažetak"))
    display(Code(code, language="json"))<jupyter_output><empty_output><jupyter_text>Kao i prije, možemo provjeriti odgovaraju li potpisi za podatke:<jupyter_code>sig_valids = [x.self_verify(message, y["signature"])
              for x, y in zip(sig_, [Signature.read(z) for z in sig_dicts])]
print("\n\n".join("Potpis je ispravan." if x else "Potpis je neispravan." for x in sig_valids))<jupyter_output>Potpis je ispravan.

Potpis je ispravan.<jupyter_text>Digitalni pečati Digitalni pečati kombinacija su digitalnih omotnica i potpisa:- prvo načinimo digitalnu omotnicu od poruke koristeći primateljev javni ključ- zatim računamo potpis za tu digitalnu omotnicu (u našem slučaju računamo potpis konkatenirane enkriptirane poruke i enkriptiranog ključa) koristeći primateljev javni ključ (ne nužno isti kao u koraku prije)- šaljemo trojku **enkriptirana poruka**, **enkriptiran ključ**, **digitalni potpis** primatelju koji će ih uz svoje privatne ključeve dekriptiratiPečaćenje vršimo uz dosad sve dostupne algoritme. Ponovo radi reprezentativnosti nakon pečaćenja poruke šaljemo nešto više informacija. Za pečat koristit ćemo **AES** enkripciju s $256$-bitnim ključevima, različite $1024$-bitne **RSA** ključeve, te **SHA-3-384** sažetke.<jupyter_code>seal = Seal(message_cipher=AES(256), secret_key_cipher=RSA(1024),
            hash_cipher=SHA3(384), signature_cipher=RSA(1024))<jupyter_output><empty_output><jupyter_text>Sada možemo zapečatiti našu poruku:<jupyter_code>sealed_message = seal.seal_to_dict(message)
display(Code(json.dumps(sealed_message, indent=2), language="json"))<jupyter_output><empty_output><jupyter_text>Mogli bismo pokušati izmijeniti potpis tako da mu izmijenimo zadnji bajt u $1$, ili ako je već $1$, da ga promijenimo u $0$:<jupyter_code>tampered_message_1 = dict(sealed_message)
tampered_message_1["signature"] = tampered_message_1["signature"][:-1] + \
                                  ("1" if tampered_message_1["signature"][-1] != "1" else "0")<jupyter_output><empty_output><jupyter_text>Sad kad pokušamo otvoriti poruku, susrest će nas poruka da potpis ne valja:<jupyter_code>try:
    print(Seal.open_from_dict(tampered_message_1,
                              seal.envelope.secret_key_cipher,
                              seal.signature.signature_cipher).decode("utf8"))
except ValueError as v:
    print(v, file=stderr)<jupyter_output>Signature check failed!<jupyter_text>Isto će se dogoditi ako nešto promijenimo u podacima:<jupyter_code>tampered_message_2 = dict(sealed_message)
tampered_message_2["encrypted_data"] = tampered_message_2["encrypted_data"][:-1] + \
                                       ("1" if tampered_message_2["encrypted_data"][-1] != "1" else "0")
try:
    print(Seal.open_from_dict(tampered_message_2,
                              seal.envelope.secret_key_cipher,
                              seal.signature.signature_cipher).decode("utf8"))
except ValueError as v:
    print(v, file=stderr)<jupyter_output>Signature check failed!<jupyter_text>No ako pokušamo otvoriti originalnu poruku, nećemo imati problema:<jupyter_code>try:
    print(Seal.open_from_dict(sealed_message,
                              seal.envelope.secret_key_cipher,
                              seal.signature.signature_cipher).decode("utf8"))
except ValueError as v:
    print(v, file=stderr)<jupyter_output>Ana voli Milovana
{'fraction_non_alphanumeric': 0.1026414010910135, 'fraction_numerical': 0.050746482917025555, 'mean_word_length': 5.38250114521301, 'pattern_counts': {'":': 1, '<': 103, '<?xml version=': 0, '>': 103, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 13, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '28', 'dir': 'jupyter-structured-clean-dedup', 'id': '325991', 'license': 'Apache-2.0', 'n_tokens_mistral': 6265, 'n_tokens_neox': 5836, 'n_words': 1238, 'path': 'LAB2/LAB2.ipynb', 'repo_name': 'Yalfoosh/NOS', 'size': '14913'}