OtpTranslate: Migrate from Raivo to Aegis, and beyond

(Photo by ZSun Fu on Unsplash)

Recently a friend asked me about copying his Raivo OTP database to Aegis. Those are both solid MFA apps. The only problem is Raivo only works on iOS, and Aegis only works on Android.

His Raivo database had hundreds of entries, so it would have been a real pain to manually set it up in Aegis. Plus, keeping them in sync would be an additional pain.

We searched for an existing open-source conversion tool, but came up empty-handed.

*Cracks knuckles*: “Time to create our own.”

Analyzing the Raivo and Aegis export formats

Both Raivo and Aegis have a nice export to archive feature for backup and data migration. Both export to an encrypted archive. Raivo uses an encrypted zip file, and Aegis uses its own encryption.

Since both tools allow exporting to an encrypted archive, we opted for this approach. Later our tool can be adjusted to handle encrypted archives.

Once we unpacked the archives, we found that both Aegis and Raivo store the entries in a JSON file.

The Raivo JSON file was an array of JSON objects with entries like this:

{
	"kind": "TOTP",
	"account": "[email protected]",
	"secret": "GoogleSecret",
	"iconType": "raivo_repository",
	"issuer": "Google.com",
	"timer": "30",
	"digits": "6",
	"counter": "0",
	"algorithm": "SHA1",
	"iconValue": "google.com/google-gmail.png"
},

The Aegis JSON file had some header information with an array of JSON objects like this:

{
	"type": "totp",
	"uuid": "b33ca6f5-7661-4289-afcc-ee9847c3e59a",
	"name": "[email protected]",
	"issuer": "Google.com",
	"note": "",
	"icon": null,
	"info": {
	  "secret": "GoogleSecret",
	  "algo": "SHA1",
	  "digits": 6,
	  "period": 30
	}
},

We could just use jq to translate one JSON file to another, but we wanted to write a more general extensible tool that would allow us to support additional OTP apps in the future, as well as eventually handle the aforementioned decryption and icon migration.

We decided to create a CLI using .NET 6, so that it would be work on Windows, Mac, and Linux.

We created OtpTranslator .

Introducing OtpTranslator

OtpTranslator is organized with separate projects for the CLI, the core library, and the tests.

The core library (OtpTranslator.Lib) has interfaces like ITranslateEntry and ITranslateFile, which are implemented in Translations/Aegis and Translations/Raivo.

ITranslateEntry, for example, looks like this:

public interface ITranslateEntry<T>
{
    StandardOtpEntry ToStandard(T other);

    T FromStandard(StandardOtpEntry standard);
}

Raivo, for example, implements the interface to both:

  • translate a Raivo entry to the neutral StandardOtpEntry
  • translate a StandardOtpEntry to a Raivo entry.

So to translate a Raivo entry to an Aegis entry:

graph TD A[Raivo] -->|translate| B B[Standard] -->|translate| C C[Aegis]

Why didn’t we just translate straight from Raivo to Aegis? Well, adding a middle man makes it easy to add new translations in the future — another OTP app, for instance, or even different versions of Aegis and Raivo. Let’s say a new OTP comes along called “SuperMFA”. All you have to do is implement the interfaces for SuperMFA to convert to/from the “standard” OTP entry, and you can now translate from SuperMFA to both Aegis and Raivo, and whatever comes next. We thought that was pretty cool.

What’s next?

As I mentioned before, it would be nice to support working with the encrypted archives directly, so that we avoid leaving a trail of unencrypted OTP entries which could potentially be intercepted.

Also, each app uses a custom approach to icons. Currently migrating icons is not supported. It would be nice to figure this out.

Enjoy! The source code is on GitHub .

Avatar
Ty Walls
Digital Construction Worker

Ty Walls is a software engineer in love with creating, learning, and teaching.

Related