Base64编码方式全解析——二进制可视化
一、open的rb,wb用二进制方式读取工作簿DB_Parsed_Result_Communal.xlsx原始字节序列然后用二进制方式将该字节序列另存为写入工作簿DB_Parsed_Result_Communal111.xlsx。# 1、二进制方式读取文件 flp rD:\... ...\DB_Parsed_Result_Communal.xlsx with open(flp, rb) as f: fl_content f.read() # class bytes # 2、二进制方式写入文件 new_flp rD:\... ...\DB_Parsed_Result_Communal111.xlsx with open(new_flp, wb) as new_f: new_content new_f.write(fl_content) # 通常是字节长度 # 3、with用于上下文管理自动使用close()对操作结束的文件执行关闭 f open(path, rb) try: data f.read() finally: f.close()这两个工作簿在大小和内容上是完全一致的类似于shutil.copy()或copy2()方法对文件进行复制区别就是read()会将整个文件从磁盘加载到内存内存占用高。模式读取/写入类型文件不存在时已存在时典型用途r文本 str报错 FileNotFoundError从开头读取读 txt、json、csv 文本w文本 str自动创建清空原内容后写入写文本文件rb二进制 bytes报错 FileNotFoundError从开头读取读 xlsx、图片、zip、exe等非文本文件wb二进制 bytes自动创建清空原内容后写入写 xlsx、图片、zip、exe等非文本文件二、BASE64编码规则Base64顾名思义就是包括小写字母a-z、大写字母A-Z、数字0-9、符号、/一共64个字符的字符集另加一个“”实际是65个字符。如果出现“”通常表示当前base64字节序列结束和补位padding它是在base64编码时自动生成的其自身不表示任何实质意义具体作用在后面会讲解。任何符号都可以转换成这个字符集中的字符这个转换过程就叫做base64编码且转换后的字符恰好为ASCII字符因此各编辑器都可以显示由Base64规则转换后的文本文件绝大多数编码规则都向下兼容ASCII编码。如何将任何字符文件等转换为Base64字符集中的字符首先字符串文件等等在磁盘中本质上都是一串字节序列只是它们各自打开方式或编辑器认得这些字节序列从而显示在应用端供我们查看或使用比如excel表格文件它从磁盘中调入内存的过程就是将二进制序列通过excel的应用软件读取转换后显示在我们面前的样子。将这些有限长度的字节序列转换为二进制后将二进制序列按每6个二进制位为一组分成若干组然后每组高位补0x00如果依然不足8位再在低位补0直至达到8位。此时每组变成了新的二进制序列最后根据base64索引表中的数值找到对应的字符替换对应字节组的内容便将文件转换为了可见的字符串这些base64字符切好也是unicode字符转换后的各字符码点即ASCII字符码点。由此可见Base64编码不仅可以将字符串文本文件的内容进行转换所有可以rb读取到自身二进制序列的文件都可以被文本化就像是把一个图片甚至是可执行程序进行加密了一样打开后是一长串的ASCII字符。举几个例子3个元素的字节串——4个元素的base64字节串验证c babc d b64encode(c) print(c) print(c.decode(utf-8)) # gbk和utf-8向下兼容ascii结果bYWJj YWJj2个元素的字节串——3个元素的base64字节串验证c bab d b64encode(c) print(d) print(d.decode(utf-8)) # gbk和utf-8向下兼容ascii结果bYWI YWI由此可见原本占3个字节的内容通过Base64编码后变成了4字节数据膨胀了而且不足3字节只有2个字节时按照规则只会产生3个字节的Base64字符但是最终结果会补齐(padding)b达成4字节以此类推1个元素的字节串——2个元素的Base64字节串最终结果会补齐2个b依旧达成4字节。验证c ba d b64encode(c) print(d) print(d.decode(utf-8)) # gbk和utf-8向下兼容ascii结果bYQ YQ所以不论字符串文件等的原始字节序列是多长进行Base64编码规则的转换后字节长度都会变成4的倍数。如何解码被Base64编码的字符串文件使用Base64后可将原文件的二进制序列可视化手动解码的方式为在Base64字符集中查找各字符对应的码点将码点值全部展开为6位二进制码点值最大为63然后从头开始以8位为一组组成解码后的字节。以解码字符串YQ为例base64字符 Y Q base64字符码点 24 16 6位二进制码点 01 1000 01 0000 8位为一组 0110 0001 0000 ascii编码 6 1 原始字符 a验证e YQ f b64decode(e.encode(utf-8)) print(f)结果ba相反的解码4个Base64字符可以得到3个字节的字节序列解码3个Base64字符不包含padding字符“”可以得到2个字节的字节序列解码2个Base64字符不包含padding字符“”可以得到1个字节的字节序列解码时base64字符的作用两大主要作用1、b64encode后如果Base64字符串长度不是4的倍数作为补充字符被添加到末尾直至使字符长度为4的倍数。2、b64decode时作为当前Base64字符decode的结束标志如果没有则说明Base64字符直至的长度刚好是64位否则当字符流结束后将提示“Incorrect padding”错误。作用1已经在之前讲解转换为Base64字符串的方法时阐明了。验证作用2# 1、获取内容的原始字节序列 a 你好!.encode(utf-8) print(a) # 2、编码字节系列后手动增加新的字节序列 b64_bytes b64encode(a) print(f{b64_bytes}) with open(base64.b64, wb) as f: f.write(b64_bytes) f.write(b\x65\x65) # 3、解码 with open(base64.b64, rb) as f: data f.read() print(len(data)) print(data) print(b64decode(data), b64decode(data, validateFalse).decode(utf, errorsreplace))结果b\xe4\xbd\xa0\xe5\xa5\xbd! b5L2g5aW9IQ 14 b5L2g5aW9IQee b\xe4\xbd\xa0\xe5\xa5\xbd! 你好!解释原理将你好!进行Base64编码后得到Base64字符串5L2g5aW9IQ长度刚好是124的倍数可以按照编码规则手动验证一下编码结果中文字符的UTF-8字节序列长度为3!作为ASCII字符经过UTD-8编码后的字节序列长度为1所以是12 4 4 2 2外加2个padding字符。此时在编码结果末尾补充两个字节b\x65\x65Base64字符串长度变为14按照解码规则Base64字符串长度必须为4的倍数才能被解码但是此时继续b64decode是没有问题的且发现b64decode解码结果与字符串的UTF-8编码结果一致b\xe4\xbd\xa0\xe5\xa5\xbd!得到的解码结果字节序列继续通过UTF-8解码为字符串最终与UTF-8编码前一致你好!。说明在解码一个连续的Base64字符串时一旦遇到符合Base64编码规则规定数量的padding字符就会自动截断不再执行后续字符的解码即解码器遍历到b时发现b恰好和前面的b5L2g5aW9IQ长度相加为12便将后面的bee被舍弃。什么叫符合Base64编码规则规定数量的padding字符将上述验证代码的第二步做如下修改# 2、编码字节系列后手动增加新的字节序列 b64_bytes b64encode(a) print(f{b64_bytes}) with open(base64.b64, wb) as f: f.write(b64_bytes.rstrip(b)) f.write(b\x65\x65)结果b\xe4\xbd\xa0\xe5\xa5\xbd! b5L2g5aW9IQ 13 b5L2g5aW9IQee b\xe4\xbd\xa0\xe5\xa5\xbd!\x07 你好!手动删除编码结果末尾的padding字符.rstrip(b)再手动塞入字节序列b\x65\x65此时对b5L2g5aW9IQee进行b64decode解码解码前和解码后的字节序列有如下对应关系解码前b5L2g5aW9IQee 解码后b\xe4\xbd\xa0\xe5\xa5\xbd!\x07 b5L2g——b\xe4\xbd\xa0 b5aW9——b\xe5\xa5\xbd bIQe——b!\x07 be——被舍弃b64decode时参数validate的作用1、validateFalse时解码过程中会将Base64字节序列中不属于Base64字符的字节自动剔除且自动在满足padding数量的位置作截断舍弃掉之后的字节串不做解码对被解码字节序列的格式要求低更灵活。验证a 你好!.encode(utf-8) print(a) b64_bytes b64encode(a) print(f{b64_bytes}) with open(base64.b64, wb) as f: f.write(b64_bytes.rstrip(b)) f.write(b\x80\x65\x65) with open(base64.b64, rb) as f: data f.read() print(len(data)) print(data) print(b64decode(data), b64decode(data, validateFalse).decode(utf, errorsreplace))结果b\xe4\xbd\xa0\xe5\xa5\xbd! b5L2g5aW9IQ 13 b5L2g5aW9IQ\x80ee b\xe4\xbd\xa0\xe5\xa5\xbd!\x07\x9e 你好!将b5L2g5aW9IQ\x80ee进行b64decode其中b\x80不是Base64字符得到b\xe4\xbd\xa0\xe5\xa5\xbd!\x07\x9e如果b\x80会被踢出那么剔除后的b5L2g5aW9IQee也会得到相同的解码结果。验证print(b64decode(b5L2g5aW9IQee))结果b\xe4\xbd\xa0\xe5\xa5\xbd!\x07\x9e2、validateTrue时要求被解码的字节序列必须是标准的base64字节串且字节序列的结束必须干净没有多余内容对格式要求更加严苛。如作为本段base64字节序列结束标志的padding字符后不能出现其他字符即使满足padding数量规则、b5L2g5aW9IQe ❌ binascii.Error: Excess data after padding如base64字符串中不能出现非base64字符、b5L2g5aW9IQ\x80ee ❌ binascii.Error: Only base64 data is allowed如不能出现非连续padding字符。b5L2g5aW9IQe ❌ binascii.Error: Discontinuous padding not allowed以上错误在validateFalse时都不会出现字节串可以被正常解码。