|
- using System;
- using System.Linq;
- using System.Numerics;
- using System.Runtime.CompilerServices;
-
- namespace Discord.Net.ED25519
- {
- internal class CryptoBytes
- {
- /// <summary>
- /// Comparison of two arrays.
- ///
- /// The runtime of this method does not depend on the contents of the arrays. Using constant time
- /// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix.
- ///
- /// It is important to use such a constant time comparison when verifying MACs.
- /// </summary>
- /// <param name="x">Byte array</param>
- /// <param name="y">Byte array</param>
- /// <returns>True if arrays are equal</returns>
- public static bool ConstantTimeEquals(byte[] x, byte[] y)
- {
- if (x.Length != y.Length)
- return false;
- return InternalConstantTimeEquals(x, 0, y, 0, x.Length) != 0;
- }
-
- /// <summary>
- /// Comparison of two array segments.
- ///
- /// The runtime of this method does not depend on the contents of the arrays. Using constant time
- /// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix.
- ///
- /// It is important to use such a constant time comparison when verifying MACs.
- /// </summary>
- /// <param name="x">Byte array segment</param>
- /// <param name="y">Byte array segment</param>
- /// <returns>True if contents of x and y are equal</returns>
- public static bool ConstantTimeEquals(ArraySegment<byte> x, ArraySegment<byte> y)
- {
- if (x.Count != y.Count)
- return false;
- return InternalConstantTimeEquals(x.Array, x.Offset, y.Array, y.Offset, x.Count) != 0;
- }
-
- /// <summary>
- /// Comparison of two byte sequences.
- ///
- /// The runtime of this method does not depend on the contents of the arrays. Using constant time
- /// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix.
- ///
- /// It is important to use such a constant time comparison when verifying MACs.
- /// </summary>
- /// <param name="x">Byte array</param>
- /// <param name="xOffset">Offset of byte sequence in the x array</param>
- /// <param name="y">Byte array</param>
- /// <param name="yOffset">Offset of byte sequence in the y array</param>
- /// <param name="length">Lengh of byte sequence</param>
- /// <returns>True if sequences are equal</returns>
- public static bool ConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int 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));
- }
-
- /// <summary>
- /// Overwrites the contents of the array, wiping the previous content.
- /// </summary>
- /// <param name="data">Byte array</param>
- public static void Wipe(byte[] data)
- {
- InternalWipe(data, 0, data.Length);
- }
-
- /// <summary>
- /// Overwrites the contents of the array, wiping the previous content.
- /// </summary>
- /// <param name="data">Byte array</param>
- /// <param name="offset">Index of byte sequence</param>
- /// <param name="length">Length of byte sequence</param>
- public static void Wipe(byte[] data, int offset, int length)
- {
- InternalWipe(data, offset, length);
- }
-
- /// <summary>
- /// Overwrites the contents of the array segment, wiping the previous content.
- /// </summary>
- /// <param name="data">Byte array segment</param>
- public static void Wipe(ArraySegment<byte> data)
- {
- 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);
- }
-
- /// <summary>
- /// Constant-time conversion of the bytes array to an upper-case hex string.
- /// Please see http://stackoverflow.com/a/14333437/445517 for the detailed explanation
- /// </summary>
- /// <param name="data">Byte array</param>
- /// <returns>Hex representation of byte array</returns>
- 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);
- }
-
- /// <summary>
- /// Constant-time conversion of the bytes array to an lower-case hex string.
- /// Please see http://stackoverflow.com/a/14333437/445517 for the detailed explanation.
- /// </summary>
- /// <param name="data">Byte array</param>
- /// <returns>Hex representation of byte array</returns>
- 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);
- }
-
- /// <summary>
- /// Converts the hex string to bytes. Case insensitive.
- /// </summary>
- /// <param name="hexString">Hex encoded byte sequence</param>
- /// <returns>Byte array</returns>
- 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;
- }
-
- /// <summary>
- /// Encodes the bytes with the Base64 encoding.
- /// More compact than hex, but it is case-sensitive and uses the special characters `+`, `/` and `=`.
- /// </summary>
- /// <param name="data">Byte array</param>
- /// <returns>Base 64 encoded data</returns>
- public static string ToBase64String(byte[] data)
- {
- if (data == null)
- return null;
- return Convert.ToBase64String(data);
- }
-
- /// <summary>
- /// Decodes a Base64 encoded string back to bytes.
- /// </summary>
- /// <param name="base64String">Base 64 encoded data</param>
- /// <returns>Byte array</returns>
- public static byte[] FromBase64String(string base64String)
- {
- if (base64String == null)
- return null;
- return Convert.FromBase64String(base64String);
- }
-
- private const string strDigits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
-
- /// <summary>
- /// Encode a byte sequence as a base58-encoded string
- /// </summary>
- /// <param name="input">Byte sequence</param>
- /// <returns>Encoding result</returns>
- public static string Base58Encode(byte[] input)
- {
- // Decode byte[] to BigInteger
- BigInteger intData = 0;
- for (int i = 0; i < input.Length; i++)
- {
- intData = intData * 256 + input[i];
- }
-
- // Encode BigInteger to Base58 string
- string result = "";
- while (intData > 0)
- {
- int remainder = (int)(intData % 58);
- intData /= 58;
- result = strDigits[remainder] + result;
- }
-
- // Append `1` for each leading 0 byte
- for (int i = 0; i < input.Length && input[i] == 0; i++)
- {
- result = '1' + result;
- }
- return result;
- }
-
- /// <summary>
- /// // Decode a base58-encoded string into byte array
- /// </summary>
- /// <param name="strBase58">Base58 data string</param>
- /// <returns>Byte array</returns>
- public static byte[] Base58Decode(string input)
- {
- // Decode Base58 string to BigInteger
- BigInteger intData = 0;
- for (int i = 0; i < input.Length; i++)
- {
- int digit = strDigits.IndexOf(input[i]); //Slow
- if (digit < 0)
- throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", input[i], i));
- intData = intData * 58 + digit;
- }
-
- // Encode BigInteger to byte[]
- // Leading zero bytes get encoded as leading `1` characters
- int leadingZeroCount = input.TakeWhile(c => c == '1').Count();
- var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount);
- var bytesWithoutLeadingZeros =
- intData.ToByteArray()
- .Reverse()// to big endian
- .SkipWhile(b => b == 0);//strip sign byte
- var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray();
- return result;
- }
- }
- }
|