有没有一种高性能的方法来替换.NET5中的BinaryFormatter?

23

在.NET5之前,我们通过以下代码对字节/对象进行序列化/反序列化:

    private static byte[] StructToBytes<T>(T t)
    {
        using (var ms = new MemoryStream())
        {
            var bf = new BinaryFormatter();
            bf.Serialize(ms, t);
            return ms.ToArray();
        }
    }

    private static T BytesToStruct<T>(byte[] bytes)
    {
        using (var memStream = new MemoryStream())
        {
            var binForm = new BinaryFormatter();
            memStream.Write(bytes, 0, bytes.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            var obj = binForm.Deserialize(memStream);
            return (T)obj;
        }
    }

但是由于安全原因,BinaryFormatter 将被移除:

https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide

那么是否有一种简单但高性能的方法来替换BinaryFormatter?


如果不仅仅是安全性和性能,而且还需要与BinaryFormatter高度兼容,您可以查看我的二进制序列化程序这里。还请参阅安全注意事项大小比较相关问题 - undefined
6个回答

17
在我的项目中,我们最近从.NET Core 3.1迁移到了.NET 5,并用Protobuf-net替换了我们的BinarySerializer代码:https://github.com/protobuf-net/protobuf-net 这些代码几乎完全相同,该项目非常有声望(目前)已经有2200万次下载和3200个星标在GitHub上。它非常快速,没有与BinarySerializer相关的任何安全隐患。
下面是我用于字节数组序列化的类:
public static class Binary
{
    /// <summary>
    /// Convert an object to a Byte Array, using Protobuf.
    /// </summary>
    public static byte[] ObjectToByteArray(object obj)
    {
        if (obj == null)
            return null;

        using var stream = new MemoryStream();

        Serializer.Serialize(stream, obj);

        return stream.ToArray();
    }

    /// <summary>
    /// Convert a byte array to an Object of T, using Protobuf.
    /// </summary>
    public static T ByteArrayToObject<T>(byte[] arrBytes)
    {
        using var stream = new MemoryStream();

        // Ensure that our stream is at the beginning.
        stream.Write(arrBytes, 0, arrBytes.Length);
        stream.Seek(0, SeekOrigin.Begin);

        return Serializer.Deserialize<T>(stream);
    }
}

我必须为我序列化的类添加属性。它只被 [Serializable] 装饰,虽然我知道 Protobuf 可以与很多常见装饰一起使用,但这个装饰并没有起作用。以下是在 GitHub 上的示例:

[ProtoContract]
class Person {
    [ProtoMember(1)]
    public int Id {get;set;}
    [ProtoMember(2)]
    public string Name {get;set;}
    [ProtoMember(3)]
    public Address Address {get;set;}
}

[ProtoContract]
class Address {
    [ProtoMember(1)]
    public string Line1 {get;set;}
    [ProtoMember(2)]
    public string Line2 {get;set;}
}

在我的情况下,我正在Redis中缓存一些东西,并且它运行得很好。
您也可以在.csproject文件中重新启用此功能:
<PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>

但这是个坏主意。BinaryFormatter是造成许多.NET历史性漏洞的罪魁祸首,而且它无法修复。在未来的.NET版本中,它可能会完全不可用,因此替换它是正确的选择。


1
https://dev59.com/A6Lia4cB1Zd3GeqPrfko - Hans Passant
5
不要因上述SO问题中的“安全”问题而感到沮丧。微软介绍的XmlSerializerSystem.Text.JSON替代方案同样容易受到窥探攻击,但这些问题都可以通过加密流来缓解。相比之下,BinaryFormatter的漏洞更为严重。一个格式不正确的二进制数据源可能会导致从崩溃到成为恶意软件的向量等各种问题。 - NPras
1
@NPras 那也是我的结论。重要的是要知道它在开箱即用时并不安全,但也不会直接受到无法解决的漏洞攻击。 - Brian MacKay
@BrianMacKay 嘿,伙计,我在复制粘贴你的代码时注意到你反序列化的部分,“确保我们的流在开头”。你不觉得你在这里做了额外的工作吗?只需将arrBytes传递到MemoryStream构造函数中,即new MemoryStream(arrbytes)...那有什么问题吗?这样不也会“确保我们的流在开头”吗? - Alireza Jamali
@AlirezaJamali 如果不这样做会发生什么?我想我之前因为在流的末尾而出现了错误 - 不过已经有一段时间了。 - Brian MacKay
@BrianMacKay好的,我成功完成了这个任务,并且没有出现任何错误。我很好奇是否有必要这样做,如果不这样做,是否会在未来出现问题。顺便说一句,谢谢。 - Alireza Jamali

15

如果您正在使用.NET Core 5或更高版本,您可以使用新的System.Text.Json.JsonSerializer.SerializeSystem.Text.Json.JsonSerializer.Deserialize方法:

public static class Binary
{
    /// <summary>
    /// Convert an object to a Byte Array.
    /// </summary>
    public static byte[] ObjectToByteArray(object objData)
    {
        if (objData == null)
            return default;

       return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(objData, GetJsonSerializerOptions()));
    }

    /// <summary>
    /// Convert a byte array to an Object of T.
    /// </summary>
    public static T ByteArrayToObject<T>(byte[] byteArray)
    {
        if (byteArray == null || !byteArray.Any())          
            return default;
            
        return JsonSerializer.Deserialize<T>(byteArray, GetJsonSerializerOptions());
    }

    private static JsonSerializerOptions GetJsonSerializerOptions()
    {
        return new JsonSerializerOptions()
        {
            PropertyNamingPolicy = null,
            WriteIndented = true,
            AllowTrailingCommas = true,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        };
    }
}

3
虽然这是一个老话题,但它仍然相关,特别是如果您发现自己处理存储在Memcached(或Redis、本地或云中的辅助存储)中的.NET数据的代码。如OP所述,BinaryFormatter存在安全问题,而且性能和大小也存在问题。
一个很好的替代方案是MessagePack格式,更具体地说是针对.NET解决方案的MessagePack NuGet package
它是安全的、维护良好的、速度更快、更小。有关详细信息,请参见基准测试ZeroFormatter 也似乎是一个很好的替代方案。
在今天以云为中心的解决方案中,尺寸和容量对于降低成本非常有帮助。

2

1
public static T Clone<T>(this T source) where T : class
        {
            if (!typeof(T).IsSerializable)
                throw new ArgumentException(string.Format("The type '{0}' must be serializable", source.GetType().Name));

            // Don't serialize a null object, simply return the default for that object
            if (ReferenceEquals(source, null))
                return default(T);

            using (Stream stream = new MemoryStream())
            {
                var writer = new BinaryWriter(stream, Encoding.UTF8, false);
                
                DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(T));
                js.WriteObject(stream, source);
                stream.Seek(0, SeekOrigin.Begin);

                // Return deserialzed object
                return (T)js.ReadObject(stream);
            }
        }

0

有一个选项可以在 .NET Core 5 中使用:

只需添加

<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>

对于类似的项目:

<PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>

我相信它会起作用。


1
是的,我已经添加了它,但我认为这不是风险的最佳解决方案。 - Fair
直到能够更改代码使用替代方案,这是最好的方法。 - user2981411
@Fair 这是最糟糕的方法之一,并且有时间限制。.NET 5.0 是一个“当前”版本,这意味着一旦明年推出 .NET 5,它将不再受到支持。不要指望这个不安全的开关还能起作用。Protocol Buffers 是一个优秀的、跨平台的、广泛支持的替代方案。 - Panagiotis Kanavos
由于存在安全问题,因此不建议在生产环境中使用。 - Mustafa Salih ASLIM
1
有趣的是,我们现在正在使用 .net 8(beta)版本,而这个类仍然得到支持。但是 @Panagiotis Kanavos 的警告仍然有效。总有一天它会被淘汰。在那之前,它仍然是不安全的,所以使用时要自行承担风险。 - Randy Kreisel
显示剩余2条评论

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