안전한 비밀번호 저장

4 minute read

개요

네이버 기술블로그에 작성된 ‘안전한 패스워드 저장’ 을 보고 작성하였습니다.

이번에도 역시 모르는 사람이 보아도 이해할 수 있을 정도로 단순하게 작성해보도록 하겠습니다.

가장 단순한 방법

가장 단순한 방법은 역시 비밀번호를 그대로 저장하는 방법입니다. 사용자 정보 등록시 ID 와 비밀번호를 평문으로 DB에 저장하는 방벙이죠. 로그인시에는 요청된 ID 와 비밀번호가 DB의 값과 일지하는지 확인하면 됩니다.

당연하게도 이는 보안에 매우 취약합니다. 통신 과정에서 비밀번호가 탈취될 수도 있으며, 최악의 경우 서버의 DB가 탈취되면 모든 사용자의 비밀번호를 알 수 있기 때문입니다. 그렇기때문에 요즘에는 이러한 방식으로 비밀번호를 저장하지 않습니다.

만약 아직도 비밀번호의 평문을 저장하는 시스템이 있다면…… 생각만해도 끔찍하네요. ㅠㅠ

해시함수를 이용한 방법

해시함수는 단방향 함수입니다. 즉 어떠한 값을 해싱하여 나온 결과값을 가지고 원문을 알 수 없습니다. 또한 원문에 조금만 바뀌어도 해시의 모든 값이 바뀐다는 특징이 있습니다.

pi@raspberrypi:~/work $ echo 0123456789 > aaa
pi@raspberrypi:~/work $ md5sum aaa
3749f52bb326ae96782b42dc0a97b4c1  aaa
pi@raspberrypi:~/work $ echo 0123456788 > bbb
pi@raspberrypi:~/work $ md5sum bbb
80f11ae13a48c1e74537a72aa445b399  bbb

이 처럼 비밀번호를 해시로 변경하여 보관한다면 좀 더 안전하게 보관할 수 있습니다.

비밀번호를 잊어버려 찾을 경우에 대부분의 웹사이트에서 비밀번호를 알려주지 않고 변경하게끔 하는 이유도 이와 같습니다. 이미 해시로 보관하고 있기 때문에 비밀번호의 원문을 모르기 때문입니다.

단점 1 : 인식 가능성

위에서 본 바와 같이 해시함수는 원문을 전혀 알아볼 수 없는 메시지로 변경합니다. 그런데 이를 인식하는 것이 가능할까요?

이론상으로는 불가능합니다. 하지만 알 수 있는 방법이 전혀 없지는 않습니다.

메시지와 이에 대한 해시값을 저장하고 있는 테이블이 있다고 가정해봅시다. 만약 자신이 확인하고 싶은 해시값이 테이블에 있다면, 해시값의 원본을 알 수 있습니다.

이러한 테이블을 레인보우 테이블(rainbow table) 라고 부릅니다.

아래의 주소는 레인보우 테이블에 해시 값을 수집하는 웹사이트입니다.

  • https://sha1.gromweb.com/

SHA-1 로 변환된 39dfa55283318d31afe5a3ff4a0e3253e2045e43 라는 해시값이 있습니다.
이를 역산하여 원문을 알아내는 것은 불가능합니다. 하지만 위의 사이트에서 해당 해시값을 입력하면 원문이 0000 이라는 것을 알 수 있습니다. 실제로 0000을 SHA-1로 해싱하게 되면 동일한 해시값이 나타나게 됩니다.

image

위와 같은 레인보우 테이블에는 비교적 간단한 비밀번호의 해시값은 모두 있습니다. 복잡한 비밀번호의 해시값은 있을수도, 없을수도 있습니다. 하지만 레인보우테이블의 해시값은 지금도 계속 입력되고 있습니다.

언젠가는 모든 비밀번호의 값이 레인보우테이블에 체워질 수도 있습니다. 그렇게 되면 해시값을 이용하여 비밀번호를 유추하는 것이 가능해 질 것입니다.

단점 2 : 속도

해시함수는 암호학에서 많이 사용되지만, 암호학에서 사용하려고 만든 함수는 아닙니다. 해시함수의 목적은 짧은 시간에 데이터를 검색하기 위해 만들어진 함수입니다. 해시테이블과 같은 자료구조를 사용하기 위해 만들어진 함수라고 이해하시면 되겠습니다.

해시함수가 빠르기 때문에 공격자는 매우 빠른 속도로 임의의 문자열을 해시값으로 변환하여 해깅 대상의 해시값과 비교해볼 수 있습니다.
최근의 컴퓨팅 성능으로는 MD5의 경우 1초에 56억개의 다이제스트를 생성할 수 있다고 합니다.

이러한 방식으로 패스워드를 추측하면 패스워드가 짧거나 단순한 경우 금방 알아낼 수 있을 것입니다.

단방향 해시 함수 보완

솔팅 (salting)

솔트(salt)는 해시함수를 수행하기 전 문자열에 추가되는 임의의 값을 나타냅니다. 말 그대로 소금을 치는것이죠.

위에서 언급한 바와 같이 해시함수는 데이터가 조금만 바뀌어도 해시값이 완전히 바뀌게 됩니다. 그렇기때문에 해시전 메시지에 솔트값을 추가한다면 완전히 다른 해시값이 나타나게 됩니다.

이처럼 단순하게 솔트를 추가하는것 만으로도 보안이 강화됩니다. 솔트를 추가하게 되면 위에서 설명한 레인보우 테이블에 방어를 할 수 있기 때문입니다.

해시함수를 통해 나온 해시값을 레인보우 테이블에서 확인 시 대부분이 확인 불가하며, 확인이 가능하다 하여도 솔트가 뭍어있기 때문에 원문을 알 수 없습니다.

키 스트레칭 (key stretching)

키 스트레칭은 해시함수를 여러번 수행하는 것을 의미합니다.

해시함수를 N회 반복하여 스트래칭을 한다고 한다면, 원문을 해싱하여 나온 결과를 다시 해싱하고…. 를 N회 반복하는 것입니다.

image

원문을 알고 반복 횟수를 알아야 원하는 해싱 결과를 얻을 수 있습니다.

위에서 언급한 바와 같이 최근 컴퓨팅 성능으로는 1초에 약 50억회의 해시함수를 수행할 수 있습니다. 하지만 키 스트래칭을 적용한다면 1초에 계산할 수 있는 해시값은 현저히 줄어들게 됩니다. 만약 컴퓨팅 성능이 좋아진다면, 키 스트래칭 시 반복수를 더 늘리면 됩니다.

Adaptive Key Derivation Functions

Adaptive Key Derivation Functions 는 위의 보완 방법인 솔팅과 키스트레칭 외에 다른 요소들을 추가하여 공격자가 비밀번호를 유추하기 어렵도록 하는 방법입니다.

이러한 함수들은 GPU와 같은 장비에서 병렬화를 하기 어렵게 제공한다고 합니다.

PBKDF2

Adaptive Key Derivation Functions 중 가장 많이 사용되는 방법이 PBKDF2 방식입니다. PBKDF2 방식은 아래와 같은 파라메타를 요구합니다.

digest = PBKDF2(PRF, passwd, salt, c, Dlen)
  • PRF : 난수
  • passwd : 패스워드
  • salt : 솔트값
  • c : iteration 반복 카운트
  • DLen : digest 길이

PBKDF2 는 미국 NIST에 의해 승인된 알고리즘이며, 미국 정부 시스템에서도 해당 방법을 사용한다고 합니다.

bcrypt

bcrypt는 패스워드를 저장할 목적으로 설계된 알고리즘입니다. OpenBSD에서 기본 암호 인증 매커니즘으로 사용되고 있으며, 미래에 PBKDF2보다 더 경쟁력이 있다고 여겨진다고 합니다.

bcrypt 알고리즘은 Blowfish 라는 암호화 알고리즘을 여러번 수행하여 다이제스트를 생성하는 방식을 사용합니다. 그리고 bcrypt 알고리즘은 항상 72byte의 character 크기를 사용하여야 한다는 점이 있습니다.

image

아래의 코드는 자바에서 bcrypt 사용시의 예시입니다. 입력값으로는 패스워드와 salt 값이 요구됩니다.

// In java
String hashed = BCrypt.hashpw(password, BCrypt.gensalt(11));

scrypt

scrypt는PKKDF2와 유사한 adptive key derivation function입니다. scrypt는 다이제스트를 생성할 때 메모리 오버헤드를 갖도록 설계되어 있습니다. 그렇기 때문에 무작위 대입공격(brute-force attack) 시 병렬처리가 매우 어렵다는 특징이 있습니다.

다음은 scrypt의 사용 예시입니다.

DIGEST = scrypt(Password, Salt, N, r, p, DLen)  
  • Password : 패스워드
  • Salt : 솔트값
  • N : CPU 비용
  • r : 메모리 비용
  • p : 병렬화 (parallelization)
  • DLen : 다이제스트 길이값

마무리

쉽게 풀어쓰려고 했는데 그냥 생각없이 설명한 감이 있네요. 특히 scrypt에 대한 자료는 따로 더 찾아봐야 할 듯 합니다. 내용이 많이 부실하네요 ㅠㅠ 시간이 되면 내용을 더 보강해보도록 하겠습니다.

참고

  • https://d2.naver.com/helloworld/318732
  • https://velog.io/@kylexid/%EC%99%9C-bcrypt-%EC%95%94%ED%98%B8%ED%99%94-%EB%B0%A9%EC%8B%9D%EC%9D%B4-%EC%B6%94%EC%B2%9C%EB%90%98%EC%96%B4%EC%A7%88%EA%B9%8C

Leave a comment