4.8. 문자 인코딩

4.8.1. 문자 인코딩 소개

미국인들은 오랫동안 아스키 문자셋을 이용하여 텍스트를 서로 교환했다; 본래 미국의 모든 시스템들이 아스키를 지원하기 때문에 영어 텍스트를 손쉽게 교환할 수 있다. 불행히도 아스키는 거의 모든 다른 언어의 문자를 다루기에는 부적절하다. 오랫동안 여러 나라들은 다른 언어로 텍스트를 교환하는 여러가지 기법을 채택해왔으며 이는 더욱 더 상호 연결된 세상에서 데이타를 교환하는 것을 어렵게 만들고 있다.

더욱 최근에 ISO 는 ISO 10646, Universal Multiple-Octet Coded Character Set (UCS) 을 개발했는데 UCS 는 세상의 모든 문자 각각에 대해 유일한 31 비트 값을 정의하는 코드화된 문자셋이다. UCS 의 첫 65536 문자 (따라서 16 비트에 꼭맞다) 는 Basic Multilingual Plane (BMP) 이라고 칭하며 BMP 는 거의 오늘날 모든 구어를 다루려고 한다. Unicode 포럼은 유니코드 표준을 개발하고 있는데 이는 UCS 에 중점을 두어 상호 이용을 돕기 위해 약간의 추가적인 합의를 추가한다. 역사적으로 유니코드와 ISO 10646 은 경쟁 그룹이 개발했지만 고맙게도 두 그룹은 공동 작업의 필요성을 깨달았으며 현재 서로 협력하며 개발하고 있다.

국제화된 문자를 다루는 새로운 소프트웨어를 작성하고 있다면 국제적인 문자를 다루기 위한 기초로 ISO 10646/유니코드를 사용해야 한다. 그러나 다양한 옛날 (언어에 특정적인) 문자셋으로 쓰여진 옛날 문서를 처리할 필요가 있을 수 있는데 이 경우에 있어 신뢰되지 않은 사용자가 다른 문서의 문자셋 설정을 제어할 수 없도록 보증할 필요가 있다 (이는 문서의 해석에 상당히 영향을 미칠 수 있기 때문이다).

4.8.2. UTF-8 소개

대부분의 소프트웨어는 16 비트 또는 32 비트 문자를 다루도록 설계되지 않았는데 이제부터 8 비트 이상의 포괄적인 문자셋을 생성하는 것이 필요하게 되었다. 따라서 기존 프로그램 및 라이브러리가 더욱 쉽게 다룰 수 있는 포맷으로 이러한 잠재적으로 국제적인 문자를 인코드할 수 있도록 UTF-8 이라는 특정 포맷이 개발되었다. UTF-8 은 다른 곳중에서 IEFT-RFC 2279 에 정의되어 있으며 이 때문에 자유로이 읽혀지고 사용될 수 있는 잘 정의된 표준이다. UTF-8 은 가변 너비 (variable-width) 인코딩으로 0 에서 0x7f (127) 까지 번호가 매겨진 문자들 자신을 싱글 바이트로 인코드하며 반면 더욱 큰 값을 갖는 문자들은 그 값에 따라 정보가 2 에서 6 바이트로 인코드된다. 인코딩은 특히 다음의 정확한 특성을 갖도록 설계되어왔다 (이 정보의 출처는 RFC 및 리눅스 utf-8 맨페이지이다):

요약하면 UTF-8 변환 포맷은 다른 정확한 특성을 갖을 뿐 아니라 모든 세계의 언어를 지원할 수 있고 지금으로서는 미국 아스키 파일과 역행 호환성을 갖고 있기 때문에 국제화 텍스트 정보를 교환하는데 있어 가장 유력한 방법이 되고 있다. 많은 경우에 있어 저자는 이를 사용하길 추천하며 특히 텍스트 파일내에서 데이타를 정렬할 때는 이를 사용하길 바란다.

4.8.3. UTF-8 보안 쟁점

UTF-8 을 언급하는 이유는 어떤 바이트 시퀀스가 비합법적인 UTF-8 이며 이것이 악용될 수 있는 보안 구멍일 수도 있기 때문이다. UTF-8 인코더는 ``가능한 가장 짧은" 인코딩을 사용한다고 알려져 있지만 순진한 디코더가 필요이상으로 긴 인코딩을 받아들일 수도 있다. 정말로 더욱 초기의 표준은 디코더가 ``non-shortest form" 인코딩을 받아들이도록 허용했다. 여기서의 문제는 이것이 잠재적으로 위험한 입력이 다수의 방식으로 표현될 수 있으며 따라서 위험한 입력을 검사하는 보안 루틴을 무력화시킬 수도 있다는 것을 의미한다는 것이다. RFC 는 다음과 같이 문제점을 기술하고 있다:

UTF-8 을 구현한 사람은 비합법적인 UTF-8 시퀀스 처리 방법의 보안 측면을 고려할 필요가 있다. 어떤 경우에 있어 공격자가 UTF-8 파서에 UTF-8 구문에서 허용되지 않는 옥텟 (팔중수, octet) 시퀀스를 보냄으로써 조심성없는 파서를 악용할 수 있을 것이라고 상상할 수 있다.

특히 미묘한 형태의 이 공격이 UTF-8 로 인코드된 입력 폼에 대해 보안에 중대한 유효성 검사를 수행하지만 어떤 비합법적인 옥텟 시퀀스를 문자로 해석하는 파서에 대해 수행될 수 있다. 예를 들어 싱글 옥텟 시퀀스 00 으로 인코드될 때는 NUL 문자를 금지하지만 C0 80 (필요이상으로 길기때문에 비합법적이다) 의 비합법적인 두 옥텟 시퀀스는 허용해서 이를 NUL 문자 (00) 으로 해석할 수도 있다. 다른 예는 옥텟 시퀀스 2F 2E 2E 2F ("/../") 를 금지하지만 아직까지는 비합법적인 옥텟 시퀀스 2F C0 AE 2E 2F 를 허용하는 파서일 것이다.

이에 대한 더욱 자세한 논의는 http://www.cl.cam.ac.uk/~mgk25/unicode.html 에서 Marjus Kuhn 의 UTF-8 and Unicode FAQ for Unis/Linux 로부터 얻을 수 있다.

4.8.4. UTF-8 합법적인 값

따라서 UTF-8 입력을 받아들일 때 유효한 UTF-8 인지를 검사할 필요가 있다. 표 4-1 은 모든 합법적인 UTF-8 시퀀스 목록이다; 이 표와 일치하지 않는 모든 문자 시퀀스는 합법적인 UTF-8 시퀀스가 아니다. 표에서 첫번째 열은 UTF-8 로 인코드되는 다양한 문자값들이다. 두번째 열은 이 문자들이 어떻게 바이너리 값으로 인코드되는 지를 보여준다; ``x" 는 데이타 (0 또는 1) 가 놓이는 곳을 나타낸다. 그러나 몇몇 값들은 가능한 가장 짧은 인코딩이 아니기 때문에 허용되지 않아야 한다. 마지막 열은 각 바이트가 가질 수 있는 (16진법) 유효한 값을 나타낸다. 따라서 프로그램은 모든 문자가 세번째 열의 패턴 중의 하나와 일치하는지 검사해야 한다. ``-" 는 합법적인 값 (포괄적인 ) 의 범위를 나타낸다. 물론 시퀀스가 합법적인 UTF-8 시퀀스라고 해서 이를 수용해야 한다는 것을 의미하지는 않으며 (스스로 다른 검사를 할 필요가 있다) 일반적으로 다른 검사를 하기 전에 UTF-8 합법성에 대해 모든 UTF-8 데이타를 검사해야 한다.

표 4-1. 합법적인 UTF-8 시퀀스

UCS Code (Hex)Binary UTF-8 FormatLegal UTF-8 Values (Hex)
00-7F0xxxxxxx00-7F
80-7FF110xxxxx 10xxxxxxC2-DF 80-BF
800-FFF1110xxxx 10xxxxxx 10xxxxxxE0 A0*-BF 80-BF
1000-FFFF1110xxxx 10xxxxxx 10xxxxxxE1-EF 80-BF 80-BF
10000-3FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxxF0 90*-BF 80-BF 80-BF
40000-FFFFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxxF1-F3 80-BF 80-BF 80-BF
40000-FFFFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxxF1-F3 80-BF 80-BF 80-BF
100000-10FFFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxxF4 80-8F* 80-BF 80-BF
200000-3FFFFFF111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxxtoo large; see below
04000000-7FFFFFFF1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxxtoo large; see below

앞에서 언급했듯이 문자셋에 대한 표준에는 ISO 10646 과 Unicode 두개가 있으며 이들은 문자 지정을 동기화하는데 동의하였다. ISO/IEC 10646 과 IETF RFC 내의 UTF-8 의 정의는 Uniforum 의 Unicode 가 지원하는 범위밖의 문자들을 인코드하기 위해 현재 5 와 6 바이트 시퀀스를 지원한다. 그러나 이 값들이 Unicode 문자를 지원하는데 사용할 수 없으며 ISO 10646 의 향후 버전에서도 동일한 한계를 갖을 것으로 생각된다. 따라서 대부분의 목적에 대해 5 와 6 바이트 UTF-8 인코딩은 합법적이지 않으며 보통 이를 버려야 한다 (특별한 목적이 아닌 경우라면).

이러한 일련의 유효한 값들을 결정하는 것은 어려우며 사실 이 문서의 초기 버전에는 다소의 틀린 엔트리를 갖고 있었다 (어떤 경우에 있어 너무 긴 문자를 허용했다). 언어 개발자들은 라이브러리에 유효한 UTF-8 값을 검사하는 함수를 포함해야 하는데 이를 올바르게 검사하는 것은 매우 어렵기 때문이다.

어떤 경우에 있어서 16진법 시퀀스 C0 80 을 부주의하게 자르고 (내부적으로 사용하고) 싶을 수도 있다고 저자는 언급한다. 이는 허용되는 경우 아스키 NUL (NIL) 을 나타낼 수 있는 너무 긴 시퀀스이다. C 와 C++ 에서 보통 문자열에 NIL 문자를 포함하는 것은 문제가 생기기 때문에 NIL 을 데이타 스트림의 일부분으로 나타내길 원할 때 이 시퀀스를 사용해왔다; 자바조차도 이러한 예를 사용하고 있다. 데이타를 처리할 때 C0 80 을 내부적으로는 자유롭게 사용해라 그러나 기술적으로 이를 데이타에 저장하기 전에 다시 00 으로 실제적으로 전환해야 한다. 필요에 따라 UTF-8 데이타 스트림내의 입력으로 C0 80 을 부주의하게 받아들일 수도 있다. 이 시퀀스는 상호운용성에 도움을 주기 때문에 보안에 해를 끼치지 않는다면 이 시퀀스를 수용하는 것이 아마도 좋은 습관이다.

이를 다루는 것은 어려울 수 있다. 변환을 다루기 위해 ftp://ftp.unicode.org/Public/PROGRAMS/CVTUTF/ConvertUTF.c 에서 얻을 수 있는 Unicode 에서 개발한 C 루틴을 살펴볼 수도 있다. 저자에게 이 루틴이 오픈 소스 소프트웨어인지 명확하지는 않다 (라이센스에 수정가능 여부가 명확히 언급되어 있지 않다) 따라서 주의해라.

4.8.5. UTF-8 관련 쟁점

이 절에서는 UTF-8 을 기술했는데 이는 가장 널리 쓰이는 UCS 멀티바이트 인코딩이기 때문이다. 이는 많은 국제화 텍스트 처리 문제를 간단하게 한다. 그러나 UTF-8 만이 유일한 인코딩은 아니며 UTF-8 과 같은 종류의 문제점을 갖고 같은 연유로 인해 유효성을 평가해야 하는 UTF-16 과 UTF-7 과 같은 다른 인코딩도 있다.

다른 문제는 ISO 10646/유니코드에서 한가지 이상의 방법으로 어떤 구 (phrase) 가 표현될 수 있다는 것이다. 예를 들어 어떤 강조된 문자는 악센트를 갖는 하나의 문자 및 일련의 문자 (기본 문자 + 별도의 구성 악센트) 로 표현될 수 있다. 이러한 두 형태는 동일하게 나타날 수도 있다. 또한 끼워 넣을 수 있는 zero-width 공백으로 이는 외관상 유사한 아이템이 다르게 생각되도록 한다. 이러한 숨겨진 텍스트가 프로그램을 방해할 수 있는 상황을 조심해라. 이는 일반적으로 해결하기 어려운 문제이다; 대부분의 프로그램은 완벽히 특별한 시퀀스가 나타날 수 있는 방법을 클라이언트가 알 수 있게끔 엄격한 제어를 하지 못한다 (이는 클라이언트의 폰트, 디스플레이 특성, 로케일 등에 의존하기 때문이다).