邮件编码的总结

最近关注邮件安全问题,调查市场上的邮件安全产品大都是一套封闭邮件系统,部署上存在使用户放弃原来的账户使用新账户的问题,这就导致在部署时存在一定困难。在邮件透明加密产品中,天御云安的隐秘邮很新颖,使用安全邮件网关对邮件透明加密,用户完全感知不到邮件网关的存在,但是邮件在服务器上却穿上了盔甲,任你黑客攻破服务器,看到的邮件却是一堆乱码。隐秘邮网址:https://mail.tyyunan.com/还可以免费试用。

废话不多说了,把处理邮件编码方面的问题,遇到的坑总结下来!!!

 

 

编码的种类

 

 

RFC 821(SMTP)将邮件限制为7位US-ASCII数据算上crlf不超过1000个字符。

 

 

当前定义了三种编码方式:  identity  (“等同”也就是不编码)、qp、base64。三种范围:二进制、8bit、7bit。

 

 

当Content-Transfer-Encoding的值为 "7bit", "8bit", "binary" 之一时,意味着不编码。仅仅提供了邮件体数据的范围。

 

 

1、为了大的二进制数据紧凑高效的编码

2、为了可读数据绝大多数仍然可读的编码

产生了一个或多或少可读的编码(quoted-printable)和“紧凑”或“统一”编码(base64)。

 

Content-Transfer-Encoding 字段限制的值为:"7bit" / "8bit" / "binary" /"quoted-printable" / "base64" /ietf-token / x-token

 

其中x-token是自定义编码方式,ietf-token是

 

 

 

An encoding type of 7BIT requires that the body is already in a 7bit mail-ready representation. This is the default value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the Content-Transfer-Encoding header field is not present.

 

7bit编码需要邮件体已经是7bit表示,也就是说解码时只需要检测编码是不是符合7bit规则,如果符合不会解码。7bit是默认编码类型。

 

编码的定义

1、7bit

参考rfc2045

 

7bit编码是指在CRLF行之间用998个八位字节或更少的字节表示的数据。 其中不允许十进制值大于127的八位字节,也不允许NUL(十进制值为0的八位字节)。

CR(十进制值13)和LF(十进制值10)仅仅作为行之间分离符号出现(crlf之后的数据按新行计数)。

 

 

8bit编码是指在CRLF行之间用998个八位字节或更少的字节表示的数据。 其中允许十进制值大于127的八位字节,不允许NUL(十进制值为0的八位字节)。

CR(十进制值13)和LF(十进制值10)仅仅作为行之间分离符号出现(crlf之后的数据按新行计数)。

 

 

3、binary

参考rfc2045

 

二进制数据允许任何形式表示。

4、QP

参考rfc2045

Quoted-Printable编码适用于内容多为US-ASCII字符集中可打印字符的情况。经过它编码的数据不再需要邮件传输系统进行转换。如果被编码的数据多为US-ASCII字符,则编码后的内容会保留那些可人为识别的部分。完全由US-ASCII字符构成的内容也可以进行Quoted-Printable编码,以确保可以通过字符转换及(或)行封装的网关来传递所有的消息 数据。

在这种编码方式中,字节按如下规则描述:

(1) (普通的8位字节描述)除了被编码内容中标准换行符CRLF序列里的CR和LF字节之外的任何字节,都应该被表示成"="后面紧跟着两个表示字节值的十六进制数字的形式。此处使用的十六进制字符表是"0123456789ABCDEF"。必须使用大写字母,而不允许使用小写字母。因此,十进制的值12可以被表示为:"=0C",十进制值61(表示US-ASCII字符集中的等号)可以被表示为"=3D"。除了选择后面规则中所规定的编码方式之外,必须遵循本规则。

(2) (文字表示法) 十进制值从33到60、从62到126的字节,可以直接表示为US-ASCII字符集中的相应字符(即从感叹号‘!'到小于号‘<'、从大于号‘>'到符号‘~')。

(3) (空格) 十进制值为9和 32的字节可以被分别表示为US-ASCII字符集中的TAB(HT)和空格(SPACE)。但是这种表示方法不可以应用在编码行的末尾。在编码后的内容中,任何TAB(HT)及空格(SPACE)字符后面都必须跟随有可打印的字符。特别的,行末的"="表示"软换行"(见规则5),它可以跟随在一个或多个TAB(HT)或空格(SPACE)字符后面。它遵循了这样一条规则:当行末的最后一个字节的值在9到32之间时,必须按照规则(1)进行编码。这是必要的,因为一些传送消息的MTA(Message Transport Agent--消息传送代理:将消息从一个用户传送至另一个用户处或进行其中一部分工作的程序)会用空白字符来填充行,而其它的则会从行末移除空白字符。因此,当进行"Quoted-Printable"解码时,要删除行末的任何空白字符, 因为它们很可能是被中间的传送代理加上的。

(4) (换行符) Quoted-Printable编码将文本内容中的换行符(CRLF序列)表示成RFC 822的换行符(也是CRLF序列)。因为,除了文本类型(text)之外的其它规范媒体类型通常都不会包含有换行符CRLF序列。这些媒体类型的Quoted-Printable编码中不会出现硬换行符(就是有意义的、要显示给用户的换行符)。所以,在Quoted-Printable编码的非文本类型的数据中就可能会出现"=0D"、"=0A"、"=0A0D"、"=0D0A"等序列。

注意,许多实现机制都是直接将不同的媒体类型编码为本地形式,而不是先将它们转换成规范的格式然后编码,最后再转换成本地形式。特别的,在不使用CRLF序列做为行终止符的系统中,当对纯文本内容进行操作时,会出现这种情况。而只有在某种组合的规范编码等价于分别执那三步操作的时候,才允许执行这种优化的操作。

(5) (软换行符) Quoted-Printable编码规则要求每个编码行的长度不超过76字节。如果需要编码更长的行,就必须要使用"软"换行符。在编码内容中,出现在一行最后位置的等号("=")表示无意义的换行符(软换行符)。

因此,如果编码前的格式是一个单独的未编码行:

Now's the time for all folk to come to the aid of their country.

那么在Quoted-Printable编码后,它可以被表示为:

Now's the time =
for all folk to come=
 to the aid of their country.
 
通过这种机制,过长的行可以被编码成一种能被用户代理存贮的形式。76个字符的限制不包含行末的CRLF序列,但是计算了其它的全部内容,包括所有的等号。

因为在Quoted-Printable中,连字符("-")可以不编码,所以当Quoted-Printable编码的内容被包含在一个或多个multipart实体中时,一定要注意不能让边界分隔符(boundary delimiter)出现在编码内容的任何位置上。(选择边界分隔符时,最好使其包含一个"=_"序列,因为这个序列永远不会出现在Quoted-Printable编码内容中。参考RFC2046中关于multipart的定义)

注意:Quoted-Printable编码为传输中数据的可读性及可靠性提供了一种折中的方法。通过Quoted-Printable编码的主体(body)能可靠的在多数的邮件网关上传输,但是在少数网关上--特别是在涉及EBCDIC转换的网关上--它也许不能很好的工作。base64编码方式提供了更高的可信度。另外一个能经过EBCDIC网关进行可靠传输的方法,就是将如下US-ASCII字符按照规则(1)进行编码:

!"#$@[\]^`{|}~

#$@[\]^`{|}~#$@[\]^`{|}~#$@[\]^`{|}~#$@[\]^`{|}~#$@[\]^`{|}~

quoted-printable数据内容都被假定为面向行的,因此可以预料,在传输过程中可能会转换各行之间的换行符。也就是说,在不同换行习惯的系统间传输的纯文本邮件通常被转换成因特网邮件。如果这种转换会构成错误的数据,那么就需要采用base64而不是quoted-printable来进行编码。

注释:quoted-printable编码规则决定了不能生成几种类型的子串,因此,如果quoted-printable编码器输出这些字符串,则说明它们是非法的。本注释列举了这些情况以及在解码过程中处理这些非法子串的方法。

(1) 等号"="后而跟着两个十六进制的数字,但两个数字或其中的一个是小写字母"abcdef"的非法格式。一个健壮的执行程序可以选择将它们当做相应的大写字母来识别。

(2) 如果等号"="后面的字符即不是十六进制数字(包括"abcdef"),也不是CRLF对中的CR字符,那么就是非法的。产生这种情况可能是因为将未编码的US-ASCII文本包含进了消息中的quoted-printable编码部分。对于健壮的执行程序来说,一个合理的解决方式是将等号及其后面的字符包含进解码内容中,而不进行任何转换。而且如若可能,要向用户指出:在这个位置处的解码也许会出现错误。

(3) 等号"="不可以是编码内容中的最后一个或倒数第二个字符。相应的处理方式可以参考上面的情况(2)。

(4) 除TAB字符、CRLF对中的CRLF字符之外,不可以出现其它的控制字符。同样也不可以存在十进制值大于126的字节。如果解码时在输入数据中出现这些字符,则健壮的执行程序要将这些非法字符从解码数据中删除,并要警告用户发现非法字符。

(5) 编码后的行长度不可以超过76字符(不包括CRLF换行符)。对于一个健壮的解码程序而言,如果在解码时从输入的编码内容中发现了更长的行,则仍然要对其进行解码,同时还可以向用户报告编码错误。

对实现者的警告:如果用quoted-printable编码二进制数据,则必须要分别将CR和LF字符编码为"=0D"和"=0A"。特别的,应该将二进制数据中的CRLF序列编码为"=0D=0A"。否则,如果CRLF序列被描述成一个硬换行符,那么在使用其它换行符的系统中解码数据时就会出错。

 按照如下语法描述quoted-printable数据:

quoted-printable := qp-line *(CRLF qp-line)

qp-line := *(qp-segment transport-padding CRLF)
                qp-part transport-padding

qp-part := qp-section
             ; 最大长度为76字符

qp-segment := qp-section *(SPACE / TAB) "="
                   ; 最大长度为76字符

qp-section := [*(ptext / SPACE / TAB) ptext]

ptext := hex-octet / safe-char

safe-char :=                   60 inclusive, and 62 through 126>
                  ; 也不推荐使用
                  ; 没有在RFC2049中列为"mail-safe"的字符

hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F")
                  ; 这些字节被用来表示值大于127的字节,
                  ; 以及出现在行尾的空格或TAB。而且,
                  ; 对于没有在RFC2049中列为"mail-safe"
                  ; 的字符,也推荐使用这种表示方式。

transport-padding := *LWSP-char
                  ; 设计者不可以产生非零长度的填充,
                  ; 但是接收者必须能够处理由传输机构
                  ; 增加的填充内容。
               
注意:本BNF中所提到的附加LWSP是不被承认的,因为这个BNF并没有详细说明一个结构头字段

 

5、base64

参考rfc2045

设计Base64内容传输编码是为了描述任意的不需要人为识别的字节序列。编码及解码算法很简单,不过,编码后的数据总是比编码前的数据长33%。Base64与RFC1421中定义的Privacy Enhanced Mail (PEM)是同一个编码方法。

由US-ASCII中65个字符组成一个子集,使用6位来表示每一个可打印的字符(第65个字符"="表示要进行特殊的操作)

注意:这个子集有个很重要的性质,那就是,在任何版本的ISO 646(包括US-ASCII)中,它都被描述成相同的内容。而且,在任何版本的EBCDIC中,子集中的所有字符也都具有相同的描述。其它常用的的编码,如UUENCODE、Macintosh binhex 4.0 [RFC-1741]、base85就没有这些性质,因此就无法满足邮件二进制传输编码的可移植性要求。

编码时,每次输入24位数据,输出为4个编码字符。将输入的3个8位字节从左到右连续排列,就可以形成24位数据。将这24位看做是4个连续的6位组,每组都可以单独译成一个base64表中的字符。当通过base64编码方式编码一个位流时,必须假定位流为"重要位优先"的顺序。就是说,位流中的第1位应该是第一个字节中的最高位;位流中的第8位应该是第一个字节中的最低位,依此累推。

用每组(6位)的值来索引64个可打印字符。索引后将得到的字符依次放入输出字符串中。选择表1中的这些字符,是为了能够完备的描述,并且排除在SMTP中有特殊意义的字符(如‘.'、CF、LF)以及在RFC2046中定义的multipart的边界分隔符中有特殊意义的字符(如‘-')。

表1:base64字母表

     Value Encoding  Value Encoding  Value Encoding  Value Encoding
         0 A            17 R            34 i             51 z
         1 B            18 S            35 j             52 0
         2 C            19 T            36 k            53 1
         3 D            20 U            37 l             54 2
         4 E            21 V            38 m            55 3
         5 F            22 W           39 n             56 4
         6 G            23 X            40 o            57 5
         7 H            24 Y            41 p            58 6
         8 I             25 Z            42 q            59 7
         9 J             26 a            43 r            60 8
        10 K           27 b            44 s            61 9
        11 L            28 c            45 t            62 +
        12 M           29 d            46 u            63 /
        13 N            30 e            47 v
        14 O            31 f            48 w         (pad) =
        15 P            32 g            49 x
        16 Q           33 h            50 y

编码输出的流必须被描述成一些不大于76字节的行。解码时,必须要忽略换行符及其它所有不存在于表1中的字符。在base64数据中,除表1中字符、换行符、空格之外的字符都可能表示存在传输错误,在某些情况下,可以适当的给出一些警告信息甚至是拒绝信息。

如果需要被编码数据的剩余部分不足24位,则要执行特殊的操作。编码量通常在主体(body)结尾部分结束。当输入少于24位时,会在末尾(右侧)添加一些值为0的位,以形成完整的6位组。用"="来表示数据结尾的填充。因为所有base64的输入都是完整的字节,所以只可能出现如下情况:(1)最后的编码输入是完整的24位;这时,编码输出的最后一个单元会是完整的4个不为"="的个字符。(2)最后的输入是8位;这时,编码输出的最后一个单元是两个编码字符后接两个填充字符"="。(3)最后的输入刚好是16位;这时,编码输出的最后一个单元是三个编码字符后接一个填充字符"="。

因为"="是用来填充数据的结尾部分,所以,它的出现意味着可能已经到达数据末尾(但不切断传输)。然而,也可能无法以这种方式进行判断:当传输的字节个数是三的整数倍时,编码中就不会出现"="。

在base64编码数据中,要忽略任何不属于base64字母表的字符。

一定要注意,当base64编码直接应用于未经过规范化的文本内容时,要使用恰当的字节做为换行符。特别是在进行base64编码前,必须要将文本换行符转换为CRLF序列。要注意,一件很重要的事情就是:这些操作可以直接由编码器来完成,而不是在一些实现中先进行一个标准化的步骤。

注释:不用担心multipart实体中的base64编码部分引用到潜在的边界分隔符(boundary delimiter),因为base64编码中没有使用连字符"-"。

posted @ 2019-02-22 11:23  隐秘邮  阅读(1094)  评论(0)    收藏  举报