前言:
因為主管資料,資料安全問題比較常見,最近遇到一個業務場景,較多資料加密的處理,結合最近優化資料安全流程的問題,重新梳理下資料加密的知識,
定義
資料加密:對原來為明文的檔案或資料按某種演算法進行處理,使其成為不可讀的一段代碼為“密文”,只能在輸入相應的密鑰之后才能顯示出原容,通過這樣的途徑來達到保護資料不被非法人竊取、閱讀的目的, 該程序的逆程序為解密,即將該編碼資訊轉化為其原來資料的程序,
簡單理解:
演算法分類
目前主流的分法有2類,對稱加密、非對稱加密,當然也還有分3類的,新增單向加密(Hash演算法),取決于每個人的理解,
| 演算法 | 對稱加密 | 非對稱加密 | 單向加密 |
|---|---|---|---|
| 類別 | AES / DES / 3DES | RSA / DSA | MD5,SHA系列 |
| 描述 | 資料加解密用相同密鑰 | 公鑰加密,加解密不同密鑰 | 只可加密不可解密 |
| 解決問題 | 資料的機密性 | 身份驗證 | 資料的完整性 |
| 優點 | 加密速度快 | 加解密密鑰不一致,公鑰公開,密鑰安全 | |
| 缺點 | 密鑰一致容易泄露 | 加密速度慢 | |
| 安全性 | AES高 > 3DES中 > DES低 | 均高 | SHA-1高>MD中 |
| 速度 | AES快 > DES中 > 3DES慢 | RSA中>ECC慢 | MD5快>SHA-1慢 |
| 資源消耗 | 3DES高 > DES中 > AES低 | ECC高>RSA中 | |
| 適用范圍 | 內部系統,適合大資料量M位元級/秒 | 小資料量/資料簽名 | 極少 |
| 其他 | 密鑰:AES(128/192/256位)/3DES(112/168位)/DES(56位) | 成熟度:高 |
主要包介紹
因為平時主要用python處理,所以主要介紹下常用的包
| 模塊名 | 描述 |
|---|---|
| base64 | 用于二進制資料與ASCII字符的轉換操作,提供了基于Base16, Base32, 和Base64演算法以及實際標準Ascii85和Base85的編碼和解碼函式 |
| hashlib | 提供常見的單向加密演算法(如MD5,SHA等),每種演算法都提供了與其同名的函式實作 |
| hmac | 實作hmac單向加密演算法,支持設定一個額外的密鑰(通常被稱為’salt’)來提高安全性 |
| secrets | Python3.6 新增的模塊,用于獲取安全亂數 |
| pycrypto | 持單向加密、對稱加密和公鑰加密以及亂數操作,該包是Python中密碼學最有名的,2012年已停止,幸運的是,該專案的分支PyCrytodome 取代了 PyCrypto, |
演算法詳解
1. base64
- 網路上最常見的用于傳輸8Bit位元組碼的編碼方式之一,是一種基于64個可列印字符來表示二進制資料的方法,
- 從二進制到字符的程序,可用于在HTTP環境下傳遞較長的標識資訊,
- 由于以上優點被廣泛應用于計算機的各個領域,由于輸出內容中包括兩個以上“符號類”字符(+, /, =),不同的應用場景又分別研制了Base64的各種“變種”,為統一和規范化Base64的輸出,Base62x被視為無符號化的改進版本,
1.1 實作原理
- 先將字符轉化為ASCII碼
- ASCII碼轉化為8位二進制
- 將二進制3個位元組歸成一組,每3個位元組在編碼之后被轉換為4個位元組一組(如一個資料有6個位元組,可編碼后將包含6/3*4=8個位元組,不足3個在后邊補0或“=”)
- 統一在6位二進制前補兩個0湊足8位,
- 將補0后的二進制轉為十進制,
- 從Base64編碼表獲取十進制對應的Base64編碼,

1.2 代碼實作
import base64
# 編碼 encode
# 想將字串轉編碼成base64,要先將字串轉換成二進制資料
tmp = "this is a test message:四叔的旅途."
res = tmp.encode("utf-8") # 默認以 utf-8 編碼
res_code = base64.b64encode(res) # 被編碼的引數必須是二進制資料
print(res_code)
# 解碼 decode
res_code = "b'dGhpcyBpcyBhIHRlc3QgbWVzc2FnZS4="
res = base64.b64decode(res_code).decode("utf-8")
print(res)
2.MD5 演算法
2.1 原理
一種資訊摘要演算法,以512位分組來處理輸入的資訊,每一分組又被劃分為16個32位子分組,經過了一系列的處理后,演算法的輸出由四個32位分組組成,將這四個32位分組級聯后將生成一個128位散列值,
簡單來說,即把一段資訊(明文),通過一種有損的方式壓縮成定長的字符(32位密文),因為這種壓縮方式是會損失資訊的,所以是無法還原出“明文”的,即不可逆,
雖然無法從數學上破解MD5演算法,但由于現在計算機具備強大的計算能力,可以通過“窮舉法”破解該演算法,如果想用該演算法加密,還可以通過**“加鹽”**的方式提高解密難度,演算法允許多傳一個引數"salt",指定通過MD5加密的次數,這樣是能夠提高解密難度的,
2.2 應用場景
常用于不可還原的密碼存盤、資訊完整性校驗,如:
- 密碼管理(加密注冊用戶的密碼)、電子簽名;
- 網站用戶上傳圖片 / 檔案后,將MD5值作為檔案名,(MD5因不可逆,可保證唯一性)
- 比較兩個檔案是否被篡改,(在下載資源的時候,發現網站提供了MD5值,就是用來檢測檔案是否被篡改)
- key-value資料庫中使用MD5值作為key,
2.3 代碼實作
# 由于MD5模塊在python3中被移除,在python3中使用hashlib模塊進行md5操作,
# pip install md5/import md5 會報錯
# ERROR: Could not find a version that satisfies the requirement md5
# ERROR: No matching distribution found for md5
# 寫法1
import hashlib
tmp_text = 'test message:四叔的旅途'
md5 = hashlib.md5() # 生成MD5物件
md5.update(tmp_text.encode('utf-8')) # 對資料加密 該方法只接受byte型別,否則會報錯,所以要在引數前添加b來轉換型別的原因, 后面方法
tmp_text = md5.hexdigest() # 獲取密文 hexdigest 十六進制的意思
print(tmp_text)
# or:
tmp_text = 'test message:四叔的旅途'
tmp_text = hashlib.md5(bytes(tmp_text,encoding = 'utf-8')).hexdigest()
# 不同寫法
tmp_text = hashlib.md5(tmp.encode("utf-8")).hexdigest() # 最簡單寫法,推薦☆
tmp_pic = hashlib.md5(b'test message:四叔的旅途').hexdigest() # 最常見的寫法,常用于圖片的命名,因為該方法只接受byte型別,否則會報錯,所以要在引數前添加b來轉換型別的原因
tmp_text = hashlib.new('md5', b'test message:四叔的旅途').hexdigest() # hashlib.new(name[, data]),name傳入的是哈希加密演算法的名稱,如md5
# MD5加鹽值(SALT)
tmp_text = 'test message:四叔的旅途'
# 生成MD5物件
md5 = hashlib.md5(b'~!@@#!#$DFDT@#$@#')
# 以下兩種方式與上面效果等同
# md5 = hashlib.md5('~!@@#!#$DFDT@#$@#'.encode('utf-8'))
# md5 = hashlib.md5(bytes('~!@@#!#$DFDT@#$@#',encoding='utf-8'))
3. SHA 演算法
3.1 原理
安全哈希演算法(英語:Secure Hash Algorithm,縮寫為SHA)一個密碼哈希函式家族,是FIPS所認證的安全哈希演算法,
- SHA家族的五個演算法:SHA-1、SHA-2(SHA-224、SHA-256、SHA-384和SHA-512),由美國國家安全域(NSA)所設計,并由美國國家標準與技術研究院(NIST)發布,是美國的政府標準,
- SHA-1在許多安全協定中廣為使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視為是MD5的后繼者,基于MD5,加密后的資料長度更長,它對長度小于264的輸入,產生長度為160bit的散列值,比MD5多32位,因此,比MD5更加安全,但SHA1的運算速度就比MD5要慢,
SHA與MD5的不同:
- 對強行攻擊的安全性,SHA-1摘要比MD5摘長,對強行攻擊有更大的強度,
- 對密碼分析的安全性,由于MD5的設計,易受密碼分析的攻擊,SHA-1顯得不易受這樣的攻擊,
- 在相同的硬體上,SHA-1的運行速度比MD5慢,
3.2 代碼實作
# md5和sha1 同一個包,均為不可逆演算法,所以寫法上基本一致;
import hashlib
tmp = "test message:四叔的旅途"
tmp_text = hashlib.sha1(tmp.encode("utf-8")).hexdigest()
print("sha1加密前為 :",tmp)
print("sha1加密前后 :",tmp_text)
4. AES 演算法
高級加密標準(Advanced Encryption Standard,縮寫:AES),在密碼學中又稱Rijndael加密法,速度快,編碼緊湊,
AES技術是一種對稱的分組加密技術,使用128位分組加密資料,提供比WEP/TKIPS的RC4演算法更高的加密強度,其加密碼表和解密碼表是分開的,并且支持子密鑰加密,這種做法優于以前用一個特殊的密鑰解密的做法,
AES演算法優點:
- 強安全性、高性能、高效率、易用和靈活
- 其獨特并行性可以有效地利用處理器資源
- 應用范圍廣、等待時間短、相對容易隱藏、吞吐量高等優點,在性能等各方面都優于WEP演算法,
AES演算法缺點: - 對硬體要求比較高,因此無法通過在原有設備上升級韌體實作,必須重新設計芯片
AES加密演算法作為新一代的資料加密標準匯聚了強安全性、高性能、高效率、易用和靈活等優點,
AES設計有三個密鑰長度:128,192,256 位,
相對而言,AES的128密鑰比DES的56密鑰強了1021倍
4.2 原理
加密方和解密方保有同一個16位長的密鑰,使用AES演算法加解密時需要傳入該密鑰引數,通過Java實作AES演算法提供的工具包加密后回傳的是一個Base64格式的位元組陣列,因此為保證密文“可讀性”,需要在加密后對密文進行Base64編碼,解密前進行Base64解碼成密文,
AES演算法的安全性,取決于密鑰的安全性,因此一定不要在加解密的URL中傳入該密鑰引數,不然沒有意義,一般的做法是:前后端協商好密鑰,或者通過不對稱加密的方式傳遞密鑰,
4.3 實作方法
AES本質上是一個基本演算法,實作AES有5種模式:ECB、CBC、CFB和OFB、CTR,最常用的是ECB 和 CBC 模式,
-
ECB模式(電子密碼本模式:Electronic codebook)
最簡單的塊密碼加密模式,加密前根據加密塊大小(如AES為128位)分成若干塊,之后將每塊使用相同的密鑰單獨加密,解密同理, -
CBC模式(密碼分組鏈接:Cipher-block chaining)
對于每個待加密的密碼塊在加密前會先與前一個密碼塊的密文異或然后再用加密器加密,第一個明文塊與一個叫初始化向量的資料塊異或, -
CFB模式(密文反饋:Cipher feedback)
與ECB和CBC模式只能夠加密塊資料不同,CFB能夠將塊密文(Block Cipher)轉換為流密文(Stream Cipher), -
OFB模式(輸出反饋:Output feedback)
OFB是先用塊加密器生成密鑰流(Keystream),然后再將密鑰流與明文流異或得到密文流,解密是先用塊加密器生成密鑰流,再將密鑰流與密文流異或得到明文,由于異或操作的對稱性所以加密和解密的流程是完全一樣的,
4.4 AES代碼實作
這里代碼實作的坑有點多,務必注意幾個事項:
4.4.1 AES加密的幾個引數
- 秘鑰:加解密的秘鑰,必須是16 or 24 or 32bits(因為python3的字串是unicode編碼,需要 encode才可以轉換成位元組型資料),這里采坑很多
- 明文:需加密的引數,位元組長度需要是16位的倍數
- 模式:5種,常用的是 ECB 和 CBC
- iv偏移量:這個引數在 ECB 不需要,CBC 需要
"""
ECB模式,不需要iv偏移量引數
"""
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
# 16bits 不足的空格補16的倍數 不寫這個會采坑
def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('\0' * add)
return text.encode('utf-8')
# 加密函式
def encrypt(text):
key = '1234567891234567'.encode('utf-8') # 需16bits
mode = AES.MODE_ECB # 模式確認
text = add_to_16(text)
cryptos = AES.new(key, mode) # 加密操作
cipher_text = cryptos.encrypt(text) # 密文
return b2a_hex(cipher_text)
# 解密后,用strip()去掉補足的空格,不去解密后結果不一致
def decrypt(text):
key = '1234567891234567'.encode('utf-8') # 加解密需同一個
mode = AES.MODE_ECB # 同一個key需同一個模式解
cryptor = AES.new(key, mode)
plain_text = cryptor.decrypt(a2b_hex(text)) # 解密后原文,因為AES加密時候得到的字串不一定是ascii字符集的,輸出or保存時候可能報錯,所以這里統一把加密后的字串轉化為16進制字串
return bytes.decode(plain_text).rstrip('\0')
if __name__ == '__main__':
jia_m = encrypt("test message:四叔的旅途") # 加密
jie_m = decrypt(jia_m) # 解密
print("原文加密:", jia_m)
print("密文解密:", jie_m)
"""
原文加密: b'c554fb642755944d9b890f90bcbc37d0ca26f34cf3fb7cbf1d446ba75148b4ea'
密文解密: test message:四叔的旅途
"""
"""
CBC模式,引數要齊 + iv偏移量引數
在引數前添加b來轉換型別的原因:資料加密方法 接受byte型別,否則會報錯,前文md5中updata有提;
"""
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
# 16bits 不足的空格補16的倍數 不寫這個會采坑
def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('\0' * add)
return text.encode('utf-8')
# 加密函式
def encrypt(text):
key = '1234567891234567'.encode('utf-8') # 同樣16bits
mode = AES.MODE_CBC
iv = b'abcdefghijklmnop' # 偏移量,參見前文加b原因
text = add_to_16(text) # 文本補足16bits
cryptos = AES.new(key, mode, iv)
cipher_text = cryptos.encrypt(text) # 解密后原文,因為AES加密時候得到的字串不一定是ascii字符集的,輸出or保存時候可能報錯,所以這里統一把加密后的字串轉化為16進制字串
return b2a_hex(cipher_text)
# 解密后,用strip()去掉補足的空格
def decrypt(text):
key = '1234567891234567'.encode('utf-8')
iv = b'abcdefghijklmnop'
mode = AES.MODE_CBC # 同一個key需同一個模式解
cryptos = AES.new(key, mode, iv)
plain_text = cryptos.decrypt(a2b_hex(text)) # 解密后原文
return bytes.decode(plain_text).rstrip('\0')
if __name__ == '__main__':
jia_m = encrypt("test message:四叔的旅途") # 加密
jie_m = decrypt(jia_m) # 解密
print("原文加密:", jia_m)
print("密文解密:", jie_m)
"""
原文加密: b'c07638565f2944ae4db4366d361f4af42b65fed9f4df0589e20461e5f5618814'
密文解密: test message:四叔的旅途
"""
5. DES/3DES 演算法
DES演算法:密碼體制中的對稱密碼體制,又被稱為美國資料加密標準,是一個分組加密演算法,典型的DES以64位為分組對資料加密,加密和解密用的是同一個演算法,
5.1 加密原理
DES加密程序:
接收一個明文盒一個64位的密鑰key,明文字串會被轉換為對各64位的塊,加密程序以塊位單位,經過初態轉換,16輪回圈加密,終態轉換,最終每個64位的塊都會被加密成一個64位的密文塊,將得到的密文塊拼起來,得到的就是最終加密后的結果,
具體演算法流程圖如下 : orz…看了n遍才看懂…

5.2 代碼實作
"""
查了好多資料,DES 有比較成熟的包,不搞一堆回圈加密的代碼...
"""
# pip install pyDes
from pyDes import des, CBC, PAD_PKCS5
import binascii
# 秘鑰 真的是采坑不斷....
# 在使用pyDes實作DES加密時,初始化des是必須要8位密碼的,超過8位報錯常見于使用Python實作JavaScript的場景中,從JavaScript獲取的密鑰有時不止8位... MD..Python問題...
# 解決辦法:可通過使用一個隨意的8位密鑰先初始化des物件,然后呼叫setkey()方法傳入超過8位的密鑰重置
# from pyDes import des, PAD_PKCS5
# import base64
# encry = des("0" * 8)
# encry.setKey("123456789") # 可設定超過8bits
# result = encry.encrypt("密文".encode(), padmode=PAD_PKCS5)
# base = base64.b64encode(result).decode()
# print '2xC7EUPxQY4='
key = 'lz@lzljn'
def encrypt(s):
"""
DES 加密
:param s: 原始字串
:return: 加密后字串,16進制
"""
iv = key # 偏移
# secret_key:加密密鑰,CBC:加密模式,iv:偏移, padmode:填充
des_obj = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
# 回傳為位元組
secret_bytes = des_obj.encrypt(s, padmode=PAD_PKCS5)
# 回傳為16進制
return binascii.b2a_hex(secret_bytes)
def descrypt(s):
"""
DES 解密
:param s: 加密后的字串,16進制
:return: 解密后的字串
"""
iv = key
des_obj = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
decrypt_str = des_obj.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
return decrypt_str
print("原文加密:",encrypt("iloveullllz"))
print("密文解密:",descrypt("3b0754717de0a05d7cb6ae88125c09d5"))
"""
b'3b0754717de0a05d7cb6ae88125c09d5'
b'iloveullllz'
"""
5.3 3DES
3DES(或稱為Triple DES)是三重資料加密演算法(TDEA,Triple Data Encryption Algorithm)塊密碼的通稱,是DES向AES過渡的加密演算法,此處不做詳細介紹了,基本和上面一致,
6. RSA 演算法
6.1 冬至吃餃子,改天在寫…
###### 題外話
希望本文有大家有幫助, 如有錯誤,歡迎指正,
轉載請注明原文鏈接:
https://blog.csdn.net/weixin_41613094/article/details/122061095
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/389076.html
標籤:其他
下一篇:畢業五年的經歷所想
