שמירת מידע רגיש ביישומי Windows - נמנעים מתצורה גלויה עם DPAPI

· עודכן בתאריך: · · פיתוח Windows, אבטחה, DPAPI, C# / .NET, Win32

במאמר הקודם, “רשימת בדיקה מינימלית לאבטחה בפיתוח יישומי Windows”, כתבנו את הרף המינימלי: “לא להחזיק מידע רגיש בקוד המקור או בתצורה גלויה” ו”ב-Win32 /‏ .NET, להשתמש ב-DPAPI /‏ ProtectedData”.

הפעם נעמיק קצת יותר ב“שימוש ב-DPAPI כדי להיות לפחות טובים יותר מטקסט גלוי” מתוך הרשימה הזו.

היעד הוא יישומי Windows מהסוג הבא:

  • יישומי שולחן עבודה ב-WPF /‏ WinForms /‏ WinUI
  • לקוחות Windows ב-C#‏ /‏ .NET
  • יישומים שרוצים לשמור בקובץ תצורה מקומי אישורי חיבור או טוקני API

מה שנטפל בו כאן הוא תכנון מעשי ל“לא להשאיר סוד שאין ברירה אלא לשמור מקומית, בטקסט גלוי כמו appsettings.json. זה לא סיפור של “הגנה מוחלטת שמנצחת כל תוקף”. אם מגזימים שם, האבטחה הופכת פתאום לסיפור אימה.

1. קודם המסקנה

בעבודה מעשית, נוח יותר לחשוב לפי הסדר הזה:

  1. מלכתחילה, לא להחזיק סוד ארוך-טווח בצד הלקוח
    • מעדיפים אימות Windows, אימות משולב, כניסה אינטראקטיבית של המשתמש, וניהול סוד בצד השרת
  2. אם באמת נדרשת שמירה מקומית, לא לשים בטקסט גלוי
    • ב-Windows, המועמד הראשון הוא DPAPI /‏ ProtectedData
  3. ביישום שולחני רגיל, DataProtectionScope.CurrentUser הוא הבסיס
    • השימוש ב-LocalMachine מוגבל מאוד
  4. DPAPI אינו מגן עד “מצב שבו המכשיר נפרץ לגמרי”
    • קוד שרץ באותה הרשאת משתמש יכול בעיקרון לפענח מה שאותו משתמש יכול לפענח

והנקודה החשובה ביותר במאמר הזה היא כאן.

“בכל מקרה צריך לשמור את מפתח הסוד איפשהו, אז מבחינת אבטחה זה לא אותו דבר בין טקסט גלוי ל-DPAPI?”

זה נכון בחצי, והמסקנה שגויה.

  • אם שומרים AES עצמי + מפתח באותו יישום או אותה תצורה, זה קרוב מאוד לטקסט גלוי
  • אבל DPAPI מעביר את ניהול המפתח למערכת ההפעלה, וקושר את מי שיכול לפענח ל”אותו משתמש Windows” או ל”אותו מחשב”
  • כתוצאה מכך, החוסן מול תקריות כמו דליפת קובץ תצורה בודד, הוצאה למחשב אחר, שליחה בטעות, דליפת גיבוי, והכנסה למאגר קוד משתנה משמעותית

כלומר, אם מסתכלים רק על הטענה המופשטת “המפתח נמצא איפשהו”, זה נראה זהה, אבל “מי, באיזה הקשר, ובאיזו קלות אפשר להשתמש בו” שונה לגמרי.

להניח מפתח מתחת לשטיח הכניסה, לעומת לזהות את הבן אדם במשרד הניהול לפני שמוציאים לו את המפתח — לומר ששניהם זהים זו קצת קיצוניות.

"המפתח נמצא איפשהו" לבדו לא הופך את זה לזההתרשים המראה שבתפיסה מופשטת של המפתח נמצא איפשהו, גם טקסט גלוי וגם DPAPI נראים זהים, אבל כשמסתכלים מי, באיזה הקשר ובאיזו קלות אפשר להשתמש בו, ההבדל גדול לגמרי, וזו הטענה המרכזית של המאמר הזה.אם מסתכלים רק על כךאם מסתכלים על כךהמפתח נמצא איפשהו (מופשט)נראה זההמי, באיזה הקשר, באיזו קלותשונה לגמרי

איור 1: בתפיסה מופשטת זה נראה זהה, אבל “מי יכול להשתמש באיזה הקשר” מפריד בין טקסט גלוי ל-DPAPI.

מפת הידע של המאמר

המאמר הזה עוסק בשימוש ב-DPAPI (‏ProtectedData) כדי לשמור סודות של יישומי שולחן עבודה ב-Windows — כמו connection strings וטוקני API — באופן בטוח יותר מתצורה בטקסט גלוי. שמירה בטקסט גלוי, וכן הצפנה עצמית שבה המפתח נמצא באותו מקום כמו הטקסט המוצפן, חושפות את הסוד מיד עם דליפה של קובץ התצורה לבדו; לעומת זאת DPAPI מקטין סיכון זה על ידי קשירת גורם הפענוח למשתמש Windows מסוים או למחשב מסוים. ביישום שולחן עבודה רגיל כדאי להתבסס על הסקופ ‏CurrentUser, ואת LocalMachine כדאי לשמור לשימושים מוגבלים כמו שירות Windows מהימן וייעודי. עם זאת, איפוס סיסמה, יצירה מחדש של פרופיל, או העתקת הטקסט המוצפן בלבד למחשב אחר גורמים לכשל פענוח — ולהפך, בפרופיל נודד (roaming) עובר גם חומר המפתח ולכן ניתן לפענח גם במחשב אחר. כאשר רוצים לשמור צמד שם משתמש וסיסמה, או כאשר נדרשת שיתוף סוד בין כמה מחשבים, ‏Credential Locker או Credential Manager מתאימים יותר מ-DPAPI.

מפת הידע של הגנה על סודות ביישומי Windows עם DPAPIתרשים המראה כיצד DPAPI מאפשר להחליף בין הסקופים CurrentUser ו-LocalMachine עבור גורם הפענוח, כיצד DPAPI מקטין את סיכון הדליפה שנובע משמירה בטקסט גלוי או מהצפנה עצמית, את החלוקה מול Credential Locker ו-Credential Manager, ואת המכשולים התפעוליים שגורמים לכשל פענוח.עלול לגרום למצמצםעלול לגרום לשימוש לא מומלץ למוגדר באמצעותמוגדר באמצעותמענה מומלץ למענה מומלץ לשימוש לא מומלץ לעלול לגרום למענה מומלץ לשימוש לא מומלץ למענה מומלץ למענה מומלץ למחייבאינו מתיישב עםמענה מומלץ לעלול לגרום לעלול לגרום לעלול לגרום למונעDPAPIסודות היישוםשמירת סודות בטקסט גלוידליפת סודות עקב הדלפת קובץ ההגדרות בלבדהצפנה עצמית שבה המפתח שמור לצד הטקסט המוצפןסוד ארוך-טווח משותף לכל הלקוחותDataProtectionScope.CurrentUserDataProtectionScope.LocalMachineיישום שולחני עם משתמש אינטראקטיבישירות Windowsכישלון פענוח ב-DPAPIצמד שם משתמש וסיסמהCredential Locker(PasswordVault)Credential Manager(CredWrite/CredRead)חשבון Microsoftשיתוף סודות בין מכונות ומשתמשיםאיפוס סיסמה בידי מנהל מערכתיצירה מחדש של הפרופילהעתקת הטקסט המוצפן בלבד למחשב אחרפרופיל נודד

בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 21, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle

2. למה תצורה גלויה מסוכנת

הסיבה ששמירה בטקסט גלוי מסוכנת הרבה יותר “ארצית” מתאוריית ההצפנה. בעבודה מעשית, זה דולף בדרך כלל דרך המסלולים האלה:

  • קובץ התצורה מוכנס ישירות ל-Git
  • קובץ התצורה נכנס בשלמותו ל-ZIP לחקירת תקלות
  • מבקשים מהלקוח לצרף את קובץ התצורה לפנייה לתמיכה
  • צד שלישי יכול לקרוא דרך גיבוי או שיתוף קבצים
  • מחרוזת חיבור או טוקן מופיעים ישירות בלוג
  • עובד שעזב או משתמש אחר יכול לקרוא קובץ באותו מכשיר

הבעיה הגדולה ביותר בטקסט גלוי היא “ברגע שקראו אותו, הסוד נגמר”.

  • פתחו את הקובץ — נגמר
  • העתיקו — נגמר
  • צירפו למייל — נגמר
  • נשאר במאגר קוד — צריך לטפל בזה כמעט לנצח

התוקף אפילו לא צריך להיות מתוחכם. זה שאפשר לפתוח בעורך טקסט כבר די חלש כשלעצמו.

טקסט גלוי נגמר ברגע שקוראים אותותרשים המראה שדרך מסלולים ארציים כמו הכנסה ל-Git, ZIP לחקירה, דליפת גיבוי או צירוף, קובץ התצורה נקרא, וברגע הזה הסוד הגלוי נגמר כסוד.הכנסה ל-Git / ZIP לחקירה / צירוף / דליפת גיבויקובץ התצורה נקראהסוד הגלוי נגמר באותו רגעהתוקף אפילו לא צריך להיות מתוחכם

איור 2: החולשה של טקסט גלוי היא שהסוד נגמר ברגע שהוא נקרא, במסלול תקרית יומיומי.

3. תשובה ל”בכל מקרה שומרים את מפתח הסוד איפשהו, אז זה לא אותו דבר?”

השאלה הזו הגיונית לגמרי. ואם עונים עליה ברשלנות, מאמר האבטחה הופך לפתע לערפילי.

התשובה: במובן ש”צריך מפתח איפשהו” — כן, ובמובן ש”לכן זה זהה” — לא.

3.1. מה זהה ומה שונה

נכון, להצפנה נדרש בסופו של דבר root of trust כלשהו. סוד לא צומח בחינם משום מקום ביקום. זה עולם קשה.

עם זאת, ההבדל מבחינת אבטחה נקבע לפי שלוש הנקודות האלה:

  • האם היישום מחזיק את המפתח ישירות
  • למי או למה המפתח קשור
  • האם אפשר לפענח כשגונבים רק את הקובץ

אם מציגים את ההבדל הזה בטבלה גסה, זה נראה כך:

שיטה הקובץ נקרא הקובץ בלבד הוצא למחשב אחר נקרא על ידי משתמש אחר באותו מחשב קוד שרץ באותה הרשאת משתמש
טקסט גלוי דולף במקום דולף כמו שהוא דולף כמו שהוא ברור שקורא
הצפנה עצמית + מפתח באותה תצורה / קובץ הרצה דולף במידה רבה דולף במידה רבה דולף במידה רבה ברור שמפענח
DPAPI +‏ CurrentUser לא ניתן לקריאה מיידית רק מהקובץ בדרך כלל קשה לפענח בדרך כלל קשה לפענח מפענח
DPAPI +‏ LocalMachine לא ניתן לקריאה מיידית רק מהקובץ מחוץ לאותו מחשב, בדרך כלל קשה לפענח על אותו מחשב, ניתן לפענח באופן רחב מפענח

הנקודה החשובה כאן היא ש-DPAPI מפריד בין “יכולת לקרוא את הקובץ” ל”יכולת להשתמש בסוד”.

היכן נשארת המפתח הראשי מול הטקסט המוצפןתרשים המראה שהטקסט המוצפן הוא מה שניתן להוציא מהמכשיר, ואילו מפתח המשתמש או מפתח המחשב נשארים בצד מערכת ההפעלה ולא כלולים בטקסט המוצפן, וכיצד כל אחד מהם קובע מי יכול לפענח.הגנת CurrentUserהגנת LocalMachineטקסט מוצפן (ניתן להוצאה)מפתח המשתמש (בצד ה-OS)מפתח המחשב (בצד ה-OS)קוד שרץ כאותו משתמש: מפענחמשתמש אחר באותו מחשב: לא מפענחכל קוד באותו מחשב: מפענחהעתקה למחשב אחר: לא מפענחפרופיל נודד: מפענח (ראו 6.5)

איור 3: הטקסט המוצפן נמצא בצד שניתן להוציא, ומפתח הפענוח הנדרש נשאר בצד מערכת ההפעלה. עם זאת, טווח ה-LocalMachine הוא כל המחשב, ולא ניתן לחסום ממנו משתמש אחר באותו מחשב.

בטקסט גלוי, שני אלה זהים. אם אפשר לקרוא את הקובץ, אפשר לקרוא גם את הסוד.

אבל ב-DPAPI, לפחות ב-CurrentUser,

  • כאותו משתמש Windows
  • בהקשר של אותו Windows
  • דרך מנגנון ההגנה של מערכת ההפעלה

צריך לפענח.

ההבדל הזה משמעותי מאוד בשטח בזמן תקרית.

3.2. “אבל אם זה אותו משתמש, אפשר לפענח בכל זאת” — נכון

זו נקודה שצריך לכתוב בלי להסתיר.

קוד שרץ באותה הרשאת משתמש יכול בעיקרון לפענח כל מה שאותו משתמש יכול לפענח.

כלומר, DPAPI לא נועד בעיקר למצבים כאלה:

  • המכשיר כבר נפרץ על ידי תוכנה זדונית
  • התוקף יכול להריץ קוד כאותו משתמש
  • ההשתלטות ברמת מנהל המכשיר, מלאה

במצב הזה, מכיוון שגם היישום עצמו יכול לפענח, גם קוד התוקף יכול לפענח. כאן, “אבל זה מוצפן” לא ממש מרגיע.

DPAPI יעיל בעיקר מול “דליפת קבצים, מיקום שגוי, הוצאה במצב לא מקוון, ועיון על ידי משתמש אחר”.

אם מתבלבלים כאן, קורים שני הדברים:

  • מזלזלים במה שאפשר להגן עליו ולא משתמשים
  • מעריכים יתר על המידה את מה שלא ניתן להגן עליו ומרגישים בטוחים

שני אלה מסוכנים בשקט.

היכן DPAPI יעיל והיכן לאתרשים המראה ש-DPAPI יעיל בצד של דליפת קבצים, מיקום שגוי ועיון על ידי משתמש אחר, אך לא יעיל מול קוד תקיפה שרץ באותה הרשאת משתמש או מכשיר שכבר נפרץ.יעיל בצד זהלא יעיל בצד זהטווח ההגנה של DPAPIדליפה · מיקום שגוי · משתמש אחרקוד תקיפה באותה הרשאהבלבול כאן מוביל להערכה שגויה

איור 4: בלי לדעת את קו הגבול בין יעיל ללא יעיל, מגיעים לזלזול או לביטחון יתר.

3.3. אז מה כל כך טוב בזה

בקיצור, מה שטוב ב-DPAPI הוא:

“אפשר להפריד את הסוד עצמו מקריאות קובץ התצורה”

זה הכול.

לדוגמה, בתקריות מהסוג הבא, יש הבדל בין טקסט גלוי ל-DPAPI:

  • המשתמש שלח בטעות את קובץ התצורה לתמיכה
  • קובץ התצורה נכנס ל-ZIP של חקירת תקלות
  • קובץ התצורה בלבד דלף מגיבוי
  • הועתק לתיקייה משותפת
  • המפתח יכל להביא את המצב שבו רואים רק את הטקסט המוצפן בלי יכולת לקרוא את התוכן

זה יתרון מעשי משמעותי למדי. בלי להפוך את התוקף לגיבור-על מסרט, אפשר לצמצם את רדיוס התקרית היומיומית.

המנגנון שמצמצם את רדיוס התקריתתרשים המראה שגם בתקרית יומיומית כמו שליחה בטעות לתמיכה, ZIP לחקירה או דליפת גיבוי, מה שיוצא החוצה הוא רק הטקסט המוצפן, ולכן זה לא הופך לדליפת סוד ורדיוס התקרית מצטמצם.שליחה בטעות · ZIP לחקירה · דליפת גיבוימה שיוצא החוצה הוא רק הטקסט המוצפןלא הופך לדליפת סוד כמו שהוארדיוס התקרית היומיומית מצטמצם

איור 5: תקרית של קובץ שיוצא החוצה לא נמנעת, אבל אפשר להפוך את מה שיוצא לטקסט מוצפן.

4. למה DPAPI מתאים בול

כשמטפלים ב-Windows בסוד ששמור מקומית, אלה הסיבות ש-DPAPI מתאים בול בעבודה מעשית.

4.1. אפשר להעביר את ניהול המפתח למערכת ההפעלה

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

עם DPAPI, אפשר להפריד את הבעיה “איך יוצרים ואיפה שמים מפתח הצפנה” ממימוש היישום.

במובן הזה, נכון יותר להתייחס ל-DPAPI לא כ“API לבחירת אלגוריתם הצפנה”, אלא כ“API שמעביר את ניהול המפתחות למערכת ההפעלה”.

הגישה המהותית ל-DPAPIתרשים המראה שקרוב יותר למהות להסתכל על DPAPI לא כ-API לבחירת אלגוריתם הצפנה, אלא כ-API שמעביר את בעיית ניהול המפתחות למערכת ההפעלה, ובכך מפריד את זה ממימוש היישום.לא זו הגישהזו הגישה המהותיתAPI לבחירת אלגוריתםDPAPIAPI שמעביר ניהול מפתחות ל-OSמפריד יצירה ואחסון מפתח ממימוש היישום

איור 6: כשמסתכלים על DPAPI כיעד להעברת ניהול המפתחות, מובן מה אפשר להפריד מהיישום.

4.2. אפשר לקשור את מי שיכול לפענח למשתמש Windows או למחשב

ביישום שולחני רגיל, ברוב המצבים מספיק לבחור ב-CurrentUser.

  • ש-אותו משתמש מחובר
  • שהעיבוד רץ בהקשר של אותו משתמש

בהנחות אלה, אפשר לפענח.

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

4.3. קל לכלול גם גילוי שיבוש

טעות נפוצה בהצפנה עצמית היא לחשוב “הצפנו עם AES, זהו”, ולשכוח גילוי שיבוש.

ל-DPAPI יש הגנת שלמות גם לנתונים המוצפנים, ולכן יש יתרון מעשי שקל לכלול גם גילוי שינוי בלתי מורשה בטקסט המוצפן במנגנון בצד מערכת ההפעלה.

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

איור 7: היתרון של DPAPI הוא שאפשר לכלול לא רק הצפנה אלא גם גילוי שיבוש במנגנון בצד מערכת ההפעלה.

4.4. אפשר להשתמש בטבעיות מ-C#‏ /‏ .NET

ב-C# אפשר להשתמש ישירות ב-System.Security.Cryptography.ProtectedData. זה שלא צריך להוסיף ספריות מיותרות עוזר מאוד ביישום ייעודי ל-Windows.

5. מה DPAPI מגן עליו ומה לא

כאן בטוח יותר להפריד בבירור.

5.1. מה קל יותר להגן עליו

DPAPI יעיל לפחות במצבים הבאים:

  • דליפת קובץ תצורה בטקסט גלוי
  • הוצאת קובץ למחשב אחר
  • עיון על ידי משתמש אחר באותו מחשב (בהנחת CurrentUser)
  • דליפה כגיבוי או כקובץ מצורף
  • מצב “נקרא בטעות” בסביבת פיתוח ותחזוקה

5.2. מה לא ניתן להגן עליו, או שההגנה חלשה

מצד שני, במצבים הבאים בטוח יותר לא להיות בטוחים מדי.

  • קוד תקיפה שרץ באותה הרשאת משתמש
  • פריצה מלאה של המכשיר עצמו
  • השתלטות בהרשאות מנהל
  • הטקסט הגלוי בזיכרון אחרי שהיישום פענח
  • סוד ארוך-טווח שמופץ באופן זהה לכל הלקוחות

הפריט האחרון, “סוד ארוך-טווח משותף לכל הלקוחות”, חשוב במיוחד.

לדוגמה, תכנון מהסוג הבא:

  • הטמעת אותו מפתח API בכל הלקוחות
  • החזקת אותה סיסמה משותפת בכל המכשירים
  • הפצת מפתח פענוח קבוע שמסתיים בצד הלקוח בלבד

נוטה להתפשט לכל המערכת ברגע שהוא נגנב ממכשיר אחד. ההיגיון פשוט: אם ה-1 מכשיר יכול לפענח, אפשר לחלץ ממנו את הסוד.

DPAPI יעיל להפוך את מיקום השמירה לטוב יותר מטקסט גלוי, אבל הוא לא מצדיק סוד שמלכתחילה לא צריך להיות אצל הלקוח.

המנגנון שבו סוד משותף מתפשטתרשים המראה שכשמפיצים סוד ארוך-טווח משותף לכל הלקוחות, כיוון שאפשר לפענח אותו ממכשיר אחד לפחות, אפשר לחלץ ממנו את הסוד, וזה מתפשט לכל המערכת ברגע שגונבים אותו ממכשיר אחד.סוד ארוך-טווח משותף לכל הלקוחותניתן לפענוח לפחות ממכשיר אחדאפשר לחלץ את הסוד מאותו מכשירמתפשט לכל המערכתשמירה ב-DPAPI לא פותרת מהשורש

איור 8: סוד משותף מתפשט לכולם ברגע שנפילת מכשיר אחד, ולכן בודקים קודם את מיקום השמירה ולא את שיטתה.

עבור סוג הסוד הזה, במקום להשקיע בשיטת השמירה, נכון יותר להעביר אותו לכיוונים הבאים:

  • שמירה בצד השרת
  • הלקוח מחזיק רק טוקן
  • הפיכה לאישורים אישיים לפי משתמש
  • הפיכה לטוקן עם תוקף

6. השימוש המחולק בין CurrentUser ל-LocalMachine

זו נקודה חשובה מאוד. בחירה רשלנית משנה את המשמעות.

6.1. הבסיס הוא CurrentUser

ביישום שולחני רגיל של Windows, קודם חושבים על CurrentUser כבסיס.

דוגמאות מתאימות:

  • יישום שולחני למשתמש ב-WPF /‏ WinForms /‏ WinUI
  • יישום שמחזיק תצורה או אישורים לפי משתמש
  • יישום שמחזיק תצורה תחת %LocalAppData% או %AppData%

במקרה כזה, קל יותר להתייחס לזה כ-“סוד של אותו משתמש Windows”.

6.2. השימוש ב-LocalMachine מוגבל מאוד

LocalMachine נראה נוח, אבל ביישום שולחני רגיל הוא רחב מדי.

מה שמתאים, לדוגמה, זה מצבים כאלה:

  • שירות Windows על מכונה ייעודית מהימנה
  • סוד שמשמש רק תהליך ספציפי באותה מכונה
  • מקרה שדורש שימוש באותו מכשיר גם כשמחליפים משתמשים מחוברים

עם זאת, נקודת הזהירות כבדה.

  • ניתן לפענוח באופן רחב מכל תהליך שרץ על אותו מחשב
  • מסוכן במכשיר משותף, RDS, מכשיר קפיצה, וסביבה עם כמה משתמשים
  • אם בוחרים כי “בינתיים כולם יכולים להשתמש בזה, נוח”, זה בדרך כלל גורם לבעיה בהמשך

והמניעים שגורמים לרצות לבחור ב-LocalMachine, ברוב המקרים, הם שלושה אלה:

  • אפשר לקרוא גם אחרי החלפת משתמש
  • אפשר לקרוא גם מהשירות
  • אם זה עובד, זה נוח

כולם “נוחים”, לא “מוגנים”. אם בוחרים ב-LocalMachine ביישום שולחני רגיל, זה מרחיב את אפשרות הפענוח גם לתהליכים אחרים על אותו מחשב, ולכן המשמעות משתנה משמעותית.

מה קורה כשבוחרים ב-LocalMachine בגלל נוחותתרשים המראה שאם בוחרים ב-LocalMachine בגלל נוחות של קריאה בין משתמשים או מהשירות, אפשרות הפענוח מתרחבת גם לתהליכים אחרים על אותו מחשב, וזה נוחות ולא הגנה.נוחות: קריאה בין משתמשים / מהשירותבוחרים LocalMachineאפשרות הפענוח מתרחבת לתהליכים אחריםזה נוחות, לא הגנה

איור 9: בחירה ב-LocalMachine מתוך נוחות מרחיבה את טווח הפענוח לכל המחשב.

6.3. אם מתלבטים, חושבים כך

  • יישום UI רגילCurrentUser
  • מקרה מיוחד שבאמת רוצים להגן ברמת מכונהLocalMachine
  • צריך לפענח מכל משתמש, אבל יש גם משתמשים אחרים על המכשיר ← בדרך כלל עדיף לבחון מחדש את התכנון

6.4. בשירות או ב-impersonation, נדרשת זהירות נוספת

כשמעורבים שירות Windows או impersonation, המשמעות של CurrentUser נהיית קצת כבדה יותר.

  • מי חשבון ההרצה
  • האם הפרופיל של אותו חשבון טעון
  • באיזה הקשר מתבצע הפענוח

אם הנקודות האלה לא מדויקות, קל להגיע ל-“הצלחנו להצפין אבל לא מצליחים לפענח”. שימוש בשירות לא תמיד מסתדר עם “פשוט CurrentUser”.

במקרה של impersonation, הכשלון הטיפוסי שכתוב מפורש גם ב-Microsoft Learn הוא “Key not valid for use in specified state.”. DPAPI מחזיק את נתוני המפתח בפרופיל המשתמש, ולכן אם הפרופיל לא טעון, לא ניתן לפענח. לפני ה-impersonate, יש לטעון מראש את הפרופיל של המשתמש הרלוונטי.

הכשלון הטיפוסי ב-impersonationתרשים המראה ש-DPAPI מחזיק את נתוני המפתח בפרופיל המשתמש, ולכן impersonate בלי פרופיל טעון גורם לכשלון פענוח טיפוסי, ולכן צריך לטעון קודם את הפרופיל של המשתמש הרלוונטי.impersonate בלי פרופיל טעוןהפענוח נכשל בשגיאהטעינת הפרופיל קודםניתן לפענח גם אחרי impersonateהמפתח נמצא בפרופיל

איור 10: כיוון שהמפתח נמצא בצד הפרופיל, יש לטעון את הפרופיל לפני ה-impersonate.

6.5. כדאי להכיר מקרים שבהם “אי אפשר לפענח” בתפעול

יותר מההצפנה עצמה, תקרית של אי-יכולת לפענח כואבת יותר בעבודה מעשית. DPAPI הוא מנגנון שקושר את מי שיכול לפענח למשתמש Windows / למחשב, ולכן אם הקשר הזה נשבר, לא ניתן לקרוא.

חמישה דברים שכדאי להכיר מראש:

מקרה מה קורה איך מתכוננים
איפוס סיסמה בידי מנהל מערכת ההגנה שקשורה לסיסמת המשתמש מוסרת, ולפעמים לא ניתן לגשת עוד לנתונים שהוגנו ב-DPAPI. גם בחומרי התמיכה של Microsoft יש תיעוד של תופעה כזו אחרי איפוס סיסמה על ידי מנהל לתכנן כך שאפשר “לקבל מחדש” את הסוד. להפנות להזנה מחדש כשהפענוח נכשל
יצירה מחדש של הפרופיל לפרופיל החדש יש חומר מפתחות שונה, ולכן לא ניתן לפענח את הטקסט המוצפן הישן להחזיק גרסה לקובץ התצורה, ולא להפוך כשלון פענוח לסיום חריג
העתקת הטקסט המוצפן בלבד למחשב אחר חומר המפתחות הדרוש לפענוח נמצא בצד פרופיל המשתמש, ולכן אי אפשר לקרוא רק עם הטקסט המוצפן של CurrentUser (זה הצד השני של “החוזק” שהוזכר ב-3.1.) לתכנן מתוך הנחה שצריך לשמור מחדש בכל מכשיר
פרופיל נודד את זה כן ניתן לקרוא. מכיוון שחומר המפתחות זז יחד עם הפרופיל, גם Microsoft Learn כותב במפורש ש”משתמש עם פרופיל נודד יכול לפענח ממחשב אחר ברשת”. אם מתייחסים לזה כמו לשורה הקודמת, זה גורם ליצירה מחדש מיותרת של אישורים בהליך ההעברה לא להניח “כי זה מחשב אחר, לא ניתן לקרוא”. קובעים את הליך ההעברה אחרי בדיקת קיום רומינג
שינוי חשבון ההרצה של השירות אם חשבון ההרצה שונה בין זמן ההגנה לזמן הפענוח, לא ניתן לקרוא ב-CurrentUser לכלול בתפעול נוהל של הגנה מחדש בעת שינוי חשבון

בקיצור, כותבים קוד מתוך הנחה ש-ProtectedData.Unprotect עלול להיכשל. כשלון פענוח זורק CryptographicException, ולכן תופסים אותו ומפנים להזנה מחדש.

using System;
using System.Security.Cryptography;
using System.Text;

// protectedBase64: הטקסט המוצפן שנקרא מקובץ התצורה (Base64)
// entropy: מעבירים את אותו הערך כמו בזמן ההגנה. אפשר גם null
static bool TryUnprotect(string protectedBase64, byte[]? entropy, out string plaintext)
{
    plaintext = string.Empty;

    try
    {
        byte[] plainBytes = ProtectedData.Unprotect(
            Convert.FromBase64String(protectedBase64),
            optionalEntropy: entropy,
            scope: DataProtectionScope.CurrentUser);

        plaintext = Encoding.UTF8.GetString(plainBytes);
        return true;
    }
    catch (CryptographicException)
    {
        // לא ניתן לפענח = סביר שהסביבה השתנתה.
        // לא מפילים כאן, אלא מפנים בצד הקורא להזנה מחדש
        return false;
    }
    catch (FormatException)
    {
        // כשה-Base64 פגום
        return false;
    }
}

פנייה עם “הצפנו אבל לא ניתן לפענח” בדרך כלל שייכת לאחד מהמקרים בטבלה למעלה.

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

איור 11: כותבים בהנחה שהפענוח עלול להיכשל, ומחברים את הכשלון להזנה מחדש ולא לסיום חריג.

7. הנחיות מינימליות למימוש

אם רוצים רק “להפסיק להשתמש בטקסט גלוי בקובץ התצורה” ביישום Windows, לא חייבים לסבך את התכנון יותר מדי. עם זאת, יש כמה נקודות שלא כדאי לפספס.

7.1. מגנים רק על הסוד

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

לדוגמה, מפרידים כך:

  • כתובת URL של השרת
  • שם משתמש
  • שם מסד נתונים
  • דגלי תכונות

אלה בדרך כלל יכולים להישאר בטקסט גלוי.

מצד שני,

  • סיסמה
  • טוקן API
  • טוקן רענון
  • אישורים לתיקייה משותפת

אלה יעד ההגנה.

עם החלוקה הזו, מתקבל:

  • עריכת תצורה נוחה
  • קל לבדוק הפרש
  • ברור היכן הסוד
  • תפעול כולל פשוט
החלוקה שמגנה רק על פריטי הסודתרשים המראה שבמקום להצפין את כל התצורה, משאירים URL ושם משתמש בטקסט גלוי ומגנים רק על פריטים כמו סיסמה וטוקן, כך שקל יותר לערוך וברור היכן הסוד.נשאר בטקסט גלוימוגןקובץ התצורהURL, שם משתמש, דגליםסיסמה, טוקן וכדומהברור היכן הסוד, ותפעול פשוט

איור 12: לא הצפנה כוללת, אלא הגנה רק על פריטי הסוד — כך משיגים גם נוחות וגם ביטחון.

7.2. מיקום השמירה — בסיסי per-user

ביישום שולחני רגיל, מיקום השמירה הבסיסי הוא מיקום per-user.

  • %LocalAppData%\Vendor\App\settings.json
  • %AppData%\Vendor\App\settings.json

לפחות, בטוח יותר לא לשים ברשלנות תחת תיקיית ההתקנה או במקום שקל לשתף.

גם עם הגנת DPAPI, אם ה-ACL של מיקום השמירה רשלני, זה מגיע למצב של “הטקסט המוצפן נקרא”, “מבנה התצורה נראה”, “יש טעות תפעול”. ההגנה לא שכבה אחת, אלא ככל שיש יותר שכבות זה עוזר יותר.

7.3. optionalEntropy הוא לא מפתח שני שכל-יכול

ל-ProtectedData אפשר להעביר optionalEntropy. זה נוח, אבל זה לא “קסם שהופך לבטוח אם מטמיעים את זה בבינארי” כמפתח שני.

  • אם שמים באותו קובץ, זה לא הופך לסוד
  • גם אם מטמיעים בבינארי כערך קבוע, זה לא סוד חזק
  • ובכל זאת, זה מועיל לזיהוי שימוש ולמניעת שימוש שגוי

בעבודה מעשית, נוח להעביר

  • שם היישום
  • שם השימוש
  • מזהה גרסה

כרצף בייטים קבוע, כדי “לא לקבל בטעות טקסט מוצפן ממטרה אחרת” — זה בערך המידה הנכונה.

המקום הנכון של optionalEntropyתרשים המראה ש-optionalEntropy הוא לא מפתח שני קסם שהופך לבטוח בהטמעה בבינארי, אלא מתאים כתגית זיהוי שימוש שמעבירים כרצף בייטים קבוע של שם היישום כדי למנוע קבלת טקסט מוצפן ממטרה אחרת בטעות.לא זו הציפייהזה השימוש הנכוןoptionalEntropyמפתח שני קסםמזהה שם יישום / שימושמניעת קבלה בטעות של טקסט ממטרה אחרת

איור 13: entropy הוא לא מפתח סוד, אלא תגית לזיהוי שימוש ומניעת שימוש שגוי.

7.4. אפשר להכניס טקסט מוצפן ל-Git — לא, זה לא נכון

גם זו נקודה חשובה בשקט.

הטקסט המוצפן של DPAPI טוב הרבה יותר מטקסט גלוי, אבל זה לא אומר שאפשר להכניס את קובץ התצורה כולו למאגר הקוד.

הסיבה פשוטה:

  • טקסט מוצפן נשאר זמן ארוך
  • ייתכן שאותו מכשיר או אותו הקשר יתקיים שוב יום אחד
  • הקובץ מכיל גם מידע שאינו הסוד
  • נוצרת תרבות של “כי זה מוגן, אפשר לטפל בזה ברשלנות”

בגלל אלה.

“טוב יותר מטקסט גלוי” ו- “בטוח בכל מקום שהוא” הם שני דברים שונים לגמרי.

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

איור 14: “טוב יותר מטקסט גלוי” לא סיבה לטפל ברשלנות במיקום השמירה.

7.5. לא מוציאים ללוג

התופעה שקורית לא מעט היא ששמים הכול על הפרק כשמוציאים ללוג אחרי הפענוח.

  • הוצאת מחרוזת החיבור כולה בעת כשלון חיבור
  • השארת כותרת Authorization ב-API בעת שגיאת 401
  • ערבוב סוד בהודעת חריגה

אם עושים את זה, גם אם הפסיקו עם טקסט גלוי בקובץ התצורה, הלוג הופך למחסן טקסט גלוי בסופו של דבר. מעציב, אבל זה עבודה מעשית לגמרי.

8. דוגמת מימוש מינימלית ב-C#‏ /‏ .NET

8.1. קודם, נדרשת הוספת הפניה

ProtectedData נראה כאילו כלול ב-BCL, אבל “מאיפה זה מגיע” משתנה לפי ה-target framework. אם נתקעים כאן, שם הסוג ProtectedData פשוט לא נפתר.

יעד פעולה נדרשת מקור
‏.NET Framework להוסיף לפרויקט הפניה ל-assembly בשם System.Security System.Security.dll
‏.NET Core /‏ .NET 5 ואילך (כולל ‎.NET 6 /‏ 8) להוסיף את חבילת ה-NuGet System.Security.Cryptography.ProtectedData System.Security.Cryptography.ProtectedData.dll

החבילה הזו לא כלולה באף shared framework מ-‎.NET Core /‏ .NET 5 ואילך. גם ביעד ייעודי ל-Windows כמו net8.0-windows, נדרשת הפניה מפורשת.

dotnet add package System.Security.Cryptography.ProtectedData

עוד נקודה שכדאי לדעת לפני המימוש. ProtectedData ייעודי ל-Windows. מכיוון שהוא תלוי ב-DPAPI, אם קוראים לו מ-‎.NET על פלטפורמה שאינה Windows, נזרקת PlatformNotSupportedException. בבסיס קוד שמניח חוצה-פלטפורמות, מתכננים אחרת מלכתחילה, כפי שכתוב ב-10.1.

מה בודקים לפני שימוש ב-ProtectedDataתרשים המראה שלפני שימוש ב-ProtectedData נדרשת הוספת הפניה בהתאם ליעד, אחרת שם הסוג לא נפתר, וגם הבנה שזה ייעודי ל-Windows, אחרת קריאה מחוץ ל-Windows גורמת לחריגה.בלי זהקריאה מחוץ ל-Windowsרוצים להשתמש ב-ProtectedDataהוספת הפניה בהתאם ליעדשם הסוג לא נפתרהבנה שזה ייעודי ל-Windowsחריגה בזמן ריצה

איור 15: לפני המימוש, מוודאים שתי הנחות — הוספת ההפניה, וההיות ייעודי ל-Windows.

8.2. מימוש מינימלי

להלן דוגמה מינימלית להגנה על מחרוזת שנשמרת בקובץ תצורה עם CurrentUser. לצורך זיהוי שימוש הוכנס optionalEntropy קבוע, אבל אל תחשבו על זה כמפתח סוד.

using System;
using System.Security.Cryptography;
using System.Text;

public static class DpapiSecretProtector
{
    // לזיהוי שימוש. לא מפתח סוד שני.
    private static readonly byte[] Entropy =
        Encoding.UTF8.GetBytes("ComComponent:DesktopApp:SettingsSecret:v1");

    public static string ProtectToBase64(string plaintext)
    {
        ArgumentNullException.ThrowIfNull(plaintext);

        byte[] plainBytes = Encoding.UTF8.GetBytes(plaintext);
        byte[] protectedBytes = Array.Empty<byte>();

        try
        {
            protectedBytes = ProtectedData.Protect(
                plainBytes,
                optionalEntropy: Entropy,
                scope: DataProtectionScope.CurrentUser);

            return Convert.ToBase64String(protectedBytes);
        }
        finally
        {
            Array.Clear(plainBytes, 0, plainBytes.Length);

            if (protectedBytes.Length > 0)
            {
                Array.Clear(protectedBytes, 0, protectedBytes.Length);
            }
        }
    }

    public static string UnprotectFromBase64(string protectedBase64)
    {
        ArgumentNullException.ThrowIfNull(protectedBase64);

        byte[] protectedBytes = Convert.FromBase64String(protectedBase64);
        byte[] plainBytes = Array.Empty<byte>();

        try
        {
            plainBytes = ProtectedData.Unprotect(
                protectedBytes,
                optionalEntropy: Entropy,
                scope: DataProtectionScope.CurrentUser);

            return Encoding.UTF8.GetString(plainBytes);
        }
        finally
        {
            Array.Clear(protectedBytes, 0, protectedBytes.Length);

            if (plainBytes.Length > 0)
            {
                Array.Clear(plainBytes, 0, plainBytes.Length);
            }
        }
    }
}

אופן השימוש פשוט.

string protectedPassword = DpapiSecretProtector.ProtectToBase64(password);

// שמירה ל-JSON וכדומה
// settings.DbPasswordProtected = protectedPassword;

string password = DpapiSecretProtector.UnprotectFromBase64(settings.DbPasswordProtected);

קובץ התצורה יכול, לדוגמה, להיראות כך:

{
  "ApiBaseUrl": "https://api.example.com/",
  "UserName": "app-user",
  "PasswordProtected": "AQAAANCMnd8BFdERjHoAwE..."
}

היתרון בצורה הזו:

  • ‏URL ושם משתמש ניתנים לעריכה רגילה
  • רק הסיסמה מוגנת
  • מבנה התצורה קריא
  • פחות תקלות משמירה בטקסט גלוי

9. תכנון שעדיין מסוכן

גם עם שימוש ב-DPAPI, התכנונים הבאים עדיין מסוכנים.

9.1. מחזיקים את הערך המפוענח זמן רב

הערך שפוענח, אם

  • מוציאים ללוג
  • מציגים על המסך
  • כוללים בחריגה
  • משאירים על אובייקט ארוך-חיים

טוב להימנע.

“בעת שמירה — מוצפן” ו- “גם בזמן שימוש — בטוח” הם עניינים נפרדים.

9.2. מחזיקים סוד משותף לכל ההתקנות

תכנון שבו כל המשתמשים מחזיקים את אותו מפתח API, גם אם שומרים אותו ב-DPAPI, לא פותר את הבעיה מהשורש. הסיבה, ולאן להעביר אותו במקום, מרוכזות ב-5.2.

9.3. בוחרים ב-LocalMachine בגלל “נוחות”

גם זו תופעה נפוצה מאוד. אבל זו “נוחות”, לא “הגנה”. המניע לבחירה, ומה מתרחב באותו רגע, כתוב ב-6.2. אם מתלבטים, ראו את שלוש השורות ב-6.3.

9.4. מוסיפים הצפנה עצמית ומרגישים בטוחים

במקום DPAPI, להכניס מימוש כמו

  • הטמעת מפתח AES בקוד המקור
  • הצבת מפתח AES בפריט נפרד בקובץ התצורה
  • התייחסות ל”מחרוזת שעברה ערפול קל” כאל מפתח

בדרך כלל אפקטיביות חלשה.

יש תהום גדולה בין “לא בטקסט גלוי” ל”בטוח”.

הסכנה בהרגשת ביטחון עם הצפנה עצמיתתרשים המראה שהצפנה עצמית שמטמיעה מפתח AES בקוד או בפריט נפרד בתצורה, או מתייחסת למחרוזת מעורפלת כמפתח, אפקטיביות חלשה, ויש תהום גדולה בין לא בטקסט גלוי לבטוח.תהום גדולה ביניהםהצפנה עצמית: מפתח בקוד או בתצורהמצב של 'לא בטקסט גלוי'מצב של 'בטוח'האפקטיביות בדרך כלל חלשה

איור 16: הצפנה עצמית עם מפתח באותו מקום היא רק “לא טקסט גלוי”, לא מגיעה ל”בטוח”.

10. מקרים שבהם DPAPI לא מספיק

DPAPI נוח, אבל הוא לא כל-יכול. במקרים הבאים כדאי לחשוב על אפשרות אחרת.

10.1. רוצים להריץ גם מחוץ ל-Windows

DPAPI /‏ ProtectedData מיועדים ל-Windows. ביישום חוצה-פלטפורמות, אי אפשר לבנות על ההנחה הזו.

10.2. רוצים לטפל באותו סוד בכמה מכונות / כמה משתמשים

דרישה כמו רצון לפענח את אותו טקסט מוצפן בכמה מחשבים, או לשתף בין כמה משתמשים, נמצאת מחוץ לתחום החוזק של DPAPI ש”קושר לאותו מכשיר, לאותו משתמש”.

במקרה הזה, כדאי לחשוב על תכנון אחר שמתאים לדרישה, כמו:

  • ניהול סוד בצד השרת
  • תשתית אישורים
  • אימות Windows / אימות משולב
  • מאגר אישורים ייעודי ליישום

10.3. מה ששומרים הוא בעצמו אישורי המשתמש

אם מה שרוצים לשמור הוא בבירור צמד של

  • שם משתמש
  • סיסמה

טבעי יותר להשתמש במאגר האישורים שמוכן ב-Windows, במקום לכתוב ב-DPAPI לקובץ עצמכם. זו נקודה שנוטים להתלבט בה בעבודה מעשית, ולכן נציג השוואה.

היבט DPAPI (‏ProtectedData) Credential Locker (‏PasswordVault) Credential Manager (‏CredWrite /‏ CredRead)
מיקום השמירה קובץ שקובעים בעצמכם (איך שמים את הטקסט המוצפן תלוי ביישום) מאגר אישורים שמנוהל על ידי Windows מאגר אישורים שמנוהל על ידי Windows
מה אפשר לשמור כל רצף בייטים (מחרוזת חיבור, טוקן, גם חלק מתצורה) צמד שם משתמש + סיסמה אישורים (מבנה לפי סוג)
API System.Security.Cryptography Windows.Security.Credentials של WinRT Win32 (‏wincred.h /‏ Advapi32.dll)
האם ניתן להשתמש מיישום שולחני ניתן לשימוש כמו שהוא ניתן לשימוש לא רק מ-WinUI, אלא גם מ-WPF /‏ WinForms (נדרשת הגדרה לקריאת WinRT API) ניתן לשימוש כמו שהוא
סנכרון אין מסתנכרן בין מכשירים עם חשבון Microsoft אין (סט אישורים מקומי למשתמש)
מגבלה בפועל אין עד 20 רשומות ליישום. לא מיועד לנתונים גדולים קשור לפגישת הכניסה של הטוקן הנוכחי
גורם הניהול היישום (גם מיקום השמירה וגם ה-ACL נקבעים לבד) מערכת ההפעלה (לא צריך לתכנן מיקום שמירה) מערכת ההפעלה (אפשר לנהל מ-Credential Manager בלוח הבקרה)

הקווים המנחים לבחירה:

  • מה שרוצים לשמור הוא צמד שם משתמש + סיסמה, ומספר הרשומות קטן ← Credential Locker /‏ Credential Manager הם המועמד הראשון. לא צריך להחזיק בעצמכם תכנון מיקום שמירה ולא ACL
  • מה שרוצים לשמור לא בצורת “שם משתמש + סיסמה” ← מחרוזת חיבור, טוקן API, טוקן רענון, חלק מקובץ תצורה — כאלה, צד DPAPI טבעי יותר. זה מה שהמאמר הזה עוסק בו
  • רוצים להעביר בין מכשירים ← הרומינג של Credential Locker יעיל. ה-CurrentUser של DPAPI, להפך, יתרונו הוא ש”לא מועבר”, ולכן כאן המטרה הפוכה
  • מספר הרשומות גדול / הגודל גדול ← נתקלים במגבלת 20 הרשומות של Credential Locker. עוברים ל-DPAPI עם קובץ עצמכם

בנוסף, גם תוכן מאגר האישורים בנוי על מנגנון ההגנה של מערכת ההפעלה, אז זה לא סדר של “בטוח יותר מ-DPAPI” או “DPAPI נחות”. נכון לבחור לפי צורת מה שרוצים לשמור, ולפי הצורך ברומינג.

ולא משנה מה בוחרים, ביישום חדש שווה לבדוק קודם גם פתרון בלי סיסמה כמו Windows Hello / passkey. אם אפשר מלכתחילה לא להחזיק סיסמה ארוכת-טווח, זה החזק ביותר.

מוקד המאמר הזה נשאר, בסופו של דבר, בקו המעשי של DPAPI ל“להפסיק עם קובץ תצורה בטקסט גלוי בלקוח Windows”.

11. סדר עדיפויות מומלץ לעבודה מעשית

לבסוף, כשמתלבטים בעבודה מעשית, נוח יותר לחשוב לפי הסדר הזה. בודקים מלמעלה למטה, ויורדים למטה רק כשלא מתקיים התנאי.

זרימת ההחלטה לבחירת שיטת שמירהתרשים המראה שבוחרים תחילה אם אפשר בלי סוד ארוך-טווח, אחר כך אם אפשר להפריד לפי משתמש, אחר כך את צורת מה שרוצים לשמור ואת הצורך בהעברה בין מכשירים, ומגיעים ל-DPAPI עם CurrentUser או LocalMachine רק כשלא מתקיימות האפשרויות הקודמות.כןלאלאכןכן, מעט רשומותכןכןדומיין / חשבון מקומילאטוקן וכדומהכןלאאחד (המשתמש עצמו, או חשבון שירות ייעודי)כמה חשבונותבטוחלא בטוחאפשר בלי סוד ארוך-טווח בלקוח?עדיפות 1: לא מחזיקים (אימות Windows / טוקן קצר)אפשר להפריד את הסוד לפי משתמש?בדקו אם זה מפתח משותף — תכננו מחדשהצורה: צמד שם משתמש + סיסמה? (10.3)נדרשת העברה בין מכשירים?מסתנכרן עם חשבון Microsoft? (10.3)Credential Locker עם רומינגDPAPI לא מעביר. ניהול בצד שרת (10.2)Credential Locker /‏ Credential Managerנדרשת העברה בין מכשירים?כמה חשבונות מפענחים את אותו סוד?עדיפות 3: DPAPI + CurrentUser. בהרצה ללא נוכחות, בדקו טעינת פרופיל (6.4)בטוח שלא נכנסים משתמשים אחרים?עדיפות 4: DPAPI + LocalMachine כחריג מתועדגם משתמשים אחרים יפענחו — בדקו את שיטת האימות

איור 17: סדר בחירת שיטת השמירה. קודם בודקים את צורת הסוד ואת הצורך ברומינג, ורק אז נכנסים ל-DPAPI. LocalMachine נבחר לא כי “זה נוח”, אלא כחריג כשאין אפשרות אחרת.

עדיפות 1: מלכתחילה לא להחזיק

  • אימות Windows
  • אימות משולב
  • כניסה אינטראקטיבית
  • החזקת הסוד בצד השרת
  • טוקן קצר-חיים

עדיפות 2: מעבירים לסוד per-user

  • per-user עדיף על סוד משותף
  • טוקן שניתן לעדכון עדיף על אישור קבוע ארוך-טווח
  • נמנעים ממפתח משותף לכל הלקוחות

עדיפות 3: אם נדרשת שמירה מקומית, DPAPI

  • בדרך כלל CurrentUser
  • מיקום השמירה — per-user
  • מגנים רק על פריטי הסוד
  • לא מוציאים ללוג

עדיפות 4: LocalMachine כחריג

  • האם באמת צריך הגנה ברמת מכונה
  • האם לא נכנסים משתמשים אחרים באותו מכשיר
  • האם זה הגיוני כתכנון שירות

12. סיכום

כשצריך לשמור מידע רגיש בקובץ תצורה של יישום Windows, כדאי להימנע משמירה בטקסט גלוי.

ולשאלה:

“בכל מקרה שומרים את המפתח איפשהו, אז זה לא אותו דבר?”

התשובה המעשית היא:

  • בהצפנה עצמית עם מפתח באותו מקום, זה כמעט אותו דבר
  • DPAPI לא אותו דבר
    • אפשר להעביר את ניהול המפתח למערכת ההפעלה
    • אפשר לקשור את מי שיכול לפענח למשתמש Windows / למחשב
    • אפשר להימנע מכך שדליפת קובץ בודד תהפוך ישירות לדליפת סוד
  • עם זאת
    • קוד שרץ באותה הרשאת משתמש
    • מכשיר שנפרץ לגמרי
    • סוד ארוך-טווח משותף שלא צריך להיות אצל הלקוח מלכתחילה

אלה לא נפתרים

בקיצור, DPAPI הוא לא חומת מבצר כל-יכולה. אבל יש לו לפחות אפקט של החלפת חלון ראווה שקוף לגמרי (טקסט גלוי בתצורה) בחלון מינימלי הגון.

בעבודה המעשית של לקוח Windows, ההבדל הזה משמעותי מאוד. להתחיל מלא לפספס את הנקודה הזו, זה המעשי ביותר.

המקום המעשי של DPAPIתרשים המראה ש-DPAPI אינו חומת מבצר כל-יכולה, אבל יש לו אפקט של החלפת חלון ראווה שקוף לגמרי בתצורה גלויה בחלון מינימלי הגון, ומעשי יותר להתחיל בדיוק מכאן.החלפה ל-DPAPIתצורה גלויה = חלון ראווה שקוףחלון מינימלי הגוןלא חומת מבצר כל-יכולהמעשי יותר להתחיל בדיוק מכאן

איור 18: DPAPI הוא לא חומה אלא החלפת חלון, אבל בעבודה מעשית ההבדל הזה משפיע הכי הרבה.

13. מקורות

  • המאמר הקודם: https://comcomponent.com/he/blog/windows-app-security-minimum-checklist/
  • Microsoft Learn: CryptProtectData https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
  • Microsoft Learn: ProtectedData https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata?view=windowsdesktop-10.0
  • Microsoft Learn: DataProtectionScope https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotectionscope?view=windowsdesktop-10.0
  • Microsoft Learn: How to: Use Data Protection https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
  • Microsoft Learn: Credential locker for Windows apps https://learn.microsoft.com/en-us/windows/apps/develop/security/credential-locker
  • Microsoft Learn: CredWrite‏ (Win32 API של Windows Credential Manager) https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credwritew
  • NuGet: System.Security.Cryptography.ProtectedData https://www.nuget.org/packages/System.Security.Cryptography.ProtectedData
  • Microsoft Support: תופעה שבה לא ניתן לגשת לנתוני DPAPI אחרי שמנהל מערכת איפס את הסיסמה https://support.microsoft.com/en-us/topic/you-cannot-access-dpapi-data-after-an-administrator-resets-your-password-on-a-windows-server-2012-based-domain-controller-4aa890cd-12b5-fe5c-9e68-06244e70673d

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

פיתוח יישומי Windows

זה נושא שנוגע בתכנון כולל של יישום Windows, כולל שיטת שמירת אישורים, מיקום שמירת תצורה לפי משתמש, ואופן הוצאת מידע ללוגים, ולכן הוא מתאים היטב לפיתוח יישומי Windows.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

מה זה DPAPI?
DPAPI‏ (Data Protection API) הוא מנגנון הגנת נתונים שמספק Windows, שמעביר את ניהול מפתחות ההצפנה למערכת ההפעלה, וקושר את מי שיכול לפענח למשתמש Windows הספציפי או למחשב הספציפי. מ-C#‏ /‏ .NET אפשר להשתמש בו דרך המחלקה System.Security.Cryptography.ProtectedData בלי להוסיף ספריות מיותרות. קרוב יותר למהות לראות בו לא API לבחירת אלגוריתם הצפנה, אלא API שמעביר את ניהול המפתחות למערכת ההפעלה. זו בחירה מעשית כדי לא להשאיר סיסמאות וטוקני API בתצורה גלויה.
המפתח נשמר איפשהו בכל מקרה, אז זה לא אותו דבר בטקסט גלוי או ב-DPAPI?
זה לא אותו דבר. אם שומרים את המפתח באותו יישום או קובץ תצורה עם הצפנת AES עצמית, זה קרוב מאוד לטקסט גלוי, אבל DPAPI מעביר את ניהול המפתח למערכת ההפעלה, וקושר את מי שיכול לפענח למשתמש Windows או למחשב. כתוצאה מכך, החוסן משתנה משמעותית מול תקריות כמו דליפת קובץ תצורה בודד, הוצאה למחשב אחר, שליחה בטעות, דליפת גיבוי, או הכנסה למאגר קוד. ההבדל המכריע של DPAPI מטקסט גלוי הוא שהוא יכול להפריד בין 'יכולת לקרוא את הקובץ' ל'יכולת להשתמש בסוד'.
מה DPAPI לא מגן עליו?
קוד שרץ באותה הרשאת משתמש יכול בעיקרון לפענח כל מה שאותו משתמש יכול לפענח. לכן זה לא מגן על מצב שבו המכשיר כבר נפרץ על ידי תוכנה זדונית, מצב של השתלטות בהרשאות מנהל, או על הטקסט הגלוי בזיכרון אחרי הפענוח. בנוסף, סוד ארוך-טווח שמפיצים באופן זהה לכל הלקוחות נוטה להתפשט לכולם ברגע שהוא נגנב ממכשיר אחד, ולכן שמירתו ב-DPAPI לא פותרת את הבעיה מהשורש. DPAPI יעיל בעיקר מול דליפת קבצים, מיקום שגוי, הוצאה במצב לא מקוון, ועיון על ידי משתמש אחר.
איזה מבין CurrentUser ו-LocalMachine ב-DataProtectionScope כדאי להשתמש בו?
ביישום שולחני רגיל של Windows, CurrentUser הוא הבסיס. הוא מטופל כסוד של אותו משתמש, ומקבלים את התכונה שקשה להשתמש בטקסט מוצפן שהועתק למחשב אחר כמו שהוא. LocalMachine ניתן לפענוח באופן רחב מכל תהליך שרץ על אותו מחשב, ולכן הוא נוטה להיות מסוכן במכשיר משותף או בסביבה עם כמה משתמשים, והשימוש בו מוגבל מאוד למקרים כמו שירות Windows על מכונה ייעודית ומהימנה. אם בוחרים ב-LocalMachine כי 'זה נוח לכולם', זה בדרך כלל גורם לבעיה בהמשך.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג