26/05/2019

עצור, סיסמא!

שיתוף ב facebook
שיתוף ב twitter
שיתוף ב linkedin

בפוסט זה נכתוב קצת על סיסמאות… איך שומרים? איפה שומרים? איך משחזרים?

עצור, סיסמא!

סיסמאות בצד הלקוח

משתמש חדש שנרשם למערכת עובר דרך טופס בו אנו מבקשים את פרטיו. בין היתר, נבקש שם משתמש (או שנשתמש בכתובת האימייל שלו כשם משתמש) וסיסמא. שדה הסיסמא יהיה מוגדר כך שהתווים המוקלדים לא חשופים (מגדירים את שדה ה- input מסוג password).

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

סיסמאות בצד השרת

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

הדרך הטובה ביותר היא כמובן לקודד את הסיסמא ולשמור אותה מקודדת בבסיס הנתונים. איך עושים זאת? ואיך נדע לשחזר סיסמא? תמשיכו לקרוא.

קידוד סיסמא בעזרת Salt

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

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

Adding random data to the input of a hash function to guarantee a unique output, the hash, even when the inputs are the same.

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

טוב, אז קידדנו את הסיסמא בעזרת Salt ויש בידינו את המחרוזת המקודדת – אותה אנו שומרים בבסיס הנתונים. המתודה לקידוד הסיסמא בה השתמשנו (שוב, ניתן למצוא דוגמאות רבות בחיפוש קליל בגוגל):

public static string ComputeHash(string plainText, string hashAlgorithm, byte[] saltBytes)
{
    // If salt is not specified, generate it.
    if (saltBytes == null)
    {
// Define min and max salt sizes.
int minSaltSize = 4;
int maxSaltSize = 8;

// Generate a random number for the size of the salt.
Random random = new Random();
int saltSize = random.Next(minSaltSize, maxSaltSize);

// Allocate a byte array, which will hold the salt.
saltBytes = new byte[saltSize];

// Initialize a random number generator.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

// Fill the salt with cryptographically strong byte values.
rng.GetNonZeroBytes(saltBytes);
    }

    // Convert plain text into a byte array.
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

    // Allocate array, which will hold plain text and salt.
    byte[] plainTextWithSaltBytes =
    new byte[plainTextBytes.Length + saltBytes.Length];

    // Copy plain text bytes into resulting array.
    for (int i = 0; i < plainTextBytes.Length; i++)
plainTextWithSaltBytes[i] = plainTextBytes[i];

    // Append salt bytes to the resulting array.
    for (int i = 0; i < saltBytes.Length; i++)
plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];

    HashAlgorithm hash;

    // Make sure hashing algorithm name is specified.
    if (hashAlgorithm == null)
hashAlgorithm = "";

    // Initialize appropriate hashing algorithm class.
    switch (hashAlgorithm.ToUpper())
    {

case "SHA384":
    hash = new SHA384Managed();
    break;

case "SHA512":
    hash = new SHA512Managed();
    break;

default:
    hash = new MD5CryptoServiceProvider();
    break;
    }

    // Compute hash value of our plain text with appended salt.
    byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);

    // Create array which will hold hash and original salt bytes.
    byte[] hashWithSaltBytes = new byte[hashBytes.Length +
    saltBytes.Length];

    // Copy hash bytes into resulting array.
    for (int i = 0; i < hashBytes.Length; i++)
hashWithSaltBytes[i] = hashBytes[i];

    // Append salt bytes to the result.
    for (int i = 0; i < saltBytes.Length; i++)
hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];

    // Convert result into a base64-encoded string.
    string hashValue = Convert.ToBase64String(hashWithSaltBytes);

    // Return the result.
    return hashValue;
}

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

אימות משתמש

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

נקודד את הסיסמא שקיבלנו מהמשתמש שוב, ונשווה בין המחרוזת המקודדת ששמרנו בבסיס הנתונים לבין המחרוזת שקיבלנו מהקידוד הנ"ל. במידה והם שווים, יש התאמה וניתן לאמת את המשתמש.

הוספנו את המתודה בה השתמשנו להשוואת הסיסמאות למטה. אנו קוראים למתודה הזו ממתודת האימות (Login), עם הפרמטרים הבאים:

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

המתודה מחזירה TRUE במידה והסיסמאות זהות, ומכאן נוכל להמשיך באימות המשתמש כפי שנחליט.

public static bool VerifyHash(string plainText, string hashAlgorithm, string hashValue)
{
    // Convert base64-encoded hash value into a byte array.
    byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);

    // We must know size of hash (without salt).
    int hashSizeInBits, hashSizeInBytes;

    // Make sure that hashing algorithm name is specified.
    if (hashAlgorithm == null)
hashAlgorithm = "";

    // Size of hash is based on the specified algorithm.
    switch (hashAlgorithm.ToUpper())
    {

case "SHA384":
    hashSizeInBits = 384;
    break;

case "SHA512":
    hashSizeInBits = 512;
    break;

default: // Must be MD5
    hashSizeInBits = 128;
    break;
    }

    // Convert size of hash from bits to bytes.
    hashSizeInBytes = hashSizeInBits / 8;

    // Make sure that the specified hash value is long enough.
    if (hashWithSaltBytes.Length < hashSizeInBytes)
return false;

    // Allocate array to hold original salt bytes retrieved from hash.
    byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes];

    // Copy salt from the end of the hash to the new array.
    for (int i = 0; i < saltBytes.Length; i++)
saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];

    // Compute a new hash string.
    string expectedHashString = ComputeHash(plainText, hashAlgorithm, saltBytes);

    // If the computed hash matches the specified hash,
    // the plain text value must be correct.
    return (hashValue == expectedHashString);
}

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

ומה עם שחזור סיסמא?

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

יש לא מעט דרכים לעשות זאת, החל משליחת קוד כהודעת טקסט (במידה ויש לנו את הטלפון של המשתמש), שליחת מייל (לכתובת מייל שאומתה בזמן הרישום), להקפיץ שאלת שחזור סיסמא וכו' – האופציות תלויות בדרך בה בחרנו לרישום המשתמש (למשל – אם בעת רישום המשתמש ביקשנו את הטלפון הסלולארי, נוכל לשלוח קוד לשחזור סיסמא).

 

הדרך בה בחרנו היא ע"י שליחת URL למייל של המשתמש. ה- URL מכיל קוד שיצרנו (שימו לב שהקוד אינו מכיל תווים שאינם חוקיים בכתובת URL, כמו "." או "/" וכו'). כמו כן, נשמור את הקוד הנ"ל בבסיס הנתונים עבור המשתמש. כאשר המשתמש ייכנס לכתובת הזו, נוודא שהקוד אכן שלו ואת תוקפו של הקוד, במידה ועברנו בהצלחה ניתן למשתמש אפשרות לשנות סיסמא.

אפשר לייצר קוד ע"י יצירת JWT – היתרון הוא שמחרוזת JWT מכילה תוקף, כך שבדיקת תוקף המחרוזת נעשית בקלות (ויש לנו את המתודה שעושה זו כי אנו משתמשים ב- JWT לאימות), החסרון – המחרוזת שנוצרת ארוכה מאוד, ומכילה תווים שאינם חוקיים בכתובת URL.

אפשרות אחרת היא יצירת קוד Hash חד ערכי – במקרה זה נצטרך למצוא פתרון לבדיקת תוקף המחרוזת (אפשרי ע"י הוספת שדה תאריך תוקף בבסיס הנתונים).

בהצלחה!

 

עוד קצת מידע תוכלו לקרוא כאן.

זה הזמן לאונליין!

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

בניית אתר תדמית, תוכן,  חנות וירטואלית או הנגשת מערכת ה-"בקאנד" של העסק לתמוך בעבודה מהבית ולהשאיר את העסק חי.

עכשיו זה הזמן! צרו קשר איתנו עוד היום ונתחיל.

 

זה הזמן לאונליין!

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

בניית אתר תדמית, תוכן,  חנות וירטואלית או הנגשת מערכת ה-"בקאנד" של העסק לתמוך בעבודה מהבית ולהשאיר את העסק חי.

עכשיו זה הזמן! צרו קשר איתנו עוד היום ונתחיל.