본문 바로가기
보안

HMAC Signature(ft. 가상 시나리오, 예시 코드)

by 시니성 2023. 10. 20.

HMAC이란?

HMAC(Hashed Message Authentication Code)는 메시지 인증 코드를 생성하기 위해 암호화 해시 함수와 함께 사용되는 키 기반 알고리즘이다. HMAC은 메시지의 무결성(integrity)과 인증(authenticity)을 보장하게 됩니다.
HMAC은 해시 함수와 비밀 키를 사용하여 작동합니다.
메시지와 함께 비밀 키를 입력으로 사용하여 특정 출력(즉, HMAC 값)을 생성합니다.
수신자는 동일한 비밀 키와 메시지를 사용하여 HMAC 값을 계산하고, 수신된 HMAC 값과 비교합니다.
일치하면 메시지가 변경되지 않았으며 예상된 발송자로부터 왔음을 확신할 수 있습니다.

HMAC이 필요한 이유

  1. 데이터 무결성: 전송 중인 데이터가 변경되지 않았음을 보장하기 위해
  2. 인증: 메시지를 전송한 주체가 진짜 그 주체인지 확인하기 위해

가상 시나리오

서버 A(Spring-Kotlin 기반)와 서버 B(Node.js Express 기반)가 서로 통신을 할 때, 각 요청이 실제 해당 서버에서 발생했는지 확인하고, 데이터가 중간에 변경되지 않았는지 확인하기 위해 HMAC 서명을 사용한다.

플로우

  1. 서버 A가 서버 B에 요청을 보내려고 할 때, 해당 요청과 비밀 키를 사용하여 HMAC 서명을 생성한다.
  2. 이 서명을 요청 헤더에 포함시켜 서버 B에 전송한다.
  3. 서버 B가 요청을 받으면 동일한 비밀 키를 사용하여 요청으로부터 HMAC 서명을 다시 생성한다.
  4. 두 서명이 일치하면 요청이 유효하다고 판단한다.

예시 코드

1. Spring-Kotlin 서버

// build.gradle.kts에 추가
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("javax.xml.bind:jaxb-api:2.3.1")
package com.example.demo

import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

@RestController
class ApiController {

    private val secretKey = "SuperSecretKey" // 비밀 키

    @PostMapping("/sendToNode")
    fun sendToNodeServer(): String {
        val message = "Hello Node.js server!"
        val signature = generateHMAC(message, secretKey)

        // signature를 헤더에 추가하고 Node.js 서버에 전송
        // ... 

        return "Message sent!"
    }

    private fun generateHMAC(data: String, key: String): String {
        val sha256Hmac = Mac.getInstance("HmacSHA256")
        val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
        sha256Hmac.init(secretKey)

        return Base64.getEncoder().encodeToString(sha256Hmac.doFinal(data.toByteArray()))
    }
}

2. Node.js Express 서버

const express = require('express');
const crypto = require('crypto');
const app = express();

const SECRET_KEY = 'SuperSecretKey';

app.post('/receiveFromSpring', (req, res) => {
    const receivedSignature = req.headers['signature']; // 헤더에서 서명 추출
    const message = req.body.message;

    const computedSignature = generateHMAC(message, SECRET_KEY);

    if (receivedSignature === computedSignature) {
        res.send('Valid request!');
    } else {
        res.send('Invalid request!');
    }
});

function generateHMAC(data, key) {
    return crypto.createHmac('sha256', key).update(data).digest('base64');
}

app.listen(3000, () => {
    console.log('Server started on port 3000');
});

이 예시는 서버 A와 B 간에 메시지를 전송하고 HMAC 서명을 사용하여 메시지의 무결성과 인증을 검증하는 과정을 보여줍니다.