在UWP应用中对一个对象进行加密并将其序列化到文件的操作

3

好的,我有一个将我的玩家对象序列化为XML文件的方法, 我一直在尝试找到一种在将对象写入文件之前加密数据的方法。 这个主题在这里和互联网上有很多帖子,我试图将所有数据结合起来让它在我的应用程序上运行,并最终得出了这个:

    public static async Task SaveAsync<T>(Player player)
    {
        IRandomAccessStream sessionRandomAccess = null;
        string strAlgName = SymmetricAlgorithmNames.AesCbc;
        UInt32 keyLength = 32;
        CryptographicKey key;
        SymmetricKeyAlgorithmProvider objAlg = SymmetricKeyAlgorithmProvider.OpenAlgorithm(strAlgName);
        IBuffer keyMaterial = CryptographicBuffer.GenerateRandom(keyLength);
        key = objAlg.CreateSymmetricKey(keyMaterial);
        IBuffer iv = null;
        if (strAlgName.Contains("CBC"))
        {
            iv = CryptographicBuffer.GenerateRandom(objAlg.BlockLength);
        }
        StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("Data.xml", CreationCollisionOption.ReplaceExisting);
        sessionRandomAccess = await sessionFile.OpenAsync(FileAccessMode.ReadWrite);
        IBuffer buffEncrypt = CryptographicEngine.Encrypt(key, ReadFully(sessionRandomAccess.AsStream()).AsBuffer(), iv);
        DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Player), new Type[] { typeof(T) });
        Stream stream = buffEncrypt.AsStream();
        sessionSerializer.WriteObject(stream, player);
        await stream.FlushAsync();
    }

将流转换为字节数组的方法:

    public static byte[] ReadFully(Stream input)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            input.CopyTo(ms);
            return ms.ToArray();
        }
    }

现在一切都运作良好,直到WriteObject方法被调用,然后我会收到以下异常:

An exception of type 'System.NotSupportedException' occurred in mscorlib.ni.dll but was not handled in user code

Additional information: Unable to expand length of this stream beyond its capacity.

有没有任何想法如何解决这个问题,为什么会发生这种情况?谢谢。

更新: 我认为我可以添加一个对我有效的解密方法,作为包含加密部分的绝佳答案的补充-由Jay Zuo - MSFT提供。

所以这就是:

    public static async Task<Player> LoadAsync<T>()
    {
        try
        {

                StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("Data.xml", CreationCollisionOption.OpenIfExists);
                if (sessionFile == null)
                {
                    return null;
                }
                IBuffer buffEncrypt = await FileIO.ReadBufferAsync(sessionFile);
                String strAlgName = SymmetricAlgorithmNames.AesCbcPkcs7;
                CryptographicKey key;              // Symmetric key
                // Open a symmetric algorithm provider for the specified algorithm.
                SymmetricKeyAlgorithmProvider objAlg = SymmetricKeyAlgorithmProvider.OpenAlgorithm(strAlgName);
                key = objAlg.CreateSymmetricKey(keyMaterial);
                IBuffer buffMsg=CryptographicEngine.Decrypt(key, buffEncrypt, iv);
                DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Player));
                return (Player)sessionSerializer.ReadObject(buffMsg.AsStream());
            }
        catch (Exception e)
        {
        }
        return null;
    }

请注意,iv IBuffer 和 keyMaterial 被存储并从数据库中加载,如 Jay Zuo - MSFT 建议的。 希望这能对某些人有所帮助 :)

正如错误消息所述,流中没有足够的空间来容纳序列化对象。您使用包含加密数据的缓冲区实例化了流;我不是100%确定,但似乎创建的流大小将根据IBuffer固定。序列化需要比那更多的空间。 - KC Wong
1个回答

2
要加密序列化对象,我们首先可以获取表示序列化对象的IBuffer
IBuffer buffMsg;

DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Player));
using (var stream = new MemoryStream())
{
    sessionSerializer.WriteObject(stream, player);

    buffMsg = stream.ToArray().AsBuffer();
}

然后我们可以像下面这样加密缓冲区:
IBuffer iv;                        // Initialization vector
CryptographicKey key;              // Symmetric key
UInt32 keyLength = 32;             // Length of the key, in bytes
String strAlgName = SymmetricAlgorithmNames.AesCbc;

// Initialize the initialization vector.
iv = null;

// Open a symmetric algorithm provider for the specified algorithm.
SymmetricKeyAlgorithmProvider objAlg = SymmetricKeyAlgorithmProvider.OpenAlgorithm(strAlgName);

// Determine whether the message length is a multiple of the block length.
// This is not necessary for PKCS #7 algorithms which automatically pad the
// message to an appropriate length.
if (!strAlgName.Contains("PKCS7"))
{
    if ((buffMsg.Length % objAlg.BlockLength) != 0)
    {
        throw new Exception("Message buffer length must be multiple of block length.");
    }
}

// Create a symmetric key.
IBuffer keyMaterial = CryptographicBuffer.GenerateRandom(keyLength);
key = objAlg.CreateSymmetricKey(keyMaterial);

// CBC algorithms require an initialization vector. Here, a random
// number is used for the vector.
if (strAlgName.Contains("CBC"))
{
    iv = CryptographicBuffer.GenerateRandom(objAlg.BlockLength);
}

// Encrypt the data and return.
IBuffer buffEncrypt = CryptographicEngine.Encrypt(key, buffMsg, iv);

一旦我们获得了加密缓冲区,就可以将其写入文件:
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("Data.xml", CreationCollisionOption.ReplaceExisting);

await FileIO.WriteBufferAsync(sessionFile, buffEncrypt);

请注意,SymmetricAlgorithmNames.AesCbc 是一种 无填充算法。在使用该算法时,我们要加密的消息缓冲区长度必须是块长度的倍数。由于序列化对象的缓冲区长度是可变的,建议使用 PKCS #7 算法,例如 SymmetricAlgorithmNames.AesCbcPkcs7,而不是 SymmetricAlgorithmNames.AesCbc。对于解密,您还需要存储对称密钥初始化向量

感谢您的回答,虽然SymmetricAlgorithmNames.AesCbc没有起作用,并且一直抛出所需双倍大小的异常,但我按照您的建议使用了PKCS#7,它完美地工作了。我还有一个小问题,我想通过我创建的Web应用程序服务将对称密钥和初始化向量存储在我的Azure Sql数据库中,您有什么建议关于最好的数据类型来存储它们,如果需要转换,应该如何进行? - Fadi Banna
@FadiBanna 在示例中,初始化向量是一个“缓冲区”,对称密钥也是从“keyMaterial”创建的,它也是一个“缓冲区”。Azure SQL 数据库支持二进制或可变二进制。因此,我们可以使用ToArray方法,如keyMaterial.ToArray();来获取byte[],然后将它们存储在Azure SQL数据库中。 - Jay Zuo
1
@FadiBanna 另一种方法是将它们存储为字符串。我们可以使用一些方法,例如CryptographicBuffer.EncodeToBase64String将缓冲区转换为base64字符串,然后将字符串存储在Azure SQL数据库中。在解密时,我们可以检索字符串并使用DecodeFromBase64String再次获取缓冲区。 - Jay Zuo
1
@FadiBanna 根据你的情况,你可以选择其中任何一个。希望这能帮到你。;-) - Jay Zuo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接