1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""
加密和解密
对称加密 - 加密和解密是同一个密钥 - DES / AES
非对称加密 - 加密和解密是不同的密钥 - RSA
pip install pycrypto
"""
import base64

from hashlib import md5

from Crypto.Cipher import AES
from Crypto import Random
from Crypto.PublicKey import RSA

# # AES加密的密钥(长度32个字节)
# key = md5(b'1qaz2wsx').hexdigest()
# # AES加密的初始向量(随机生成)
# iv = Random.new().read(AES.block_size)


def main():
"""主函数"""
# 生成密钥对
key_pair = RSA.generate(1024)
# 导入公钥
pub_key = RSA.importKey(key_pair.publickey().exportKey())
# 导入私钥
pri_key = RSA.importKey(key_pair.exportKey())
message1 = 'hello, world!'
# 加密数据
data = pub_key.encrypt(message1.encode(), None)
# 对加密数据进行BASE64编码
message2 = base64.b64encode(data[0])
print(message2)
# 对加密数据进行BASE64解码
data = base64.b64decode(message2)
# 解密数据
message3 = pri_key.decrypt(data)
print(message3.decode())
# # AES - 对称加密
# str1 = '我爱你们!'
# cipher = AES.new(key, AES.MODE_CFB, iv)
# # 加密
# str2 = cipher.encrypt(str1)
# print(str2)
# # 解密
# cipher = AES.new(key, AES.MODE_CFB, iv)
# str3 = cipher.decrypt(str2)
# print(str3.decode())


if __name__ == '__main__':
main()

fs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
"""
哈希摘要 - 数字签名/指纹 - 单向哈希函数(没有反函数不可逆)
应用领域:
1. 数据库中的用户敏感信息保存成哈希摘要
2. 给数据生成签名验证数据没有被恶意篡改
3. 云存储服务的秒传功能(去重功能)
"""


class StreamHasher():
"""摘要生成器"""

def __init__(self, algorithm='md5', size=4096):
"""初始化方法
@params:
algorithm - 哈希摘要算法
size - 每次读取数据的大小
"""
self.size = size
cls = getattr(__import__('hashlib'), algorithm.lower())
self.hasher = cls()


def digest(self, file_stream):
"""生成十六进制的摘要字符串"""
# data = file_stream.read(self.size)
# while data:
# self.hasher.update(data)
# data = file_stream.read(self.size)
for data in iter(lambda: file_stream.read(self.size), b''):
self.hasher.update(data)
return self.hasher.hexdigest()

def __call__(self, file_stream):
return self.digest(file_stream)


def main():
"""主函数"""
hasher1 = StreamHasher()
hasher2 = StreamHasher('sha1')
hasher3 = StreamHasher('sha256')
with open('Python-3.7.2.tar.xz', 'rb') as file_stream:
print(hasher1.digest(file_stream))
file_stream.seek(0, 0)
print(hasher2.digest(file_stream))
file_stream.seek(0, 0)
print(hasher3(file_stream))


if __name__ == '__main__':
main()

Hashlib

一般包括常见的MD5SHA1, SHA256等等, 属使用消息摘要算法。

消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,目前可以被解密逆向的只有CRC32算法,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。

消息摘要算法分为三类:

  • MD(Message Digest):消息摘要

  • SHA(Secure Hash Algorithm):安全散列

  • MAC(Message Authentication Code):消息认证码

这三类算法的主要作用:验证数据的完整性

以MD5为例,计算出一个字符串的MD5值:

1
2
3
4
5
6
7
import hashlib

md5 = hashlib.md5()
md5.update('猫'.encode('utf-8'))
print(md5.hexdigest())
# b655de688e0a05b8f364a89b4ab0bddc
# cat ==> d077f244def8a70e5ea758bd8352fcd8

当字符很长时,也可分块多次调用update(),结果一样:

1
2
3
4
5
6
7
import hashlib

md5 = hashlib.md5()
md5.update('Logic will get you from A to B.'.encode('utf-8'))
md5.update(' Imagination will take you everywhere.'.encode('utf-8'))
print(md5.hexdigest())
# c907c43f8e3874ae45543611ac4973ed

MD5生成的结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

简单的用户登录模块,就是把你的密码进行的md5加密后储存于数据库当中,这样即使被人恶意脱库了,Ta也不知道原始密码是什么,但实际上,有很多网站的用户密码都是明文保存的,因为要审查。

SHA的话,包括了(SHA-1,SHA-2,SHA-224,SHA-256,SHA-384,SHA-512), 返回对应固定长度的摘要信息(除了SHA1的结果是160 bit字节,用一个40位的16进制字符串表示)。

1
2
3
4
5
6
7
import hashlib

sha1 = hashlib.sha1()
sha1.update('Hello '.encode('utf-8'))
sha1.update('My Majesty.'.encode('utf-8'))
print(sha1.hexdigest())
# c907c43f8e3874ae45543611ac4973ed

但是这样就安全了吗?很多用户喜欢用123456888888password或者简单的生日弱口令,于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表,即彩虹表进行反推。

加盐

更安全的办法,是对原始口令加一个复杂字符串来实现,俗称“加盐”,这个可以这么想:

盐加多了,你还能尝得出汤原来的味道吗? 所以,足够安全,除非你拿到盐,制作出的新的彩虹表。

比如,一个修改后的MD5算法实现用户登录的验证算法,取自雪峰的博客:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
import hashlib, random

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

class User(object):
def __init__(self, username, password):
self.username = username
# 盐为随机的20位的0-z
self.salt = ''.join([chr(random.randint(48, 122)) for i in range(20)])
self.password = get_md5(password + self.salt)
db = {
'michael': User('michael', '123456'),
'bob': User('bob', 'abc999'),
'alice': User('alice', 'alice2008')
}
1
2
3
4
def login(username, password):
user = db[username]
# salt是随机的,所以要加实例属性。
return user.password == get_md5(password+user.salt)

Base64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""
编码和解码 - BASE64
0-9A-Za-z+/
1100 0101 1001 0011 0111 0110
00110001 00011001 00001101 00110110
base64
b64encode / b64decode
-------------------------------------
序列化和反序列化
序列化 - 将对象变成字节序列(bytes)或者字符序列(str) - 串行化/腌咸菜
反序列化 - 把字节序列或者字符序列还原成对象
Python标准库对序列化的支持:
json - 字符形式的序列化
pickle - 字节形式的序列化
dumps / loads
"""
import base64
import json
import redis

from example02 import Person


class PersonJsonEncoder(json.JSONEncoder):

def default(self, o):
return o.__dict__


def main():
cli = redis.StrictRedis(host='120.77.222.217', port=6379,
password='123123')
data = base64.b64decode(cli.get('guido'))
with open('guido2.jpg', 'wb') as file_stream:
file_stream.write(data)
# with open('guido.jpg', 'rb') as file_stream:
# result = base64.b64encode(file_stream.read())
# cli.set('guido', result)
# persons = [
# Person('骆昊', 39), Person('王大锤', 18),
# Person('白元芳', 25), Person('狄仁杰', 37)
# ]
# persons = json.loads(cli.get('persons'))
# print(persons)
# cli.set('persons', json.dumps(persons, cls=PersonJsonEncoder))


if __name__ == '__main__':
main()


In computer science, Base64 is a group of binary-to-text encoding schemes that represent binary data in an ASCII string format by translating it into a radix-64 representation.

​ -----------from wikipedia

Base64是一种用64个字符来表示二进制数据的方法, 一般用于URL、Cookie、网页中传输少量二进制数据,其转换表格如下:

Index Binary Char Index Binary Char Index Binary Char Index Binary Char
0 000000 A 16 010000 Q 32 100000 g 48 110000 w
1 000001 B 17 010001 R 33 100001 h 49 110001 x
2 000010 C 18 010010 S 34 100010 i 50 110010 y
3 000011 D 19 010011 T 35 100011 j 51 110011 z
4 000100 E 20 010100 U 36 100100 k 52 110100 0
5 000101 F 21 010101 V 37 100101 l 53 110101 1
6 000110 G 22 010110 W 38 100110 m 54 110110 2
7 000111 H 23 010111 X 39 100111 n 55 110111 3
8 001000 I 24 011000 Y 40 101000 o 56 111000 4
9 001001 J 25 011001 Z 41 101001 p 57 111001 5
10 001010 K 26 011010 a 42 101010 q 58 111010 6
11 001011 L 27 011011 b 43 101011 r 59 111011 7
12 001100 M 28 011100 c 44 101100 s 60 111100 8
13 001101 N 29 011101 d 45 101101 t 61 111101 9
14 001110 O 30 011110 e 46 101110 u 62 111110 +
15 001111 P 31 011111 f 47 101111 v 63 111111 /
padding =

Examples

base64实质是3个字符(24个bit)为一组,对应ascii码的二进制,如Lov对应01001100 01101111 01110110, 再重新划分为4组,每组有6个bit,如:010011 000110 111101 110110, 然后查表即可。

若不满足3个字符的要求,则用=补齐。

1
2
3
import base64
base64.b64encode(b'Love')
# b'TG92ZQ=='
L 76 O 111 V 118 E 101
01001100 01101111 01110110 01100101
10011 ==> T 110 ==> G 111101 ==> 9 110110 ==> 2
11001 ==> Z 10000 ==> Q = =
1
2
base64.b64decode(b'TG92ZQ==')
# b'Love'

相应的,也有base32base16的加密方式,这里不做赘述

+/在URL中就不能作为参数,于是就有"url safe"的base64编码,把字符+/替换为成-_.

这里引用一下@阮一峰(https://www.liaoxuefeng.com/wiki/1016959663602400/1017684507717184),错了@廖雪峰的那段代码,

1
2
3
4
5
6
base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
# b'abcd++//'
base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
# b'abcd--__'
base64.urlsafe_b64decode('abcd--__')
# b'i\xb7\x1d\xfb\xef\xff'

然后,=若在URL、Cookie会有歧义,实际运用中会去掉,解码代码如下:

1
2
3
4
def safe_base64decode(s):
if len(s)%4!=0:
s=s+b'='*(4-len(s)%4)
return base64.urlsafe_b64decode(s)
1
2
3
4
5
6
7
8
9
'去月球Moon'.encode('utf-8')
# b'\xe5\x8e\xbb\xe6\x9c\x88\xe7\x90\x83Moon'
base64.b64encode('去月球Moon'.encode('utf-8'))
# b'5Y675pyI55CDTW9vbg=='

assert b'\xe5\x8e\xbb\xe6\x9c\x88\xe7\x90\x83Moon' == safe_base64decode(b'5Y675pyI55CDTW9vbg')
assert b'\xe5\x8e\xbb\xe6\x9c\x88\xe7\x90\x83Moon' == base64.b64decode(b'5Y675pyI55CDTW9vbg==')
print('ok')
# ok

Hmac

HMAC(keyed-Hash Message Authentication Code):含有密钥的散列函数算法,包含了MD和SHA两个系列的消息摘要算法, 实际上当使用我们自己随机生成的salt,譬如md5(message + salt),就是简单的Hmac算法。

包括:

MD系列:HmacMD2,HmacMD4,HmacMD5

SHA系列:HmacSHA1,HmacSHA224,HmacSHA256,HmacSHA38,HmacSHA512

Hmac算法针对所有哈希算法都通用,无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。

1
2
3
4
5
6
import hmac
message = b'Bonjour le monde!'
key = b'0229'
h = hmac.new(key, message, digestmod='MD5')
h.hexdigest()
# '09b93ecd1c25f5842ee21529720b0a64'

此时,登陆算法,可改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding: utf-8 -*-
import hmac, random

def hmac_md5(key, s):
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest()

class User(object):
def __init__(self, username, password):
self.username = username
self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)])
self.password = hmac_md5(self.key, password)

def login(username, password):
user = db[username]
return user.password == hmac_md5(user.key, password)

REFERENCES