C语言版的Base-64加密解密函数
Encoding and decoding functions for RFC 4648 compliant base-64. The code is written in standard conforming C11 and backward compatible with C99 (pre-C99 is not supported without code changes).
#include <stdint.h> #include <stdlib.h> #include <string.h> #define BLOCK_BYTE 3 // Number of bytes in each base-64 24-bit block #define BLOCK_CHAR 4 // Number of base-64 characters in a 24-bit block #define BASE64_LINE_LEN 76 // Maximum line length of a base-64 string #define NEWLINE_LEN 2 // Number of characters in the newline sequence static const char *digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char *newline = "\r\n"; static const char padding = '='; static size_t digit_value(char ch); static void check_append_newline(char *dst, size_t *end); static void convert_block(char *dst, size_t *end, const uint8_t *src, size_t bytes); /* @description: Converts n bytes pointed to by src into base-64 representation. */ extern char *encode_base64(const void *src, size_t n) { size_t partial_block_bytes = n % BLOCK_BYTE; size_t full_blocks = n / BLOCK_BYTE; // Make room for all full blocks plus one extra for a partial block char *dst = malloc((full_blocks + 1) * BLOCK_CHAR + 1); if (!dst) return NULL; const uint8_t *bytes = src; size_t k; // Convert all complete 24-bit blocks to base-64 for (k = 0; full_blocks--; bytes += BLOCK_BYTE) { check_append_newline(dst + k, &k); convert_block(dst, &k, bytes, BLOCK_BYTE); } // Convert the final (possibly empty) partial block check_append_newline(dst + k, &k); convert_block(dst, &k, bytes, partial_block_bytes); dst[k] = '\0'; return dst; } /* @description: Converts a valid base-64 string into a decoded array of bytes and stores the number of decoded bytes in the object pointed to by n. */ extern void *decode_base64(const char *src, size_t *n) { size_t len = strlen(src); if (len % BLOCK_CHAR) return NULL; // Invalid number of base-64 characters // There will never be more bytes than base-64 characters by definition uint8_t *dst = malloc(len + 1); for (*n = 0; *src; src += BLOCK_CHAR) { if (*src == '\r') { ++src; // Skip the CR part of CRLF if (*src != '\n') { free(dst); return NULL; // Unmatched CR } ++src; // Skip the LF part of CRLF } size_t encoded[] = { digit_value(src[0]), digit_value(src[1]), digit_value(src[2]), digit_value(src[3]) }; // Check for invalid base-64 characters for (size_t i = 0; i < sizeof encoded / sizeof *encoded; ++i) { if (encoded[i] == (size_t)-1) { free(dst); return NULL; } } // Precompute the decoded bytes to faciliate handling of zero byte values uint8_t bytes[] = { (encoded[0] << 2) + ((encoded[1] & 0x30) >> 4), ((encoded[1] & 0x0f) << 4) + ((encoded[2] & 0x3c) >> 2), ((encoded[2] & 0x03) << 6) + encoded[3] }; // Push non-zero decoded bytes into the destination for (size_t i = 0; i < BLOCK_BYTE; ++i) { // Zero bytes should only occur for padding, which we don't save if (bytes[i]) dst[(*n)++] = bytes[i]; } } return dst; } /* @description: Locates the index value of a base-64 character for decoding. */ static size_t digit_value(char ch) { if (ch == padding) return 0; const char *p = strchr(digits, ch); return p ? p - digits : (size_t)-1; } /* @description: Appends a newline to dst if the value of end is at the correct position. Returns the updated value of end, which may not change if a newline was not appended. */ static void check_append_newline(char *dst, size_t *end) { if ((*end + 1) % BASE64_LINE_LEN == 0) { memcpy(dst, newline, NEWLINE_LEN); *end += NEWLINE_LEN; } } /* @description: Converts a 24-bit block represented by 3 octets into four base-64 characters and stores them in dst starting at end. Returns the updated value of end for convenience. */ static void convert_block(char *dst, size_t *end, const uint8_t *src, size_t bytes) { switch (bytes) { case 3: dst[(*end)++] = digits[src[0] >> 2]; dst[(*end)++] = digits[((src[0] & 0x03) << 4) | (src[1] >> 4)]; dst[(*end)++] = digits[((src[1] & 0x0f) << 2) | (src[2] >> 6)]; dst[(*end)++] = digits[src[2] & 0x3f]; break; case 2: dst[(*end)++] = digits[src[0] >> 2]; dst[(*end)++] = digits[((src[0] & 0x03) << 4) | (src[1] >> 4)]; dst[(*end)++] = digits[((src[1] & 0x0f) << 2)]; dst[(*end)++] = padding; break; case 1: dst[(*end)++] = digits[src[0] >> 2]; dst[(*end)++] = digits[((src[0] & 0x03) << 4)]; dst[(*end)++] = padding; dst[(*end)++] = padding; break; } } #define TEST_DRIVER #ifdef TEST_DRIVER #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { const char *test[] = {"", "f", "fo", "foo", "foob", "fooba", "foobar"}; size_t n; for (size_t i = 0; i < sizeof test / sizeof *test; ++i) { char *p = encode_base64(test[i], strlen(test[i])); char *q = decode_base64(p, &n); printf("BASE64(\"%s\") = \"%s\" = (%zd)\"%.*s\"\n", test[i], p, n, n, q); free(p); free(q); } return 0; } #endif