I have tried to write a more user-friendly AES CTR wrapper with PyCrypto, but I'm not sure if it's safe enough. I mean, by default there is IV=1 for CTR and in documentation it's said that IV is ignored anyway, so I'm not sure if I should use it or not (or if it's even necessary rather than overkill).
This code should be for files on a local disk, nothing to do with networking. The code saves initial part of a Counter
i.e. secret.secret
to a separate file together with the encrypted one (two files). Counter (or rather parts of it) in the script is incremented always by 1 and if it comes to 255 (which seems to throw some error), then it resets to 0 and starts again, so for each block for file stream there should be a unique counter (go print it in the while
loop), therefore each block should be safe enough if only user knows the key and the counter incrementing isn't something obvious as +1
, but rather something more complex such as working with %
and remainders.
- Is my code safe to use in public? Of course this is only an example, counter incrementing is much more complex than
+1
. - Is my code still safe if I reveal how Counter is incrementing and someone has access to
.ctr
file if he/she doesn't know the key(32)?
Note: the key(32) is generated with user's input combined with a machine's constant + salt and other stuff. User has to input password for each usage of the code and user's input isn't stored anywhere.
import os
import array
from Crypto.Cipher import AES
class Secret(object):
def __init__(self, secret=None):
if secret is None:
secret = os.urandom(16)
self.secret = secret
self.reset()
def counter(self):
for i, c in enumerate(self.current):
if c + 1 == 255:
self.current[i] = 0
else:
self.current[i] = c + 1
return self.current.tostring()
def reset(self):
self.current = array.array('B', self.secret)
class Vial(object):
def __init__(self, key):
self.key = key
def encrypt(self, text, counter_path):
secret = Secret()
with open(counter_path, 'wb') as f:
f.write(secret.secret)
crypto = AES.new(self.key, AES.MODE_CTR, counter=secret.counter)
encrypted = crypto.encrypt(text)
return encrypted
def decrypt(self, text, counter_path):
with open(counter_path, 'rb') as f:
load_secret = f.read()
secret = Secret(load_secret)
crypto = AES.new(self.key, AES.MODE_CTR, counter=secret.counter)
decrypted = crypto.decrypt(text)
return decrypted
def encrypt_stream(self, input, output):
secret = Secret()
counter_path = os.path.splitext(output.name)[0] + '.ctr'
with open(counter_path, 'wb') as f:
f.write(secret.secret)
crypto = AES.new(self.key, AES.MODE_CTR, counter=secret.counter)
while True:
data = input.read(4096)
if not data:
break
data = crypto.encrypt(data)
output.write(data)
def decrypt_stream(self, input, output):
counter_path = os.path.splitext(input.name)[0] + '.ctr'
with open(counter_path, 'rb') as f:
counter_read = f.read()
secret = Secret(counter_read)
crypto = AES.new(self.key, AES.MODE_CTR, counter=secret.counter)
while True:
data = input.read(4096)
if not data:
break
data = crypto.decrypt(data)
output.write(data)
if __name__ == '__main__':
key32 = '0123456789' * 3 + 'QW'
vial = Vial(key32)
path = os.path.abspath(os.path.dirname(__file__))+'/vial_test.ctr'
enc = vial.encrypt(16 * 'a', path)
print 'enc: ', enc, len(enc)
vial = Vial(key32)
path = os.path.abspath(os.path.dirname(__file__))+'/vial_test.ctr'
dec = vial.decrypt(enc, path)
print 'dec: ', dec, len(dec)
vial = Vial(key32)
finput = open('encrypt_me.png', 'rb')
foutput = open('im_encrypted.png', 'wb')
vial.encrypt_stream(finput, foutput)
finput.close()
foutput.close()
vial = Vial(key32)
finput = open('im_encrypted.png', 'rb')
foutput = open('im_decrypted.png', 'wb')
vial.decrypt_stream(finput, foutput)
finput.close()
foutput.close()