Here is a idea which uses NuGet package protobuf-net. In the code below I save data to a file name DataLibrary.dll which I doubt that someone looking to hack your app will look at and its encrypted.
I use a list as I stole the code from here which a list is used.
Viewing the file with JetBrains dotPeek
With VS Code
using System.Security.Cryptography;
#pragma warning disable SYSLIB0023
#pragma warning disable SYSLIB0021
namespace TODO;
/// <summary>
/// Serializer to encrypt/decrypt objects.
/// Uses https://github.com/protobuf-net/protobuf-net
/// </summary>
/// <typeparam name="T">Type of object to serialize/deserialize.</typeparam>
public class CryptoSerializer<T>
{
private readonly byte[] _secretKey;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="secretKey">
/// Secret key. Legal AES keys are 16, 24, or 32 bytes long.
/// </param>
public CryptoSerializer(byte[] secretKey)
{
_secretKey = secretKey;
}
/// <summary>
/// Serialization callback that can be registered with
/// a cache using CacheBuilder.SetSerialization
/// </summary>
public void Serialize(List<T> list, Stream stream)
{
// The first 16 bytes of the serialized stream is the
// AES initialization vector. (An IV does not need to be
// secret, but the same IV should never be used twice with
// the same key.)
byte[] iv = GenerateRandomBytes(16);
stream.Write(iv, 0, 16);
using AesCryptoServiceProvider aes = new();
aes.Key = _secretKey;
aes.IV = iv;
CryptoStream cryptoStream = new(stream, aes.CreateEncryptor(), CryptoStreamMode.Write);
// Using protobuf-net for serialization
// (but any serializer can be used to write to this CryptoStream).
ProtoBuf.Serializer.Serialize(cryptoStream, list);
cryptoStream.FlushFinalBlock();
}
/// <summary>
/// Deserialization callback that can be registered with
/// a cache using CacheBuilder.SetSerialization
/// </summary>
public List<T> Deserialize(Stream stream)
{
// First 16 bytes is the initialization vector.
byte[] ivBytes = new byte[16];
stream.Read(ivBytes, 0, 16);
using AesCryptoServiceProvider aes = new();
aes.Key = _secretKey;
aes.IV = ivBytes;
CryptoStream cryptoStream = new(stream, aes.CreateDecryptor(), CryptoStreamMode.Read);
return ProtoBuf.Serializer.Deserialize<List<T>>(cryptoStream);
}
private static readonly RNGCryptoServiceProvider provider = new();
private static byte[] GenerateRandomBytes(int length)
{
byte[] randomBytes = new byte[length];
provider.GetBytes(randomBytes);
return randomBytes;
}
}
The following has several classes mashed together which you can break-apart.
using ProtoBuf;
namespace TODO;
internal class Operations
{
/// <summary>
/// Give the file holding information a name which prying eye's most likely
/// will not look at.
/// </summary>
public static string FileName => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DataLibrary.dll");
public static void Reset()
{
File.Delete(FileName);
}
public static void Serialize(List<ItemData> list)
{
CryptoSerializer<ItemData> cryptoSerializer = new(Secrets.Key);
using FileStream fileStream = new(FileName, FileMode.OpenOrCreate);
cryptoSerializer.Serialize(list, fileStream);
}
public static List<ItemData> Deserialize()
{
CryptoSerializer<ItemData> cryptoSerializer = new(Secrets.Key);
using FileStream fileStream = new(FileName, FileMode.Open);
return cryptoSerializer.Deserialize(fileStream);
}
}
[ProtoContract]
public class ItemData
{
[ProtoMember(1)]
public string LabelValue { get; set; }
[ProtoMember(2)]
public string UserName { get; set; }
}
public class Secrets
{
// IMPORTANT:
// This cryptographic key is defined in code for demonstration purposes.
// Production keys should be stored in a secure location,
// (such as Azure Key Vault or AWS KMS) or protected using .NET's
// ProtectedData class.
public static readonly byte[] Key =
{
87, 167, 103, 151, 197, 100, 254, 130, 74, 59, 51, 28, 26, 230, 7,
97, 137, 224, 69, 23, 51, 110, 3, 37, 157, 41, 12, 12, 158, 24, 30, 86
};
}
Save data (note the UserName is optional)
Operations.Serialize(new List<ItemData>() {new ItemData()
{
LabelValue = label1.Text,
UserName = "KP"
}});
Read back
List<ItemData> data = Operations.Deserialize();
label1.Text = data.FirstOrDefault().LabelValue;