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. diff --git a/generate.py b/generate.py old mode 100644 new mode 100755 index e43844d..62f74c9 --- a/generate.py +++ b/generate.py @@ -1,18 +1,53 @@ -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 +import qrcode +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) +totp_secret = bytes.fromhex(decrypted_seed) -# fill these with values explained in README.md -android_ssaid = b'' -seed = b'' +totp_secret_encoded = str(base64.b32encode(totp_secret), "utf-8") +print("TOTP SECRET: %s" % totp_secret_encoded) -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 = TOTP(totp_secret_encoded, interval=60) +print("Current TOTP: %s" % totp.now()) -totp = TOTP(data, interval = 60) -print(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 0d7e176..cc30850 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ -pycryptodome==3.10.1 +pycryptodome==3.11.0 pyotp==2.6.0 +pycryptodomex==3.16.0 +qrcode==7.3.1 +pillow==9.3.0