Skip to content

Commit

Permalink
Optimized AES-CTR mode for use with the scrypt file format.
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed Jun 2, 2014
1 parent 00915e1 commit 0b63111
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 55 deletions.
2 changes: 1 addition & 1 deletion pyscrypt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@
from .file import InvalidScryptFileFormat, ScryptFile
from .hash import hash

VERSION = [1, 3, 1]
VERSION = [1, 4, 1]

__all__ = ['hash', 'InvalidScryptFileFormat', 'ScryptFile']
75 changes: 26 additions & 49 deletions pyscrypt/aesctr.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,61 +137,38 @@ def encrypt(self, plaintext):
return result


class Counter(object):
def __init__(self, nbits, initial_value = 1):
if nbits % 8 != 0: raise ValueError('invalid counter length')
self._counter = [ 0 ] * (nbits // 8)

# Initialize the vector with the initial value
index = len(self._counter) - 1
while initial_value:
self._counter[index] = initial_value % 256
initial_value //= 256
index -= 1
if index == -1: raise ValueError('initial_value too large')

# Generator that returns big-endian ++ operations on our counter array
def next_value(self):
while True:
yield self._counter

# Add one to the right-most byte of the counter and carry if overflows a byte
index = len(self._counter) - 1
while True:
self._counter[index] += 1

# Carry...
if self._counter[index] == 256:
self._counter[index] = 0
index -= 1

# Overflow... Wrap around
if index == -1:
self._counter = [ 0 ] * 16
break
else:
break

self._next_value = next_value(self)

def __call__(self):
return self._next_value.next()


class AESCounterModeOfOperation(object):

def __init__(self, key, counter):
def __init__(self, key):
self._aes = AES(key)

self._counter = counter
# Convert the initial value into an array of bytes
self._counter = [ 0 ] * 16

# Remaining bytes to xor our input against
self._remaining_counter = [ ]

def encrypt(self, plaintext):
encrypted = [ ]
for c in plaintext:
if len(self._remaining_counter) == 0:
self._remaining_counter = self._aes.encrypt(self._counter())
encrypted.append(self._remaining_counter.pop(0) ^ ord(c))

# Fill up our bytes to xor against
while len(self._remaining_counter) < len(plaintext):
self._remaining_counter += self._aes.encrypt(self._counter)

# Increment the counter
for i in xrange(15, -1, -1):
self._counter[i] += 1

if self._counter[i] < 256: break

# Carry the one
self._counter[i] = 0

# Overflow! Exposing the same (key, counter_value) could reveal key
else:
raise ValueError('counter value would overflow, compromising security.')

encrypted = [ (ord(p) ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ]

return "".join(chr(c) for c in encrypted)

Expand Down Expand Up @@ -219,13 +196,13 @@ def decrypt(self, crypttext):
kaes = KAES.new(key, KAES.MODE_CTR, counter = KCounter.new(128, initial_value = 0))
kenc = kaes.encrypt(plaintext)

aes = AESCounterModeOfOperation(key, counter = Counter(nbits = 128, initial_value = 0))
aes = AESCounterModeOfOperation(key)
enc = aes.encrypt(plaintext)

result = {True: "pass", False: "fail"}[kenc == enc]
print "Test Encrypt: key_size=%d text_length=%d trial=%d result=%s" % (key_size, text_length, i, result)

aes = AESCounterModeOfOperation(key, counter = Counter(nbits = 128, initial_value = 0))
aes = AESCounterModeOfOperation(key)
result = {True: "pass", False: "fail"}[plaintext == aes.decrypt(kenc)]
print "Test Decrypt: key_size=%d text_length=%d trial=%d result=%s" % (key_size, text_length, i, result)

Expand Down
6 changes: 2 additions & 4 deletions pyscrypt/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ def _read_header(self):
self._checksumer.update(header[64:96])

# Prepare the AES engine
counter = aesctr.Counter(nbits = 128, initial_value = 0)
self._crypto = aesctr.AESCounterModeOfOperation(key = self.key[:32], counter = counter)
self._crypto = aesctr.AESCounterModeOfOperation(key = self.key[:32])

self._done_header = True

Expand Down Expand Up @@ -372,8 +371,7 @@ def _write_header(self):
self._fp.write(header)

# Prepare the AES engine
counter = aesctr.Counter(nbits = 128, initial_value = 0)
self._crypto = aesctr.AESCounterModeOfOperation(key = self.key[:32], counter = counter)
self._crypto = aesctr.AESCounterModeOfOperation(key = self.key[:32])
#self._crypto = aes(self.key[:32])

self._done_header = True
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
reference and details.'''

setup(name = 'pyscrypt',
version = '1.3.1',
version = '1.4.1',
description = 'Pure-Python Implementation of the scrypt password-based key derivation function and scrypt file format library',
long_description = LONG_DESCRIPTION,
author = 'Richard Moore',
Expand Down

0 comments on commit 0b63111

Please sign in to comment.