Published on

เราควรออกแบบวิธีการจัดเก็บ password อย่างไรดี

Authors

วิธีการจัดเก็บรหัสผ่าน

โดยปกติระบบจัดการผู้ใช้งาน ต้องมีการ login เข้ามาในระบบ เราจะต้องมีการจัดเก็บ username กับ password ไว้ที่ server แล้วให้ผู้ใช้งานจะต้องส่ง username กับ password มาตรวจสอบว่าตรงกันหรือไม่ ดังนั้นเราจะมาดูว่า เราควรออกแบบวิธีการจัดเก็บ password นี้ไว้อย่างไรดี

ลองมาดูวิธีแรก

แบบง่ายๆ เลย เราก็เก็บ username กับ password ไว้ตรงๆ ไปเลย แบบนี้

UsernamePassword
user1password1
user2password2
user3password3

ข้อเสีย ถ้ามี hacker สามารถเจาะเข้ามาในระบบเราได้ hacker ก็จะได้ username กับ password ออกไปใช้ได้เลย และถ้าเกิดผู้ใช้งานดันใช้ username กับ password เดียวกันกับระบบอื่นๆ อีก hacker ก็สามารถเอาไปใใช้กับระบบอื่นต่อได้เลย

สรุปว่า ไม่ดี ไม่ควรทำ

งั้นก็เข้ารหัส password ไว้สิ

ตอนจัดเก็บกับเอา password มาเข้ารหัสก่อน เช่น MD5, SHA1 เป็นต้น แล้วจัดเก็บเอาไว้

------------       -----------------        ------------------------------------
| password |  ---> | Hash Function |  --->  | 5f4dcc3b5aa765d61d8327deb882cf99 |
------------       -----------------        ------------------------------------

ก็จะได้เป็นแบบนี้ แค่นี้ hacker อ่านไม่รู้เรื่องแล้ว

UsernamePassword (MD5)
user17c6a180b36896a0a8c02787eeafb0e4c
user26cb75f652a9b52798eb6cf2201057c73
user3819b0643d6b89dc9b579fdfc9094f28e

ส่วนขั้นตอนการ login ก็เอา password ที่ส่งมาเข้ารหัสด้วยฟังก์ชันเดียวกัน เสร็จแล้วเอาไปเปรียบเทียบกับอันที่เราจัดเก็บเอาไว้ ว่าตรงกันรึป่าว

สรุป วิธีนี้ก็ยังไม่ดีอ เพราะถึงจะอ่านไม่รู้เรื่อง แต่ hacker ก็สามารถเอาไปใช้ได้ง่ายๆ อยู่ดี เช่น hacker อาจจะทำตารางเปรียบเทียบเอาไว้ว่า password แต่ละตัว มีรหัส MD5 ว่าอะไร เพราะคนส่วนใหญ่จะใช้รหัสง่ายๆ กัน เช่น 1234, password1234 ถ้าดันไปเจอข้อมูลที่ตรงกัน hacker ก็จะทราบว่า username นั้นๆ ให้ password จริงๆ ว่าอะไร

งั้นต้องเพิ่มความยากเข้าไปอีก

โดยการใช้ "salt" + password แล้วเอาไปเข้ารหัสอีกที

---------------------       -----------------        ------------------------------------
| "salt" + password |  ---> | Hash Function |  --->  | 67a1e09bb1f83f5007dc119c14d663aa |
---------------------       -----------------        ------------------------------------

"salt" คือ ค่าๆ หนึ่ง ที่เป็นความลับในระบบของเรา

ในขั้นตอนการ login ก็เอา "salt" ในระบบ + password ที่ส่งมา แล้วเอาไปเปรียบเทียบกับตัวที่เข้ารหัสเก็บไว้

สรุป วิธีนี้ก็จะทำให้ hacker ใช้วิธีเปรียบเทียบกับตารางของ hacker ไม่ได้แล้ว แต่ถ้า hacker สามารถเจาะเข้ามา ก็อาจเอา "salt" ของเราไปได้เช่นกัน

งั้นก็ให้ "salt" ของแต่ละคนไม่ซ้ำกันไปเลยสิ

ก็คือให้สร้าง "salt" แยกไปแต่ละผู้ใช้งานไปเลย ส่วนการทำงานที่เหลือก็เหมือนเดิม

Usernamesaltsalt + Password (MD5)
user155f312f84e8a9998e55776b25b56b9ecd648325c34
user27785aa1efafc2b07a0eda86c2b7094a2ca8efbd49b
user3552acbf25119f4345ebf2e34d1e091873c0ea76c12

ตัวอย่างโค้ด ใน Node.js

การเข้ารหัส

// controller
create(req, res) {
    const {username, password} = req.body;
    User.create(username, password).then(user => {
        res.status(201).json({user});
    });
}



// model
const crypto = require('crypto');

function hashPassword (salt, password) {
    /*
    crypto.pbkdf2Sync(password, salt, iterations, keylen, digest)
    - iterations => จำนวนรอบ
    - keylen => byte length
    - digest => digest
    */
    return crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha1').toString('base64');
};

function create(username, password) {
    return new Promise((resolve, reject) => {
        const salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');

        const hash = hashPassword(salt, password);

        // create new user object
        const user = {
            username,
            salt,
            password: hash
        }
        // save user to db
        db.save(user);
        resolve(user);
    });

}

การตรวจสอบ

// user ต้องเอา username ไปหามาจากใน db ก่อน
function verify(user, password) {
  return new Promise((resolve, reject) => {
    const hash = hashPassword(user.salt, password)
    const isValid = hash === user.password
    resolve(isValid)
  })
}

สรุป เอาวิธีนี้หล่ะ ทำให้ hacker ลำบากสุด

แต่ขอเพิ่มความยากอีกนิดนึง

เนื่องจาก MD5 หรือ SHA1 แบบที่เลือกข้างต้นนั้น เป็นอัลกอริทึมที่ออกแบบเพื่อให้ประมวลได้เร็ว ทำให้ hacker สามารถลองสุ่ม password เข้ามา แล้วรู้ผลได้เร็วเช่นกัน ซึ่งเราควรใช้งานอัลกอริทึมที่ทำงานช้าแทน เพื่อเพิ่มความยากให้กับ hacker เช่น bcrypt

ตัวอย่างการใช้ bcrypt ใน Node.js

การเข้ารหัส

// controller
create(req, res) {
    const {username, password} = req.body;
    User.create(username, password).then(user => {
        res.status(201).json({user});
    });
}



// model
function create(username, password) {
    return new Promise((resolve, reject) => {
        let saltRound = 10; // คือจำนวนรอบในการทำงานผ่านอัลกอริทึมใน bcrypt 10 = 2 ยกกำลัง 10 รอบ
        bcrypt.hash(password, saltRound, (err, hash) => {
            if(err){
                reject(err);
            }else{
                // เมื่อทำงานเสร็จจะได้ผลลัพธ์ออกมาเป็น hash
                // create new user object
                const user = {
                    username,
                    password: hash
                }
                // save user to db
                db.save(user);
                resolve(user);
            }
        });
    });

}

การตรวจสอบ

// user ต้องเอา username ไปหามาจากใน db ก่อน
function verify(user, password) {
  return new Promise((resolve, reject) => {
    const hash = user.password

    bcrypt.compare(password, hash, (err, isValid) => {
      if (err) {
        reject(err)
      } else {
        resolve(isValid)
      }
    })
  })
}