0. 前言
之所以取這個題目,是因為在面試的過程中,許多求職者對問題“請列舉常用的加密演算法”給出了比較普遍的回答:“用過 MD5
和 Base64
”,更有甚者說,“ Base64
是對稱加密, MD5
是非對稱加密”。那麼,通過接下來的三篇文章科普下,在程式設計過程中常見的三個術語:位元組編碼 、資料摘要、 報文加密。
1. 編碼介紹
在計算機領域,資料儲存單位叫位元組——byte,最小的儲存單元的容量是1位-1bit。一個bit有兩個狀態 0 和 1。1byte = 8bit。通常,一個英文字母佔1位元組,漢字採用GBK編碼時,佔用2位元組。UTF-8是可變長度編碼,一般用 0-4 位元組表示。
以上介紹,僅侷限於計算機可以顯示在螢幕上的字元。但是 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進製表示:
計算方式比較簡單,對 100 用 16 進行取整取餘。發現 100 = 6 * 16 + 4。即,100 = 0x64。再轉成2進位制,分別對 6 和 4 轉成 4bit 0和1 表示。0110 0100。
所以 100 = 0x64 = 01100100
2.3 程式碼實現
根據上一節的換算規則:
- 將位元組陣列轉16進位制字串,需要對每個位元組進行獨立運算。分別取高四位和第四位,然後轉成兩個10進位制數作為基數索引,最後組合成16進製表示。
- 將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
10進製表示 49 。
16進製表示 0x31。
2進製表示 00110001。一共8位 ,不能被6位整除,因此不足位補 0。
補充後12位 001100 010000。
對應 base64 索引 12 16。
base64 MQ==對補位的00,需要用 = 標記。
-
字串 1A
10進製表示 49 65。
16進製表示 0x31 0x41。
2進製表示 00110001 01000001。一共16位,不能被6位整除,因此不足位補 0。
補充後18位 001100 010100 000100。
對應 base64 索引 12 20 4。
base64 MUE= -
字串 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位元組的資料編碼:
-
16進位制編碼
編碼後長度:100 * 2 = 200。
-
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;
}
複製程式碼
覺得有用?那打賞一個唄。我要打賞
此處是廣告:Flueky的技術小站