編碼、摘要和加密(一)——字節編碼

0. 前言

之因此取這個題目,是由於在面試的過程當中,許多求職者對問題「請列舉經常使用的加密算法」給出了比較廣泛的回答:「用過 MD5Base64 」,更有甚者說,「 Base64 是對稱加密, MD5 是非對稱加密」。那麼,經過接下來的三篇文章科普下,在編程過程當中常見的三個術語:字節編碼信息摘要數據加密git

1. 編碼介紹

在計算機領域,數據存儲單位叫字節——byte,最小的存儲單元的容量是1位-1bit。一個bit有兩個狀態 0 和 1。1byte = 8bit。一般,一個英文字母佔1字節,漢字採用GBK編碼時,佔用2字節。UTF-8是可變長度編碼,通常用 0-4 字節表示。github

以上介紹,僅侷限於計算機能夠顯示在屏幕上的字符。可是 1byte 一般可表示 256 個不一樣的數據。二進制表示:00000000-11111111,即 2^8 。根據 ASCII 中顯示,可見字符不足 100 個。若想完整的顯示 1byte 表示的所有內容,須要對其進行編碼。一般使用16進制的方式,0x00-0xFF。0x31 表示字符 '1' ,0x01 表示字母 1,0x41 表示 'A' ,0x61 表示 'a' 。再也不一一列舉,有興趣的小夥伴能夠查閱 ASCII 碼錶。面試

2. 十六進制編碼

2.1 概念

16進制編碼,是基於2進制轉換的過程。下表列舉些常見的數值編碼及其意義。算法

十進制 2進制 16進制 意義
0 00000000 0x00 null
1 00000001 0x01 1
49 00110001 0x31 '1'
65 01000001 0x41 'A'
97 01100001 0x61 'a'

此處,須要引入一個概念——基數。2 進制基數:0、1。10進制基數:0-9。16進制基數0-9,A-F。經過觀察表示一串內容的基數,能夠快速判斷它使用的編碼方式哦!編程

下表表示16進制基數與10進制、2進制的關係。均用 1byte 表示。數組

16進制 10進制 2進制
0x00 0 00000000
0x01 1 00000001
0x02 2 00000010
0x03 3 00000011
0x04 4 00000100
0x05 5 00000101
0x06 6 00000110
0x07 7 00000111
0x08 8 00001000
0x09 9 00001001
0x0A 10 00001010
0x0B 11 00001011
0x0C 12 00001100
0x0D 13 00001101
0x0E 14 00001110
0x0F 15 00001111

不難發現,16進制用 4bit 表示一個基數(16 = 2^4)。網絡

2.2 換算

將數字 100 轉成 16進製表示:app

計算方式比較簡單,對 100 用 16 進行取整取餘。發現 100 = 6 * 16 + 4。即,100 = 0x64。再轉成2進制,分別對 6 和 4 轉成 4bit 0和1 表示。0110 0100。編程語言

因此 100 = 0x64 = 01100100編碼

2.3 代碼實現

根據上一節的換算規則:

  1. 將字節數組轉16進制字符串,須要對每一個字節進行獨立運算。分別取高四位和第四位,而後轉成兩個10進制數做爲基數索引,最後組合成16進製表示。
  2. 將16進制字符串轉成字節數組,須要每兩個16進制基數一組。分別找出其表示的10進制數,而後作高四位和第四位相加。
/** * 16 進制基數 */
static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

/** * 編碼:字節數組轉 16 進制字符串 * * @param data * @return */
public static final String encode(byte[] data) {
    if (data == null)
        return null;

    StringBuffer hexSrtBuff = new StringBuffer();
    for (byte b : data) {
        int height = b >> 4 & 0x0f; // 取高四位
        int low = b & 0x0f;// 取低四位
        hexSrtBuff.append(hex[height]);
        hexSrtBuff.append(hex[low]);
    }
    return hexSrtBuff.toString();
}

/** * 解碼:16 進制字符串轉字節數組 * * @param hexStr * @return */
public static final byte[] decode(String hexStr) {
    if (hexStr == null)
        return null;
    if (hexStr.length() % 2 != 0) { // 不合法的十六進制字符串參數
        throw new IllegalArgumentException("The hex string was illegal");
    }
    byte[] data = new byte[hexStr.length() / 2];
    for (int i = 0; i < hexStr.length(); i += 2) {
        char h = hexStr.charAt(i);
        char l = hexStr.charAt(i + 1);
        int height = (h >= hex[10] && h <= hex[15]) ? (h - hex[10] + 10) : (h - hex[0]);
        int low = (l >= hex[10] && l <= hex[15]) ? (l - hex[10] + 10) : (l - hex[0]);
        data[i / 2] = (byte) ((height << 4) + (low & 0x0f));
    }
    return data;
}
複製代碼

3. Base64 編碼

上一節介紹了16進制的編碼規則和代碼實現。不難發現,作一次16進制編碼時候,所需的存儲空間翻一倍。這雖然方便計算機顯示,能夠用於網絡通訊,但耗費的存儲空間和傳輸效率都將減半。所以,base64編碼誕生。(樓主瞎編,base64編碼是否所以誕生,沒作考究)。

3.1 概念

base64 一共有 64 個基數。每一個基數佔 6bit(64 = 2^6)。

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

3.2 換算

根據上表的索引關係,試換算幾個案例。

  1. 字符 1

    10進製表示 49 。
    16進製表示 0x31。
    2進製表示 00110001。

    一共8位 ,不能被6位整除,所以不足位補 0。
    補充後12位 001100 010000。
    對應 base64 索引 12 16。
    base64 MQ==

    對補位的00,須要用 = 標記。

  2. 字符串 1A

    10進製表示 49 65。
    16進製表示 0x31 0x41。
    2進製表示 00110001 01000001。

    一共16位,不能被6位整除,所以不足位補 0。
    補充後18位 001100 010100 000100。
    對應 base64 索引 12 20 4。
    base64 MUE=

  3. 字符串 1Aa

    10進製表示 49 65 97。
    16進製表示 0x31 0x41 0x61。
    2進製表示 00110001 01000001 01100001。

    一共24位,能夠被6位整除,所以不需補位。
    劃分後24位 001100 010100 000101 100001。
    對應 base64 索引 12 20 5 33。
    base64 MUFh

根據上面的換算得出,base64 編碼後的字符串長度必定是4的整數倍。也許1 2 字節的數據使用 base64 編碼後並不能體現出它的優點。可是對100字節的數據編碼:

  1. 16進制編碼

    編碼後長度:100 * 2 = 200。

  2. base64 編碼

    編碼後長度 ⌈100 / 3⌉ * 4 = 34 *4 = 136。

3.3 代碼實現

考慮到補位場景,所以實現較爲複雜。可供編程語言入門時,練手使用。

/** * base 64 基數:26個大寫字母、26個小寫字母、10個阿拉伯數字、'+、'/' */
static char[] base64 = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G',
        'H', 'I', 'J', 'K', 'L', 'M', 'N',
        'O', 'P', 'Q', 'R', 'S', 'T',
        'U', 'V', 'W', 'X', 'Y', 'Z',
        'a', 'b', 'c', 'd', 'e', 'f', 'g',
        'h', 'i', 'j', 'k', 'l', 'm', 'n',
        'o', 'p', 'q', 'r', 's', 't',
        'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        , '+', '/'
};

/** * 編碼:字節數組轉 base64 * <p> * 字符 '1' 二進制:00110001 轉base64後 001100 010000 補充 兩個等號 * * @param data * @return */
public static final String encode(byte[] data) {
    if (data == null)
        return null;
    StringBuffer base64StrBuff = new StringBuffer();
    int leftBit = 0; //保存剩餘字節
    int index = 0;
    int num;
    for (index = 0; index < data.length; index++) {
        switch (index % 3) {
            case 0:
                num = data[index] >> 2 & 0x3f;  // 取前六位
                leftBit = data[index] & 0x03; // 保留後兩位
                base64StrBuff.append(base64[num]);
                break;
            case 1:
                num = (leftBit << 4 & 0x30) + (data[index] >> 4 & 0x0f); // 取前4位 與以前的後兩位相加
                leftBit = data[index] & 0x0f; // 保留後四位
                base64StrBuff.append(base64[num]);
                break;
            case 2:
                num = (leftBit << 2 & 0x3c) + (data[index] >> 6 & 0x03); // 取前兩位 與以前的後四位相加
                leftBit = data[index] & 0x3f;// 保留後六位
                base64StrBuff.append(base64[num]);
                base64StrBuff.append(base64[leftBit]);
                leftBit = 0;
                break;
        }
    }
    /** * 對剩餘位作補位處理,並用等號標記 */
    switch (index % 3) {
        case 0:
            break;
        case 1:
            base64StrBuff.append(base64[leftBit << 4 & 0x30]);
            base64StrBuff.append('=');
            base64StrBuff.append('=');
            break;
        case 2:
            base64StrBuff.append(base64[leftBit << 2 & 0x3c]);
            base64StrBuff.append('=');
            break;
    }

    return base64StrBuff.toString();
}

/** * 解碼: base64 轉字節數組 * * @param base64Str * @return */
public static final byte[] decode(String base64Str) {
    if (base64Str == null)
        return null;
    if (base64Str.length() % 4 != 0)
        throw new IllegalArgumentException("thr base64 string was illegal");
    // 檢查末尾等號的個數,
    int equalSignCount = 0;
    for (int i = base64Str.length() - 1; i > 0; i--) {
        if (base64Str.charAt(i) != '=') {
            break;
        }
        equalSignCount++;
    }
    // 轉成字節數組的長度
    int bytesLen = base64Str.length() / 4 * 3 - equalSignCount;
    byte[] data = new byte[bytesLen];
    int index = 0;
    for (int i = 0; i < base64Str.length(); i += 4) {
        // 四個字節一組處理,轉成三個字節
        int one = getCharIndex(base64Str.charAt(i));
        int two = getCharIndex(base64Str.charAt(i + 1));
        int three = getCharIndex(base64Str.charAt(i + 2));
        int four = getCharIndex(base64Str.charAt(i + 3));
        if (one < 0)
            break;
        int first = one << 2 & 0xfc;
        if (two < 0)
            break;
        first += (two >> 4 & 0x03);
        data[index++] = (byte) first;
        if (three < 0)
            break;
        int second = (two << 4 & 0xf0);
        second += (three >> 2 & 0x0f);
        data[index++] = (byte) second;
        if (four < 0)
            break;
        int third = (three << 6 & 0xc0);
        third += four;
        data[index++] = (byte) third;
    }
    return data;
}

/** * 尋找字符在字符串中的索引 * * @param c * @return */
private static int getCharIndex(char c) {
    if (c >= 'A' && c <= 'Z')
        return c - 'A';
    else if (c >= 'a' && c <= 'z')
        return c - 'a' + 26;
    else if (c >= '0' && c <= '9')
        return c - '0' + 52;
    else if (c == '+')
        return 62;
    else if (c == '/')
        return 63;
    else
        return -1;

}
複製代碼

4. 總結

雖然 16 進制和 Base64 編碼是可逆的,具有對應的解碼操做,但他們卻不是加密算法。加密算法通常須要使用密鑰,只有正確的密鑰,才能解密出正確的明文。

以爲有用?那打賞一個唄。我要打賞

此處是廣告Flueky的技術小站

相關文章
相關標籤/搜索