1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
'''mcrypt wrapper for Python 2 and 3
Copyright (C) 2010 Michael B. Trausch <mike@trausch.us>
License: GPL v3 as published by the Free Software Foundation
The EncryptedWriter module is used to create encrypted data streams
from plaintext data streams. The output data stream contains a header
(which EncryptedReader understands) to identify the decryption
parameters, and a footer to specify how much padding is present (if
padding is necessary).
While the EncryptedWriter class can be used to create files that are
encrypted, it is probably not a terribly good idea to do so. There is
absolutely no protection against corruption or tampering in this
class. The user can provide such protection for him or herself, if
necessary, or use the HashedEncryptedReader/Writer classes to generate
files where tampering and/or corruption can be detected.'''
import io
import platform
import struct
from .libmcrypt import *
from .common import ALGO_MAP, MODE_MAP
class EncryptedWriter(io.BufferedWriter):
'''EncryptedWriter(raw, passwd[, algo[, mode]]) -> EncryptedWriter
A sequential, writable, block-oriented encrypted stream writer
This is just like io.BufferedWriter, but it encrypts data as it
writes. The 'raw' parameter is passed to the io.BufferedWriter
constructor, and the password parameter is required in order to
create the key.
The stream will have a header written to it, which identifies the
file format to an EncryptedReader object, as well as certain
parameters required for decryption: the algorithm, the mode, the
salt, and IV (if applicable). The PBKDF2 key derivation function
is the only KDF used for generating the keys for these files.'''
def __init__(self, raw, password, algo = ALGO_DEFAULT,
mode = MODE_DEFAULT):
pyver = platform.python_version_tuple()
if pyver[0] == '3':
self._parentProxy = super()
else:
self._parentProxy = super(io.BufferedWriter, self)
self._parentProxy.__init__(raw)
self._encrypter = raw_encrypt(algo, mode)
self._encrypter.set_parameters(password)
self._encrypter.begin()
self._write_header()
self._buffer = bytearray()
if self._encrypter.get_iv_len() > 0:
self.write(self._encrypter.get_iv())
return
def close(self):
'''close([pending_behavior]) -> None
Closes the EncryptedWriter stream. If there are any bytes
pending flushing, padding will be appended to them. Lastly,
two blocks will be written to the stream: a sentinel block and
the footer block, which contains the padding length for the
last block.'''
if len(self._buffer) == 0:
self._parentProxy.close()
return
to_write = bytes(self._buffer)
pcount = self._padding(len(to_write))
to_write += b'\xff' * pcount
self.write(to_write)
blksz = self._encrypter.get_block_size()
self._pwrite(b'\xff' * blksz)
self._pwrite(struct.pack('!H' + ('x' * (blksz - 2)),
pcount))
self._parentProxy.close()
self._encrypter.end()
return
def get_block_size(self):
return self._encrypter.get_block_size()
def write(self, bytestr):
'''write(bytestr) -> int
Write the supplied bytes to the stream.'''
blksz = self._encrypter.get_block_size()
self._buffer.extend(bytestr)
buflen = len(self._buffer)
(blocks, leftover) = divmod(buflen, blksz)
if(blocks > 0):
self._write_blocks(blocks)
return
def _padding(self, bytes):
blksz = self._encrypter.get_block_size()
pad_len = blksz - (bytes % blksz)
if pad_len == self._encrypter.get_block_size():
return 0
return pad_len
def _pwrite(self, bytestr):
return self._parentProxy.write(bytestr)
def _write_blocks(self, block_count):
'''_write_blocks(bytes) -> int
Returns the bytes written'''
byte_count = block_count * self._encrypter.get_block_size()
bytesTR = bytes(self._buffer[:byte_count])
self._buffer = self._buffer[byte_count:]
encBytes = self._encrypter.step(bytesTR)
return self._pwrite(encBytes)
def _write_header(self):
'''_write_header() -> None
Writes metainfo header to make decryption possible.'''
def _pack(length):
return(struct.pack('!B', length))
self._pwrite(b'EW0\x00')
enc = self._encrypter
params = enc.get_parameters()
iv = enc.get_iv()
salt = enc.get_salt()
_a = params['algorithm'].encode('utf8')
_m = params['mode'].encode('utf8')
self._pwrite(_pack(ALGO_MAP[_a]))
self._pwrite(_pack(MODE_MAP[_m]))
self._pwrite(_pack(len(salt)))
self._pwrite(salt)
if iv is not None:
self._pwrite(_pack(len(iv)))
self._pwrite(iv)
else:
self._pwrite(b'\x00')
cpos = self.tell()
blksz = enc.get_block_size()
pad_len = blksz - (cpos % blksz)
if pad_len != blksz:
self._pwrite(b'\xff' * pad_len)
|