보안에 있어서 Salt를 쓰면 좋다길래 나도 한번 만들어볼까 했다.
그런데 Spring Security의 BcryptPasswordEncoder에는 이미 Salt가 사용되고 있다고 한다
그래서 Salt를 어떻게 만들고 사용하는지, Salt를 쓰는게 도대체 왜 의미가 있는지 이해해보고자 노력했다
그 내용을 정리한 글
해시 함수란
보통 비밀번호는 해시함수를 한번 거쳐서 저장한다.
문자를 해시 함수에 넣으면 해시 알고리즘을 통해서 요상한 값(출력 해시)을 출력해준다.

위 표와 같은 함수들이 있다고 한다
해시 함수의 특징
- 함수별로 알고리즘이랑 출력 해시(Digest)가 달라서 계산 속도나 보안에 차이가 있다
- 단방향 암호화라서 복호화 할 수 없다
- 비밀번호 >> 해싱된 값 : 가능
- 해싱된 값 >> 비밀번호 : 불가능
- 해시 알고리즘은 전부 공개되어 있다 >> 해커들도 알고있다
- 동일한 해시 함수에서 같은 입력은 같은 출력 해시를 뱉는다
- 다른 입력인데 같은 출력 해시가 나올 수 있음
- 중복(충돌)이 적을수록 좋은 해시 함수
- 해시 함수마다 출력 해시의 길이는 고정된 길이의 값으로 나온다
레인보우 테이블
위 특징으로 해커들은 해시 함수를 뚫을 수 있는 방법을 찾았다.
특정 값을 해시 알고리즘에 하나씩 대입해 보는 것

해커들은 공개된 해시 알고리즘으로 어떤 값이 어떤 해시를 출력하는지 테이블을 만들었다.
이러한 테이블을 레인보우 테이블이라 한다
특징이 몇개 있다
- 문자의 조합이 엄청나게 많은 만큼 테이블의 크기도 굉장히 크다

- 모든 text를 해싱해서 저장한건 아니고, 특정 문자와 숫자의 조합의 제한을 두고 저장한다고 한다
- 레인보우 테이블에서 해시의 결과를 다시 레인보우 테이블에 입력 가능한 형태로 바꿔주는 함수를 거쳐서 저장 공간을 줄이는 방법이 있다고 한다 (레인보우 테이블 나무위키 참고. 맞는지 모르겠네)
레인보우 테이블을 통해 서버에 저장되어있는 해싱된 비밀번호와 대조함으로써 진짜 비밀번호가 무엇인지 확인해서 계정의 비밀번호를 알아낼 수 있다
MD5, SHA-1 등의 경우 위처럼 테이블이 완성되어 있어서 이미 뚫린 해시 함수이다.
사용하면 안됨.
이런 레인보우 테이블 공격을 막기위해 나온게 Salt
Salt
음식에 소금을 치듯이 비밀번호에 소금처럼 뭔가 첨가해주는게 Salt

위 그림처럼 기존 비밀번호에 특정 값을 뒤에 추가하게 되면 해시 함수를 거쳤을 때 출력 해시가 바뀌게 된다
출력 해시가 바뀌면 레인보우 테이블을 써도 원본 비밀번호를 알 수 없다
레인보우 테이블 공격의 경우 특정 문자에 대한 출력해시를 입력해서 원본을 찾아내는 방식인데, Salt가 추가되면 공격자가 알게되는 원본은 '비밀번호'가 아닌 '비밀번호 + Salt'이다.
위 그림의 경우 lvn49sa를 입력해서 공격자는 p4s5w3rdzet52ed를 얻었는데 실제 비밀번호는 p4s5w3rdz이다.
Salt가 뭔지 모르면 실제 비밀번호를 알 수 없다는 말
BCrypt
- 해시 매커니즘 중 하나
- 값을 해싱하는 것 뿐만 아니라 매 입력마다 다른 Salt를 생성해서 해싱을 해준다
- 출력 해시 내부에 Salt가 포함되어 있어서 Salt를 따로 저장하지 않아도 입력과 출력해시를 비교할 수 있다.
- Salt가 노출되어 있음!
BCryptPasswordEncoder의 Salt
BCryptPasswordEncoder의 encode 부분을 보면 Salt가 생성되고 저장됨을 알 수 있다

encode 메서드에서는 getSalt() 메서드를 이용해 입력마다 다른 Salt를 생성한다.
리턴 값을 보면 getVersion, strength, random이 있는데, 이런 뜻이라 한다
예시:
$2a$10$w47WGAfxIYm7RyIJcjmvduXnCPmvMoeoFdemGLVDdDy8VSujwQYI.
- getVersion : BCrypt Version
- BCrypt의 버전
- $2A, $2B, $2Y 세 버전이 있다
- 예시에서 $2a
- strength : Log Round
- 계산에 걸리는 시간
- 값이 커질수록 느려짐
- 예를 들어 10이면 2^10 번 반복된다
- 예시에서 10
- random : 랜덤으로 생성된 Salt 값 + 해당 Salt가 포함된 상태로 해시 알고리즘을 거친 출력 해시
- 아래 두 가지가 합쳐진 값
- 랜덤으로 생성된 22개의 문자가 Salt
- Salt + 비밀번호 를 해싱해서 나온 출력 해시
- 예시에서 w47WGAfxIYm7RyIJcjmvdu (Salt)
- 예시에서 XnCPmvMoeoFdemGLVDdDy8VSujwQYI. (출력 해시)
- 아래 두 가지가 합쳐진 값

이렇게 저장된다는 뜻
의문
출력 해시에 Salt가 포함되어 있다면, 공격자도 답을 알 수 있는거 아닌가?? 하는 생각이 들었다
- 공격하려는 DB에 있는 출력 해시를 레인보우 테이블에 넣어봄
- 뭔가 Text가 나옴
- Text 중 앞 22자리를 제외한 값이 password아닌가??
좀 찾아보니 결론은 '레인보우 테이블 자체를 만들수 없다. 그래서 공격이 불가능' 이다.
레인보우 테이블이란게 a 부터 aaaaaaaa... 까지 엄청나게 많은 수의 글자의 조합을 출력 해시로 만들어두는건데, 128비트의 Salt가 추가되면 이 테이블의 사이즈가 어마어마하게 커진다.
테이블의 크기가 말도안되게 커지니까 테이블을 만드는 것 조차 하기 어려운 것.
결국 Salt의 의도는 레인보우 테이블을 막기위해 존재한다.
내가 생각하는 22자리를 제거하는 논리...의 공격은 불가능!
아래 글이 도움이 됐다.
참고
해시 함수, 레인보우 테이블, Salt, Spring Security에서의 Salt등의 내용을 정리했다.
엄청나게 많은 블로그를 참고했다
BCryptPasswordEncoder에 Salt가 노출되도 괜찮은 이유
어떻게 BCrypt는 Salt가 매번 달라져도 비밀번호가 맞는지 확인하는가?
끋.