.Net 4.6破坏了XOR密码模式?

12

在.NET 4.5中,此加密算法在32位和64位架构上完美运行。将项目切换到.NET 4.6后,64位上完全破坏了此密码,而在32位上有一个奇怪的补丁来解决问题。

在我的方法“DecodeSkill”中,“SkillLevel是唯一在.NET 4.6上出现问题的部分。这里使用的变量从网络流中读取并进行编码。

DecodeSkill(始终返回适当解码的值)

    private void DecodeSkill()
    {
        SkillId = (ushort) (ExchangeShortBits((SkillId ^ ObjectId ^ 0x915d), 13) + 0x14be);
        SkillLevel = ((ushort) ((byte)SkillLevel ^ 0x21));
        TargetObjectId = (ExchangeLongBits(TargetObjectId, 13) ^ ObjectId ^ 0x5f2d2463) + 0x8b90b51a;
        PositionX = (ushort) (ExchangeShortBits((PositionX ^ ObjectId ^ 0x2ed6), 15) + 0xdd12);
        PositionY = (ushort) (ExchangeShortBits((PositionY ^ ObjectId ^ 0xb99b), 11) + 0x76de);
    }

交换短位

    private static uint ExchangeShortBits(uint data, int bits)
    {
        data &= 0xffff;
        return (data >> bits | data << (16 - bits)) & 65535;
    }

DecodeSkill (.NET 4.6 32位版已打补丁,注意“var patch = SkillLevel”)

    private void DecodeSkill()
    {
        SkillId = (ushort) (ExchangeShortBits((SkillId ^ ObjectId ^ 0x915d), 13) + 0x14be);
        var patch = SkillLevel = ((ushort) ((byte)SkillLevel ^ 0x21));
        TargetObjectId = (ExchangeLongBits(TargetObjectId, 13) ^ ObjectId ^ 0x5f2d2463) + 0x8b90b51a;
        PositionX = (ushort) (ExchangeShortBits((PositionX ^ ObjectId ^ 0x2ed6), 15) + 0xdd12);
        PositionY = (ushort) (ExchangeShortBits((PositionY ^ ObjectId ^ 0xb99b), 11) + 0x76de);
    }

将变量命名为SkillLevel,并且仅在32位模式下,将会导致SkillLevel始终是正确的值。如果删除此修补程序,则该值将始终不正确。在64位模式下,即使使用修补程序,此值也始终不正确。

我尝试在解码方法上使用MethodImplOptions.NoOptimization和MethodImplOptions.NoInlining,以期望获得改进。

有什么想法会引起这种情况吗?

编辑:我被要求提供输入、良好输出和错误输出的示例。 这来自一个实际的使用场景,值由客户端发送,并使用.NET 4.6上的“修补程序”正确解码。

输入:

ObjectId = 1000001

TargetObjectId = 2778236265
PositionX = 32409
PositionY = 16267
SkillId = 28399
SkillLevel = 8481

良好的输出

TargetObjectId = 0
PositionX = 302
PositionY = 278
SkillId = 1115
SkillLevel = 0

输出错误

TargetObjectId = 0
PositionX = 302
PositionY = 278
SkillId = 1115
SkillLevel = 34545

编辑#2:

我应该包含这一部分,这绝对是其中一个重要的部分。

EncodeSkill(时间戳为Environment.TickCount)

     private void EncodeSkill()
    {
        SkillId = (ushort) (ExchangeShortBits(ObjectId - 0x14be, 3) ^ ObjectId ^ 0x915d);
        SkillLevel = (ushort) ((SkillLevel + 0x100*(Timestamp%0x100)) ^ 0x3721);
        Arg1 = MathUtils.BitFold32(SkillId, SkillLevel);
        TargetObjectId = ExchangeLongBits(((TargetObjectId - 0x8b90b51a) ^ ObjectId ^ 0x5f2d2463u), 19);
        PositionX = (ushort) (ExchangeShortBits((uint) PositionX - 0xdd12, 1) ^ ObjectId ^ 0x2ed6);
        PositionY = (ushort) (ExchangeShortBits((uint) PositionY - 0x76de, 5) ^ ObjectId ^ 0xb99b);
    }

BitFold32

    public static int BitFold32(int lower16, int higher16)
    {
        return (lower16) | (higher16 << 16);
    }

交换长整型位

    private static uint ExchangeLongBits(uint data, int bits)
    {
        return data >> bits | data << (32 - bits);
    }

1
你能举一个输入数据、好的输出和坏的输出的例子吗? - Ron Beyer
好的,请给我2-3分钟。 - Scotty
4
@Scotty,你能否发布一个sscce,以实际复现该问题?这样就能够获得一个实际可编译的东西,而不必分析这个伪代码,这会很有帮助。 - Asad Saeeduddin
1
在C#语言中,这些操作没有未定义的行为。结果是精确定义的。无论它是什么,它应该始终保持相同。这是.NET 4.6发布后我听说的第三个JIT错误。请在CoreCLR项目的GitHub上报告它。 - usr
1
调试模式下,如果使用 var x = SkillLevel,则值将正确。发布模式下,您无法纠正该值。但是Asad提供了解决此问题的方法。 - Scotty
显示剩余9条评论
1个回答

5
这是我设计的一段代码,我认为它类似于你实际情况中的代码:
using System;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        var dc = new Decoder();
        dc.DecodeSkill();
        Debug.Assert(dc.TargetObjectId == 0m && dc.PositionX == 302 && dc.PositionY == 278 && dc.SkillId == 1115 && dc.SkillLevel == 0);
    }
}

class Decoder
{
    public uint ObjectId = 1000001;
    public uint TargetObjectId = 2778236265;
    public ushort PositionX = 32409;
    public ushort PositionY = 16267;
    public ushort SkillId = 28399;
    public ushort SkillLevel = 8481;

    public void DecodeSkill()
    {
        SkillId = (ushort)(ExchangeShortBits((SkillId ^ ObjectId ^ 0x915d), 13) + 0x14be);
        SkillLevel = ((ushort)((byte)(SkillLevel) ^ 0x21));
        TargetObjectId = (ExchangeLongBits(TargetObjectId, 13) ^ ObjectId ^ 0x5f2d2463) + 0x8b90b51a;
        PositionX = (ushort)(ExchangeShortBits((PositionX ^ ObjectId ^ 0x2ed6), 15) + 0xdd12);
        PositionY = (ushort)(ExchangeShortBits((PositionY ^ ObjectId ^ 0xb99b), 11) + 0x76de);
    }

    private static uint ExchangeShortBits(uint data, int bits)
    {
        data &= 0xffff;
        return (data >> bits | data << (16 - bits)) & 65535;
    }

    public static int BitFold32(int lower16, int higher16)
    {
        return (lower16) | (higher16 << 16);
    }

    private static uint ExchangeLongBits(uint data, int bits)
    {
        return data >> bits | data << (32 - bits);
    }
}

您正在将8481与33进行异或运算。这是8448,这也是我在我的机器上看到的。假设SkillLevel是一个ushort,我认为正在发生的是您希望将SkillLevel强制转换为byte以截断SkillLevel,使得剩下的只有最后8位,但实际情况并非如此,因此当您再次强制转换为ushort时,高阶位仍然存在。
如果您想可靠地截断低8位后的所有数字,您需要像这样进行按位掩码:
SkillLevel = ((ushort) ((SkillLevel & 255) ^ 0x21));

编辑:

我怀疑这与运算符的数字升级有关。当应用于byte ushortint时,^ 运算符将提升两个操作数为int,因为从第一个操作数的两种可能类型到int都存在隐式转换。看起来发生的是ushort转换为byte显式转换被跳过了,这会导致截断。现在你只有两个int,当进行异或运算后,再截回ushort,保留它们的高位。


1
那么将其转换为字节,他在某些机器上的预期结果是正常的(包括我的32位4.5版本),但在4.6版本下却不行?规格中是否有什么改变?还是实现行为发生了改变? - Mark Peters
2
这是虚假的。在C#语言中,这些操作没有未定义的行为。结果是精确定义的。不管它是什么,在过去10年的所有CLR上都应该始终相同。 - usr
位运算是答案,显然自.NET 4.5-4.6以来,将值重新打包为字节发生了变化。 - Scotty
@MarkPeters 我并没有在.NET 4.5和.NET 4.6上看到任何行为差异。在这两种情况下,它在32位中工作,在64位中给我8448。但是,这可能是因为.NET 4.6是.NET 4.5的就地升级。关于“34545”,我无法复现。 - Asad Saeeduddin
4
可信。一种破损的优化,跳过了一个截断转换。 - usr
显示剩余7条评论

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