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