Base64 就是一種基於 64 個可列印字符來表示二進位數據
Base64,就是說選出64個字符----小寫字母a-z、大寫字母A-Z、數字0-9、符號"+"、"/"(再加上作為墊字的"=",實際上是65個字符)作為一個基本字符集。然後,其他所有符號都轉換成這個字符集中的字符
-- 選自《阮一峰老師的博客》
Base 系列還有 16/32/62/64/85/36/58/91/92 等,分別表示用不同個數的可列印字符表示二進位數據
那麼為什麼會用 Base 系列算法?有的字符在一些環境中是不能顯示或使用的,比如 &, = 等字符在 URL 被保留為特殊作用的字符;
比如描述一張圖片,而圖片中的二進位碼如果轉成對應的字符的話,會有很多不可見字符和控制符(如換行、回車之類),這時就需要對進行編碼。
Base 系列的就是用來將字節編碼為 ASCII 中的可見字符的。
這個在之前也有一篇文章用來描述關於請求中傳遞驗證碼使用的就是base64
文末彩蛋 | 這個 Request URL 長得好不一樣
他的本質其實就是把原本不好顯示的字符切片分組後用好顯示的ascii碼來展示
今天就講講base64對字符做了啥?
第一種待轉換的字符串長度正好是 3 的整數倍
三個字符Man,轉換示意圖如下
1、把待轉換的字符串,分割成 3 個一組,並且轉換為共 24 個的二進位位2、將轉換好的二進位位再按照每 6 個一組整成 4 組4、將補成的 8 位的二進位數據轉化為 10 進位數5、將轉化為的 10 進位數對照 Base64 的碼錶注意轉化為碼錶中的字符,得出Base64的編碼如果一個編碼後的 base64 編碼是沒有等號填充的,那麼說明原字符的長度是 3 的整數倍
第二種待轉換的字符串長度正好比 3 的整數倍多 1 個字節或 2 個字節
像單個字符A,轉換示意圖如下
一個字節:一個字節共 8 個二進位位,依舊按照規則進行分組。此時共 8 個二進位位,每 6 個一組,則第二組缺少 4 位後面用 0 補齊得,得到兩個 Base64 編碼,而後面兩組沒有對應數據,都用 「 = 」 補上。像兩個字符AB,轉換示意圖如下
兩個字節:兩個字節共 16 個二進位位,依舊按照規則進行分組。此時總共 16 個二制位,每 6 個一組,則第三組缺少 2 位,用 0 補齊,得到三個 Base64 編碼,第四組完全沒有數據則用 「 = 」 補上。按照上面的算法,我們可以得到以下規律
如果後面填充一個=那麼原文長度是 3n + 2 的長度
如果後面填充的是兩個=,那麼原文長度是 3n + 1 長度
逆向反推快速識別知道原理之後,快速用代碼實現以下:
Python 無中文字符版:
import base64
str_encrypt="aaaa"
# 不要使用 base64.encodebytes(rq_time.encode()).decode()
base64_encrypt=base64.b64encode(str_encrypt.encode('utf-8')).decode()
print(base64_encrypt)Python 含中文字符版:
"""
base64實現
"""
import base64
import string
# base 字符集
base64_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
def encode(origin_bytes):
"""
將bytes類型編碼為base64
:param origin_bytes:需要編碼的bytes
:return:base64字符串
"""
# 將每一位bytes轉換為二進位字符串
base64_bytes = ['{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes]
resp = ''
nums = len(base64_bytes) // 3
remain = len(base64_bytes) % 3
integral_part = base64_bytes[0:3 * nums]
while integral_part:
# 取三個字節,以每6比特,轉換為4個整數
tmp_unit = ''.join(integral_part[0:3])
tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
# 取對應base64字符
resp += ''.join([base64_charset[i] for i in tmp_unit])
integral_part = integral_part[3:]
if remain:
# 補齊三個字節,每個字節補充 0000 0000
remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
# 取三個字節,以每6比特,轉換為4個整數
# 剩餘1位元組可構造2個base64字符,補充==;剩餘2位元組可構造3個base64字符,補充=
tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='
return resp
def decode(base64_str):
"""
解碼base64字符串
:param base64_str:base64字符串
:return:解碼後的bytearray;若入參不是合法base64字符串,返回空bytearray
"""
if not valid_base64_str(base64_str):
return bytearray()
# 對每一個base64字符取下標索引,並轉換為6為二進位字符串
base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if
s != '=']
resp = bytearray()
nums = len(base64_bytes) // 4
remain = len(base64_bytes) % 4
integral_part = base64_bytes[0:4 * nums]
while integral_part:
# 取4個6位base64字符,作為3個字節
tmp_unit = ''.join(integral_part[0:4])
tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]]
for i in tmp_unit:
resp.append(i)
integral_part = integral_part[4:]
if remain:
remain_part = ''.join(base64_bytes[nums * 4:])
tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)]
for i in tmp_unit:
resp.append(i)
return resp
def valid_base64_str(b_str):
"""
驗證是否為合法base64字符串
:param b_str: 待驗證的base64字符串
:return:是否合法
"""
if len(b_str) % 4:
return False
for m in b_str:
if m not in base64_charset:
return False
return True
if __name__ == '__main__':
s = '我的目標是星辰大海. One piece, all Blue'.encode()
local_base64 = encode(s)
print('使用本地base64加密:', local_base64)
b_base64 = base64.b64encode(s)
print('使用base64加密:', b_base64.decode())
print('使用本地base64解密:', decode(local_base64).decode())
print('使用base64解密:', base64.b64decode(b_base64).decode())Java 版快速實現:
import sun.misc.BASE64Encoder;
public class Base64Utils {
public static void main(String[] args) {
String man = "Man";
String a = "A";
String bc = "BC";
BASE64Encoder encoder = new BASE64Encoder();
System.out.println("Man base64結果為:" + encoder.encode(man.getBytes()));
System.out.println("BC base64結果為:" + encoder.encode(bc.getBytes()));
System.out.println("A base64結果為:" + encoder.encode(a.getBytes()));
}
}
這裡為什麼強調有中文版和無中文版的 base64 算法呢?因為按照上面的的示意圖可以知道,我們第一步是將代碼轉化為ascii碼之後再轉化為二進位的,但是ascii碼中並沒有包含中文
所以其他的字符集就需要統一轉化為二進位之後再分割,中文就是要同一編碼方式,例如同一使用utf-8字符集轉化為二進位之後分割
知道算法原理和規律有什麼用?知道算法原理和規律有助於我們快速識別這個編碼方式是不是簡單常用的算法,一定程度上減輕 app 逆向的工作量
我們主要需要快速識別下面幾個要素,就能幫助我們在判斷 APP 加密/編碼方式的時候減少工作量:
Love&Share
[ 完 ]
對了,看完記得一鍵四連,這個對我真的很重要。