Skip to content

Commit 9aeb12b

Browse files
wtgodbeAArnott
andauthored
Use a collision-resistant hash algorithm for untrusted data (#5)
* Fix conflict * Use safer unaligned-read methods --------- Co-authored-by: Andrew Arnott <[email protected]>
1 parent 9511905 commit 9aeb12b

File tree

8 files changed

+551
-139
lines changed

8 files changed

+551
-139
lines changed

sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\MessagePackSecurity.cs">
7676
<Link>Code\MessagePackSecurity.cs</Link>
7777
</Compile>
78+
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\SipHash.cs">
79+
<Link>Code\SipHash.cs</Link>
80+
</Compile>
7881
<Compile Include="..\..\src\MessagePack.UnityClient\Assets\Scripts\MessagePack\MonoProtection.cs">
7982
<Link>Code\MonoProtection.cs</Link>
8083
</Compile>

src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSecurity.cs

+119-134
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
// Copyright (c) All contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
//// Originally from: https://github.com/paya-cz/siphash
5+
//// Author: Pavel Werl
6+
//// License: Public Domain
7+
//// SipHash website: https://131002.net/siphash/
8+
9+
using System;
10+
using System.Buffers;
11+
using System.Buffers.Binary;
12+
using System.Runtime.CompilerServices;
13+
using System.Runtime.InteropServices;
14+
using System.Security.Cryptography;
15+
16+
namespace MessagePack
17+
{
18+
/// <summary>
19+
/// Implements the <see href="https://en.wikipedia.org/wiki/SipHash">SipHash pseudo-random function</see>.
20+
/// </summary>
21+
/// <remarks>
22+
/// This class is immutable and thread-safe.
23+
/// </remarks>
24+
internal class SipHash
25+
{
26+
/// <summary>
27+
/// Part of the initial 256-bit internal state.
28+
/// </summary>
29+
private readonly ulong initialState0;
30+
31+
/// <summary>
32+
/// Part of the initial 256-bit internal state.
33+
/// </summary>
34+
private readonly ulong initialState1;
35+
36+
/// <summary>Initializes a new instance of the <see cref="SipHash"/> class using a random key.</summary>
37+
public SipHash()
38+
{
39+
using RandomNumberGenerator rng = RandomNumberGenerator.Create();
40+
#if SPAN_BUILTIN
41+
Span<byte> key = stackalloc byte[16];
42+
rng.GetBytes(key);
43+
#else
44+
byte[] buffer = ArrayPool<byte>.Shared.Rent(16);
45+
rng.GetBytes(buffer, 0, 16);
46+
Span<byte> key = buffer;
47+
#endif
48+
49+
this.initialState0 = 0x736f6d6570736575UL ^ BinaryPrimitives.ReadUInt64LittleEndian(key);
50+
this.initialState1 = 0x646f72616e646f6dUL ^ BinaryPrimitives.ReadUInt64LittleEndian(key.Slice(sizeof(ulong)));
51+
52+
#if !SPAN_BUILTIN
53+
ArrayPool<byte>.Shared.Return(buffer);
54+
#endif
55+
}
56+
57+
/// <summary>Initializes a new instance of the <see cref="SipHash"/> class using the specified 128-bit key.</summary>
58+
/// <param name="key">Key for the SipHash pseudo-random function. Must be exactly 16 bytes long.</param>
59+
/// <exception cref="ArgumentException">Thrown when <paramref name="key"/> is not exactly 16 bytes long (128 bits).</exception>
60+
public SipHash(ReadOnlySpan<byte> key)
61+
{
62+
if (key.Length != 16)
63+
{
64+
throw new ArgumentException("SipHash key must be exactly 128-bit long (16 bytes).", nameof(key));
65+
}
66+
67+
this.initialState0 = 0x736f6d6570736575UL ^ BinaryPrimitives.ReadUInt64LittleEndian(key);
68+
this.initialState1 = 0x646f72616e646f6dUL ^ BinaryPrimitives.ReadUInt64LittleEndian(key.Slice(sizeof(ulong)));
69+
}
70+
71+
/// <summary>
72+
/// Gets a 128-bit SipHash key.
73+
/// </summary>
74+
/// <param name="key">The 16-byte buffer that receives the key originally provided to the constructor.</param>
75+
public void GetKey(Span<byte> key)
76+
{
77+
if (key.Length != 16)
78+
{
79+
throw new ArgumentException("SipHash key must be exactly 128-bit long (16 bytes).", nameof(key));
80+
}
81+
82+
BinaryPrimitives.WriteUInt64LittleEndian(key, this.initialState0 ^ 0x736f6d6570736575UL);
83+
BinaryPrimitives.WriteUInt64LittleEndian(key.Slice(sizeof(ulong)), this.initialState1 ^ 0x646f72616e646f6dUL);
84+
}
85+
86+
/// <summary>Computes 64-bit SipHash tag for the specified message.</summary>
87+
/// <param name="data">The byte array for which to computer SipHash tag.</param>
88+
/// <returns>Returns 64-bit (8 bytes) SipHash tag.</returns>
89+
public long Compute(ReadOnlySpan<byte> data)
90+
{
91+
unchecked
92+
{
93+
// SipHash internal state
94+
ulong v0 = this.initialState0;
95+
ulong v1 = this.initialState1;
96+
97+
// It is faster to load the initialStateX fields from memory again than to reference v0 and v1:
98+
ulong v2 = 0x1F160A001E161714UL ^ this.initialState0;
99+
ulong v3 = 0x100A160317100A1EUL ^ this.initialState1;
100+
101+
// We process data in 64-bit blocks
102+
ulong block;
103+
104+
// The last 64-bit block of data
105+
int finalBlockPosition = data.Length & ~7;
106+
107+
// Process the input data in blocks of 64 bits
108+
for (int blockPosition = 0; blockPosition < finalBlockPosition; blockPosition += sizeof(ulong))
109+
{
110+
block = MemoryMarshal.Read<ulong>(data.Slice(blockPosition));
111+
112+
v3 ^= block;
113+
114+
// Round 1
115+
v0 += v1;
116+
v2 += v3;
117+
v1 = v1 << 13 | v1 >> 51;
118+
v3 = v3 << 16 | v3 >> 48;
119+
v1 ^= v0;
120+
v3 ^= v2;
121+
v0 = v0 << 32 | v0 >> 32;
122+
v2 += v1;
123+
v0 += v3;
124+
v1 = v1 << 17 | v1 >> 47;
125+
v3 = v3 << 21 | v3 >> 43;
126+
v1 ^= v2;
127+
v3 ^= v0;
128+
v2 = v2 << 32 | v2 >> 32;
129+
130+
// Round 2
131+
v0 += v1;
132+
v2 += v3;
133+
v1 = v1 << 13 | v1 >> 51;
134+
v3 = v3 << 16 | v3 >> 48;
135+
v1 ^= v0;
136+
v3 ^= v2;
137+
v0 = v0 << 32 | v0 >> 32;
138+
v2 += v1;
139+
v0 += v3;
140+
v1 = v1 << 17 | v1 >> 47;
141+
v3 = v3 << 21 | v3 >> 43;
142+
v1 ^= v2;
143+
v3 ^= v0;
144+
v2 = v2 << 32 | v2 >> 32;
145+
146+
v0 ^= block;
147+
}
148+
149+
// Load the remaining bytes
150+
block = (ulong)data.Length << 56;
151+
switch (data.Length & 7)
152+
{
153+
case 7:
154+
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition)) | (ulong)MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition + 4)) << 32 | (ulong)data[finalBlockPosition + 6] << 48;
155+
break;
156+
case 6:
157+
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition)) | (ulong)MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition + 4)) << 32;
158+
break;
159+
case 5:
160+
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition)) | (ulong)data[finalBlockPosition + 4] << 32;
161+
break;
162+
case 4:
163+
block |= MemoryMarshal.Read<uint>(data.Slice(finalBlockPosition));
164+
break;
165+
case 3:
166+
block |= MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition)) | (ulong)data[finalBlockPosition + 2] << 16;
167+
break;
168+
case 2:
169+
block |= MemoryMarshal.Read<ushort>(data.Slice(finalBlockPosition));
170+
break;
171+
case 1:
172+
block |= data[finalBlockPosition];
173+
break;
174+
}
175+
176+
// Process the final block
177+
{
178+
v3 ^= block;
179+
180+
// Round 1
181+
v0 += v1;
182+
v2 += v3;
183+
v1 = v1 << 13 | v1 >> 51;
184+
v3 = v3 << 16 | v3 >> 48;
185+
v1 ^= v0;
186+
v3 ^= v2;
187+
v0 = v0 << 32 | v0 >> 32;
188+
v2 += v1;
189+
v0 += v3;
190+
v1 = v1 << 17 | v1 >> 47;
191+
v3 = v3 << 21 | v3 >> 43;
192+
v1 ^= v2;
193+
v3 ^= v0;
194+
v2 = v2 << 32 | v2 >> 32;
195+
196+
// Round 2
197+
v0 += v1;
198+
v2 += v3;
199+
v1 = v1 << 13 | v1 >> 51;
200+
v3 = v3 << 16 | v3 >> 48;
201+
v1 ^= v0;
202+
v3 ^= v2;
203+
v0 = v0 << 32 | v0 >> 32;
204+
v2 += v1;
205+
v0 += v3;
206+
v1 = v1 << 17 | v1 >> 47;
207+
v3 = v3 << 21 | v3 >> 43;
208+
v1 ^= v2;
209+
v3 ^= v0;
210+
v2 = v2 << 32 | v2 >> 32;
211+
212+
v0 ^= block;
213+
v2 ^= 0xff;
214+
}
215+
216+
// 4 finalization rounds
217+
{
218+
// Round 1
219+
v0 += v1;
220+
v2 += v3;
221+
v1 = v1 << 13 | v1 >> 51;
222+
v3 = v3 << 16 | v3 >> 48;
223+
v1 ^= v0;
224+
v3 ^= v2;
225+
v0 = v0 << 32 | v0 >> 32;
226+
v2 += v1;
227+
v0 += v3;
228+
v1 = v1 << 17 | v1 >> 47;
229+
v3 = v3 << 21 | v3 >> 43;
230+
v1 ^= v2;
231+
v3 ^= v0;
232+
v2 = v2 << 32 | v2 >> 32;
233+
234+
// Round 2
235+
v0 += v1;
236+
v2 += v3;
237+
v1 = v1 << 13 | v1 >> 51;
238+
v3 = v3 << 16 | v3 >> 48;
239+
v1 ^= v0;
240+
v3 ^= v2;
241+
v0 = v0 << 32 | v0 >> 32;
242+
v2 += v1;
243+
v0 += v3;
244+
v1 = v1 << 17 | v1 >> 47;
245+
v3 = v3 << 21 | v3 >> 43;
246+
v1 ^= v2;
247+
v3 ^= v0;
248+
v2 = v2 << 32 | v2 >> 32;
249+
250+
// Round 3
251+
v0 += v1;
252+
v2 += v3;
253+
v1 = v1 << 13 | v1 >> 51;
254+
v3 = v3 << 16 | v3 >> 48;
255+
v1 ^= v0;
256+
v3 ^= v2;
257+
v0 = v0 << 32 | v0 >> 32;
258+
v2 += v1;
259+
v0 += v3;
260+
v1 = v1 << 17 | v1 >> 47;
261+
v3 = v3 << 21 | v3 >> 43;
262+
v1 ^= v2;
263+
v3 ^= v0;
264+
v2 = v2 << 32 | v2 >> 32;
265+
266+
// Round 4
267+
v0 += v1;
268+
v2 += v3;
269+
v1 = v1 << 13 | v1 >> 51;
270+
v3 = v3 << 16 | v3 >> 48;
271+
v1 ^= v0;
272+
v3 ^= v2;
273+
v0 = v0 << 32 | v0 >> 32;
274+
v2 += v1;
275+
v0 += v3;
276+
v1 = v1 << 17 | v1 >> 47;
277+
v3 = v3 << 21 | v3 >> 43;
278+
v1 ^= v2;
279+
v3 ^= v0;
280+
v2 = v2 << 32 | v2 >> 32;
281+
}
282+
283+
return (long)((v0 ^ v1) ^ (v2 ^ v3));
284+
}
285+
}
286+
}
287+
}

src/MessagePack.UnityClient/Assets/Scripts/MessagePack/SipHash.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSecurityTests.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,8 @@ public void EqualityComparer_Enums()
125125
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt16Enum>());
126126
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt32Enum>());
127127
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt32Enum>());
128-
129-
// Supporting enums with backing integers that exceed 32-bits would likely require Ref.Emit of new types
130-
// since C# doesn't let us cast T to the underlying int type.
131-
Assert.Throws<TypeAccessException>(() => MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt64Enum>());
132-
Assert.Throws<TypeAccessException>(() => MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt64Enum>());
128+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeInt64Enum>());
129+
Assert.NotNull(MessagePackSecurity.UntrustedData.GetEqualityComparer<SomeUInt64Enum>());
133130
}
134131

135132
[Fact]

0 commit comments

Comments
 (0)