参考项目:pyaes
之前仿照Github上的项目Python-File-Encryptor,分别在两台计算机上建立了加密端和解密端,加密端将用来充当实验品的文件加密,解密端负责远程拷贝加密后的文件再解密文件。
加密端代码:
from Crypto import Random
from Crypto.Cipher import AES
import os
import os.path
Key = b'\x60\x3d\xeb\x10\x15\xca\x71\xbe\x2b\x73\xae\xf0\x85\x7d\x77\x81\x1f\x35\x2c\x07\x3b\x61\x08\xd7\x2d\x98\x10\xa3\x09\x14\xdf\xf4'
def pad(s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def Encryption(file):
with open(r'E:\EncryptionTest\FileEncryption' + '\\' + file, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, Key)
with open(r'E:\EncryptionTest\NewFile' + '\\'+ file, 'wb') as fo:
fo.write(enc)
os.remove(r'E:\EncryptionTest\FileEncryption'+ '\\'+ file)
# 打开文件夹并统计文件个数
path = r'E:\EncryptionTest\FileEncryption' # 输入文件夹地址
files = os.listdir(path) # 读入文件夹
for file in files:
Encryption(file)
解密端解密:
#!/usr/bin/env python3
import os
import paramiko
import unicodedata
import sys
from scp import SCPClient
from Crypto import Random
from Crypto.Cipher import AES
import os.path
from os import listdir
from os.path import isfile, join
import time
Key = b'\x60\x3d\xeb\x10\x15\xca\x71\xbe\x2b\x73\xae\xf0\x85\x7d\x77\x81\x1f\x35\x2c\x07\x3b\x61\x08\xd7\x2d\x98\x10\xa3\x09\x14\xdf\xf4'
def decrypt(ciphertext, key):
iv = ciphertext[:AES.block_size]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return plaintext.rstrip(b"\0")
def decrypt_file(file_name):
with open(file_name, 'rb') as fo:
ciphertext = fo.read()
os.remove(file_name)
dec = decrypt(ciphertext, Key)
with open(file_name, 'wb') as fo:
fo.write(dec)
destination = sys.argv[1] # 目的文件路径
print('\n'+destination+'\n')
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(address, 22, username, password)
scp = SCPClient(client.get_transport())
scp.get(os.path.join('E:/EncryptionTest/NewFile'), destination, recursive=True)
time.sleep(1200)
scp.close()
client.close()
path = destination + '/' + 'NewFile'
files = os.listdir(path)
for file in files:
decrypt_file(path+'/'+file)
然而,在实验中一些文件出现了损坏。打不开了。
接下来分析Github项目pyaes:
这其实是一个算法库,不是一个单独的应用程序,要用到里面的东西可以调用,先看看作者给的README文件:
AES常见的操作模式包括CBC, CFB, CTR, ECB和OFB
通常推荐CBC和CTR模式,ECB模式不推荐
当然,这几种模式都需要密钥和初始化向量,比如这样:
import pyaes
# A 256 bit (32 byte) key
key = "This_key_for_demo_purposes_only!"
# For some modes of operation we need a random initialization vector
# of 16 bytes
iv = "InitializationVe"
由于大多数操作模式需要特定块大小或段大小的块中的数据,因此在处理大型任意数据流或数据字符串时可能会很困难。为此,函数库提供了BlockFeeder,具体的操作方法如下:
import pyaes
# Any mode of operation can be used; for this example CBC
key = "This_key_for_demo_purposes_only!"
iv = "InitializationVe"
ciphertext = ''
# We can encrypt one line at a time, regardles of length
encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv))
for line in file('/etc/passwd'):
ciphertext += encrypter.feed(line)
# Make a final call to flush any remaining bytes and add paddin
ciphertext += encrypter.feed()
# We can decrypt the cipher text in chunks (here we split it in half)
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
decrypted = decrypter.feed(ciphertext[:len(ciphertext) / 2])
decrypted += decrypter.feed(ciphertext[len(ciphertext) / 2:])
# Again, make a final call to flush any remaining bytes and strip padding
decrypted += decrypter.feed()
print file('/etc/passwd').read() == decrypted
在上述这段代码中,属于加密部分的是:
# We can encrypt one line at a time, regardles of length
encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv))
for line in file('/etc/passwd'):
ciphertext += encrypter.feed(line)
# Make a final call to flush any remaining bytes and add paddin
ciphertext += encrypter.feed()
属于解密部分的是:
# We can decrypt the cipher text in chunks (here we split it in half)
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
decrypted = decrypter.feed(ciphertext[:len(ciphertext) / 2])
decrypted += decrypter.feed(ciphertext[len(ciphertext) / 2:])
# Again, make a final call to flush any remaining bytes and strip padding
decrypted += decrypter.feed()
关于Blockfeeder中的Encrypter类和Decrypter类,抽出源码部分:
class BlockFeeder(object):
'''The super-class for objects to handle chunking a stream of bytes
into the appropriate block size for the underlying mode of operation
and applying (or stripping) padding, as necessary.'''
def __init__(self, mode, feed, final, padding = PADDING_DEFAULT):
self._mode = mode
self._feed = feed
self._final = final
self._buffer = to_bufferable("")
self._padding = padding
def feed(self, data = None):
'''Provide bytes to encrypt (or decrypt), returning any bytes
possible from this or any previous calls to feed.
Call with None or an empty string to flush the mode of
operation and return any final bytes; no further calls to
feed may be made.'''
if self._buffer is None:
raise ValueError('already finished feeder')
# Finalize; process the spare bytes we were keeping
if data is None:
result = self._final(self._buffer, self._padding)
self._buffer = None
return result
self._buffer += to_bufferable(data)
# We keep 16 bytes around so we can determine padding
result = to_bufferable('')
while len(self._buffer) > 16:
can_consume = self._mode._can_consume(len(self._buffer) - 16)
if can_consume == 0: break
result += self._feed(self._buffer[:can_consume])
self._buffer = self._buffer[can_consume:]
return result
class Encrypter(BlockFeeder):
'Accepts bytes of plaintext and returns encrypted ciphertext.'
def __init__(self, mode, padding = PADDING_DEFAULT):
BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding)
class Decrypter(BlockFeeder):
'Accepts bytes of ciphertext and returns decrypted plaintext.'
def __init__(self, mode, padding = PADDING_DEFAULT):
BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding)
列举一下其中调用的其他的函数和变量:
(1)
PADDING_DEFAULT = 'default'
(2)
def to_bufferable(binary):
if isinstance(binary, bytes):
return binary
return bytes(ord(b) for b in binary)
def to_bufferable(binary):
return binary
(3)
def _block_can_consume(self, size):
if size >= 16: return 16
return 0
def _stream_can_consume(self, size):
return size
def _segment_can_consume(self, size):
return self.segment_bytes * int(size // self.segment_bytes)
AESSegmentModeOfOperation._can_consume = _segment_can_consume
AESStreamModeOfOperation._can_consume = _stream_can_consume
AESBlockModeOfOperation._can_consume = _block_can_consume