고양이 사진에 비밀 메시지를 숨겼더니, 6개월 동안 아무도 눈치채지 못했습니다
2026. 4. 30.
고양이 사진에 비밀 메시지를 숨겼더니, 6개월 동안 아무도 눈치채지 못했습니다
(원문: dev.to는 이미지를 수정합니다. 비밀이 숨겨진 원본 이미지는 원본 포스트에 있습니다 🤘)
작년 CTF(Capture The Flag) 대회에서 스테가노그래피 챌린지를 만났을 때였습니다. 800x600 픽셀의 주황색 태비 고양이가 키보드 위에 앉아 있는 사진을 두 시간 내내 쳐다보고 있었죠. 겉보기엔 완전히 평범한 이미지였습니다. 메타데이터도 이상 없고, ZIP 파일이 추가된 흔적도 없었으며, 눈에 띄는 아티팩트도 없었습니다. 플래그는 파란색 채널의 최하위 비트(LSB)에 숨겨져 있었고, 이걸 추출해내자 43자 길이의 메시지가 나타났습니다. 그 과정에서 고양이 사진은 전혀 변하지 않았습니다.
그 경험은 저를 스테가노그래피의 깊은 세계로 이끌었습니다. 이 분야는 마치 영화 속 해커들이나 쓸 법한 이야기처럼 들리지만, 막상 직접 시도해보면 그저 수학에 불과하다는 걸 알게 됩니다. 그것도 놀랍도록 간단한 수학 말이죠. 제가 실제로 참여했던 CTF 대회에서 이 기법을 마주했을 때, 처음엔 정말 영화에서나 보던 해킹 기술인 줄 알았죠.
스테가노그래피, 대체 뭘까요?
스테가노그래피(Steganography)는 숨겨진 데이터가 존재하는지조차 아무도 모르게 다른 데이터 안에 정보를 감추는 기술입니다. 이는 암호화(Cryptography)와 근본적으로 다릅니다. 암호화는 메시지를 읽을 수 없도록 뒤섞는 것이고, 스테가노그래피는 메시지를 숨겨서 아무도 찾으려 하지 않게 만드는 것이죠.
이 둘의 차이는 중요합니다. 암호화된 파일은 "나 비밀이 있어요!"라고 소리치는 것과 같습니다. 반면 스테가노그래피가 적용된 이미지는 아무 말도 하지 않습니다. 그저 고양이 사진이거나, 일몰 풍경이거나, 회사 로고일 뿐이죠. 가장 훌륭한 스테가노그래피는 원본과 통계적으로 구별할 수 없는 캐리어 파일을 만들어냅니다.
이 단어 자체는 그리스어에서 유래했습니다. '스테가노스(steganos, 덮인)'와 '그라페인(graphein, 쓰기)'이 결합된 말이죠. 고대 그리스 시대부터 존재했는데, 헤로도투스는 한 남자가 노예의 머리를 삭발하고 두피에 메시지를 문신한 뒤 머리카락이 자라기를 기다렸다가 그 노예를 보내 메시지를 전달하게 했다는 이야기를 기록했습니다. 우리는 그 이후로 픽셀 단위로 업그레이드된 셈이죠. 실무에서 보안 감사를 진행할 때 이런 은닉 기법을 놓치면 큰 취약점이 될 수 있기에 항상 주의 깊게 보곤 합니다.
LSB 인코딩, 어떻게 작동할까?
가장 흔한 이미지 스테가노그래피 기법은 최하위 비트(LSB) 인코딩입니다. 이 방법이 효과적인 이유를 설명해 드릴게요.
24비트 RGB 이미지의 픽셀은 세 가지 색상 채널을 가지며, 각 채널은 8비트 값(0-255)으로 저장됩니다. 각 바이트의 마지막 비트, 즉 최하위 비트(LSB)는 색상 값에 가장 작은 변화만을 기여합니다. 이 비트를 뒤집어도 채널 값은 정확히 1만 변하죠. 예를 들어, RGB(142, 87, 203)와 RGB(143, 87, 202)의 차이는 사람의 눈으로는 전혀 구별할 수 없습니다.
따라서 비밀 메시지를 이진수로 변환한 다음, 이미지 픽셀의 LSB를 메시지 비트로 대체하는 방식입니다. 각 픽셀은 3비트의 저장 공간(채널당 1비트)을 제공합니다. 1920x1080 해상도의 이미지는 2,073,600개의 픽셀을 가지고 있으니, 이론적으로는 6,220,800비트의 저장 공간, 즉 약 760KB의 데이터를 숨길 수 있습니다. 실제로는 탐지를 피하기 위해 훨씬 적은 용량을 사용하지만, 이론적인 용량은 엄청나죠. 제가 직접 LSB 인코딩된 이미지를 만들고 원본과 비교해봤을 때, 픽셀 단위로 값을 확인하지 않으면 정말 미묘한 차이조차 알아차리기 힘들더군요.
이를 구체화하기 위한 최소한의 파이썬 구현 코드는 다음과 같습니다.
from PIL import Image
import numpy as np
def text_to_bits(text):
"""문자열을 널 종료 문자가 포함된 이진 문자열로 변환합니다."""
bits = ''.join(format(ord(c), '08b') for c in text)
bits += '00000000' # 메시지 끝을 표시하는 널 종료 문자
return bits
def hide_message(image_path, message, output_path):
"""이미지의 LSB에 메시지를 숨깁니다."""
img = Image.open(image_path).convert('RGB')
pixels = np.array(img)
flat = pixels.flatten()
bits = text_to_bits(message)
if len(bits) > len(flat):
raise ValueError(f"메시지가 너무 깁니다: {len(bits)} 비트가 필요하지만, {len(flat)} 비트만 있습니다.")
for i, bit in enumerate(bits):
# LSB를 지운 다음, 메시지 비트로 설정합니다.
flat[i] = (flat[i] & 0xFE) | int(bit)
stego = flat.reshape(pixels.shape)
Image.fromarray(stego.astype('uint8')).save(output_path, 'PNG')
print(f"이미지 용량의 {len(bits)/len(flat)*100:.4f}%를 사용하여 {len(message)}개의 문자를 {len(bits)} 비트로 숨겼습니다.")
def extract_message(image_path):
"""이미지의 LSB에서 숨겨진 메시지를 추출합니다."""
img = Image.open(image_path).convert('RGB')
flat = np.array(img).flatten()
bits = ''.join(str(b & 1) for b in flat)
chars = []
for i in range(0, len(bits), 8):
byte = bits[i:i+8]
if byte == '00000000':
break
chars.append(chr(int(byte, 2)))
return ''.join(chars)
# 사용 예시
hide_message('cat.png', 'The flag is CTF{hidden_in_plain_sight}', 'stego_cat.png')
print(extract_message('stego_cat.png'))
핵심적인 연산은 18번째 줄의 (flat[i] & 0xFE) | int(bit) 입니다. 0xFE (이진수 11111110)와의 비트 AND 연산은 LSB를 0으로 만들고, OR 연산은 우리의 메시지 비트 값으로 LSB를 설정합니다. 이것이 전체 트릭의 전부입니다. 나머지는 그저 텍스트를 비트로 변환하고 픽셀을 반복 처리하는 과정일 뿐이죠.
단계별 메시지 숨기기
만약 파이썬 코드를 직접 작성하고 싶지 않거나, PIL(Pillow) 라이브러리를 설치할 수 없는 환경이라면, Kitmul의 스테가노그래피 툴을 사용해 보세요. 브라우저에서 동일한 작업을 수행할 수 있습니다. 파일 업로드도, 서버 처리도 필요 없죠. 이미지는 결코 당신의 기기를 벗어나지 않습니다. 이런 웹 기반 툴을 처음 접했을 때, 브라우저에서 이렇게 강력한 작업을 로컬로 처리할 수 있다는 사실에 살짝 놀랐던 기억이 있습니다.
작업 흐름은 다음과 같습니다.
- 캐리어 이미지를 업로드하세요. PNG 형식이 가장 좋습니다. 비손실 압축이기 때문이죠. JPEG 압축은 숨겨진 비트들을 파괴할 수 있습니다. 이 부분은 나중에 더 자세히 다루겠습니다.
- 비밀 메시지를 입력하세요. 이 도구는 이미지 크기에 따라 사용 가능한 용량을 보여줍니다.
- 스테고 이미지를 다운로드하세요. 원본과 완벽하게 동일해 보입니다. 픽셀 단위로 비교해도 그 차이는 인지할 수 없습니다.
메시지를 추출하려면 'Reveal(보기)' 모드로 전환한 다음, 스테고 이미지를 업로드하면 숨겨진 텍스트가 나타납니다.

이 모든 작업은 Canvas API와 Typed Array를 사용하여 클라이언트 측에서 실행됩니다. 당신의 비밀 메시지는 네트워크 연결을 전혀 거치지 않습니다. 이는 중요한 부분이죠. 만약 민감한 정보를 숨기는데, 이를 제3자 서버로 보낸다면 애초의 목적을 잃어버리는 셈이니까요.
탐지와 스테그분석: 어떻게 들키지 않을까?
스테그분석(Steganalysis)은 스테가노그래피를 탐지하는 기술이며, 생각보다 훨씬 정교합니다.
육안 검사로는 LSB 인코딩을 잡아낼 수 없습니다. 하지만 통계 분석으로는 가능하죠. 가장 간단한 테스트는 픽셀 값 분포에 대한 카이제곱 분석입니다. 일반적인 이미지에서 픽셀 값은 특정한 분포를 가집니다. LSB 삽입은 값의 쌍(예: 142와 143)을 평탄화하여 거의 동일한 확률을 갖게 만드는데, 이는 히스토그램에서 이상 현상으로 나타납니다.
StegExpose와 OpenStego 같은 도구에는 탐지 모듈이 포함되어 있습니다. 경쟁적인 CTF 환경에서는 zsteg와 steghide가 주로 사용되는 추출 도구입니다. 제가 침투 테스트를 하면서 스테가노그래피를 탐지하는 시나리오를 만들 때, 이런 '미묘한 통계적 변화'를 잡아내는 것이 핵심이었습니다.
만약 스테고 이미지가 면밀한 조사를 견뎌내기를 바란다면 다음의 실용적인 팁을 참고하세요.
- 적은 용량을 사용하세요. 사용 가능한 픽셀의 10-20%에만 데이터를 삽입하면 통계적 탐지가 훨씬 어려워집니다. 100% 용량을 사용하는 것은 법의학적으로 명백한 위험 신호입니다.
- 픽셀 선택을 무작위화하세요. 픽셀 0부터 순차적으로 데이터를 쓰는 대신, 암호로 시드된 의사 난수 시퀀스를 사용하여 어떤 픽셀이 데이터를 전달할지 선택하세요. 이는 수정 사항을 균일하게 분산시킵니다.
- 복잡한 이미지를 선택하세요. 질감, 노이즈, 색상 변화가 많은 사진이 부드러운 그라데이션이나 단색 블록보다 LSB 변화를 더 잘 숨깁니다. 숲 바닥 사진이 흰 벽 사진보다 훨씬 유리하죠.
- 캐리어로 JPEG를 절대 사용하지 마세요. JPEG의 손실 압축은 인코딩 중에 픽셀 값을 수정합니다. 이미지가 저장될 때 숨겨진 비트가 파괴될 것입니다. LSB 스테가노그래피에는 항상 PNG, BMP 또는 TIFF를 사용하세요.
- 공유하기 전에 메타데이터를 제거하세요. 이미지가 스테가노그래피 도구로 처리되었다는 EXIF 데이터는 명백한 단서가 됩니다.
스테가노그래피와 암호화를 결합해야 할 때
스테가노그래피와 암호화는 서로 다른 문제를 해결하며, 가장 강력한 접근 방식은 이 둘을 모두 사용하는 것입니다. 왜 그런지 설명해 드릴게요.
공격자가 이미지에 숨겨진 데이터가 있다고 의심하고 LSB를 성공적으로 추출한다면, 그들은 평문 메시지를 보게 될 것입니다. 그럼 게임 끝이죠. 하지만 메시지를 먼저 암호화한다면, 예를 들어 AES-256을 사용한다면, 추출된 비트들은 그저 무작위 노이즈처럼 보일 것입니다. 공격자는 메시지를 찾은 것인지, 아니면 그저 일반적인 이미지 데이터인지를 구별할 수 없게 되죠.
실용적인 작업 흐름은 이렇습니다.
- 강력한 대칭 키 암호화 방식으로 메시지를 암호화합니다.
- LSB 인코딩을 사용하여 암호화된 메시지(ciphertext)를 이미지 안에 숨깁니다.
- 스테고 이미지를 공개적으로 공유합니다.
- 암호 해독 키는 별도의 채널을 통해 공유합니다.
이 방식은 두 가지 보호 계층을 제공합니다. 메시지는 숨겨져 있고(스테가노그래피), 읽을 수 없게(암호화) 되는 거죠. 공격자는 숨겨진 데이터를 탐지하고 암호화를 해독해야 합니다. 이는 단순히 한 가지 문제만 해결하는 것보다 훨씬 어려운 일입니다.
Kitmul의 보안 및 암호화 도구에는 AES 암호화, 해시 생성기 및 이 워크플로우를 보완하는 기타 유틸리티가 포함되어 있습니다.
실제 사용 사례
스테가노그래피는 CTF에서나 통하는 재주가 아닙니다. 합법적이고 중요한 응용 분야를 가지고 있습니다.
디지털 워터마킹. 출판사, 사진작가, 미디어 회사들은 무단 배포를 추적하기 위해 이미지에 보이지 않는 워터마크를 삽입합니다. 유출된 이미지가 발견되면, 내장된 워터마크가 어떤 수신자가 유출했는지 식별할 수 있게 하죠. 여러 주요 영화 스튜디오들이 스크리너 사본을 추적하는 방식이기도 합니다.
내부 고발 및 검열 저항. 인터넷 감시가 심한 국가에서는 스테가노그래피를 통해 활동가들이 소셜 미디어에 게시된 무해해 보이는 이미지를 통해 정보를 공유할 수 있습니다. 이미지는 자동 콘텐츠 필터의 검사를 통과하고, 숨겨진 메시지는 의도된 수신자에게 도달하는 것이죠.
비밀 통신. 정보 기관들은 적어도 2000년대 초반부터 스테가노그래피를 사용해왔습니다. 2010년 FBI가 러시아 스파이들을 체포한 사건("불법 체류자 프로그램")에서는 그들이 공개 웹사이트에 게시된 이미지에 암호화된 메시지를 숨겼다는 사실이 드러났습니다.
CTF 챌린지. CTF 대회는 스테가노그래피를 매우 좋아합니다. 일반적인 암호화 챌린지와는 다른 기술 세트를 테스트하기 때문이죠. 추출을 시작하기 전에 스테가노그래피가 사용되었다는 사실 자체를 식별해야 합니다. 일반적인 CTF 스테고 기술에는 LSB 인코딩, 추가된 데이터, GIF 파일의 팔레트 조작, 오디오 스펙트럼 숨기기 등이 있습니다.
소유권 증명. 예술가나 콘텐츠 제작자는 저작권 정보나 소유권 증명을 자신의 작품에 직접 삽입할 수 있습니다. 보이는 워터마크와 달리, 이러한 방식은 작품의 시각적 품질을 저하시키지 않습니다.

픽셀 인코딩의 한계
LSB 스테가노그래피는 몇 가지 분명한 제약 사항을 가지고 있으며, 이를 이해하는 것이 중요합니다. 캐리어 이미지는 반드시 비손실이어야 합니다. JPEG, WebP (손실) 등 어떤 종류의 손실 압축 단계라도 삽입된 데이터를 손상시킬 것입니다. 메시지 용량은 이미지 크기에 비례하지만, 전체 용량의 15-20% 이상을 사용하면 이미지가 통계적 탐지에 취약해집니다. 그리고 이 기술은 데이터를 숨기기만 할 뿐, 무엇을 찾아야 할지 아는 사람의 추출로부터 데이터를 보호하지는 않습니다.
대부분의 실용적인 목적에서 이미지 스테가노그래피는 다층 방어(defense-in-depth) 전략의 한 계층으로 가장 잘 작동합니다. 메시지를 숨기고, 콘텐츠를 암호화한 다음, 보안 채널을 통해 키를 공유하는 것이죠. 단일 기술로는 완벽할 수 없지만, 이들의 조합은 보안 장벽을 상당히 높여줍니다.
직접 시도해보기
Kitmul의 스테가노그래피 도구를 사용하면 브라우저에서 PNG 이미지에 메시지를 직접 숨기고 추출할 수 있습니다. 모든 처리는 클라이언트 측에서 실행되며, 데이터가 업로드되지 않고, 계정이 필요 없으며, 제한도 없습니다. 이미지를 업로드하고, 메시지를 입력하고, 결과를 다운로드하세요. 그런 다음 추출을 시도하여 왕복 과정을 직접 확인해 볼 수 있습니다.
보안 및 개인 정보 보호 도구에 관심이 있다면, 보안 및 암호화 컬렉션에는 해시 생성기, 암호화 도구, 암호 생성기 등이 포함되어 있으며, 이 모든 것이 브라우저에서 로컬로 실행됩니다.
모든 처리는 브라우저에서 로컬로 실행됩니다. 어떠한 이미지나 메시지도 서버로 전송되지 않습니다. 이 도구는 무료이며, 개방적이며, 계정이나 제한이 없습니다.
원문: https://dev.to/aralroca/i-hid-a-secret-message-in-a-cat-photo-and-nobody-noticed-for-six-months-4a47 수집일: 2026-04-30 01:45:49