비밀번호를 데이터베이스에 안전하게 저장하는 방법
오늘날 우리는 기술의 발전으로 손 쉽게 웹 개발을 할 수 있게 되었지만
그에 따른 보안의 중요성도 중요하게 여겨야 한다.
웹 개발을 하면서 어떤 것을 고려해야 하고, 어떻게 개발해야 하는지 알아보자
1. 서론
최근 페이스북의 개인정보 유출 사례 를 보면 5억 3300만 명의
개인정보가 유출되는 사건이 있었는데,
그 중 12 만명의 한국인 정보가 유출되었고, 심지어 페이스북의 창립자인 마크 주커버그의 연락처도 유출되었다.
이 사건으로 인해 페이스북은 50억 달러 (한화 약 5조 9천억원)이라는 거금을 벌금으로 물게 되었는데. 우리가 개발을 하면서 보안이라는 요소는 선택이 아니라 필수라는 것을 알 수 있다.
하지만 개발자의 측면에서 보안을 신경써야 하는 것은 그렇게 쉽지만은 않다. 특히나 백엔드 개발자의 경우 코드 설계를 할 때 Database Architecture Design, Authorization Methods... 등 많은 지식을 필요로 하기 때문이다. 보안에 대해 설명하려면 끝도 없이 많지만 우리는 기본적인 웹 개발자가 필요한 최소한의 보안 지식에 대해 알아보자
2. 보안의 중요성
홈페이지의 악의적인 목적을 가진 사용자가 서버를 해킹해 해당 데이터베이스가 유출이 되었다면 어떻게 될까? 아마도 그 데이터베이스 안에는 아이디 , 비밀번호 , 이메일 등... 사용자의 정보가 담겨있을 것이다. 우리는 이런 최악의 상황에 대비해 사용자의 중요 정보는 암호화 처리 후 데이터베이스에 저장을 해야 한다. 그렇다면 여기서 중요 정보는 어떤 것이 있을까?
우리나라에서는 개인정보 안전성 확보 조치 기준에 따른 가이드를 KISA(한국인터넷진흥원) 에서 명시하고 있는데. 이곳에서 어떤 개인정보를 암호화 해야하는지 알려주고 있다.
우리는 홈페이지에서 가장 많이 다루는 사용자의 개인정보인 비밀번호 암호화에 대해 알아보고자 한다.
2.1. 단방향 암호화
단방향 암호화에서 가장 유명하고 자주 사용되는 알고리즘은 해시 알고리즘인데 이러한 해시 알고리즘의 특징은 크게 세 가지가 있다.
1. Collision Resistant; 생성된 다이제스트(digest) 로 입력 값을 유추할 수 없어야 한다.
2. Fixed-Length; Merkle–Damgård construction 을 기반으로 한 해시 함수는 어떤 입력 값을 받아도 동일한 길이의 다이제스트(digest) 를 출력한다.
3. Deterministic Algorithm (Avalanche Effect); 입력 값이 조금만 달라지더라도 전혀 다른 다이제스트(digest)를 출력한다.
2.2. 단방향 암호화의 단점
1. 인식 가능성 (Recognizability)
이 해시 알고리즘에도 단점이 있는데. 가장 큰 문제는 가능성(Recognizability) 이다. 입력 값이 같으면 출력되는 다이제스트 값이 같으므로 다른 사용자와 비밀번호가 같으면 Rainbow Attack 으로 한꺼번에 모든 정보가 탈취될 수 있다.
해시 함수의 입력 값을 모아 놓은 데이터베이스(Rainbow Table) 로 공격하는 기법을 Rainbow Attack 이라고 한다.
일례로 단방향 암호화는 복호화가 되지 않지만, 복호화를 할 수 있는 홈페이지가 존재한다.
이는 진짜로 복호화의 개념이 아닌 단순 Rainbow Table 에 들어가 있는 Input Data를 저장해 보여주는 것 뿐이다.
2. 속도 (Speed)
기술의 발전으로 Hash Algorithm 의 빠른 처리 속도로 인해 Brute-Force Attack과 Rainbow Attack 취약점이 발생한다.
오늘날 해커들은 GPU 병렬 처리 기법으로 초당 수십 억 개의 다이제스트를 비교한다.
2.3. 단방항 암호화 보완하기; Salting
비밀번호를 해시 알고리즘을 통해 저장하는 것은 평문으로 저장하는 것보다 안전할 수 있겠지만, 더 안전하게 비밀번호를 저장하는 방법은 Salting (non-secret parameters), 즉 랜덤한 임의의 값을 비밀번호에 붙여 암호화 하는 것이다.
Salt 는 길면 길어질수록 계산 복잡성(Computational complexity)이 증가한다.
OWASP(The Open Web Application Security Project) 에서 Salt 의 역할을 두 가지로 설명하고 있다.
- 각 유저가 동일한 정보를 저장하더라도 Salt 의 값이 달라 동일한 자격 증명이 노출을 막음.
- 단방향 암호화의 단점 - 인식 가능성(Recognizability) 을 보완하여 시간 기반 공격에 더 많은 자원을 소모하게 만듦.
서버는 데이터베이스에 생성된 다이제스트(DIGEST)와 Salt를 저장하고, 사용자가 로그인 처리를 위해 비밀번호를 서버로 보내면 테이블에 저장됐던 Salt로 다시 암호화를 진행하여 비교한다.
2.4. 단방항 암호화 보완하기; Key Stretching
만약, 해커가 특정한 경로로 모든 데이터베이스 정보를 탈취한다면, 모든 작업이 헛수고가 될 것이다.
하지만 데이터를 탈취 하더라도 시간 복잡도 (Time Complexity Key) 를 늘려 암호화를 반복하는 Key Stretching 이 있다.
Key Stretching 이란?
생성된 다이제스트 값을 무작위로 N회 반복하여 Brute-Force Attack에 더 많은 시간을 들이도록 하는 방법이다.
위 이미지에서 dK는 다이제스트, h는 해시함수, s 와 p 는 각각 Salt 와 Password 에 해당한다.
최근에는 서버측에서 Brute-Force Attack과 Rainbow Attack에 대해 쉽게 탐지하고 검사 속도를 늦춰 공격을 방어할 수 있지만, 해커가 이미 유저 정보가 담긴 테이블을 가지고 있다면 최대한 개인정보를 보호할 수 있는 방법은 시간 복잡도 (Time Complexity Key) 를 늘리는 방법 밖에 없다.
3. 키 유도 함수; Adaptive Key Derivation Functions, a.k.a (ADF , KDF)
키 유도 함수(Adaptive Key Derivation Functions)는 암호를 안전하게 저장하기 위해 특별히 설계된 알고리즘이며, 위의 단방향 암호화를 보완하기 위한 알고리즘이다.
단순히 Salting 과 Key Stretching 을 한다고 Brute-Force Attack 이 막아지는 것은 아니다.
다이제스트 값을 10,000 번 반복해도 컴퓨터는 0.1 초 만에 연산이 되어 dK 값이 나오기 때문이다. (단방향 암호화의 단점 - 속도)
그렇다면 이 방법을 어떻게 막을 수 있을까? 방법은 간단하다. 하나의 다이제스트를 생성할 때 딜레이을 두고 생성하면 아무리 빠른 컴퓨터라 할지라도 Brute-Force Attack 에 많은 시간이 소요될 수 밖에 없다.
그래서 우리는 일반적인 해시 함수로 암호화를 하는 것 보다 키 유도 함수를 이용해 암호화하는 것이 더욱 안전하다.
해커가 비밀번호 비교를 위해 i 번째 시행에 걸리는 시간을 t_i, 각 반복의 최소 시간을 t_min 이라 할 때,
전체 걸리는 시간을 T 라고 하면 이런 점화식이 만들어지는데
라고 가정하고 계산해 보자, 아무리 성능이 좋은 컴퓨터로 하나의 dK를 계산한다고 해도 최소 1초가 걸릴 것이다.
3.1. PBKDF2
PBKDF2 는 NIST(미국 국립표준기술연구소) 에서 권장하는 ADF 이며, 가장 많이 사용되는 알고리즘 중에 하나이다 이 알고리즘은 5개의 입력 값으로 다이제스트(dK)를 생성하는데
아래의 코드는 NodeJS 에 기본적으로 내장되어있는 crypto 모듈을 이용해 PBKDF2로 암호화하는 코드이다 .
3.2. Bcrypt
Bcrypt는 Blowfish 기반으로 한 해시 함수 알고리즘이다.
PBKDF2와 다르게 3개의 입력 값으로 다이제스트(dK)를 생성하고, Key Stretching 반복 횟수는 해당 식으로 결정이 된다.
1 번째 입력 값인 cost 값을 10으로 설정한다면 반복 횟수는 아래와 같다
bcrypt 를 거쳐 만든 dK 값은 다른 ADF로 암호화된 dK 값을 구분하기 위해 앞에 식별자 $2a$ 를 붙인다.
MD5 같은 경우 $1$ 의 식별자를 사용하고 있다.
현재의 암호 알고리즘 체계는 첫 번째 텍스트가 $ 로 나오지 않으므로, 대부분 식별자로 쓰인다.
- Cost; 2^n회의 반복 횟수를 거쳤음을 의미한다.
- Salt; 16바이트의 Salt를 의미한다.
- Hash; 24바이트의 Hash를 의미한다.
긴 글 읽어주셔서 감사합니다.
출처
Brute-Force Attack - https://en.wikipedia.org/wiki/Brute-force_attack
Rainbow Table - https://en.wikipedia.org/wiki/Rainbow_table
ADF - https://pycryptodome.readthedocs.io/en/latest/src/protocol/kdf.html
Password Storage Cheat Sheet - https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
PBKDF2 - https://en.wikipedia.org/wiki/PBKDF2
Bcrypt - https://ko.wikipedia.org/wiki/Bcrypt
A Future Adaptable Password Scheme - https://www.usenix.org/legacy/event/usenix99/provos/provos.pdf