מחולל HMAC ובודק חתימות
מחשב HMAC-SHA-1, SHA-256, SHA-384 ו־SHA-512 מהודעה וממפתח, ומזהה מאיזה מהם הגיעה חתימה נתונה.
הזינו מפתח כדי לחשב את החתימות.
מה הכלי הזה עושה
HMAC הופך הודעה וסוד משותף לחתימה קצרה. כל מי שמחזיק באותו סוד יכול לחשב אותה מחדש ולראות אם ההודעה הגיעה ללא שינוי ואם היא הגיעה ממישהו שגם הוא יודע את הסוד. הכלי הזה מחשב את כל ארבע הווריאציות הנפוצות בבת אחת, ובהינתן חתימה שקיבלתם — אומר לכם איזו מהן ייצרה אותה.
הכיוון השני הוא בדרך כלל הסיבה שאנשים מגיעים. אימות webhook נכשל, והשאלה האמיתית אינה «האם החתימה תקפה» אלא «איזה מבין כמה דברים סבירים אני עושה אחרת מהשולח».
המפתח הוא המקום שבו זה משתבש
HMAC חותם בתים עם בתים. ההודעה בדרך כלל ברורה — גוף הבקשה הגולמי — אבל המפתח כמעט אף פעם לא, מפני שסוד מגיע כמחרוזת ומחרוזת אינה בתים עד שמחליטים איך לקרוא אותה:
a3f2 -> 61 33 66 32 4 bytes כטקסט a3f2 -> a3 f2 2 bytes כ-hex
שתי הקריאות לגיטימיות, שתיהן מייצרות חתימה תקינה לחלוטין, ואין בין שתי החתימות שום דבר משותף. שום דבר אינו מזהיר אתכם: אין שגיאה, אין תלונה על אורך, רק ערך שאינו תואם למה שהשולח חישב. לכן קידוד המפתח כאן הוא בחירה גלויה ולא ניחוש — כשחתימה לא תואמת, זה הדבר הראשון להחליף.
כאומדן גס: סוד עם קידומת כמו whsec_ או רצף של אותיות וספרות בשתי רישיות מיועד בדרך כלל כטקסט; מחרוזת של בדיוק 32 או 64 תווים שמשתמשת רק ב-0-9 וב-a-f היא בדרך כלל hex; ומחרוזת שנגמרת ב-= היא כמעט בוודאות base64. אבל בדקו בתיעוד של השולח ולא לפי הצורה, כי הצורה אינה הוכחה.
ההודעה חייבת להיות הבתים המדויקים
החצי השני של אי-התאמה הוא ההודעה. HMAC מוגדר מעל בתים, ולכן כל דבר שמשנה את הבתים משנה את החתימה לחלוטין — אין ניקוד חלקי ואין כמעט-פגיעה.
- סריאליזציה מחדש של JSON. ניתוח גוף ובנייתו מחדש עלולים לשנות סדר מפתחות, רווחים או ייצוג מספרים. חתמו ואמתו את הגוף הגולמי שקיבלתם, לעולם לא עותק שעבר הלוך ושוב.
- מעבר שורה בסוף. חלק מהכלים מוסיפים אחד כששומרים את הגוף לקובץ; זה בית, וזה משנה הכול.
- קידוד תווים. גוף שמכיל טקסט שאינו ASCII חייב להיקרא באותו קידוד בשני הצדדים — בפועל UTF-8.
- דחיסה או middleware. אם משהו לפני המטפל שלכם מפרק דחיסה או משכתב את הגוף, אמתו לפני שזה קורה ולא אחרי.
גם ספקים רבים אינם חותמים על הגוף לבדו. Stripe חותם על חותמת זמן ועל הגוף מחוברים בנקודה; AWS חותם על בקשה קנונית שנבנית ממתודה, נתיב, כותרות וגיבוב של המטען. אם האימות נכשל מול הגוף הפשוט, המחרוזת החתומה כנראה אינה הגוף הפשוט — זה מתועד אצל השולח ושווה לקרוא לפני שממשיכים לנפות.
למה SHA-1 מוצע כאן ולא בכלי ההאש
כלי ההאש מסמן את SHA-1 כשבור. הכלי הזה מציג HMAC-SHA-1 בלי אזהרה, וזה מכוון ולא פספוס.
SHA-1 אינו שמיש לחתימות ולתעודות מפני שאפשר לבנות התנגשויות: אפשר לגרום לשני מסמכים שונים לחלוק תקציר. HMAC אינו תלוי בתכונה הזו. אבטחתו נשענת על המפתח הסודי, והבנייה — גיבוב ההודעה פעמיים כשהמפתח מעורבב בשתיהן — מחזיקה מעמד גם כשהגיבוב הבסיסי חלש להתנגשויות. HMAC-SHA-1 נותר תקין ועדיין זה מה ש-OAuth 1.0a וחתימת AWS ישנה משתמשים בו, ולכן כלי שהיה מסרב לחשב אותו היה פשוט פחות שימושי בלי להיות בטוח יותר.
לכל דבר חדש, SHA-256 היא ברירת המחדל ההגיונית. תקצירים ארוכים יותר אינם חזקים יותר באופן משמעותי כאן — תקרת האבטחה היא המפתח ולא אורך התקציר — ולכן שווה לבחור ב-SHA-384 וב-SHA-512 רק כשמשהו שאתם חייבים לעבוד מולו מבקש אותם.
השוואת חתימות בבטחה
העמוד הזה משווה בהשוואת מחרוזות רגילה, וזה בסדר כאן: אתם מחזיקים את המפתח בעצמכם, אז אין מה לדלוף. בשרת שמאמת בקשה נכנסת זה לא בסדר. השוואה רגילה נעצרת בבית הראשון שנבדל, ולכן הזמן שהיא לוקחת מסגיר כמה מהניחוש היה נכון, ותוקף שיכול לשלוח הרבה בקשות יכול לשחזר חתימה תקפה בית אחר בית.
השתמשו בהשוואה בזמן קבוע שהפלטפורמה שלכם מספקת — crypto.timingSafeEqual ב-Node, hmac.compare_digest ב-Python, hash_equals ב-PHP — על הבתים הגולמיים ולא על מחרוזות hex. לצד זה, השוו מול חתימה שחישבתם בעצמכם ולא סמכו על אלגוריתם ששמו מופיע בבקשה, ודחו הודעה שחותמת הזמן שלה רחוקה מעכשיו כדי שחתימה תקפה ישנה לא תשוחזר.
HMAC אינו האש ואינו חתימה דיגיטלית
שלושה דברים מתבלבלים כאן, וההבדל חשוב למה שאפשר לטעון אחר כך:
- האש לוקח הודעה ומייצר תקציר. כל אחד יכול לחשב אותו, ולכן הוא מוכיח שההודעה לא השתנתה בטעות — לא שהיא הגיעה ממישהו מסוים.
- HMAC לוקח הודעה וסוד משותף. שני הצדדים מחזיקים באותו מפתח, ולכן הוא מוכיח שהשולח ידע את הסוד. הוא אינו יכול להוכיח מי מהצדדים שלח, כי כל אחד מהם יכול היה לייצר אותו.
- חתימה דיגיטלית משתמשת במפתח פרטי שרק השולח מחזיק, ובמפתח ציבורי שכל אחד יכול לאמת איתו. זה מה שנותן אי-הכחשה: השולח אינו יכול להתכחש לה אחר כך.
לכן HMAC הוא הכלי הנכון בין שתי מערכות שכבר חולקות סוד — webhooks, ממשקי API פנימיים, אסימוני הפעלה — והלא נכון כשצריך להוכיח לצד שלישי מי שלח משהו. שימו לב גם שהוא מאמת אך אינו מסתיר: ההודעה נוסעת גלויה, ו-HMAC אינו אומר דבר על סודיות.
שאלות נפוצות
- החתימה שלי לא תואמת. מה לבדוק קודם?
- את קידוד המפתח, ואחר כך את בתי ההודעה. סוד שנראה כמו hex מיועד לעיתים קרובות כ-hex ולא כטקסט, ושניהם מייצרים חתימות שונות לגמרי בלי שום שגיאה. אחר כך ודאו שאתם חותמים על הגוף הגולמי בדיוק כפי שהתקבל ולא על עותק שעבר סריאליזציה מחדש.
- למה SHA-1 מוצע כאן כשכלי ההאש אומר שהוא שבור?
- מפני ש-HMAC אינו נשען על עמידות להתנגשויות, שהיא התכונה ש-SHA-1 איבד. אבטחתו מגיעה מהמפתח. HMAC-SHA-1 עדיין תקין ועדיין בשימוש ב-OAuth 1.0a ובחתימת AWS ישנה, אף ש-SHA-256 היא ברירת המחדל הנכונה לכל דבר חדש.
- האם תקציר ארוך יותר בטוח יותר?
- לא באופן משמעותי. חוזק HMAC חסום על ידי הסוד ולא על ידי אורך התקציר, ולכן SHA-512 אינה טובה פי ארבעה מ-SHA-256. בחרו את זו שהצד השני מצפה לה.
- מה ההבדל בין HMAC לחתימה דיגיטלית?
- HMAC משתמש בסוד אחד ששני הצדדים יודעים, ולכן הוא מוכיח שהשולח ידע את הסוד אבל לא איזה צד שלח. חתימה דיגיטלית משתמשת במפתח פרטי שרק השולח מחזיק, ולכן צד שלישי יכול לאמת אותה והשולח אינו יכול להתכחש לה. אם אתם צריכים את התכונה האחרונה, HMAC הוא הכלי הלא נכון.
- האם להשוות חתימות עם ===?
- לא בשרת. השוואה רגילה חוזרת ברגע שבתים נבדלים, והתזמון הזה מסגיר כמה מחתימה מנוחשת היה נכון. השתמשו בהשוואה בזמן קבוע של הפלטפורמה על הבתים הגולמיים. בעמוד הזה זה לא משנה, כי אתם כבר מחזיקים את המפתח.
- האם המפתח שלי נשלח לאנשהו?
- לא. הכול מחושב בדפדפן שלכם עם WebCrypto; ההודעה והמפתח לעולם אינם עוזבים את המכשיר.