해시함수와 적용
728x90
SMALL
  1. 해시함수를 이용한 비밀번호 저장 및 비교 서비스

DB의 보안성을 보장할 수 없기 때문에, 해시함수를 이용해 비밀번호를 해시화해서 비교해야한다.

  1. 적용 알고리즘: 해시함수
  2. 알고리즘 개요

2.1. 해시 함수란?

해시 알고리즘이란 어떤 문자열 x가 해시함수 h(x)를 통해 해시화 된 fixed length의 y의 문자열을 만드는 알고리즘을 말한다.

암호화 해시 함수를 사용해서 비밀번호의 무결성과 보안성을 지킬 수 있다.

해시함수의 성질은 다음과 같다.

Properties of Cryptographic Hash Functions (암호화 해시함수의 성질)

  • Compression (압축) - 해시 함숫값(출력)의 크기는 평문(입력)의 크기보다 작다.
  • Efficiency (효율성) - 해시 함숫값은 빠르게 계산된다.  - 즉, 암호화가 빠르다.
  • One-Way Property (단방향성) - h(x)=y일 때, y를 통해서 x가 얼마인지를 알아낼 수 없다.
  • Weak Collision Resistance (약한 충돌 저항성) - h(x)=h(y)일 때, x와 h(x)를 알고 있다 하더라도, x≠y인 y가 얼마인지를 알아낼 수 없다.  - 즉, Hash Collision을 발생시키는 값(해시함숫값이 같은 값)을 알아낼 수 있을 확률이 충분히 작은 경우, "약한 충돌 저항성을 가졌다."라 표현한다.
  • Strong Collision Resistance (강한 충돌 저항성) - h(x)=h(y)일 때, x≠y인 어떠한 (x,y) Pair를 알아낼 수 없다.  - 즉, Hash Collision을 발생시키는 값들(해시함숫값이 같은 값들)을 하나라도 찾아낼 수 없는 경우, "강한 충돌 저항성을 가졌다."라 표현한다.  (Hash Collision이 발생되지 않음을 보장하는 것은 아니다.)

2.2. 해시 함수의 종류

MD5 (Message-Digest Algorithm 5; RFC 1321) (DEPRECATED)

  • 1991년에 Ronald Rivest가 개발한 128비트 암호화 해시 함수이다.
  • 512비트의 Plaintext를 128비트로 암호화한다.
  • 파일이 원본 상태인지를 확인하는 무결성 검사에 사용된다.
  • Collision Pair(충돌 쌍; 해시값이 같은 두 값)를 찾는 방법이 공개되어 사용하지 않는 것이 권장된다.

SHA-1 (Secure Hash Algorithm-1; 안전한 해시 알고리즘 1)

  • 미국 정부에서 표준으로 지정한, 160비트 암호화 방식이다.
  • MD5와 내부 동작 방식이 유사하다.
  • 2017년에 Break 되어 사용하지 않는 것이 권장된다.

SHA-2 Family (Secure Hash Algorithm-2; 안전한 해시 알고리즘 2)

  • SHA-224, SHA-256, SHA-384, SHA-512가 속하는 암호화 방식 군이다.

(뒤에 붙는 숫자는 출력 값의 비트수를 의미한다.)

  • 기본 알고리즘은 SHA-1와 동일하다.
  • 일반적으로 사용되는 그룹이며, 특별히 높은 보안성이 요구되는 서비스에서는 SHA-3가 사용된다.

SHA-3 Family (Secure Hash Algorithm-3; 안전한 해시 알고리즘 3)

  • SHA3-224, SHA3-256, SHA3-384, SHA3-512가 속하는 암호화 방식군이다.

(뒤에 붙는 숫자는 출력값의 비트수를 의미한다.)

  • SHA-2 Family, SHA-3 Family와 다른 방식의 알고리즘으로 동작한다.
  • 특별히 높은 보안성이 요구되는 서비스에 사용되는 그룹이며, 일반적인 경우에는 SHA-2가 사용된다.

해시 함수는 위와 같은 종류가 있다. 그러나 SHA-1 family까지는 break 되었기 때문에, 사용하지 않는 것이 권장된다. 이는 해시 함수의 길이를 기준으로 그 충돌 저항성이 낮아지기 때문인데, 해시 함수의 충돌 저항성과 그것이 보안성에 미치는 영향에 대해서 다음과 같은 예시를 들어 설명하겠다.

 

2.3. Birthday Paradox

한 방에 N명의 사람이 존재한다고 하자. 그 방에 나와 생일이 정확히 일치하는 사람이 존재하는확률이 1/2이상이 되기 위한 N의 최솟값은 몇일까?

1/2 = 1 – (364/365)^N 이므로, N의 근사치는 253이다.

그렇다면 한 방에 생일이 정확히 같은 두 명이 존재하는 확률이 1/2 이상인 N의 수는? 100명? 50명? 놀랍게도 23이다. 이는 다음의 식으로 알 수 있다.

K 개의 공을 n개의 통으로 넣을 대, 단 하나의 통도 2개의 공을 포함하지 않을 확률은

이 확률은 k가 √n개를 약간 넘는다면 1/2이 된다. 만약 n이 무한대가 된다면, 공이 충돌할 확률은 √n*pi / 2가 된다.

  • 즉, 생일 문제(생일 역설)를 기반으로, 해시 충돌을 일으키는 값들을 찾아내는 비용을 낮출 수 있다.
  • 해시 함숫값이 n Bits일 때, 해시 함숫값의 경우의 수는 2n 개이지만, 생일 문제를 통해 우리는 √2^n 개의 Random Value만을 탐색해도 50%에 가까운 확률로 충돌을 일으키는 값을 찾아낼 수 있다.
  • 암호학에서 생일 문제가 암시하는 바는 아래와 같다.

N Bit로 구성된 Symmetric Key를 알아내기 위해서는 2^(N−1) 개의 경우를 조사해야 하지만,

N Bit로 구성된 Hash Value를 알아내기 위해서는 √2^n 개의 경우만 조사해도 충분하다.

그러므로, 해시값의 크기(Bit)를 충분히 크게 확보해놓아야 Break를 방지할 수 있다.

따라서 SHA-2 family 이상을 사용하는 것이 적합하다.

 

위에서 약한 충돌 저항성에 대해 설명했다. 다시 설명하자면, Hash Collision을 발생시키는 값(해시함숫값이 같은 값)을 알아낼 수 있을 확률이 충분히 작은 경우, "약한 충돌 저항성을 가졌다” 라고 표현한다. 따라서 해시 함수는 가능한 한 충돌이 적게 발생하는 것이 좋으나, 입력 값의 크기가 매우 크다면 일부 충돌을 피할 수 없다.

 

2.4. Salt and pepper

하지만, salt and pepper 기법을 사용하면 약한 충돌 저항성을 강화할 수 있다. 이 기법은 해시 함수에 추가적인 임의의 값을 추가해 해시 값을 변형 시키는데, salt 는 입력 값과 함께 사용하는 임의의 문자열이고, pepper는 해시 함수 내부에서 사용되는 고정된 값이다. Salt는 일반적으로 각 사용자마다 다른 랜덤 값을 사용해, 그 사용자가 이미 해킹된 다른 사이트와 같은 비밀번호를 사용하더라도, 공격자가 그 값을 재사용해서 공격할 수 있는 가능성을 줄인다. Pepper는 해시 함수에 고정된 값을 추가함으로서, 공격자가 해시 함수 내부를 파악하여 공격하는 것을 어렵게 만든다.

즉, salt를 사용하는 식은 다음과 같다.

y = h(pwd, salt)

pepper를 사용하는 식은 다음과 같다.

y = h(pwd, pepper)

  1. 적용 서비스

해시함수를 이용한 비밀번호 저장 및 비교 서비스

  1. 적용 서비스 개발 개요

 

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class PasswordEncryption {

	// salt and pepper values
	private static final String PEPPER = "fixedPepper";

	// method to generate a random salt
	private static String generateSalt() {
    
        SecureRandom random = new SecureRandom();
        byte[] saltBytes = new byte[16];
        random.nextBytes(saltBytes);
        
        return Base64.getEncoder().encodeToString(saltBytes);
}

// method to encrypt the password using salt and pepper

    public static String encrypt(String password) throws NoSuchAlgorithmException {

        String salt = generateSalt();

        password += salt + PEPPER;
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(password.getBytes());
        byte[] hashedPassword = md.digest();

        return salt + ":" + Base64.getEncoder().encodeToString(hashedPassword);
    }

// method to check if the input password matches the encrypted password

    public static boolean match(String inputPassword, String encryptedPassword) throws NoSuchAlgorithmException {

        String[] parts = encryptedPassword.split(":");
        String salt = parts[0];
        String hashedPassword = parts[1];
        inputPassword += salt + PEPPER;
        
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(inputPassword.getBytes());
        byte[] hashedInputPassword = md.digest();
        return Base64.getEncoder().encodeToString(hashedInputPassword).equals(hashedPassword);

    }

// example usage

    public static void main(String[] args) throws NoSuchAlgorithmException {

        String password = "myPassword123";

        String encryptedPassword = encrypt(password);

        System.out.println("Encrypted password: " + encryptedPassword);
        System.out.println("Match: " + match(password, encryptedPassword));
        System.out.println("Match with wrong password: " + match("wrongPassword", encryptedPassword));
    }

}

 

위의 코드는 SHA-256 해시 함수를 사용하여 비밀번호를 암호화하고, Base64 인코딩을 사용하여 바이너리 데이터를 문자열로 변환한다. Salt와 Pepper는 각각 generateSalt()와 PEPPER 상수에서 생성된다.

match() 메서드는 저장된 암호화된 비밀번호와 입력한 비밀번호를 비교하여 일치 여부를 반환한다.

728x90
LIST