SSD Nodes Learn 🎉 VPS החל מ־$5.50/חודש
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-13

איך לבדוק ולהפעיל AES-NI בשרת VPS

בדקו אם ספק ה-VPS שלכם מסתיר את תמיכת ה-AES-NI מהמעבד. למדו כיצד למדוד את הפגיעה בביצועי AES-GCM ולהחזיר את האצת החומרה באמצעות משתנה הסביבה OPENSSL_ia32cap בקלות.

מה המשמעות האמיתית של AES-NI בשרת VPS

AES-NI בשרת VPS הוא קבוצה של שש פקודות x86 המבצעות סבב אחד של AES (תקן הצפנה מתקדם) ברמת החומרה. אם דגם ה-CPU של ספק השרתים מסתיר אותן, הסיליקון עדיין כולל אותן, אך OpenSSL אינו מזהה אותן ועובר למימוש תוכנתי שצורך בערך פי עשרה יותר מחזורי מעבד לכל בייט. ניתן לבדוק את קיום התכונה בפקודה אחת, למדוד את הפער בשתיים, ולעיתים קרובות להחזיר את הנתיב המהיר לפעולה באמצעות משתנה סביבה אחד.

הפקודות הן AESENC, AESENCLAST, AESDEC, AESDECLAST, AESIMC ו-AESKEYGENASSIST. אינטל הפיצה אותן ב-2010 ו-AMD הלכה בעקבותיה, כך שלכל מעבד שרתים שתשכרו ככל הנראה יש את הסיליקון הדרוש. פקודה נלווית, PCLMULQDQ, מבצעת כפל ללא נשא (carry-less multiplication), שזה מה ש-GCM (מצב Galois/counter) צריך כדי לבנות את תג האימות שלו. AES-GCM מהיר רק כאשר שתיהן זמינות, כיוון שהצופן והתג הם פעולות נפרדות.

ארבעה מקומות ב-VPS שבהם זה בא לידי ביטוי בניטור שלכם:

  • TLS (אבטחת שכבת תעבורה) termination. שרת אינטרנט שמגיש AES-128-GCM או AES-256-GCM מבלה את רוב זמן ההצפנה הכבד שלו בתוך AES.
  • כרכים מוצפנים. LUKS (הגדרת מפתח מאוחדת של לינוקס) ו-dm-crypt מריצים את aes-xts בכל קריאה ובכל כתיבה, בתוך ה-kernel, על ה-CPU.
  • תעבורת VPN מבוססת AES. OpenVPN עם AES-256-GCM ו-IPsec עם AES-GCM נשענים שניהם על כך.
  • גיבויים מוצפנים. כל תהליך שמצפין זרם נתונים ב-AES לפני שהוא עוזב את השרת משלם את אותו המחיר.

עומס עבודה נפוץ אחד אינו מושפע כלל. WireGuard משתמש ב-ChaCha20-Poly1305 עבור הנתונים שלו ולעולם אינו נוגע ב-AES, לכן שרת VPN מסוג WireGuard בניהול עצמי ירוץ באותה מהירות גם במארח שבו הדגל מוסתר. הבדל זה הוא סיבה מעשית לשקול WireGuard מול OpenVPN לפני שבוחרים מנהרה עבור שרת VPS זול.

כיצד לבדוק אם ל-VPS שלכם יש תמיכה ב-AES-NI

ה-kernel מעתיק את סיביות המאפיינים של ה-CPUID לתוך /proc/cpuinfo, לכן פקודת grep אחת תענה על השאלה.

grep -m1 -o '\baes\b' /proc/cpuinfo
lscpu | grep -i -o '\baes\b'

כל פקודה שמדפיסה aes משמעותה שה-CPU מדווח ל-guest על תמיכה ב-AES-NI. אם לא מודפס דבר, התמיכה אינה קיימת. lscpu קורא את אותם ה-flags, לכן שתי הפקודות תמיד יסכימו ביניהן. השתמשו בזו שמותקנת אצלכם.

כעת בדקו באיזה CPU ה-host טוען שאתם משתמשים.

grep -m1 'model name' /proc/cpuinfo

מחרוזת דגם אמיתית כמו Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz או AMD EPYC 7443P 24-Core Processor משמעותה שה-host מעביר אליכם את דגם ה-CPU הפיזי. QEMU Virtual CPU version 2.5+ או Common KVM processor משמעותם שמשהו אחר מתרחש, וזהו המקרה שכדאי להבין.

מדוע הדגל חסר למרות שהחומרה תומכת בו

CPUID היא הפקודה שבה תוכנית משתמשת כדי לשאול את ה-CPU אילו יכולות הוא תומך בהן. בתוך מכונה וירטואלית, CPUID תמיד מבצע trap ל-hypervisor, ולכן ה-hypervisor הוא שמחליט מה יוצג למערכת האורחת. רוב לוחות הניהול חושפים החלטה זו כמודל CPU לאורח. qemu64 ו-kvm64 הם מודלים גנריים בסיסיים, ואף אחד מהם אינו כולל AES-NI או SSSE3 בסט התכונות שלו, לכן האורח לא רואה את דגל ה-aes גם כאשר השרת הפיזי הוא EPYC מודרני. שרת VPS הוא אורח על חומרה של מישהו אחר, כך שכל תכונה שהוא מדווח עליה היא החלטה שהתקבלה רמה אחת מעל. אם שכבות אלו חדשות עבורך, התחל ב-מהו VPS.

מארחים בוחרים במודל גנרי באופן מכוון, כיוון ש-live migration בין מכונות עם מעבדים שונים עובד רק אם האורח מעולם לא קיבל מידע על תכונה שחסרה ביעד. המחיר נופל עליך. ה-kernel שלך והעותק של OpenSSL שברשותך קוראים את ה-CPUID המסונן הזה פעם אחת בעת העלייה, ושניהם בוחרים לאחר מכן בנתיב הקוד האיטי למשך כל זמן ריצת התהליך.

התיקון במקור הוא הגדרה בצד המארח: -cpu host במונחי QEMU, מודל בעל שם הכולל AES-NI, או +aes מפורש שנוסף למודל. לא ניתן להגדיר דבר מכל אלו מתוך האורח. פתיחת כרטיס תמיכה, או בחירה בתוכנית שה-hypervisor שלה מעביר את מודל ה-CPU כפי שהוא (pass-through), היא הפתרון הקבוע.

מדידת הפער באמצעות openssl speed

אל תסתמכו על מדדי ביצועים שפורסמו. הריצו את הצופן שהשרת שלכם מנהל בפועל.

openssl version
openssl speed -evp aes-128-gcm

שורת התוצאה מסומנת ב-AES-128-GCM ומציגה את קצב העברת הנתונים בשישה גדלי בלוקים, ביחידות של 1000 בתים לשנייה. קראו את העמודה של 8192 בתים עבור העברת נתונים בנפח גדול, כיוון שהעמודה של 16 בתים מושפעת בעיקר מהתקורה של כל קריאה ואינה מעידה על מהירות הורדת קובץ.

כעת הריצו את אותה פקודה כאשר AES-NI ו-PCLMULQDQ מנוטרלים בתוכנה:

OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcm

ערך זה מגיע מתיעוד וקטור היכולות של OpenSSL עצמו. סימן ~ בתחילת המחרוזת פירושו "נקה את הביטים הללו". ביט 57 הוא AES-NI וביט 33 הוא PCLMULQDQ, לכן 0x200000200000000 מציין בדיוק את שני אלו ולא שום דבר אחר. מספר שני הנמוך משמעותית מהראשון מעיד על כך שבשרת שלכם AES-NI פעיל וסיימתם את הבדיקה. שני מספרים זהים מעידים על כך ש-OpenSSL כבר פעל בנתיב התוכנה, כיוון שהדגל לא היה קיים כדי לנקות אותו.

ChartAES-128-GCM throughput, one core, 8192-byte blocks (representative published figures)
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
  }
]

אלו הם נתונים מייצגים שפורסמו עבור ליבת x86 מודרנית בתדר של כ-3.4 GHz, ולא מדידה שנלקחה משרת ספציפי. התייחסו אליהם כאל הערכה כללית. נתיב החומרה פועל בקצב של בערך 0.7 מחזורי מעבד לבית, ונתיב התוכנה החלופי פועל בקצב של בערך 11.0, מה שמתרגם לכ-4,850 MB/s לעומת 310 MB/s על ליבה אחת. שתי הפקודות שלעיל מפיקות את המספר היחיד שמתאר את השרת שלכם. אותה משמעת תקפה לשאר חלקי המכונה, לכן שלבו זאת עם דרך הדירה לבצע benchmark ל-VPS לפני שתסיקו מסקנות לגבי תוכנית עבודה.

כפיית הפעלת הביטים באמצעות OPENSSL_ia32cap

זהו החלק שמפתיע אנשים. הוראות AES-NI אינן דורשות הרשאות מיוחדות, וה־hypervisor אינו מבצע להן trap. רק CPUID עובר trap. לכן, המארח (host) יכול לומר לאורח (guest) ש־AES-NI חסר, בעוד ש־AESENC ממשיך להתבצע באופן טבעי במהירות מלאה. התוכנה מדלגת על הנתיב המהיר כי היא שאלה את ה־CPUID וקיבלה תשובה שגויה. ההוראה עצמה מעולם לא הפסיקה לעבוד.

OpenSSL מאפשר לכם לענות בשם ה־CPU. ערך הקסדצימלי פשוט בתוך OPENSSL_ia32cap דורס את וקטור היכולות במקום לבצע לו masking.

OPENSSL_ia32cap="0x0200020207000000" openssl speed -evp aes-128-gcm

אם הרצה זו מהירה פי כמה מהרצה רגילה, סימן שהחומרה כוללת AES-NI והמארח שלכם מסתיר זאת. זוהי קודם כל אבחנה. עבור OpenSSL, זהו במקרה גם פתרון.

כיצד נבנה הערך ההקסדצימלי

הווקטור הלוגי הראשון אורז את ה־leaf 1 EDX של ה־CPUID לתוך 32 הביטים הנמוכים ואת ה־leaf 1 ECX לתוך 32 הביטים הגבוהים. בחצי הנמוך, ביט 24 הוא FXSR, ביט 25 הוא SSE וביט 26 הוא SSE2, מה שנותן 0x07000000. בחצי העליון, ביט 33 הוא PCLMULQDQ, ביט 41 הוא SSSE3 וביט 57 הוא AES-NI, מה שנותן 0x02000202. בחיבורם מתקבל 0x0200020207000000. SSSE3 נמצא ברשימה מכיוון ש־GHASH מבוסס PCLMULQDQ של OpenSSL משתמש ב־pshufb כדי להחליף בתים, ומודל CPU אורח גנרי מסתיר את SSSE3 לצד AES-NI.

קיימות שתי אזהרות, ואת שתיהן ניתן להפעיל בכוונה.

הגדרה של הווקטור הראשון בלבד משאירה את הווקטורים הבאים על אפס, מה שמכבה את נתיבי הקוד של AVX2 ו־AVX-512. זה נעשה כאן במכוון. אל תנסו לכפות ביטי AVX על אורח שביצע להם mask, כיוון שרגיסטרי AVX דורשים שמערכת ההפעלה תפעיל מצב מורחב ב־XCR0, והקרנל שלכם סירב לעשות זאת בהתבסס על אותו CPUID מוסתר. הוראת VEX-encoded תגרום אז לשגיאת undefined-opcode והתהליך יקרוס.

כפיית AES-NI על ליבה שבאמת חסרה אותו תהרוג את התהליך מיד:

Illegal instruction (core dumped)

זהו AESENC שמעלה שגיאת undefined-opcode, כיוון שעל ליבה זו אין הוראה כזו לביצוע. קושחת שרת מסוימת יכולה גם היא להשבית AES-NI בחומרה עד לאתחול הבא, והתסמין זהה. כך או כך, הפתרון הוא מארח אחר, לא משתנה סביבה אחר.

כדי לשמור על הדריסה עבור שירות שרץ לאורך זמן, השתמשו ב־drop-in של systemd.

sudo systemctl edit nginx
[Service]
Environment="OPENSSL_ia32cap=0x0200020207000000"
sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl show nginx -p Environment

הפקודה האחרונה אמורה להדפיס את המשתנה בחזרה אליכם. הבינו למה אתם מתחייבים: אם השרת יועבר אי פעם למארח שה־CPU שלו באמת חסר AES-NI, nginx יקרוס עם שגיאת illegal instruction בחיבור ה־TLS הראשון שלו. הכניסו זאת ל־runbook שלכם, או הימנעו משימוש בדריסה בסביבת ייצור והשתמשו בה רק כדי להוכיח את הטענה בעת פתיחת כרטיס תמיכה.

מה ה-override אינו פותר

OPENSSL_ia32cap משפיע על OpenSSL בלבד. כל תוכנה אחרת מריצה זיהוי תכונות משלה ולעולם אינה קוראת למשתנה זה.

הליבה (kernel) היא המקרה החשוב. dm-crypt ו-LUKS משתמשים ב-kernel crypto API, והמודול aesni_intel מסרב להיטען כאשר סיבית תכונת ה-CPU חסרה:

modprobe: ERROR: could not insert 'aesni_intel': No such device

אין משתנה user-space עבור זה. הליבה קוראת את ה-CPUID פעם אחת בזמן האתחול, והחלטה זו נשארת בתוקף עד לביצוע reboot במארח אחר, לכן הכרך המוצפן שלכם יישאר עם צופן תוכנה ללא קשר למה ש-OpenSSL עושה. מדדו מה אתם מקבלים בפועל:

sudo cryptsetup benchmark -c aes-xts -s 256

השורה aes-xts 256b מגיעה לאלפי MiB/s עם AES מבוסס חומרה, ולמאות בודדות בלעדיו. סביבות הרצה של שפות עם מנגנון זיהוי עצמאי, ביניהן Go ו-Java, נמצאות גם הן מחוץ לטווח השפעה זה. ה-crypto/aes של Go בודק את ה-CPUID ישירות ומשתמש בשקט במימוש התוכנה שלו (constant-time) כאשר הסיבית אינה מוגדרת. אם השירות שמבצע TLS termination הוא קובץ בינארי של Go, המשתנה של OpenSSL לא ישנה דבר עבורו.

אם אין ברשותכם AES-NI, העדיפו את ChaCha20

האלגוריתם ChaCha20-Poly1305 תוכנן להיות מהיר במימוש תוכנתי טהור. על ליבת מעבד ללא תמיכה ב-AES-NI, הוא לרוב עוקף את AES-GCM בפער ניכר, ולכן הצעד ההגיוני במארח כזה הוא להפסיק להעדיף AES.

עבור Nginx בגרסה 1.19.4 ומעלה, המקומפל מול OpenSSL בגרסה 1.1.1 ומעלה:

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 מכסה את TLS 1.2. ההגדרה ssl_conf_command Ciphersuites מכסה את TLS 1.3, שבו ל-Nginx אין הנחיה ייעודית והוא מעביר את המחרוזת ישירות ל-OpenSSL ללא בדיקה, כך ששגיאת הקלדה שם תתקבל ללא התראה. בצעו reload וודאו מה מוצע ללקוח:

sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>/dev/null | grep -i cipher

תוצאה תקינה תציג את TLS_CHACHA20_POLY1305_SHA256. לפני שתחילו את השינוי באופן קבוע, הריצו את openssl speed -evp chacha20-poly1305 לצד הרצת AES על אותו שרת ותנו לשני המספרים להכריע.

מארחי ARM משתמשים בסיומות שונות

AES-NI זמין עבור x86 בלבד. שרת VPS מבוסס ARM משתמש בסיומות קריפטוגרפיות של ARMv8, שהן סט פקודות נפרד המבצע את אותה המשימה. ב־aarch64 הדגלים נמצאים תחת Features ולא תחת flags:

grep -m1 Features /proc/cpuinfo

חפשו את aes ו־pmull. pmull הוא המקביל ב־ARM ל־PCLMULQDQ, ו־GCM זקוק לו מאותה סיבה. משתנה העקיפה (override) של OpenSSL ב־ARM הוא OPENSSL_armcap, עם מבנה סיביות משלו המוגדר ב־crypto/arm_arch.h בקוד המקור של OpenSSL, לכן הערך ההקסדצימלי של x86 במדריך זה אינו רלוונטי שם. בפועל, ליבות שרת ARM הנמכרות כ־VPS חושפות את הסיומות הללו, כך שבעיית הסתרת התכונות (masked-feature) היא בעיקרה סיפור של x86.

מצבי כשל והודעות שיופיעו

אין aes בתוך /proc/cpuinfo, וההרצה הכפויה מהירה בהרבה. המארח מבצע מיסוך (masking) ל-CPUID. ודאו ששם הדגם הוא גנרי, ולאחר מכן שאלו את ספק השירות שלכם איזה דגם מעבד אורח מציג ה-hypervisor שלהם.

אין aes בתוך /proc/cpuinfo, וההרצה הכפויה מדפיסה Illegal instruction. ההוראות אכן חסרות, או שהקושחה השביתה אותן. העבירו את עומס העבודה למארח אחר.

aes קיים אך התפוקה עדיין נמוכה. ודאו שאתם קוראים את העמודה של 8192-byte, ושאף תהליך אחר לא משתמש בליבה. בתוכנית שיתופית, "שכן רועש" נראה בדיוק כמו תכונת מעבד חסרה עד שתריצו את הבדיקה פעמיים בזמנים שונים ביום.

ההרצה הממוסכת וההרצה הרגילה מניבות את אותה תוצאה. OpenSSL כבר היה בנתיב התוכנה. תוצאה זו היא הממצא, ולא טעות בבדיקה.

וירטואליזציה מקוננת (Nested virtualisation) משנה את התשובה רמה אחת למטה. אורח בתוך אורח מקבל את ה-CPUID שהשכבה האמצעית בחרה להעביר, וקל לאבד את AES-NI שם מבלי להבחין בכך. אם אתם מריצים מכונות וירטואליות מקוננות על גבי VPS, בדקו את ה-flag בתוך האורח הפנימי וגם במכונה ששכרתם.

FAQ

מדוע ל-VPS שלי אין את ה-flag‏ aes בתוך ‎/proc/cpuinfo?

מכיוון שה-hypervisor מציג מודל מעבד וירטואלי גנרי. qemu64 ו-kvm64 אינם כוללים AES-NI בסט התכונות שלהם, לכן CPUID מדווח על היעדרם ללא קשר למעבד הפיזי. מארחים מבצעים זאת כדי לאפשר ניוד של אורח פעיל בין מכונות בעלות מעבדים שונים. הריצו את grep -m1 'model name' /proc/cpuinfo: מחרוזת כמו QEMU Virtual CPU version 2.5+ או Common KVM processor היא סימן ההיכר, בעוד שמחרוזת מודל אמיתית של Xeon או EPYC מעידה על כך שמודל המעבד מועבר ישירות (passthrough) ושה-flag אכן חסר ברמת הסיליקון.

האם OPENSSL_ia32cap באמת מפעיל AES-NI, או שהוא רק מעמיד פנים?

הוא מפעיל את ההוראות האמיתיות. הוראות AES-NI אינן דורשות הרשאות מיוחדות וה-hypervisor לעולם לא עוצר אותן (trap), לכן AESENC מתבצע באופן טבעי ללא קשר למה ש-CPUID מדווח. רק הוראת ה-CPUID עוברת יירוט. הגדרת OPENSSL_ia32cap לערך הקסדצימלי פשוט מחליפה את התשובה ש-OpenSSL קיבל מ-CPUID, כך ש-OpenSSL בוחר בנתיב הקוד המותאם לחומרה והחומרה מבצעת אותו במהירות מלאה. אם הסיליקון אכן חסר את ההוראות, התהליך יקרוס עם Illegal instruction (core dumped) בפעולת ה-AES הראשונה.

האם הדריסה (override) תאיץ את נפח ה-LUKS המוצפן שלי?

לא. OPENSSL_ia32cap נקרא על ידי OpenSSL בלבד. LUKS ו-dm-crypt משתמשים ב-kernel crypto API, שם מודול aesni_intel נכשל בטעינה עם modprobe: ERROR: could not insert 'aesni_intel': No such device כאשר ה-bit של התכונה אינו מוגדר. ה-kernel קורא את ה-CPUID בזמן ה-boot ושום משתנה ב-user-space לא משנה זאת. מדדו את הנתון האמיתי באמצעות sudo cryptsetup benchmark -c aes-xts -s 256 והשוו את שורת ה-aes-xts 256b מול מארח שמדווח על ה-flag.

האם חוסר ב-flag של AES-NI מאט את WireGuard?

לא. WireGuard משתמש ב-ChaCha20-Poly1305 עבור כל הנתונים ולעולם אינו משתמש בהוראות AES, לכן קצב העברת הנתונים שלו זהה במארח מוסתר (masked) ובמארח גלוי. OpenVPN ו-IPsec המוגדרים עם AES-GCM אכן מאבדים ביצועים במארח ללא AES-NI. לכן, שתי מנהרות (tunnels) על אותו VPS יכולות להתנהג בצורה שונה מאוד, דבר שכדאי לדעת לפני שמטילים את האשמה על הרשת.

כיצד אוכל לבדוק תמיכה ב-AES-NI ב-VPS מבוסס ARM?

לליבות ARM אין AES-NI. יש להן את הרחבות הקריפטוגרפיה של ARMv8, שמבצעות את אותה עבודה עם הוראות שונות. הריצו את grep -m1 Features /proc/cpuinfo וחפשו את aes ו-pmull, שכן ב-aarch64 הם מופיעים תחת Features ולא תחת flags. לערך ה-OPENSSL_ia32cap של x86 אין משמעות ב-ARM. המשתנה המקביל של OpenSSL שם הוא OPENSSL_armcap, ומבנה ה-bit שלו מוגדר ב-crypto/arm_arch.h בתוך קוד המקור של OpenSSL.