吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 299|回复: 0
收起左侧

[Python 原创] cryptography 实现 AES-128 加密,RSA 加密

[复制链接]
pyjiujiu 发表于 2024-11-12 23:16
本帖最后由 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()


rsa_gui_app.JPG

1 点击 「generate keys」按钮, 会在脚本目录生成 公钥和私钥 两个文件(PEM格式)(若已拖入文件,则会在对应文件目录)
2 文件体积超过190byte 会解密失败,于是加了byte 的提示标签

3 对于没用过非对称加密的朋友,有必要提醒,常规做法是 公钥加密,私钥解密,私钥要自己保存。
#还有加密后是 ".enc" 文件,解密也是强制 ".enc" 文件 (避免操作失误的设置)

---结尾---
之前在本论坛学了不少,多谢各位,这篇算是一个敬意

#本篇已经很长,不确定是否会 继续更新 对RSA算法的解释(其实目前也是一头雾水)

#其次 欢迎讨论 有错误可以随意指出(真不会介意)



免费评分

参与人数 2吾爱币 +8 热心值 +2 收起 理由
helian147 + 1 + 1 热心回复!
侃遍天下无二人 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-28 10:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表