using System; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Diagnostics.Contracts; namespace Framework.Cryptography.Ed25519 { public static class CryptoBytes { /// /// 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. /// /// Byte array /// Byte array /// True if arrays are equal public static bool ConstantTimeEquals(byte[] x, byte[] y) { if (x.Length != y.Length) return false; return InternalConstantTimeEquals(x, 0, y, 0, x.Length) != 0; } /// /// 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. /// /// Byte array segment /// Byte array segment /// True if contents of x and y are equal public static bool ConstantTimeEquals(ArraySegment x, ArraySegment y) { if (x.Count != y.Count) return false; return InternalConstantTimeEquals(x.Array, x.Offset, y.Array, y.Offset, x.Count) != 0; } /// /// 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. /// /// Byte array /// Offset of byte sequence in the x array /// Byte array /// Offset of byte sequence in the y array /// Lengh of byte sequence /// True if sequences are equal 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)); } /// /// Overwrites the contents of the array, wiping the previous content. /// /// Byte array public static void Wipe(byte[] data) { InternalWipe(data, 0, data.Length); } /// /// Overwrites the contents of the array, wiping the previous content. /// /// Byte array /// Index of byte sequence /// Length of byte sequence public static void Wipe(byte[] data, int offset, int length) { InternalWipe(data, offset, length); } /// /// Overwrites the contents of the array segment, wiping the previous content. /// /// Byte array segment public static void Wipe(ArraySegment 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(ref T data) where T : struct { data = default(T); } /// /// 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 /// /// Byte array /// Hex representation of byte array 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); } /// /// 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. /// /// Byte array /// Hex representation of byte array 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); } /// /// Converts the hex string to bytes. Case insensitive. /// /// Hex encoded byte sequence /// Byte array 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; } /// /// Encodes the bytes with the Base64 encoding. /// More compact than hex, but it is case-sensitive and uses the special characters `+`, `/` and `=`. /// /// Byte array /// Base 64 encoded data public static string ToBase64String(byte[] data) { if (data == null) return null; return Convert.ToBase64String(data); } /// /// Decodes a Base64 encoded string back to bytes. /// /// Base 64 encoded data /// Byte array public static byte[] FromBase64String(string base64String) { if (base64String == null) return null; return Convert.FromBase64String(base64String); } private const string strDigits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; /// /// Encode a byte sequence as a base58-encoded string /// /// Byte sequence /// Encoding result public static string Base58Encode(byte[] input) { // Decode byte[] to BigInteger System.Numerics.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; } /// /// // Decode a base58-encoded string into byte array /// /// Base58 data string /// Byte array public static byte[] Base58Decode(string input) { // Decode Base58 string to BigInteger System.Numerics.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(true, true); var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray(); return result; } } }