## Secrets v IoT #### ako bezpečne ukladať heslá na mikrokontroléroch [Mirek Biňas](https://bletvaska.github.io) / [**OpenAlt 2026**](https://openalt.cz/2026/)
[![This is Mirek](/images/mirek.na.hackathone.jpg)](https://bletvaska.github.io)
> The _S_ in _IoT_ Stands for Security. notes: - neda mi nezacat inac, ako standardnym vtipom na tuto temu a sice, ze oblast bezpecnosti v IoT je stale velmi problematicka
![Raspberry Pi Pico 2 WH](images/rpi.pico.2wh.jpg)
## Čo sú to Secrets?
## Secrets * **citlivé údaje**, ktoré **umožňujú prístup** k systémom, službám alebo dátam * **musia byť bezpečne uložené a chránené pred neoprávneným prístupom** * napr.: heslá, API kľúče, tokeny alebo certifikáty
[![](images/owasp.iot.top.ten.png)](https://owasp.org/www-project-internet-of-things/) notes: * OWASP je nezisková organizácia zameraná na bezpečnosť softvéru, známa hlavne rebríčkom OWASP Top 10 pre web * jednym z jej projektov je vsak aj OWASP IoT Top 10 * zoznam 10 najvacsich hrozieb v oblasti IoT * problem - je z roku 2018 :-( * nic lepsie som zatial nevidel * vy ano?
### #1 Weak, guessable or hard coded passwords
> Use of easily bruteforced, publicly available, or unchangeable credentials, including backdoors in firmware or client software that grants unauthorized access to deployed systems.
![](images/students-js1.jpg) notes: * staticka (hardcoded passwords) konfiguracia pre mqtt pripojenie * TUYA like - posiela to na cinske servery
![](images/students-js2.jpg) notes: * to iste, len zapisane inac * co sa stane, ak dojde k vyzradeniu loginu a hesla a admini prijmu strasne rozhodnutie, ze credentials zmenia?
![](images/students-python1.jpg) notes: * to iste, ale pre zmenu v pythone
![](images/students-python2.jpg) notes: * opat defaults pre mqtt a databazu
![](images/students-python3.jpg)
![](images/students-python4.jpg)
![](images/students-java1.jpg)
## Ďalšie zdroje * tutoriály - [Random Nerd Tutorials](https://randomnerdtutorials.com/micropython-mqtt-esp32-esp8266/) * knihy * čo na to AI? * a čo na to _Jan Tleskač_?
## Kde do $I&^% je problém?
### Secrets sú súčasťou kódu! * hodnoty sú uložené v (globálnych) premenných * nedajú sa zmeniť bez zmeny kódu * sú čitateľné * nie sú šifrované
## Čo s tým?
1. **Izolácia** Secrets uložíme do samostatného súboru. 2. **Šifrovanie** Zašifrujeme uložené údaje, aby boli čitateľné iba s kľúčom. 3. **Odvodenie kľúča** Kľúč neukladáme, ale odvodíme ho z hardvéru a inštalácie. 4. **Binárny bytecode** Kód pracujúci so secrets preložíme do binárneho bytecode, čím sťažíme jeho analýzu. notes: * Preklad do .mpy (MicroPython bytecode) sťažuje analýzu zdrojového kódu, ale nie je to bezpečnostný mechanizmus porovnateľný so šifrovaním. Skôr ide o obfuskáciu alebo sťaženie reverzného inžinierstva.
### Krok 1. Izolácia * secrets uložíme do samostatného súboru * použijeme formát, ktorý umožňuje jednoduché čítanie aj zápis * vystačíme si s formátom JSON * priama podpora v jazyku MicroPython * umožňuje vnáranie * výhodné je použiť dataclass pre validáciu * `/data/settings.json`
```python import json SETTINGS_FILE = '/data/settings.json' # content of settings settings = { "ssid": "OpenAlt 2026", "password": "Linux.Rocks!", } # write settings with open(SETTINGS_FILE, 'w') as file: json.dump(settings, file) # read settings with open(SETTINGS_FILE, 'r') as file: settings = json.load(file) ```
### Dump flash pamäte * útočník môže odpájkovať flash pamäť a prečítať ju pomocou čítačky * dá sa to aj jednoduchšie (softvérovo) ```bash # ak je prístupný REPL režim a viem, kde sa súbor nachádza, tak $ mpremote cat /data/settings.json # ak neviem, kde súbor je, prepnem zariadenie do BOOTSEL režimu # (bez REPL: podržím tlačidlo BOOTSEL pri pripájaní USB) $ mpremote bootloader # ulož celý obsah flash pamäte do súboru $ picotool save --all dump.bin # pozri sa, čo je vo vnútri $ strings dump.bin | grep -i password ```
![Encoding vs Hashing vs Encryption](images/encoding.vs.hashing.vs.encryption.png) notes: * https://devcookies.medium.com/encoding-vs-hashing-vs-encryption-explained-with-examples-d92b423a4584 * este predtym ako sa posunieme ku kroku 2 mi neda jedno kratke pedagogicko-terminologicke okienko * encoding - kódovanie * hashing - hašovanie * encryption - šifrovanie
### Krok 2. Šifrovanie * obmedzenia MCU pri šifrovaní * nízky výpočtový výkon * malá pamäť * obmedzená spotreba energie * chýbajúce bezpečné úložisko kľúčov (TrustZone) * slabá odolnosť voči fyzickým útokom * nie vždy kvalitný generátor náhodných čísel
[`cryptolib`](https://docs.micropython.org/en/latest/library/cryptolib.html)
## AES Advanced Encryption Standard * **symetrická bloková šifra** * pracuje s blokmi o dĺžke 128 bitov / 16 bytov * podporuje kľúče s dĺžkou 128, 192 alebo 256 bitov
![](images/aes.webp) notes: - https://nordlayer.com/blog/aes-encryption/
### Get Ready! ```python # inštancia šifry AES >>> import cryptolib # konštruktor AES šifry potrebuje: # kľúč (16/24/32 bytov) >>> key = 'SuperTajnyKluc!!' # inicializačný vektor (vždy 16 bytov) >>> iv = '1234567890123456' # vytvorenie objektu šifry >>> cipher = cryptolib.aes(key, 2, iv) # mode 2 = CBC ```
### Šifrovanie textu ```python # majme nasledujúci text # dĺžka musí byť násobkom 16, inak treba doplniť (PKCS#7 padding) >>> text = 'Hello, Pico 2 W!' # zašifrovanie textu >>> encrypted = cipher.encrypt(text) # výsledok bude zašifrovaný >>> print(encrypted) b'\xa4`\xa1\xd1(w\xe3\xe2\x13\xb4\xc8$\x04\xac\xbf\xe0' ```
### Dešifrovanie textu ```python # pre dešifrovanie potrebujeme opäť vytvoriť objekt šifry # (zmenil sa vnútorný stav a zmenil sa iv) >>> cipher = cryptolib.aes(key, 2, iv) # dešifrujeme >>> decrypted = cipher.decrypt(bytearray(encrypted)) # výsledok je ako pole bytov >>> decrypted b'Hello, Pico 2 W!' # spravíme z toho reťazec >>> decrypted.decode('utf8') 'Hello, Pico 2 W!' ```
> A cryptosystem should be secure, even if everything about the system, **except the key**, is public knowledge. > > -- [Auguste Kerckhoffs](https://en.wikipedia.org/wiki/Kerckhoffs%27s_principle) (1883) ![Auguste Kerckhoffs](images/kerckhoffs.jpg) note: - holandský lingvista a kryptograf, ktorý pôsobil vo Francúzsku - v roku 1883, v článku La cryptographie militaire v časopise Journal des sciences militaires. - volne prelozene: "systém nesmie vyžadovať utajenie a musí byť možné, aby padol do rúk nepriateľa bez toho, aby to spôsobilo problém."
> The enemy knows the system. > > -- [Claude Shannon](https://en.wikipedia.org/wiki/Claude_Shannon) (1949) ![Claude Shannon](images/claude.shannon.jpg) note: - Claude Shannon ho v roku 1949 skrátil na vetu „The enemy knows the system“. - Tej sa hovorí aj Shannonova maxima.
Kľúčový problém: ### Ako zabezpečíme kľúč?
### Bezpečnosť a RP2350 * Arm TrustZone (Cortex-M33) * secure boot (podpísaný firmvér) * 8 kB OTP pamäte pre kľúče * možnosť uzamknúť debug (SWD) aj BOOTSEL * HW generátor náhodných čísel (TRNG), SHA-256 akcelerátor notes: * RP2350 nema HW AES - sifrovanie bezi softverovo; encrypted boot riesi bootloader (AES-256 desifrovanie do SRAM) * RP2350 ma aj HW ochranu proti glitchom (glitch detectors) - Raspberry Pi k tomu vypisalo aj hacking challenge * konfiguruje sa cez Pico SDK / `picotool`, MicroPython to priamo nevystavuje * pozor: zapis do OTP je nevratny - zle nastavenie = mrtve zariadenie
### Bezpečnosť a ESP32 * flash encryption - šifrovanie celého obsahu flash * secure boot * eFuse - jednorazovo zapisovateľné úložisko kľúčov * HW akcelerátory AES, SHA, RSA, TRNG notes: * ESP32-S2/S3/C3 maju navyse HMAC a Digital Signature periferiu - kluc z eFuse nikdy neopusti cip, softver ho neprecita * konfiguruje sa cez ESP-IDF (;espefuse.py;), MicroPython to priamo nevystavuje * pozor: zapis do eFuse je nevratny - zle nastavenie = mrtve zariadenie
### A čo MicroPython? * HW bezpečnosť využíva len **nepriamo** (akcelerácia, TRNG) * secure boot a flash encryption sú vecou **firmvéru**, nie MicroPythonu * bezpečné úložisko kľúčov (OTP/eFuse) z MicroPythonu **použiť nevieme** notes: * ESP32: cryptolib a hashlib idu cez mbedTLS, ktory pouziva HW AES/SHA; os.urandom() pouziva HW RNG * RP2350: os.urandom() ide cez TRNG (pico_rand) * secure boot / flash encryption = vlastny build firmveru + espefuse.py / picotool; oficialne buildy z micropython.org to nemaju zapnute * OTP/eFuse, HMAC/DS periferie, TrustZone - bez API; jedine cez machine.mem32 = hack * => ked neviem pouzit to, co je na doske, idealne je externe samostatne zariadenie
[![ATECC 608](images/atecc.608.jpg)](https://www.adafruit.com/product/4314) notes: * mozeme si pomoct napr. tak, ze k nasmu rieseniu pridame externý šifrovací koprocesor (crypto-koprocesor), ako napr ATECC608 * kľúč tak nikdy neopustí čip * pripaja sa cez I2C, takze sa da pouzit aj z MicroPythonu * ja ho ale nemam => kompromis: kluc odvodime zo salt + unique_id
### Krok 3. Odvodenie kľúča * kľúč zostavíme ako kompozíciu z: * `salt` - náhodne vygenerované pole bytov, ktoré bude viazané na inštaláciu * `unique_id` - viazané na hardvér * na šifrovanie/dešifrovanie vytvoríme vlastný modul `crypto.py`
### Vytvorenie salt ```python import os SALT_FILE = '/data/.salt' def create_salt(salt_path: str = SALT_FILE) -> None: with open(salt_path, 'wb') as file: file.write(os.urandom(16)) ```
### Vytvorenie kľúča ```python import machine import hashlib def create_key(salt_path: str = SALT_FILE) -> bytes: with open(salt_path, 'rb') as file: salt = file.read() hasher = hashlib.sha256() # adding data to hash hasher.update(machine.unique_id()) hasher.update(salt) # return hash return hasher.digest()[:16] # AES-128 ```
### Funkcia na šifrovanie ```python import cryptolib import binascii def encrypt(plaintext: str, salt_path: str = SALT_FILE) -> str | None: if plaintext is None: return None key = create_key(salt_path) iv = os.urandom(16) data = plaintext.encode('utf-8') pad = 16 - (len(data) % 16) data = bytearray(data + bytes([pad] * pad)) cipher = cryptolib.aes(key, 2, iv) encrypted = cipher.encrypt(data) return binascii.hexlify( bytearray(iv) + bytearray(encrypted) ).decode('ascii') ```
### Funkcia na dešifrovanie ```python def decrypt(ciphertext: str, salt_path: str = SALT_FILE) -> str | None: if ciphertext is None: return None key = create_key(salt_path) raw = binascii.unhexlify(ciphertext) iv = raw[:16] data = bytearray(raw[16:]) cipher = cryptolib.aes(key, 2, iv) decrypted = cipher.decrypt(data) pad = decrypted[-1] return bytes(decrypted[:-pad]).decode('utf-8') ```
```python import json from crypto import encrypt, decrypt SETTINGS_FILE = '/data/settings.json' # content of settings settings = { "ssid": "OpenAlt 2026", "password": encrypt("Linux.Rocks!"), } # write settings with open(SETTINGS_FILE, 'w') as file: json.dump(settings, file) # read settings and decrypt password with open(SETTINGS_FILE, 'r') as file: settings = json.load(file) print(decrypt(settings['password'])) ```
### Krok 4. Binárny bytecode * modul `crypto.py` preložíme do binárneho bytecode (`crypto.mpy`) * na zariadenie nahráme iba `crypto.mpy`, nie zdrojový kód * sťaží to analýzu, ale nie je to šifrovanie (iba obfuskácia)
## [mpy-cross](https://gitlab.com/alelec/mpy_cross ) the micropython cross compiler utility
```bash # najprv nainštalujeme (venv) $ pip install mpy-cross # overíme verziu: # (verzia .mpy musí sedieť s firmvérom na zariadení) (venv) $ mpy-cross --version MicroPython v1.29.0 on 2026-08-29; mpy-cross emitting mpy v6.3 # preložíme modul crypto.py (venv) $ mpy-cross crypto.py # crypto.mpy presunieme do /src/lib # crypto.py presunieme do /stubs/ ```
### Zhrnutie * **secrets nepatria do kódu** - ani do repozitára * izolácia + šifrovanie + kľúč viazaný na HW + `.mpy` = **sťaženie útoku** * bez HW podpory je to stále len **obfuskácia** - útočník s fyzickým prístupom vyhrá * skutočná ochrana = **HW**: secure boot, flash encryption, OTP/eFuse, krypto-koprocesor * **nedokonalé zabezpečenie je stále lepšie ako žiadne** notes: * zopakovat 4 kroky: izolacia, sifrovanie, odvodenie kluca, bytecode * uprimne: Kerckhoffs - bezpecnost ma stat na kluci; nas kluc je ale odvodeny z udajov, ktore su na zariadeni * cielom je, aby skopirovany settings.json bez zariadenia nebol na nic a aby heslo nebolo vidiet cez strings * ak to myslim vazne: RP2350/ESP32 s HW zabezpecenim alebo ATECC608
## Otázky?
![qr code](https://api.qrserver.com/v1/create-qr-code/?data=https://bit.ly/4epMP7z&size=300x300) (**https://bit.ly/4epMP7z**)