Loading

Python内置模块之hashlib与logging

一、hashlib模块

1.1 算法介绍

Python的hashlib模块提供了常见的摘要算法,如MD5,SHA1等等。

什么是摘要算法呢?——摘要算法又称哈希算法,散列算法。它通过一个函数,把任意长度的数据转换成为一个长度固定的数据串(通常用16进制的字符串表示)。

摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。

摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:

import hashlib

md5 = hashlib.md5()
md5.update(b'how to use md5 in Python hashlib?')
print(md5.hexdigest())

打印的计算结果如下:

577361e734741bb6dfd97891d134c294

如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:

import hashlib

md5 = hashlib.md5()
md5.update(b'how to use md5 in ')
md5.update(b'Python hashlib?')
print(md5.hexdigest())  # 结果仍为: 577361e734741bb6dfd97891d134c294

MD5是最常见的摘要算法,速度很快,生成结果是固定的128bit字节,通常用一个32位的16进制字符串表示。另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:

import hashlib

sha1 = hashlib.sha1()
sha1.update(b'how to use sha1 in ')
sha1.update(b'Python hashlib?')
print(sha1.hexdigest())

打印的计算结果如下:

34b0bed244a1597f45f515913ba1c32b4a06a09b

SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。

1.2 摘要算法应用

任何允许用户登录的网站都会存储用户登录的用户名和密码。如何存储用户名和密码呢?方法是存到数据库表中:

name    | password
--------+----------
michael | 123456
bob     | abc999
alice   | alice2008

如果以明文保存用户密码,如果数据库泄露,所有用户的密码就落入黑客的手里;此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的密码。

正确的保存密码的方式是不存储用户的明文密码,而是存储用户密码的摘要—即密文密码,比如MD5:

username | password
---------+---------------------------------
michael  | e10adc3949ba59abbe56e057f20f883e
bob      | 878ef96e86145580c38c87f0410ad153
alice    | 99b1c2188db85afee403b1536010c2c9

还可以考虑这么个情况:很多用户喜欢用123456,888888,password这些简单的密码,于是,黑客可以事先计算出这些常用密码的MD5值,得到一个反推表:

'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'

这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用密码的用户账号。

对于用户来讲,当然不要使用过于简单的密码。但是,我们能否在程序设计上对简单密码加强保护呢?

由于常用密码的MD5值很容易被计算出来,所以,要确保存储的用户密码不是那些已经被计算出来的常用密码的MD5,这一方法通过对原始密码加一个复杂字符串来实现,俗称“加盐”:

import hashlib

salt = 'Eason'  # 自定义 "盐"
sha1 = hashlib.md5()
sha1.update(salt.encode('utf8'))
sha1.update(b'123456')
print(sha1.hexdigest())

加“盐”后的计算结果如下:

e981e1de208970049edb9c10ff66ecec

经过salt处理的MD5密码,只要salt不被黑客知道,即使用户输入简单密码,也很难通过MD5反推明文密码。

但是如果有两个用户都使用了相同的简单密码比如:123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同密码的用户存储不同的MD5呢?

如果假定用户无法修改登录名,就可以通过把登录名作为salt的一部分来计算MD5,从而实现使用相同密码的用户也存储不同的MD5,该方法称为动态加“盐”,当然也可以使用其他的一些数据作为动态“盐“(时间,uuid等)。

摘要算法在很多地方都有广泛的应用(校验文件一致性)。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文密码的情况下验证用户密码。

二、logging模块

2.1 日志等级

日志有五个等级(从上往下重要程度不一样):

import logging

logging.debug('debug级别')  # 10
logging.info('info级别')  # 20
logging.warning('warning级别')  # 30
logging.error('error级别')  # 40
logging.critical('critical级别')  # 50
"""默认记录的级别在30及以上"""

默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),默认的日志格式为日志级别:Logger名称:用户输出消息。

2.2 函数式简单配置与使用

灵活配置日志级别,日志格式,输出位置:

import logging

file_handler = logging.FileHandler(filename='x1.log', mode='a', encoding='utf-8', )
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[file_handler, ],
    level=logging.ERROR
)

logging.error('你好')

x1.log文件打印格式如下:

2021-11-29 16:51:29 PM - root - ERROR -logging模块:  你好

2.3 日志切割

import time
import logging
from logging import handlers

sh = logging.StreamHandler()
rh = handlers.RotatingFileHandler('myapp.log', maxBytes=1024, backupCount=5)
fh = handlers.TimedRotatingFileHandler(filename='x2.log', when='s', interval=5, encoding='utf-8')
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[fh, sh, rh],
    level=logging.ERROR
)

for i in range(1, 10):
    time.sleep(1)
    logging.error('KeyboardInterrupt error %s' % str(i))

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

filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”,还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

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

%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息

2.4 logger对象配置与使用

  1. 生成logger对象:负责产生日志
import logging

logger = logging.getLogger()
  1. filter对象:负责过滤日志(可以直接忽略)

  2. 生成handler对象:负责日志产生的位置

hd1 = logging.FileHandler('a1.log', encoding='utf8')  # 产生到文件的
hd2 = logging.FileHandler('a2.log', encoding='utf8')  # 产生到文件的
hd3 = logging.StreamHandler()  # 产生在终端的
  1. 生成formatter对象:负责日志的格式
fm1 = logging.Formatter(
    fmt='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
)
fm2 = logging.Formatter(
    fmt='%(asctime)s - %(name)s %(message)s',
    datefmt='%Y-%m-%d',
)
  1. 为handler对象绑定formatter对象:
hd1.setFormatter(fm1)
hd2.setFormatter(fm2)
hd3.setFormatter(fm1)
  1. 为logger对象绑定handler对象:
logger.addHandler(hd1)
logger.addHandler(hd2)
logger.addHandler(hd3)
  1. 设置日志等级:
logger.setLevel(30)  # 表示只记录warning级别及以上,括号里也可以写成logging.WARNING
  1. 记录日志:
logger.warning('写了半天 好累啊 好热啊')

logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。

2.5 配置字典的使用(CV大法)

配置字典格式如下:

# 自定义两种日志输出格式
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]'  # 其中name为getlogger指定的名字

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

logfile_path = '日志输出文件路径'
# log配置字典:
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},  # 过滤日志
    'handlers': {
        # 打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到终端屏幕
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集debug及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
    },
    'loggers': {
        # logging.getLogger(__name__)拿到的logger配置  空字符串作为键 能够兼容所有的日志
        '': {
            'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到终端屏幕
            'level': 'DEBUG',  # 定义logger对象日志输出级别
            'propagate': True,  # 向上(更高level的logger)传递
        },  # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
    },
}

使用配置字典方式如下:

import logging.config

logging.config.dictConfig(LOGGING_DIC)  # 自动加载字典中的配置
logger1 = logging.getLogger('日志名字')
logger1.debug('日志记录内容')

三 、第三方模块下载

第三方模并不是Python自带的,需要基于网络下载,下面介绍两种第三方模块的下载方式:

  • pip工具:

    在使用pip工具之前,需要将pip所在的路径添加到系统环境变量中,或者在命令行中切换到pip所在的路径;

    使用方式:

    • pip3 install 模块名(默认最新版本)
    • pip3 install 模块名==版本号(指定版本下载)
    • pip3 install 模块名 -i 仓库地址(临时切换下载源)

    pip命令默认下载的渠道是国外的Python官网,这就导致有时候下载模块速度会非常的慢,那么我们便可以通过上述第三种方式切换下载的源(仓库):

    (1)阿里云:http://mirrors.aliyun.com/pypi/simple/
    (2)豆瓣:http://pypi.douban.com/simple/
    (3)清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
    (4)中国科学技术大学:http://pypi.mirrors.ustc.edu.cn/simple/
    (5)华中科技大学:http://pypi.hustunique.com/

    示例如下:

    pip3 install openpyxl -i http://mirrors.aliyun.com/pypi/simple/
    

    使用该方式下载第三方模块可能会产生报错,以下是一些可能的报错情况及解决措施:

    1. 报错的提示信息中含有关键字timeout:

      原因:网络不稳定;

      措施:再次尝试,或者切换更加稳定的网络后再进行下载;

    2. 找不到pip命令:

      原因:环境变量问题;

      措施:将pip工具所在的路径添加到环境变量中即可;

    3. 没有任何关键字—不同的模块报不同的错:

      原因:模块需要特定计算机环境或者需要其他依赖模块才能下载等;

      措施:拷贝报错信息,打开浏览器,“百度”搜索即可;

  • PyCharm快捷方式:

    settings—>project—>Python Interpreter—>双击或者点击+号—>搜索对应模块下载即可。

    该方式也可以通过点击右界面下方的Manage Repositories管理下载的源(仓库)

下载完所需要的第三方模块之后,直接使用import或from import句式导入使用即可。

posted @ 2021-11-29 20:01  JZEason  阅读(65)  评论(0编辑  收藏  举报