读取加密文件长度_【Python技术进阶11】使用Python读写文件(下)

二进制文件以字节(byte)流的方式写入或读取,不会对输入数据进行任何处理,因此对计算机友好。相比文本文件,可以有效的减少存储空间,也很适合网络传输。二进制文件一般被用于存储自定义格式的数据、视频、音频、图片、程序指令等等。

有了前面2篇文章的知识,对于二进制文件的相关知识,就很容易掌握。首先,飞哥会给小伙伴讲解读写操作的相关函数。最后,带领小伙伴完成一个使用二进制文件保存私密信息的实战案例。

01 读写二进制文件的函数

读写二进制文件的函数和读写文本文件的函数都是一样的,功能和用法也是一样,只是参数的含义有差别。但操作二进制文件的函数主要是下面3个:

1object = open(file, mode)

打开指定文件,返回文件对象。注意: mode参数中要多加一个b
# 写入模式打开二进制文件,文件不存在会先创建,文件存在则会空清空文件内容f = open("私密文件.bin", 'wb')# 追加模式打开二进制文件,文件不存在会先创建,文件存在不会空清空文件内容f = open("私密文件.bin", 'ab')# 读取模式打开二进制文件,文件不存在会报异常f = open("私密文件.bin", 'rb')# 读写模式打开二进制文件,文件不存在会先创建,文件存在则会空清空文件内容f = open("私密文件.bin", 'wb+')

2f.read (n=-1)

从文件中读取n个字节(byte)的数据。返回bytes类型的字节数据。

3f.write(s: Union[bytes,bytearray])

bytes或bytearray类型的字节数据写入到文件中。返回成功写入到文件中的数据的字节个数。

4f.close()  

关闭文件。

备注:有关bytes和字节数组(bytearray)的相关知识,在上一篇的文章《【Python技术进阶-10】Python数据类型bytes、bytearray详细指南》(点击左侧标题查看文章),有详细的讲解。

02 案例设计

6d7824f1522ee8eed3415b63a258d54a.png

1、需求

老王有一天突然找到飞哥说,“我电脑上有很多的私密照片,由于涉及到个人隐私。我可不想在网络上出名,听说你是搞IT的,有什么办法,给我处理一下吗?”

飞哥,“没问题,小菜一碟。给我半天时间,给你做一个老王专用的图片加解密软件。傻瓜式操作,点点鼠标就可以了。保证隐私安全,只有用我这软件,同时输入你的密码才能查看你的私密图片。”

老王,“太好了,真是没找错人,搞定后,请你吃大餐。”

2、设计说明

需求的核心是对私密图片进行加密处理,非常适合自定义一个二进制文件的数据格式,自定义数据结构示意图如上图所示。处理思路如下所示:

1)整个文件分为头部数据和图片加密数据;

2)读取原始图片数据,加密图片数据。然后生成头部数据,最后将这2部分数据存入新的二进制文件中;

3)查看私密图片时,先读取头部数据,获取到被加密图片的文件名称,加密数据的长度,然后进行数据的校验;

4)然后解密数据,获取到真实的图片数据;

5)使用图片库显示图片。

3、文件头部数据说明

1)2个字节的标识符,如0xF66F。当读取一个文件时,先读取2个字节的数据,进行比对,进行一个简单的判断,看是不是我们生成的二进制文件。

2)3个字节的版本号,如字符串2.1,2.2。主要为了以后自定义的二进制数据格式升级后,能够兼容以前的老文件。在处理时根据版本号,分别进行处理。

3)  记录文件创建时的日期,可以使用时间戳,也可以使用格式化后的时间字符串。先读取1个字节的数据,就知道日期数据占用几个字节,然后读取出日期字节数据,转换为真实的日期,就知道文件的创建日期。

4)  文件名,可以记录当时加密时的真实图片的名称。由于名称的长度也不确定,因此也需要1个字节,用于保存文件名称的长度。

4)  32位md5校验码,主要是为了避免文件被损坏或篡改,通过这个校验码就可以知道文件是否完整。版本号、创建日期、文件名、数据长度、原始私密照片的二进制数据,都要参与md5校验码的生成。

5)  4字节的数据长度,由于图片文件的长度是不确定的,所以加密后的图片数据的长度也是不确定的,所以需要用4个字节存储加密数据的字节长度。

03 案例实现

1、代码

# -*- coding:utf-8 -*-import osimport timeimport structfrom Crypto.Cipher import AESfrom Crypto import Randomimport hashlibdef add_16key(key):    """    将key的字节长度补齐为16字节的倍数    :param        key: 加解密时使用的字符串key    :return:        补齐后的bytes类型的key    """    key_bytes = bytes(key, encoding='utf-8')    count = len(key_bytes)    if count % AES.block_size > 0:        add_count = AES.block_size - (count % AES.block_size)        # 以补齐的字节个数作为填充        key_bytes = key_bytes + bytes([0]) * add_count    return key_bytesdef encrypt(key, data_bytes):    """    对传递来的data_bytes数据进行加密处理。    :param        key: 加密时使用的key        data_bytes: 被加密的数据,如果data_bytes不是16的倍数,需要进行处理补齐为16的倍数。    :return        返回加密后的bytes类型的数据    """    if len(data_bytes) == 0:        return data_bytes    key = add_16key(key)    iv = Random.new().read(AES.block_size)    cipher = AES.new(key, AES.MODE_CBC, iv)    # 这里密钥key 长度必须为16(AES-128), 24(AES-192),或者32 (AES-256)Bytes 长度    # 目前AES-128 足够目前使用    count = len(data_bytes)    add_count = 0    if count % AES.block_size > 0:        add_count = AES.block_size - (count % AES.block_size)        # 以补齐的字节个数作为填充        data_bytes = data_bytes + (bytes([add_count]) * add_count)        # 调用加密    crypt_data_bytes = cipher.encrypt(data_bytes)        # iv、填充个数和加密数据一起返回    return iv + bytes([add_count]) + crypt_data_bytesdef decrypt(key, crypt_bytes):    """    对加密数据crypt_bytes进行解密处理。    :param        key: 解密时用到的key。        crypt_bytes: 被加密的数据。    :return        解密后的bytes类型的原始数据。    """    if len(crypt_bytes) <= AES.block_size:        return ''        key = add_16key(key)    iv = crypt_bytes[:AES.block_size]    cipher = AES.new(key, AES.MODE_CBC, iv)    decrypt_bytes = cipher.decrypt(crypt_bytes[AES.block_size + 1:])        # 去掉填充的数据    add_count = crypt_bytes[AES.block_size]    if add_count > 0:        decrypt_bytes = decrypt_bytes[:-add_count]        return decrypt_bytesclass CryptoFile:    """    使用AES对文件进行加解密    """    def __init__(self, key):        self.flag = b'\xf6\x6f'        self.version = b'1.1'        self.key = key        def encrypt_file(self, src_path, dst_dir):        """        对文件加密处理,加密后的文件保存在指定的目录。        :param            src_path: 被加密的文件的路径        :param            dst_dir: 加密后的文件保存的目录        :return:            True=加密成功, False=加密失败        """        name = os.path.split(src_path)[1]                # 获取文件的加密内容        suc, data_bytes = self.get_file_encrypt_data(src_path)        if not suc:            return False                # 获取头部数据        header_bytes = self.get_header_bytes(name, data_bytes)        # 写目标文件        if not os.path.exists(dst_dir):            os.makedirs(dst_dir)        name_ext = os.path.splitext(name)        dst_path = os.path.join(dst_dir, name_ext[0] + '_1' + name_ext[1])        with open(dst_path, 'wb') as f:            f.write(header_bytes)            f.write(data_bytes)                    return True                def decrypt_file(self, src_path, dst_dir):        """        对加密文件进行解密处理,并将解密后的文件保存在指定的目录        :param            src_path: 加密文件的路径        :param            dst_dir: 解密后的文件保存的目录        :return:            True=解密成功, False=解密失败        """        suc, file_name, file_bytes = self.get_file_decrypt_bytes(src_path)        if not suc:            return False                # 保存文件        if not os.path.exists(dst_dir):            os.makedirs(dst_dir)        dst_path = os.path.join(dst_dir, file_name)        with open(dst_path, 'wb') as f:            f.write(file_bytes)                    return True            def get_file_decrypt_bytes(self, file_path):        # 读取文件内容        data = None        with open(file_path, 'rb') as f:            data = f.read()        if data is None:            return False, None, None                # 判断其实标志        if not data.startswith(self.flag):            return False, None, None                # 版本号        index = 0        version = data[2:5].decode(encoding='utf-8')        index = 5                # 计算文件中的md5        md5 = hashlib.md5()        md5.update(data[:5])                # 时间信息        field_byte_length = data[index]        index += 1        create_times = struct.unpack(', data[index:index+field_byte_length])[0]        md5.update(data[index:index+field_byte_length])        str_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(create_times))        index += field_byte_length                # 文件名长度        field_byte_length = data[index]        index += 1        src_name = data[index:index+field_byte_length].decode(encoding='utf-8')        md5.update(data[index:index+field_byte_length])        index += field_byte_length                # 校验码        field_byte_length = 32        md5_code = data[index:index+field_byte_length].decode(encoding='utf-8')        index += field_byte_length                # 加密数据的长度        field_byte_length = 4        encrypt_data_length = struct.unpack(', data[index:index+field_byte_length])[0]        md5.update(data[index:index+field_byte_length])        index += field_byte_length                # 加密数据        md5.update(data[index:index + encrypt_data_length])                # 计算新的md5,然后进行对比        new_md5_code = md5.hexdigest()        if md5_code != new_md5_code:            return False, None, None        # 解密密文件内容        return True, src_name, decrypt(self.key, data[index:index + encrypt_data_length])    def get_file_encrypt_data(self, file_path):        # 读取文件内容        data = None        with open(file_path, 'rb') as f:            data = f.read()        if data is None:            return False, None            # 加密文件内容        return True, encrypt(self.key, data)        def get_header_bytes(self, name, data_bytes):        md5 = hashlib.md5()        header_data = bytearray()        header_data.extend(self.flag)        header_data.extend(self.version)        md5.update(header_data)                now = time.time()        now_bytes = struct.pack(', now)        # 日期长度        header_data.extend(struct.pack(', len(now_bytes)))        # 日期数据        header_data.extend(now_bytes)        md5.update(now_bytes)                # 文件名称        name_bytes = name.encode(encoding='utf-8')        header_data.extend(struct.pack(', len(name_bytes)))        header_data.extend(name_bytes)        md5.update(name_bytes)                # 数据长度(4个字节)        data_length_bytes = struct.pack(', len(data_bytes))        md5.update(data_length_bytes)        md5.update(data_bytes)                # 计算校验码        md5_code = md5.hexdigest()        header_data.extend(md5_code.encode(encoding='utf-8'))        header_data.extend(data_length_bytes)                return header_dataif __name__ == '__main__':    # 生成对象时,要传入一个key(密码)    crypto = CryptoFile("python is good")    suc = crypto.encrypt_file("车展.jpg", "加密测试")    print("加密结果:", suc)    suc = crypto.decrypt_file("加密测试\\车展_1.jpg", "解密测试")    print("解密结果:", suc)        # encrypt_data = encrypt("123456", b'asfasdee')    # print(encrypt_data)    # decrypt_data = decrypt("123456", encrypt_data)    # print(decrypt_data)

2、效果

废话不多说,直接看加解密效果图。

92760efe648be12429ab7937952c00a4.png

加密后的二进制文件,使用十六进制查看软件打开,可以看到文件开头的2个标记字节。标记后面的3个字节是版本号,在这一行的右侧可以看见字符1.1

5041bbae7c32783f0d07e8cd42eec23a.png

好了,今天的内容就分享到这里了,后面的案例代码,小伙伴可以直接把代码拷贝到自己的PyCharm上运行。如有不清楚的或疑问,可以私信飞哥交流。

Python新手入门、进阶问题,欢迎私信交流,看到就回复。

END 2c62aed1525efb864696c84cf4f0d802.gif 扫码关注我们 64ff372075fe0ea332802d19b235892f.png 专业提供 定制学习计划和职业规划服务 公众号:Python编程研习社 2c62aed1525efb864696c84cf4f0d802.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值