UTF-8

UTF-8은 유니코드를 위한 가변 길이 문자 인코딩 방식 중 하나로, 켄 톰프슨과 롭 파이크가 만들었다. UTF-8은 Universal Coded Character Set + Transformation Format – 8-bit의 약자이다. 본래는 FSS-UTF(File System Safe UCS/Unicode Transformation Format)라는 이름으로 제안되었다.
UTF-8 인코딩은 유니코드 한 문자를 나타내기 위해 1바이트에서 4바이트까지를 사용한다. 예를 들어서, U+0000부터 U+007F 범위에 있는 ASCII 문자들은 UTF-8에서 1바이트만으로 표시된다. 4바이트로 표현되는 문자는 모두 기본 다국어 평면(BMP) 바깥의 유니코드 문자이며, 거의 사용되지 않는다. UTF-16과 UTF-8 중 어느 인코딩이 더 적은 바이트를 사용하는지는 문자열에서 사용된 코드 포인트에 따라 달라지며, 실제로 DEFLATE와 같은 일반적인 압축 알고리즘을 사용할 경우 이 차이는 무시할 수 있을 정도이다. 이러한 압축 알고리즘을 사용하기 힘들고 크기가 중요할 경우 유니코드 표준 압축 방식(Standard Compression Scheme for Unicode)을 대신 사용할 수 있다.
구조.
UTF-8은 여러 표준 문서에서 다른 방법으로 정의되어 있지만, 일반적인 구조는 모두 동일하다고 볼 수 있다.
유니코드 코드 포인트를 나타내는 비트들은 여러 부분으로 나뉘어서, UTF-8로 표현된 바이트의 하위 비트들에 들어 간다. codice_1까지의 문자는 7비트 ASCII 문자와 동일한 방법으로 표시되며, 그 이후의 문자는 다음과 같은 4바이트까지의 비트 패턴으로 표시된다. 7비트 ASCII 문자와 혼동되지 않게 하기 위하여 모든 바이트들의 최상위 비트는 1이다.
예를 들어서, 문자 "위"(codice_2)는 다음과 같은 방법으로 UTF-8로 인코딩된다.
따라서 첫 128 문자는 1바이트로 표시되고, 그 다음 1920 문자는 2바이트로 표시되며, 나머지 문자들 중 BMP 안에 들어 있는 것은 3바이트, 아닌 것은 4바이트로 표시된다.
위의 패턴을 사용하면 더 큰 코드 포인트를 표시할 수도 있다. 원래 UTF-8은 6바이트를 사용해서 codice_10까지의 코드 포인트를 표현할 수 있게 하였으나, 2003년 11월에 발표된 RFC 3629에서는 유니코드에서 실제로 정의하는 codice_11까지의 문자만을 표시할 수 있도록 제한하였다. 따라서 이전까지는 UTF-8에서 나타날 수 없는 바이트가 codice_12와 codice_13 뿐이었지만, RFC 3629에 따라서 codice_14, codice_15, 그리고 codice_16부터 codice_13까지의 13개의 바이트가 나타날 수 없게 되었다.
변형된 UTF-8.
자바는 내부적으로 문자열을 UTF-16 인코딩으로 저장하며, 문자열 직렬화를 위하여 UTF-8을 변형하여 사용하고 있다. 이를 변형된 UTF-8이라 부른다.
표준 UTF-8과의 차이는 크게 두 가지로, 한 가지는 U+0000을 1바이트가 아니라 2바이트, 즉 11000000 10000000으로 표현하는 것이다. 따라서 수정된 UTF-8에서는 인코딩된 문자열에 널 문자가 나타나지 않게 되며, 따라서 널 문자를 문자열의 끝으로 사용하는 C와 같은 언어에서 처리할 때 문자열이 잘리는 것을 막을 수 있다.
다른 한 가지 차이는 BMP 바깥의 문자를 인코딩하는 방법이다. 표준 UTF-8에서는 이 문자들은 위와 같이 4바이트로 인코딩되지만, 수정된 UTF-8에서는 이 문자들을 surrogate pair로 표시하여 두 문자로 나눈 뒤 같은 방법으로 인코딩한다. (이는 CESU-8과 동일하다) 이러한 방법은 자바의 문자 형이 16비트 크기이며, 따라서 U+10000 이상의 영역에 속한 유니코드 문자는 항상 두 개의 자바 문자로 표현되어야 한다는 것에서 유래하였다. 하지만 이 방법은 BMP 바깥의 문자를 UTF-8보다 더 긴 6바이트로 인코딩해야 한다.
이러한 차이 때문에 이 인코딩은 UTF-8과 엄격하게 구별해야 하며, 자바에서 내부적인 처리에만 사용하도록 권장된다. 또한 UTF-8과는 달리 IANA에 정식으로 등록된 문자 인코딩이 아니므로 인터넷 상의 정보 교환을 위해 사용하지 않아야 한다.
UTF-8의 설계 원칙.
그 구조로부터, UTF-8이 다음과 같은 성질을 가지고 있다는 것을 알 수 있다.
UTF-8이 이런 성질을 가지도록 설계한 까닭은 어떤 경우에도 한 문자에 대한 바이트 표현이 다른 문자에 대한 바이트 표현의 일부가 되는 경우가 없도록 하기 위함이다. 따라서 텍스트 안에 들어 있는 다른 텍스트를 찾는 데 쓰이는 바이트 단위의 부문자열 매칭이 UTF-8 문자열에서도 쓰일 수 있다. CP949, 상용 조합형, Shift-JIS, Big5과 같이 ISO 2022 체계에 부합하지 않는 이전의 가변 길이 인코딩은 그런 성질을 지니지 않았고(기존 인코딩 가운데 EUC-KR이나 EUC-JP 등은 이 점에서는 UTF-8과 비슷한 성질을 지닌다.), 더 복잡한 알고리즘을 써야 했다. 왜냐하면, 이들 인코딩은 ASCII 영역의 글자를 나타내는 바이트를 2바이트로 나타내는 다른 글자의 두 번째 바이트로 사용하기 때문이다. 또한, UTF-8에서 하나 이상의 바이트들이 손실되었을 때도, 다음의 정상 문자를 찾아서 동기화할 수 있기 때문에 피해를 줄일 수 있다.
그 설계 때문에 어떤 바이트들이 올바른 UTF-8로 확인되면, 그 문자열이 실제로 UTF-8로 인코딩되었을 가능성이 매우 높다. 임의의 바이트들이 순수한 ASCII 인코딩이 아닌 UTF-8 문자열일 가능성은 2바이트 문자의 경우 1/32, 3바이트 문자의 경우 5/256으로 매우 낮다. 또한 ISO-8859-1과 같은 기존의 인코딩으로 표현된 자연어 문자열이나 문서를 UTF-8로 표현된 것으로 오인할 가능성도 매우 낮다.
오류 처리.
잘못된 입력이 들어 왔을 때 UTF-8 디코더가 해야 할 동작은 표준들에 일정하게 정의되어 있지 않다. 일반적으로 이러한 경우에 디코더가 취할 수 있는 동작은 다음과 같을 수 있다.
또한 디코더들은 다른 잘못된 입력에 대해서 다른 동작을 보일 수 있다.
RFC 3629에서는 UTF-8 디코더들이 ‘너무 긴 형식’, 즉 해당 문자를 표현하는 데 필요한 최소의 바이트보다 더 많은 바이트를 사용해서 표현한 바이트들을 잘못된 입력으로 처리해야 한다고 요구하고 있다. 유니코드 표준에서는 유니코드를 따르는 디코더가 ‘잘못된 형식으로 표현된 모든 단위 문자열을 오류로 처리하고, 따라서 잘못된 형식으로 표현된 단위 문자열을 해석하거나 생성해 내서는 안 된다’라고 요구하고 있다.
모든 가능한 해석은 장단점을 가지고 있으며, 만약 UTF-8을 해석하기 전에 검증이 이루어질 경우 보안 관련해서 주의가 요구된다.
너무 긴 형식은 처리하기 곤란한 경우 중 하나이다. 현재의 RFC에는 이러한 형식이 디코드되어서는 안 된다고 규정하고 있지만, 옛 UTF-8 표준에는 경고만 하였으며 대부분의 간단한 디코더들은 이러한 데이터를 정상적으로 해석한다. 이 형식은 실제로는 같은 코드 포인트를 서로 다른 두 개의 UTF-8 문자열로 나타내어 마이크로소프트의 IIS 웹 서버와 같은 제품의 보안 검증을 우회하는 데 사용되었다.
잘못된 입력이 들어 왔을 때 보안을 지키기 위해서는 두 가지 선택이 있다. 한 가지는 입력을 검증하기 전에 항상 UTF-8로부터 디코드하는 것이며, 다른 한 가지는 잘못된 입력을 언제나 오류로 처리하는 엄격한 디코더를 사용하는 것이다.
예.
utf-8 표준에서 문자 인코딩(character encoding)과 디코딩(decoding)의 예