본문 바로가기
[JAVA]

자바에서 SHA-256 Hashing 구현하기

by 황원용 2023. 9. 3.
728x90
💡 자바에서 SHA-256 해시 알고리즘으로 해싱하는 방법을 알아보자.

 

❓SHA-256

  • SHA-256은 해시 알고리즘 중 하나로, 임의의 길이를 가진 데이터를 입력으로 받아 256비트(32바이트) 길이의 고정된 해시 값을 출력한다.
  • 입력 데이터가 조금만 변경되어도 완전히 다른 해시 값을 생성하기 때문에 변조에 대한 높은 보안성을 제공한다.
  • 충돌 가능성(서로 다른 문자에서 같은 해시값이 나오는 등)이 매우 낮아 데이터 무결성 검사와 비밀번호 저장 등 다양한 보안 용도에 사용된다.
  • 데이터 무결성 검사의 경우, 같은 문자로부터 나오는 해시 값은 같다는 것을 이용하여 메시지의 변조 여부를 확인하는 데에 사용된다.
  • 단방향 함수이므로 일반적으로는 복호화가 불가능하다.

 

자바에서의 SHA-256

자바에서는

import java.security.MessageDigest;

를 통해 Sha-256 해시함수를 구현할 수 있다.

 

 

📌 MessageDigest 클래스에 있는 주석

This MessageDigest class provides applications the functionality of a message digest algorithm, such as SHA-1 or SHA-256. Message digests are secure one-way hash functions that take arbitrary-sized data and output a fixed-length hash value.

MessageDigest 클래스는 SHA-1 또는 SHA-256과 같은 메시지 다이제스트 알고리즘의 기능을 애플리케이션에 제공합니다. 메시지 다이제스트는 임의 크기의 데이터를 입력으로 받아 고정 길이의 해시 값으로 출력하는 안전한 단방향 해시 함수입니다.

...

Application developers should only take notice of the methods defined in this MessageDigest class; all the methods in the superclass are intended for cryptographic service providers who wish to supply their own implementations of message digest algorithms.

응용 프로그램 개발자들은 이 MessageDigest 클래스에서 정의된 메서드만 주목해야 하며, 슈퍼클래스에 정의된 모든 메서드는 암호 서비스 제공자가 자체적인 메시지 다이제스트 알고리즘 구현을 제공하려는 경우에 사용됩니다.

Every implementation of the Java platform is required to support the following standard MessageDigest algorithms:
Java 플랫폼의 모든 구현체에서 다음 표준 MessageDigest 알고리즘들을 지원해야 합니다:

- SHA-1
- SHA-256

...
  • 위 설명에 나와있듯이 MessageDigest 클래스로 SHA-1, SHA-256 알고리즘을 구현할 수 있다.

 

📌 Message Digest

  • 임의 길이 메시지에 단방향 해시함수가 적용되어 일정한 길이로 축약 생성된 비트열을 뜻한다.

 

🤔 여기서 드는 의문,

다른 해시 함수 알고리즘도 있는데 MessageDigest 클래스는 왜 SHA-1과 SHA-256만 지원하는 걸까?

 

 

🤖 뤼튼의 답변

  • Java에서 MessageDigest 클래스에서 SHA-1과 SHA-256만 제공하는 이유는 암호학적 요구사항과 알고리즘의 안정성에 기인합니다.
  • SHA (Secure Hash Algorithm)은 해시 함수로서, 데이터의 무결성을 검증하거나 고유한 식별자를 생성하는 데 사용됩니다. 그러나 SHA-1은 현재 강력한 보안 요구사항을 충족하지 못하고 있습니다.
  • SHA-1은 충돌 저항성(collision resistance)이 부족하여 같은 해시값을 가진 서로 다른 입력 데이터를 찾는 것이 상대적으로 쉽게 가능해지는 문제가 있습니다. 이러한 취약점으로 인해 2005년부터 보안 커뮤니티에서 SHA-1의 사용을 권장하지 않고 있으며, 현재는 안전하지 않은 해시 알고리즘으로 분류됩니다.
  • 반면에 SHA-256은 충돌 저항성이 강화된 보다 안전한 해시 알고리즘이며, 현재까지 널리 사용되고 있는 암호화 기법 중 하나입니다. SHA-256은 32바이트(256비트) 길이의 해시 값을 생성하며, 보다 큰 비트 수를 가지기 때문에 충돌 발생 확률도 매우 낮아집니다.

현재는 SHA-256을 메인으로 사용하는 것이 안전하게 해싱하는 방법인 것 같다. 

 

자주 사용되는 메서드

getInstance(String algorithm)

  • 파라미터로 받은 알고리즘을 수행하는 MessageDigest 객체를 생성한다.

update(byte[] input)

  • 객체 내에 저장된 digest의 값을 갱신한다.

digest()

  • update()를 실행하여 해시 값을 산출한 후 해시화된 값을 반환한다.

 

📌 구현 코드

return to Base64

public class Sha256ToBase64 {

    /**
     * 입력 받은 문자열을 SHA-256으로 해싱하여 해시 값을 반환함
     * @param text
     * @return 해시값
     * @throws NoSuchAlgorithmException
     */
    public String encrypt(String text) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(text.getBytes());

        return Base64.getEncoder().encodeToString(md.digest());
    }
}
  • Base64 인코딩은 일반적으로 이진 데이터를 텍스트 형식으로 변환하여 전송하거나 저장하는 데 사용된다.
    • 원래의 데이터를 6비트 블록으로 나눈 다음, 각 블록을 해당하는 문자로 매핑하여 인코딩 된 문자열을 생성한다.
  • 리턴을 제외하고는 Hex 방식과 같기 때문에 나머지는 아래에서 설명한다.

return to Hex

public class Sha256ToHex {

    /**
     * 입력 받은 문자열을 SHA-256으로 해싱하여 해시 값을 반환함
     * @param text
     * @return 해시값
     * @throws NoSuchAlgorithmException
     */
    public String encrypt(String text) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(text.getBytes());

        return bytesToHex(md.digest());
    }

    /**
     * 바이트 배열을 16진수 문자열로 반환함
     * @param bytes
     * @return 16진수 문자열
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder builder = new StringBuilder();
        for (byte b : bytes) {
            builder.append(String.format("%02x", b));
        }
        return builder.toString();
    }
}

encrypt(String text) 메서드

  • 주어진 문자열 text를 SHA-256으로 암호화(해싱) 한 결과를 반환한다.
  • 먼저 MessageDigest.getInstance("SHA-256")을 호출하여 SHA-256 해시 함수를 사용할 수 있는 MessageDigest 객체(md)를 얻는다.
  • 입력 문자열(text)을 바이트 배열로 변환하고 md.update() 메서드에 전달한다.
    • 문자열을 해시 함수에 직접 전달할 수는 없고 문자열을 바이트 배열로 변환한 후에야 비로소 해시 함수에 전달할 수 있다.
  • text.getBytes()를 호출할 때 파라미터로 아무 값도 적지 않으면 문자열 text를 해당 플랫폼의 기본 인코딩(대부분은 UTF-8)을 사용하여 바이트 배열로 변환한다.
    • 파라미터를 적는 경우 getBytes("utf-8")과 같은 형태로 적을 수 있다.
  • 최종적으로 해시 함수의 결괏값인 바이트 배열(md.digest())을 16진수 형식의 문자열로 변환하기 위해 bytesToHex() 메서드를 호출하고 그 결과 값을 반환한다.

 

bytesToHex(byte[] bytes) 메서드

  • 주어진 바이트 배열(bytes)의 각 요소를 16진수 형식의 문자열로 변환하여 이들을 연결한 후 해당 결과 값을 반환한다.
  • 먼저 변환된 16진수 값을 저장할 StringBuilder 객체(builder)를 생성한다.
  • 반복문에서는 바이트 배열의 각 요소인 byte b에 대해 %02x 포맷 지정자를 사용하여 현재 바이트 값을 2자리 16진수로 변환하고 그 결과 값을 builder.append() 메서드로 추가한다.
    • "0a", "1b", "ff" 등
  • 모든 요소가 처리되면, 최종적으로 연결된 16진수 값들을 나타내는 문자열 (builder.toString()) 을 반환한다.

 

📌 결과 예시

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

        Sha256ToBase64 sha256ToBase64 = new Sha256ToBase64();
        Sha256ToHex sha256ToHex = new Sha256ToHex();

        String text = "안녕하세요.";

        // SHA-256 Return Base64
        System.out.println(sha256ToBase64.encrypt(text)); //ixGNZ0H3z6Gn7iRtDdo58vAL+f0ge05sf62HoVQ0pRM=
        System.out.println(sha256ToHex.encrypt(text)); //8b118d6741f7cfa1a7ee246d0dda39f2f00bf9fd207b4e6c7fad87a15434a513
    }

 

📌 정리

  • 리턴값에서 바이트 배열 타입을 문자열 타입으로 변환해 주는 방법에는 대표적으로 Base64를 이용한 방법과 16진수 변환을 이용한 방법이 있다.
  • 어느 쪽을 사용해도 상관이 없지만 해시함수를 만들어 개인키나, 대칭키로 암호화하여 보내는 작업을 할 때 Encrypt 하는 쪽과 Decrypt 하는 쪽이 모두 같은 방법으로 통일해야 한다.

 

 

참고

뤼튼

http://wiki.hash.kr/index.php/SHA256#cite_note-8

 

728x90