Menu
CoddyTech

בדיקת סיסמה

חלק מהיחידה אימות ואבטחה במסלול ה-Node.js של Coddy. שיעור 6 מתוך 24.

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

// קוד הזמנה נשמר בתור salt.keyHex
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);

השווה את רצפי הבתים שהתקבלו באמצעות timingSafeEqual, לאחר שווידאת שאורכיהם זהים. כך נמנעת יציאה מוקדמת שתלויה בתוכן ועלולה לחשוף מידע באמצעות מדידת זמנים. הבדיקה === של Buffer בודקת זהות, והשוואת מחרוזות רגילה אינה מבטיחה זמן ריצה קבוע. פונקציית הסיסמה שלך משתמשת במפריד הנקודתיים של רשומת הסיסמה ובמפתח באורך 64 בתים. הנתיב POST /login שתוסיף משתמש בתוצאה כדי להחזיר 200 או 401.

challenge icon

אתגר

קל

השלב הזה פותח את פרויקט ה־API של ההערות הפרטיות שהוכן עבור הקורס, ולא את קובצי הפרויקט שלך מתוך Never Store Passwords (כולל passwords.js). העבודה הקודמת שלך נשארת בשיעור משלה. קרא את הקבצים שסופקו לפני עריכת passwords.js. העותק הזה מכיל פונקציות עזר לגיבוב סיסמאות ואת נתיבי הניפוי שסופקו.

הרשמה שומרת עכשיו גיבובים עם salt. הוסף התחברות שבודקת סיסמה מול מה שההרשמה שמרה.

  1. ב־passwords.js, הוסף וייצא פונקציית async בשם verifyPassword(password, stored). stored הוא ערך שההרשמה שמרה, כמו f2bb...f065:c3f7...dbd2. החזר true כאשר password היא הסיסמה שממנה נוצר הערך, ו־false אחרת:
    • קח את ה־salt מהחלק שלפני הנקודתיים.
    • גבש את password עם ה־salt הזה למפתח באורך 64 בתים, באותה דרך שבה hashPassword עושה זאת.
    • פענח את המפתח השמור שאחרי הנקודתיים מ־hex. אם אורכי המפתחות בבתים שונים, החזר false בלי לקרוא ל־timingSafeEqual, כפי שמוסבר ב־Comparing Key Bytes. אחרת, החזר את תוצאת ההשוואה שלו.
  2. ב־server.js, ייבא את verifyPassword לצד hashPassword. הוסף את POST /login. הוא מקבל את אותו גוף בקשה כמו ההרשמה, למשל {"username": "ada", "password": "correct horse"}.
    • מצא את המשתמש עם שם המשתמש הזה. verifyPassword איטית ומחזירה promise, לכן המתן לה.
    • כאשר המשתמש קיים והסיסמה נכונה, השב עם סטטוס 200 ו־{"username": "ada"}.
    • אחרת, השב עם סטטוס 401 ו־{"error": "Wrong username or password"}. ‏401 Unauthorized פירושו שהבקשה לא הוכיחה מי מבקש אותה.
    • שלח בדיוק אותה תשובת 401 עבור סיסמה שגויה ועבור שם משתמש שאף אחד לא נרשם איתו. כך גוף התשובה לעולם לא יגלה לאיש אילו שמות משתמש קיימים.

נסה זאת: לחץ על Run. שלח POST /signup עם {"username": "ada", "password": "correct horse"}: סטטוס 201. שלח POST /login עם אותו גוף: סטטוס 200 ו־{"username": "ada"}. שלח POST /login עם {"username": "ada", "password": "wrong horse"}: סטטוס 401 ו־{"error": "Wrong username or password"}. שלח POST /login עם {"username": "nobody", "password": "correct horse"}: אותה תשובת 401.

נסו בעצמכם

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// ממירה סיסמה לטקסט ש-signup שומר.
export async function hashPassword(password) {
    const salt = randomBytes(16).toString('hex');
    const key = await scryptAsync(password, salt, 64);
    return `${salt}:${key.toString('hex')}`;
}

כל השיעורים ביחידה אימות ואבטחה

תרגלו בעצמכם: Express אונליין