1. 程式人生 > >UTF8編碼位元組流錯誤小析

UTF8編碼位元組流錯誤小析

對於對稱加密,首先生成位元組流形式的key與iv,之後對指定字串進行加密,然後將key與iv轉換為字串傳輸到另一端,另一端再將字串轉換回位元組流完成解密工作.在key與iv的位元組流與字串之間的轉換中如果選用Utf8編碼,則可能會讓轉換回的二進位制流與之前的位元組流不一致.程式碼如下:
byte[] bytes = new byte[]{146, 174, 27, 74, 223, 159, 52, 180};
string s = Encoding.UTF8.GetString(bytes);
Console.WriteLine(s);
byte[] bytes2 = Encoding.UTF
8.GetBytes(s); Console.WriteLine(bytes2);
 你會發現,bytes2的內容變為了:{239,191,189,239,191,189,27,74,223,159,52,239,191,189}.

  從表面看,其將146, 174, 180三個數擴充套件成了 239,191,189.但毫無規律可尋,223比它們都大,保持了下來,27之類的比他們小,也保持了下來,甚至是159,在它們中間,也儲存了下來.說雙數變吧,74是雙數,卻也保持了下來.後來經過不懈努力,終於大致弄懂原因了.



  1.什麼是字元編碼.

  字元編碼,就是將計算機裡記錄的0與1以一定的規則轉換成人類的字元,如英文或中文,是計算機表達字元的方式,其本質就是一個翻譯,.目前計算機有多種表式方式,比如ASCII之類的.不同的方式對位元組基數要求不一樣,有的固定位元組/字元的,如Ascii是一位元組一字元,Gb2312是兩位元組一字元,有的是不固定位元組/字元的,如Utft8,就從一位元組到四位元組表示一個字元.不同的方式翻譯方式也不一樣,比如對於位元組{84,128,26,144},如果使用Gb2312翻譯,就是"聯通"兩字,使用Utf8翻譯,就是亂碼"T�□�"



  2.Utf8的特殊性

  位元組流的組合有無數種,但能被字符集識別的只是其中的一部分.就像人的嗓子可以發出很多種聲音,但只有26個聲母與5個韻母的組合才能發音成人類語言.Utf8也不例外.Utf8的編碼規則如下:

Unicode編碼(16進位制) UTF-8 位元組流(二進位制)
000000 – 00007F 0xxxxxxx
000080 – 0007FF 110xxxxx 10xxxxxx
000800 – 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000 – 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
可以看到

  1.以0,110,1110,11110開頭的位元組,可以被識別

  2.如果以10開頭,則要求前一個位元組必須以10,110,1110,11110開頭

  3.如果以10開頭且前一個也以10開頭,則再前一個必須以10,1110,11110開頭

  4.如果以10開頭且前一個以10開頭且再前一個也以10開頭,則再前一個必須以11110開頭

  5.不能被識別的位元組,每一個會轉換成三個位元組{11101111,10111111,10111101},也就是239,191,189



  3.解析問題

  上面所提到的位元組流所對應的二進位制流如下:

146 10010010
174 10101110
27 00011011
74 01001010
223 11011111
159 10011111
52 00110100
180 10110100
可以看到,27,74,223,52滿足規則1,159滿足規則2,而146,174,180一個也不滿足,各個被轉換成了239,191,189.所以,最終的結果就是如上所示了.

  使用Ascii,Utf7, Utf32編碼都會有不同程度的變樣,理由同Utf8.



  4.正確的做法

  雖然經過我的實驗,使用Unicode編碼可以完全還原,但將位元組流轉換成字串的正確做法是進行Base64編碼.不同與其它的語言編碼,這是專門用於位元組流與節符串轉換的編碼.使用Convert類的ToBase64String方法與FromBase64String方法即可完成全部功能.Base64的相關知識請自行谷哥.