One-Way Encryption

박주현·2025년 4월 11일

One-Way Encryption

특징

  • Irreversible (단방향)
    • 복호화 불가
  • Deterministic (결정적)
  • 입력 값이 같을 때, 출력 값도 동일
  • Collision Resistant (충돌 저항성)
    • 충돌이 많은 경우
      • 보안 문제 발생 (다른 입력 값 → 같은 출력 값)
      • 추가적인 비교 로직이 필요
  • Avalanche Effect (눈사태 효과)
    • 입력값이 조금 바뀌더라도 완전히 다른 출력값 도출
      • 출력값을 통한 입력값 추측 불가
  • Fast (빠름)
    • 실시간 처리 및 대량 처리에 적합

활용

  • 비밀번호 및 기타 인증 정보 저장
    • Hash 값을 통해 암호화 이후 입력이 들어올 시, 해시 값을 비교
  • 무결성 검증
    • 동일 Hash값인지 확인을 통해 변조 여부 확인
  • 디지털 서명 / 인증서
    • 메시지 요약의 역할
      • 비대칭키 암호화와 함께 사용
  • 블록체인
    • Block의 data, 이전 Block의 Hash 등을 Hashing → Chain 구조 형성
      • Hash값이 바뀌면 Chain이 해제 → 변조 감지

추가 보안 사항

  • Salt
    • 같은 입력값이더라도 다른 Hash 결과를 생성하는 Random 값
      • Rainbow Table 공격 방지
  • Pepper
    • 입력값에 추가되는 공통 비밀값

Hacking Attack

Brute Force Attack

  • 공격 방법
    • 가능한 모든 조합을 시도해서 공격
  • 대응 방법
    • 복잡한 비밀번호
    • 시도 횟수 제한
    • CAPTCHA 도입
    • Hash 함수의 속도를 느리게 만드는 버전 사용
      • brcrypt, scrypt, Argon2

Rainbow Table Attack

  • 공격 방법
    • 미리 계산한 Plain Text → Hash Table로 빠르게 역추적
  • 대응 방법
    • Salt 사용을 통한 Hash값 변조
    • Pepper 추가 사용
    • Rainbow Table 무력화

Main-in-the-Middle

  • 공격 방법
    • 통신 중간에서 정보를 가로채거나 조작
    • Wifi 도청, HTTPS 사용 X
  • 대응 방법
    • HTTPS / TLS 사용
    • SSL 인증서 검증
    • VPN
    • 인증 Token이나 Cookie에 Secure, HttpOnly, SameSite 설정

MD5 (Message Digest Algorithm 5)

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Example {
    public static void main(String[] args) {
        String input = "hello";
        String md5Hash = getMD5Hash(input);
        System.out.println("MD5 Hash: " + md5Hash);
    }

    public static String getMD5Hash(String input) {
        try {
            // MessageDigest 객체 생성
            MessageDigest md = MessageDigest.getInstance("MD5");

            // 입력 문자열을 바이트 배열로 변환 후 해시 계산
            byte[] hashBytes = md.digest(input.getBytes());

            // 바이트 배열을 16진수 문자열로 변환
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                // 0xff로 마스킹 후 16진수로 변환
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0'); // 한 자리일 경우 앞에 0 추가
                hexString.append(hex);
            }

            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 알고리즘이 존재하지 않습니다.", e);
        }
    }
}
  • 입출력
    • 출력
      • 128bit (16byte)
      • 32자리의 16진수 문자열
    • 입력 제한 : X
  • 동작 과정
    1. Padding

      • Input Data를 512bit의 배수로 변환
        • 1 + 0…0 + InputData + InputData_Length(64bit)
    2. Initialization

      • 4개의 32bit buffer 초기화
    3. Block Processing

      • 512bit Block으로 Message 분할
        • Block = 16개의 32bit Word
    4. Main Loop

      • 4Round * 16 연산 ⇒ 64회 연산
      A = B + ((A+F(B,C,D) + M[k] +T[i]) <<< s)
      • F : 라운드 별 논리 함수
        RoundF(B,C,D)
        1`(B & C)
        2`(B & D)
        3B ^ C ^ D
        4`C ^ B
      • M[k] : Message Block
      • T[i] : 연산 별 상수
      • <<< s : 왼쪽으로 s bit 회전
    5. Result Add

      • 각 라운드가 끝날 때마다 A,B,C,D update
      • A, B, C, D를 합산하여 128bit hash 값으로 반환
  • 장점
    • 매우 빠름
    • 구현이 쉬움
    • CPU 사용량이 적음
    • Legacy 시스템과의 호환성
  • 단점
    • Collision 발생 가능
      • 무결정 보장 실패
    • Reverse Engineering에 취약
      • Rainbow Table Attack에 취약

SHA-1 (Secure Hash Algorithm 1)

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Scanner;

public class SHA1Example {

    public static String toHexString(byte[] hash) {
        StringBuilder hexString = new StringBuilder();

        for (byte b : hash) {
            // 각 바이트를 두 자리의 16진수로 변환
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0'); // 한 자리면 앞에 0 붙임
            hexString.append(hex);
        }

        return hexString.toString();
    }

    public static String sha1Hash(String input) {
        try {
            // SHA-1 해시 인스턴스 생성
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] hashBytes = md.digest(input.getBytes());

            return toHexString(hashBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e); // 예외 던지기
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("입력할 문자열: ");
        String input = scanner.nextLine();

        String hash = sha1Hash(input);
        System.out.println("SHA-1 해시값: " + hash);

        scanner.close();
    }
}
  • 입출력
    • 출력
      • 160bit (20byte)
    • 입력 제한 : X
  • 동작 과정
    1. Padding

      • Input Data를 512bit의 배수로 변환
        • 1 + 0…0 + InputData + InputData_Length(64bit)
    2. Initialize Hash Value

      • 5개의 32bit buffer 초기화
    3. Block Processing

      • 512bit Block으로 Message 분할
        • Block = 16개의 32bit Word
    4. Main Loop

      • 총 80회 세트 반복
      TEMP = (A <<< 5) + f(i,B,C,D) + E + W[i] + K[i]
          E = D
          D = C
          C = B <<< 30
          B = A
          A = TEMP
      • F(i, B, C, D) : i 값 범위에 따른 연산
        i 범위f(B,C,D)K[i] 값
        0 ≤ i ≤ 19B & D(~B) & D
        20 ≤ i ≤ 39B ^ C ^ D0x6ED9EBA1
        40 ≤ i ≤ 59B & CB & D
        60 ≤ i ≤ 79B ^ C ^ D0XCA62C1D6
    5. Hash Value Update

      H0 = H0 + A  
      H1 = H1 + B  
      H2 = H2 + C  
      H3 = H3 + D  
      H4 = H4 + E
    6. Hash Value Return

      • 각 Hash 값을 이어 붙여서 반환
  • 장점
    • Fast
      • 연산이 가볍고 구현이 간단
    • 넓은 호환성
      • 많은 시스템과 프로토콜에서 지원
        • Legacy System 포함
    • 간단한 구조
      • 학습용 또는 Hash 개념을 익히기에 구조가 깔끔
    • 고정된 출력 길이
      • 일정한 길이의 Hash 값을 출력
  • 단점
    • 보안성 부족
      • 낮은 Collision Resistant
        • Google의 "SHAttered" 공격 (2017)
        • 서로 다른 입력이 같은 Hash값을 반환 가능
      • 무차별 공격 대응력 낮음
        • 160bit의 길이가 무차별 공격을 방어하기에 짧음

SHA-2 (Secure Hash Algorithm 2)

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Scanner;

public class SHA256Example {

    public static String toHexString(byte[] hash) {
        StringBuilder hexString = new StringBuilder();

        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }

        return hexString.toString();
    }

    public static String sha256Hash(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256"); //SHA-512
            byte[] hashBytes = md.digest(input.getBytes());

            return toHexString(hashBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("입력할 문자열: ");
        String input = scanner.nextLine();

        String hash = sha256Hash(input);
        System.out.println("SHA-256 해시값: " + hash);

        scanner.close();
    }
}
  • 입출력
    • 출력
      이름출력 크기block 크기연산 bit
      SHA-224224bit512bit32bit
      SHA-226256bit512bit32bit
      SHA-384384bit1024bit64bit
      SHA-512512bit1024bit64bit
      SHA-512/224224bit1024bit64bit
      SHA-512/256256bit1024bit64bit
  • 동작 과정
    1. Padding & Parsing

      • Input Data를 512bit/1024bit의 배수로 변환
        • 1 + 0…0 + InputData + InputData_Length(64bit/128bit)
      • 512bit/1024bit씩 Block으로 분할
    2. Inital Hash Values

      • 8개의 32bit 해시값 초기화
        • 소수의 제곱근 기반
    3. 메시지 스케줄 배열 W 생성

      • Block을 16개의 32bit word로 분할
        • 64개의 Word를 제작
          • 0~15 : Block의 Word
          • 16~53 : 수식을 통해 연산
            W= σ₁(Wₜ₋₂) + Wₜ₋₇ + σ₀(Wₜ₋₁₅) + Wₜ₋₁₆
            • σ₀, σ₁ : bit 회전, shift 조합 함수
    4. Main Compress Loop

      T1 = h + Σ₁(e) + Ch(e, f, g) + K[t] + W[t];
      T2 = Σ₀(a) + Maj(a, b, c);
      
      h = g;
      g = f;
      f = e;
      e = d + T1;
      d = c;
      c = b;
      b = a;
      a = T1 + T2;
      • 총 64번의 연산
      • K : 라운드 상수 (소수의 제곱근 기반 상수)
      • Ch(x,y,z) : (x & y) ^ ((~X) & Z)
      • Maj(x,y,x) : (x & y) ^ (x & z) ^ (y & z)
      • Σ₀, Σ₁ : bit 회전 함수 조합
    5. Hash Value Return

      • 각 초기 Hash Value에 압축 값을 더한 Hash Value 반환
  • 장점
    • 높은 Collision Resistant
    • 다양한 출력 길이 제공
      • 보안성과 용량의 균형 조절 가능
  • 단점
    • 큰 계산 비용
    • 복호화 불가
    • 빠른 속도
      • 민감정보 hash에는 적절하지 않음
        • Brute Force Attack

Argon 2

import com.password4j.*;

public class Argon2HashingExample {
    public static void main(String[] args) {
        String password = "mySecretPassword";

        // Argon2 파라미터 설정
        Argon2Function argon2 = Argon2Function.getInstance(
                16 * 1024,     // memory cost in KB (예: 16MB)
                3,             // iterations (time cost)
                1,             // parallelism (number of threads)
                Argon2.ID,     // Argon2 type (ID가 일반적인 추천값)
                32,            // hash length
                64             // salt length
        );

        // 비밀번호 해싱
        Hash hash = Password.hash(password)
                .addRandomSalt()
                .with(argon2);

        System.out.println("Hash: " + hash.getResult());
        System.out.println("Salt: " + hash.getSalt());
        System.out.println("Algorithm: " + hash.getAlgorithm());

        // 해시 검증
        boolean verified = Password.check(password, hash.getResult())
                .addSalt(hash.getSalt())
                .with(argon2);

        System.out.println("비밀번호 일치 여부: " + verified);
    }
}
  • 종류
    Version용도특징
    Argon2d암호화 키 생성 등GPU 공격에 강함
    메모리 접근 패턴이 데이터 의존적
    Argon2i비밀번호 저장메모리 접근이 랜덤 → 사이드 채널 공격 방어
    Argon2id다목적d와 i의 하이브리드
    i→d로 변화 ⇒ 균형잡힌 보안
  • 동작 과정
    1. Parameters (Hash 강도 설정)

      passwordMessage
      salt무작위 문자열
      timeCost연산 반복 횟수
      memoryCost사용할 메모리 크기 (KB 단위)
      parallelism병렬 처리 Thread 개수
      outputLength출력 hash 길이
      typeArgon2i, Argon2d, Argon2id
    2. Initialize Hash Value

      H0 = Hash(password, salt, timeCost, memoryCost, parallelism, ...)
      • 이후 Memory Block Initialize에 사용
    3. Memory Block 구조 생성

      • MemoryCost에 따라 1024byte의 고정 크기 Block 할당
        • Argon2 연산의 공간
    4. Fill Memory

      Block[0] = Hash(H0 || 0)
      Block[1] = Hash(H0 || 1)
      Block[i] = G(Block[i-1], Block[ref]) 
      // G() : Block *2 -> Block (BLAKE2 기반)
      // 1. B' = BlockA XOR BlockB
      // 2. B'' = BLAKE2-like compression on B'
      // 3. Output Block = B''
      • Memory Block을 채우면서 timeCost만큼 반복 연산
        • 각 Block은 이전 Block, 다른 위치 Block, Round에 따라 참조되며 Hash
        • GPU/FPGA에서 효율적으로 병렬 처리되기 어려워짐 ⇒ 보안 강화
    5. Return Hash Value

      • 마지막 m개의 Block을 압축해서 반환
        • outputLength만큼 잘라서 사용
  • 장점
    • 강력한 보안성
      • 메모리 + 시간 기반 연산 병행
    • 메모리 하드닝
      • 대량의 메모리 사용량 ⇒ GPU/ASIC으로 병렬 계산 어려움
    • 사이드 채널 공격 방어
      • 메모리 접근 예측 불가능
    • 설정 가능한 Parameter
      • 환경에 맞는 보안 설정
  • 단점
    • CPU 및 메모리 비용
      • Resource를 많이 필요 → Embedded, Low Spec Server에 부담
    • Slow
      • Memory를 많이 사용할수록 Hashing 속도 둔화
    • 복잡한 하드웨어 구현
      • 복잡한 메모리 접근
        • ASIC/GPU 최적화 까다로움
    • 지원
      • Legacy System, Library 지원 여부 확인 필요
        • Java 내장 Library X (외부 Library 필요)
profile
코딩초보의 공부일기

0개의 댓글