1. 程式人生 > >C++ 筆記——字串自定義加密處理

C++ 筆記——字串自定義加密處理

根據慣例,先放定義。加密,是以某種特殊的演算法改變原有的資訊資料,使得未授權的使用者即使獲得了已加密的資訊,但因不知解密的方法,仍然無法瞭解資訊的內容。 加密演算法非常多,常見的加密演算法有MD5、AES、Base64、DES等等。但是此篇部落格記錄的加密演算法和上述加密演算法無關,主要記錄的是按照個人喜好對字串做處理的簡單方法。

字串編碼

一段字串,把所有的a變成c,把c變成d,把d變成a,我們可以把這樣改變原有資訊的處理過程看做加密。拿到處理後的字串的人,重新把字串中所有的a變成d,把d變成c,把c變成a。這樣他才能得到原來的字串。我們可以把這個根據加密演算法對加密後的資料逆向恢復成原有資料的過程叫做解密。
我們知道,在ASCII表中字元(也就是英文字母、數字和一些標點符號及控制符),我們都可以用一個char

來儲存。其他的字元,比如漢字、韓文、日文等,都需要用wchar_t才能儲存。而charwchar_t只是記憶體上的區別,就如同charint一樣,我們可以把一個int用四個char來儲存,內容並不會改變。
儲存位文字檔案時,字串儲存也會有不同的編碼方法。比如我們所熟知的UTF-8、GB2312等。不同的編碼方式儲存相同的內容,一般得到的是不同結果。當程式需要重新從文字檔案中讀回內容時,就需要按照儲存時候的規則(也就是編碼方式),來用相應的解碼方式來讀回。
以UTF-8為例,我們以UTF-8的方式寫入文字檔案時,並不是直接把字元按照01010001這樣的二進位制方式直接把字元對應的值寫入到檔案中的。而是按照UTF-8的編碼規範,把字元變換成一個新的數值,然後寫入到檔案中的。
UTF-8是一種變長編碼,所有的字元並不是按照固定的長度來進行編碼的,不同的字元所佔的位元組數可能會不同。按照UTF-8的編碼規範會有以下規律:

  • 當一個字元轉換成二進位制後,用7位足以表示時,這個字元編碼佔一個位元組,且最高位為0。
  • 當一個字元轉換成二進位制後,需要用8-11位表示時,這個字元編碼佔兩個位元組,且第一個位元組前三位為110,第二個位元組前兩位為10。
  • 當一個字元轉換成二進位制後,需要用12-17位表示時,這個字元編碼佔三個位元組,且第一個位元組前四位為,1110,後兩個位元組的前兩位都位10。
  • 當一個字元轉換成二進位制後,需要用更多位表示時,按照以上規律,進行編碼。

而通常情況下,我們並不需要心UTF-8的加密演算法,在我們儲存文字時候,選擇使用UTF-8即可。這是因為庫函式已經為我們做好了文字儲存編碼的事情。

自定義加密

按照以上分析,我們對一段字串進行自定義變換。
原始資料為:

{
"employees": [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName":"Carter" }
]
}

將原始字串自定義加密下。我們把所有的字元對於的值都左移位3位,然後加上106。這時,雖然我們的原始資料用的都是英文字元,但是因為進行了移位和加法運算,最終得到的值可能會超出char的取值範圍,所以我們直接用wchar_t來操作了。

void encode(const std::string &file){
    std::wofstream outfile(file, ios::out);
    outfile.imbue(locale("zh_CN.UTF-8"));
    std::wstring data = L"{\n"
                        "\"employees\": [\n"
                        "{ \"firstName\":\"Bill\" , \"lastName\":\"Gates\" },\n"
                        "{ \"firstName\":\"George\" , \"lastName\":\"Bush\" },\n"
                        "{ \"firstName\":\"Thomas\" , \"lastName\":\"Carter\" }\n"
                        "]\n"
                        "}";
    for (wchar_t chr : data) {
        auto dt = ((chr << 3) + 106);
        outfile.put(dt);
    }
    outfile.close();
}

經過變換後,得到加密後的字串:

тºźΒϒϪϊϢвΒΒЂźȺŪ͂ºтŪźΚβϺЂЊ˚ͲϒΒźȺźɺβϊϊźŪNJŪźϊͲЂЊ˚ͲϒΒźȺźʢͲЊΒЂźŪђNJºтŪźΚβϺЂЊ˚ͲϒΒźȺźʢΒϢϺ΢ΒźŪNJŪźϊͲЂЊ˚ͲϒΒźȺźɺВЂΪźŪђNJºтŪźΚβϺЂЊ˚ͲϒΒźȺź̊ΪϢϒͲЂźŪNJŪźϊͲЂЊ˚ͲϒΒźȺźʂͲϺЊΒϺźŪђº͒ºђ

這只是簡單的加密,為了更安全,可以採用更復雜一些的演算法。甚至在UTF-8編碼規範上做手腳,比如將所有的編碼位元組數擴充套件一位,存放一個隨機數,指向後續資料中的某位取反。等等,諸如此類的操作,只要保證操作可逆即可。

對應的解密

按照上述的加密演算法,做相應的解密。總的來說,就一個方法,取出儲存的字元,進行逆運算,減去106然後右移3位。因為存入文字檔案中的字元,進行了變換,在原有的字元上進行了左移位和加法,無法保證所有變換後的字元是單位元組字元。所以問題的重點其實是,要以多位元組字元的方式來進行運算。
知道問題關鍵點,程式碼上的實現就有很多種方式了,以下有三種示例。

按照UTF-8編碼規範來進行讀取

void translate1(const std::string &file){
    std::ifstream infile(file,ios::binary);
    //轉碼參考Utf-8編碼規範
    //如果字元頭9位是0,則用一個位元組表示,首位為0,後續7位保持不變
    //如果字元頭5位是0,則用兩個位元組表示,第一個位元組用110開頭,第二個位元組用10開頭
    //否則用三個位元組表示,首位元組用1110開頭,後面兩個位元組都用10開頭
    //轉碼如下:
    while(!infile.eof()){
        short num = 0;
        unsigned char tmp;

        infile.read(reinterpret_cast<char *>(&tmp), 1);

        if((tmp>>7) == 0){
            num = short(tmp&0x7f);
        }else if((tmp>>5) == 6){
            num = short(tmp & 0x1F);
            infile.read(reinterpret_cast<char *>(&tmp), 1);
            num = static_cast<short>((num << 6) | (tmp & 0x3F));
        }else if((tmp>>4) == 14){
            num = short(tmp & 0x0F);
            infile.read(reinterpret_cast<char *>(&tmp), 1);
            num = static_cast<short>((num << 6) | (tmp & 0x3f));
            infile.read(reinterpret_cast<char *>(&tmp), 1);
            num = static_cast<short>((num << 6) | (tmp & 0x3f));
        }else{
            break;
        }
        auto dt = static_cast<char>((num - 106) >> 3);
        std::cout<<dt;
    }
    std::cout<<endl;
}

藉助C++ 的codecvt中的轉換函式來進行讀取

void translate2(const std::string &file){
    std::ifstream infile(file,ios::binary);
    std::string data;
    infile>>data;
    std::wstring_convert<std::codecvt_utf8<wchar_t>> strCnv;
    wstring wss = strCnv.from_bytes(data);
    for (wchar_t ws : wss) {
        char d = static_cast<char>((ws - 106) >> 3);
        std::cout << d;
    }
    std::cout<<endl;
}

藉助wifstream來實現讀取

void translate3(const std::string &file){
    std::wifstream infile(file,ios::binary);
    infile.imbue(locale("zh_CN.UTF-8"));
    std::wstringstream wss;
    wss << infile.rdbuf();
    std::wstring ss = wss.str();
    for (wchar_t ws : ss) {
        char d = static_cast<char>((ws - 106) >> 3);
        std::cout << d;
    }
    std::cout<<endl;
}