Skip to content

Commit eae5920

Browse files
committed
Fixed a critical bug/vulnerability in ObliviousTransfer/HashRandomOracle.
After filling the buffer stream over which the hash would be computed to generate randomized output bytes, the stream cursor was not moved to the beginning, causing the hash to be always computed over the empty word, thus giving a constant output no matter the seed / supplied query value.
1 parent dc6da7d commit eae5920

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

CompactMPC/ObliviousTransfer/HashRandomOracle.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public HashRandomOracle(HashAlgorithm hashAlgorithm)
2121
_hashAlgorithm = hashAlgorithm;
2222
_hashAlgorithmLock = new object();
2323
}
24-
24+
2525
public override IEnumerable<byte> Invoke(byte[] query)
2626
{
2727
byte[] seed;
@@ -38,6 +38,9 @@ public override IEnumerable<byte> Invoke(byte[] query)
3838
{
3939
stream.Write(seed, 0, seed.Length);
4040
stream.Write(BitConverter.GetBytes(counter), 0, 4);
41+
// note(lumip): seek to beginning of the stream! otherwise, ComputeHash will start at the end and compute the
42+
// hash over the empty word no matter what query/seed is given!
43+
stream.Seek(0, SeekOrigin.Begin);
4144

4245
lock (_hashAlgorithmLock)
4346
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Text;
3+
using System.Linq;
4+
using System.Collections;
5+
using System.Threading.Tasks;
6+
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using System.Collections.Generic;
9+
10+
namespace CompactMPC.ObliviousTransfer.UnitTests
11+
{
12+
[TestClass]
13+
public class HashRandomOracleTest
14+
{
15+
[TestMethod]
16+
public void TestInvoke()
17+
{
18+
using (CryptoContext cryptoContext = CryptoContext.CreateDefault())
19+
{
20+
RandomOracle oracle = new HashRandomOracle(cryptoContext.HashAlgorithm);
21+
22+
byte[] query1Bytes = { 235, 12, 13, 72, 138, 13, 62, 13, 39, 147, 198, 173, 23, 87, 27, 99 };
23+
byte[] query2Bytes = { 84, 23, 123, 85, 62, 28, 54, 98, 187, 238, 18, 5, 78, 1, 78, 243 };
24+
25+
byte[] response1 = oracle.Invoke(query1Bytes).Take(10).ToArray();
26+
byte[] response2 = oracle.Invoke(query2Bytes).Take(10).ToArray();
27+
CollectionAssert.AreNotEqual(response1, response2);
28+
}
29+
}
30+
}
31+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System;
2+
using System.Text;
3+
using System.Linq;
4+
using System.Collections;
5+
using System.Threading.Tasks;
6+
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using System.Collections.Generic;
9+
10+
namespace CompactMPC.ObliviousTransfer.UnitTests
11+
{
12+
internal class RandomOracleStub : RandomOracle
13+
{
14+
private Dictionary<byte[], byte[]> _valueDict;
15+
16+
public RandomOracleStub(Dictionary<byte[], byte[]> valueDict)
17+
{
18+
_valueDict = new Dictionary<byte[], byte[]>(valueDict);
19+
}
20+
21+
/// <inheritdoc />
22+
/// <remarks>
23+
/// <exception cref="KeyNotFoundException">If the query is not found in the value dictionary passed into the constructor.</exception>
24+
/// </remarks>
25+
public override IEnumerable<byte> Invoke(byte[] query)
26+
{
27+
return _valueDict[query];
28+
}
29+
}
30+
31+
[TestClass]
32+
public class RandomOracleTest
33+
{
34+
[TestMethod]
35+
public void TestStub()
36+
{
37+
Dictionary<byte[], byte[]> keyValuePairs = new Dictionary<byte[], byte[]>();
38+
keyValuePairs[BitArray.FromBinaryString("1001").ToBytes()] = BitArray.FromBinaryString("100101011100101").ToBytes();
39+
keyValuePairs[BitArray.FromBinaryString("1010").ToBytes()] = BitArray.FromBinaryString("101011110101100").ToBytes();
40+
41+
RandomOracle oracle = new RandomOracleStub(keyValuePairs);
42+
foreach (KeyValuePair<byte[], byte[]> pair in keyValuePairs)
43+
{
44+
Assert.AreEqual(pair.Value, oracle.Invoke(pair.Key));
45+
}
46+
}
47+
48+
[TestMethod]
49+
public void TestMask()
50+
{
51+
var query1 = BitArray.FromBinaryString("1001");
52+
var query2 = BitArray.FromBinaryString("1010");
53+
byte[] query1Bytes = query1.ToBytes();
54+
byte[] query2Bytes = query2.ToBytes();
55+
56+
var value1 = BitArray.FromBinaryString("1001010111001011");
57+
var value2 = BitArray.FromBinaryString("1010111101011001");
58+
59+
Dictionary<byte[], byte[]> keyValuePairs = new Dictionary<byte[], byte[]>();
60+
keyValuePairs[query1Bytes] = value1.ToBytes();
61+
keyValuePairs[query2Bytes] = value2.ToBytes();
62+
63+
RandomOracle oracle = new RandomOracleStub(keyValuePairs);
64+
65+
var message1 = BitArray.FromBinaryString("0000000000000000");
66+
var message2 = BitArray.FromBinaryString("1100110011001100");
67+
byte[] message1Bytes = message1.ToBytes();
68+
byte[] message2Bytes = message2.ToBytes();
69+
70+
byte[] masked11 = oracle.Mask(message1Bytes, query1Bytes);
71+
byte[] masked12 = oracle.Mask(message1Bytes, query2Bytes);
72+
CollectionAssert.AreEqual(keyValuePairs[query1Bytes], masked11);
73+
CollectionAssert.AreEqual(keyValuePairs[query2Bytes], masked12);
74+
75+
76+
byte[] masked21 = oracle.Mask(message2Bytes, query1Bytes);
77+
byte[] masked22 = oracle.Mask(message2Bytes, query2Bytes);
78+
79+
BitArray expected12 = BitArray.FromBytes(keyValuePairs[query2Bytes], 16);
80+
expected12.Xor(message1);
81+
BitArray expected22 = BitArray.FromBytes(keyValuePairs[query2Bytes], 16);
82+
expected22.Xor(message2);
83+
84+
CollectionAssert.AreEqual(expected12.ToBytes(), masked12);
85+
CollectionAssert.AreEqual(expected22.ToBytes(), masked22);
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)