Encryption

Hash와 Salt

yeonx 2023. 10. 5. 11:40
728x90

단방향 해시 함수는 어떤 수학적 연산(또는 알고리즘)에 의해 원본 데이터를 매핑시켜 완전히 다른 암호화된 데이터로 변환시키는 것을 의미한다. 이 변환을 해시라고 하고, 해시에 의해 암호화된 데이터를 다이제스트(digest)라고 한다.

 

또한, 앞서 말했듯 해싱은 단방향이다. 한마디로 단방향 해시 함수는 다이제스트를 복호화, 즉 원본 데이터를 구할 수는 없어야 한다. 말 그대로 단방향성이다.

 

예시로 비밀번호 123456을 들어보자.


즉 원본메시지 123456을 해시 함수에 돌려서 다이제스트인 fs32a3xzz0을 생성하고 해당 데이터를 DB에 저장하는 것이다.
이렇게 저장된 다이제스트는 설령  DB가 털려도 단방향으로 해싱된 문자라 복호화할 수 없다.

 

이러한 단방향 해시 함수의 종류들은 매우 많으며 대표적으로 아래와 같은 알고리즘이 있다.

  • SHA
  • MD
  • HAS
  • WHIRLPOOL

그중 가장 대표적인 해시 알고리즘인 SHA-256을 통해 123456을 해싱하면

8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

 

만약 조금만 변경하여 123456 다음에 마침표(.) 하나만 더 찍어도 완전히 다른 값이 나온다.

43fae6c11d7632acc6059de1cced9b09a58caaa878071308ad67f32ef6b11691

 

이렇게 사용자들로부터 입력받은 정보를 그대로 저장하는게 아니라 해싱을 해서 저장하는 것이다.

그러면 DB를 털어 저런 값을 얻었다고 한들 기존 원래 패스워드를 유추하기 힘들게 된다.

 

단방향 해시 함수의 한계점

1. 동일한 메시지는 동일한 다이제스트를 갖는다. -> 해커가 rainbow 테이블을 통해 찾을 수 있음
2. 무차별 대입 공격(브루트포스)

 

단방향 해시 함수 보완하기

가장 유명한 방법으로는 키-스트레칭과 솔트가 있다.

 

해시 함수 여러번 수행하기(키 스트레칭 _ key stretching)

우리가 패스워드를 저장할 때 가장 쉽게 생각할 수 있는 방법이다.

 

물론 돌리는 횟수는 개발자 본인만 알고 있는 것이 최고지만, 설령 소스파일을 들여다보았다 하더라도 최종 다이제스트의 원문 메시지를 얻기 위해 소모되는 시간은 더욱 많이 소요되므로 해커입장에서는 곤란해지게 된다.

 

또한 해시 함수를 여러번 돌리는 만큼 최종 다이제스트를 얻는데 그만큼 시간이 소요되기 마련이다.

사용자의 경우 패스워드를 입력하고 일치여부를 확인할 때, 0.2~0.5초만 결려도 크게 문제가 없다. 그러나 앞서 말했듯이 임의의 문자열을 무차별 대입하는 해커 입장에서는 1초에 10억번의 다이제스트를 얻을 수 있었으나 다이제스트를 얻기까지의 시간을 지연시켜 이제는 한 횟수당 0.2~0.5초가 걸리기 때문에 치명적이다.

 

즉, 브루트포스를 최대한 무력화하기 위한 방법인 것이다.

 

솔트(Salt)

여러번 돌리더라도 결국 몇 번 돌렸는지 횟수만 알면 상징성 있는 대표 문자열들을 추려서 대입해보면 적어도 공격하는 입장에서는 조금이나마 찾는데 시간을 줄이고, 각 횟수별 다이제스트가 Rainbow table에 있을 확률이 높기 때문이다. 또한 같은 비밀번호를 사용하는 사용자들이 있다면 하나의 결과를 갖고도 다수 사용자의 password를 알아내는 것이나 마찬가지다.

 

이를 방지하기 위해 도입한 것이 바로 솔트다.

 

솔트란 해시함수를 돌리기 전에 원문에 임의의 문자열을 덧붙이는 것을 말한다. 단어 뜻 그대로 원문에 임의의 문자열을 붙인다는 의미로 소금친다(salting)는 것이다.

이렇게 하면 설령 다이제스트를 알아낸다 하더라도 password를 알아내기 더욱 어려워진다. 그리고 사용자마다 다른 salt를 사용한다면 설령 같은 비밀번호라도 다이제스트의 값은 다르다. 이는 결국 한 명의 패스워드가 유출되더라도 같은 비밀번호를 사용하는 다른 사용자는 비교적 안전하다는 의미기도 한다.

 

사용자1과 사용자2가 123456이라는 같은 password를 사용하고 있다.
하지만 사용자1은 솔트 값이 sffs13osz043opq9dsfdkj32 이고, 사용자2는 osela31dif3298hcwaw8s301 이다.

즉 사용자1이 해시함수를 돌리기 전에 솔팅된 문자열은 123456sffs13osz043opq9dsfdkj32 이고, SHA-256 에 돌리면 다음과 같은 값을 얻을 수 있다.
343099b2867417f1b60462a8c70aa9dc33f4b1cec287fdb22e9fcf9b999ee3ce​


사용자2의 경우 해시함수를 돌리기 전 솔팅된 문자열은 123456osela31dif3298hcwaw8s301 이다.
이를 SHA-256 을 사용하여 해싱 하면 다음과 같은 값을 얻는다.
725c8c66c181855dd578961d90b2a051a250b232ede85a7ab5da5d0d4586d135​


즉, 같은 패스워드를 사용하더라도 salting된 문자열은 서로 다르기 때문에 각 사용자의 다이제스트는 서로 다른 값으로 저장될 것이다.


해커가 123456의 다이제스트를 갖고 있다고 하더라도 레인보우 테이블에서 비교하기 어렵게 만드는 것이다.
(123456의 SHA-256을 사용한 다이제스트는 다음과 같다.)
8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92​

더욱이 솔트는 임의의 문자열이기 때문에 Rainbow Table에 없을 가능성이 매우 높아진다.
물론 솔팅할 문자열이 간단하거나, 짧으면 큰 의미는 없어진다. 가장 효과적인 방법을 구축하려면 적어도 각 사용자별 고유의 솔트를 갖고 있어야 하며 솔트의 길이는 32비트 이상되어야 솔트와 다이제스트를 추측하기 어렵다고 한다. 그렇기에 솔트의 경우 암호학적으로 안전한 난수 생성기를 사용하여 예측가능성을 줄여야 한다.

설령 사용자의 고유 솔트를 알았다고 한들, 해당 솔트와 결합하여 임의의 문자열을 무차별 대입을 해보아야 하기 때문에 공격하는 사람 입장에서는 곤란하게 만들 수 밖에 없다.

솔트의 가장 큰 목적은 해당 솔트의 레인보우 테이블 새로 생성하여 만들기 위해서는 엄청나게 큰 데이터를 필요로 하기 때문에 자연스럽게 레인보우 테이블 생성을 방지하는 역할을 해주기도 한다.

 

위 두가지 방법을 혼용하면 다음과 같다.

 

 

참고 : https://st-lab.tistory.com/100