본문 바로가기

Spring Boot

[Spring Boot] HTTP Body 암호화 및 간단한 예제

728x90

민감한 데이터 전송 시 보안 방법

1. 전송 시 사용하는 보안 방식

  • TLS(Transport Layer Security) 또는 SSL(Secure Sockets Layer) 프로토콜은 인터넷을 통한 데이터 전송에서 암호화를 제공합니다.
  • 민감한 데이터를 전송할 때, 클라이언트와 서버 간의 연결을 암호화하여 중간에서 데이터가 탈취되는 것을 방지합니다.
  • TLSHTTP over SSL(HTTPS)로 구현되어 웹 브라우저와 서버 간의 통신을 안전하게 암호화합니다.
  • 이 방식은 데이터 전송 중 보안을 보장하며, 연결이 안전하게 유지되도록 합니다.

추가 설명:

  • HTTPSHTTP 프로토콜 위에 TLS/SSL을 적용하여 데이터의 기밀성을 보장합니다. 이로 인해 중간자 공격(MITM)이나 데이터 스니핑을 방지할 수 있습니다.

 

 

2. 데이터 자체의 암호화

민감한 데이터를 서버에 전송하기 전에 암호화하여 안전하게 보호합니다.

이때 사용하는 암호화 방식은 대칭키 암호화(AES) 또는 비대칭키 암호화(RSA)입니다.

  • AES (Advanced Encryption Standard):
    • 대칭키 암호화 방식으로, 클라이언트에서 데이터를 암호화하여 서버에 전송하고, 서버에서 복호화하는 방식입니다.
    • 암호화된 데이터는 AES 키를 사용하여 복호화합니다.
  • RSA (Rivest–Shamir–Adleman):
    • 공개키 암호화 방식으로, 서버가 공개키를 제공하고 클라이언트는 이 공개 키를 통해 데이터를 암호화합니다.
    • 서버는 자신의 비공개키를 사용하여 복호화할 수 있습니다.
    • RSA는 성능상 느리지만 보안성이 높아 더 안전한 방식으로 사용됩니다.

추가 설명:

  • AES는 주로 대용량 데이터 암호화에 적합하며 빠른 처리 속도를 자랑합니다. RSA는 공개키와 비공개키의 관리가 필요하지만, 비대칭 암호화로 보안성이 뛰어납니다.

 

 

3. 세션 키 관리

  • 세션 키는 클라이언트와 서버 간의 단기적인 암호화 키로, 세션이 종료되면 폐기됩니다.
  • 세션 키는 대칭키 방식(AES)을 사용하여 데이터를 암호화하며, 고유한 세션 키가 클라이언트마다 다르게 생성됩니다.
  • 세션 키를 안전하게 관리하고 암호화된 데이터를 전송하는 방식으로 보안을 강화합니다.

 

4. AES와 CBC 모드

  • AES-CBC (Cipher Block Chaining)는 블록 단위로 데이터를 암호화하는 방식으로, 각 블록을 암호화할 때 이전 블록의 암호문을 사용하여 암호화합니다.
  • IV(Initialization Vector)를 사용하여 각 암호화 세션마다 다르게 암호화된 결과를 생성합니다.

추가 설명:

  • CBC 모드는 IV암호문을 함께 사용하며, IV는 무작위로 생성되고, 암호문은 블록 크기에 맞춰 암호화됩니다.
  • AES-CBC는 암호화의 안전성을 보장하는 방식으로, 패딩암호화 후 복호화 과정을 신중하게 처리해야 합니다.

 

 

5. 클라이언트 측 암호화 구현 예제

아래는 세션 키 기반의 암호화/복호화 예제를 구현한 코드입니다. 이 코드는 AES-CBC 방식으로 데이터 암호화 및 복호화를 진행하며, 서버와 클라이언트 간의 안전한 데이터 전송을 구현합니다.

Spring Boot 백엔드 예제 (세션 키 생성 및 데이터 복호화)

import org.springframework.web.bind.annotation.*;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/session")
public class SessionKeyController {

    private final Map<String, SecretKey> sessionKeys = new HashMap<>();

    // 세션 키 생성 및 반환
    @GetMapping("/key")
    public Map<String, String> generateSessionKey(@RequestParam String sessionId) throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128); // AES-128 키 생성
        SecretKey secretKey = keyGen.generateKey();

        sessionKeys.put(sessionId, secretKey); // 세션 키 저장
        String encodedKey = Base64.getEncoder().encodeToString(secretKey.getEncoded());
        return Map.of("sessionKey", encodedKey);
    }

    // 클라이언트 데이터 복호화
    @PostMapping("/decrypt")
    public Map<String, String> decryptData(@RequestParam String sessionId, @RequestBody Map<String, String> encryptedData) throws Exception {
        SecretKey secretKey = sessionKeys.get(sessionId);
        if (secretKey == null) {
            return Map.of("error", "Invalid session ID");
        }

        // 암호화된 데이터 추출
        String encryptedText = encryptedData.get("data");
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);

        // IV와 암호문 분리
        byte[] ivBytes = new byte[16];
        System.arraycopy(decodedBytes, 0, ivBytes, 0, 16);
        IvParameterSpec iv = new IvParameterSpec(ivBytes);

        byte[] cipherText = new byte[decodedBytes.length - 16];
        System.arraycopy(decodedBytes, 16, cipherText, 0, cipherText.length);

        // 복호화 수행
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
        String decryptedText = new String(cipher.doFinal(cipherText));
        return Map.of("decryptedData", decryptedText);
    }
}
 

클라이언트 측 HTML/JavaScript 예제 (세션 키 및 데이터 암호화)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Session Key Encryption Example</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        input, button { margin: 10px 0; padding: 8px; }
    </style>
</head>
<body>
<h1>Session Key 암호화 예제</h1>
<p>세션 키를 가져와 데이터를 암호화/복호화합니다.</p>

<label for="sessionId">Session ID:</label>
<input type="text" id="sessionId" placeholder="Enter session ID" required>

<button onclick="getSessionKey()">세션 키 가져오기</button>
<p id="keyOutput"></p>

<label for="dataToEncrypt">데이터 입력:</label>
<input type="text" id="dataToEncrypt" placeholder="Enter data to encrypt">

<button onclick="sendEncryptedData()">데이터 전송</button>
<p id="serverResponse"></p>

<script>
    let sessionKey;

    async function getSessionKey() {
        const sessionId = document.getElementById("sessionId").value;
        const response = await fetch(`/api/session/key?sessionId=${sessionId}`);
        const data = await response.json();
        sessionKey = data.sessionKey;
        document.getElementById("keyOutput").innerText = "Session Key: " + sessionKey;
    }

    async function sendEncryptedData() {
        if (!sessionKey) {
            alert("세션 키를 먼저 가져오세요!");
            return;
        }

        const data = document.getElementById("dataToEncrypt").value;
        const encryptedData = await encryptData(data, sessionKey);
        const sessionId = document.getElementById("sessionId").value;
        const response = await fetch(`/api/session/decrypt?sessionId=${sessionId}`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ data: encryptedData })
        });

        const result = await response.json();
        document.getElementById("serverResponse").innerText = "서버 응답: " + JSON.stringify(result);
    }

    async function encryptData(data, key) {
        const keyBytes = base64ToUint8Array(key);
        const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, { name: "AES-CBC" }, false, ["encrypt"]);
        const iv = crypto.getRandomValues(new Uint8Array(16));
        const encoder = new TextEncoder();
        const encrypted = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, cryptoKey, encoder.encode(data));

        const combined = new Uint8Array(iv.length + encrypted.byteLength);
        combined.set(iv, 0);
        combined.set(new Uint8Array(encrypted), iv.length);

        return btoa(String.fromCharCode(...combined));
    }

    function base64ToUint8Array(base64) {
        const binaryString = atob(base64);
        const len = binaryString.length;
        const bytes = new Uint8Array(len);
        for (let i = 0; i < len; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        return bytes;
    }
</script>
</body>
</html>

 

728x90