티스토리 뷰
개요
사용자의 비밀번호를 그대로 보관하는 것은 위험하다.
비밀번호 보관에 특화된 bcrypt 를 알아보자.
참고링크
- 링크: https://auth0.com/blog/hashing-in-action-understanding-bcrypt/
- 링크: https://d2.naver.com/helloworld/318732
TL;DR
- 빠르게, 보안도 신경썼다는 티를 내면서 구현하려면 bcrypt 를 사용하자
- 보안에 좀더 민감한 곳이라면 scrypt 를 쓰자
- 현 시점 비밀번호 저장의 끝판왕은 Argon2id 이다.
비밀번호를 보관하자.
1) Plaintext 를 저장한다면 데이터베이스가 털리면 바로 끝이다.
- 이녀석 비밀번호는 asdf1234 이구나.
2) Hashing 을 한번 해주고 그걸 저장하면 낫지만 rainbow table 을 이용한 공격을 한다면?
- 미리 많이 쓰는 비밀번호와 그 hash 값들을 테이블로 만들어놓으면,
- 유출된 hash 값에서 비밀번호을 역으로 찾아낼 수 있다.
참고링크: http://bit.ly/36z3ujp 1) Dictionary attack: 많이 쓰는 비밀번호를 하나씩 넣어보자 2) Brute force attack: 사용가능한 문자열 내에서 무작위로 넣어보자 3) Rainbow Table: 위에 이미 언급 |
3) 비밀번호에 Salt 를 추가한 다음 hashing 하자
- salt 만들기와 hashing 이 한방에 이루어지면 금상첨화
SHA 가 있는데 bcrypt 는 왜 만들었지?
SHA2, SHA3 는 너무 빠른게 오히려 단점이다.
- 빨리빨리 rainbow table 을 만들 수 있다.
- 최신 CPU, GPU라면 SHA-256 을 초당 백반, 천만번씩 수행할 수 있다.
한계들
1) constant password length: 잉간이 기억하고 쓸 수 있는 비밀번호라면 길이의 한계를 가진다.
2) rapidly evolving hardware: 컴터가 빨라진다고 해싱 속도가 빨라지면 난감하다.
bcrypt 는
key setup phase 라는 일종의 막대한 전처리 요구로 느리게 만든 Blowfish 란 녀석의 특징에
반복횟수를 변수로 지정가능하게 하여 작업량 (=해싱시간) 을 조절할 수 있게 해주었다.
→ 원하는 만큼 속도를 조절가능한 hash 함수란 말이다.
bcrypt 동작 원리
이미지 출처 : https://auth0.com/blog/hashing-in-action-understanding-bcrypt/ |
1단계: Key setup phase
eksblowfish 함수 - expensive key schedule Blowfish, Blowfish 의 key setup algorithm 강화버전이다. - 파라미터는 원하는 비용 (= 연산량, 소요시간), salt, password - password 를 가지고 key stretching 하며 연산속도도 늦춰줌
2단계: Operation phase
- plaintext ↔ ciphertext 로 encrypt/decrypt 하는 단계 1) OrpheanBeholderScryDoubt 는 192 비트 (= 24 바이트) 이며 1단계의 결과이다. 2) 이걸 eksblowfish 가 동작하는 ECB mode 로 64번 암호화한다. 3) 암호화 루프 64번의 결과 + salt 128 비트 (=16 바이트) 등을 합친 것이 최종 결과이다. - 아래 예시를 보면 이해가 쉬울 것이다. |
결과 해시 알아보기
온라인에서 bcrypt 로 암호화를 해보았다. 링크: https://www.devglan.com/online-tools/bcrypt-hash-generator
1) $2b 는 bcrypt 의 버전정보이다.
2) $10 은 10 round 를 의미하며, cost 정보를 의미한다. 이게 클 수록 연산의 cost 가 증가하는 것이다.
3) 까지가 cost 정보를 통해 생성된 salt 이다. 이 값은 매 실행시마다 변한다.
4) 나머지가 hash 값이다.
|
예시를 하나만 더 보자 - 링크: https://stackoverflow.com/a/6833165/6513756 - cost 가 10 이면 2^10번 돌린 것이다. - 버전과 cost 정보를 뺀 나머지가 salt + cipher text 인데 Base-64 로 인코딩 된 것이다. - 앞부분 22 문자를 decode 해주면 16 바이트 salt 가 된다. 나머지가 cipher text 이다. |
이 결과물은
1) preimage attack (역상공격) 에 대해 저항성을 가진다. http://bit.ly/2RSFhPC
- 해시함수의 출력값이 같은 새로운 입력값을 찾는 공격
2) 충분히 큰 salt 공간을 가지고 있어서 rainbow attack 에 저항성을 가진다.
3) 변경이 가능한 cost (연산량, 연산시간)
실전을 통해 알아보기
- node.js 의 bcrypt 패키지를 이용하면 salt 생성과 hash 생성을 분리해서 볼 수 있어서 이해에 도움이 된다.
- 코드는 링크에 나와 있다. https://auth0.com/blog/hashing-in-action-understanding-bcrypt/
우선 cost 가 소요시간에 미치는 영향을 테스트 해보았다.
1) node.js. 설치
2) npm install bcryptjs 로 패키지 설치
3) 아래 코드 작성 후 node bcrypt1.js 실행
|
코드를 잠깐 보자
1) bcryptjs 라는 패키지를 가져온 다음 2) 비밀번호를 asdf1234 로 가정했다. 3) bcrypt.hashSync() 함수를 실행하는데 - 파라미터는 plainTextPassword 와 10 부터 20까지의 saltRounds 값이다. - 즉 cost 인 salt round 를 바꿔가며 소요시간을 측정해본 것이다.
결과를 보면 cost 가 10일때는 0.1초도 안되던 시간이 cost 를 20으로만 주어도 62초가 넘게 걸리는 것을 알 수 있다. → cost 가 30이 넘어가면 웬만한 시스템에서는 1년이 넘어 걸릴 수도 있다.
이러한 결과값으로 방정식을 역으로 만들어서 운영하는 시스템에 적절한 cost 를 계산해낼 수 있다. 이것 만으로도 불안하다면 정답은 two-factor, 혹은 multi-factor 인증이다. |
salt 와 hash 분리해서 만들어보기
|
1) saltRounds 값을 10으로 하여 bcrypt.genSalt() 함수로 salt 를 만들었다. - $2a$10$h3a5Cw4Amac.LDNjOWLAQu - bcrypt 버전은 $2a 이니 2a 이고 - saltRounds 는 $10 이니 10 rounds 란걸 알 수 있다. → 중요한 것은 매번 실행시마다 salt 값이 바뀐다는 것!
2) bcrypt.hash() 함수에 plainTextPassword 와 salt 를 넣어서 hash 를 만들었다. - hash 앞부분에 salt 가 포함되어 있는 것을 알 수 있다.
Salt: $2a$10$DTKVAPansJtga9/d.K770e Hash: $2a$10$DTKVAPansJtga9/d.K770eSgvzoh/mW.1vMrd0FzzHArYlqaTQT9m
Salt: $2a$10$z7wcUjnXVMGoaURjXmgvXO Hash: $2a$10$z7wcUjnXVMGoaURjXmgvXOQe65u0NnEaEDLWP7hGlSopyUgAVc342
|
한방에 hash 를 만들어보자
|
코드에 따로 설명이 필요할 것 같지는 않다. 아래와 같이 매 실행시마다 salt (=Hash 앞부분)과 전체 결과가 바뀌는 것을 알 수 있다.
Hash: $2a$10$IzweIEGkW4A1uzMNnPH/GegLN8bAb2dXe7Gg9QKDFmzIQ9gvTuQYK Hash: $2a$10$uz0MJWTUvwQtdYertA9ibe/R0LOh9s1eH/3zxdev8vDzRgiLoqDgK Hash: $2a$10$krPc1D50B0XR6E1PGvSZ8eoPAvKc9yqH3HwCJzUtCbhGq57gixEPC
위 코드와 이 코드를 보면 무엇을 알 수 있을까?
1. 이 코드처럼, plainTextPassword 와 saltRounds 만 넣으면 hash 를 생성해서 저장해둘 수 있다.
2. 이제 이렇게 등록된 사용자가 비밀번호를 넣어 로그인을 시도한다면? 1) 입력한 비밀번호와 저장된 비밀번호 hash 의 앞 salt 로 hash 를 만들어보면 비밀번호가 맞는지 확인할 수 있겠지? |
실제 validate 를 해보자
plainTextPassword 는 asdf1234 로 하고
hash 는 위에서 생성한 $2a$10$IzweIEGkW4A1uzMNnPH/GegLN8bAb2dXe7Gg9QKDFmzIQ9gvTuQYK 를 써보자
참고. Golang bcrypt
링크: https://godoc.org/golang.org/x/crypto/bcrypt
링크만 보아도 hash 생성과, 이후 입력된 비밀번호와의 비교를 알 수 있다.
'golang' 카테고리의 다른 글
Rob Pike의 The Laws of Reflection 블로그 포스팅 분석 (0) | 2020.02.28 |
---|---|
Russ Cox 의 Interface 블로그 포스팅 분석 (0) | 2020.02.26 |
Go 깨알: if err := json.Unmarshal(bytes, &book2); err != nil { (0) | 2020.01.17 |
Go Slice Tricks (0) | 2019.12.27 |
Slack Slash Command - timezone current time 2/2 (0) | 2019.09.19 |
- Total
- Today
- Yesterday
- bun
- Gin
- intellij
- clean agile
- golang
- 엉클 밥
- 독서
- notion
- 제이펍
- 오블완
- 잡학툰
- 인텔리제이
- ChatGPT
- Bug
- solid
- OpenAI
- 체호프
- 노션
- github
- strange
- agile
- 2023
- folklore
- 티스토리챌린지
- go
- 영화
- websocket
- 독서후기
- 클린 애자일
- API
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |