Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<setting id="32" name="10309" value="12dddfc4a3b45678" package="com.fortinet.android.ftm" defaultValue="12dddfc4a3b45678" defaultSysSet="false" tag="null" />
<setting id="32" name="10309" value="eefd7d4837294e94" package="com.fortinet.android.ftm" defaultValue="eefd7d4837294e94" defaultSysSet="false" tag="null" />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a bogus value, i hope you didn't replace it (and the other two) with your actual ones

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are fake values from the article.

```

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
<setting id="31" name="android_id" value="eefd7d4837294e94" package="android" />
```

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
<string name="UUID">N7gAr30eX72sR2owbVR4WrFiw4e3ignGBO6IcgA4qJjvBYjZvIxZXIMTHOix8QDt</string>
```

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.
61 changes: 48 additions & 13 deletions generate.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this article does not exist (anymore?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yesterday it was in google cache and now it's gone.
Here is the link to web.archive.org:
https://web.archive.org/web/20221022175145/https://huggable.tech/blog/extracting-fortitoken-mobile-totp-secret

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I place link to web.archive.org instead then?

#
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()
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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