2022. 4. 3. 10:32ㆍ카테고리 없음
이는 제 글이 아니라 학습을 위해 Naver D2의 글을 필사한 내용입니다.
이글 에서는 보안 시스템의 여러 부분 중 패스워드를 저장할 때 사용되는 해시 함수의 개념을 설명하고 대부분의 웹 사이트에서 사용하고 있는 암호화 알고리즘의 안정성을 검토하겠습니다. 그리고 어떤 암호화 알고리즘을 선택해야 안전한지 설명하겠씁니다.
단방향 해시 함수
보통 프로그래머는 아래의 두 가지 중 한 가지로 사용자의 패스워드를 저장한다.
- 단순 텍스트
- 단방향 해시 함수의 다이제스트
단순 텍스트로 패스워드를 저장하는 것은 범죄를 저지르는 것이나 다름없다.
단방향 해시 함수는 수학적인 연산으로 통해 원본 메시지를 변환하여 암호화된 메시지인 다이제스트를 생성한다.
원본 메시지를 알면 암호화된 메시지를 구하기는 쉽지만 암호화된 메시지로는 원본 메시지를 구할 수 없어야 하며
이를 '단방향성'이라고 한다.
예를 들어 사용자의 패스워드가 "hunter2"라면 이 문자열을 흔히 사용하는 해시 알고리즘인 SHA-256으로 인코딩하여 아래와 같은 값을 얻을 수 있다.
f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
위의 값을 저장하면 사용자의 패스워드를 직접 저장하는 위험을 피할 수 있다.
그리고 사용자가 로그인할 때 패스워드를 입력하면 해시한 값을 저장된 값과 비교하여 일치여부를 확인할 수 있다.
대부분의 해시 함수는 입력 값의 일부가 변경되었을 때 다이제스트가 완전히 달라지도록 설계되어 있다.
이 특징을 avalanche 효과라고 하며, 사용자의 원본 패스워드를 추론하기 어렵게 만드는 중요한 요소이다.
그러나 이것도 보안이 충분히 안전하다고 할 수 없다.
단방향 해시 함수의 문제점
대부분의 웹 사이트에서는 SHA-256과 같은 해시 함수를 사용해 패스워드를 암호화해 저장하고 값을 비교하는 것만으로 충분한 매커니즘을 적용했다고 생각하지만, 실제로는 다음 같은 두 가지 문제점이 있다.
인식 가능성 (Recognizabiliy)
동일한 메시지가 언제나 동일한 다이제스트를 갖는다면, 공격자가 전처리된 다이제스트를 가능한 많이 확보한 다음 탈취한 다이제스트와 비교해 원본 메시지를 찾아내거나 동일한 효과의 메시지를 찾을 수 있다.
이와 같은 다이제스트 목록을 레인보우 테이블(Rainbox Table)이라 하고, 이와 같은 공격 방식을 레인보우 공격(Rainbox Attack)이라 한다.
게다가 다른 사용자의 패스워드가 같으면 다이제스트도 같으므로 한꺼번에 모두 정보가 탈취될 수 있다.
속도(Speed)
해시 함수는 암호학에서 널리 사용되지만 원래 패스워드를 저장하기 위해서 설계된 것이 아니라 짧은 시간에 데이터를 검색하기 위해 설계된 것이다. 바로 여기에서 문제가 발생한다.
해시 함수의 빠른 처리 속도로 인해 공격자는 매우 빠른 속도로 임의의 문자열의 다이제스트와 해킹할 대상의 다이제스트를 비교할 수 있다.
이런 방식으로 패스워드를 추측하면 패스워드가 충분히 길거나 복잡하지 않은 경우에는 그리 긴 시간이 걸리지 않는다.
그리고 대부분 사용자의 패스워드는 길거나 복잡하지 않을 뿐 아니라, 동일한 패스워드를 사용하는 경우도 많다.
반면 사용자는 웹사이트에서 패스워드를 인증하는 데 걸리는 시간에는 그리 민감하지 않다.
사용자가 로그인하기 위해 아이디와 패스워드를 입력하고 확인 버튼을 누르는 과정에 10초가 걸린다고 가정했을 때, 다이제스트를 생성하는 데 0.1초 대신 1초가 소요된다고 해서 크게 신경 쓰는 사람은 많지 않다.
즉 해시 함수의 빠른 처리 속도는 사용자들보다 공격자들에게 더 큰 편의성을 제공하게 된다.
단방향 해시 함수의 보완
솔팅(Salting)
솔트(Salt)는 단방향 해시 함수에서 다이제스트를 생성할 때 추가되는 바이트 단위의 임의의 문자열이다.
그리고 이 원본 메시지에 문자열을 추가하여 다이제스트를 생성하는 것을 솔팅(salting)이라고 한다.
예를 들어 다음과 같이 "redfl0wer"에 솔트인 "8zff4fgflgfd93fgdl4fgdgf4mlf45p1"를 추가해 다이제스트를 생성할 수 있다.
이 방법을 사용하면 공격자가 redfl0wer의 다이제스트를 알아내더라도 솔팅된 다이제스트를 대상으로 패스워드 일치 여부를 확인하기 어렵다.
또한 사용자별로 다른 솔트를 사용한다면 동일한 패스워드를 사용하는 사용자의 다이제스트가 다르게 생성되어 인식 가능성 문제가 크게 개선된다.
솔트와 패스워드의 다이제스트를 데이터베이스에 저장하고
사용자가 로그인할 때 입력한 패스워드를 해시하여 일치여부를 확인할 수 있다.
이 방법을 사용할 때는 모든 패스워드가 고유의 솔트를 갖고 솔트의 길이는 32바이트 이상이어야 솔트와 다이제스트를 추측하기 어렵다.
키 스트레칭(Key Stretching)
입력한 패스워드의 다이제스트를 생성하고, 생성된 다이제스트를 입력 값으로 하여 다이제스트를 생성하고, 또 이를 반복하는 방법으로 다이제스트를 생성할 수도 있다.
이렇게 하면 입력한 패스워드를 동일한 횟수만큼 해시해야만 입력한 패스워드의 일치여부를 확인할 수 있다.
이것이 기본적인 키 스트레칭 과정이다.
잘 설계된 패스워드 저장 시스템에서는 하나의 다이제스트를 생성할 대 어느 정도(일반적인 장비에서 0.2초 이상)의 시간이 소요되게 설정한다.
이는 Brute Force Attack으로 패스워드를 추측하는 데 많은 시간이 소요되도록 하기 위한 것이다.
최근에는 일반적인 장비로 1초에 50억 개 이상의 다이제스트를 비교할 수 있지만, 키 스트레칭을 적용하여 동일한 장비에서 1초에 5번 정도만 비교할 수 있게 한다.
GPU(Graphics Processing Unit)를 사용하더라도 수백에서 수천 번 정도만 비교할 수 있다. 50억 번과는 비교할 수도 없을 정도로 적은 횟수다. 앞으로 컴퓨터 성능이 더 향상되면 몇 번의 반복을 추가하여 보완할 수 있다.
다음 그림은 솔트를 추가한 패스워드에 여러 단계의 해시 함수를 적용하여 다이제스트를 생성하는 과정을 나타낸 것이다.
앞에서 설명한 바와 같이 솔팅과 키 스트레칭으로 구성된 암호화 시스템을 구현하려고 한다면 이미 검증된 암호화 시스템을 사용할 것을 권장한다. 널리 알려진 검증된 시스템을 사용하면, 암호화 시스템을 잘못 구현해서 발생하는 위험을 피할 수 있기 때문이다.
이에 비해 자신만의 암호화 시스템을 구현하는 것은 매우 위험하다.
이 경우 취약점을 확인하기 어렵고, 대부분의 경우 구현된 암호화 시스템을 점검하고 확인하는 사람은 암호화 시스템을 구현한 당사자 한 명이다.
만약 구현한 암호화 시스템에 취약점이 있다면 많은 사람들이 사용할수록 그만큼 많은 사람들이 피해를 입게 된다.
이런 취약점이 내포된 시스템은 여러 차례 발견되었고, 이와 같은 시스템을 사용한 프로그램이 여러 해 동안 BSD나 Linux에서 사용되어 왔다.
다음 절에서는 위에서 설명한 사항들을 고려하여 선택할 수 있는 대안을 제시한다.
Adaptive Key Derivation Functions
Adaptive Key Derivation Function은 다이제스트를 생성할 때 솔팅과 키스트레칭을 반복하며 솔트와 패스워드 외에도 입력값을 추가하여 공격자가 쉽게 다이제스트를 유추할 수 없도록 하고 보안의 강도를 선택할 수 있다.
이 함수들은 GPU와 같은 장비를 이용한 병렬화를 어렵게 하는 기능을 제공한다.
이와 같은 기능은 프로그램이 언어에서 제공하는 라이브러리만으로는 구현하기 어렵다.
Adaptive Key Derivation Function 중 주요한 Key Derivation Function은 다음과 같다.
PBKDF2
가장 많이 사용되는 Key Derivation Functiondms PBKDF2(Password-Based Key Derivation Function)이다.
해시 함수의 컨테이너인 PBKDF2는 솔트를 적용한 후 해시 함수의 반복 횟수를 임의로 선택할 수 있다.
아주 가볍고 구현하기 쉬우며, SHA와 같이 검증된 해시 함수만을 사용한다.
PBKDF2의 기본 파라미터는 다음과 같은 5개 파라미터다.
DIGEST = PBKDF2(PRF, Password, Salt, c, DLen)
- PRF: 난수(예: HMAC)
- Password: 패스워드
- Salt: 암호학 솔트
- c: 원하는 iteration 반복 수
- DLen: 원하는 다이제스트 길이
PBKDF2는 NIST(National Institute of Standards and Technology, 미국표준기술연구소)에 의해서 승인된 알고리즘이고, 미국 정부 시스템에서도 사용자 패스워드의 암호화된 다이제스트를 생성할 때 사용한다.
bcrypt
bcrypt는 애초부터 패스워드 저장을 목적으로 설계되었다.
현재까지 사용되는 가장 강력한 해시 매커니즘 중 하니이다.
bcrypt는 보안에 집착하기로 유명한 OpenBSD에서 기본 암호 인증 매커니즘으로 사용되고 있고 미래에 PBKDF2보다 더 경쟁력이 있다고 여겨진다.
bcrypt에서 "work factor" 인자는 하나의 해시 다이제스트를 생성하는 데 얼마만큼의 처리 과정을 수행할지 결정한다. "work factor"를 조정하는 것만으로 간단하게 시스템의 보안성을 높일 수 있다.
다만 PBKDF2나 scrypt와는 달리 bcrypt는 입력 값으로 72 bytes character를 사용해야 하는 제약이 있다.
// 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");
scrypt
scrypt는 PBKDF2와 유사한 adaptive key derivation function이며 Colin Percival이 2012년 9월 17일 설계했다. scrypt는 다이제스트를 생성할 때 메모리 오버헤드를 갖도록 설계되어, 억지 기법 공격(brute-force attack)을 시도할 때 병렬화 처리가 매우 어렵다. 따라서 PBKDF2보다 안전하다고 평가되며 미래에 bcrypt에 비해 더 경쟁력이 있다고 여겨진다. scrypt는 보안에 아주 민감한 사용자들을 위한 백업 솔루션을 제공하는 Tarsnap에서도 사용하고 있다. 또한 scrypt는 여러 프로그래밍 언어의 라이브러리로 제공받을 수 있다.
scrypt의 파라미터는 다음과 같은 6개 파라미터다.
DIGEST = scrypt(Password, Salt, N, r, p, DLen)
- Password: 패스워드
- Salt: 암호학 솔트
- N: CPU 비용
- r: 메모리 비용
- p: 병렬화(parallelization)
- DLen: 원하는 다이제스트 길이
참고
https://d2.naver.com/helloworld/318732