Add support for decrypting RNCryptor-based files

This commit is contained in:
Roland Moers
2017-12-31 14:29:35 +01:00
parent dedef1b306
commit 08c8c3d6d4

View File

@@ -13,6 +13,8 @@ from bpylist.archive_types import uid
from Crypto.Cipher import AES
from rncryptor import RNCryptor
from rncryptor import bord
class Type(Enum):
Unknown = 0
@@ -126,6 +128,13 @@ archiver.update_class_map({'NSMutableString': MutableString})
archiver.update_class_map({'ACOTPFolder': OTPFolder})
archiver.update_class_map({'ACOTPAccount': OTPAccount})
class RawRNCryptor(RNCryptor):
def post_decrypt_data(self, data):
"""Remove useless symbols which
appear over padding for AES (PKCS#7)."""
data = data[:-bord(data[-1])]
return data
class DangerousUnarchive(archiver.Unarchive):
@@ -188,7 +197,18 @@ def decrypt_account(encrypted_otpauth_account):
# Decode wrapping archive
archive = archiver.Unarchive(data).top_object()
# Get IV and key for actual archive
if archive['Version'] == 1.1:
account = decrypt_account_11(archive, password)
elif archive['Version'] == 1.2:
account = decrypt_account_12(archive, password)
else:
print('Encountered unknow file version', archive['Version'])
return
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
def decrypt_account_11(archive, password):
# Get IV and key for actual archive
iv = hashlib.sha1(archive['IV']).digest()[:16]
salt = archive['Salt']
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
@@ -201,9 +221,17 @@ def decrypt_account(encrypted_otpauth_account):
archive = DangerousUnarchive(data).top_object()
# Construct OTPAccount object from returned dictionary
account = OTPAccount.from_dict(archive)
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
return OTPAccount.from_dict(archive)
def decrypt_account_12(archive, password):
# Decrypt using RNCryptor
data = data = RawRNCryptor().decrypt(archive['Data'], password)
# Decode archive
archive = DangerousUnarchive(data).top_object()
# Construct OTPAccount object from returned dictionary
return OTPAccount.from_dict(archive)
@cli.command()
@click.option('--encrypted-otpauth-backup',
@@ -225,7 +253,20 @@ def decrypt_backup(encrypted_otpauth_backup):
# Decode wrapping archive
archive = archiver.Unarchive(data).top_object()
# Get IV and key for actual archive
if archive['Version'] == 1.0:
accounts = decrypt_backup_10(archive, password)
elif archive['Version'] == 1.1:
accounts = decrypt_backup_11(archive, password)
else:
print('Encountered unknow file version', archive['Version'])
return
for account in accounts:
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
input("Press Enter to continue...")
def decrypt_backup_10(archive, password):
# Get IV and key for actual archive
iv = hashlib.sha1(archive['IV'].encode('utf-8')).digest()[:16]
salt = archive['Salt']
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
@@ -237,11 +278,16 @@ def decrypt_backup(encrypted_otpauth_backup):
# Decode actual archive
archive = DangerousUnarchive(data).top_object()
accounts = [account for folder in archive['Folders'] for account in folder.accounts]
for account in accounts:
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
input("Press Enter to continue...")
return [account for folder in archive['Folders'] for account in folder.accounts]
def decrypt_backup_11(archive, password):
# Decrypt using RNCryptor
data = data = RawRNCryptor().decrypt(archive['WrappedData'], password)
# Decode archive
archive = DangerousUnarchive(data).top_object()
return [account for folder in archive['Folders'] for account in folder.accounts]
if __name__ == '__main__':
cli()