비밀번호 해싱

고객의 개인정보를 보호하기 위해서 비밀번호를 해싱하는 방법을 알아봅니다.
해싱, 비밀번호, SHA


해싱의 필요성

개인정보보호법은 개인정보의 암호화에 대해서 다음과 같이 규정하고 있습니다.

비밀번호, 바이오정보, 주민등록번호 등과 같은 주요 개인정보가 암호화되지 않고 개인정보처리 시스템에 저장되거나 네트워크를 통해 전송될 경우, 노출 및 위·변조 등의 위험이 있으므로 암호화 등의 안전한 보호조치가 제공되어야 한다.
※ “암호화”는 개인정보취급자의 실수 또는 해커의 공격 등으로 인해 개인정보가 비인가자에게 유·노출되더라도 그 내용 확인을 어렵게 하는 보안기술이다.

즉, 시스템이 인터넷에서 격리된 네트워크에 위치하는 경우나, 예외적인 개인정보 항목을 다루는 경우를 제외하고는 국가에서 권고하는 상용 암호화 알고리즘을 이용해 개인정보를 암호화하도록 법적으로 요구하고 있습니다. 또한 외부의 해킹 뿐만 아니라, 내부 DB 관리자의 개인정보 악용을 방지하기 위해서도 개인정보를 저장하거나 통신 할 때 암호화 할 필요가 있습니다.

특히 비밀번호가 노출될 경우, 일반적으로 수 많은 서비스에 같은 비밀번호를 쓰는 사람들이 많기 때문에 큰 피해를 초래 할 수 있습니다. 이번 챕터에서는 비밀번호를 안전하게 저장하는 방법에 대해서 알아보겠습니다.

  • DB에 저장시
    개인정보를 해싱하여 복원 할 수 없도록 함

  • 통신시
    개인정보를 주고 받을 때(로그인/회원가입 페이지 등) SSL을 적용하여 암호화

일반적으로 개인정보를 처리 할 땐, 통신시에는 SSL을 적용하고, 저장 할 때는 데이터를 복원 할 수 없도록 해싱하여 저장합니다. 예를 들어 "1234"를 그대로 DB에 저장하는 것이 아니라, 특정 해싱 알고리즘을 이용하여 HASH(“1234”)를 계산, 저장하고, 추후에 로그인 과정에서는 HASH(“입력값”)과 해시값을 비교함으로써 인증 로직을 수행합니다.

이렇게 비교 검증에 쓰이는 해시값을 특히 Digest라고도 부릅니다.

해싱, 해시 함수는 자료구조에서 빠른 자료의 검색, 또는 데이터의 위변조 체크를 위해서 쓰이나, 복원이 불가능(알고리즘을 알아도 역으로 연산이 불가)한 단방향 해시함수의 경우에는 암호학적 용도로 쓰이고 있습니다.

해싱 (Hashing)

자 그럼 MD5나 SHA-1, SHA-256 (MD5, SHA-1은 모두 보안적 결함이 경고됨) 등의 해시 알고리즘을 써서 비밀번호를 해싱한 뒤 저장하도록 하겠습니다.

평문Digest (SHA-256 해시값)
123403ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4
benzene55fe3d91090865027e8c35b993cb30f7639b6910bf7f46920ec3cb883e4780d
qwer1234!@6da4b98bfb06b104ff34b6e0806df208f78a16cec2a001c0964be9fc4c18c42b

자 이제는 데이터가 노출되어도 원본 데이터에 대해서 안심 할 수 있겠습니다.

Rainbow Table

Rainbow TableRainbow Table

그런데 이게 뭘까요, 어떤 사람들이 특정 길이의 문자열들에 대해서 모든 해시값을 계산해서 판매하고 있습니다. 이 Rainbow Table이라는 자료에 혹시라도 유출된 해시값을 대조해보면 원본 데이터를, 특히 비밀번호처럼 짧은 데이터를 유추하는 것이 어렵지 않습니다.

Rainbow Table을 판매하는 사이트 뿐만 아니라, 온라인 상에서 각종 해시 알고리즘의 Rainbow Table을 이용해 해시값을 유추해주는 사이트 또한 존재합니다. 이 사이트에서 위에서 계산한 테이블의 해시값들을 입력해보면, 평문을 바로 확인 할 수 있습니다.

소금 간하기 (Salting)

SaltingSalting

위처럼 단순 해시값은 해킹에 쉽게 노출될 수 있습니다. 이 때문에 Salting이라는 아이디어가 생겨났습니다. 비밀번호와 임의로 생성한 문자열(Salt)를 합쳐서 해싱하여 이 해시값를 저장하는 방법입니다.

평문Salt (랜덤값)Digest (SHA-256 해시값)
123454abcf98296c1cd862644758055f28204d6248c6babb18f97dd3532c9633e27771df41c7839a1
benzenf2f6e9faf7f276eae2b57421159cb8bd0d3783740650b72806968b519e1ab4692d9aca8dc72b1
qwer1234!@e7822b7e9e0ad62c57bb55d422a8fa06d87abb4939cd4b4baca6bbe0c671f6d6099dd1bb52849

이 때는 물론 DB에 해시값과 소금을 같이 보관해야 합니다. 그래야 인증시 HASH(“입력값” + “소금”)과 해시값의 비교 로직을 수행 할 수 있기 때문입니다.

위에서 소금 간을 해서 생성한 해시값은 위의 해시값을 유추해주는 사이트에서 복원되지 않습니다. 단순히 소금 간을 하는 로직으로 보안 수준을 확연히 높혔습니다.

반복하기 (Key Stretching)

Key StretchingKey Stretching

자 여기서 한술 더떠서 위 해시 과정을 여러번 반복(Key Stretching)한다면, 해커는 패스워드를 무작위 대입하며(Brute Force) 해시값과 비교하는 과정에서 훨씬 많은 계산량을 필요로하게 됩니다. 즉, 해싱 반복 횟수가 많아 질수록 보안 수준을 높힐 수 있습니다. 물론 이 때는 정상적인 유저의 로그인 요청에 대한 응답 시간 또한 늦어질 수 있습니다.

bcrypt

bcryptbcrypt

위와 같은 해싱 과정을 검증된 알고리즘(SHA-256 등)을 이용해서 직접 구현 할 수도 있지만, 기존에 이미 안전성이 검증된 비밀번호 해싱 라이브러리들이 구현되어 있습니다. 그 중 대표적으로 쓰이고 있는 bcrypt 라이브러리를 소개하고자 합니다. bcrypt는 Node.js 뿐만 아니라 다양한 플랫폼에서 이미 구현되어있어, 쉽게 프로그램에 적용 할 수 있습니다.

또한 bcrypt는 Digest 자체에 소금값과 해시값 및 반복 횟수를 같이 보관하기 때문에, 비밀번호 해싱을 적용하는데 있어서 DB 설계를 복잡하게 할 필요가 없습니다.

회원가입 예시

const bcrypt = require('bcrypt');

// ... 유저의 요청 데이터를 파싱 한 후

// 임의의 소금을 생성하고, 10번의 키 스트레칭을 통해 해시를 생성
bcrypt.hash(request.password, 10)
  .then(hash => {
    return User.create({
      password: hash,
      // ...  
    });
  })
  .then(user => {
    // ...
  });

로그인 예시

const bcrypt = require('bcrypt');

// ... 유저의 요청 데이터를 파싱 한 후, 유저의 저장된 bcrypt 해시값을 가져옴
const stored_hash = ...;

bcrypt.compare(request.password, stored_hash)
  .then(okay => {
    if (!okay) {
      throw new Error('PASSWORD_NOT_MATCHED');
      // ...
    }
    // ...
  })
  .then(() => {
    // ...
  });
목차
6. 개발과 배포
7. 다른 플랫폼으로
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 04월 09일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.