好友
阅读权限 10
听众
最后登录 1970-1-1
本帖最后由 pyjiujiu 于 2024-11-13 11:01 编辑
**前言: (也可直接跳到代码部分)
写这个帖,一方面 为了记录学习心得,另一方面 看到 网上很多代码都已经过时(比如backend 参数早已不用),包括GPT生成的,因为深度学习 是按照概率来突出的,旧的代码会占上风 也算自然。
(第一次发帖,不足请多指教)
cryptography 库是 python 目前替代 pycryto 的推荐选择 ,(cryptodome 则是为了兼容pycryto的操作,pycryto本身已经不被维护 不推荐继续使用 )
使用库前,建议阅读下 库文档的FAQ页面,对库状态有个了解:https://cryptography.io/en/latest/faq/
使用这个库,有几个问题
1 high level 用起来比较简单,可仅实施了 AES-128,还把 版本信息,timestamp, IV 等都绑在一起 ,万一遇到「解码 ts文件」这种零散的任务,就完全没法用(也是本人翻文档的初衷,一个库搞定所有任务 是我追求的省力)
操作代码举例
from cryptography.fernet import Fernet
password = Fernet.generate_key() #这个密码 是44位(base64 转码过的),实际32byte,一半是AES的key 另一半是 hmac的签名key
#加密
cipher = Fernet(password)
encrypted_data = cipher.encrypt(message)
#解密
plain_text = cipher.decrypt(encrypted_data)
2 库文档不推荐对底层进行操作
hazmat.primitives 其中 hazmat 就是 hazard + materials 是极度危险的材料 之意,还说里面都是恐龙
原文:This is a “Hazardous Materials” module. You should ONLY use it if you’re100% absolutely sure that you know what you’re doing because this module isfull of land mines, dragons, and dinosaurs with laser guns.
(# 实没有那么吓人,然而对底层的操作 确实需要慎重)
---分割线---
** 简单介绍 术语
对称加密 symmetric ,意思是 加密和 解密 都是同一个密钥。这个概念是相对 非对称加密(asymmetric)而言的 ,本来不必这么复杂,但博弈双方传输密码有困难,不如想个办法打明牌,公钥分发给要发送的人,他加密后 发给要看信息的人(掌握私钥),过程中 公钥和密文都是堂而皇之的,因为解密必须用 私钥不可,其自信的来源,就是大的数的因式分解对现有计算机算力比较困难,(虽然在量子计算机眼里不一样)
cipher_text 一般仅仅是纯信息相关的密文 ,不包括密文块中 辅助的信息(如时间信息),但cipher 这个词在密码学中 本身有算法的意思 。
token 是加密后的密文整体( 其中包含cipher_text部分) ,比如 cryptography 库中,high-level 加密后是,包含 5 块信息,version--IV--timestamp--cipher_text--hamc
(在大模型时代,token就是语言分解后的单位)
对比 Authentication 和 Verification : 前者是个人身份的认定,后者是两边是否一样的确认。比如 登录网站时 网站要用户输入密码,是为了authentication (将登录的动作 绑到具体的人身上),而两次输入密码,其实仅仅是为了 verify 密码是否一致。
#有了这几个概念的初步理 解,就可以无碍地 进入加密解密的世界
---分割线---
** 理解 加密模型
加密的操作,一致都遵循相同的原理 。从古希腊,罗马的 凯撒密码,到当代的加密算法,都分为两个部分 「操作的量」「操作模式」(书上叫 the Permutation , the mode of operation)
遵循一种模式,按照量去操作。就像现实的锁,也是钥匙的纹理千变万化(对应量),而模式却很简单,仅仅是转动一下。
理解这,就很好理解 下面这段代码
[Python] 纯文本查看 复制代码
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
其中,Cipher(algorithms.AES(key)) 对算法AES来说已经完备,量--key,操作模式--AES
而 modes.CBC(iv) 则是 一种附加的 链接的方案(可以看作对message的前处理)
在这个库中,这种模式会反复出现,如hmac 参数也是key + algorithm,熟悉了就不会觉得陌生
---分割线---
** 下面是 AES-128 的代码,可以方便组合
[Python] 纯文本查看 复制代码
import os #为了生成随机 bytes
import base64
import pathlib
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import cryptography #这句为了写 exceptions
from cryptography.hazmat.primitives import hashes, hmac
# 参数 backend 已经不再使用,虽然 gpt 还是会生成代码
#
#提醒:key 和 iv 是可以自己设置,如解密已有的文件(也可修改加密函数 encrypt)
key = ''
iv = ''
def hmac_sign(message:bytes):
'''hamc the message to signature with sign_key'''
sign_key = os.urandom(32) # 32byte 是推荐写法,保持和SHA256 一致的长度, (在high level 官方实施中是16byte的)
h = hmac.HMAC(sign_key, algorithm=hashes.SHA256())
h.update(message)
signature = h.finalize()
return (sign_key,signature)
def hmac_verify(message, signature:bytes, sign_key):
h = hmac.HMAC(sign_key, algorithm=hashes.SHA256())
h.update(message)
try:
h_copy = h.copy()
h_copy.verify(signature)
print('确认成功')
except cryptography.exceptions.InvalidSignature:
print('确认 fail')
except Exception as e:
print(f'发生错误:{e}')
def padder(message):
padder = padding.PKCS7(128).padder()
padded_data_body = padder.update(message)
padded_data_tail = padder.finalize()
padded_data = padded_data_body + padded_data_tail
return padded_data
def unpadder(padded_data):
unpadder = padding.PKCS7(128).unpadder()
data_body = unpadder.update(padded_data)
data_tail = unpadder.finalize()
data = data_body + data_tail
return data
def decrypt(key,iv,cipher_text):
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
data = decryptor.update(cipher_text) + decryptor.finalize()
plain_text = unpadder(data)
return plain_text
def encrypt(message:bytes):
'''encrypt the data in modes.CBC and padding.PKCS7'''
key = os.urandom(16)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
padded_message = padder(message)
cipher_text = encryptor.update(padded_message)+ encryptor.finalize()
return (key,iv,cipher_text)
def url_safe_encode(data:bytes):
return base64.urlsafe_b64encode(data)
def url_safe_decode(data:bytes):
return base64.urlsafe_b64decode(data)
if __name__ == "__main__":
message = b"this is a paragraph for testing" #这句长度是 31byte,根据padding规则,AES 加密后会被扩展到 32byte
print(f'original message:{message}')
print('\n测试 hamc')
sign_key,signature = hmac_sign(message=message)
print(f'sign_key:{sign_key} \n signature:{signature}')
print(f'length: {len(sign_key)},{len(signature)}')
print('测试验证 hmac')
hmac_verify(message,signature,sign_key)
print('\n测试 AES 加密')
aes_key,iv,cipher_text = encrypt(message=message)
print(f'AES_key:{aes_key} \n IV:{iv} \n cipher_text:{cipher_text}')
print(f'length: {len(aes_key)},{len(iv)},{len(cipher_text)}')
print('\n测试验证 AES 解密')
plain_t = decrypt(aes_key,iv,cipher_text)
print(f'文本:{plain_t}')
print('\n\n测试base64 转码')
print('♢ hmac')
print(f'sign_key:{url_safe_encode(sign_key)} \n signature:{url_safe_encode(signature)}')
print('\n♢ AES')
print(f'AES_key:{url_safe_encode(aes_key)} \n IV:{url_safe_encode(iv)} \n cipher_text:{url_safe_encode(cipher_text)}')
input('')
说明:
简单介绍,如果不用 hamc ,直接调用encrypt() 和 decrypt() 函数 ,然后对key ,iv ,密文,等进行base64 加密即可,如本地文件,不需要明文展示 ,也可以不经过base64,以原始字符串存在。
*对于AES加密来说,选用链接块模式 CBC(IV) -->要求padding操作( PKCS7(128)) ,这个128 bit 也是对应block的大小。
---分割线---
下面是 自己写的 RSA 的 gui脚本 ,可以用来加密 190 byte以内的文件的数据(适合 sha256的数据,AES128后的密码)
#仅需要另安装 tkinterdnd2 以支持拖文件操作
[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinterdnd2 import TkinterDnD, DND_FILES
from cryptography.hazmat.primitives import hashes,serialization
from cryptography.hazmat.primitives.asymmetric import rsa,padding
import pathlib
import datetime
import ctypes #设置 高清DPI
ctypes.windll.shcore.SetProcessDpiAwareness(2)
def encrypt_file(public_key, file_path):
file_origin = pathlib.Path(file_path)
with open(file_origin, 'rb') as f:
data = f.read()
encrypted_data = public_key.encrypt(
plaintext = data,
padding = padding.OAEP(
mgf=padding.MGF1(hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
encrypted_file_path =file_origin.with_name(file_origin.name + '.enc')
with open(encrypted_file_path, 'wb') as f:
f.write(encrypted_data)
print(f'加密成功: {file_origin.name}')
return encrypted_file_path
def decrypt_file(private_key, file_path):
encrypted_file = pathlib.Path(file_path)
with open(encrypted_file, 'rb') as f:
encrypted_data = f.read()
decrypted_data = private_key.decrypt(
ciphertext = encrypted_data,
padding = padding.OAEP(
mgf=padding.MGF1(hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
decrypted_file_path = encrypted_file.with_name('(decrypted)'+ encrypted_file.name.replace('.enc',''))
with open(decrypted_file_path, 'wb') as f:
f.write(decrypted_data)
return decrypted_file_path
def generate_keys():
file_path = file_entry.get()
if file_path:
folder = pathlib.Path(file_path).parent
else:
folder = pathlib.Path(__file__).parent
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
pem_private = private_key.private_bytes(
encoding= serialization.Encoding.PEM,
format= serialization.PrivateFormat.PKCS8,
encryption_algorithm = serialization.NoEncryption()
)
pem_public = public_key.public_bytes(
encoding= serialization.Encoding.PEM,
format= serialization.PublicFormat.SubjectPublicKeyInfo,
)
date_info = datetime.datetime.today().strftime('%Y%m%d')
with open(folder / f'private_key_{date_info}.pem', 'wb') as f:
f.write(pem_private)
with open(folder / f'public_key{date_info}.pem', 'wb') as f:
f.write(pem_public)
messagebox.showinfo("成功", "公私钥已生成并保存!")
def load_public_key():
public_key_file = filedialog.askopenfilename(filetypes=[("PEM files", "*.pem")])
if public_key_file:
with open(public_key_file, 'rb') as f:
public_key = serialization.load_pem_public_key(
f.read()
)
return public_key
else:
return None
def load_private_key():
private_key_file = filedialog.askopenfilename(filetypes=[("PEM files", "*.pem")])
if private_key_file:
with open(private_key_file, 'rb') as f:
private_key = serialization.load_pem_private_key(
f.read(),
password=None
)
return private_key
else:
return None
def handle_encrypt():
public_key = load_public_key()
if public_key is None:
messagebox.showerror("错误", "未选择有效的公钥文件")
return
file_path = file_entry.get()
if not file_path:
messagebox.showerror("错误", "请选择文件")
return
encrypted_file_path = encrypt_file(public_key, file_path)
messagebox.showinfo("加密成功", f"文件已加密并保存至: {encrypted_file_path}")
def handle_decrypt():
private_key = load_private_key()
if private_key is None:
messagebox.showerror("错误", "未选择有效的私钥文件")
return
file_path = file_entry.get()
if not file_path or not file_path.endswith('.enc'):
messagebox.showerror("错误", "请选择有效的加密文件")
return
decrypted_file_path = decrypt_file(private_key, file_path)
messagebox.showinfo("解密成功", f"文件已解密并保存至: {decrypted_file_path}")
def count_bytes(path):
file_path = pathlib.Path(path)
if file_path.suffix == '.enc':
byte_count_number_lable['text'] = ''
return
byte_num = len(file_path.read_bytes())
if byte_num > 190:
byte_count_number_lable['fg'] = '#0000aa'
byte_count_number_lable['text'] = str(byte_num) + ' (too much)'
else:
byte_count_number_lable['fg'] = '#000000'
byte_count_number_lable['text'] = byte_num
def browse_file():
file_path = filedialog.askopenfilename(filetypes=[("All files", "*.*")])
if file_path:
file_entry.delete(0, tk.END)
file_entry.insert(tk.END, file_path)
count_bytes(file_path)
root = TkinterDnD.Tk()
root.title("RSA 加密解密工具")
frame = tk.Frame(root)
frame.pack(padx=10, pady=10)
file_label = tk.Label(frame, text="file path:")
file_label.grid(row=0, column=0, padx=5, pady=5)
file_entry = tk.Entry(frame, width=40)
file_entry.grid(row=0, column=1, padx=5, pady=5)
file_button = tk.Button(frame, text='choose',command=browse_file)
file_button.grid(row=0, column=2, padx=5, pady=5)
byte_count_lable = tk.Label(frame, text="bytes (<190):")
byte_count_lable.grid(row=1, column=0, padx=5, pady=5)
byte_count_number_lable = tk.Label(frame, text="")
byte_count_number_lable.grid(row=1, column=1, padx=5, pady=5,sticky='w')
def on_drop(event):
file_path = event.data.strip('{}')
file_entry.delete(0, tk.END)
file_entry.insert(0, file_path)
count_bytes(file_path)
root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', on_drop)
frame2 = tk.Frame(root)
frame2.pack(padx=10, pady=0,expand=1)
encrypt_button = tk.Button(frame2, text="Encrypt (公)", command=handle_encrypt)
encrypt_button.pack(padx=40, pady=10,side='left')
decrypt_button = tk.Button(frame2, text="Decrypt (私)", command=handle_decrypt)
decrypt_button.pack(padx=40, pady=10,side='right')
generate_key_button = tk.Button(root, text="Generate Keys", command=generate_keys)
generate_key_button.pack(padx=10, pady=10,expand=1)
root.mainloop()
1 点击 「generate keys」按钮, 会在脚本目录生成 公钥和私钥 两个文件(PEM格式)(若已拖入文件,则会在对应文件目录)
2 文件体积超过190byte 会解密失败,于是加了byte 的提示标签
3 对于没用过非对称加密的朋友,有必要提醒,常规做法是 公钥加密,私钥解密,私钥要自己保存。
#还有加密后是 ".enc" 文件,解密也是强制 ".enc" 文件 (避免操作失误的设置)
---结尾---
之前在本论坛学了不少,多谢各位,这篇算是一个敬意
#本篇已经很长,不确定是否会 继续更新 对RSA算法的解释(其实目前也是一头雾水)
#其次 欢迎讨论 有错误可以随意指出(真不会介意)
免费评分
查看全部评分