day33-常见内置模块二(hashlib、shutil、configparse)

时间:2023-03-08 21:54:37

一、hashlib算法介绍

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

1、什么是摘要算法呢?

摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

主要用途:
1、密码加密
2、文件较验

2、MD5

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

import hashlib

md5 = hashlib.md5()
md5.update('admin'.encode('utf-8'))
print(md5.hexdigest()) 结果:
# 21232f297a57a5a743894a0e4a801fc3
update()必须指定要加密的字符串的字符编码,否则会报错TypeError: Unicode-objects must be encoded before hashing

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

md5 = hashlib.md5()
md5.update('ad'.encode('utf-8'))
md5.update('min'.encode('utf-8'))
print(md5.hexdigest())
# 21232f297a57a5a743894a0e4a801fc3

或者写成这样(方法二)

md5 = hashlib.md5('admin'.encode('utf-8'))
print(md5.hexdigest())

3、SHA1

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

sha1 = hashlib.sha1()
sha1.update('admin'.encode('utf8'))
print(sha1.hexdigest())
# d033e22ae348aeb5660fc2140aec35850c4da997

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

4、摘要算法应用

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

name    | password
--------+----------
michael | 123456
bob | abc999
tom | tom123

如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。
此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。
正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:

username | password
---------+---------------------------------
michael | e10adc3949ba59abbe56e057f20f883e
bob | 878ef96e86145580c38c87f0410ad153
alice | 5caf72868c94f184650f43413092e82c

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

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

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

5、“加盐”
对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?
由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:

md5 = hashlib.md5('salt'.encode('utf-8'))
md5.update('password'.encode('utf-8'))
print(md5.hexdigest())
# 67a1e09bb1f83f5007dc119c14d663aa

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

但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?
如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

username = 'admin'
md5 = hashlib.md5(username.encode('utf-8'))
md5.update('password'.encode('utf-8'))
print(md5.hexdigest())
# e3274be5c857fb42ab72d786e281b4b8

6、例子:
6.1、密文登录

username = input('please input your username:')
password = input('please input your password:')
md5 = hashlib.md5()
md5.update(password.encode('utf-8'))
md5 = hashlib.md5()
with open('user', 'a', encoding='utf-8') as f:
f.write('%s|%s\n' %(username, md5.hexdigest())) #user文件存储账号和哈希后的密码
admin|0192023a7bbd73250516f069df18b500
user1|6ad14ba9986e3615423dfca256d04e3f #登录验证
for count in range(3):
with open('user', 'r', encoding='utf-8') as f:
username = input('please input your username:')
password = input('please input your password:')
md5 = hashlib.md5()
md5.update(password.encode('utf-8'))
for line in f:
line = line.strip().split('|')
if username == line[0] and md5.hexdigest() == line[1]:
print('ok')
exit()
else:
print('error')

6.2、加盐的密文登陆

用户名admin,密码123,salt:用户名

import hashlib
for i in range(3):
username = input('请输入用户名:')
passowrd = input('请输入密码:')
md5_input = hashlib.md5(username.encode('utf-8'))
md5_input.update(passowrd.encode('utf-8'))
hexdigest_input = md5_input.hexdigest()
md5 = hashlib.md5('admin'.encode('utf-8'))
md5.update(''.encode('utf-8'))
if md5.hexdigest() == hexdigest_input:
print('登录成功')
break
else:
print('登录失败,还剩%s次机会' %(2-i))

6.3、校验两个文件的一致性

import os
import hashlib
file1 = os.path.abspath('file1.py')
file2 = os.path.abspath('file2.py')
with open(file1) as f1, open(file2) as f2:
md5_1 = hashlib.md5()
md5_1.update(f1.read().encode('utf-8'))
hash_1 = md5_1.hexdigest() md5_2.update(f2.read().encode('utf-8'))
md5_2 = hashlib.md5()
hash_2 = md5_2.hexdigest()
# print(hash_1, hash_2)
if hash_1 == hash_2:
print('两个文件相同')

二、shutil模块

shutil模块是一个高级的文件、文件夹、压缩包 处理模块

1、shutil.copyfileobj(fsrc, fdst[, length])
将文件内容拷贝到另一个文件中

import shutil
shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))

2、shutil.copyfile(src, dst)

shutil.copyfile('f1.log', 'f2.log') #目标文件无需存在

3、shutil.copymode(src, dst)

仅拷贝权限。内容、组、用户均不变

shutil.copymode('f1.log', 'f2.log') #目标文件必须存在

4、shutil.copystat(src, dst)

仅拷贝状态的信息,包括:mode bits, atime, mtime, flags

shutil.copystat('f1.log', 'f2.log') #目标文件必须存在

5、shutil.copy(src, dst)

拷贝文件和权限

import shutil
shutil.copy('f1.log', 'f2.log')

6、shutil.copy2(src, dst)

拷贝文件和状态信息

import shutil
shutil.copy2('f1.log', 'f2.log')
shutil.ignore_patterns(*patterns)

7、shutil.copytree(src, dst, symlinks=False, ignore=None)

递归的去拷贝文件夹

import shutil
shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) #目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除 import shutil
shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
'''
通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件
'''

8、shutil.rmtree(path[, ignore_errors[, onerror]])

递归的去删除文件,参数必须是一个文件夹而不能是文件

import shutil
shutil.rmtree('folder1')

9、shutil.move(src, dst)

递归的去移动文件,它类似mv命令,其实就是重命名。

import shutil
shutil.move('folder1', 'folder3')

10、shutil.make_archive(base_name, format,...)

创建压缩包并返回文件路径,例如:zip、tar
创建压缩包并返回文件路径,例如:zip、tar

base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
如 data_bak =>保存至当前路径
如:/tmp/data_bak =>保存至/tmp/
format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
root_dir: 要压缩的文件夹路径(默认当前目录)
owner: 用户,默认当前用户
group: 组,默认当前组
logger: 用于记录日志,通常是logging.Logger对象

#将 /data 下的文件打包放置当前程序目录
import shutil
ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data') #将 /data下的文件打包放置 /tmp/目录
import shutil
ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data')

11、shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的

zipfile压缩解压缩

import zipfile

# 压缩
z = zipfile.ZipFile('laxi.zip', 'w')
z.write('a.log')
z.write('data.data')
z.close() # 解压
z = zipfile.ZipFile('laxi.zip', 'r')
z.extractall(path='.')
z.close()

tarfile压缩解压缩

import tarfile
# 压缩
>>> t=tarfile.open('/tmp/egon.tar','w')
>>> t.add('/test1/a.py',arcname='a.bak')
>>> t.add('/test1/b.py',arcname='b.bak')
>>> t.close() # 解压
>>> t=tarfile.open('/tmp/egon.tar','r')
>>> t.extractall('/egon')
>>> t.close()

三、configparse模块(了解)

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

1、创建文件
来看一个好多软件的常见文档格式如下:

[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes [bitbucket.org]
User = hg [topsecret.server.com]
Port = 50022
ForwardX11 = no

如果想用python生成一个这样的文档怎么做呢?

import configparser

config = configparser.ConfigParser()
config['DEFAULT'] = {'ServerAliveInterval': 45,
'Compression': 'yes',
'CompressionLevel': 9,
'ForwardX11': 'yes'
} config['bitbucket.org'] = {'User': 'hg'}
config['topsecret.server.com'] = {'Host Port': '', 'ForwardX11': 'no'} with open('example.ini', 'w') as f1:
config.write(f1)

2、查找文件

import configparser
config = configparser.ConfigParser()
config.read('example.ini')

查找文件内容,基于字典的形式

print(config.sections())        #['bitbucket.org', 'topsecret.server.com']

通过原码可以看到read默认有2个参数:filenames, encoding=None,如果有中文就需要指定编码

config.read('example.ini', encoding='utf8')

判断key值是否在config中

print('bytebong.com' in config) # False
print('bitbucket.org' in config) # True

打印'DEFAULT'下面的键

print(config['DEFAULT']) #<Section: DEFAULT>
print(config['DEFAULT']['compression']) #yes

打印Section下的key

for k in config['DEFAULT']:
print(k)
# serveraliveinterval
# compression
# compressionlevel
# forwardx11 for k in config['bitbucket.org']:
print(k + ':' + config['bitbucket.org'][k])
# user:hg
# serveraliveinterval:45
# compression:yes
# compressionlevel:9
# forwardx11:yes
# 注意,有default会默认输出default的键

找出所有键值对

print(config.options('bitbucket.org'))
# ['user', 'serveraliveinterval', 'compression', 'compressionlevel', 'forwardx11']
# 同for循环,找到'bitbucket.org'下所有键,有default会默认输出default的键

找到Section下所有键值对

print(config.items('DEFAULT'))
# [('serveraliveinterval', '45'), ('compression', 'yes'), ('compressionlevel', '9'), ('forwardx11', 'yes')] print(config.items('bitbucket.org'))
# # [('serveraliveinterval', '45'), ('compression', 'yes'), ('compressionlevel', '9'), ('forwardx11', 'yes'), ('user', 'hg')]

get方法Section下的key对应的value

print(config.get('DEFAULT', 'compression')) #yes
print(config.get('topsecret.server.com', 'host port')) #

3、增删改操作

import configparser

config = configparser.ConfigParser()
config.read('example.ini', encoding='utf8')

增加

config.add_section('test1')
with open('example.ini', 'w') as f:
config.write(f)
# 增加了一个section,名称为[test1]

删除

config.remove_section('bitbucket.org')
with open('example.ini', 'w') as f:
config.write(f)
# 删除了section:[bitbucket.org] config.remove_option('topsecret.server.com', 'forwardx11')
with open('example.ini', 'w') as f:
config.write(f)
# 删除了topsecret.server.com下的forwardx11键值对

修改

config.set('topsecret.server.com', 'k1', '')
config.set('topsecret.server.com', 'host port', '') config.set('test1', 'k2', '')
config.write(open('example.ini', 'w'))
# 设置topsecret.server.com下的k1的值为123, 设置host port的值为50021
# 设置test1下的k2的值为111
# set方法,有则更改,无则添加
# config.write(open(file, 'w'))写法同with open(file, 'w') as config_file:config.write(config_file) example.ini 结果:
[DEFAULT]
serveraliveinterval = 45
compression = yes
compressionlevel = 9
forwardx11 = yes [topsecret.server.com]
host port = 50021
k1 = 123 [test1]
k2 = 111