jamie-mh / AuthenticatorPro

📱 Two-Factor Authentication (2FA) client for Android + Wear OS
https://authenticatorpro.jmh.me
GNU General Public License v3.0
2.98k stars 198 forks source link

Import from freeOTP (2.x) externalBackup.xml does not work #1003

Closed rhopp closed 9 months ago

rhopp commented 11 months ago

Describe the bug I'm trying to import backup file from freeOTP 2.x into Authenticator Pro. When I select "Import from other apps", FreeOTP, and select correct externalBackup.xml file, I'm correctly asked for password. But when I fill in the password and press OK I got message

The password is incorrect or the file is corrupt

Which is not true. I've verified it by installing FreeOTP into different profile (my work profile) and tried to import the same file there. Writing the same password phrase in FreeOTP, the file was correctly imported (this proves the file is fine and the password I'm providing is also correct)

I have 9 tokens in my FreeOTP export. Some of them are HOTP, some of them are TOTP.

To Reproduce Steps to reproduce the behavior:

  1. Go to "Import from other apps"
  2. Click on 'FreeOTP'
  3. Select externalBackup.xml you already exported from your FreeOTP app.
  4. Provide correct password
  5. See error The password is incorrect or the file is corrupt

Expected behavior Tokens are imported.

App Version Authenticator Pro 1.23.0 FreeOTP 2.0.2 (43)

Additional context

jamie-mh commented 11 months ago

Hi @rhopp ,

Can you capture a log when attempting to import the file? It sounds like there may be an issue when parsing.

Thanks

rhopp commented 11 months ago

@jamie-mh I'm happy to capture the logs - do you have some docs on how to do that?

rpavlik commented 9 months ago

It looks like it's trying to load it as json? I cleared my logcat before doing an import (from 2.0.4), and this is really the only thing I see from Authenticator Pro:

12-10 13:06:58.749  6579  6579 E AUTHPRO : Newtonsoft.Json.JsonReaderException: Invalid property identifier character: {. Path '', line 1, position 1.
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonTextReader.ParseProperty()
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonTextReader.ParseObject()
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonTextReader.Read()
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonReader.ReadAndAssert()
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader , Type , JsonContract , JsonProperty , JsonContainerContract , JsonProperty , Object )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader , Type , JsonContract , JsonProperty , JsonContainerContract , JsonProperty , Object )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader , Type , Boolean )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader , Type )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader , Type )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonConvert.DeserializeObject(String , Type , JsonSerializerSettings )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonConvert.DeserializeObject[TokenInfo](String , JsonSerializerSettings )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at Newtonsoft.Json.JsonConvert.DeserializeObject[TokenInfo](String )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at AuthenticatorPro.Core.Converter.FreeOtpBackupConverter.DecryptAndConvert(Dictionary`2 , String )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at AuthenticatorPro.Core.Converter.FreeOtpBackupConverter.<>c__DisplayClass6_0.<ConvertAsync>b__0()
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at System.Threading.Tasks.Task`1[[AuthenticatorPro.Core.Backup.ConversionResult, AuthenticatorPro.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].InnerInvoke()
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at System.Threading.Tasks.Task.<>c.<.cctor>b__273_0(Object )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
12-10 13:06:58.749  6579  6579 E AUTHPRO : --- End of stack trace from previous location ---
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
12-10 13:06:58.749  6579  6579 E AUTHPRO : --- End of stack trace from previous location ---
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at AuthenticatorPro.Core.Converter.FreeOtpBackupConverter.ConvertAsync(Byte[] , String )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at AuthenticatorPro.Core.Service.Impl.ImportService.ImportAsync(BackupConverter , Byte[] , String )
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at AuthenticatorPro.Droid.Activity.MainActivity.<>c__DisplayClass89_0.<<ImportFromData>g__ConvertAndRestore|0>d.MoveNext()
12-10 13:06:58.749  6579  6579 E AUTHPRO : --- End of stack trace from previous location ---
12-10 13:06:58.749  6579  6579 E AUTHPRO :    at AuthenticatorPro.Droid.Activity.MainActivity.<>c__DisplayClass89_1.<<ImportFromData>b__3>d.MoveNext()
rpavlik commented 9 months ago

Hmm, though it actually doesn't look like XML when I view it. It looks like a strange header, then a sequence of JSON files (containing more json inside of a string), glued together with shorter amounts of header info and delimiter.

¬í sr java.util.HashMapÚÁÃ`Ñ F 
loadFactorI     thresholdxp?@     `w   €   =t $c9a6910b-097d-4322-945e-39511175f3dct  {"key":"{\"mCipher\":\"AES\/GCM\/NoPadding\",\"mCipherText\"

and so on, until the end of that bit of json, then the delimiter and the start of the next json chunk (obviously I am omitting the actual data in there, uuid's seem safe to share though):

t *c65f4aa6-1e16-4615-ad9a-aef1953ce083-tokent Š{"algo":"SHA1","digits":6
jamie-mh commented 9 months ago

Hmm, though it actually doesn't look like XML when I view it. It looks like a strange header, then a sequence of JSON files (containing more json inside of a string), glued together with shorter amounts of header info and delimiter.

It has an XML file extension for some reason. But in reality, it's the Java ObjectOutputStream representation of a String, String hashmap. The values are encoded as JSON. It's a bit of a pain to deal with outside of Java really.

I've made some tweaks in the 1.24.0 beta, if anyone could try it out and give some feedback that would be much appreciated. It's a different package name so it can be installed alongside an existing install.

Thanks

rpavlik commented 9 months ago

Yep, that beta works for me it seems! Got all my credentials imported, and I tested one of them and it works right.

jamie-mh commented 9 months ago

Thanks for testing this @rpavlik . Fixed in 1.24.0.