From 69eb402c1ec8ca4356c2d500c8898d9e70d10199 Mon Sep 17 00:00:00 2001 From: Andrey Skvortsov Date: Mon, 14 Nov 2022 22:57:44 +0300 Subject: [PATCH 1/4] Update generator code Code is based on following article https://huggable.tech/blog/extracting-fortitoken-mobile-totp-secret It's cached here: https://webcache.googleusercontent.com/search?q=cache:qnm2jp3TG5EJ:https://huggable.tech/blog/extracting-fortitoken-mobile-totp-secret&cd=2&hl=en&ct=clnk&gl=ru --- generate.py | 52 ++++++++++++++++++++++++++++++++++++------------ requirements.txt | 2 +- 2 files changed, 40 insertions(+), 14 deletions(-) mode change 100644 => 100755 generate.py diff --git a/generate.py b/generate.py old mode 100644 new mode 100755 index e43844d..f1dc71a --- a/generate.py +++ b/generate.py @@ -1,18 +1,44 @@ -from Crypto.Cipher import AES -from Crypto.Util.Padding import pad, unpad -from pyotp import TOTP -import base64 +#!/bin/env python3 +# +# This code is based on article +# https://huggable.tech/blog/extracting-fortitoken-mobile-totp-secret +# import hashlib +import base64 +from Cryptodome.Cipher import AES +from pyotp import TOTP + +SEED = 'MNmAN7drtlNJxjFqo5bgSN/DZcdWVK9Qv1YyUP3OjuJkDXgV06siQYlQfO0678Lg' +UUID = 'N7gAr30eX72sR2owbVR4WrFiw4e3ignGBO6IcgA4qJjvBYjZvIxZXIMTHOix8QDt' +DEVICE_ID = 'eefd7d4837294e94' + +SERIAL = 'TOKENSERIALunknown' + +def unpad(s): + return s[0:-ord(s[-1])] + +def decrypt(cipher, key): + sha256 = hashlib.sha256() + sha256.update(bytes(key, 'utf-8')) + digest = sha256.digest() + iv = bytes([0] * 16) + aes = AES.new(digest, AES.MODE_CBC, iv) + decrypted = aes.decrypt(base64.b64decode(cipher)) + return unpad(str(decrypted, "utf-8")) + +uuid_key = DEVICE_ID + SERIAL[11:] +print("UUID KEY: %s" % uuid_key) +decoded_uuid = decrypt(UUID, uuid_key) +print("UUID: %s" % decoded_uuid) +seed_decryption_key = uuid_key + decoded_uuid +print("SEED KEY: %s" % seed_decryption_key) +decrypted_seed = decrypt(SEED, seed_decryption_key) -# fill these with values explained in README.md -android_ssaid = b'' -seed = b'' +totp_secret = bytes.fromhex(decrypted_seed) -key = hashlib.sha256(android_ssaid + b'unknownnull').digest() -aes = AES.new(key, AES.MODE_CBC, b'\x00'*16) -data = unpad(aes.decrypt(base64.b64decode(seed)), 8) -data = base64.b32encode(bytes.fromhex(data.decode('utf-8'))) +totp_secret_encoded = str(base64.b32encode(totp_secret), "utf-8") +print("TOTP SECRET: %s" % totp_secret_encoded) -totp = TOTP(data, interval = 60) -print(totp.now()) +totp = TOTP(totp_secret_encoded, interval = 60) +print("Current TOTP: %s" % totp.now()) diff --git a/requirements.txt b/requirements.txt index 0d7e176..27194ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pycryptodome==3.10.1 +pycryptodome==3.11.0 pyotp==2.6.0 From 22490e5d1031ca7d195eafc16b5edf7455b1e346 Mon Sep 17 00:00:00 2001 From: Andrey Skvortsov Date: Mon, 14 Nov 2022 23:03:14 +0300 Subject: [PATCH 2/4] fix pep8 warnings --- generate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/generate.py b/generate.py index f1dc71a..5e2c625 100755 --- a/generate.py +++ b/generate.py @@ -14,9 +14,11 @@ SERIAL = 'TOKENSERIALunknown' + def unpad(s): return s[0:-ord(s[-1])] + def decrypt(cipher, key): sha256 = hashlib.sha256() sha256.update(bytes(key, 'utf-8')) @@ -26,6 +28,7 @@ def decrypt(cipher, key): decrypted = aes.decrypt(base64.b64decode(cipher)) return unpad(str(decrypted, "utf-8")) + uuid_key = DEVICE_ID + SERIAL[11:] print("UUID KEY: %s" % uuid_key) decoded_uuid = decrypt(UUID, uuid_key) @@ -40,5 +43,5 @@ def decrypt(cipher, key): totp_secret_encoded = str(base64.b32encode(totp_secret), "utf-8") print("TOTP SECRET: %s" % totp_secret_encoded) -totp = TOTP(totp_secret_encoded, interval = 60) +totp = TOTP(totp_secret_encoded, interval=60) print("Current TOTP: %s" % totp.now()) From 10fdcacd0b9eadeaae53edfe5afb25d4c5574d32 Mon Sep 17 00:00:00 2001 From: Andrey Skvortsov Date: Mon, 14 Nov 2022 23:52:46 +0300 Subject: [PATCH 3/4] update README Included instruction for Android versions before Android 8. --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b129e0e..baac059 100644 --- a/README.md +++ b/README.md @@ -6,36 +6,80 @@ You can extract the actual TOTP seed and use it with apps like [KeePassXC](https ## Prequisites -For this to work, you need to extract 2 things - [SSAID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) and encrypted seed. +For this to work, you need to extract 3 things - [SSAID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID), UUID and encrypted seed. This needs root access on your Android device. ### SSAID -Run this command in a rooted shell: +#### Android 8.0+ +For Android 8.0+ SSAID is unique for every installed app, to get value for FTM run this command in a rooted shell: ``` # grep com.fortinet /data/system/users/0/settings_ssaid.xml ``` Output should look like this: ```xml - + ``` -Copy the value from quotes and paste it to the script as `android_ssaid`. +#### Android versions before 8.0 + +For previous versions of Android SSAID is the same for all apps to get it run following command +``` +# grep android_id /data/system/users/0/settings_secure.xml +``` + +```xml + +``` + +Copy the value from quotes and paste it to the script as `DEVICE_ID`. + +### UUID + +The encrypted UUID is stored in the UUID key of the XML file stored at /data/data/com.fortinet.android.ftm/shared_prefs/FortiToken_SharedPrefs_NAME.xml. + +``` +grep UUID /data/data/com.fortinet.android.ftm/shared_prefs/FortiToken_SharedPrefs_NAME.xml + N7gAr30eX72sR2owbVR4WrFiw4e3ignGBO6IcgA4qJjvBYjZvIxZXIMTHOix8QDt +``` + +Copy the value paste it to the script as `UUID`. ### Seed The seed is stored in app's database: `/data/data/com.fortinet.android.ftm/databases/FortiToken.db` -You can copy the file and open it with an SQLite3 editor, or run this command: (I know it's ugly, but does the job) +You can copy the file and open it with an SQLite3 editor, + +``` +$ sudo sqlite3 /data/data/com.fortinet.android.ftm/databases/FortiToken.db 'SELECT seed FROM Account WHERE type="totp"' +MNmAN7drtlNJxjFqo5bgSN/DZcdWVK9Qv1YyUP3OjuJkDXgV06siQYlQfO0678Lg +``` + +or run this command: (I know it's ugly, but does the job) ``` # grep -Eao 'totp.{64}' /data/data/com.fortinet.android.ftm/databases/FortiToken.db | cut -c5- +MNmAN7drtlNJxjFqo5bgSN/DZcdWVK9Qv1YyUP3OjuJkDXgV06siQYlQfO0678Lg ``` -Copy the output and paste it as `seed`. + +Copy the output and paste it as `SEED`. + ## Usage Install requirements with `pip3 install -U -r requirements.txt`, then run with `python3 generate.py`. +``` +$ python3 generate.py +UUID KEY: eefd7d4837294e94unknown +UUID: bbc350195b88433dbcc7365cdbd130e5 +SEED KEY: eefd7d4837294e94unknownbbc350195b88433dbcc7365cdbd130e5 +TOTP SECRET: DEADBEEFDEADBEEFDEADBEEFDEADBEEF +Current TOTP: 779726 +``` + +Printed TOTP SECRET is base32-encoded and can be used to setup TOTP codes in other authenticator applications like: KeePassXC, andOTP, SailOTP, Numberstation. Make sure to set the period to *60 seconds*. + ## Disclaimer All product and company names are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them. From 88e842e1f21d18ffb539b36b76ba437057e245e6 Mon Sep 17 00:00:00 2001 From: Ildar Abdullin Date: Tue, 20 Dec 2022 01:12:44 +0300 Subject: [PATCH 4/4] Add qr-code generation --- generate.py | 6 ++++++ requirements.txt | 3 +++ 2 files changed, 9 insertions(+) diff --git a/generate.py b/generate.py index 5e2c625..62f74c9 100755 --- a/generate.py +++ b/generate.py @@ -5,6 +5,7 @@ # import hashlib import base64 +import qrcode from Cryptodome.Cipher import AES from pyotp import TOTP @@ -45,3 +46,8 @@ def decrypt(cipher, key): totp = TOTP(totp_secret_encoded, interval=60) print("Current TOTP: %s" % totp.now()) + +str=totp.provisioning_uri(name='vpn', issuer_name='fortivpn') +img=qrcode.make(str) +type(img) +img.show() diff --git a/requirements.txt b/requirements.txt index 27194ce..cc30850 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ pycryptodome==3.11.0 pyotp==2.6.0 +pycryptodomex==3.16.0 +qrcode==7.3.1 +pillow==9.3.0