.NET MAUI iOS应用重启后用户数据丢失问题求助
.NET 8 iOS应用Release模式下用户数据重启丢失
问题现象
使用Visual Studio 2022 v17.13.6开发.NET 8.0 iOS应用时,Android端Debug和Release模式均运行正常,但iOS仅Debug模式正常。重启iOS应用后,settings.xml中的用户数据会丢失,用户每次重启都需要重新登录;SecureStorage也存在相同问题。
相关代码实现
SharedSettings类(用户数据XML读写)
using System; using System.Collections.Generic; using System.Data.SqlTypes; using System.Linq; using System.Net.Http.Headers; using System.Runtime; using System.Text; using System.Xml.Linq; using System.Xml.Serialization; using CryptoForms; namespace MyApp { public class SharedSettings : IDisposable { private static SharedSettings _instance; private static object _syncRoot = new Object(); private bool _disposed = false; private string _userName; private string _userId; private string _passWord; public string UserName { get { return _userName; } set { _userName = value; } } public string UserId { get { return _userId; } set { _userId = value; } } public string Password { get { return _passWord; } set { _passWord = value; } } public static SharedSettings Instance { get { if (_instance == null) { lock (_syncRoot) { if (_instance == null) { _instance = new SharedSettings(); _instance.Init(); } } } return _instance; } } #region IDisposable Members public void Dispose() { if (!_disposed) { SaveSettings(); _instance = null; _disposed = true; } } #endregion private void Init() { try { var loaded = _instance.LoadSettings().Result; if (!loaded) { UserName = ""; UserId = ""; Password = ""; _instance.SaveSettings(); } } catch (Exception e) { Console.WriteLine($"SharedSettings.Init: {e.Message}"); } } public async void SaveSettings() { try { var pass = "asd123"; byte[] salt = Encoding.UTF8.GetBytes("123asd"); var encryptedPasswordBytes = SharedCrypto.EncryptAes(Password, pass, salt); var encryptedPasswordString = Convert.ToBase64String(encryptedPasswordBytes); var settings = new UserSettings { UserName = UserName, UserId = UserId, PassWord = encryptedPasswordString }; await SaveSettingsAsync(settings); } catch (Exception e) { ResetSettings(); Console.WriteLine($"SharedSettings.Init: {e.Message}"); } } public async Task<bool> LoadSettings() { try { bool retVal = false; var loadedSettings = new UserSettings(); try { loadedSettings = await LoadSettingsAsync(); if (loadedSettings != null) { Console.WriteLine($"Loaded User: {loadedSettings.UserName}"); UserName = loadedSettings.UserName; } else { Console.WriteLine("No settings file found."); return false; } } catch (Exception) { ResetSettings(); return false; } if (!string.IsNullOrEmpty(UserName)) { string encryptedPasswordString = ""; encryptedPasswordString = loadedSettings.PassWord; Byte[] encryptedPassWordBytes = Convert.FromBase64String(encryptedPasswordString); var pass = "asd123"; byte[] salt = Encoding.UTF8.GetBytes("123asd"); var decryptedPasswordString = SharedCrypto.DecryptAes(encryptedPassWordBytes, pass, salt); Password = decryptedPasswordString; retVal = true; } return retVal; } catch (Exception ex) { Console.WriteLine($"SharedSettings.LoadSettings: {ex.Message}"); ResetSettings(); return false; } } public async Task SaveSettingsAsync(UserSettings settings) { try { string filePath = Path.Combine(FileSystem.AppDataDirectory, "settings.xml"); using (var writer = new StreamWriter(filePath)) { XmlSerializer serializer = new XmlSerializer(typeof(UserSettings)); serializer.Serialize(writer, settings); } Console.WriteLine($"Settings saved successfully: {filePath}"); } catch (Exception ex) { Console.WriteLine($"Error saving settings: {ex.Message}"); } } public async Task<UserSettings> LoadSettingsAsync() { try { string filePath = Path.Combine(FileSystem.AppDataDirectory, "settings.xml"); Console.WriteLine($"Settings file path: {filePath}"); if (!File.Exists(filePath)) { Console.WriteLine("No settings file found."); return null; // No settings file exists } using (var reader = new StreamReader(filePath)) { XmlSerializer serializer = new XmlSerializer(typeof(UserSettings)); return (UserSettings)serializer.Deserialize(reader); } } catch (Exception ex) { Console.WriteLine($"Error loading settings: {ex.Message}"); return null; } } public void ResetSettings() { try { SecureStorage.Default.RemoveAll(); } catch (Exception ex) { Console.WriteLine($"SharedSettings.ResetSettings: {ex.Message}"); } } } public class UserSettings { public string UserName; public string UserId; public string PassWord; } }
SharedCrypto加密工具类
using System.Text; using PCLCrypto; namespace CryptoForms { /// <summary> /// Common cryptographic helper /// </summary> public static class SharedCrypto { /// <summary> /// Creates Salt with given length in bytes. /// </summary> /// <param name="lengthInBytes">No. of bytes</param> /// <returns></returns> public static byte[] CreateSalt(int lengthInBytes) { try { return WinRTCrypto.CryptographicBuffer.GenerateRandom(lengthInBytes); } catch (Exception e) { Console.WriteLine($"SharedCrypto.CreateSalt: {e.Message}"); return null; } } /// <summary> /// Creates a derived key from a combination /// </summary> /// <param name="password"></param> /// <param name="salt"></param> /// <param name="keyLengthInBytes"></param> /// <param name="iterations"></param> /// <returns></returns> public static byte[] CreateDerivedKey(string password, byte[] salt, int keyLengthInBytes = 32, int iterations = 1000) { try { byte[] key = NetFxCrypto.DeriveBytes.GetBytes(password, salt, iterations, keyLengthInBytes); return key; } catch (Exception e) { Console.WriteLine($"SharedCrypto.CreateDerivedKey: {e.Message}"); return null; } } /// <summary> /// Encrypts given data using symmetric algorithm AES /// </summary> /// <param name="data">Data to encrypt</param> /// <param name="password">Password</param> /// <param name="salt">Salt</param> /// <returns>Encrypted bytes</returns> public static byte[] EncryptAes(string data, string password, byte[] salt) { try { byte[] key = CreateDerivedKey(password, salt); ISymmetricKeyAlgorithmProvider aes = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); ICryptographicKey symetricKey = aes.CreateSymmetricKey(key); var bytes = WinRTCrypto.CryptographicEngine.Encrypt(symetricKey, Encoding.UTF8.GetBytes(data)); return bytes; } catch (Exception e) { Console.WriteLine($"SharedCrypto.EncryptAes: {e.Message}"); return null; } } /// <summary> /// Decrypts given bytes using symmetric algorithm AES /// </summary> /// <param name="data">data to decrypt</param> /// <param name="password">Password used for encryption</param> /// <param name="salt">Salt used for encryption</param> /// <returns></returns> public static string DecryptAes(byte[] data, string password, byte[] salt) { try { byte[] key = CreateDerivedKey(password, salt); ISymmetricKeyAlgorithmProvider aes = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); ICryptographicKey symetricKey = aes.CreateSymmetricKey(key); var bytes = WinRTCrypto.CryptographicEngine.Decrypt(symetricKey, data); return Encoding.UTF8.GetString(bytes, 0, bytes.Length); } catch (Exception e) { Console.WriteLine($"SharedCrypto.DecryptAes: {e.Message}"); return null; } } } }
Entitlements.plist配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>aps-environment</key> <string>production</string> <key>keychain-access-groups</key> <array> <string>$(AppIdentifierPrefix)fi.mycompany.myApp</string> </array> </dict> </plist>
问题修复
经排查,问题根源为PCLCrypto NuGet包不支持iOS平台。建议替换为.NET 8内置的加密API(如System.Security.Cryptography命名空间下的类),或使用支持iOS平台的现代加密库,以确保Release模式下加密解密逻辑正常执行,避免用户数据丢失。
内容的提问来源于stack exchange,提问作者user30393331
相关产品推荐
相关产品推荐

