AES-NI su VPS: come verificarlo e riattivarlo
Verifica AES-NI sul VPS, misura il costo di CPUID mascherato su AES-GCM e riattiva le istruzioni con OPENSSL_ia32cap quando necessario.
Cosa offre realmente AES-NI su un VPS
AES-NI su un VPS è un insieme di sei istruzioni x86 che eseguono in hardware un round di AES (advanced encryption standard). Se il modello della CPU esposto dal provider non le rende visibili, il silicio sottostante le include comunque, ma OpenSSL non può rilevarle e passa a un'implementazione software, che richiede circa dieci volte più cicli per byte. Puoi verificare la presenza della funzionalità con un comando, misurare la differenza con due comandi e spesso riattivare il percorso rapido impostando una variabile d'ambiente.
Le istruzioni sono AESENC, AESENCLAST, AESDEC, AESDECLAST, AESIMC e AESKEYGENASSIST. Intel le ha introdotte nel 2010 e AMD le ha adottate in seguito, quindi qualsiasi CPU server che probabilmente noleggerai include queste funzionalità hardware. Un'istruzione complementare, PCLMULQDQ, esegue moltiplicazioni senza riporto, necessarie a GCM (Galois/counter mode) per creare il relativo tag di autenticazione. AES-GCM è veloce solo quando entrambe sono disponibili, perché il cifrario e il tag sono operazioni separate.
Ecco quattro aree di un VPS in cui questa funzionalità emerge nel monitoraggio:
- Terminazione TLS (transport layer security). Un web server che gestisce AES-128-GCM o AES-256-GCM trascorre la maggior parte del tempo dedicato alla crittografia dei dati all'interno di AES.
- Volumi cifrati. LUKS (Linux unified key setup) e dm-crypt eseguono
aes-xtsa ogni lettura e a ogni scrittura, nel kernel, sulla CPU. - Traffico VPN basato su AES. OpenVPN con
AES-256-GCMe IPsec con AES-GCM fanno entrambi ampio uso di questa funzionalità. - Backup cifrati. Qualsiasi strumento che cifra un flusso con AES prima di inviarlo fuori dal server sostiene lo stesso costo.
Un carico di lavoro comune non è interessato dalla funzionalità. WireGuard usa ChaCha20-Poly1305 per i dati e non utilizza mai AES, quindi una VPN WireGuard self-hosted funziona alla stessa velocità su un host che nasconde il flag. Questa differenza è un motivo pratico per valutare WireGuard rispetto a OpenVPN prima di scegliere un tunnel per un VPS economico.
Come verificare se il proprio VPS supporta AES-NI
Il kernel copia i bit delle funzionalità CPUID in /proc/cpuinfo. Per verificarlo basta un comando grep.
grep -m1 -o '\baes\b' /proc/cpuinfo
lscpu | grep -i -o '\baes\b'Se uno dei due comandi restituisce aes, la CPU espone AES-NI a questo guest. Se non restituisce nulla, la funzionalità non è disponibile. lscpu legge gli stessi flag, quindi i due comandi restituiscono sempre lo stesso risultato. Usare quello installato sul sistema.
Ora verificare quale CPU dichiara l'host in uso.
grep -m1 'model name' /proc/cpuinfoUna stringa di modello reale, come Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz o AMD EPYC 7443P 24-Core Processor, indica che l'host espone il modello della CPU fisica al guest. QEMU Virtual CPU version 2.5+ o Common KVM processor indicano che sta accadendo qualcos'altro. È questo il caso che vale la pena analizzare.
Perché il flag è assente anche se il processore lo supporta
CPUID è l'istruzione che un programma usa per chiedere alla CPU quali funzionalità supporta. All'interno di una macchina virtuale, CPUID causa sempre un'intercettazione da parte dell'hypervisor, che decide quindi quali informazioni comunicare al guest. La maggior parte dei pannelli espone questa scelta come modello di CPU del guest. qemu64 e kvm64 sono modelli baseline generici e nessuno dei due include AES-NI o SSSE3 nel proprio set di funzionalità. Di conseguenza, il guest non vede alcun flag aes anche quando l'host fisico è un EPYC recente. Un VPS è un guest eseguito sull'hardware di un altro soggetto, quindi ogni funzionalità che riporta dipende da una scelta effettuata a un livello superiore. Se questo livello di astrazione è nuovo per te, inizia da che cos'è un VPS.
Gli host scelgono deliberatamente un modello generico, perché la migrazione live tra macchine con processori diversi funziona solo se il guest non è mai stato informato di una funzionalità assente nella macchina di destinazione. Il costo ricade su di te. Il kernel e la tua copia di OpenSSL leggono entrambi quel CPUID filtrato una volta all'avvio, quindi selezionano il percorso di esecuzione lento per tutta la durata del processo.
La correzione alla fonte richiede un'impostazione sull'host: -cpu host nei termini di QEMU, un modello denominato che includa AES-NI oppure un +aes esplicito aggiunto al modello. Non puoi impostare nessuna di queste opzioni dall'interno del guest. Aprire un ticket al supporto oppure scegliere un piano il cui hypervisor esponga il modello della CPU è la soluzione definitiva.
Misurare il divario con openssl speed
Non considerare attendibile un benchmark pubblicato senza verificarlo. Esegui il cifrario che il tuo server negozia realmente.
openssl version
openssl speed -evp aes-128-gcmLa riga del risultato è contrassegnata con AES-128-GCM e riporta il throughput per sei dimensioni di blocco, espresso in 1000 byte al secondo. Per i trasferimenti di grandi dimensioni, leggi la colonna da 8192 byte. La colonna da 16 byte è dominata dall'overhead di ogni chiamata e non fornisce informazioni utili sul download di un file.
Ora esegui lo stesso comando disattivando in software AES-NI e PCLMULQDQ:
OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcmQuesto valore proviene dalla documentazione di OpenSSL sul proprio vettore delle funzionalità. Un ~ iniziale significa «cancella questi bit». Il bit 57 corrisponde ad AES-NI e il bit 33 a PCLMULQDQ, quindi 0x200000200000000 indica esattamente queste due funzionalità e nessun'altra. Se il secondo valore è molto inferiore al primo, il server dispone di AES-NI funzionante e la verifica è conclusa. Se i due valori coincidono, OpenSSL utilizzava già il percorso software, perché il flag da cancellare non era presente.
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
}
]Questi sono valori pubblicati rappresentativi per un moderno core x86 a circa 3.4 GHz, non una misurazione eseguita su un host specifico. Considerali come un'indicazione dell'andamento. Il percorso hardware richiede circa 0.7 cicli per byte, mentre il fallback software richiede circa 11.0; su un singolo core, ciò corrisponde a circa 4,850 MB/s contro 310 MB/s. I due comandi precedenti producono l'unico valore che descrive il tuo server. Lo stesso criterio si applica al resto della macchina, quindi affianca questa verifica a un metodo ripetibile per eseguire il benchmark di una VPS prima di trarre conclusioni sul piano scelto.
Riattivare le istruzioni con OPENSSL_ia32cap
Questa è la parte che sorprende molte persone. Le istruzioni AES-NI non richiedono privilegi e l'hypervisor non le intercetta. Viene intercettata soltanto l'istruzione CPUID. Un host può quindi comunicare al guest che AES-NI non è disponibile, mentre AESENC continua a essere eseguito nativamente alla massima velocità. Il software salta il percorso ottimizzato perché ha interrogato CPUID e ricevuto una risposta errata. L'istruzione continua invece a funzionare.
OpenSSL consente di rispondere al posto della CPU. Un semplice valore esadecimale in OPENSSL_ia32cap sovrascrive il vettore delle funzionalità, invece di applicarvi una maschera.
OPENSSL_ia32cap="0x0200020207000000" openssl speed -evp aes-128-gcmSe questa esecuzione è diverse volte più veloce di quella normale, il processore dispone di AES-NI e l'host lo nasconde. Prima di tutto, questo è un test diagnostico. Per OpenSSL, però, è anche una correzione.
Come viene costruito il valore esadecimale
Il primo vettore logico inserisce il valore EDX del leaf 1 di CPUID nei 32 bit inferiori e il valore ECX del leaf 1 nei 32 bit superiori. Nella metà inferiore, il bit 24 è FXSR, il bit 25 è SSE e il bit 26 è SSE2, per un valore pari a 0x07000000. Nella metà superiore, il bit 33 è PCLMULQDQ, il bit 41 è SSSE3 e il bit 57 è AES-NI, per un valore pari a 0x02000202. Il valore completo è quindi 0x0200020207000000. SSSE3 è incluso nell'elenco perché il GHASH basato su PCLMULQDQ di OpenSSL usa pshufb per scambiare l'ordine dei byte e un modello generico di CPU guest nasconde SSSE3 insieme ad AES-NI.
Si applicano due avvertenze, ed è possibile verificarle entrambe intenzionalmente.
Impostare soltanto il primo vettore lascia a zero i vettori successivi e disattiva i percorsi di codice AVX2 e AVX-512. In questo caso è una scelta intenzionale. Non tentare di forzare l'attivazione dei bit AVX su un guest a cui sono stati mascherati, perché i registri AVX richiedono che il sistema operativo abiliti lo stato esteso in XCR0 e il kernel ha rifiutato di farlo sulla base dello stesso valore CPUID mascherato. Un'istruzione codificata con VEX genera quindi un errore di opcode non definito e il processo termina.
Forzare AES-NI su un core che ne è realmente privo termina immediatamente il processo:
Illegal instruction (core dumped)È AESENC a generare l'errore di opcode non definito, perché su quel core non esiste alcuna istruzione di questo tipo da eseguire. Anche alcuni firmware dei server possono disattivare AES-NI a livello hardware fino al riavvio successivo e il sintomo è identico. In entrambi i casi serve un host diverso, non una variabile d'ambiente diversa.
Per mantenere l'override su un servizio a esecuzione prolungata, usa un drop-in di systemd.
sudo systemctl edit nginx[Service]
Environment="OPENSSL_ia32cap=0x0200020207000000"sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl show nginx -p EnvironmentL'ultimo comando dovrebbe ristampare la variabile. Considera con attenzione le conseguenze: se quel server viene migrato su un host la cui CPU è realmente priva di AES-NI, nginx termina con un'istruzione illegale alla prima connessione TLS. Documenta questa impostazione nella procedura operativa oppure tieni completamente l'override fuori dalla produzione e usalo soltanto per dimostrare il problema quando apri un ticket.
Cosa non corregge l’override
OPENSSL_ia32cap viene letto da OpenSSL e da nessun altro componente. Ogni altro software esegue il proprio rilevamento delle funzionalità e non legge questa variabile.
Il kernel è il caso più importante. dm-crypt e LUKS usano l’API crittografica del kernel e il modulo aesni_intel rifiuta di caricarsi quando il bit della funzionalità CPU è assente:
modprobe: ERROR: could not insert 'aesni_intel': No such deviceNon esiste una variabile equivalente nello spazio utente. Il kernel legge CPUID una volta durante l’avvio e mantiene questa decisione fino al riavvio su un host diverso. Per questo il volume crittografato continua a usare il cifrario software, indipendentemente dal comportamento di OpenSSL. Misura ciò che stai effettivamente ottenendo:
sudo cryptsetup benchmark -c aes-xts -s 256La riga aes-xts 256b raggiunge migliaia di MiB/s con AES hardware e valori nell’ordine delle centinaia senza accelerazione hardware. Anche i runtime dei linguaggi con un proprio rilevamento, tra cui Go e Java, non sono interessati. crypto/aes di Go controlla direttamente CPUID e usa senza messaggi la propria implementazione software a tempo costante quando il bit è disattivato. Se il servizio che termina TLS è un binario Go, la variabile di OpenSSL non cambia nulla per quel servizio.
Se non puoi usare AES-NI, preferisci ChaCha20
ChaCha20-Poly1305 è stato progettato per essere veloce in puro software. Su un core senza AES-NI utilizzabile, in genere supera nettamente AES-GCM. Su questo tipo di host conviene quindi smettere di preferire AES.
Per nginx 1.19.4 e versioni successive, compilato con OpenSSL 1.1.1 o versioni successive:
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 riguarda TLS 1.2. ssl_conf_command Ciphersuites riguarda TLS 1.3. In questo caso nginx non dispone di una direttiva dedicata e passa la stringa direttamente a OpenSSL senza verificarla. Un errore di battitura viene quindi accettato senza alcun avviso. Ricarica la configurazione e verifica quali algoritmi vengono offerti a un client:
sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>/dev/null | grep -i cipherUn risultato corretto indica TLS_CHACHA20_POLY1305_SHA256. Prima di applicare la modifica in modo definitivo, esegui openssl speed -evp chacha20-poly1305 accanto al test con AES sullo stesso host e lascia che siano i due valori a determinare la scelta.
I nodi ARM usano estensioni diverse
AES-NI è disponibile solo su x86. Un VPS ARM usa le estensioni crittografiche ARMv8, un set di istruzioni separato che svolge la stessa funzione. Su aarch64 i flag si trovano in Features invece che in flags:
grep -m1 Features /proc/cpuinfoCercare aes e pmull. pmull è l'equivalente ARM di PCLMULQDQ e GCM ne ha bisogno per lo stesso motivo. Su ARM, la variabile di override di OpenSSL è OPENSSL_armcap, con una propria disposizione dei bit definita in crypto/arm_arch.h nel codice sorgente di OpenSSL; per questo il valore esadecimale x86 usato in questa guida non ha alcun significato. In pratica, i core dei server ARM offerti come host VPS espongono queste estensioni, quindi il problema delle funzionalità mascherate riguarda soprattutto x86.
Modalità di errore e stringhe visualizzate
Nessun aes in /proc/cpuinfo, e l’esecuzione forzata è molto più rapida. L’host sta mascherando CPUID. Verifica che il nome del modello sia generico, quindi chiedi al provider quale modello di CPU virtuale presenta il suo hypervisor.
Nessun aes in /proc/cpuinfo, e l’esecuzione forzata stampa Illegal instruction. Le istruzioni sono effettivamente assenti oppure il firmware le ha disabilitate. Sposta il carico di lavoro su un altro host.
aes è presente, ma il throughput è ancora basso. Verifica di leggere la colonna da 8192 byte e che nessun altro processo stia utilizzando il core. In un piano condiviso, un vicino rumoroso produce esattamente lo stesso effetto di una funzionalità CPU mancante, finché non esegui il test due volte in momenti diversi della giornata.
L’esecuzione con mascheramento e quella normale restituiscono lo stesso numero. OpenSSL utilizzava già il percorso software. Questo risultato è il dato rilevato, non un errore del test.
La virtualizzazione annidata cambia il risultato a un livello inferiore. Un guest all’interno di un altro guest riceve il CPUID che il livello intermedio ha scelto di inoltrare. In questo passaggio è facile perdere AES-NI senza accorgersene. Se esegui macchine virtuali annidate su un VPS, controlla il flag anche all’interno del guest più interno, oltre che sulla macchina che hai noleggiato.
FAQ
Perché il mio VPS non presenta il flag aes in /proc/cpuinfo?
Perché l’hypervisor espone un modello generico di CPU guest. qemu64 e kvm64 non includono AES-NI nei rispettivi set di funzionalità, quindi CPUID lo segnala come assente indipendentemente dal processore fisico. Gli host adottano questa configurazione per consentire la migrazione di un guest in esecuzione tra macchine con CPU diverse. Esegui grep -m1 'model name' /proc/cpuinfo: una stringa come QEMU Virtual CPU version 2.5+ o Common KVM processor è l’indicatore, mentre una stringa di modello Xeon o EPYC reale significa che il modello della CPU viene passato direttamente e che il flag manca davvero nel silicio.
OPENSSL_ia32cap abilita davvero AES-NI o si limita a simularlo?
Abilita le istruzioni reali. Le istruzioni AES-NI non richiedono privilegi e l’hypervisor non le intercetta, quindi AESENC le esegue nativamente indipendentemente da ciò che riporta CPUID. Viene intercettata soltanto l’istruzione CPUID. Impostando OPENSSL_ia32cap su un valore esadecimale semplice si sostituisce la risposta ricevuta da OpenSSL tramite CPUID; OpenSSL seleziona così il percorso di codice hardware e l’hardware lo esegue alla massima velocità. Se il silicio non dispone realmente delle istruzioni, il processo termina con Illegal instruction (core dumped) alla prima operazione AES.
L’override velocizzerà il mio volume LUKS cifrato?
No. OPENSSL_ia32cap viene letto da OpenSSL e da nessun altro componente. LUKS e dm-crypt usano l’API crittografica del kernel, dove il modulo aesni_intel non riesce a caricarsi con modprobe: ERROR: could not insert 'aesni_intel': No such device quando il bit della funzionalità è azzerato. Il kernel legge CPUID all’avvio e nessuna variabile dello spazio utente può modificare questo comportamento. Misura il valore effettivo con sudo cryptsetup benchmark -c aes-xts -s 256 e confronta la riga aes-xts 256b con quella di un host che segnala il flag.
Un flag AES-NI assente rallenta WireGuard?
No. WireGuard usa ChaCha20-Poly1305 per tutti i dati e non usa mai le istruzioni AES, quindi il throughput è identico su un host con il flag mascherato e su uno che lo espone. OpenVPN e IPsec configurati con AES-GCM perdono invece throughput su un host privo di AES-NI. Due tunnel sullo stesso VPS possono quindi comportarsi in modo molto diverso. È utile saperlo prima di attribuire il problema alla rete.
Come verifico la presenza di AES-NI su un VPS ARM?
I core ARM non dispongono di AES-NI. Usano le estensioni crittografiche ARMv8, che svolgono la stessa funzione con istruzioni diverse. Esegui grep -m1 Features /proc/cpuinfo e cerca aes e pmull, perché aarch64 le elenca sotto Features anziché sotto flags. Il valore x86 OPENSSL_ia32cap non ha alcun significato su ARM. L’equivalente della variabile di OpenSSL è OPENSSL_armcap, mentre la disposizione dei bit è definita in crypto/arm_arch.h nel codice sorgente di OpenSSL.