python實現AES加密解密

1. 前言
AES是一種對稱加密,所謂對稱加密就是加密與解密使用的秘鑰是一個。

之前寫過一片關于python AES加密解密的文章,但是這里面細節實在很多,這次我從 參數類型、加密模式、編碼模式、補全模式、等等方面 系統的說明如何使用AES加密解密。

看文章不能急功近利,為了解決一個問題臨時查到一個代碼套用進去,或許可以迅速解決問題,但是遇到新的問題還需要再次查詢,這種我認為還是比較浪費時間的。我相信看完認真看完這篇文章的你會大有收獲。

2. 環境安裝
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome

前面兩個卸載命令是為了防止一些安裝環境問題,具體請看章

3.加密模式

?AES 加密最常用的模式就是 ECB模式 和 CBC 模式,當然還有很多其它模式,他們都屬于AES加密。ECB模式和CBC 模式倆者區別就是 ECB 不需要 iv偏移量,而CBC需要。

4.AES加密使用參數

以下參數都是在python中使用的。

參數作用及數據類型
秘鑰加密的時候用秘鑰,解密的時候需要同樣的秘鑰才能解出來; 數據類型為bytes
明文需要加密的參數; 數據類型為bytes
模式aes?加密常用的有?ECB?和?CBC?模式(我只用了這兩個模式,還有其他模式);數據類型為aes類內部的枚舉量
iv 偏移量這個參數在?ECB?模式下不需要,在?CBC?模式下需要;數據類型為byte

下面簡單的一個例子ECB模式加密解密 :

from Crypto.Cipher import AESpassword = b'1234567812345678' #秘鑰,b就是表示為bytes類型
text = b'abcdefghijklmnhi' #需要加密的內容,bytes類型
aes = AES.new(password,AES.MODE_ECB) #創建一個aes對象
# AES.MODE_ECB 表示模式是ECB模式
en_text = aes.encrypt(text) #加密明文
print("密文:",en_text) #加密明文,bytes類型
den_text = aes.decrypt(en_text) # 解密密文
print("明文:",den_text)

輸出:

密文: b'WU\xe0\x0e\xa3\x87\x12\x95\\]O\xd7\xe3\xd4 )'
明文: b'abcdefghijklmnhi'

以上是針對ECB模式的加密解密,從這個例子中可以看出參數中有幾個限制。

  1. 秘鑰必須為16字節或者16字節的倍數的字節型數據。
  2. 明文必須為16字節或者16字節的倍數的字節型數據,如果不夠16字節需要進行補全,關于補全規則,后面會在補全模式中具體介紹。

通過CBC模式例子:

from Crypto.Cipher import AES
password = b'1234567812345678' #秘鑰,b就是表示為bytes類型
iv = b'1234567812345678' # iv偏移量,bytes類型
text = b'abcdefghijklmnhi' #需要加密的內容,bytes類型
aes = AES.new(password,AES.MODE_CBC,iv) #創建一個aes對象
# AES.MODE_CBC 表示模式是CBC模式
en_text = aes.encrypt(text) 
print("密文:",en_text) #加密明文,bytes類型
aes = AES.new(password,AES.MODE_CBC,iv) #CBC模式下解密需要重新創建一個aes對象
den_text = aes.decrypt(en_text)
print("明文:",den_text)

輸出:

密文: b'\x93\x8bN!\xe7~>\xb0M\xba\x91\xab74;0'
明文: b'abcdefghijklmnhi'

通過上面CBC模式的例子,可以簡單看出CBC模式與ECB模式的區別:AES.new() 解密和加密重新生成了aes對象,加密和解密不能調用同一個aes對象,否則會報錯TypeError: decrypt() cannot be called after encrypt()。

總結:

1. 在Python中進行AES加密解密時,所傳入的密文、明文、秘鑰、iv偏移量、都需要是bytes(字節型)數據。python 在構建aes對象時也只能接受bytes類型數據。

2.當秘鑰,iv偏移量,待加密的明文,字節長度不夠16字節或者16字節倍數的時候需要進行補全。

3. CBC模式需要重新生成AES對象,為了防止這類錯誤,我寫代碼無論是什么模式都重新生成AES對象。


5.編碼模式

前面說了,python中的 AES 加密解密,只能接受字節型(bytes)數據。而我們常見的 待加密的明文可能是中文,或者待解密的密文經過base64編碼的,這種都需要先進行編碼或者解碼,然后才能用AES進行加密或解密。反正無論是什么情況,在python使用AES進行加密或者解密時,都需要先轉換成bytes型數據。

我們以ECB模式針對中文明文進行加密解密舉例:

from Crypto.Cipher import AESpassword = b'1234567812345678' #秘鑰,b就是表示為bytes類型
text = "好好學習天天向上".encode('gbk') #gbk編碼,是1個中文字符對應2個字節,8個中文正好16字節
aes = AES.new(password,AES.MODE_ECB) #創建一個aes對象
# AES.MODE_ECB 表示模式是ECB模式
print(len(text))
en_text = aes.encrypt(text) #加密明文
print("密文:",en_text) #加密明文,bytes類型
den_text = aes.decrypt(en_text) # 解密密文
print("明文:",den_text.decode("gbk")) # 解密后同樣需要進行解碼

輸出:

16
密文: b'=\xdd8k\x86\xed\xec\x17\x1f\xf7\xb2\x84~\x02\xc6C'
明文: 好好學習天天向上

對于中文明文,我們可以使用encode()函數進行編碼,將字符串轉換成bytes類型數據,而這里我選擇gbk編碼,是為了正好能滿足16字節,utf8編碼是一個中文字符對應3個字節。這里為了舉例所以才選擇使用gbk編碼。

在解密后,同樣是需要decode()函數進行解碼的,將字節型數據轉換回中文字符(字符串類型)。

現在我們來看另外一種情況,密文是經過base64編碼的(這種也是非常常見的,很多網站也是這樣使用的),我們用 http://tool.chacuo.net/cryptaes/ 這個網站舉例子:

模式:ECB

密碼: 1234567812345678

字符集:gbk編碼

輸出: base64

我們來寫一個python 進行aes解密:

from Crypto.Cipher import AES
import base64password = b'1234567812345678' 
aes = AES.new(password,AES.MODE_ECB) 
en_text = b"Pd04a4bt7Bcf97KEfgLGQw=="
en_text = base64.decodebytes(en_text) #將進行base64解碼,返回值依然是bytes
den_text = aes.decrypt(en_text)
print("明文:",den_text.decode("gbk")) 

輸出:

明文: 好好學習天天向上

這里的?b"Pd04a4bt7Bcf97KEfgLGQw=="?是一個bytes數據, 如果你傳遞的是一個字符串,你可以直接使用?encode()函數 將其轉換為 bytes類型數據。

from Crypto.Cipher import AES
import base64password = b'1234567812345678' 
aes = AES.new(password,AES.MODE_ECB) 
en_text = "Pd04a4bt7Bcf97KEfgLGQw==".encode() #將字符串轉換成bytes數據
en_text = base64.decodebytes(en_text) #將進行base64解碼,參數為bytes數據,返回值依然是bytes
den_text = aes.decrypt(en_text) 
print("明文:",den_text.decode("gbk")) 

因為無論是?utf8?和?gbk?編碼,針對英文字符編碼都是一個字符對應一個字節,所以這里**encode()**函數主要作用就是轉換成bytes數據,然后使用base64進行解碼。

hexstr,base64編碼解碼例子:

import base64
import binascii
data = "hello".encode()
data = base64.b64encode(data)
print("base64編碼:",data)
data = base64.b64decode(data)
print("base64解碼:",data)
data = binascii.b2a_hex(data)
print("hexstr編碼:",data)
data = binascii.a2b_hex(data)
print("hexstr解碼:",data)

輸出:

base64編碼: b'aGVsbG8='
base64解碼: b'hello'
hexstr編碼: b'68656c6c6f'
hexstr解碼: b'hello'

這里要說明一下,有一些AES加密,所用的秘鑰,或者IV向量是通過 base64編碼或者 hexstr編碼后的。針對這種,首先要進行的就是進行解碼,都轉換回 bytes數據,再次強調,python實現 AES加密解密傳遞的參數都是 bytes(字節型) 數據。

另外,我記得之前的 pycryptodome庫,傳遞IV向量時,和明文時可以直接使用字符串類型數據,不過現在新的版本都必須為 字節型數據了,可能是為了統一好記。

6.填充模式

前面我使用秘鑰,還有明文,包括IV向量,都是固定16字節,也就是數據塊對齊了。而填充模式就是為了解決數據塊不對齊的問題,使用什么字符進行填充就對應著不同的填充模式

AES補全模式常見有以下幾種:

模式意義
ZeroPadding用b’\x00’進行填充,這里的0可不是字符串0,而是字節型數據的b’\x00’
PKCS7Padding當需要N個數據才能對齊時,填充字節型數據為N、并且填充N個
PKCS5Padding與PKCS7Padding相同,在AES加密解密填充方面我沒感到什么區別
no padding當為16字節數據時候,可以不進行填充,而不夠16字節數據時同ZeroPadding一樣

這里有一個細節問題,我發現很多文章說的也是不對的。

ZeroPadding填充模式的意義:很多文章解釋是當為16字節倍數時就不填充,然后當不夠16字節倍數時再用字節數據0填充,這個解釋是不對的,這解釋應該是no padding的,而ZeroPadding是不管數據是否對其,都進行填充,直到填充到下一次對齊為止,也就是說即使你夠了16字節數據,它會繼續填充16字節的0,然后一共數據就是32字節。

這里可能會有一個疑問,為什么是16字節 ,其實這個是 數據塊的大小,網站上也有對應設置,網站上對應的叫128位,也就是16字節對齊,當然也有192位(24字節),256位(32字節)。

本文在這個解釋之后,后面就說數據塊對齊問題了,而不會再說16字節倍數了。

除了no padding 填充模式,剩下的填充模式都會填充到下一次數據塊對齊為止,而不會出現不填充的問題。

PKCS7Padding和 PKCS5Padding需要填充字節對應表:

明文長度值(mod 16)添加的填充字節數每個填充字節的值
0160x10
1150x0F
2140x0E
3130X0D
4120x0C
5110x0B
6100x0A
790x09
880x08
970x08
1060x07
1150x06
1240x05
1330x04
1420x03
1510x01

這里可以看到,當明文長度值已經對齊時(mod 16 = 0),還是需要進行填充,并且填充16個字節值為0x10。ZeroPadding填充邏輯也是類似的,只不過填充的字節值都為0x00,在python表示成 b'\x00'。

填充完畢后,就可以使用 AES進行加密解密了,當然解密后,也需要剔除填充的數據,無奈Python這些步驟需要自己實現(如果有這樣的庫還請評論指出)。

7.Python的完整實現:
from Crypto.Cipher import AES
import base64
import binascii# 數據類
class MData():def __init__(self, data = b"",characterSet='utf-8'):# data肯定為bytesself.data = dataself.characterSet = characterSetdef saveData(self,FileName):with open(FileName,'wb') as f:f.write(self.data)def fromString(self,data):self.data = data.encode(self.characterSet)return self.datadef fromBase64(self,data):self.data = base64.b64decode(data.encode(self.characterSet))return self.datadef fromHexStr(self,data):self.data = binascii.a2b_hex(data)return self.datadef toString(self):return self.data.decode(self.characterSet)def toBase64(self):return base64.b64encode(self.data).decode()def toHexStr(self):return binascii.b2a_hex(self.data).decode()def toBytes(self):return self.datadef __str__(self):try:return self.toString()except Exception:return self.toBase64()### 封裝類
class AEScryptor():def __init__(self,key,mode,iv = '',paddingMode= "NoPadding",characterSet ="utf-8"):'''構建一個AES對象key: 秘鑰,字節型數據mode: 使用模式,只提供兩種,AES.MODE_CBC, AES.MODE_ECBiv: iv偏移量,字節型數據paddingMode: 填充模式,默認為NoPadding, 可選NoPadding,ZeroPadding,PKCS5Padding,PKCS7PaddingcharacterSet: 字符集編碼'''self.key = keyself.mode = modeself.iv = ivself.characterSet = characterSetself.paddingMode = paddingModeself.data = ""def __ZeroPadding(self,data):data += b'\x00'while len(data) % 16 != 0:data += b'\x00'return datadef __StripZeroPadding(self,data):data = data[:-1]while len(data) % 16 != 0:data = data.rstrip(b'\x00')if data[-1] != b"\x00":breakreturn datadef __PKCS5_7Padding(self,data):needSize = 16-len(data) % 16if needSize == 0:needSize = 16return data + needSize.to_bytes(1,'little')*needSizedef __StripPKCS5_7Padding(self,data):paddingSize = data[-1]return data.rstrip(paddingSize.to_bytes(1,'little'))def __paddingData(self,data):if self.paddingMode == "NoPadding":if len(data) % 16 == 0:return dataelse:return self.__ZeroPadding(data)elif self.paddingMode == "ZeroPadding":return self.__ZeroPadding(data)elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":return self.__PKCS5_7Padding(data)else:print("不支持Padding")def __stripPaddingData(self,data):if self.paddingMode == "NoPadding":return self.__StripZeroPadding(data)elif self.paddingMode == "ZeroPadding":return self.__StripZeroPadding(data)elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":return self.__StripPKCS5_7Padding(data)else:print("不支持Padding")def setCharacterSet(self,characterSet):'''設置字符集編碼characterSet: 字符集編碼'''self.characterSet = characterSetdef setPaddingMode(self,mode):'''設置填充模式mode: 可選NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding'''self.paddingMode = modedef decryptFromBase64(self,entext):'''從base64編碼字符串編碼進行AES解密entext: 數據類型str'''mData = MData(characterSet=self.characterSet)self.data = mData.fromBase64(entext)return self.__decrypt()def decryptFromHexStr(self,entext):'''從hexstr編碼字符串編碼進行AES解密entext: 數據類型str'''mData = MData(characterSet=self.characterSet)self.data = mData.fromHexStr(entext)return self.__decrypt()def decryptFromString(self,entext):'''從字符串進行AES解密entext: 數據類型str'''mData = MData(characterSet=self.characterSet)self.data = mData.fromString(entext)return self.__decrypt()def decryptFromBytes(self,entext):'''從二進制進行AES解密entext: 數據類型bytes'''self.data = entextreturn self.__decrypt()def encryptFromString(self,data):'''對字符串進行AES加密data: 待加密字符串,數據類型為str'''self.data = data.encode(self.characterSet)return self.__encrypt()def __encrypt(self):if self.mode == AES.MODE_CBC:aes = AES.new(self.key,self.mode,self.iv) elif self.mode == AES.MODE_ECB:aes = AES.new(self.key,self.mode) else:print("不支持這種模式")  return           data = self.__paddingData(self.data)enData = aes.encrypt(data)return MData(enData)def __decrypt(self):if self.mode == AES.MODE_CBC:aes = AES.new(self.key,self.mode,self.iv) elif self.mode == AES.MODE_ECB:aes = AES.new(self.key,self.mode) else:print("不支持這種模式")  return           data = aes.decrypt(self.data)mData = MData(self.__stripPaddingData(data),characterSet=self.characterSet)return mDataif __name__ == '__main__':key = b"1234567812345678"iv =  b"0000000000000000"aes = AEScryptor(key,AES.MODE_CBC,iv,paddingMode= "ZeroPadding",characterSet='utf-8')data = "好好學習"rData = aes.encryptFromString(data)print("密文:",rData.toBase64())rData = aes.decryptFromBase64(rData.toBase64())print("明文:",rData)

我簡單的對其進行了封裝,加密和解密返回的數據類型可以使用toBase64(),toHexStr()?進行編碼。另外我沒有對key和iv進行補全,可以使用MData類自己實現,更多詳細使用可以通過源碼中注釋了解。

文章出自:

原文鏈接:https://blog.csdn.net/chouzhou9701/article/details/122019967

csdn上發現的一位大佬寫,怕找不到了就完全復制他是文章

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/712283.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/712283.shtml
英文地址,請注明出處:http://en.pswp.cn/news/712283.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

直觀理解卷積

卷積直觀理解 原文來自最容易理解的對卷積(convolution)的解釋 🎬個人簡介:一個全棧工程師的升級之路! 📋個人專欄:計算機雜記 🎀CSDN主頁 發狂的小花 🌄人生秘訣:學習的本質就是極致…

從經典學習 NLP:小白到大白:1. Word Tokenization

文章目錄 1 Word Tokenization1.1 Top-down/rule-based tokenization1.2 Byte-pair Encoding: A Bottom-up tokenization algorithm 1 Word Tokenization 來源:JM3 Chapter 2.5 p19-23 tokenization 就是 把 running text 分割成為 words; 常有兩種方…

AVL 樹

AVL樹的概念 二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化為單支樹,查找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數學家G.M.Adelson-Velskii和E.M.Landis在1962年 發明了一種解決…

k8s筆記26--快速實現prometheus監控harbor

k8s筆記26--快速實現prometheus監控harbor 簡介采集指標&配置grafana面板采集指標配置grafana面板 說明 簡介 harbor是當前最流行的開源容器鏡像倉庫項目,被大量IT團隊廣泛應用于生產、測試環境的項目中。本文基于Harbor、Prometheus、Grafana介紹快速實現監控…

3. 臺階問題

數樓梯 題目描述 樓梯有 N N N 階,上樓可以一步上一階,也可以一步上二階。 編一個程序,計算共有多少種不同的走法。 輸入格式 一個數字,樓梯數。 輸出格式 輸出走的方式總數。 樣例 #1 樣例輸入 #1 4樣例輸出 #1 5提示…

FPGA之帶有進位邏輯的加法運算

module ADDER( input [5:0]A, input [5:0]B,output[6:0]Q ); assign Q AB; endmodule 綜合結果如下圖所示: 使用了6個Lut,,6個LUT分布…

詳細介紹如何用windows11自帶Hyper-V安裝虛擬機

通過系統自帶的hyper-v安裝windows11,舒服又愜意,相比用第三方虛擬機軟件速度快很多。 硬件準備 1、對于電腦自帶的虛擬機Hyper-V,不是每種電腦系統版本都帶著的。我們先要確定您的系統符合 Hyper-V 的最低要求。我們跟著下面的步驟來執行&…

鴻蒙開發相關知識(四)【數據持久化(用戶首選項、關系型數據庫)、通知(基礎通知、進度條通知、通知意圖)】

文章目錄 一、數據持久化1、用戶首選項(1)語法說明(2)完整代碼示例 2、關系型數據庫(1)初始化數據庫(2)增刪改數據(3)查詢數據(4)完整…

《2023年勒索軟件攻擊態勢報告》

獲取方式: 鏈接:https://pan.baidu.com/s/1zd-yVsuGwJADyyGNFR_TIQ?pwd2lo0 提取碼:2lo0

探索數據結構:解鎖計算世界的密碼

?? 歡迎大家來到貝蒂大講堂?? 🎈🎈養成好習慣,先贊后看哦~🎈🎈 所屬專欄:數據結構與算法 貝蒂的主頁:Betty‘s blog 前言 隨著應用程序變得越來越復雜和數據越來越豐富,幾百萬、…

600萬訂單每秒Disruptor +SpringBoot,如何解決消息不丟失?

尼恩說在前面 在40歲老架構師 尼恩的讀者交流群(50)中,最近有小伙伴拿到了一線互聯網企業如得物、阿里、滴滴、極兔、有贊、shein 希音、百度、網易的面試資格,遇到很多很重要的面試題: Disruptor 官方說能達到每秒600w OPS訂單處理能力&…

Java——Object

1.Object萬類之祖 1.1 Object類型的概述 Object類是所有類型的頂層父類,所有類型的直接或者間接的父類;所有的類型中都含有Object類中的所有方法。 隨意定義一個類型,不手動顯式定義其父類,那么這個類的父類就是Object類 public Object() …

【C語言】指針初階2.0版本

這篇博文我們來繼續學習指針的其他內容 指針2.0 傳值調用與傳址調用傳值調用傳址調用 一維數組與指針理解數組名使用指針深入理解一維數組 二級指針指針數組二維數組與指針 傳值調用與傳址調用 在開始之前,我們需要先了解這個概念,后面才能夠正常的學習…

利用 Python 抓取數據探索汽車市場趨勢

一、引言 隨著全球對環境保護意識的增強和技術的進步,新能源汽車作為一種環保、高效的交通工具,正逐漸受到人們的關注和青睞。在這個背景下,對汽車市場的數據進行分析和研究顯得尤為重要。 本文將介紹如何利用 Python 編程語言,結…

VSCode上搭建C/C++開發環境(vscode配置c/c++環境)Windows系統---保姆級教程

引言勸退 VSCode,全稱為Visual Studio Code,是由微軟開發的一款輕量級,跨平臺的代碼編輯器。大家能來搜用VSCode配置c/c,想必也知道VSCode的強大,可以手握一個VSCode同時編寫如C,C,C#&#xff…

微服務day02-Ribbon負載均衡與Nacos安裝與入門

一.Ribbon負載均衡 在上一節中,我們通過在RestTemplte實例中加上了注解 LoadBalanced,表示將來由RestTemplate發起的請求會被Ribbon攔截和處理,實現了訪問服務時的負載均衡,那么他是如何實現的呢? 1.1 Ribbon負載均衡的原理 Rib…

鏈表的歸并排序-LeetCode(Python版)

雙指針歸并排序!圖解排序鏈表!-知乎 class ListNode(object):def __init__(self, val0, nextNone):self.val valself.next nextclass Solution(object):def find_mid(self, head): # 快慢指針slow, fast head, headwhile fast.next and fast.next.n…

linux 硬盤存儲剩余容量自動化監控+報警通知

linux 硬盤存儲剩余容量自動化監控報警通知 編寫shell腳本 #!/bin/bash# 獲取系統存儲大小(單位為GB) storage_size$(df -h / | awk NR2 {print $4} | sed s/G//)# 閾值(小于10GB觸發報警) threshold10# 釘釘機器人 Webhook UR…

LabVIEW非接觸式電阻抗層析成像系統

LabVIEW非接觸式電阻抗層析成像系統 非接觸式電阻抗層析成像(NEIT)技術以其無輻射、非接觸、響應速度快的特點,為實時監測提供了新的解決方案。基于LabVIEW的電阻抗層析成像系統,實現了數據的在線采集及實時成像,提高…

代碼隨想錄算法訓練營第四十四天|139.單詞拆分、56.攜帶礦石資源

139.單詞拆分 思路:將字符串s看作為背包容量,從字符串中獲取物品,剛好滿足背包容量的過程,因為可以從字符串中多次取值,相當于物品的數量是不限制,這就是一個完全背包的問題!這個題有個關鍵點&a…