들어가기

이제 당당한 로그인 API 설계자(?)가 되었다는 뽕에 차기에는
아직도 내가 갈 길은 멀다!

미약한 내 실력으로 만약 배포 단계까지 가게 되었는데,
데이터베이스가 탈탈 털린다면?😱

이에 대한 보안 대책 1단계를 세워보려고 한다!
바로 비밀번호 암호화!

새삼 개발 겉핥러라는 걸 매번 깨닫는데
오늘 내가 겉핥았던 것은 바로 비밀번호는 그냥 저장되었을 것이라는 것!

사실은 그게 아니었습니다

Bcrypt란?

Bcrypt는 단방향 해시 함수 중 하나로
주로 사용자 비밀번호를 안전하게 저장하기 위해 사용된다

해시함수란?

해시함수란 어떤 입력값을 특정 길이의 문자열로 변환해주는 암호함수라고 생각하면 된다
간단하게 예를 들어보자면

const ranNum = Math.floor(Math.random()*10000)
console.log(ranNum)
// 9697

const func = (num) => {
  return num % 10
}

func(ranNum)
// 3

이처럼 특정 숫자를 10으로 나눈 나머지를 구하는 함수도 해시함수이다
어떤 숫자를 넣든 한자리 숫자로 변환을 할 수 있기 때문!

단방향 해시함수

근데 그게 단방향이다?
한번 가면 돌아오지 못한다고 생각하면 된다..
즉, 암호화는 되는데 복호화(역함호화)가 안되는 것
(더 간단하게, 한번 암호화 해버리면 원본을 알 수가 없다..)

하지만 이 부분을 조금 더 정교하게 만들면
원본을 알 수는 없지만, 결과값을 알고 해시함수의 구조(10으로 나눈 나머지)를 알면
원본과 암호화된 함수가 같은 값을 가지는 지는 알 수 있다!
(이게 바로 뽀.인.트.)

그래도 이해가 안된다면 해시함수를 체험할 수 있는 곳이 있다!

이 곳 에서 아무 글자나 입력해보시라
한글자를 넣거나, 긴 문장을 넣어도 출력물은
고양이가 키보드 위에서 같은 시간 동안 춤을 춘것 같은
일정 길이의 문자열이 계속해서 나올 것이다

이제 좀 감이 잡히지?
이어서 조금 더 알아보자

Bcrypt의 특징

솔팅(Salting) : 예시함수에서 나누는 수 10과 같은 의미

  • Bcrypt는 각각의 해시에 무작위 솔트 값을 추가하여
    레인보우 테이블 공격 등을 방지하고
    솔팅을 통해 같은 비밀번호라도 서로 다른 해시 값을 생성할 수 있다.
  • 레인보우 공격이란?
    해커들은 생각보다 일순간 공격하는 것이 아니고 지긋이..
    우리를 지켜보면서 데이터 수집을 처리한 후 공격을 하는데
    이때 실제로 그 웹에 저장되는 데이터보다 더 많은 양의 데이터를 수집한다
    (오가는 모든 데이터를 수집하니까, 우리가 쓰고 버리는 데이터도 저장하니까
    간단하게 우리가 그냥 버리는 택배 운송장 태그도 다 뒤져본다고 생각하면 된다..😰)
    그 데이터를 보면 무지개 같은 모양으로 보인다고 해서 이렇게 이름 붙여졌다고 한다
    빨간색은 빨간색끼리.. 보라색은 보라색끼리..
    매서드는 같은데 문자는 다르다.. 그럼 이건 같은 문장이겠네..?
    하는 식의 추측이 가능할만한 정도의 데이터를 계속해서 모은다고 보면 된다
  • 찐 방어
    그래서 솔팅은 같지만 같은 내용을 받아도 다른 문자열을 발생시키니
    이 레인보우 공격을 방어 할 수 있다는 것이다

가변적인 비용 요소

  • 비용 요소(cost factor)는 해싱에 소요되는 시간을 결정하는데
    비용 요소가 높을수록 해시를 계산하는 데에 시간이 오래 걸린다
    이는 공격자가 더 많은 계산을 해야 하므로 공격을 어렵게 만든다는 것
    즉, 공격을 하는지 우리가 매번 알 수 없으니
    그냥 거기에 필요한 비용을 늘려 나가 떨어지게끔 하는 방식을 취한다는 것
    (이래도 내가 좋아? 이래도 내가 막 갖고싶어? 응??!?!? 거의 이이제이..)

Bcrypt 사용법(node.js 기준)

일단 터미널 창을 열고 설치를 한다

npm install bcrypt

그리고 코드 적용은 아래와 같이

## javascript
const bcrypt = require('bcrypt');
// 솔팅 설정 주로 10 ~ 13이 권장된다
// 그 이하는 복잡도가 떨어져 보안이 취약해지고, 그 이상은 암호화가 시간이 길어지기때문
const saltRounds = 10;

// 암호화되기 전인 유저가 입력한 패스워드 원본
const plaintextPassword = 'mySecurePassword';

// 암호화
bcrypt.hash(plaintextPassword, saltRounds);

// 이후 이 암호화된 비밀번호를 데이터베이스에 저장

...

// 데이터베이스에서 받아온 암호화된 비밀번호
const hashedPasswordFromDB = '...';

// 이후 로그인한 유저의 비밀번호를 받아 두 데이터가 일치하는지 확인
bcrypt.compare(plaintextPassword, hashedPasswordFromDB);

이렇게 우리는 사용자가 입력하는 비밀번호를 알 필요도 없거니와
알 수도 없는 비밀번호 중개사(?)로 한 발짝 물러설 수 있게되었다!
(물론 중간에 빼서 볼 수는 있지만..)

이런식으로 비밀번호를 처리하다 보니
잃어버린 비밀번호를 찾아주는 것이 아닌 재발급을 해주는 방식으로
은근슬쩍 바뀐게 아닌가 생각이 든다(인지는 못했지만 언제부터인가 그렇게 바뀌어있다)
그리고 이전에 쓰던 비밀번호를 알려주는게 많이 위험하기도 하고..
(항상 보안정책에 맞춰 대/소문자, 숫자, 특수문자를 쓰지만 우리는 대부분의 사이트에서 같은 비밀번호를 사용하는걸 알고 있지 않은가..?)

마무리

Bcrypt는 안전한 비밀번호 저장을 위한 강력하고 신뢰성 있는 해시 함수로
비밀번호 보안에 중요한 역할을 한다
하지만 그럼에도 불구하고 변수는 항상 있게 마련
장인은 도구 탓을 하지 않기에 우리는 이 도구를 항상 올바르게 사용해야 한다.
항상 취약점은 발견되기 마련이므로 라이브러리 업데이트는 꼭 주기적으로 확인해주는 것이 좋다!

++
발돋움 중인 예비 개발자 입니다.
태클 및 의견 공유 언제나 환영 :D

profile
컨텐츠 기획자 출신 백엔드 개발자 :D

0개의 댓글