mirror of
https://github.com/nikdoof/decrypt-otpauth-files.git
synced 2025-12-17 13:59:26 +00:00
Add support for decrypting RNCryptor-based files
This commit is contained in:
@@ -13,6 +13,8 @@ from bpylist.archive_types import uid
|
|||||||
|
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
from rncryptor import RNCryptor
|
||||||
|
from rncryptor import bord
|
||||||
|
|
||||||
class Type(Enum):
|
class Type(Enum):
|
||||||
Unknown = 0
|
Unknown = 0
|
||||||
@@ -126,6 +128,13 @@ archiver.update_class_map({'NSMutableString': MutableString})
|
|||||||
archiver.update_class_map({'ACOTPFolder': OTPFolder})
|
archiver.update_class_map({'ACOTPFolder': OTPFolder})
|
||||||
archiver.update_class_map({'ACOTPAccount': OTPAccount})
|
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):
|
class DangerousUnarchive(archiver.Unarchive):
|
||||||
|
|
||||||
@@ -188,7 +197,18 @@ def decrypt_account(encrypted_otpauth_account):
|
|||||||
# Decode wrapping archive
|
# Decode wrapping archive
|
||||||
archive = archiver.Unarchive(data).top_object()
|
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]
|
iv = hashlib.sha1(archive['IV']).digest()[:16]
|
||||||
salt = archive['Salt']
|
salt = archive['Salt']
|
||||||
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
|
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
|
||||||
@@ -201,9 +221,17 @@ def decrypt_account(encrypted_otpauth_account):
|
|||||||
archive = DangerousUnarchive(data).top_object()
|
archive = DangerousUnarchive(data).top_object()
|
||||||
|
|
||||||
# Construct OTPAccount object from returned dictionary
|
# Construct OTPAccount object from returned dictionary
|
||||||
account = OTPAccount.from_dict(archive)
|
return OTPAccount.from_dict(archive)
|
||||||
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
|
|
||||||
|
|
||||||
|
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()
|
@cli.command()
|
||||||
@click.option('--encrypted-otpauth-backup',
|
@click.option('--encrypted-otpauth-backup',
|
||||||
@@ -225,7 +253,20 @@ def decrypt_backup(encrypted_otpauth_backup):
|
|||||||
# Decode wrapping archive
|
# Decode wrapping archive
|
||||||
archive = archiver.Unarchive(data).top_object()
|
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]
|
iv = hashlib.sha1(archive['IV'].encode('utf-8')).digest()[:16]
|
||||||
salt = archive['Salt']
|
salt = archive['Salt']
|
||||||
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
|
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
|
||||||
@@ -237,11 +278,16 @@ def decrypt_backup(encrypted_otpauth_backup):
|
|||||||
# Decode actual archive
|
# Decode actual archive
|
||||||
archive = DangerousUnarchive(data).top_object()
|
archive = DangerousUnarchive(data).top_object()
|
||||||
|
|
||||||
accounts = [account for folder in archive['Folders'] for account in folder.accounts]
|
return [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...")
|
|
||||||
|
|
||||||
|
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__':
|
if __name__ == '__main__':
|
||||||
cli()
|
cli()
|
||||||
|
|||||||
Reference in New Issue
Block a user