Base85 編碼簡介
Base85(也稱為 Ascii85)是一種二進制到文本的編碼方案,用于將二進制數據轉換為可打印的ASCII字符。它的效率高于Base64,但生成的字符串可能包含特殊字符(如引號或反斜杠),需在特定場景(如JSON)中謹慎使用。
編碼原理
Base85將每4個字節(32位)的二進制數據轉換為5個Base85字符。計算公式如下:
- 將4字節數據視為一個32位無符號整數(大端序)。
- 重復除以85,取余數作為Base85字符的索引值。
- 將索引映射到字符集(如
!
到u
)。
示例:
假設4字節數據為0x4A3B2C1D
,轉換步驟如下:
- 數值為
1,246,434,333
。 - 依次除以85:
1246434333 ÷ 85 = 14663933
,余數28
→ 字符<
(索引28)。14663933 ÷ 85 = 172517
,余數48
→ 字符o
(索引48)。- 繼續計算剩余字符。
常用字符集
不同實現可能使用不同字符集,常見兩種:
- RFC 1924版本:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu
- Adobe Ascii85:
- 字符范圍:
!
(33)到u
(117)。 - 特殊標記:
z
表示4字節全零,~~~~~
為數據流結束符。
- 字符范圍:
注意事項
- 數據對齊:輸入數據長度若非4字節倍數,需補零處理。
- 特殊字符:避免在XML/JSON中直接使用,需額外轉義。
- 效率權衡:Base85比Base64節省約1/4空間,但復雜度更高。
應用場景
- Adobe PDF:用于內嵌二進制數據(如字體)。
- 網絡傳輸:需要緊湊編碼的場景。
- Git存儲:Git的二進制補丁可能使用Base85。
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>#define BLOCK_SIZE 4
#define ENCODED_BLOCK_SIZE 5// Base85編碼函數
char *base85_encode(const uint8_t *data, size_t len) {if (data == NULL || len == 0) return NULL;// 計算輸出緩沖區大小(每4字節→5字符)size_t max_out_len = ((len + BLOCK_SIZE - 1) / BLOCK_SIZE) * ENCODED_BLOCK_SIZE + 1;char *output = malloc(max_out_len);if (!output) return NULL;size_t out_index = 0;uint32_t block = 0;for (size_t i = 0; i < len; i += BLOCK_SIZE) {size_t remaining = len - i;size_t block_len = (remaining < BLOCK_SIZE) ? remaining : BLOCK_SIZE;// 構建32位大端序塊block = 0;for (size_t j = 0; j < block_len; j++) {block |= (uint32_t)data[i + j] << (24 - 8 * j);}// 全零塊縮寫為'z'if (block_len == BLOCK_SIZE && block == 0) {output[out_index++] = 'z';continue;}// 計算5個Base85字符(從高位到低位)char encoded[ENCODED_BLOCK_SIZE];for (int j = ENCODED_BLOCK_SIZE - 1; j >= 0; j--) {encoded[j] = (block % 85) + '!';block /= 85;}// 根據實際字節數復制有效字符size_t chars_to_copy = block_len + 1; // 字節數+1for (size_t j = 0; j < chars_to_copy; j++) {output[out_index++] = encoded[j];}}output[out_index] = '\0';return output;
}// Base85解碼函數
uint8_t *base85_decode(const char *input, size_t *out_len) {if (input == NULL || out_len == NULL) return NULL;size_t in_len = strlen(input);if (in_len == 0) return NULL;// 計算最大輸出長度(每5字符→4字節)size_t max_out_len = (in_len * BLOCK_SIZE) / ENCODED_BLOCK_SIZE;uint8_t *output = malloc(max_out_len);if (!output) return NULL;size_t in_index = 0;size_t out_index = 0;uint32_t block = 0;while (in_index < in_len) {// 處理全零塊縮寫if (input[in_index] == 'z') {for (int j = 0; j < BLOCK_SIZE; j++) {output[out_index++] = 0;}in_index++;continue;}// 讀取5個字符(不足時用'u'填充)size_t chars_in_block = 0;block = 0;for (int j = 0; j < ENCODED_BLOCK_SIZE; j++) {if (in_index >= in_len) break;char c = input[in_index++];if (c < '!' || c > 'u') continue; // 跳過無效字符block = block * 85 + (c - '!');chars_in_block++;}// 填充不足的字符while (chars_in_block < ENCODED_BLOCK_SIZE) {block = block * 85 + ('u' - '!');chars_in_block++;}// 提取4個字節(大端序)size_t bytes_to_write = chars_in_block - 1; // 字符數-1=原始字節數for (int j = 0; j < bytes_to_write; j++) {output[out_index++] = (block >> (24 - 8 * j)) & 0xFF;}}*out_len = out_index;return output;
}// 測試函數
int main() {// 編碼測試uint8_t data[] = {0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0x00, 0x00};char *encoded = base85_encode(data, sizeof(data));printf("Encoded: %s\n", encoded); // 輸出: L/Ch[+>Gz// 解碼測試size_t decoded_len;uint8_t *decoded = base85_decode(encoded, &decoded_len);printf("Decoded: ");for (size_t i = 0; i < decoded_len; i++) {printf("%02X ", decoded[i]); // 輸出: 86 4F D2 6F B5 59 00 00}printf("\n");// 清理內存free(encoded);free(decoded);return 0;
}
base85的實現規則相對比較多,如果不替換原始字母表,建議使用openssl接口。