Hesla SSH klíčů jsou špatně hashovaná, lze je prolomit velmi rychle

6. 8. 2018
Doba čtení: 5 minut

Sdílet

Uživatelský SSH klíč představuje důležitý autentizační prvek každého administrátora či vývojáře. I když je chráněn heslem, ve většině případů je možné heslo velmi rychle uhodnout. Jak ho více zabezpečit?

Na problém šifrování privátního SSH klíče upozornila na svém blogu společnost Latacora. Pokud pomocí utility ssh-keygen z balíku OpenSSH vygenerujeme pár privátního a veřejného klíče ve výchozím nastavení, získáme RSA klíč o délce 2048 bitů šifrovaný heslem, na které se generátor zeptá:

$ ssh-keygen -f test-key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): password
Enter same passphrase again: password
Your identification has been saved in test-key.
Your public key has been saved in test-key.pub
$ head -5 test-key
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9B988E8C115DC7EBC71C0489C73E56FB

xGCyovggT8k0NCxxn8WnisYA8GVVpqC9GqopC8ZymZ3WsADJ1TNwLpmZHmf2/fi4

Z třetího řádku privátního klíče plyne, že k šifrování privátního klíče je použit algoritmus AES se 128bitovým klíčem a blokovým režimem CBC, což je vcelku běžný a přiměřeně bezpečný způsob šifrování. Zbytek řádku pak je evidentně inicializačním vektorem. Tou zásadní otázkou tedy je, jakým způsobem je ze zadaného hesla odvozen klíč pro algoritmus AES.

Analýzou zdrojového kódu OpenSSH zjistíme, že k uložení privátního klíče do souboru slouží funkce sshkey_private_pem_to_blob(), která však pouze předá klíč a heslo funkci PEM_write_bio_RSAPrivateKey() z knihovny OpenSSL. A vskutku: takovýto privátní klíč OpenSSH je možné bez problému načíst a rozšifrovat pomocí utility  openssl rsa:

$ openssl rsa -check -in test-key
Enter pass phrase for test-key: password
RSA key ok
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqWkswd1+Fvs4vdNbTakX1Q9kapJX0/7L10P8z/mn0cbl0O14

Funkci pro zašifrování privátního klíče tedy musíme hledat přímo v OpenSSL. Jde o funkci PEM_ASN1_write_bio(), která nejprve vygeneruje náhodný inicializační vektor a následně zavolá funkci  EVP_BytesToKey():

if (RAND_bytes(iv, EVP_CIPHER_iv_length(enc)) <= 0) /* Generate a salt */
    goto err;
/*
 * The 'iv' is used as the iv and as a salt.  It is NOT taken from
 * the BytesToKey function
 */
if (!EVP_BytesToKey(enc, EVP_md5(), iv, kstr, klen, 1, key, NULL))
    goto err;

Prototyp funkce EVP_BytesToKey() vypadá takto:

#include <openssl/evp.h>

int EVP_BytesToKey(const EVP_CIPHER *type,
                   const EVP_MD *md,
                   const unsigned char *salt,
                   const unsigned char *data,
                   int datal,
                   int count,
                   unsigned char *key,
                   unsigned char *iv);

Je tedy vidět, že jako hashovací funkce je napevno zvolena MD5, jako sůl je použit inicializační vektor – podle manuálové stránky je ze soli použito jen prvních osm bajtů – a počet iterací je nastaven na jednu. Výstupem funkce je klíč a inicializační vektor, ten se ale v tomto případě nepoužívá.

Prolomení MD5 není problém, rychlost hashování ano

Hashovat heslo pomocí MD5 je špatný nápad. Jedná se totiž, podobně jako funkce z rodiny SHA, o tzv. rychlou hashovací funkci, jejíž výpočet je možné urychlit dostatečně výkonným počítačem, grafickým procesorem, či speciálním hardwarem, jaký se používá třeba pro těžbu kryptoměn. Je-li takový stroj schopen provádět miliardy MD5 operací za sekundu, je možné projít všechna hesla složená z osmi alfanumerických znaků během několika hodin, devítiznaková pak během několika dnů. Současné útoky na hesla jsou přitom mnohem sofistikovanější, používají různé kombinace slovníkových slov a předvídatelných úprav, jak o tom na LinuxDays mluvil Michal Špaček:

Pro ukládání hesel je mnohem vhodnější používat tzv. pomalé hashovací funkce, které jsou navrženy tak, aby je nebylo možné snadno urychlovat. Takovým algoritmem je například bcrypt, který používají BSD systémy pro hashování hesel uživatelů; Linuxové systémy namísto toho používají 5000× opakované SHA512. Namísto miliard MD5 hashů zvládne obdobný hardware jen několik stovek pokusů každou sekundu. Tím se crackování stává pro útočníka mnohem obtížnější.

Snadná pomoc existuje od OpenSSH 6.5

V lednu 2014 vyšlo OpenSSH 6.5, které jako jednu z novinek představilo nový formát privátního klíče. Tento formát již nepoužívá funkce OpenSSL, šifruje pomocí 256bitového AES s blokovým režimem CTR a pro hashování hesla používá právě funkci bcrypt. Nový formát je automaticky použit pro privátní klíče využívající algoritmus Ed25519, pro ostatní algoritmy je možné jeho použití vynutit přepínačem -o programu ssh-keygen. Převod do nového formátu je možné vyvolat například změnou hesla přepínačem  -p:

$ ssh-keygen -f test-key -o -p
Enter old passphrase: password
Enter new passphrase (empty for no passphrase): password
Enter same passphrase again: password
Your identification has been saved with the new passphrase.
$ head -2 test-key
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDZZnBbnI

Privátní klíč v tomto novém formátu nebude možné načíst ve starší verzi OpenSSH. To by ale nemělo představovat velký problém, protože načítání privátního klíče probíhá na straně klienta; formát privátního klíče (na rozdíl od změny algoritmu klíče) nemá žádný vliv na straně serveru. Dá se tedy jen doporučit vždy při generování privátního klíče použít přepínač -o a vynutit tak použití modernějšího a bezpečnějšího uložení klíče.

Stejný problém má většina šifrovaných privátních klíčů

Uvedený způsob šifrování privátního klíče heslem hashovaným pomocí MD5 je výchozím chováním OpenSSL. To tedy znamená, že stejným způsobem jsou chráněny také všechny privátní klíče vyrobené pomocí utility openssl rsa. Jak však přímo uvádí manuálová stránka příkazu rsa, tento příkaz používá tradiční formát předchůdce OpenSSL zvaného SSLeay, nové aplikace by měly používat bezpečnější standardizovaný formát PKCS#8. Do něj lze RSA klíč převést takto:

$ openssl pkcs8 -topk8 -in test-key -out test-key2
Enter pass phrase for test-key: password
Enter Encryption Password: password
Verifying - Enter Encryption Password: password
$ head -2 test-key2
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6TAbBgkqhkiG9w0BBQMwDgQIXk3SB2xVLj0CAggABIIEyP7L/AwihWXdkzV8

Privátní klíč ve formátu PKCS#8 načte OpenSSL stejně ochotně, jako původní tradiční formát. V tomto případě je však heslo hashováno standardizovaným algoritmem PBKDF2 s 2048 iteracemi funkce HMAC-SHA-1. Podporu pro upravenou variantu PBKDF s funkcí bcrypt, jakou používá OpenSSH, vývojáři OpenSSL nechtějí implementovat, dokud z ní nebude standard.

ict ve školství 24

S opravdu bezpečným heslem není problém

Použití pomalé hashovací funkce je důležité hlavně pro hesla, která si má uživatel pamatovat a zadávat ručně. V takovém případě v podstatě nelze zaručit, že heslo bude unikátní, zcela náhodné a dostatečně dlouhé. Je-li heslo dlouhé třeba 15 unikátních alfanumerických znaků, i při použití MD5 bude jeho prolomení na současném hardwaru trvat řádově tisíce let.

V případě SSH klíčů je riziko zneužití největší: jsou často chráněny právě heslem, které uživatel zadává ručně, přitom jsou obvykle přístupné všem aplikacím běžícím s uživatelským oprávněním. Lepší zabezpečení přitom nic nestojí. Zkontrolujte a převeďte své SSH klíče na nový formát ještě dnes!

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.