Jak sprawdzić i włączyć AES-NI na VPS
Sprawdź dostępność instrukcji AES-NI na serwerze VPS. Dowiedz się jak zmierzyć spadek wydajności AES-GCM przy braku wsparcia CPU i wymusić aktywację przez zmienną OPENSSL_ia32cap.
Co w praktyce daje AES-NI na VPS
AES-NI na VPS to zestaw sześciu instrukcji x86, które wykonują jedną rundę AES (Advanced Encryption Standard) w sprzęcie. Jeśli model procesora u dostawcy ukrywa te instrukcje, fizyczny układ nadal je posiada, lecz OpenSSL ich nie wykrywa i przełącza się na implementację programową, która zużywa około dziesięć razy więcej cykli na bajt. Można sprawdzić dostępność tej funkcji jednym poleceniem, zmierzyć różnicę w dwóch krokach i często wymusić użycie szybkiej ścieżki za pomocą jednej zmiennej środowiskowej.
Instrukcje to AESENC, AESENCLAST, AESDEC, AESDECLAST, AESIMC oraz AESKEYGENASSIST. Intel wprowadził je w 2010 roku, a AMD poszło w jego ślady, więc każdy serwerowy procesor, który prawdopodobnie wynajmiesz, posiada te układy. Instrukcja towarzysząca, PCLMULQDQ, wykonuje mnożenie bez przeniesienia (carry-less multiplication), co jest niezbędne dla trybu GCM (Galois/Counter Mode) do budowy tagu uwierzytelniającego. AES-GCM jest szybki tylko wtedy, gdy obie funkcje są dostępne, ponieważ szyfrowanie i generowanie tagu to oddzielne operacje.
Cztery obszary na VPS, w których widać to w monitoringu:
- Terminacja TLS (Transport Layer Security). Serwer WWW obsługujący AES-128-GCM lub AES-256-GCM spędza większość czasu operacji kryptograficznych wewnątrz AES.
- Szyfrowane wolumeny. LUKS (Linux Unified Key Setup) oraz dm-crypt uruchamiają
aes-xtsprzy każdym odczycie i zapisie, w jądrze systemu, na procesorze. - Ruch VPN oparty na AES. OpenVPN z
AES-256-GCMoraz IPsec z AES-GCM w pełni polegają na tym rozwiązaniu. - Szyfrowane kopie zapasowe. Każdy proces szyfrujący strumień danych za pomocą AES przed wysłaniem go z serwera ponosi ten sam koszt wydajnościowy.
Jedno typowe obciążenie nie jest w ogóle dotknięte tym problemem. WireGuard używa ChaCha20-Poly1305 do obsługi danych i nigdy nie korzysta z AES, dlatego samodzielnie hostowany VPN WireGuard działa z tą samą prędkością na hoście z zamaskowaną flagą. Ta różnica jest praktycznym powodem, aby rozważyć porównanie WireGuard z OpenVPN przed wyborem tunelu dla taniego VPS.
Jak sprawdzić, czy VPS obsługuje AES-NI
Jądro systemu kopiuje bity funkcji CPUID do /proc/cpuinfo, więc jedno polecenie grep pozwala uzyskać odpowiedź.
grep -m1 -o '\baes\b' /proc/cpuinfo
lscpu | grep -i -o '\baes\b'Wyświetlenie przez którekolwiek z poleceń wartości aes oznacza, że procesor zgłasza obsługę AES-NI dla tej instancji. Brak wyniku oznacza brak obsługi. lscpu odczytuje te same flagi, więc oba polecenia zawsze zwracają spójne informacje. Należy użyć tego, które jest zainstalowane w systemie.
Następnie należy sprawdzić, jaki model procesora jest raportowany przez hosta.
grep -m1 'model name' /proc/cpuinfoRzeczywisty ciąg znaków modelu, taki jak Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz lub AMD EPYC 7443P 24-Core Processor, oznacza, że host przekazuje informacje o fizycznym modelu procesora bezpośrednio do instancji. QEMU Virtual CPU version 2.5+ lub Common KVM processor oznacza, że zachodzi inny proces, który warto przeanalizować.
Dlaczego flaga jest niewidoczna, mimo że procesor ją obsługuje
CPUID to instrukcja, której program używa, aby zapytać procesor o obsługiwane funkcje. Wewnątrz maszyny wirtualnej instrukcja CPUID zawsze wywołuje pułapkę do hypervisora, więc to hypervisor decyduje, jakie informacje otrzyma system gościa. Większość paneli sterowania przedstawia tę decyzję jako model procesora gościa. qemu64 oraz kvm64 to ogólne modele bazowe i żaden z nich nie zawiera AES-NI ani SSSE3 w swoim zestawie funkcji, dlatego gość nie widzi flagi aes, nawet jeśli fizycznym hostem jest nowoczesny procesor EPYC. VPS jest gościem na sprzęcie należącym do kogoś innego, więc każda raportowana funkcja jest decyzją podjętą poziom wyżej. Jeśli ta warstwowość jest nowym zagadnieniem, warto zacząć od czym jest VPS.
Hosty wybierają model ogólny celowo, ponieważ migracja na żywo między maszynami z różnymi procesorami działa tylko wtedy, gdy gość nigdy nie otrzymał informacji o funkcji, której brakuje w miejscu docelowym. Koszt tego rozwiązania ponosi użytkownik. Zarówno jądro systemu, jak i instancja OpenSSL odczytują zamaskowane CPUID raz podczas uruchamiania, a następnie wybierają wolniejszą ścieżkę wykonywania kodu przez cały czas działania procesu.
Rozwiązanie problemu u źródła wymaga zmiany ustawień po stronie hosta: -cpu host w terminologii QEMU, wybór nazwanego modelu zawierającego AES-NI lub jawne dodanie +aes do modelu. Tych ustawień nie można zmienić z wnętrza systemu gościa. Trwałym rozwiązaniem jest otwarcie zgłoszenia do pomocy technicznej lub wybór planu, w którym hypervisor przekazuje model procesora bezpośrednio do gościa.
Pomiar różnicy za pomocą openssl speed
Nie należy polegać na opublikowanych wynikach testów wydajności. Należy uruchomić szyfr, który faktycznie negocjuje serwer.
openssl version
openssl speed -evp aes-128-gcmWiersz wyników jest oznaczony jako AES-128-GCM i podaje przepustowość dla sześciu rozmiarów bloków w jednostkach 1000 bajtów na sekundę. W przypadku transferu masowego należy odczytać kolumnę 8192-bajtową, ponieważ kolumna 16-bajtowa jest zdominowana przez narzut pojedynczego wywołania i nie dostarcza informacji o pobieraniu plików.
Teraz należy uruchomić to samo polecenie z wyłączonym programowo AES-NI oraz PCLMULQDQ:
OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcmWartość ta pochodzi z dokumentacji wektora możliwości OpenSSL. Wiodące ~ oznacza "wyczyść te bity". Bit 57 to AES-NI, a bit 33 to PCLMULQDQ, więc 0x200000200000000 wskazuje dokładnie na te dwa elementy i nic więcej. Druga liczba znacznie niższa od pierwszej oznacza, że serwer posiada działający AES-NI i proces jest zakończony. Dwie identyczne liczby oznaczają, że OpenSSL już wcześniej korzystał ze ścieżki programowej, ponieważ flaga nie była ustawiona.
The data behind this chart
[
{
"label": "AES-NI and PCLMULQDQ",
"mb_per_sec": "4,850",
"cycles_per_byte": 0.7
},
{
"label": "Software fallback",
"mb_per_sec": 310,
"cycles_per_byte": 11.0
}
]Są to reprezentatywne, opublikowane dane dla nowoczesnego rdzenia x86 o taktowaniu około 3.4 GHz, a nie pomiar wykonany na konkretnym hoście. Należy traktować je jako wzorzec. Ścieżka sprzętowa działa z wydajnością około 0.7 cykli na bajt, a programowa ścieżka awaryjna około 11.0, co przekłada się na około 4,850 MB/s w porównaniu do 310 MB/s na jednym rdzeniu. Dwa powyższe polecenia generują jedyną liczbę opisującą dany serwer. Ta sama dyscyplina dotyczy reszty maszyny, dlatego przed wyciągnięciem wniosków na temat planu warto połączyć to z powtarzalną metodą testowania wydajności VPS.
Wymuszenie obsługi instrukcji za pomocą OPENSSL_ia32cap
Oto aspekt, który często zaskakuje. Instrukcje AES-NI są nieuprzywilejowane, a hypervisor ich nie przechwytuje. Przechwytywany jest jedynie CPUID. Host może zatem poinformować system gościa, że AES-NI jest nieobecne, podczas gdy AESENC nadal wykonuje się natywnie z pełną prędkością. Oprogramowanie pomija szybką ścieżkę, ponieważ otrzymało błędną odpowiedź z CPUID. Sama instrukcja nigdy nie przestała działać.
OpenSSL pozwala na udzielenie odpowiedzi w imieniu procesora. Zwykła wartość szesnastkowa w OPENSSL_ia32cap nadpisuje wektor możliwości, zamiast go maskować.
OPENSSL_ia32cap="0x0200020207000000" openssl speed -evp aes-128-gcmJeśli ten test wykonuje się wielokrotnie szybciej niż standardowy, oznacza to, że krzem posiada AES-NI, a host ukrywa tę funkcję. Jest to przede wszystkim diagnoza. W przypadku OpenSSL okazuje się to również rozwiązaniem.
Jak budowana jest ta wartość szesnastkowa
Pierwszy wektor logiczny pakuje rejestr EDX z liścia CPUID 1 w dolne 32 bity, a rejestr ECX z liścia 1 w górne 32 bity. W dolnej połowie bit 24 to FXSR, bit 25 to SSE, a bit 26 to SSE2, co daje 0x07000000. W górnej połowie bit 33 to PCLMULQDQ, bit 41 to SSSE3, a bit 57 to AES-NI, co daje 0x02000202. Po złożeniu otrzymujemy 0x0200020207000000. SSSE3 znajduje się na liście, ponieważ funkcja GHASH w PCLMULQDQ wykorzystuje pshufb do zamiany bajtów, a generyczny model procesora gościa ukrywa SSSE3 wraz z AES-NI.
Obowiązują dwa ostrzeżenia, z których oba można wywołać celowo.
Ustawienie tylko pierwszego wektora pozostawia kolejne wektory wyzerowane, co wyłącza ścieżki kodu AVX2 oraz AVX-512. Jest to tutaj działanie zamierzone. Nie należy próbować wymuszać bitów AVX na zamaskowanym gościu, ponieważ rejestry AVX wymagają od systemu operacyjnego włączenia rozszerzonego stanu w XCR0, a jądro odmówiło tego na podstawie tego samego zamaskowanego CPUID. Instrukcja zakodowana w VEX wywoła wówczas błąd nieprawidłowego kodu operacji (undefined-opcode fault) i proces zostanie zakończony.
Wymuszenie AES-NI na rdzeniu, który go fizycznie nie posiada, natychmiast kończy proces:
Illegal instruction (core dumped)Jest to AESENC wywołujące błąd nieprawidłowego kodu operacji, ponieważ na tym rdzeniu taka instrukcja nie istnieje. Niektóre oprogramowanie układowe serwera może również wyłączyć AES-NI na poziomie sprzętowym do czasu następnego resetu, a objaw będzie identyczny. W obu przypadkach rozwiązaniem jest zmiana hosta, a nie zmiennej środowiskowej.
Aby zachować nadpisanie dla długo działającej usługi, należy użyć pliku typu drop-in dla systemd.
sudo systemctl edit nginx[Service]
Environment="OPENSSL_ia32cap=0x0200020207000000"sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl show nginx -p EnvironmentOstatnie polecenie powinno wyświetlić wartość zmiennej. Należy mieć świadomość konsekwencji: jeśli serwer zostanie kiedykolwiek przeniesiony na hosta, którego procesor rzeczywiście nie posiada AES-NI, nginx zakończy działanie z błędem nielegalnej instrukcji przy pierwszym połączeniu TLS. Należy odnotować to w dokumentacji operacyjnej (runbook) lub całkowicie zrezygnować z nadpisywania w środowisku produkcyjnym, używając go jedynie do celów diagnostycznych przy zgłaszaniu problemu do wsparcia technicznego.
Czego obejście nie naprawia
OPENSSL_ia32cap dociera jedynie do OpenSSL i nigdzie indziej. Każde inne oprogramowanie przeprowadza własne wykrywanie funkcji i nigdy nie odczytuje tej zmiennej.
Jądro systemu stanowi istotny przypadek. dm-crypt oraz LUKS korzystają z kernel crypto API, a moduł aesni_intel odmawia załadowania, gdy brakuje bitu funkcji procesora:
modprobe: ERROR: could not insert 'aesni_intel': No such deviceDla tego przypadku nie istnieje zmienna w przestrzeni użytkownika. Jądro odczytuje CPUID jednorazowo podczas rozruchu i decyzja ta pozostaje wiążąca do momentu restartu na innym hoście, więc zaszyfrowany wolumen pozostaje przy szyfrze programowym niezależnie od działań OpenSSL. Należy zmierzyć rzeczywiste wyniki:
sudo cryptsetup benchmark -c aes-xts -s 256Wiersz aes-xts 256b osiąga tysiące MiB/s przy sprzętowym AES oraz niskie setki bez niego. Środowiska uruchomieniowe języków posiadające własne mechanizmy wykrywania, w tym Go oraz Java, są również poza zasięgiem. Pakiet crypto/aes w języku Go sprawdza CPUID bezpośrednio i po cichu używa implementacji programowej o stałym czasie wykonania, gdy bit jest wyczyszczony. Jeśli usługą kończącą TLS jest plik binarny Go, zmienna OpenSSL nie zmienia dla niej niczego.
Jeśli sprzęt nie obsługuje AES-NI, należy preferować ChaCha20
Algorytm ChaCha20-Poly1305 zaprojektowano z myślą o wysokiej wydajności w implementacjach programowych. Na rdzeniach procesora bez dostępnego AES-NI zazwyczaj znacznie przewyższa on AES-GCM, dlatego na takim hoście rozsądnym krokiem jest rezygnacja z preferowania AES.
Dla Nginx w wersji 1.19.4 i nowszych, zbudowanego z OpenSSL 1.1.1 lub nowszym:
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384;ssl_ciphers dotyczy TLS 1.2. ssl_conf_command Ciphersuites dotyczy TLS 1.3, gdzie Nginx nie posiada dedykowanej dyrektywy i przekazuje ciąg znaków bezpośrednio do OpenSSL bez jego weryfikacji, więc literówki są tam akceptowane bez ostrzeżenia. Przeładuj konfigurację i sprawdź, co jest oferowane klientowi:
sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>/dev/null | grep -i cipherPoprawny wynik wskazuje TLS_CHACHA20_POLY1305_SHA256. Przed zatwierdzeniem zmian uruchom openssl speed -evp chacha20-poly1305 na tej samej maszynie obok testu AES i pozwól, aby uzyskane wartości liczbowe przesądziły o wyborze.
Hosty ARM używają innych rozszerzeń
AES-NI występuje wyłącznie w architekturze x86. VPS oparty na architekturze ARM korzysta z rozszerzeń kryptograficznych ARMv8, czyli odrębnego zestawu instrukcji realizującego to samo zadanie. W architekturze aarch64 flagi znajdują się w Features, a nie w flags:
grep -m1 Features /proc/cpuinfoNależy szukać aes oraz pmull. pmull jest odpowiednikiem PCLMULQDQ dla architektury ARM i jest wymagane przez GCM z tego samego powodu. Zmienna nadpisująca w OpenSSL dla architektury ARM to OPENSSL_armcap, posiadająca własny układ bitów zdefiniowany w crypto/arm_arch.h w kodzie źródłowym OpenSSL, dlatego wartość szesnastkowa dla x86 podana w tym przewodniku nie ma tam zastosowania. W praktyce rdzenie serwerów ARM oferowane jako hosty VPS udostępniają te rozszerzenia, więc problem maskowania funkcji dotyczy głównie architektury x86.
Tryby awaryjne i odpowiadające im komunikaty
Brak aes w /proc/cpuinfo, a wymuszone uruchomienie jest znacznie szybsze. Host maskuje CPUID. Należy sprawdzić, czy nazwa modelu jest ogólna, a następnie zapytać dostawcę, jaki model procesora gościa jest prezentowany przez hypervisor.
Brak aes w /proc/cpuinfo, a wymuszone uruchomienie wypisuje Illegal instruction. Instrukcje są faktycznie nieobecne lub zostały wyłączone w oprogramowaniu układowym. Należy przenieść obciążenie na inny host.
aes jest obecne, ale przepustowość nadal jest niska. Należy sprawdzić, czy odczytywana jest kolumna 8192-bajtowa oraz czy żaden inny proces nie korzysta z rdzenia. W planach współdzielonych "hałaśliwy sąsiad" wygląda identycznie jak brakująca funkcja procesora, dopóki test nie zostanie wykonany dwukrotnie o różnych porach dnia.
Wymuszone uruchomienie i normalne uruchomienie dają ten sam wynik. OpenSSL korzystał już ze ścieżki programowej. Uzyskany wynik jest poprawny i nie stanowi błędu w teście.
Wirtualizacja zagnieżdżona zmienia wynik o jeden poziom niżej. Gość uruchomiony wewnątrz innego gościa otrzymuje taki CPUID, jaki przekazała warstwa pośrednia, przez co łatwo nieświadomie utracić AES-NI. W przypadku korzystania z zagnieżdżonych maszyn wirtualnych na VPS, należy sprawdzić flagę zarówno wewnątrz wewnętrznego gościa, jak i na wynajętej maszynie.
FAQ
Dlaczego mój VPS nie posiada flagi aes w /proc/cpuinfo?
Ponieważ hypervisor prezentuje ogólny model procesora gościa. qemu64 oraz kvm64 nie uwzględniają AES-NI w swoich zestawach funkcji, więc CPUID zgłasza jego brak niezależnie od fizycznego procesora. Hosty stosują to rozwiązanie, aby umożliwić migrację działającego gościa między maszynami z różnymi procesorami. Uruchom grep -m1 'model name' /proc/cpuinfo: ciąg znaków taki jak QEMU Virtual CPU version 2.5+ lub Common KVM processor wskazuje na wirtualizację, podczas gdy nazwa modelu Xeon lub EPYC oznacza, że model procesora jest przekazywany bezpośrednio i flaga faktycznie nie jest obsługiwana przez krzem.
Czy OPENSSL_ia32cap faktycznie włącza AES-NI, czy tylko udaje?
Włącza rzeczywiste instrukcje. Instrukcje AES-NI są nieuprzywilejowane i hypervisor nigdy ich nie przechwytuje, więc AESENC wykonuje się natywnie, niezależnie od tego, co zgłasza CPUID. Przechwytywana jest tylko instrukcja CPUID. Ustawienie OPENSSL_ia32cap na zwykłą wartość szesnastkową zastępuje odpowiedź, którą OpenSSL otrzymał z CPUID, dzięki czemu OpenSSL wybiera ścieżkę sprzętową, a procesor wykonuje ją z pełną prędkością. Jeśli procesor faktycznie nie posiada tych instrukcji, proces kończy się błędem Illegal instruction (core dumped) przy pierwszej operacji AES.
Czy to obejście przyspieszy mój zaszyfrowany wolumen LUKS?
Nie. OPENSSL_ia32cap jest odczytywane tylko przez OpenSSL. LUKS i dm-crypt korzystają z jądrowego API kryptograficznego, gdzie moduł aesni_intel nie ładuje się z błędem modprobe: ERROR: could not insert 'aesni_intel': No such device, gdy bit funkcji jest wyczyszczony. Jądro odczytuje CPUID podczas startu i żadna zmienna przestrzeni użytkownika tego nie zmieni. Zmierz rzeczywistą wydajność za pomocą sudo cryptsetup benchmark -c aes-xts -s 256 i porównaj wiersz aes-xts 256b z hostem, który zgłasza tę flagę.
Czy brak flagi AES-NI spowalnia WireGuard?
Nie. WireGuard używa ChaCha20-Poly1305 do wszystkich danych i nigdy nie korzysta z instrukcji AES, więc jego przepustowość jest taka sama na hoście z zamaskowaną flagą, jak i bez niej. OpenVPN oraz IPsec skonfigurowane z AES-GCM tracą na wydajności na hoście bez AES-NI. Dwa tunele na tym samym VPS mogą zatem zachowywać się bardzo różnie, co warto wiedzieć przed obwinianiem sieci.
Jak sprawdzić obsługę AES-NI na VPS z architekturą ARM?
Rdzenie ARM nie posiadają AES-NI. Posiadają rozszerzenia kryptograficzne ARMv8, które wykonują to samo zadanie przy użyciu innych instrukcji. Uruchom grep -m1 Features /proc/cpuinfo i poszukaj aes oraz pmull, ponieważ aarch64 wymienia je w Features, a nie w flags. Wartość OPENSSL_ia32cap z x86 nie ma znaczenia na ARM. Odpowiednikiem zmiennej OpenSSL jest tam OPENSSL_armcap, a układ jej bitów jest zdefiniowany w crypto/arm_arch.h w kodzie źródłowym OpenSSL.