Overview
-
내부 테스트용 API에서 민감한 데이터를 숨기기 위해 HTTP 요청과 응답 본문을 AES로 암호화하는 방식을 검토했다.
-
사내 개발 서버에서는 HTTPS, HTTP, localhost 환경 모두 기본 통신 테스트가 끝난 상태였고, 이미 존재하던 AES 암호문 요청/응답 API를 웹 클라이언트에 붙이는 작업을 진행했다.
-
HTTPS에서는 문제없이 동작했지만, HTTP에서 AES 암호문 요청과 응답을 사용할 때 에러가 발생했다. localhost 역시 HTTP 기반으로 테스트하고 있었기 때문에 같은 현상이 재현됐다.
-
이번 정리는 그 과정에서 "HTTP 위에 AES를 얹는 것"과 "HTTPS로 전송 구간을 보호하는 것"이 전혀 다른 문제라는 점을 다시 이해한 기록이다.
문제 상황
사내 개발 서버에는 이미 HTTPS, HTTP, localhost 환경이 모두 구성되어 있었고, 일반적인 JSON API 요청/응답은 정상 동작하고 있었다. 이번 작업은 새 암호화 구조를 설계하는 단계가 아니라, 서버에 이미 존재하던 AES 암호문 기반 API 요청/응답 방식을 웹 클라이언트에 연결하는 작업이었다.
테스트를 진행해보니 결과는 명확했다.
-
클라이언트에서 요청 본문을 JSON 문자열로 만든다.
-
AES로 암호화한다.
-
Base64 문자열로 변환해서 HTTP body에 넣는다.
-
서버에서 같은 키로 복호화한다.
-
HTTPS에서는 정상 동작했다.
-
HTTP에서는 AES 암호문이 포함된 요청/응답에서 에러가 발생했다.
-
localhost도 HTTP 기반으로 동작하고 있었기 때문에 같은 에러가 발생했다.
초반에는 서버의 프록시 작업 쪽을 먼저 의심했다. 이미 존재하던 서버 AES API를 그대로 붙이는 상황이었고, HTTPS에서는 같은 요청이 정상 동작하고 있었기 때문이다.
문제는 프록시 동작이 아니고, "어떤 계층에서 무엇을 보호하고 있는가"에 대한 이해부족이었다.
HTTP 위에 AES만 올렸다고 안전해지지 않는 이유
가장 먼저 이해해야 할 점은 HTTP는 기본적으로 평문 프로토콜이라는 사실이다. 요청 body를 AES로 암호화하더라도 HTTP 연결 자체가 보호되는 것은 아니다.
예를 들어 요청 body가 아래처럼 암호문으로 바뀌었다고 가정해도,
{
"ciphertext": "U2FsdGVkX1..."
}중간에서 네트워크를 볼 수 있는 사람은 여전히 다음 정보를 확인할 수 있다.
-
어떤 도메인과 경로로 요청했는지
-
POST /api/login, GET /api/license/list 같은 API 목적이 무엇인지
-
어떤 헤더를 보냈는지
-
요청을 언제 몇 번 보냈는지
-
응답 크기가 얼마나 되는지
-
특정 시점에 어떤 기능이 호출됐는지
즉, 본문이 가려졌다고 해서 통신 전체가 안전해지는 것은 아니다.
이번 테스트에서 중요했던 점도 여기에 있었다.
-
HTTPS에서는 같은 AES 암호문 요청/응답이 안정적으로 동작했다.
-
반면 HTTP에서는 암호화된 body를 얹은 순간 에러가 발생했다.
즉, 이 현상은 "AES를 쓰면 언제나 문제다"가 아니라 "보호되지 않은 전송 계층 위에서 암호문 payload를 얹었을 때 예상하지 못한 문제가 생긴다"는 문제이다.
또한 HTTP 환경에서는 서버 인증 자체가 되지 않는다. HTTPS는 TLS 인증서를 통해 "내가 지금 접속한 서버가 누구인지"를 검증하지만, HTTP는 이런 검증 과정이 없다.
이 말은 곧 다음과 같은 위험으로 이어진다.
-
공격자가 중간에서 가짜 서버처럼 응답할 수 있다.
-
클라이언트는 그 응답이 위조된 것인지 알기 어렵다.
-
AES로 body를 감쌌더라도 키를 탈취하거나 클라이언트에 동일한 로직을 흉내 내면 통신을 계속 속일 수 있다.
브라우저 클라이언트에서는 키 관리가 더 큰 문제
이 구조를 검토하면서 가장 현실적인 문제는 키를 어디에 둘 것인가였다. 브라우저에서 AES 암호화를 하려면 결국 자바스크립트 코드가 키를 알아야 한다. 하지만 브라우저로 내려가는 코드는 사용자에게 전달되는 코드이므로, 충분한 시간과 도구가 있으면 키나 암호화 로직을 역으로 확인할 수 있다.
처음에는 "코드를 난독화하면 괜찮지 않을까"라고도 생각했지만, 그건 근본 해결이 아니다. 클라이언트가 암호화를 수행할 수 있다는 말은 결국 복호화에 필요한 단서도 클라이언트 쪽에 존재한다는 뜻이기 때문이다.
결국 이 방식은 아래와 같은 한계를 가진다.
-
키를 안전하게 배포하기 어렵다.
-
키를 교체하는 절차도 복잡하다.
-
키가 노출되면 모든 요청 본문 보호가 무력화된다.
-
사용자가 직접 실행하는 클라이언트에서는 "비밀 키를 안전하게 숨긴다"는 전제가 약하다.
즉, HTTP 위에 AES를 추가하는 설계는 "통신 자체를 안전하게 만든다"기보다 "본문 일부를 읽기 어렵게 만든다"의 의미였다..
그래서 정리한 결론
이번에 가장 크게 정리한 점은 "본문 암호화"와 "전송 구간 보안"을 같은 문제로 보면 안 된다는 것이다.
-
HTTPS는 전송 구간 전체를 보호한다.
-
HTTP + AES는 일부 body를 추가로 감추는 수준에 가깝다.
-
HTTP에서는 요청 경로, 헤더, 타이밍, 서버 인증 문제 등이 여전히 남는다.
-
브라우저에서는 키 관리가 어려워서 구조 자체가 쉽게 흔들린다.
-
보안 요구사항이 있다면 우선 HTTPS를 적용하는 것이 먼저다.
이미 AES가 있으니 HTTP에서도 충분히 안전하고 잘 동작할 것"이라고 보면 안 된다.
기본은 HTTPS이고, 그 위에서 필요한 경우에만 애플리케이션 레벨 암호화를 추가하여 보안을 강화하는 목적으로 사용해야 한다.
느낀 점
-
같은 AES 로직이 HTTPS에서는 동작하고 HTTP에서는 문제를 일으킨다는 사실이, 전송 계층의 차이를 더 분명하게 이해하게 만들었다.
-
"암호화가 들어갔는가"보다 "어떤 계층에서 무엇을 보호하고 있는가"를 먼저 봐야 문제를 정확히 해석할 수 있다.
-
앞으로 비슷한 상황을 만나면 AES 구현부터 의심하기보다, 현재 통신이 HTTP인지 HTTPS인지와 그 차이가 어떤 영향을 주는지부터 확인해야 한다.