bcryptjs

이재익·2019년 12월 1일
1

1. 단방향 해시 함수

단방향 해시 함수는 알고리즘을 통해 암호화된 문자열로 만들어 원본 문자열을 구할 수 없어야 한다.
즉 암호화를 한 번 하면 복호화를 할 수 없기 때문에 단방향이기 때문에 단방향 해시 함수라고 부른다.

예를들어

"4231" // 패스워드
"04c5be6ef82e5c1d2b2339d4d2bb57efab89903a" // 암호화된 문자열

위의 코드처럼 해싱을 통해 전혀 알아볼 수 없는 문자열로 바뀌게 된다.

"4321"
"d5f12e53a182c062b6bf30c1445153faff12269a"

그리고 입력되는 문자열이 조금만 바뀌어도 암호화된 문자열이 전혀 추측할 수 없게 바뀌는 것을 확인할 수 있다.

이 특징이 유저의 원본 비밀번호를 추론하기 어렵게 만들지만 이것만으로는 사용자의 비밀번호가 안전하다고는 말할 수 없다.

단방향 해시 함수의 문제점

해시 함수 자체는 원래 목적이 비밀번호를 저장하기 위해서 설계된 알고리즘이 아니다.
원래 목적은 짧은 시간에 데이터를 검색하기 위해 설계된 것인데 이것이 바로 문제점이다.
해시 함수가 가지고 있는 빠른 처리 속도 때문에 비밀번호를 탈취할 공격자는 아주 빠른 속도로 임의의 문자열들을
계속해서 입력하여 원본 문자열을 알아낼 수도 있기때문이다.

이 문제점을 보완하기 위하여 비밀번호에 salt 값을 추가하거나 해시 함수 자체를 여러번 돌리는 방법이 존재한다.

salt는 각각의 유저마다 다른 임의의 값을 대입하여 해시함수를 적용할 때 해당되는 salt 값을 이용하여 비밀번호를 암호화한다.

이 방법을 사용하게 되면 salt 값을 알아내더라도 유저 한명의 데이터만이 유출될 뿐 유저 데이터의 전부가 유출되는
방법을 막을 수 있다.

그래서 좋은? 해시 함수를 만드는 방법은 salt를 넣고 해시 함수를 여러번 적용하는 것이다.

2. bcryptjs

개.고.수 프로젝트는 비밀번호 암호화를 쓰기 위해 crypto 모듈을 사용했고 알고리즘은 sha1 사용했었다.

// user 모델의 hooks
hooks: {
        beforeCreate: data => {
          if (data.password) {
            const shasum = crypto.createHash("sha1");
            shasum.update(data.password);
            data.password = shasum.digest("hex");
            console.log(data.password);
          }
        }

sequelizehooks 기능을 이용하였다.

패스워드가 입력되면 sha1 알고리즘으로 암호화를 한 후 데이터베이스에 저장을 하였다.
그러나 이번에 새로운 사이드 프로젝트를 진행할 때 새로운 알고리즘을 적용해보고 싶어서 검색해본 결과
sha1 알고리즘을 사용하지 말라는 글을 보았다.

Md5, sha-1, has180-은 이미 뚫려버렸기 때문에 sha-256 이상의 알고리즘 사용을 권장한다고 한다.

그래서 이번에는 bcrypt 알고리즘을 이용했는데 crypto 모듈 자체가 bcrypt를 사용하지 못 한다고 에러가 나오길래
사용할 수 있는 모듈이 없을까 해서 찾아낸게 brcyptjs 모듈이다.

bcryptjs 모듈의 메소드인 genSalt(), hash(), compare() 이 3가지를 이용하여 기능을 구현하였다.

bcryptjs.genSalt(10, (_, salt) => {
  console.log(salt); // 첫번째 salt 값 $2a$10$iVKgt8eqTV9sKLNgVfgMbu
					 // 두번째 salt 값 $2a$10$7s3nzM/qbyulug7fJHp62.
}

genSalt()는 임의의 값을 만들어주는 역할을 한다.
메소드를 실행할 때마다 매번 다른 값을 만들어준다.

bcryptjs.genSalt(10, (_, salt) => {
    bcryptjs.hash(password, salt, (_, hash) => {
      password = hash;

      User.create({ email, nickName, password, provider, profileImg })
        .then(_ => {
          res.status(201);
          res.send(true);
        })
        .catch(err => {
          res.status(500);
          res.send(err);
        });
    });
  });

hash() 메소드로는 salt 값과 평문을 조합하여 암호화된 문자열을 만들어낸다.
salt 값이 매번 바뀌기 때문에 암호화된 문자열도 똑같은 평문을 조합하여도 전혀 다른 문자열이 나온다.

"1234"
"$2a$10$iVKgt8eqTV9sKLNgVfgMbu4Br/DCSMHokY3XnhERU.maDeq3jMKNO"

"1234"
"$2a$10$7s3nzM/qbyulug7fJHp62.ZLZimwPMf7MnX0d1/b8x6UCqMmqzcWW"

이렇듯 bcryptjs를 이용하여 암호화 기능을 구현하였다.

그리고 로그인 기능은 compare() 메소드를 이용하였다.

bcryptjs.compare(password, hashPassword, (err, res) => {
  // password = "1234" reslut는 true
  // password = "1243" result는 false
}

비교할 원본의 평문과 해시값을 넣어주면 res 값이 boolean 값으로 같다면 true, 다르면 false를 받아옵니다.

이 기능을 이용하여 로그인 성공 실패 여부를 만들어줄 수 있습니다.

profile
열정 있는 개발자

0개의 댓글