伯乐共勉

讨论。NET专区
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

UTF-7 邮件安全的 Unicode 转换编码

Posted on 2007-11-23 10:25  伯乐共勉  阅读(575)  评论(0编辑  收藏  举报

摘要

Unicode标准,2.0版本, 以及ISO/IEC 10646-1联合定义了一种字符集,它包含了世界上大多数可书写的字符系统。(后文都直接用Unicode一词)。

事 实上,因特网邮件(STD 11, RFC 822)目前所支持的仅仅是7-bit的ASCII字符集。MIME(RFC 2045到2049)扩展了网络邮件以支持不同的媒体类型和字符集,也因此能够在邮件信息里支持Unicode了。虽然它确实提供了随着时间而增加的字符 集注册的方法,但MIME既没有把Unicode定义为容许的字符集,也没有说明它要怎么编码。

本 文档描述了一种Unicode的转换编码,它只使用7-bit的ASCII字节,并且意图在文件由US-ASCII表中字符组成的限定条件下,对人来说是 易读的。还说明了在MIME的内容中怎么使用这种转换编码(RFC 1641“在MIME中使用 Unicode”)。

起因

虽 然存在着其他的Unicode转换格式,并且也足以在这样的环境下使用(最明显的就是UTF-8,还有UTF-2 or UTF-FSS),但他们都有一个缺点,就是他们使用了128-255这一范围对Unicode编码,超过了US-ASCII的范围。因此,在邮件环境 中,8位字节必须要被编码。这要求让文本经历两次连续的编码,让US-ASCII范围以外的字符有一个显著的扩展,这使得非英文的使用者处于非常不利的地 步。例如,使用UTF-8和MIME中的Quoted-Printable内容编码方式一起处理,让US-ASCII字符出现在一个字节里,但其 它的字符可能需要9个字节。

概述

UTF -7把Unicode字符编码为US-ASCII的字节,并且用替换序列编码超出范围(0-127)的字符。为了这个目的,US-ASCII 表里的一个字符就要保留作为替换字符使用。大多邮件网关和系统不能处理完整的US-ASCII字符集(例如那些基于EBCDIC的),所以UTF-7包含 了在US-ASCII用于编码的预留,以便所有的邮件系统都能兼容。UTF-7应该一般用在7-bit传输的环境中,比如邮件。其他的环境中 Unicode或者UTF-8还是

首选的。参见RFC 1641,“在MIME中使用Unicode”整体描述了在MIME中Unicode转换编码的使用。

定义

首先,定义Unicode:

16-bit字符集,Unicode,由"Unicode标准,2.0版本"所定义。这套字符集与国际标准组织的编码ISO/IEC 10646-1一致。 编码后形式为UCS-2;子集为300;实现等级为3,包含对10646+的前7个修正。

注:Unicode 2.0 还进一步说明了这些字符在ISO标准组织以外的使用和交互。事实上,一个有效的10646序列就是一个有效的Unicode序列,反之也是;Unicode 协会提供对序列的解析,而ISO标准组织却一直没有。

然后,定义一些US-ASCII字符子集:

集合D:(直接字符)包含如下的字符(取自RFC 1521,附录B,在RFC 2045中不再出现了):

大写字母A-Z,小写字母a-z,和10个数字0-9,和后面的9个特殊字符(注意:"+"和"-"遗漏了)

字符     ASCII & Unicode值 (十进制)
                  ''           39
                  (           40
                  )           41
                  ,           44
                  -           45
                  .           46
                  /           47
                  :           58
                  ?           63

集合O: (可选的直接字符) 包含如下的字符: (注意 "\" 和 "~" 遗漏了):

字符     ASCII & Unicode值 (十进制)
                  !           33
                  "           34
                  #           35
                  $           36
                  %           37
                  &           38
                  *           42
                  ;           59
                  <           60
                  =           61
                  >           62
                  @           64
                  [           91
                  ]           93
                  ^           94
                  _           95
                  ''           96
                  {           123
                  |           124
                  }           125

基本原理:有两个字符 "\" 和 "~" 被遗漏了是因为在ASCII表中他们经常被重新定义变量值。

集合B: (更改过的Base64) (RFC 2045)定义的Base64字母表中的字符,除了衬垫字符:"=" (十进制值为 61)。

基 本原理: 衬垫字符 "=" 被排除了是因为UTF-7被设计用来在报头字段中使用,就如RFC 2047中的集合4。 因为RFC 2047编码中唯一易读的编码就是"Q",(基于RFC 2045''s Quoted Printable),所以"="不适合使用(没有很多的转义序列)。这很不幸,但是却不可避免。其实,"=" 字符在UTF-7中也可以作为转义字符使用,如果不是使用"+"。

注:所有US-ASCII在Unicode中都是用同样的值,补0扩展到16 bits。

UTF-7 定义

一个UTF-7流用如下方式使用7-bit US-ASCII字节表示16-bit Unicode字符:

  • 规则1:(直接编码) 上面的集合D中的Unicode字符可以直接的编码为ASCII的等价物。集合O中的字符可以有随意的直接编码为ASCII的等价物,但要记得其中的很多的字符在报头字段是不合法的,或者不能正确的穿过邮件网关;
  • 规 则2:(Unicode替换编码) 通过在前面加上转换字符"+"(US-ASCII字符十进制值为43),任何一个Unicode序列都可以使用集合B中的字符编码。"+"意味着后面的字 节将被作为更改过的BASE64字母表中的元素解析,直到遇到一个不是字母表中的字符为止。这些字符中包含控制字符,比如回车和换行;因此,一个 Unicode转换序列总是在一行上结束。作为一个特殊的情形,如果序列结束于一个"-"字符(US-ASCII值45),那么那个字符就要被关注;其他 的结束字符不用关心,而且可以正常处理;
  • 注意:如果跟在转换字符后的第一个字符就是"-",那么一个多余的必须被加上,以便结束转换序列,所以真正的"-"不是它关心的;
  • 基 本原理:一个结束字符在如下的情形是必须的:在更改的base64序列的下一个字符是集合B中的部分,或者本身就是一个结束字符。通过界定编码的序列也可 以增强可读性。作为特定情形,序列"+-"可以用来编码字符"+"。一个"+"字符之后立即跟着的字符若不是集合B的成员或者"-",就是一个形式有误的 序列;

 

对Unicode使用更改的base64编 码时候,可以首先转换Unicode的16bit的数量值为一个字节流。通过把成对中的一个当作单独的值以后,就会转换为字节对。奇数个字节的的文本是错 误的形式,ISO1646 字符超过可设定地址范围的部分转换为字节对后也无法编码。

  • 基本原理:ISO/IEC 10646-1:1993(E)说明了当UCS2形式被字节流序列化时,哪个字节在前面。选定一种用来发送的规范格式,在一般网络应用中也是遵循的;
  • 基本原理:ISO 10646和Unicode标准中代码点定位的策略是一个同步一致的字

     

    符表。如果字节对超过了ISO10646可寻址范围,就无法对代码点定

    位了。

 

然 后,对字节流进行编码时使用了"更改的base64编码"算法(定义于RFC 2045),更改中去掉了衬垫字符"="。在编码时候,增加了代替的"0 bits"以便衬垫base64编码字符的边界。在解码时,在"更改的base64编码"序列最后的一些bits,如果不能组成一个完整的 16-bit的Unicode字符,那么就会被丢弃。如果丢弃的bits不是0,那么这个编码序列就是有格式错误的。

  • 基本原理:在对更改的 Base64 进行编码时,不使用衬垫字符"=",因为就象上文提及的,它与在 RFC 2047 中将它用做 "Q 内容传输编码" 的转义字符相冲突;
  • 规 则3: 空格 (十进制 32),tab (十进制 9),回车(十进制 13),和换行(十进制 10)字符可以直接使用ASCII等价字符表示。事实上,应该注意到MIME传输编码也有使用这些字符的规则。如果使用并不遵循RFC 822的限制,必须要使用MIME编码而不是7bit或者8bit的一些编码,比如quoted-printable,binary,或者base64。

 

鉴于给定的一组规则,Unicode字符可以经由规则1或者规则3编码,一个字符占用一字节;然后其他的Unicode字符用规则2编码,一个字符占用平均(2 + 2/3)个字节,加上一个转换开关字节用来进入"更改的base64编码",加一个可选的转换跳出字节。

例如:Unicode序列 "A<NOT IDENTICAL TO><ALPHA>." (十进制: 0041,2262,0391,002E) 可以被编码如下:

A+ImIDkQ.

例 如:Unicode序列 "Hi Mom -<WHITE SMILING FACE>-!" (十进制: 0048, 0069, 0020, 004D, 006F, 006D, 0020, 002D, 263A,002D, 0021) 可以被编码如下:

Hi Mom -+Jjo--!

例如:Unicode序列 用汉语表示日文 "nihongo" (十进制: 65E5,672C,8A9E) 可以被编码如下:

+ZeVnLIqe-

在MIME中使用UTF-7字符集

UTF -7字符集对邮件传输是安全的,因此可以应用于MIME中任何内容的编码(除非出现了对行长度和行中断的强制约束)。特定的,7-bit的编码用于主体, "Q内容编码"用于报头的情况也可以使用。MIME字符集的标签是UTF-7,这一点对大于等于2.0版本的Unicode很重要。

例子:这是一个MIME消息的片段,包含了一段Unicode序列:

"Hi Mom <WHITE SMILING FACE>!" (十进制 0048,0069, 0020, 004D, 006F, 006D, 0020, 263A, 0021)。Content-Type: text/plain; charset=UTF-7 Hi Mom +Jjo-!

例子:这是一个MIME消息的片段,包含了一段用汉语表示日语词 "nihongo" 的 Unicode 序列:

(十进制: 65E5, 672C, 8A9E)。Content-Type: text/plain; charset=UTF-7 +ZeVnLIqe-

例子: 这是一个MIME消息的片段 ,包含了一段Unicode序列:

"A<NOT IDENTICAL TO><ALPHA>." (十进制: 0041, 2262, 0391, 002E)Content-Type: text/plain; charset=utf-7 A+ImIDkQ.

例子: 这是一个MIME消息的片段,包含了一段Unicode序列:

"Item 3 is <POUND SIGN>1." (十进制 0049, 0074, 0065, 006D, 0020, 0033, 0020, 0069, 0073, 0020, 00A3, 0031, 002E)。Content-Type: text/plain; charset=UTF-7 Item 3 is +AKM-1.

注意:为了和 "可能不支持Unicode与MIME的系统"达到最好的互用性,在准备邮件传输正文的时候,"行中断"应该遵守网络惯例。这意味着行应该很短而且要使用 SMTP的CRLF来标记结束。Unicode的行分隔符(十进制 2028)和段分隔符 (十进制 2029)应该被替换为SMTP的行中断。理想的情况,这应该由一个理解 Unicode的用户代理透明的处理。

这样的准备也不是绝对必要的,因为UTF-7和适当的MIME编码可以在不遵守网络惯例的情况下处理正文,但是对于没有Unicode或者MIME的系统的可读性就会被削弱了。关于邮件协同工作能力问题的讨论可以参见 RFC 2045。

在UTF -7转换序列中行是不可以被打断的,因为这样的序列不可以跨越行中断。因此,UTF-7编码可以放在行中断后面。如果一行含有转换后会很长的序列,可以使 用MIME中的编码来处理正文,比如 Quoted Printable。另一个可行性就是同时执行行中断和UTF-7编码,这样包含转换序列的行就已经符合长度限制了。

讨论

在本节中,我们将引入UTF-7编码,它反对选择现有的Unicode转换编码(例如UTF-8)和MIME编码一起使用。在讨论之前,有必要先列举一些假设,这些假设有关于典型自然语言文本串中字符出现的频率,而这些频率可以用来评估典型存储的需求:

  1. 多数西欧语言使用大概7/8的US-ASCII字符和1/8的打丁字符(ISO-8859-1);
  2. 多数基于非罗马字母表的语言(比如希腊)使用大约1/6的ASCII字符(因为空格 也算是7bit的),其他的来源于他他们自己的字母表;
  3. 东亚基于表意字表的语言(包括日文)基本使用他们自己的字符表,Han或者CJK;
  4. 非直接编码的标点字符的出现次数不足以影响结果;

     

    注意:当前的8bit标准,比如ISO-8859-x,要求使用内容传输编码。为了和后续的讨论对比,把代价列举如下,(注意:很多其中的数字只是接近的,因为他们依赖文本确切的组成形式):

 

Base64中的8859-x 文本类型 平均字节数/字符 所有 1.33 Quoted Printable中的8859-x 文本类型 平均字节数/字符 US-ASCII 1 西欧 1.25 其他 2.67

注意:使用base64编码的Unicode平均一个字符占用了2.67个字节。为了对比,我们看一下UTF-8和UTF-7在Base64和Quoted中的情况。还要指出的是:一个较长的字符串有固定的经常性开销,大约为 1/n,n是指编码后字节串的长度。

UTF -8在 Base64中 文本类型 平均字节数/字符 US-ASCII 1.33 西欧 1.5 一些字母表的 2.44 其他 4 UTF-8在Quoted Printable中 文本类型 平均字节数/字符 US-ASCII 1 西欧 1.63 一些字母表的 5.17 其他 7-9 UTF-7 文本类型 平均字节数/字符 多数 US-ASCII 1 西欧 1.5 其他 2.67+2/n

我 们会发现UTF-8在Quoted Printable选项中是不可行的,因为在文本中有太多的除了西欧语言外的其他的语言。当只有当文本中大多是ASCII或者拉丁数字字符,偶尔有其他的 字符散布的时候,这样编码才是可行的。我们将首选介绍一种编码能很好的适用于所有用户。我们还会发现UTF-8与base64编码的使用者中,有大量的非 西欧用户。即便是里面有很多的ASCII字符,因为没有很好的可读性,这样的编码也不是十分适用。基于UTF-7的编码可以给出很好的结果,并且对 ASCII文本有很好的可读性。

UTF-7 给出的结果挑战了ISO-8859-x,而且适用于所有的Unicode字符。我想,这证明了引入一种新的Unicode转换编码是正确。

UTF -7作为一种可选的方案,但是,因为multipart/mixed内容类型忽略了行中断的问题,也可能在使用现有的MIME机制的时候会和其他的 Unicode字符集产生混乱。(感谢Nathaniel Borenstein提了这个建议),例如:(重新说一下前面的例子)

Content-type: multipart/mixed; boundary=foo Content-Disposition: inline --foo Content-type: text/plain; charset=us-ascii Hi Mom --foo Content-type: text/plain; charset=UNICODE-2-0 Content-transfer-encoding: base64 Jjo= --foo Content-type: text/plain; charset=us-ascii ! --foo--

理 论上,这里去掉了在消息体里对UTF-7的需求。(多部分类型不可以在报头字段里使用)事实上,我们发现使用Unicode字符集变得更为广泛了,间断使 用一些特殊的Unicode字符(例如钱和数学符号)的情况会出现,并且文本里还会包含很多小块的其他的语句,比如西里尔的,希腊的和东亚的语言。(罗马 的文字都已经能被MIME字符集充分处理了)虽然多部分技术对于大块用于交互的文本来说工作的很好,我们还是觉得它不能充分的支持刚刚讨论的应用类型,所 以我们认为引入UTF-7是合理的。

总结

UTF -7编码方式使得Unicode字符集能在US-ASCII的7-bit范围中编码。对于一些Unicode序列,如果含有相对较长的US-ASCII字 符串,中间夹杂了单个的Unicode字符或者Unicode串,这种编码是高效的。因为它容许没有Unicode支持的系统直接阅读US-ASCII的 部分。UTF-7仅仅应该用在7-bit传输的时候,比如邮件。在其他的环境下,Unicode 或者UTF-8应该是首选的。

致谢

对如下人的贡献,评论和建议表示感谢,如果因为疏忽而遗漏了某一位,也不是故意的!

Glenn Adams Harald T. Alvestrand Nathaniel Borenstein Lee Collins Jim Conklin Dave Crocker Steve Dorner Dana S. Emery Ned Freed Kari E. Hurtta John H. Jenkins John C. Klensin Valdis Kletnieks Keith Moore Masataka Ohta Einar Stefferud Erik M. van der Poel

附录 A —— 例子:

这里有个更大的例子,是从一个BIG5编码的文档里拿来的。只是精简了一些。这里有两个版本,第一个使用了可选的"O"字符集(这可能会造成不能通过一些邮件网关),第二个没有。

Content-type: text/plain; charset=utf-7

下面就是全都是中文的一端节选 (+itaKng-)。原文是这样的:

"The sayings of Confucius," James R. Ware, trans. +U/BTFw-:+ZYeB9FH6ckh5Pg-, 1980. (中文文本做了英文转换)+Vttm+E6UfZM-, +W4tRQ066bOg-, +UxdOrA-: +Ti1XC2b4Xpc-, 1990."The Chinese Classics with a Translation, Critical and ExegeticalNotes, Prolegomena, and Copius Indexes," James Legge, trans., Taipei:Southern Materials Center Publishing, Inc., 1991.

(中文文本做了英文翻译)分别做了个BIG5和GB两个版本:

本 文中没有包含BIG5或者GB的所有字符。缺失的字符已经使用它们的Unicode/ISO代码点指示出来了。"U+-" 后面跟着四个十六进制指定一个Unicode/10646代码(例如:U+-9F08)。这对小规模的BIG5和GB使用的问题,不是一个好的解决方案; 但是这种解决方案的表现,对我个人而言是很满意的。

(缺失了…)+ XrdxmVtXUXg-(缺失了…)John H. Jenkins +TpVPXGBG- jenkins@apple.com 5 January 1993(缺失了…)Content-type: text/plain; charset=utf-7

下面就是全都是中文的一端节选(+itaKng-)。原文是这样的:

+ACI-The sayings of Confucius,+ACI- James R. Ware, trans. +U/BTFw-:+ZYeB9FH6ckh5Pg-, 1980. (中文做了英文转换)+Vttm+E6UfZM-, +W4tRQ066bOg-, +UxdOrA-: +Ti1XC2b4Xpc-, 1990.+ACI-The Chinese Classics with a Translation, Critical and ExegeticalNotes, Prolegomena, and Copius Indexes,+ACI- James Legge, trans.,Taipei: Southern Materials Center Publishing, Inc., 1991.

(中文文本做了英文转换)分别做了个BIG5和GB两个版本:

本 文中没有包含BIG5或者GB的所有字符。缺失的字符已经使用它们的Unicode/ISO代码点指示出来了。+ACI-U+-+ACI- 后面跟着四个十六进制指定一个Unicode/10646代码(例如:U+-9F08)。这对小规模的BIG5和GB(+ADs-)使用的问题,不是一个 好的解决方案;但是这种解决方案的表现,对我个人而言是很满意的。

(缺失了…)+XrdxmVtXUXg-(缺失了…)John H. Jenkins +TpVPXGBG- jenkins+AEA-apple.com 5 January 1993(缺失了…)

对安全的考虑

本文没有讨论安全问题