-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathCryptoBytes.cs
More file actions
190 lines (174 loc) · 8.04 KB
/
CryptoBytes.cs
File metadata and controls
190 lines (174 loc) · 8.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using System;
using System.Runtime.CompilerServices;
namespace Bifrost.Security
{
public static class CryptoBytes
{
public static bool ConstantTimeEquals(byte[] x, byte[] y)
{
if (x == null)
throw new ArgumentNullException("x");
if (y == null)
throw new ArgumentNullException("y");
if (x.Length != y.Length)
throw new ArgumentException("x.Length must equal y.Length");
return InternalConstantTimeEquals(x, 0, y, 0, x.Length) != 0;
}
public static bool ConstantTimeEquals(ArraySegment<byte> x, ArraySegment<byte> y)
{
if (x.Array == null)
throw new ArgumentNullException("x.Array");
if (y.Array == null)
throw new ArgumentNullException("y.Array");
if (x.Count != y.Count)
throw new ArgumentException("x.Count must equal y.Count");
return InternalConstantTimeEquals(x.Array, x.Offset, y.Array, y.Offset, x.Count) != 0;
}
public static bool ConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length)
{
if (x == null)
throw new ArgumentNullException("x");
if (xOffset < 0)
throw new ArgumentOutOfRangeException("xOffset", "xOffset < 0");
if (y == null)
throw new ArgumentNullException("y");
if (yOffset < 0)
throw new ArgumentOutOfRangeException("yOffset", "yOffset < 0");
if (length < 0)
throw new ArgumentOutOfRangeException("length", "length < 0");
if (x.Length - xOffset < length)
throw new ArgumentException("xOffset + length > x.Length");
if (y.Length - yOffset < length)
throw new ArgumentException("yOffset + length > y.Length");
return InternalConstantTimeEquals(x, xOffset, y, yOffset, length) != 0;
}
private static uint InternalConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length)
{
int differentbits = 0;
for (int i = 0; i < length; i++)
differentbits |= x[xOffset + i] ^ y[yOffset + i];
return (1 & (unchecked((uint)differentbits - 1) >> 8));
}
public static void Wipe(byte[] data)
{
if (data == null)
throw new ArgumentNullException("data");
InternalWipe(data, 0, data.Length);
}
public static void Wipe(byte[] data, int offset, int count)
{
if (data == null)
throw new ArgumentNullException("data");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset");
if (count < 0)
throw new ArgumentOutOfRangeException("count", "Requires count >= 0");
if ((uint)offset + (uint)count > (uint)data.Length)
throw new ArgumentException("Requires offset + count <= data.Length");
InternalWipe(data, offset, count);
}
public static void Wipe(ArraySegment<byte> data)
{
if (data.Array == null)
throw new ArgumentNullException("data.Array");
InternalWipe(data.Array, data.Offset, data.Count);
}
// Secure wiping is hard
// * the GC can move around and copy memory
// Perhaps this can be avoided by using unmanaged memory or by fixing the position of the array in memory
// * Swap files and error dumps can contain secret information
// It seems possible to lock memory in RAM, no idea about error dumps
// * Compiler could optimize out the wiping if it knows that data won't be read back
// I hope this is enough, suppressing inlining
// but perhaps `RtlSecureZeroMemory` is needed
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void InternalWipe(byte[] data, int offset, int count)
{
Array.Clear(data, offset, count);
}
// shallow wipe of structs
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void InternalWipe<T>(ref T data)
where T : struct
{
data = default(T);
}
// constant time hex conversion
// see http://stackoverflow.com/a/14333437/445517
//
// An explanation of the weird bit fiddling:
//
// 1. `bytes[i] >> 4` extracts the high nibble of a byte
// `bytes[i] & 0xF` extracts the low nibble of a byte
// 2. `b - 10`
// is `< 0` for values `b < 10`, which will become a decimal digit
// is `>= 0` for values `b > 10`, which will become a letter from `A` to `F`.
// 3. Using `i >> 31` on a signed 32 bit integer extracts the sign, thanks to sign extension.
// It will be `-1` for `i < 0` and `0` for `i >= 0`.
// 4. Combining 2) and 3), shows that `(b-10)>>31` will be `0` for letters and `-1` for digits.
// 5. Looking at the case for letters, the last summand becomes `0`, and `b` is in the range 10 to 15. We want to map it to `A`(65) to `F`(70), which implies adding 55 (`'A'-10`).
// 6. Looking at the case for digits, we want to adapt the last summand so it maps `b` from the range 0 to 9 to the range `0`(48) to `9`(57). This means it needs to become -7 (`'0' - 55`).
// Now we could just multiply with 7. But since -1 is represented by all bits being 1, we can instead use `& -7` since `(0 & -7) == 0` and `(-1 & -7) == -7`.
//
// Some further considerations:
//
// * I didn't use a second loop variable to index into `c`, since measurement shows that calculating it from `i` is cheaper.
// * Using exactly `i < bytes.Length` as upper bound of the loop allows the JITter to eliminate bounds checks on `bytes[i]`, so I chose that variant.
// * Making `b` an int avoids unnecessary conversions from and to byte.
public static string ToHexStringUpper(byte[] data)
{
if (data == null)
return null;
char[] c = new char[data.Length * 2];
int b;
for (int i = 0; i < data.Length; i++)
{
b = data[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = data[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c);
}
// Explanation is similar to ToHexStringUpper
// constant 55 -> 87 and -7 -> -39 to compensate for the offset 32 between lowercase and uppercase letters
public static string ToHexStringLower(byte[] data)
{
if (data == null)
return null;
char[] c = new char[data.Length * 2];
int b;
for (int i = 0; i < data.Length; i++)
{
b = data[i] >> 4;
c[i * 2] = (char)(87 + b + (((b - 10) >> 31) & -39));
b = data[i] & 0xF;
c[i * 2 + 1] = (char)(87 + b + (((b - 10) >> 31) & -39));
}
return new string(c);
}
public static byte[] FromHexString(string hexString)
{
if (hexString == null)
return null;
if (hexString.Length % 2 != 0)
throw new FormatException("The hex string is invalid because it has an odd length");
var result = new byte[hexString.Length / 2];
for (int i = 0; i < result.Length; i++)
result[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
return result;
}
public static string ToBase64String(byte[] data)
{
if (data == null)
return null;
return Convert.ToBase64String(data);
}
public static byte[] FromBase64String(string s)
{
if (s == null)
return null;
return Convert.FromBase64String(s);
}
}
}