mirror of
https://github.com/nikdoof/decrypt-otpauth-files.git
synced 2025-12-13 08:22:16 +00:00
Merge pull request #2 from CooperRS/feature/file-version-1.2
Add support for decrypting RNCryptor-based file versions
This commit is contained in:
1
Pipfile
1
Pipfile
@@ -11,6 +11,7 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
|
||||
rncryptor = "==3.2.0"
|
||||
bpylist = "==0.1.4"
|
||||
click = "==6.7"
|
||||
pycrypto = "==2.6.1"
|
||||
|
||||
9
Pipfile.lock
generated
9
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "81d534465fc2e9af42dacab3d37d305414bfe0fb1642f22fb3142ffebf745505"
|
||||
"sha256": "6b165af75f2543e65ceee44608b5377627ca18809ea66b2b85c012dcdcb9eeb8"
|
||||
},
|
||||
"host-environment-markers": {
|
||||
"implementation_name": "cpython",
|
||||
@@ -54,6 +54,13 @@
|
||||
"sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"
|
||||
],
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"rncryptor": {
|
||||
"hashes": [
|
||||
"sha256:a3b521d22953bffc4f125faf53f4c9c2a41c8186e0b0e331314abe455be92c48",
|
||||
"sha256:156253246f3e3521e5080191e9b4ec100e162d07c261a9df86169f8943bcc7a3"
|
||||
],
|
||||
"version": "==3.2.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
||||
BIN
account-1.2.otpauth
Normal file
BIN
account-1.2.otpauth
Normal file
Binary file not shown.
BIN
backup-1.1.otpauthdb
Normal file
BIN
backup-1.1.otpauthdb
Normal file
Binary file not shown.
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user