본문 바로가기
웹 프로그래밍

안전한 패스워드 만드는 방식

by 코리늬 2019. 5. 2.

안전한 패스워드 저장

 

저번 개인 프로젝트에서 나는 해시 알고리즘 SHA-256을 사용해서 사용자의 패스워드를 암호화했었다.

SHA-256 방식은 **단방향 해시함수의 다이제스트(digest)** 방식이다.

이 방식은 수학적인 연산을 통해 원본 메시지를 변환하여 암호화된 메시지인 다이제스트를 생성한다.

원본 메시지를 알면 암호화된 메시지를 구하기는 쉽지만 암호화된 메시지로는 원본 메시지를 구할 수 없어야 하며, 이를 '단방향성' 이라고 한다.

 

실제로는 사용자의 패스워드가 변환된 해시 값으로 저장되기 때문에 패스워드를 직접 저장하는 위험을 피할 수 있다. 그리고 로그인 시 해시 값을 비교해 일치 여부를 확인한다.

 

 

나는 이 방식으로 암호화가 됐으니 끝이겠지 하고 넘어갔었다.

하지만 새로 다른 프로젝트를 진행하려 했을 때, 이 글을 참고하라고 받아보고 놀랐다.

 

문제가 있다니..

 

 

SHA-256에는 **두 가지 문제점**이 있다.

  1. 인식 가능성(recognizability)

동일한 메시지가 언제나 동일한 다이제스트를 갖는다면, 공격자가 전처리된 다이제스트를 가능한 많이 확보한 다음 이를 탈취한 다이제스트와 비교해 원본 메시지를 찾아내거나 동일한 효과의 메시지를 찾을 수 있다.

이와 같은 다이제스트 목록을 레인보우 테이블(rainbow table)이라고 하고, 이와 같은 공격 방식을 레인보우 공격(rainbow attack)이라고 한다.

 

게다가 다른 사용자의 패스워드가 같으면 다이제스트도 같으므로 한번에 모든 정보가 탈취될 수 있다.

 

 

  1. 속도(speed)

해시 함수는 원래 패스워드를 저장하기 위해서 설계된 것이 아니라 짧은 시간에 데이터를 검색하기 위해 설계된 것이다. 여기에서 **문제**가 발생한다. 해시 함수의 빠른 처리 속도로 공격자는 매우 빠른 속도로 임의의 문자열의 다이제스트와 해킹할 대상의 다이제스트를 비교할 수 있다. (MD5라는 것을 사용할 경우 일반적인 장비를 이용해 1초당 56억개의 다이제스트를 대입할 수 있다.)

이런 방식으로 패스워드를 추측하면 패스워드가 충분히 길거나 복잡하지 않은 경우에는 그리 긴 시간이 걸리지 않는다. 또한 대부분의 사용자는 패스워드가 길거나 복잡하지 않고, 동일한 패스워드를 사용하는 경우도 많다.

반면 사용자는 웹 사이트에서 패스워드를 인증하는 데 걸리는 시간에 민감하지 않다.

 

해시 함수의 빠른 처리 속도는 사용자보다 공격자들에게 더 큰 편의성을 제공하게 된다.

 

 

 

단방향 해시 함수 보완하기

솔팅(salting)

솔트(salt)는 단방향 해시 함수에서 다이제스트를 생성할 때 추가되는 바이트 단위의 임의의 문자열이다.

그리고 이 원본 메시지에 문자열을 추가해 다이제스트를 생성하는 것을 솔팅이라고 한다.

예를들어 "tistory12"라는 비밀번호에 솔트인 "8zff4fgflgfd93fgdl4fgdgf4mlf45p1"를 추가해 다이제스트를 생성할 수 있다.

(password) tistory12 + (salt) 8zff4fgflgfd93fgdl4fgdgf4mlf45p1 = DIGEST

 

솔트와 패스워드의 다이제스트를 DB에 저장하고, 사용자가 로그인할 때 입력한 패스워드를 해시화해 일치 여부를 확인할 수 있다. 이 방법을 사용할 때에는 모든 패스워드가 고유의 솔트를 갖고 솔트의 길이는 32바이트 이상이어야 해서 솔트와 다이제스트를 추측하기 어렵다.

 

 

키 스트레칭(key stretching)

입력한 패스워드로 다이제스트를 생성하고, 생성된 다이제스트를 입력 값응로 해 다이제스트를 또 생성하고, 이를 반복하는 방법으로 다이제스트를 생성할 수 있다. 이렇게 하면 입력한 패스워드를 동일한 횟수만큼 해시해야만 입력한 패스워드의 일치 여부를 확인할 수 있다. 이것이 기본적인 키 스트레칭 과정이다.

잘 설계된 패스워드 저장 시스템에서는 하나의 다이제스트를 생성할 때 어느 정도(일반적인 장비에서 0.2초 이상)의 시간이 소요되게 설정한다. 이는 억지 기법 공격(brute-force attack)으로 패스워드를 추측하는데 많은 시간이 소요되도록 하기 위한 것이다.

키 스트레층을 적용해 동일한 장비에서 1초에 5번 정도만 비교할 수 있게 한다. GPU를 사용하더라도 수백에서 수천 번 정도만 비교할 수 있다.

 

솔팅과 키 스트레칭으로 구성된 암호화 시스템을 구현하려면 이미 검증된 암호화 시스템을 사용하는 것을 권장한다.

 

 

Adaptive Key Derivation Functions

Adaptive Key Derivation Functions는 다이제스트를 생성할 때 솔팅과 키 스트레칭을 반복하며 솔트와 패스워드 외에도 입력 값을 추가하여 공격자가 쉽게 다이제스트를 유추할 수 없도록 하고 보안의 강도를선택할 수 있다.

 

이 함수들은 GPU와 같은 장비를 이용한 병렬화를 어렵게 하는 기능을 제공한다. 프로그램이 언어에서 제공하는 라이브러리만으로는 구현하기 어렵다.

 

 

Adaptive Key Derivation Function 중 주요한 key derivation function은 다음과 같다.

 

가장 많이 사용되는 key derivation function은 PBKDF2(Password-Based Key Derivation Function)이다.

해시 함수의 컨테이너인 PBKDF2는 솔트를 적용한 후 해시 함수의 반복 횟수를 임의로 선택할 수 있다.

PBKDF2는 아주 가볍고 구현하기 쉬우며, SHA와 같이 검증된 해시 함수만을 사용한다.

PBKDF2의 기본 파라미터는 다음과 같이 5개 파라미터다.

DIGEST = PBKDF2(PRF, Password, Salt, C, DLen)

  • PRF : 난수

  • Password : 패스워드

  • Salt : 암호학 솔트

  • c : 원하는 iteration 반복 수

  • DLen : 원하는 다이제스트 길이

 

PBKDF2는 NIST(National Institute of Standards and Technology, 미국 표준기술연구소)에 의해 승인된 알고리즘이고, 미국 정부 시스템에서도 사용자 패스워드의 암호화된 다이제스트를 생성할 때 사용한다.

 

 

bcrypt

bcrypt는 애초부터 패스워드 저장을 목적으로 설계되었다.

bcrypt에서 "work factor"인자는 하나의 해시 다이제스트를 생성하는 데 얼마만큼의 처리 과정을 수행할지 결정한다.

이 인자를 조정하는 것만으로 간단하게 시스템의 보안성을 높일 수 있다.

다만 입력 값으로 72 bytes character를 사용해야 하는 제약이 있다.

1
2
3
4
5
6
7
// Sample code for jBCrypt is a Java 
// gensalt is work factor and the default is 10 String hashed = BCrypt.hashpw(password, BCrypt.gensalt(11)); 
// Check that an unencrypted password matches one that has 
// previously been hashed 
if (BCrypt.checkpw(candidate, hashed)) 
System.out.println("It matches"); 
else System.out.println("It does not match");
cs

 

scrypt

scrypt는 PBKDF2와 유사한 adaptive key derivation function이다.

다이제스트를 생성할 때 메모리 오버헤드를 갖도록 설계되어, brute-force attack을 시도할 때 병렬화 처리가 매우 어렵다.

scrypt는 6개의 파라미터를 가진다.

DIGEST = scrypt(Password, Salt, N, r, p, DLen)

  • Password : 패스워드

  • Salt : 암호학 솔트

  • N : CPU 비용

  • r : 메모리 비용

  • p : 병렬화

  • DLen : 원하는 다이제스트 길이.

     

결론

MD5, SHA-1, SHA-256, SHA-512 등의 해시 함수는 메시지 인증과 무결성 체크를 위한 것이다.

이를 패스워드 인증에 사용하면 인식 가능성과 빠른 처리속도에 기인하는 취약점이 존재한다.

이를 해결하기 위해 key derivation function를 사용하는 것은 권장한다.

매우 강력한 패스워드 다이제스트를 생성하는 시스템을 쉽게 구현하고 싶다면 bcrypt를 사용하는 것이 좋다. 대부분의 프로그래밍 언어에서 라이브러리를 사용할 수 있고, 검색 엔진에서 "bcrypt <프로그래밍 언어>"로 검색하면 쉽게 예제를 구할 수 있다.

구현하려는 시스템이 매우 민감한 정보를 다루고, 보안 시스템을 구현하는 데 많은 비용을 투자할 수 있다면 scrypt를 사용하면 된다.

 

 

[참고]

(https://d2.naver.com/helloworld/318732)

'웹 프로그래밍' 카테고리의 다른 글

Controller에서 Parameter를 받는 방법  (0) 2019.05.23
CORS (Cross-Origin Resource Sharing)  (0) 2019.05.22
API + REST API 개념 정리  (0) 2019.03.11
Request, Response 객체 이해하기  (0) 2018.05.02
Servlet 이란?  (0) 2018.05.02

댓글