一篇文章了解编码
因为最近在写代码和做一些渗透的时候经常遇到乱码的情况,而且原因各不相同,所以准备系统地学习一下编码的问题,以及实际中的应用。这篇文章是总结了网上的一些博客的内容完成的。
字符、字符集、字符编码
- 字符:其实就是各种文字和符号的总成,可以是一个英文、一个汉字、一个标点,总之是一个符号。
- 字符集:多个字符的集合,例如GB2312是中国国家标准的简体中文字符集,包含了6763个常用的汉字和字符
- 字符编码:是一种映射规则,根据这个规则将字符映射为其他形式的数据方便在计算机中存储和使用。比如ASCII码用7个比特去编码所有的字符,将'A'编码为65进行存储,写入机器中就是01000001.常见的字符编码有UTF-8、GBK、Big5等
Unicode:UTF-8、UTF-16、UTF-32
在了解了上面字符集和字符编码的知识,我们要知道,Unicode是一个字符集,而像UTF-8才是真正的字符编码规则,UTF-8这些字符编码规则规定了用多少字节、是定长字节还是变长字节来映射Unicode中的字符。
Unicode
Unicode是国际标准字符集,它的目标是包含世界所有的文字和符号,它的字符集编码范围是0x0000-0x10ffff,现在的规模可以容纳100多万个符号。每个符号的编码都不同。
Unicode只是一个字符集,它只规定了符号的二进制代码,却没有规定这个二进制代码如何编码如何存储。
UTF-8
就像上文提到的,UTF-8是一种编码方式,用来将Unicode中的字符编码进行使用。并且UTF-8是使用最广的一种编码方式,UTF-16和UTF-32其实并不常见。
UTF-8的特点是对不同范围的字符使用不同长度的编码,是一种变长编码方式。长度是1~4个字节
UTF-8的编码规则记住两条:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode编码,所以我们可以知道,对于英文字母,UTF-8和ASCII码是相同的,(这也是因为英文字母是使用最多的字符,所以用最短的长度来表示可以最大程度地节省内存)
- 对于n字节的符号(n>1),第一个字节的前n位设置为1,第n+1位设置为0,后面的所有字节的前两位一律设置为10。剩下的二进制位就是这个符号的Unicode编码
image-20230329114205103
下面, 还是以汉字“严”为例,演示如何实现UTF-8编码。
已知“严”的unicode是\u4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。
UCS-2和UCS-4
在区别UTF-16与UTF-32之前,我们要知道UCS-2和UCS-4,分别是用2个字节和4个字节来表示码点,也就是一个字符使用2个字节的二进制码还是4个字节的二进制码,还是没有定义字符的编码格式。
UTF-16
它使用UCS-2,将UCS-2规定的码点通过大端或小端的方式保存下来。UTF-16包括三种:UTF-16,UTF-16BE(Big Endian)和UTF-16LE(Little Endian)。UTF-16需要通过在文件开头以名为BOM(Byte Order Mark)的字符来表明文件是Big Endian还是Little Endian。
我们知道Unicode的范围为0x0~0x10FFFF,0x0!0xFFFF这段区间正好是16位也就是2字节。
0xFFFF~0x10FFFF这段,16位肯定存不下,所以就使用32位,也就是4字节,所以UTF-16的长度是2字节或者4字节。UTF-16的编码规则稍微复杂一点:
- 对于 Unicode 码小于 0x10000 的字符, 使用 2 个字节存储,并且是直接存储 Unicode 码,不用进行编码转换
- 对于 Unicode 码在 0x10000 和 0x10FFFF 之间的字符,使用 4 个字节存储,这 4 个字节分成前后两部分,每个部分各两个字节,其中,前面两个字节的前 6 位二进制固定为 110110,后面两个字节的前 6 位二进制固定为 110111, 前后部分各剩余 10 位二进制表示符号的 Unicode 码 减去 0x10000 的结果
- 大于 0x10FFFF 的 Unicode 码无法用 UTF-16 编码
UTF-32
UTF-32使用4个字节表示码点,这样可以完全表示UCS-4的所有码点,不需要任何编码转换直接存储Unicode码,无需像UTF-8那样进行变长编码。
和UTF-16相似,UTF-32也区分UTF-32、UTF-32BE、UTF-32LE三种编码,也需要BOM字符
GB2312、GBK、GB18030
GB2312
1980 年,中国发布了第一个汉字编码标准,也即 GB2312 ,全称 《信息交换用汉字编码字符集·基本集》,通常简称 GB (“国标”汉语拼音首字母), 共收录了 6763 个常用的汉字和字符,此标准于次年5月实施,它满足了日常 99% 汉字的使用需求
编码方式:
- 对于ASCII字符,使用1个字节存储,并且该字节最高位为0
- 对于中国的字符,使用2个字节存储,并且规定每个字节的最高位为1
区位码
GB2312 对汉字进行了分区处理,每个区含有 94 个汉字或者字符,总共有 94 个区,每个汉字或者字符都对应一个 分区编号和分区内的位置编号,称为区位码
比如:汉字 "中" 字的 分区编号是 54,分区内位置编号是 48,所以,"中" 字的区位码是 54 48
国标码
国标码 也叫 交换码,用于交换文件所使用的编码,在早期,不同的操作系统可能使用不同的内码,如果它们之间要交换文件,则会发生乱码的现象,当时的解决方法是交换文件之前先转成交换码再交换,接收者收到之后再转成内码
ASICII 码为 0- 31 的这 32 个字符是不可显示的字符,为了避免和这些字符的码点冲突,将 分区编号和分区内位置编号都加上 32 ,把这个转换的结果称为 国标码
比如:汉字 "中" 字分区编号是 54,分区内位置编号是 48,加上 32 之后,分区编号是 54 + 32 = 86, ,分区内位置编号是 48 + 32 = 80,所以 "中" 字 的国标码是 86 80
内码
国标码 和 ASICII 码还是存在一定的重复,比如 "中" 字 的国标码是 86 80,对应第一个字节是 86,第二个字节是 80,而在 ASICII 码中它们分别代表大写字母V 和 大写字母 P,这就无法区分它们到底是一个汉字,还是两个字母
为了解决这一点,把国标码中的每个字节的最高位置为 1,也即相当于每个字节都加上 128 ( 2的7次方 ),还是以 "中" 字为例,它的 国标码是 86 80,加上 128 后, 第一个字节是 86 + 128 = 214, 第二个字节是 80 + 128 = 208,转化成 16 进制是 0xD6 0xD0 ( 214 的十六进制是 0xD6, 208 的十六进制是 0xD0 )
汉字的 区位码 + 32 + 128 就得到了内码,进一步简化,区位码 + 32 + 128 = 区位码 + 160 = 区位码 + 0xA0(128 的十六进制) , 因此 内码 = 区位码 + 0xA0
GBK
由于有些汉字是在 GB2312 标准发布之后才简化的,还有一些人名、繁体字、日语和朝鲜语中的汉字也没有包括在内,所以,在 GB2312 的基础上添加了这部分字符,就形成了 GBK ,全称 《汉字内码扩展规范》,共收录了两万多个汉字和字符,它完全兼容 GB2312
和 GB2312 一样,GBK 也是双字节编码
GB18030
GB18030 全称《信息技术 中文编码字符集》 ,共收录七万多个汉字和字符, 它在 GBK 的基础上增加了中日韩语中的汉字 和 少数名族的文字及字符,完全兼容 GB2312,基本兼容 GBK
与 GBK 不同的是,GB18030 是变长多字节字符集,每个字或字符可以由一个,两个或四个字节组成,所以它的编码空间是很大的,最多可以容纳 161 万个字符
ANSI
ANSI并不是某一种特定的字符编码,而是在不同系统中,ANSI表示不同的编码
ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f 范围的1 个字节来表示1个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码
比如:在简体中文Windows操作系统中,ANSI编码代表GBK编码,在繁体中文Windows操作系统中,ANSI编码代表Big5,在日文Windows操作系统中,ANSI代表JIS编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。
Big-5
在中国台湾、中国香港、中国澳门地区普遍使用繁体中文的情况下,当地电脑软件或操作系统经常使用Big5(又称大五码)作为繁体中文的默认文字编码。
同GBK编码一样,Big5编码也是采用双字节编码,兼容ASCII码。也就是说每个繁体中文汉字在Big5下占据2字节。
相关知识
宽字符和窄字符(多字节字符)
有的编码方式采用 1~n 个字节存储,是变长的,例如 UTF-8、GB2312、GBK 等;如果一个字符使用了这种编码方式,我们就将它称为多字节字符,或者窄字符。
有的编码方式是固定长度的,不管字符编号大小,始终采用 n 个字节存储,例如 UTF-32、UTF-16 等;如果一个字符使用了这种编码方式,我们就将它称为宽字符。
全角和半角
GB2312编码表有个值得注意的点,这个表中也有一些数字和字母,与ASCII里面的字母非常像。例如A3B2对应的是数字2(如下图),但是ASCII里面50(十进制)对应的也是数字2。他们的区别就是输入法中所说的“半角”和“全角”。全角的数字2占两个字节。
Windows系统如何区分ANSI背后的真实编码
微软用一个叫“Windows code pages”(在命令行下执行chcp命令可以查看当前code page的值)的值来判断系统默认编码,比如:简体中文的code page值为936(它表示GBK编码,win95之前表示GB2312),繁体中文的code page值为950(表示Big-5编码)
我们可以通过cmd命令修改当前终端的ANSI值:
chcp 437 //ANSI
CHCP 936 //gbk
执行chcp 437:
image-20230329172644817
执行chcp 936:
image-20230329172711878
chcp命令只改变当前终端的编码,并不影响系统默认的ANSI编码。
Windows下code page是根据当前系统区域(locale)来设置的,要想修改系统默认的“ANSI编码”,我们可以通过修改系统区域来实现(“控制面板” =>“时钟、语言和区域”=>“区域和语言”=>“管理”=>“更改系统区域设置...”):
image-20230329172928975
ps:(如果一个APT组织针对某一国家某一地区进行精确打击,就可以通过ANSI编码来确定机器的国家,从而选择是否进行网络攻击)
烫烫烫与锟斤拷
手持两把锟斤拷,口中疾呼烫烫烫。脚踏千朵屯屯屯,笑看万物锘锘锘
这个段子就是将编码错误,从而导致乱码的常见情况,都是Unicode和GBK编码的转换错误。
对于VCS的编译器,分配空间后总要在里面填一些东西。默认填入的东西是这样的:
未分配或静态分配而未赋初值的内存空间,初值用0xCC填充,如不小心访问了它们,则会看到如下内容:
按字符输出为烫(0xCCCC)
按int输出为-858993460(0xCCCCCCCC)
动态分配(new,malloc)而未赋初值的内存空间,用0xCD填充,如不小心访问了它们,则会看到如下内容:
按字符输出为屯(0xCDCD)
按int输出为-842150451(0xCDCDCDCD)
锟斤拷则涉及Unicode的字符集转换问题,Unicode和老编码体系的转化过程中,肯定有一些字,用Unicode是没法表示的,Unicode官方用了一个占位符来表示这些文字,这就是:U+FFFD
U+FFFD的UTF-8编码是0xEFBFBD,如果重复多次形成:EFBFBDEFBFBDEFBFBD 。
在GBK/CP936/GB2312/GB18030的环境中显示的话,一个汉字2个字节,最终的结果就是:锟斤拷——锟(0xEFBF),斤(0xBDEF),拷(0xBFBD)。
出现锘锘锘的情况应该是BOM出了问题,因为锘的编码就是EFBB,BOM在UTF-16中本来是FFFE,到UTF-8就变成了EFBBBF,这个问题就是因为UTF-8没有BOM
标签:编码,UTF,字节,字符,32,Unicode From: https://www.cnblogs.com/sca1p31/p/17474637.html