본문 바로가기

플밍 is 뭔들/그 외..

안전한 패스워드 저장 및 자바로 SHA-256 해싱 알고리즘 사용하기

 

일반적인 패스워드 저장방법
 
  • 단순 텍스트
  • 단방향 해시 함수의 다이제스트(digest)
 
단순 텍스트를 사용하고 있다면, 지금 당장 변경해야한다.  절대 해서는 안되는 일이다.
단방향 해시 함수는 수학적인 연산을 통해 원본 메시지를 변환하여 암호화된 메시지인 다이제스트를 생성한다. 원본 메시지를 알면 암호화된 메시지를 구하기는 쉽지만 암호화된 메시지로 원본 메시지를 구할 수 없어야 하며 이를 '단방향성' 이라고 한다.
 
※ 단방향 해시 함수
사용자의 패스워드가 "1234"라면 이 문자열을 흔히 사용하는 해시 알고리즘은 SHA-256으로 인코딩하여 아래와 같은 값을 얻을 수 있다.
 
  • FF0EB2864FEB22354747F8C85D42CCB5
 
위의 입력값을 저장하면 사용자는 패스워드를 직접 저장하는 위험을 피할 수 있다. 그리고 사용자가 로그인할 때 패스워드를 입력하면, 이를 해시한 값을 저장된 값과 비교하여 일치여부를 확인할 수 있다.
 
해시는 입력값의 일부가 변경되었을 때 다이제스트가 완전히 달라지도록 되어있다. "1235"라는 값의 SHA-256 다이제스트는 아래와 같으며 위의 "1234"와는 완전히 달라진 것을 확인할 수 있다.
 
  • 9996535E07258A7BBFD8B132435C5962
 
이 특징을 avalanche 효과라고 하며 사용자의 원본 패스워드를 추론하기 어렵게 만드는 중요한 요소중 하나이다. 그러나 이것만으로는 패수워드 보안이 충분히 안전하지 않다.
 
 
※ 단방향 해시 함수의 문제점
대부분의 웹 사이트에서는 SHA-256과 같은 해시 함수를 사용해 패스워드를 암호화해 저장하고 값을 비교하는 것만으로 충분한 암호화 메커니즘을 적용했다고 생각하지만, 실제로는 두가지 문제점이 있다.
 
인식 가능성(recognizability)
동일한 메시지가 언제나 동일한 다이제스트를 갖기때문에 공격자는 가능한 많은 다이제스트를 확보한 다음 이를 탈취한 다이제스트와 비교하여 원본 메시지를 찾아낼 수 있다. 이와같은 다이제스트 목록을 레인보우 테이블(rainbow table)이라 하고, 이와 같은 공격 방식을 레인보우 공격(rainbow attack)이라고 한다. 게다가 다른 사용자의 패스워드가 같으면 다이제스트도 같으므로 한꺼번에 모두 정보가 탈취될 수 있다.
 
속도(speed)
해시함수는 원래 패스워드 저장을 위해 만들어진것이 아니라 짧은 시간에 데이터를 검색하기 위해 설계된 것이다. 그래서 해시 함수의 빠른 처리속도로 인해 공격자는 매우 빠른 속도로 임의의 문자열의 다이제스트와 해킹할 대상의 다이제스트를 비교할 수 있다. (임의의 값과 일일이 비교하여 해킹)
이런 방식으로 패스워드를 추측하면 패스워드가 충분히 길거나 복잡하지 않은 경우에는 그리 긴시간이 걸리지 않는다.
 
※ 단방향 해시 함수 문제점 보완하기
솔팅(salting)
솔트(salt)는 단방향 해시 함수에서 다이제스트를 생성할 때 추가되는 바이트 단위의 임의 문자열이다. 원본 메시지에 문자열을 추가하여 다이제스트를 생성하는 것을 솔팅(salting)이라고 한다. 
 
바이트 단위의 문자열(salt) + 비밀번호(1234) => hash function => DiGEST 획득
 
이 방법은 공격자가 다이제스트를 알아내더라도 솔팅된 다이제스트를 대상으로 패스워드 일치여부를 확인하기 어렵다. 또한 사용자별로 다른 솔트를 사용한다면 동일한 패스워드를 사용하는 사용자의 다이제스트가 다르게 생성되어 인식 가능성 문제가 크게 개선된다.
 
솔트와 패스워드의 다이제스트를 데이터베이스에 저장하고, 사용자가 로그인할 때 직접 입력학 패스워드를 해시하여 일치 여부를 확인할 수 있다. 이 방법을 사용할 때에는 모든 패스워드가 고유의 솔트를 갖고 솔트의 길이는 32바이트 이상이어야 솔트와 다이제스트를 추측하기 어렵다.
 
 
키 스트레칭(key stretching)
입력한 패스워드의 다이제스트를 생성한다. 그리고 생성된 다이제스트를 입력값으로 다이제스트 생성하고 이를 반복한다. 이러한 방식으로 다이제스트를 만들어내면 패스워드를 동일한 반복 횟수만큼 해시해야만 패스워드 일치여부를 알 수 있다. 이것이 기본적인 키 스트레칭 과정이다.
잘 설계된 패스워드 저장 시스템에서는 하나의 다이제스트를 생성할 때 어느 정도의 시간이 소요되게 설정한다. 이는 억지 기법 공격(brute-force attacl)으로 패스워드를 추측하는데 많은 시간이 소요되도록 하기 위한것이다. 이렇게 한다면 해시의 속도문제도 개선된다.
 
억지 기법 공격(brute-force attacl) 이란?
암호를 풀기 위해 임의의 문자의 조합을 하나씩 대입해 보는 공격기법 -> 노가다로 하나하나 입력해본다.
 
 

※ SHA-256 in JAVA

● SHA-256으로 해싱하기
public byte[] sha256(String password, byte[] salt){
             
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    digest.reset();
    //digest.update(salt);                                     //솔트 필요하면 추가
    return digest.digest(password.getBytes("UTF-8"));          //digest 생성
 
    //만약 해시를 여러번 반복해서 다이제스트를 만드고 싶다면 아래와 같은 형태로 만들면 됨
    /*
    byte[] inputDigest = digest.digest(password.getBytes("UTF-8"));
    for(int i = 0; i < n ; i ++){
        digest.reset();
        intput = digest.digest(inputDigest);
    }
        return input
        */
}
 
위 함수에서 리턴을 byte[]로 받기 때문에 아래처럼 String 변환이 필요하다.
 
● btye to hex
public String bytesToHex(byte[] hash) {
             
    StringBuilder builder = new StringBuilder();
    for(byte b : hash) {
        builder.append(String.format("%02x", b));    
    }
             
    return builder.toString();
}
 
● 두 함수 사용 예시
String pw = bytesToHex(sha256("1234", salt));