איך להתמודד בבטחה עם סיסמאות באמצעות BcryptsJS ב-JavaScript

הקדמה

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

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

דרישות קדם

כדי להמשיך עם המדריך הזה, יש לך צריכה להיות ברשותך את הסביבה הבאה.

  1. גרסה יציבה של Node.js מותקנת על המחשב שלך בגרסה 12.x או מעליה. ניתן להשתמש במדריך של דיגיטלאושן זה כדי להתקין את הגרסה העדכנית ביותר של Node.js על המחשב שלך.

  2. עליך לדעת לתכנת ב־JavaScript.

  3. _עליך להתקין את Express JS על המחשב שלך. באפשרותך להשתמש ב-מדריך זה כדי ללמוד איך להגדיר שרת Express.

  4. לבסוף, תצטרך את מסד הנתונים MongoDB Community או MongoDB Atlas כדי להשלים את המדריך הזה. באפשרותך להתקין אותו באמצעות אחד מהמדריכים של DigitalOcean על_ איך להתקין את MongoDB.

למה להשתמש ב-BcryptJS?

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

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

  1. אבטחה – BcryptJS מיישם את אלגוריתם ה-Bcrypt, אלגוריתם איטי (שבגיבוב הוא דבר טוב) ודורש כוח חישובי מרובה. זה מהווה משימה מאתגרת לתוקפים לפרוץ את גיבוב הסיסמה, מבטיח את בטיחות הסיסמאות גם במקרה של פריצת נתונים._

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

  3. נוחות בשימוש – BcryptJS מספקת למפתחי JavaScript כלי להצפנת הסיסמאות שלהם מבלי לדרוש הבנה עמוקה של גיבובים._

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

איך פיצול עובד?

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

פיצול

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

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

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

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

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

שיבוץ

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

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

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

  1. Password = ‘sammy’
  2. Hash = £%$^&£!23!3%!!
  3. Salt = 2vqw£4Df$%sdfk
  4. Hash + Salt = £%$^&£!23!3%!!2vqw£4Df$%sdfk

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

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

התקנת BcryptJS ומודולים נדרשים נוספים

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

Project Structure

ראשית, נתחיל ביצירת פרויקט npm עם השלבים הבאים:

  1. פתח תיקייה וצור קובץ, app.js.

  2. פתח חלון טרמינל בתיקייה זו והקלד את הפקודה

npm init

לאחר מכן, ישאלו אותך לקלטים אך תוכל ללחוץ על Enter מבלי לספק קלטים.

  1. אז, צור עוד 3 קבצים, שנקראים
  • auth.js
  • db.js
  • User.js
  1. באותו חלון טרמינל, הקלד את הפקודה הבאה כדי להתקין את החבילות הדרושות.
npm install express mongoose BcryptJS nodemon

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

התקנת שרת עם Express JS

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

שלב 1 – יצירת חיבור למסד הנתונים של MongoDB

כדי להתחבר ל־mongoDB, אנו משתמשים בגרסה הקהילתית. כדי לשמור על סדר בפרויקט, תשמור על הקוד להגדרת חיבור בקובץ db.js.

  1. const mongoose = require("mongoose");
  2. const mongoURI = "mongodb://127.0.0.1:27017/bcrypt_database";
  3. const connectMongo = async () => {
  4. try {
  5. await mongoose.connect(mongoURI);
  6. console.log("Connected to MongoDB!");
  7. } catch (error) {
  8. console.error("Error connecting to MongoDB: ", error.message);
  9. }
  10. };
  11. module.exports = connectMongo;

כאן, אתה מייבא את חבילת mongoose, שמספקת API לחיבור של ג'אווהסקריפט ל־MongoDB. גם, במקרה זה, URI החיבור הוא עבור התקנה מקומית של mongoDB. אם אתה משתמש במסד נתונים בענן (כמו Atlas), עליך רק לשנות את ה־URI ל־URI הספציפי של המסד הנתונים שלך.

מידע
URI דומה לכתובת URL של שרת אך עם הבדל ש-URI יכול לזהות את שם ואת זהות המשאבים ואת המיקום שלהם באינטרנט. לעומת זאת, URL הוא תת-קבוצה של URI שיכולה לבצע רק את האחרונה.

הפונקציה connectToMongo היא פונקציה async מכיוון ש-mongoose.connect מחזירה הבטחת JavaScript. פונקציה זו תחזיר חיבור לביצוע מוצלח. אחרת, תחזיר שגיאה.

לבסוף, אנו משתמשים ב-module.exports כדי לייצא את הפונקציה הזו כאשר מודול db.js מיובא.

שלב 2 – יצירת סכימת משתמש

תצטרך סכימה בסיסית כדי ליצור או לאמת משתמשים עם מסד נתונים של MongoDB. אם אינך יודע מהו סכימה, תוכל להשתמש במדריך המצוין הזה של DO כדי להבין וליצור את הסכימה ב-MongoDB. אנו נשתמש רק בשני שדות עבור הסכימה שלנו, אימייל ו-סיסמה.

השתמש בקוד הבא במודול שלך User.js.

  1. const mongoose = require("mongoose");
  2. const UserSchema = new mongoose.Schema({
  3. email:{
  4. type:String,
  5. required:true,
  6. unique:true
  7. },
  8. password:{
  9. type:String,
  10. required:true
  11. }
  12. });
  13. module.exports = mongoose.model('user', UserSchema)

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

לבסוף, אנו מייצאים מודל בשם users. מודל מייצג סכמה בצד המסד נתונים. ניתן לחשוב על סכמה כעל כלל להגדרת מודל, בעוד שהמודל נשמר כאוסף במסד נתונים של MongoDB.

ניתן להמיר סכמה למודל באמצעות הפונקציה model() של ספריית mongoose.

שלב 3 – הגדרת השרת בקובץ app.js

לאחר שביצעת את השלבים הקודמים, יש לך מודל ומודול ליצירת חיבור למסד נתונים של MongoDB. כעת, תלמד להגדיר את השרת. השתמש בקוד הבא בקובץ app.js שלך.

  1. const connectToMongo = require("./db");
  2. const express = require("express");
  3. const app = express();
  4. connectToMongo();
  5. app.use(express.json());
  6. app.use("/auth", require("./auth"));
  7. const port = 3300;
  8. app.listen(port, () => {
  9. console.log(`Listening at http://localhost:${port}`);
  10. });

כאן, אנו מייבאים את המודולים express ו־db.js כדי להתחבר ל־MongoDB. לאחר מכן, אנו משתמשים ב- middleware של express.json() לטיפול במענה JSON. הנתיבים נוצרים במודול שונה (auth.js) כדי לשמור על קוד נקי ומאורגן. ולבסוף, אנו יוצרים נקודת קצה לשרת הקשובה על פורט 3300 ב־localhost. (ניתן להשתמש בכל פורט שבחירתך)

הצפנת סיסמאות ואחסון אותם במסד נתונים של MongoDB

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

בקובץ auth.js, הקלד את הקוד הבא:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // מסלול 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. module.exports = router;

אנו מבצעים את הייבואים הנדרשים ואז מגדירים את ה־express router כדי ליצור את נקודת הסיום /signup. אנו משתמשים בשיטת POST כך שהאישורים לא ייחשפו בכתובת ה־URL באפליקציה. לאחר מכן, אנו יוצרים מלח באמצעות הפונקציה genSalt של החבילה scripts; הפרמטר שנשלח לפונקציה genSalt() מכיל את אורך תווי המלח. לאחר מכן, אנו משתמשים בפונקציה hash() של BcryptJS, אשר מקבלת פרמטר חובה, מחרוזת הסיסמה שיש להמיר לקוד הגב, וארגומנט אופציונלי, מחרוזת המלח. ואז היא מחזירה קוד גב שמכיל גם את password ו־salt.

אחרי זה, אנו משתמשים בפונקציית create() של מודול ה־mongoose כדי ליצור מסמך במסד הנתונים שלנו הוגדר על ידי הכללים של מודל ה־users. היא מקבלת אובייקט של Javascript המכיל אימייל וסיסמה, אך במקום לתת לה מחרוזת גולמית, אנו נותנים לה את ה־secPass (password hash + salt) להישמר במסד הנתונים. בכך, אנו מאחסנים בצורה מאובטחת סיסמה במסד הנתונים באמצעות ההאש שלה משולב עם מלח במקום מחרוזת גולמית. לבסוף, אנו מחזירים תגובת JSON המכילה את מודל המשתמש. (שיטת זו לשליחת תגובות היא רק לשלב ה־development; בשלב הייצור, יש להחליף זאת באסימון אימות או משהו אחר).

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

cd <path to your project folder>
nodemon ./app.js

הפקודה תפעיל את השרת שלך ב־localhost ובפורט 3300 (או בפורט שתציין). לאחר מכן, תוכל לשלוח בקשת HTTP לכתובת http://localhost:3300/auth/signup עם הגוף הבא:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

זה יגרום לפלט/תגובה הבאה להיווצר:

{
  "user": {
    "email": "[email protected]",
    "password": "$2a$10$JBka/WyJD0ohkzyu5Wu.JeCqQm33UIx/1xqIeNJ1AQI9kYZ0Gr0IS",
    "_id": "654510cd8f1edaa59a8bb589",
    "__v": 0
  }
}

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

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

גישה לסיסמה מוצפנת ושימוש בה לאימות

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

כדי להוסיף שיטת אימות, הוסף את הנתיב הבא ב-auth.js שלך לאחר הנתיב /signup:

  1. // נתיב 2:
  2. router.post("/login", async (req, res) => {
  3. let user = await User.findOne({ email: req.body.email });
  4. if (!user) {
  5. return res.status(400).json({ error: "Login with proper credentials!" });
  6. }
  7. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  8. if (!passwordCompare) {
  9. return res
  10. .status(400)
  11. .json({ error: "Login with proper credentials!" });
  12. }
  13. res.json({ success: "Authenticated!" });
  14. });

כאן, אנו משתמשים בתת-נתיב /auth/login כדי לבצע אימות כניסה עבור משתמש קיים כבר. דומה לנקודת הקצה /auth/signup, זה יהיה פונקציה async-await מאחר ו- BcryptJS מחזיר פרומיסים.

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

אם המשתמש עם האימייל המסופק קיים, התוכנית ממשיכה להשוואת הסיסמה. לצורך כך, BcryptJS מספק את השיטה compare(), שמקבלת מחרוזת גולמית כארגומנט הראשון ו-hash (עם או בלי salt) כארגומנט השני. לאחר מכן, היא מחזירה מובטח Boolean; true אם הסיסמה מתאימה ל-hash ו-false אם הם לא מתאימים. לאחר מכן, ניתן להוסיף בדיקה פשוטה באמצעות הצהרת if ולהחזיר הצלחה או שגיאה על סמך ההשוואה.

לבסוף, תייצא את express router באמצעות module.exports כדי שנקודת ההתחלה app.js תוכל להשתמש בו עבור הנתיבים.

כדי לבדוק נתיב זה, תוכל לשלוח תגובת HTTP נוספת לנתיב הזה http://localhost:3300/auth/login עם התוכן הבא:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

בקשה זו תחזיר את התגובה הבאה:

{
  "success": "Authenticated!"
}

לבסוף, auth.js ייראה כך:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // נתיב 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. // נתיב 2:
  16. router.post("/login", async (req, res) => {
  17. let user = await User.findOne({ email: req.body.email });
  18. if (!user) {
  19. return res.status(400).json({ error: "Login with proper credentials!" });
  20. }
  21. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  22. if (!passwordCompare) {
  23. return res
  24. .status(400)
  25. .json({ error: "Login with proper credentials!" });
  26. }
  27. res.json({ success: "Authenticated!" });
  28. });
  29. module.exports = router;

מסקנה

מדריך זה יצר שרת כדי להסביר את השימוש ב-BcryptJS לאחסון וגישה מאובטחת לסיסמאות במסד הנתונים. ניתן להמשיך בתהליך על ידי:

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

  • מיושם טוקנים אימות לשליחה כתגובות, וכו'

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

Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-passwords-safely-with-bcryptsjs-in-javascript