Secrets ביישומי Windows: DPAPI במקום plaintext ב-config

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

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 16 Mar 2026)
פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173498)

מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.

Go Komura (2026). Secrets ביישומי Windows: DPAPI במקום plaintext ב-config. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173498 https://comcomponent.com/he/blog/windows-app-secret-storage-best-practices-dpapi/

DOI (הגרסה האחרונה)
10.5281/zenodo.22173498
DOI (הגרסה הזו)
10.5281/zenodo.22173499

במאמר הקודם, “רשימת בדיקה מינימלית לאבטחה בפיתוח יישומי Windows”, כתבנו את הרף המינימלי: “לא לשים secrets בקוד או ב-plaintext ב-config” ו-“ב-Win32 / .NET, להשתמש ב-DPAPI / ProtectedData”.

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

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

  • אפליקציות desktop ב-WPF / WinForms / WinUI
  • לקוחות Windows ב-C# / .NET
  • יישומים שרוצים לשמור בקובץ config מקומי credentials של חיבור או API tokens

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

1. קודם כל, המסקנה

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

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

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

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

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

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

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

לשים מפתח מתחת לשטיח מול למסור מפתח אחרי זיהוי במשרד — לקרוא לזה אותו דבר זה מוגזם.

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

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

ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 21, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle

2. למה plaintext ב-config מסוכן

הסיבה ששמירה ב-plaintext מסוכנת היא לא תיאוריית הצפנה. בפועל זה דולף במסלולים יומיומיים לגמרי:

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

ב-plaintext, ברגע שצד שלישי קרא, זה כבר לא secret.

  • פתחו את הקובץ — זהו
  • העתיקו — זהו
  • צירפו למייל — זהו
  • נשאר ב-repository — מטפלים בזה כמעט לנצח

התוקף אפילו לא צריך להיות מתוחכם. זה שאפשר לפתוח ב-text editor כבר חלש כשלעצמו.

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

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

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

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

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

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

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

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

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

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

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

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

איפה נשאר ה-master key מול ה-ciphertextתרשים שמראה שה-ciphertext הוא מה שאפשר להוציא מהמכשיר, ואילו master key של המשתמש או של המחשב נשאר בצד ה-OS ואינו נכלל ב-ciphertext, וכיצד כל אחד מהם קובע מי יכול לפענח.מה שנדרש לפענוח — נשאר בצד ה-OS, לא נכלל ב-ciphertextהגנה ב-CurrentUserהגנה ב-LocalMachinemaster key של המשתמש (כשההגנה היא CurrentUser)master key של המחשב (כשההגנה היא LocalMachine)ciphertext (נכנס לקובץ config / עמודה ב-DB / ZIP לחקירה = אפשר להוציא)קוד שרץ כאותו משתמש: מפענחמשתמש אחר באותו מחשב: לא מפענחקוד על אותו מחשב: מפענח גם למשתמש אחרהעתקת ciphertext בלבד למחשב אחר: לא מפענחמעבר עם roaming profile: key material זז יחד, ולכן מפענח (סעיף 6.5)

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

ב-plaintext, שני אלה זהים. אם אפשר לקרוא את הקובץ, אפשר לקרוא גם את ה-secret.

אבל ב-DPAPI, לפחות ב-CurrentUser, צריך לפענח:

  • כמשתמש Windows הזה
  • ב-context של אותו Windows
  • דרך מנגנון ההגנה של ה-OS

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

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

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

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

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

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

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

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

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

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

שניהם מסוכנים, גם אם זה לא נשמע דרמטי.

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

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

3.3. אז מה היתרון

בקצרה, היתרון של DPAPI הוא זה:

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

זה הכול.

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

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

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

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

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

4. למה DPAPI מתאים כאן בפועל

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

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

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

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

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

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

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

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

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

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

  • שהמשתמש הזה מחובר (logon)
  • שהעיבוד רץ ב-context של אותו משתמש

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

4.3. קל לכלול גם זיהוי שינוי

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

ל-DPAPI יש גם integrity protection על הנתונים המוצפנים, ולכן יש יתרון מעשי: קל לכלול גם זיהוי של שינוי לא מורשה ב-ciphertext במנגנון בצד ה-OS.

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

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

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

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

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

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

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

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

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

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

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

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

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

למשל, תכנון מהסוג הזה:

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

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

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

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

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

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

  • שמירה בצד השרת
  • הלקוח מחזיק רק token
  • להפוך ל-credentials אישיים לפי משתמש
  • להפוך ל-token עם תוקף

6. איך מחלקים בין CurrentUser ל-LocalMachine

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

6.1. הבסיס הוא CurrentUser

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

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

  • אפליקציית desktop למשתמש ב-WPF / WinForms / WinUI
  • יישום שמחזיק config או credentials לפי משתמש
  • יישום שמחזיק config תחת %LocalAppData% או %AppData%

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

מקרה מה קורה איך מתכוננים
איפוס סיסמה בידי Administrator ההגנה שקשורה לסיסמת המשתמש מתנתקת, ולפעמים אי אפשר יותר לגשת לנתונים שהוגנו ב-DPAPI. גם בחומרי התמיכה של Microsoft יש תיעוד של תופעה כזו אחרי איפוס סיסמה על ידי Administrator לתכנן כך שאפשר “לקבל מחדש” את ה-secret. כשהפענוח נכשל, מפנים להזנה מחדש
יצירה מחדש של ה-profile ל-profile החדש יש key material אחר, ולכן אי אפשר לפענח את ה-ciphertext הישן מחזיקים גרסה לקובץ ה-config, ולא הופכים כשלון פענוח לקריסה
העתקת ciphertext בלבד למחשב אחר ה-key material הדרוש לפענוח נמצא בצד ה-user profile, ולכן אי אפשר לקרוא רק עם ciphertext של CurrentUser (זה הצד השני של “החוזק” שהוזכר ב-3.1) מתכננים מתוך הנחה שצריך לשמור מחדש בכל מכשיר
roaming profile את זה כן אפשר לקרוא. מכיוון ש-key material זז יחד עם ה-profile, גם Microsoft Learn כותב במפורש ש”משתמש עם roaming profile יכול לפענח ממחשב אחר ברשת”. אם מתייחסים לזה כמו לשורה הקודמת, זה גורם ליצירה מחדש מיותרת של credentials בהליך ההעברה לא להניח “כי זה מחשב אחר, אי אפשר לקרוא”. קובעים את הליך ההעברה אחרי בדיקה אם יש roaming
שינוי חשבון ההרצה של ה-service אם חשבון ההרצה שונה בין זמן ה-Protect לזמן ה-Unprotect, אי אפשר לקרוא ב-CurrentUser כוללים בתפעול נוהל של Protect מחדש בעת שינוי חשבון

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

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

// protectedBase64: ciphertext שנקרא מקובץ ה-config (Base64)
// entropy: מעבירים את אותו ערך כמו ב-Protect. אפשר גם 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שימוש ב-secretתופסים את החריגה בלי לקרוסהפניה להזנה מחדשהפענוח עלול להיכשל אם הקשר נשבר

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

7. קווים מנחים מינימליים למימוש

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

7.1. מגנים רק על ה-secret

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

למשל, מפרידים כך:

  • URL של השרת
  • שם משתמש
  • שם מסד נתונים
  • feature flags

אלה בדרך כלל יכולים להישאר ב-plaintext.

מצד שני,

  • סיסמה
  • API token
  • refresh token
  • credentials לתיקייה משותפת

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

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

  • עריכת config נוחה
  • קל לבדוק diff
  • ברור איפה ה-secret
  • תפעול כולל פשוט
החלוקה שמגנה רק על פריטי ה-secretתרשים שמראה שבמקום להצפין את כל ה-config, משאירים URL ושם משתמש ב-plaintext ומגנים רק על פריטים כמו סיסמה ו-token, כך שקל יותר לערוך וברור איפה ה-secret.נשאר ב-plaintextמוגןקובץ ה-configURL, שם משתמש, flagsסיסמה, token וכדומהברור איפה ה-secret, ותפעול פשוט

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

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

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

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

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

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

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

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

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

בפועל, נוח להעביר

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

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

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

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

7.4. אפשר להכניס ciphertext ל-Git — לא

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

ה-ciphertext של DPAPI טוב בהרבה מ-plaintext, אבל זה לא אומר שאפשר להכניס את קובץ ה-config כולו ל-repository.

הסיבה פשוטה:

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

בגלל אלה.

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

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

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

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

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

  • הוצאת ה-connection string כולו בעת כשלון חיבור
  • השארת כותרת Authorization ב-API בעת שגיאת 401
  • ערבוב secret בהודעת exception

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

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

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

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

יעד פעולה נדרשת מקור
.NET Framework להוסיף לפרויקט reference ל-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, נדרש reference מפורש.

dotnet add package System.Security.Cryptography.ProtectedData

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

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

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

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

להלן דוגמה מינימלית להגנה על מחרוזת שנשמרת בקובץ config עם 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);

קובץ ה-config יכול, למשל, להיראות כך:

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

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

  • URL ושם משתמש ניתנים לעריכה רגילה
  • רק הסיסמה מוגנת
  • מבנה ה-config קריא
  • פחות תקלות משמירה ב-plaintext

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

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

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

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

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

עדיף להימנע.

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

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

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

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

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

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

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

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

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

יש פער גדול בין “לא plaintext” לבין “בטוח”.

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

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

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

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

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

DPAPI / ProtectedData מיועדים ל-Windows. ביישום cross-platform אי אפשר לבנות על ההנחה הזו.

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

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

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

  • ניהול secrets בצד השרת
  • תשתית credentials
  • Windows authentication / integrated authentication
  • מאגר credentials ייעודי ליישום

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

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

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

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

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

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

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

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

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

מוקד המאמר הזה נשאר, בסופו של דבר, בקו המעשי של DPAPI ל“להפסיק עם קובץ config ב-plaintext בלקוח Windows”.

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

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

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

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

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

  • Windows authentication
  • integrated authentication
  • logon אינטראקטיבי
  • החזקת ה-secret בצד השרת
  • token קצר-חיים

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

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

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

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

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

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

12. סיכום

כשצריך לשמור מידע רגיש בקובץ config של יישום Windows, כדאי להימנע משמירה ב-plaintext.

ולשאלה:

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

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

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

בקיצור, DPAPI אינו חומת מגן מושלמת. אבל יש לו אפקט של החלפת חלון plaintext פתוח לרווחה בקובץ ה-config, בחלון סביר מינימלית.

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

המיקום המעשי של DPAPIתרשים שמראה ש-DPAPI אינו חומת מגן מושלמת, אבל הוא מחליף config ב-plaintext — חלון פתוח לרווחה — בחלון סביר מינימלית, ומשם נכון להתחיל.מחליפים ב-DPAPIplaintext ב-config = חלון פתוח לרווחהחלון סביר מינימליתלא חומת מגן מושלמתמשם נכון להתחיל בפועל

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

13. מקורות

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

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

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

שאלות נפוצות

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

מה זה DPAPI?
DPAPI (Data Protection API) הוא מנגנון להגנת נתונים ש-Windows מספק. הוא מעביר את ניהול מפתחות ההצפנה ל-OS, וקושר את מי שיכול לפענח למשתמש Windows הספציפי או למחשב הספציפי. מ-C# / .NET משתמשים בו דרך המחלקה System.Security.Cryptography.ProtectedData, בלי להוסיף ספריות מיותרות. נכון יותר לראות בו לא API לבחירת אלגוריתם הצפנה, אלא API שמעביר את ניהול המפתחות ל-OS. זו בחירה מעשית כדי לא להשאיר סיסמאות ו-API tokens ב-plaintext בקובץ config.
המפתח נשמר איפשהו בכל מקרה, אז plaintext ו-DPAPI זה אותו דבר?
לא. אם מצפינים ב-AES עצמאי ושומרים את המפתח באותו יישום או באותו קובץ config, זה קרוב מאוד ל-plaintext. DPAPI מעביר את ניהול המפתח ל-OS, וקושר את מי שיכול לפענח למשתמש Windows או למחשב. בגלל זה החוסן משתנה משמעותית מול תקריות כמו דליפה של קובץ ה-config לבדו, הוצאה למחשב אחר, שליחה בטעות, דליפת גיבוי, או הכנסה ל-repository. ההבדל המכריע מול plaintext: DPAPI מפריד בין 'אפשר לקרוא את הקובץ' לבין 'אפשר להשתמש ב-secret'.
מה DPAPI לא מגן עליו?
קוד שרץ באותן הרשאות משתמש יכול, ככלל, לפענח כל מה שאותו משתמש יכול לפענח. לכן DPAPI לא מגן על מצב שבו המכשיר כבר נפרץ על ידי malware, על השתלטות בהרשאות Administrator, או על ה-plaintext בזיכרון אחרי פענוח. בנוסף, secret ארוך-טווח שמפיצים זהה לכל הלקוחות נוטה להתפשט לכולם ברגע שחילצו אותו ממחשב אחד, ולכן שמירה ב-DPAPI לא פותרת את זה מהשורש. DPAPI יעיל בעיקר מול דליפת קבצים, מיקום שגוי, הוצאה offline, וקריאה על ידי משתמש אחר.
ב-DataProtectionScope, לבחור CurrentUser או LocalMachine?
ביישום desktop רגיל של Windows, CurrentUser הוא הבסיס. מתייחסים לזה כ-secret של אותו משתמש, ומקבלים את התכונה ש-ciphertext שהועתק למחשב אחר קשה לשימוש כמו שהוא. LocalMachine ניתן לפענוח באופן רחב מכל process שרץ על אותו מחשב, ולכן הוא מסוכן במכשיר משותף או בסביבה עם כמה משתמשים. השימוש בו מוגבל מאוד — למשל Windows service על מכונה ייעודית ומהימנה. אם בוחרים LocalMachine כי 'כולם יכולים להשתמש, זה נוח' — בדרך כלל זה חוזר כבעיה אחר כך.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג