Skip to content

Commit

Permalink
Ported d83363a from detunized/lastpass-ruby
Browse files Browse the repository at this point in the history
Change the way decryption is handled, a bit less automatic.
Fixed problems with long shared folder names
  • Loading branch information
konomae committed Aug 21, 2014
1 parent 6439ce7 commit 8e4fa3f
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 34 deletions.
45 changes: 24 additions & 21 deletions lastpass/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ def extract_chunks(cls, blob):
def parse_ACCT(cls, chunk, encryption_key):
io = BytesIO(chunk.payload)
id = cls.read_item(io)
name = cls.decode_aes256_auto(cls.read_item(io), encryption_key)
group = cls.decode_aes256_auto(cls.read_item(io), encryption_key)
name = cls.decode_aes256_plain_auto(cls.read_item(io), encryption_key)
group = cls.decode_aes256_plain_auto(cls.read_item(io), encryption_key)
url = cls.decode_hex(cls.read_item(io))
notes = cls.decode_aes256_auto(cls.read_item(io), encryption_key)
notes = cls.decode_aes256_plain_auto(cls.read_item(io), encryption_key)
for _ in range(2):
cls.skip_item(io)
username = cls.decode_aes256_auto(cls.read_item(io), encryption_key)
password = cls.decode_aes256_auto(cls.read_item(io), encryption_key)
username = cls.decode_aes256_plain_auto(cls.read_item(io), encryption_key)
password = cls.decode_aes256_plain_auto(cls.read_item(io), encryption_key)
for _ in range(2):
cls.skip_item(io)
secure_note = cls.read_item(io)
Expand Down Expand Up @@ -110,9 +110,9 @@ def parse_SHAR(cls, chunk, encryption_key, rsa_key):
# TODO: rsa_key.private_decrypt(encrypted_key, RSA_PKCS1_OAEP_PADDING)
key = cls.decode_hex(rsa_key.decrypt(encrypted_key))
else:
key = cls.decode_hex(cls.decode_aes256_auto(key, encryption_key))
key = cls.decode_hex(cls.decode_aes256_plain_auto(key, encryption_key))

name = cls.decode_aes256_auto(encrypted_name, key)
name = cls.decode_aes256_base64_auto(encrypted_name, key)

# TODO: Return an object, not a dict
return {'id': id, 'name': name, 'encryption_key': key}
Expand Down Expand Up @@ -199,28 +199,31 @@ def decode_hex(cls, data):
def decode_base64(cls, data):
return b64decode(data)

# Guesses AES encoding/cipher from the length of the data.
# Possible combinations are:
# - ciphers: AES-256 EBC, AES-256 CBC
# - encodings: plain, base64
# Guesses AES cipher (EBC or CBD) from the length of the plain data.
@classmethod
def decode_aes256_auto(cls, data, encryption_key):
def decode_aes256_plain_auto(cls, data, encryption_key):
assert isinstance(data, bytes)
length = len(data)
length16 = length % 16
length64 = length % 64

if length == 0:
return b''
elif length16 == 0:
return cls.decode_aes256_ecb_plain(data, encryption_key)
elif length64 == 0 or length64 == 24 or length64 == 44:
return cls.decode_aes256_ecb_base64(data, encryption_key)
elif length16 == 1:
elif data[0] == b'!'[0] and length % 16 == 1 and length > 32:
return cls.decode_aes256_cbc_plain(data, encryption_key)
elif length64 == 6 or length64 == 26 or length64 == 50:
else:
return cls.decode_aes256_ecb_plain(data, encryption_key)

# Guesses AES cipher (EBC or CBD) from the length of the base64 encoded data.
@classmethod
def decode_aes256_base64_auto(cls, data, encryption_key):
assert isinstance(data, bytes)
length = len(data)

if length == 0:
return b''
elif data[0] == b'!'[0]:
return cls.decode_aes256_cbc_base64(data, encryption_key)
else:
raise RuntimeError("'{}' doesn't seem to be AES-256 encrypted".format(repr(data)))
return cls.decode_aes256_ecb_base64(data, encryption_key)

# Decrypts AES-256 ECB bytes.
@classmethod
Expand Down
29 changes: 16 additions & 13 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,27 +248,30 @@ def test_decode_base64_decodes_base64(self):
self.assertEqual(Parser.decode_base64('YWJj'), b'abc')
self.assertEqual(Parser.decode_base64('YWJjZA=='), b'abcd')

def test_decode_aes256_auto_decodes_a_blank_string(self):
self.assertEqual(Parser.decode_aes256_auto('', self.encryption_key), b'')
def test_decode_aes256_plain_auto_decodes_a_blank_string(self):
self.assertEqual(Parser.decode_aes256_plain_auto(b'', self.encryption_key), b'')

def test_decode_aes256_auto_decodes_ecb_plain_string(self):
self.assertEqual(Parser.decode_aes256_auto(
def test_decode_aes256_plain_auto_decodes_ecb_plain_string(self):
self.assertEqual(Parser.decode_aes256_plain_auto(
b64decode('BNhd3Q3ZVODxk9c0C788NUPTIfYnZuxXfkghtMJ8jVM='), self.encryption_key),
b'All your base are belong to us')

def test_decode_aes256_auto_decodes_ecb_base64_string(self):
self.assertEqual(Parser.decode_aes256_auto(
'BNhd3Q3ZVODxk9c0C788NUPTIfYnZuxXfkghtMJ8jVM=', self.encryption_key),
def test_decode_aes256_plain_auto_decodes_cbc_plain_string(self):
self.assertEqual(Parser.decode_aes256_plain_auto(
b64decode('IcokDWmjOkKtLpZehWKL6666Uj6fNXPpX6lLWlou+1Lrwb+D3ymP6BAwd6C0TB3hSA=='), self.encryption_key),
b'All your base are belong to us')

def test_decode_aes256_auto_decodes_cbc_plain_string(self):
self.assertEqual(Parser.decode_aes256_auto(
b64decode('IcokDWmjOkKtLpZehWKL6666Uj6fNXPpX6lLWlou+1Lrwb+D3ymP6BAwd6C0TB3hSA=='), self.encryption_key),
def test_decode_aes256_base64_auto_decodes_a_blank_string(self):
self.assertEqual(Parser.decode_aes256_base64_auto(b'', self.encryption_key), b'')

def test_decode_aes256_base64_auto_decodes_ecb_base64_string(self):
self.assertEqual(Parser.decode_aes256_base64_auto(
b'BNhd3Q3ZVODxk9c0C788NUPTIfYnZuxXfkghtMJ8jVM=', self.encryption_key),
b'All your base are belong to us')

def test_decode_aes256_auto_decodes_cbc_base64_string(self):
self.assertEqual(Parser.decode_aes256_auto(
'!YFuiAVZgOD2K+s6y8yaMOw==|TZ1+if9ofqRKTatyUaOnfudletslMJ/RZyUwJuR/+aI=', self.encryption_key),
def test_decode_aes256_base64_auto_decodes_cbc_base64_string(self):
self.assertEqual(Parser.decode_aes256_base64_auto(
b'!YFuiAVZgOD2K+s6y8yaMOw==|TZ1+if9ofqRKTatyUaOnfudletslMJ/RZyUwJuR/+aI=', self.encryption_key),
b'All your base are belong to us')

def test_decode_aes256_ecb_plain_decodes_a_blank_string(self):
Expand Down

0 comments on commit 8e4fa3f

Please sign in to comment.