python之常用模块二(hashlib logging configparser)

一、hashlib模块

1、介绍

Python的hashlib提供了常见的摘要算法,如MD5、SHA1、SHA256、SHA512等等
摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。 摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过

特点:

  • 这个算法是不可逆,就是说经过这个算法处理后的保存数据,不能还原为原来的数据
  • 不同的字符串通过这个算法的计算得到的密文总是不同的
  • 即使是不同的语言不同的环境(操作系统、版本、时间)只要是用相同的算法处理相同的字符串,那么获得的结果总是相同的。

注意:

摘要算法不是加密算法,因为摘要算法是不可逆的,无法通过摘要反推出原数据,因此不能用于加密,只能用于防篡改。 

摘要算法主要有:SHA-1、SHA-256、MD5 等。

常见的加密算法有:
    对称加密算法主要有:AES、DES、3DES等
    非对称算法主要有: RSA、DSA、ECC等

 

md5算法 :32位16进制的数字字符组成的字符串

    应用最广泛的摘要算法

    效率高,相对不复杂,如果只是传统摘要不安全

 

sha算法 :40位的16进制的数字字符组成的字符串

    sha算法要比md5算法更复杂

    且shan n的数字越大算法越复杂,耗时越久,结果越长,越安全

 

主要应用场景:确认通过网络传输的数据是否完整(如网上下载安装包等)

 

2、MD5

MD5算法:速度很快,生成的结果是固定的长度(128 bit(128位二进制),用32位的16进制字符串表示)

例1:计算出一个字符串的MD5值

import hashlib  # 导入hashlib模块

md5_obj = hashlib.md5()  # 实例化一个md5算法对象
md5_obj.update('hello,world!'.encode('utf-8'))  # update里面是要处理的数据(但必须是bytes类型)
ret = md5_obj.hexdigest()  # 调用hexdigest生成结果
print(ret)  # 结果:c0e84e870874dd37ed0d164c7986f03a

 

例2:如果数据量很大,可以分块多次调用update()

import hashlib

md5_obj = hashlib.md5()
md5_obj.update('hello,'.encode('utf-8'))
md5_obj.update('world!'.encode('utf-8'))
ret = md5_obj.hexdigest()
print(ret)  # 结果:c0e84e870874dd37ed0d164c7986f03a

 

例3:登录验证

说明:

正常场景下,对密码的加密应该用我上面说的那几种对称加密或者非对称加密算法。

我这里只是为了演示,采用了摘要算法。

试想如果用户的账号密码等信息都是以明文的形式存储在数据库中,如果数据库泄露,

那么用户的密码立刻就泄露了,只要截取到你数据库信息的人都能登录你所有的用户,

这样以明文的形式存储密码是极其不安全的,所以正确的存储方式是摘要的形式存储,

比如MD5、SHA1等。

原数据库:

xiaoming | 123456

zhangsan | 666

wangxiaoer | 888

(这样的话泄露数据库就把所有信息都泄露了)

 

MD5:

xiaoming | e10adc3949ba59abbe56e057f20f883e

zhangsan | fae0b27c451c728867a567e8c1bb4e53

wangxiaoer | 0a113ef6b61820daa5611c870ed8d5ee

(这样存储的话,即使数据库泄露了,也没人知道是什么密码)

复制代码
"""
那么用户登录的时候,只需要把用户输入的密码进行MD5算法,再与存储的MD5值比较,
相同的就登录成功
"""
import hashlib


def get_md5(s):
    md5_obj = hashlib.md5()
    md5_obj.update(s.encode('utf-8'))
    return md5_obj.hexdigest()


def login():
    username = input('>>>:')
    passwd = input('>>>:')
    with open('user_info.txt', encoding='utf-8', mode='r') as f:
        for line in f:
            usr, pwd = line.strip().split('|')
            if username == usr and get_md5(passwd) == pwd:
                print('登录成功!')
                break
        else:
            print('登录失败!')


login()
复制代码

 

例4、加盐

上述的方法是不是看起来很完美了?还是太年轻了,虽然MD5算法是不可逆的,但是,很多用户喜欢用123456,888888等

简单的密码,于是,黑客可以事先计算出一些常用密码的MD5值,得到一个反推表:

'e10adc3949ba59abbe56e057f20f883e':'123456'

'fae0b27c451c728867a567e8c1bb4e53':'666'

'0a113ef6b61820daa5611c870ed8d5ee':'888'

这样,无需破解,黑客只需要对比他们制作的MD5数据库,就可以破解一些常用的密码的用户账号,俗称撞库。

那么如何加强对密码的保护呢?可以使用‘加盐的方法’

什么是加盐?

就是在对密码进行MD5算法的时候,为密码增加一个复杂的字符串,形成新的MD5值,那样即使是使用简单的密码,

只要这个'盐'的值不被黑客知道,那么他们也是破解不了密码的。比如:

复制代码
"""
原始的123456的MD5值是:e10adc3949ba59abbe56e057f20f883e
用户在登录验证的时候,只要我们的程序默认把'盐'的值加进去进行验证,那么用户还是只需要输入123456就可以正常登录。
"""
import hashlib

md5_obj = hashlib.md5('密码保护'.encode('utf-8'))
md5_obj.update('123456'.encode('utf-8'))
ret = md5_obj.hexdigest()
print(ret)  # 88e7b7b01cb04043824d5c65f9455dd1(这个值的明文就相当于:'密码保护123456')
复制代码

 

例5、动态加盐

有了加盐之后,是不是觉得又完美了?哎,还是年轻啊,虽然加盐后的密码很难破解了,但是如果有人获取了你的数据库,

然后去你的网站恶意注册账号,注册的密码是那些简单的密码(123456,666,999等),当他注册了很多这样的账号后,

他可以自己加盐去尝试,如果加了某个盐后的密码123456跟他窃取的数据库上的某个记录一致,那么这个'盐'就被知道了,

那么他就可以把所有简单的密码又给破解了。

所以这个时候就需要用到动态加盐,每个密码所加的盐都不是固定的(可以为密码加上用户名,或者用户名的第一个字符,最后一个字符等)

看看例子吧:

import hashlib

username = input('>>>:')
passwd = input('>>>:')
md5_obj = hashlib.md5(username.encode('utf-8'))
md5_obj.update(passwd.encode('utf-8'))
ret = md5_obj.hexdigest()
print(ret)

 

例6、文件的一致性校验

我们在网上下载文件(视频、安装包等)的时候,如果下载的文件不完整,就无法使用,而我们往往下载好一个文件的时候,电脑都会自动给我们

检查下载的文件是否完整,那么判断下载的文件是否这个步骤是怎么执行的呢?

其实要下载的文件都会有一个原文件的MD5值,当我们把文件下载到电脑后,系统会帮我们计算文件的MD5,

然后对比原文件的MD5值,如果MD5不一致,就证明下载的文件不完整(丢帧)。

(校验文件的MD5就不需要加盐了,因为本身就只是判断文件是否完整,就是公开的值)

我们这里看看如何计算一个文件的MD5值:

复制代码
import os
import hashlib

file_path = r'你下载好的文件的路径'


def get_file_md5(file_path, buffer=1024):  # buffer:默认每次读取的大小
    md5_obj = hashlib.md5()
    file_size = os.path.getsize(file_path)  # 文件的大小
    with open(file_path, mode='rb') as f:
        while file_size:
            content = f.read(buffer)
            file_size -= len(content)
            md5_obj.update(content)
    return md5_obj.hexdigest()


print(get_file_md5(file_path))
复制代码

 

2、sha算法

定义:SHA算法是一个系列的算法,有SHA1、SHA224、SHA256、SHA384、SHA512等,

使用较多的是SHA1,SHA1的结果是160 bit,通常用一个40位的16进制字符串表示。

SHA1、SHA224、SHA256、SHA384、SHA512等都比SHA1更安全,不过越安全的算法越慢,而且摘要长度更长。

import hashlib

# SHA的调用和MD5完全类似
sha_obj = hashlib.sha1()
sha_obj.update('hello,world!'.encode('utf-8'))
ret = sha_obj.hexdigest()
print(ret)  # 4518135c05e0706c0a34168996517bb3f28d94b5

 

二、logging模块

1、说明

正规的开发所有的程序都必须记录日志。

日志的作用:

1.给用户看的,例如:

    购物软件

    视频软件

    银行卡

2.给内部人员看的

    2.1给技术人员看的

        计算器

        500个小表达式

        一些计算过程、或者是一些操作过程需要记录下来

        程序出现bug的时候 来帮助我们记录过程 排除错误

    2.2给非技术人员看的

        学校、公司的软件

            谁在什么时候做了什么事儿,增删改查操作(尤其是删除操作)

 

2、logging的级别

import logging

logging.debug('debug message')  # 计算或者工作的细节
logging.info('info message')  # 记录一些用户的增删改查的操作
logging.warning('warning operation')  # 警告操作
logging.error('error message')  # 错误操作
logging.critical('critical message')  # 批判的 直接导致程序出错退出的

结果:

WARNING:root:warning operation

ERROR:root:error message

CRITICAL:root:critical message

 

从上面可以看出:

默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于warning级别的日志,

这说明默认的日志级别设置为WARNING(日志级别等级critical > error > warning > info > debug),

默认的日志格式为日志级别:level级别:用户:输出消息。

 

3、简单配置

logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为

复制代码
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M',  # 把asctime的时间格式改为:年-月-日 时:分
                    filename='test.log',
                    filemode='w')

# 简单配置中文显示的是乱码
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
复制代码

参数说明:

level:设置logger的日志级别(大于等于这个参数的级别都会显示)

format:指定handler使用的日志显示格式。

datefmt:指定日期时间格式。

filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。

filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。

stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),

默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

 

format参数中可能用到的格式化串:

%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒(可用datefmt修改默认日期时间格式。)

%(filename)s 调用日志输出函数的模块的文件名

%(lineno)d 调用日志输出函数的语句所在的代码行

%(levelname)s 日志级别

%(message)s用户输出的消息

%(name)s 用户的名字

 

%(levelno)s 数字形式的日志级别

%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有

%(module)s 调用日志输出函数的模块名

%(funcName)s 调用日志输出函数的函数名

%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示

%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数

%(thread)d 线程ID。可能没有

%(threadName)s 线程名。可能没有

%(process)d 进程ID。可能没有

 

4、对象的配置

解决中文问题、同时向文件和屏幕输出内容

复制代码
import logging

# 先创建一个log对象 logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)  # 设置显示级别

# 还要创建一个控制文件输出的文件操作符
fh = logging.FileHandler('mylog.log', encoding='utf-8')  # 指定编码可以解决中文乱码问题

# 还要创建一个控制屏幕输出的屏幕操作符
sh = logging.StreamHandler()

# 要创建一个格式(多个格式也可以)
fmt = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s [line:%(lineno)d]')

# 文件操作符绑定一个格式
fh.setFormatter(fmt)
# fh.setLevel(logging.WARNING)  # 可以设置写入文件的级别,不设置的话就默认用log对象设置的级别(一般全部级别的信息都写进去)

# 屏幕操作符绑定一个格式
sh.setFormatter(fmt)
sh.setLevel(logging.WARNING)  # 可以设置屏幕打印的级别,不设置的话就默认用log对象设置的级别
# 但是不能设置比log对象设置的级别低,否则只会执行log对象的级别,比如:log对象设置的级别是WARNING,你这里设置INFO,级别
# 没有WARNING高,不会显示

# logger对象来绑定:文件操作符, 屏幕操作符
logger.addHandler(sh)
logger.addHandler(fh)

logger.debug('debug message')  # 计算或者工作的细节
logger.info('info message')  # 记录一些用户的增删改查的操作
logger.warning('warning message')  # 警告操作
logger.error('error message')  # 错误操作
logger.critical('critical message')  # 批判的 直接导致程序出错退出的
复制代码

 

三、configparser模块

配置文件模块,该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值)

1、创建配置文件

复制代码
import configparser

config = configparser.ConfigParser()  # 实例化一个对象

# 中括号里面的是组名,后面的值是字典,[DEFAULT]组可用被其他组调用
config["DEFAULT"] = {
    'ServerAliveInterval': '50',
    'Compression': 'hello',  # 字典里面的字母,写入配置文件后均变为小写字母
}

config['bit.org'] = {'User': 'Big_rookie'}
config['topsecret.server.com'] = {'Host Port': '8080', 'ForwardX11': 'no'}
with open('example.ini', 'w') as configfile:  # 把刚才创建的配置信息写入文件
    config.write(configfile)  # 对象.write(文件句柄)
复制代码

 

2、查

复制代码
import configparser

config = configparser.ConfigParser()  # 实例化一个对象
config.read('example.ini')  # 读取配置文件信息
# 打印配置文件的组名(DEFAULT默认不显示):['bitbucket.org', 'topsecret.server.com']
print(config.sections())
print('bytebong.com' in config)  # 判断bytebong.com这个组是否在配置文件中:False
print('bit.org' in config)  # 判断bitbucket.org这个组是否在配置文件中:True
print(config['bit.org']["user"])  # 打印bit.org组下的user的值:Big_rookie
print(config['DEFAULT']['compression'])  # 打印DEFAULT组下的compression的值:hello
# 打印topsecret.server.com组下的compression的值(会去DEFAULT组找):hello
print(config['topsecret.server.com']['compression'])
print(config['bit.org'])  # 打印组名<Section: bitbucket.org>
for key in config['bit.org']:  # 循环输出bit.org组的键,有default也会打印default的键
    print(key)
print(config.options('bit.org'))  # 跟for循环一样,打印'bit.org'下所有键
print(config.items('bit.org'))  # 打印'bit.org'下所有键值对
print(config.get('bit.org', 'user'))  # get方法:找到bit.org下的user对应的值
复制代码

 

3、增删改

复制代码
import configparser

config = configparser.ConfigParser()

config.read('example.ini')

config.add_section('person')  # 增加组

config.remove_section('bit.org')  # 删除组
config.remove_option('topsecret.server.com', "forwardx11")  # 删除某个组的某个值

config.set('topsecret.server.com', 'k1', '11111')  # 为某个组设置一个键值对
config.set('person', 'k2', '22222')

with open('newwxample.ini', "w") as f:
    config.write(f)  # 把修改的内容写进新的文件(旧文件没有改动的会原样写到新文件中)
复制代码

 

posted @   我用python写Bug  阅读(237)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示