Skip to content

Instantly share code, notes, and snippets.

@chowder
Last active May 14, 2025 05:59
Show Gist options
  • Save chowder/2ead734d60d84d4d15034fcce81aaaf9 to your computer and use it in GitHub Desktop.
Save chowder/2ead734d60d84d4d15034fcce81aaaf9 to your computer and use it in GitHub Desktop.
Exporting Microsoft Authenticator TOTP secrets

Background

Workplaces may enforce TOTP 2FA to be enabled Office 365 accounts, which require the Microsoft Authenticator app to be installed.

Regular TOTP applications (such as Aegis, Authy, or LastPass) cannot be used as Microsoft uses a proprietary scheme called phonefactor. Furthermore, the application requires Google Services Framework (GSF) to be installed (likely to provide device notifications), and will refuse to work when it is not present on the device.

Forunately, after the registration is complete, the underlying mechanism the app uses to generate TOTP codes is regular otpauth, and its secrets can be exported with a little bit of effort.

Extracting the keys

  1. To extract the keys, a complete registration must first be done with a rooted Android device. I used a virtual Android device created with Android Studio's Device Manager.

  2. Once complete, an SQLite database storing the keys can be found on the device at:

    /data/data/com.azure.authenticator/databases/PhoneFactor

    (accessing the /data partition is what requires root)

  3. ADB can then be used to connect to the device/emulator, using its bundled sqlite3 tool to view the database:

    $ adb root  # Ensure we run as the root user 
    $ adb shell  # Launch a shell as the root user 
    emu64xa:/ # whoami
    root 
    emu64xa:/ # sqlite3 /data/data/com.azure.authenticator/databases/PhoneFactor  # Connect to the database file
    sqlite> SELECT name, username, oath_secret_key from accounts;
    GitHub|[email protected]|w0swofa8wl02vqml0pkbzphvp54zyx5x
    

    The 32-length string in the oath_secret_key column can then be imported into any TOTP application.

@ChrisIdema
Copy link

ChrisIdema commented Jul 21, 2024

Thank you for this guide. It gave he hope there was a way to export the codes.
I was eventually able to do it, but not exactly this way.

First I needed an emulator image with a play store that was rooted too.
It turned out that the images without play store were rooted by default and the ones with play store are not.
I tried adding a play store to a rooted one, but that failed.
I tried installing the apk image of the MS Authenticator app on an image without play store and that failed.
Eventually I decided to use a non-rooted image and root it using this tool: https://gitlab.com/newbit/rootAVD
That worked and gave me a rooted image with play store that allowed installing the MS Authenticator app.
This was the Pixel_3a with API_33-ext5.

I installed the MS Authenticator app and had to select recover from backup right from the beginning, because you cannot do it afterwards (it will just overwrite a backup instead of syncing). I had to delete my account first (or clear data of the app) in order to see that option again.

I copied the database to a different folder:

cd C:\Users\user\AppData\Local\Android\Sdk\platform-tools
adb shell
su
cat /data/data/com.azure.authenticator/databases/PhoneFactor> /mnt/sdcard/Download/PhoneFactor

But this was not needed.

Then I discovered my image did not come with sqlite3. I tried various ways of installing, but instructions were unclear or outdated.
I ended up installing the app "SQLite Editor" which gave me access to the database.

One code in the database were not base32, but base64. I have to convert it. I used python for that:

a = base64.decodebytes(b'<my base64 code>')
base64.b32encode(a)

This was the entry for the Microsoft account and also the only one with 8 digit code instead of 6.

They were all transferred Aegis which can also import Google Authenticator QR codes directly and can export regular QR codes. Some codes were exported from Aegis to an airgapped hardware TOTP as a backup.

@diffs
Copy link

diffs commented Aug 3, 2024

I was trying to go down the route of intercepting Authenticator's web requests with Fiddler and disabling SSL with Frida to try to get the keys as they're being synced into the emulator.. but turns out this is way easier lol.

Thanks!

@CerebralDatabank
Copy link

CerebralDatabank commented May 11, 2025

Thanks for this guide; this still works in 2025! Just wanted to say that and add some extra steps that I took:

  1. I created a normal emulator image with Google Play Services and rooted it with the rootAVD tool (InstallKernelModules option), as in one of the above comments.
  2. I signed in with Google into the Play Store, installed the Microsoft Authenticator app, and restored the backup.
  3. I used adb shell to copy the MS Auth's app's folder in /data/data to /sdcard, then used adb pull to copy it to my computer.
  4. On my computer, I ran the following commands:
    > cd com.azure.authenticator\databases
    > sqlite3 PhoneFactor
    sqlite> .headers on
    sqlite> .mode csv
    sqlite> .output accounts.csv
    sqlite> SELECT * FROM accounts;
    sqlite> .quit
  5. I created two Node.js scripts to work with the resulting CSV:
    • conv.js
      This reads the CSV and writes a plain text file with a list of otpauth:// URIs. This can be imported into other authenticator apps (sometimes indirectly, e.g. plain text β†’ import to Aegis β†’ export as Aegis β†’ import into Bitwarden Authenticator)
    • show-qrs.js
      This reads the list of otpauth:// URIs and shows the QR codes in the terminal one-by-one for apps that I wasn't able to import into (e.g. Google Authenticator)

@jneidel
Copy link

jneidel commented May 13, 2025

Works great. Thank you, the people above me, for writing it up!

These were my full steps to do this on MacOS. It's not complicated if you are familiar with the terminal.

  • $ brew install --cask android-studio
  • In android studio > Device Manger > New Device (pick any, I used Pixel 9)
  • run emulator once, shutdown
  • get https://gitlab.com/newbit/rootAVD
git clone https://gitlab.com/newbit/rootAVD.git
cd rootAVD
  • put this temporarily in .zshrc:
export PATH=~/Library/Android/sdk/platform-tools:$PATH
export ANDROID_HOME=~/Library/Android/sdk
  • reload shell (source ~/.zshrc)
  • locate system image: find $ANDROID_HOME -name "ramdisk.img"
  • run rootAVD FAKEBOOTIMG. rootAVD expects a img path relative to $ANDROID_HOME, so clean up the img path to start with system-images, like here:
./rootAVD.sh system-images/android-36/google_apis_playstore/x86_64/ramdisk.img FAKEBOOTIMG
  • rootAVD will tell you when you need to open Magisk in the emulator and press "Install > Patch file > Select file "/sdcard/Download/fakeboot.img", then press Enter in rootAVD
  • shut down emulator and cold reboot
  • on the emulator search for and install aurora from https://aurorastore.org, configure, use Anonymous account
  • in Aurora install Microsoft Authenticator and set up your 2FA
  • $ adb shell
  • inside run:
su
# grant the Magisk request in the emulator
whoami #==> root (success)
cd /data/data/com.azure.authenticator
cp -r databases/ /sdcard/Download/
  • on your machine
adb pull /sdcard/Download/databases
cd databases
sqlite3 PhoneFactor

# inside sqlite
.headers on
.mode csv
.output accounts.csv
 SELECT * FROM accounts;
.quit

# back in shell
cat accounts.csv | cut -d, -f5
#=> oath_secret_key
#=> ______________ (your secret)
  • open KeepassXC, right click db entry, setup TOTP, paste secret
  • test that 2FA works

@thefyfy
Copy link

thefyfy commented May 13, 2025

Thank you @jneidel πŸ™
Am I missing something at this step ? 🧐

jeremy@MacBook-Air-de-Jeremy-2 rootAVD % find $ANDROID_HOME -name "ramdisk.img"
/Users/jeremy/Library/Android/sdk/system-images/android-36/google_apis_playstore/arm64-v8a/ramdisk.img
jeremy@MacBook-Air-de-Jeremy-2 rootAVD % ./rootAVD.sh /Users/jeremy/Library/Android/sdk/system-images/android-36/google_apis_playstore/arm64-v8a/ramdisk.img FAKEBOOTIMG
rootAVD A Script to root AVD by NewBit XDA

Usage:	rootAVD [DIR/ramdisk.img] [OPTIONS] | [EXTRA ARGUMENTS]
or:	rootAVD [ARGUMENTS]

Arguments:
	ListAllAVDs			Lists Command Examples for ALL installed AVDs...

rootAVD does nothing....

@jneidel
Copy link

jneidel commented May 14, 2025

@thefyfy yes, rootAVD does not want the absolute path to the image, but one relative from $ANDROID_HOME. I clarified this above. In your case:

# this
./rootAVD.sh system-images/android-36/google_apis_playstore/arm64-v8a/ramdisk.img FAKEBOOTIMG
# not this
./rootAVD.sh /Users/jeremy/Library/Android/sdk/system-images/android-36/google_apis_playstore/arm64-v8a/ramdisk.img FAKEBOOTIMG

This tripped me up too πŸ˜„

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment