[보안] HMAC이란? 개념, 동작 원리, Java 구현까지 총정리

업데이트:

✅ HMAC란?

HMAC 동작 구조

HMAC은 Hash-based Message Authentication Code의 약자로, 암호화 해시 함수(Hash Function)와 비밀 키(Secret Key)를 조합하여 메시지의 무결성과 인증을 검증하는 방법이다. 쉽게 말해, HMAC은 메시지가 변조되지 않았음을 확인할 수 있도록 하는 보안 기법으로, 메시지 인증 코드(MAC, Message Authentication Code) 의 한 종류다.

메세지를 주고받을 때, 우리는 메세지가 변조되었는지 확인 할 필요가 있다. 반드시 필요하지는 않지만 API호출처럼 정확한 호출과 반환이 필요한 경우나 보안을 위해서는 확인해주는 편이 좋다. 전송할때의 원본 메시지와 전달된 메시지를 비교하여 변조 여부를 확인하는 방식을 MAC라고 하는데 이때 해싱과 공유키를 사용한 MAC 기술이 바로 HMAC이다.

✅ HMAC의 특징

1. 비밀 키(Secret Key) 사용

  • 단순한 해시 함수가 아닌, 비밀 키를 추가하여 해시 값을 생성한다.
  • 비밀 키 없이는 올바른 해시 값을 생성할 수 없으므로 위조 방지가 가능하다.

2. 무결성(Integrity) 보장

  • 메시지가 전송 중 변조되지 않았음을 확인할 수 있다.

3. 메시지 인증(Authentication) 제공

  • 메시지를 생성한 주체가 신뢰할 수 있는 발신자임을 확인할 수 있다.

4. 다양한 해시 함수 사용 가능

  • SHA-256, SHA-512, MD5 등 다양한 해시 함수와 함께 사용할 수 있다.

✅ HMAC vs 일반 해시 함수 차이

구분 일반 해시 (SHA-256 등) HMAC
비밀 키 사용하지 않음 비밀 키 필수
인증 불가능 (누구나 해시 생성 가능) 가능 (키를 가진 사람만 생성 가능)
무결성 보장 보장
위조 방지 취약 (공격자가 해시 재생성 가능) 강력 (키 없이 재생성 불가)
사용 예 파일 체크섬, 비밀번호 저장 API 인증, JWT 서명, TLS

일반 해시 함수만으로는 누가 해시를 생성했는지 알 수 없다. 공격자가 메시지를 변조한 뒤 새로운 해시를 생성하면 수신자는 이를 구별할 수 없다. HMAC은 비밀 키를 추가하여 이 문제를 해결한다.

✅ HMAC 동작 원리

HMAC의 내부 동작은 다음과 같은 수식으로 표현된다.

HMAC(K, M) = H((K' ⊕ opad) || H((K' ⊕ ipad) || M))
  • K : 비밀 키
  • M : 메시지
  • H : 해시 함수 (SHA-256 등)
  • K’ : 블록 크기에 맞게 조정된 키
  • opad : 외부 패딩 (0x5c 반복)
  • ipad : 내부 패딩 (0x36 반복)
  • : XOR 연산
  • || : 문자열 연결

동작 순서:

  1. 비밀 키를 해시 함수의 블록 크기에 맞게 조정
  2. 조정된 키와 ipad를 XOR → 메시지와 결합 → 내부 해시 생성
  3. 조정된 키와 opad를 XOR → 내부 해시 결과와 결합 → 최종 HMAC 생성

이중 해시 구조 덕분에 길이 확장 공격(Length Extension Attack) 에 안전하다.

✅ HMAC을 왜 사용할까?

일반적인 해시 함수(SHA-256, MD5 등)는 메시지를 고정된 크기의 해시 값으로 변환하지만, 보안적인 약점이 있다.
예를 들어, 단순한 해시 값만으로는 인증을 보장할 수 없으며, 공격자가 해시 값을 조작할 가능성이 존재한다. HMAC은 비밀 키를 추가하여 이러한 문제를 해결한다.
즉, HMAC을 사용하면 공격자가 메시지를 변조하더라도, 올바른 비밀 키 없이는 새로운 HMAC 값을 생성할 수 없으므로 위조가 불가능하다.

✅ HMAC은 어디에 사용할까?

1. API 인증(Authentication)

  • REST API, GraphQL API 등에서 클라이언트와 서버 간의 인증을 위해 사용된다.
  • 예: AWS Signature Version 4, OAuth

2. 데이터 무결성 보호

  • 메시지가 변조되지 않았는지 확인하는 용도로 사용된다.
  • 예: 금융 거래, 온라인 결제 시스템

3. 비밀번호 저장 및 검증

  • HMAC을 이용하여 비밀번호를 안전하게 저장하고 검증할 수 있다.
  • 예: PBKDF2(Password-Based Key Derivation Function 2)

4. TLS/SSL 보안 프로토콜

  • 네트워크 통신에서 데이터 변조 방지 및 인증을 제공한다.
  • 예: HTTPS 통신, VPN

5. 디지털 서명 및 보안 토큰

  • JSON Web Token(JWT) 등의 토큰 기반 인증 시스템에서 HMAC을 활용할 수 있다.
  • 예: HMAC-SHA256을 이용한 JWT 서명

✅ HMAC 사용예시 (API 검증)

📌 1. 클라이언트 측 (HMAC 서명 생성)


클라이언트는 API 요청을 보낼 때 비밀 키(Secret Key)와 요청 데이터를 이용하여 HMAC을 생성한 후, 이를 헤더에 포함해야 한다.

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class HmacUtil {

    // HMAC 서명 생성 함수
    public static String generateHMAC(String data, String secretKey) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            mac.init(secretKeySpec);

            byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(hmacBytes);
        } catch (Exception e) {
            throw new RuntimeException("Error generating HMAC", e);
        }
    }
}

이제 API 요청을 보낼 때, HmacUtil.generateHMAC()을 사용하여 서명을 생성하고 헤더에 포함시킨다.

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class HmacApiClient {
    private static final String API_URL = "http://localhost:8080/api/protected";
    private static final String SECRET_KEY = "mySuperSecretKey";

    public static void main(String[] args) {
        String requestBody = "{\"userId\": \"12345\", \"action\": \"getInfo\"}";

        String signature = HmacUtil.generateHMAC(requestBody, SECRET_KEY);

        HttpHeaders headers = new HttpHeaders();
        headers.set("X-API-KEY", "publicKey123");
        headers.set("X-SIGNATURE", signature);

        RestTemplate restTemplate = new RestTemplate();
        HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
        ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, request, String.class);

        System.out.println("Response: " + response.getBody());
    }
}

🔹 클라이언트 요청 흐름

  1. 요청 데이터(JSON)를 문자열로 변환
  2. 비밀 키와 함께 HMAC-SHA256 서명 생성
  3. X-API-KEY(공개 키)와 X-SIGNATURE(서명)를 요청 헤더에 추가
  4. 서버로 요청 전송

📌 2. 서버 측 (HMAC 서명 검증)


서버는 클라이언트가 보낸 X-API-KEYX-SIGNATURE를 검증하여 요청을 인증한다.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@RestController
@RequestMapping("/api")
public class HmacApiController {
    
    private static final String SECRET_KEY = "mySuperSecretKey";
    private static final String PUBLIC_KEY = "publicKey123";

    @PostMapping("/protected")
    public ResponseEntity<String> handleRequest(
            @RequestHeader("X-API-KEY") String apiKey,
            @RequestHeader("X-SIGNATURE") String clientSignature,
            @RequestBody String requestBody) {

        if (!PUBLIC_KEY.equals(apiKey)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid API Key");
        }

        String serverSignature = generateHMAC(requestBody, SECRET_KEY);

        if (!serverSignature.equals(clientSignature)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid Signature");
        }

        return ResponseEntity.ok("Authenticated Successfully");
    }

    private String generateHMAC(String data, String secretKey) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            mac.init(secretKeySpec);
            byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(hmacBytes);
        } catch (Exception e) {
            throw new RuntimeException("Error generating HMAC", e);
        }
    }
}

🔹 서버 검증 흐름

  1. API 키 검증 - 요청 헤더에 포함된 X-API-KEY가 사전에 등록된 키인지 확인
  2. HMAC 서명 재생성 - 서버에서도 동일한 방식으로 요청 데이터로 HMAC을 생성
  3. 서명 비교 - 클라이언트가 보낸 X-SIGNATURE 값과 서버가 생성한 값이 일치하는지 확인
  4. 검증 성공 시 요청 처리, 실패 시 401(Unauthorized) 응답 반환

⚠️ 실무에서는 SECRET_KEY를 코드에 하드코딩하지 않고, 환경변수나 AWS Secrets Manager 같은 비밀 관리 서비스를 사용해야 한다.

첫 번째 글입니다 가장 최근 글입니다

댓글남기기