[보안] 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을 왜 사용할까?

일반적인 해시 함수(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\"}";

        // HMAC 서명 생성
        String signature = HmacUtil.generateHMAC(requestBody, SECRET_KEY);

        // HTTP 요청 헤더 설정
        HttpHeaders headers = new HttpHeaders();
        headers.set("X-API-KEY", "publicKey123");  // 공개 키
        headers.set("X-SIGNATURE", signature);    // 생성한 HMAC 서명

        // HTTP 요청 전송
        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. 서버 측

서버는 클라이언트가 보낸 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) {

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

        // 2. 서버에서 HMAC 서명 생성 (클라이언트와 같은 로직)
        String serverSignature = generateHMAC(requestBody, SECRET_KEY);

        // 3. 서명 비교 (클라이언트가 보낸 서명과 비교)
        if (!serverSignature.equals(clientSignature)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid Signature");
        }

        // 4. 요청 정상 처리
        return ResponseEntity.ok("Authenticated Successfully");
    }

    // HMAC 생성 함수
    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 서명 재생성

    • 서버에서도 HmacUtil.generateHMAC()과 동일한 방식으로 요청 데이터로 HMAC을 생성
  3. 서명 비교

    • 클라이언트가 보낸 X-SIGNATURE 값과 서버가 생성한 값이 일치하는지 확인
  4. 검증 성공 시 요청 처리, 실패 시 401(Unauthorized) 응답 반환

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

댓글남기기