Computer

SHA1 + salt로 패스워드 보안 이슈 회피가 가능한가?

steloflute 2014. 4. 15. 19:56

http://www.mimul.com/pebble/default/2013/02/18/1361189648941.html

 

최근 들어서 개인 정보 유출 등으로 인해 개인 정보 암호화 방법에 대해서 많이들 고민할 것입니다.
그 중에서 가장 중요하게 생각하는 것 중에 하나가 사용자의 패스워드인데, 과거에는 대부분 해시 MD5, SHA1 또는 SHA-256을 사용하고 있고, 좀 더 안다는 고급 개발자들이 있는 기업들만이 패스워드에 salt를 넣어 사용하고 있는 것으로 파악됩니다.(제가 잘못 알고 있을수도 있습니다.) 물론 최근들어 KISA의 제재로 salt + SHA1로 가는 추세이긴 합니다.
그러나 SHA1 + salt가 안전하다고 생각하는 분들은 아마 없을 듯 합니다. 그저 정부의 제재 수단을 회피하고자 적용하는 기업들이 많을 겁니다.

SHA1 + salt로 패스워드 보안 이슈 회피가 가능한가?

MD5, SHA1 또는 SHA-256 알고리즘은 보안이 우수하지 않다는 것은 다음 사이트에서 테스트해 보면 알 수 있습니다.
"http://google.com"을 MD5 인코딩한 값(c7b920f57e553df2bb68272f61570210)을 md5.rednoize.com 사이트에 넣고 검색 버튼을 클릭하면 바로 복호화되어 나옵니다.
md5.rednoize.com은 원본 데이터와 해시값의 데이터베이스를 참조해, 등록된 것은 즉시 해시값에서 데이터로 변환되어 출력하게 되는 구조입니다. 딕셔너리가 많이 확보되면 쉽게 패스워드를 풀 수 있게 된다는 소리도 됩니다.
이처럼 Rainbow Table("해시값이 이것이면, 암호는 이것"라는 테이블)을 사용해 일방향 암호화 기술을 무력화하게 됩니다. 이것만으로 보아도 일방향 암호화 알고리즘으로 MD5, SHA1 또는 SHA-256은 이제 한물간 일방향 암호화 알고리즘이라는 것을 쉽게 알 수 있죠.

그래서 보완책으로 나온 것이 SHA1 + salt입니다. salt를 붙이는 것으로, Rainbow Table 사용 접근 방식을 실질적으로 사용할 수 없게 하는 것으로 생각해 왔습니다.
SHA1 + salt의 처리방식은 아래와 같습니다.

$salt = "this is a salt";
$password = 'this is an password';
$hash = sha1($salt.$password);


자! 이 방법 또한 여기를 보듯이 GPU가 장착된 디바이스로 병렬처리하면 초당 수억건의 처리가 가능해 무력화 기술에 의해 무너질 수 있습니다. 이렇게 되면 딕셔너리 공격과 무력의 기술을 결합하여 많은 계정을 가진 대형 사이트에서도 상당한 양의 암호를 해독하는데, 그리 오랜 시간이 걸리지 않을 것입니다.

현재까지의 좀 더 강화된 방법으로는 뭐가 있을까요?

  • 각 사용자에게 고유 salt 값과 반복 횟수를 마련하는 것이고.
  • 좀 더 강력한 PBKDF2(http://en.wikipedia.org/wiki/PBKDF2), Bcrypt(http://www.openwall.com/crypt/), HMAC(http://en.wikipedia.org/wiki/HMAC)를 사용하는 것.
  • 불편하더라도 사용자에게 강한 강도의 패스워드를 받도록 유도하는 것.

정도가 될 것으로 보입니다.

PBKDF2 사용 샘플(Java)

public class PBKDF2 {

	//임의 salt를 생성
    private static byte[] createSalt() throws NoSuchAlgorithmException {
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[32];
        random.nextBytes(salt);
        return salt;
    }

    private static byte[] pbkdf2(char[] password, byte[] salt) 
     throws InvalidKeySpecException, NoSuchAlgorithmException {
        SecretKeyFactory sf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        // 반복 횟수 : 10000 번 결과 길이 : 256bit
        KeySpec ks = new PBEKeySpec(password, salt, 10000, 256);
        SecretKey sk = sf.generateSecret(ks);
        return sk.getEncoded();
    }

    private static void logging(String format, Object ... args) {
        System.out.printf(format + "%n", args);
    }

    public static void main(String[] args) 
     throws NoSuchAlgorithmException, InvalidKeySpecException {
        String p1 = args[0];
        String p2 = args[1];
        logging("password 1= %s", p1);
        logging("password 2= %s", p2);

        byte[] salt = createSalt();
        logging("salt: %s", Arrays.toString(salt));

        byte[] d1 = pbkdf2(p1.toCharArray(), salt);
        byte[] d2 = pbkdf2(p2.toCharArray(), salt);
        logging("derived 1= %s", Arrays.toString(d1));
        logging("derived 2= %s", Arrays.toString(d2));
    }
}

위의 10000이라는 숫자가 반복횟수이므로 이를 증가시켜 안전성을 높일 수 있습니다. 이 수를 사용자별로 데이터베이스에 저장하여 두면 중간에 반복 횟수를 높이고 안전성을 향상시킬 수도 있습니다.

[참조사이트]