"Base64 로 인코딩하면 아무도 못 읽어" —— 이 문장은 코드 리뷰, Slack, Stack Overflow 답변에 필요 이상으로 자주 등장합니다. Base64 는 암호화가 아닙니다. 난독화도 아닙니다. 어떤 종류의 보안도 아닙니다. 디코딩은 한 번의 함수 호출이면 되고 키나 비밀 정보가 전혀 필요 없습니다. 본 글은 Base64 가 실제 무엇인지, 왜 오해가 지속되는지, 진짜로 데이터를 보호해야 할 때 무엇을 써야 하는지 설명합니다.
Base64 가 실제로 무엇인가
Base64 는 인코딩 방식 입니다 —— 이진 데이터와 64 개의 인쇄 가능 ASCII 문자(A–Z, a–z, 0–9, +, /, 패딩에 =) 사이의 가역 매핑입니다.
입력 3 바이트마다 Base64 문자 4 개가 됩니다. 알고리즘은 완전히 결정적이며 키가 필요 없습니다:
// 인코딩
btoa("hello") // → "aGVsbG8="
btoa("secret password") // → "c2VjcmV0IHBhc3N3b3Jk"
// 디코딩 —— 한 번의 호출, 키 불필요
atob("c2VjcmV0IHBhc3N3b3Jk") // → "secret password"그것이 전부입니다. 인코딩된 문자열에 접근 가능한 사람은 누구나 어떤 언어든 어떤 환경에서든 추가 정보 없이 즉시 디코딩할 수 있습니다.
왜 오해가 생기는가
Base64 인코딩 문자열은 암호화된 데이터처럼 보입니다. 대소문자, 숫자, 특수 문자가 섞여 있고 원본 입력과 시각적 유사성이 없습니다. 이 시각적 노이즈는 비기술 관찰자를 속이기에 충분하고 —— 놀랍게도 자주 기술 인력도 속입니다.
이 패턴은 근접성에 의해서도 강화됩니다. Base64 는 보안 관련 문맥에 지속적으로 등장합니다:
- HTTPS 인증서는 Base64 인코딩(PEM 형식)
- JWT 토큰의 header 와 payload 는 Base64url 사용
- HTTP Basic 인증은 자격 증명을 Base64 로 전송
- SSH 키는 Base64 로 저장
- 암호화된 암호문도 종종 Base64 로 전송
이런 보안 인접 장소에서 Base64 를 반복적으로 보면 개발자는 인코딩과 보안을 혼동합니다. 인코딩은 단지 포장이고 보안은 그 아래의 암호 연산에서 옵니다.
인코딩 vs 암호화: 핵심 차이
차이는 단순하고 절대적입니다:
| 인코딩(Base64) | 암호화(AES, RSA…) | |
|---|---|---|
| 목적 | 이진 데이터를 텍스트로 표현 | 권한 없는 자에게서 데이터 숨김 |
| 키 필요? | 아니오 | 예 |
| 누구나 역으로? | 예 —— 한 번의 함수 호출 | 올바른 키가 있을 때만 |
| 기밀성 제공? | 아니오 | 예(올바르게 사용 시) |
| 출력 크기 | 입력보다 약 33% 더 큼 | 알고리즘에 따라 다름 |
암호화는 키로 데이터를 변환해 키 없이 원본 복구가 계산적으로 실행 불가능하게 만듭니다. Base64 인코딩에는 그런 성질이 없습니다.
현실 세계의 보안 실수
소스 코드에 "인코딩된" 비밀 저장
// ❌ 이것은 비밀이 아님 —— 누구나 디코드 가능
const API_KEY = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");
// 이 코드를 읽은 사람은 실행:
atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=") // → "sk-live-abc123456789"자동 시크릿 스캐닝 도구(GitHub 시크릿 스캐닝, GitGuardian, truffleHog)는 탐지의 일부로 Base64 문자열을 디코드합니다. Base64 가 그것들을 막을 거라 기대하면 못 막습니다.
HTTP Basic 인증
// Authorization 헤더는 이렇게 생김:
Authorization: Basic dXNlcjpwYXNzd29yZA==
// 그 Base64 는 자명하게 디코드:
atob("dXNlcjpwYXNzd29yZA==") // → "user:password"HTTP Basic Auth 는 HTTPS 위에서만 안전합니다 —— 실제 암호화는 TLS 계층이 제공합니다. 헤더의 Base64 는 순전히 username:password 쌍을 단일 텍스트 값으로 인코딩하기 위한 것이지 숨기기 위한 것이 아닙니다. 평문 HTTP 에서는 트래픽을 가로채는 누구든 즉시 자격을 읽을 수 있습니다.
JWT "토큰" 을 비밀로 취급
// JWT 는 점으로 연결된 세 Base64url 섹션처럼 보임:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.abc123
// 중간(payload)은 자명하게 디코드:
atob("eyJ1c2VySWQiOjQyfQ") // → '{"userId":42}'JWT 페이로드는 암호화되지 않습니다 —— Base64url 인코딩되고 서명 됩니다. 토큰을 가진 누구나 페이로드를 읽을 수 있습니다. 서명은 변조를 막지만 기밀성을 제공하지 않습니다. JWE(JSON Web Encryption)를 쓰지 않는 한 민감 데이터(비밀번호, 신용 카드 번호, 개인정보)를 JWT 페이로드에 넣지 마세요 —— JWE 는 별개의 훨씬 덜 흔한 표준입니다.
URL 에서 데이터 난독화
// 레거시 앱의 일반 패턴
/profile?data=eyJ1c2VySWQiOjQyfQ==
// 어떤 디코더에든 붙여 넣기:
atob("eyJ1c2VySWQiOjQyfQ==") // → '{"userId":42}' URL 의 Base64 는 보안 이득이 없습니다. 또한 쉽게 깨집니다 —— 표준 Base64 의 + 와 / 문자는 URL 안전하지 않아 백분율 인코딩이 필요합니다. 그래서 Base64url 변종(+ → -, / → _, 패딩 없음)이 존재합니다.
Base64 가 실제로 무엇을 위한 것인가
Base64 는 정당하고 중요한 용도가 있습니다 —— 보안용은 아닙니다:
- 텍스트 형식에 이진 데이터 임베드 —— 이미지, 폰트, 파일을
data:URI 로 HTML/CSS 에 임베드, 또는 JSON API 응답에 첨부 - 이메일 첨부 —— MIME 인코딩은 7 비트 ASCII 만 다루는 프로토콜로 이진 파일을 Base64 로 전송
- 전송 안전성 —— 일부 채널은 이진(널 바이트, 제어 문자)을 망가뜨립니다; Base64 는 내용이 무결히 통과함을 보장
- 불투명 식별자 —— ID 나 커서를 Base64 로 인코딩하면 API 소비자에게 "파싱하지 마라" 를 알리지만 보안은 제공하지 않음
정말 보안이 필요할 때 무엇을 쓸까
목표가 권한 없는 데이터 접근 차단이라면 진짜 암호 도구를 쓰세요:
| 목표 | 사용 |
|---|---|
| 저장 데이터 암호화 | AES-256-GCM(대칭 암호화) |
| 전송 데이터 암호화 | TLS 1.3(HTTPS) |
| 비밀번호 해싱 | bcrypt, scrypt, Argon2 —— SHA-256 단독 금지 |
| 토큰 서명 | HMAC-SHA256(JWT HS256) 또는 RS256 |
| 비밀 저장 | 환경 변수, 시크릿 매니저(Vault, AWS Secrets Manager) |
| 엔드투엔드 토큰 암호화 | JWE(JSON Web Encryption) —— 일반 JWT 아님 |
정말로 암호화된 토큰이 필요할 때: JWE 와 JOSE
표준 JWT 는 서명되었지(JWS) 암호화되지 않았습니다 —— 페이로드는 읽힙니다. 클레임의 기밀성이 정말로 필요하면 JWE(JSON Web Encryption, RFC 7516)를 쓰세요. JWE 는 JOSE 표준군의 일부:
- JOSE 알고리즘 —— 콘텐츠 암호화(예:
A256GCM)와 키 관리(예:RSA-OAEP-256,ECDH-ES,A256KW). "alg" 는 콘텐츠 암호화 키가 어떻게 래핑되는지, "enc" 는 페이로드 자체가 어떻게 암호화되는지 선택. - 키 래핑 —— JWE 는 메시지마다 새 콘텐츠 암호화 키를 만들고 수신자의 키(비대칭 또는 대칭) 아래 래핑. 래핑된 키는 토큰 헤더에 실립니다. 같은 평문을 본문 재암호화 없이 다른 수신자에게 암호화할 수 있습니다.
- JWE 콤팩트 형식 —— 점으로 연결된 다섯 Base64url 섹션(header.key.iv.ciphertext.tag), 시각적으로 JWT 같지만 세 조각이 아니라 네 조각.
검증된 라이브러리(jose, node-jose, python-jose)를 고르고 JWE 를 직접 만들지 마세요 —— 알고리즘과 키 형식 조합은 미묘하며 약한 알고리즘으로의 조용한 폴백은 고전적인 JOSE 버그입니다.
자신의 코드에 대한 빠른 테스트
코드베이스에서 btoa(, atob(, Buffer.from(…, 'base64') 를 검색하 세요. 각 등장에 대해 물어보세요:
- 텍스트를 필드에 맞추거나 전송에 살아남게 하려고 하는가? → 괜찮음.
- 누군가에게서 데이터를 숨기려 하는가? → 진짜 암호화나 접근 제어로 교체.
자주 묻는 질문
Base64 는 암호화인가요?
아니요. Base64 는 키 없는 가역 인코딩 입니다 —— 누구나 한 번의 함수 호출(atob())로 디코드 가능. 암호화는 키가 필요하며 키 없이 원본 복구를 실행 불가능하게 만듭니다.
Base64 로 비밀번호나 API 키를 저장해도 안전한가요?
아니요. 디코딩은 자명하고 시크릿 스캐너는 Base64 를 자동 디코드합니다. 시크릿 매니저나 환경 변수를 쓰고 비밀번호는 bcrypt, scrypt, Argon2 로 해시하세요.
JWT 페이로드는 누구나 읽을 수 있나요?
예. JWT 페이로드는 Base64url 인코딩되고 서명 되지 암호화되지 않습니다 —— 토큰을 가진 누구나 모든 클레임을 읽습니다. JWE 를 쓰지 않는 한 비밀을 JWT 에 넣지 마세요. 토큰을 온라인 도구에 붙여 넣는 것도 위험; 왜 민감한 JSON 을 온라인에 붙여 넣지 말아야 하는지.
Base64 는 정말 무엇을 위한 것인가요?
이진 데이터를 텍스트로 안전하게 표현 —— data: URI, 이메일(MIME) 첨부, ASCII 만 다루는 채널에서의 전송. 포장이지 보안 수단이 아닙니다.
브라우저에서 인코딩 & 디코딩
지금 Base64 문자열을 검사해야 하나요? fixjson.org 의 Base64 인코드 & 디코드 는 임의 문자열을 즉시 인코드 또는 디코드합니다 —— 완전한 Unicode 와 UTF-8 포함 —— 설치 없이. JWT 페이로드 디버깅, API 응답 검사, data: URI 안 내용 확인에 유용합니다.
- Base64 인코드 & 디코드 —— 즉시, 브라우저 안, 데이터는 어떤 서버로도 보내지 않음
- RFC 4648: Base64 표준 —— Base64 와 Base64url 의 역사와 공식 정의
- JWT 를 디코드하는 방법 —— 토큰의 클레임을 읽기, 왜 디코딩이 검증이 아닌가
- RFC 7519: JSON Web Token(JWT) —— Base64url 이 JWT 표준에서 어떻게 쓰이는가, JWT 페이로드 보안 고려
- URL 디코드 —— URL 이나 쿼리 문자열을 백분율 디코드
- JSON Stringify —— JSON 값을 이스케이프된 문자열 리터럴로 인코드