base32编码必须将输入视为连续比特流而非字节流处理,严格按rfc 4648执行5位分组、末尾补零、大写字母表“abcdefghijklmnopqrstuvwxyz234567”及精确填充(余1→6个=等),任一偏差(如有符号char位运算、查表越界、填充错位)均导致与google authenticator等工具不互通。

Base32 编码不能靠 std::string 逐字节查表实现,必须把输入当比特流处理;解码也一样,查表越界、填充校验缺失、符号表不匹配,三者任一出错就会和 Google Authenticator、RFC 4648 工具互不通。
为什么 base32_encode("a") 返回 "ME======" 是错的?
这不是字符数对了就算对——"a" 是 1 字节(8 位),按 RFC 4648 要补 2 个 0 得到 10 位,拆成 2 组 5 位 → 输出 2 字符;但输出长度必须是 8 的倍数,所以补 6 个 '=',表面看是 "ME======"。问题在于:若中间用了有符号 char 右移(如 char c = '\xFF'; c >> 3),结果就是算术右移,高位填 1,整个比特流崩坏;更隐蔽的是,补零必须发生在比特串末尾,不是字节末尾,也不是字符串末尾。一旦把补零理解成“往 std::string 尾插 '0'”,就全错了。
- 所有输入字节必须先转
unsigned char,否则在 GCC 默认有符号char平台下,\xFF提升为负值,位运算行为未定义 - 补零是补“比特”(bit),不是补字符
'0'或字符串"0000" - 填充
'='是另一层规则:只出现在最终输出末尾,数量由input.size() % 5决定 —— 余 1 → 6 个,余 2 → 4 个,余 3 → 3 个,余 4 → 1 个,余 0 → 0 个
uint64_t 位缓冲 + 静态查表怎么写才不触发 realloc?
核心是用一个 uint64_t 当比特寄存器,高位对齐,每次取最高 5 位查表,左移腾空间。这样全程无 push_back、无 append、无临时 vector,避免多次内存重分配。
- 初始化
uint64_t buf = 0;,int bits = 0;记录当前缓存了多少位 - 每读一个
unsigned char b:执行buf |= static_cast<uint64_t>(b) ,然后 <code>bits += 8; - 只要
bits >= 5,就取低 5 位:out += table[buf & 0x1F]; buf >>= 5; bits -= 5; - 循环结束后,若
bits > 0,说明还有剩余位(不足 5 位),补 0 后再取低 5 位:out += table[buf & 0x1F]; - 查表必须用
static constexpr char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";,不能用std::string或std::vector
解码时 std::string::find 为什么容易崩溃?
因为没检查返回值是否为 std::string::npos。非法字符(如 '0'、'l'、小写字母)查表失败,find 返回极大值,直接当数组下标用就越界。
- 预处理必须剔除空白符,并拒绝任何不在
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="中的字符 - 填充符
'='只允许出现在末尾,且数量只能是 0/1/3/4/6 —— 对应输入长度模 5 余 0/4/2/1/3;中间出现'='直接返回错误 - 推荐用
std::array<int8_t></int8_t>做 O(1) 反向映射:初始化时把合法字符 ASCII 映射到 0–31,非法字符设为 -1,查表后先判idx == -1 - 输出 buffer 长度必须严格按
input.size() * 5 / 8计算(向下取整),不能靠reserve()估算
最易被忽略的点:编码时 std::string_view 看似安全,但如果传入的是 std::vector<char>::data()</char> 且 vector 在函数内 resize,data() 可能失效;解码时哪怕字符全合法,若输入长度不是 8 的倍数,也必须拒收 —— RFC 4648 明确要求 Base32 输出长度为 8 的倍数。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











