<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />

OpenBsdBCrypt

public class OpenBsdBCrypt
Password hashing scheme BCrypt.
using Org.BouncyCastle.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Org.BouncyCastle.Crypto.Generators { public class OpenBsdBCrypt { private static readonly byte[] EncodingTable; private static readonly byte[] DecodingTable; private static readonly string DefaultVersion; private static readonly HashSet<string> AllowedVersions; static OpenBsdBCrypt() { EncodingTable = new byte[64] { 46, 47, 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, 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, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 }; DecodingTable = new byte[128]; DefaultVersion = "2y"; AllowedVersions = new HashSet<string>(); AllowedVersions.Add("2a"); AllowedVersions.Add("2y"); AllowedVersions.Add("2b"); Arrays.Fill(DecodingTable, byte.MaxValue); for (int i = 0; i < EncodingTable.Length; i++) { DecodingTable[EncodingTable[i]] = (byte)i; } } private static string CreateBcryptString(string version, byte[] password, byte[] salt, int cost) { if (!AllowedVersions.Contains(version)) throw new ArgumentException("Version " + version + " is not accepted by this implementation.", "version"); byte[] data = BCrypt.Generate(password, salt, cost); StringBuilder stringBuilder = new StringBuilder(60); stringBuilder.Append('$'); stringBuilder.Append(version); stringBuilder.Append('$'); stringBuilder.Append((cost < 10) ? ("0" + cost.ToString()) : cost.ToString()); stringBuilder.Append('$'); EncodeData(stringBuilder, salt); EncodeData(stringBuilder, data); return stringBuilder.ToString(); } public static string Generate(char[] password, byte[] salt, int cost) { return Generate(DefaultVersion, password, salt, cost); } public static string Generate(string version, char[] password, byte[] salt, int cost) { if (!AllowedVersions.Contains(version)) throw new ArgumentException("Version " + version + " is not accepted by this implementation.", "version"); if (password == null) throw new ArgumentNullException("password"); if (salt == null) throw new ArgumentNullException("salt"); if (salt.Length != 16) throw new DataLengthException("16 byte salt required: " + salt.Length.ToString()); if (cost < 4 || cost > 31) throw new ArgumentException("Invalid cost factor.", "cost"); byte[] array = Strings.ToUtf8ByteArray(password); int newLength = System.Math.Min(72, array.Length + 1); byte[] array2 = Arrays.CopyOf(array, newLength); Array.Clear(array, 0, array.Length); string result = CreateBcryptString(version, array2, salt, cost); Array.Clear(array2, 0, array2.Length); return result; } public static bool CheckPassword(string bcryptString, char[] password) { if (bcryptString.Length != 60) throw new DataLengthException("Bcrypt String length: " + bcryptString.Length.ToString() + ", 60 required."); if (bcryptString[0] != '$' || bcryptString[3] != '$' || bcryptString[6] != '$') throw new ArgumentException("Invalid Bcrypt String format.", "bcryptString"); string text = bcryptString.Substring(1, 2); if (!AllowedVersions.Contains(text)) throw new ArgumentException("Bcrypt version '" + text + "' is not supported by this implementation", "bcryptString"); if (!int.TryParse(bcryptString.AsSpan(4, 2), out int result)) throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString"); if (result < 4 || result > 31) throw new ArgumentException("Invalid cost factor: " + result.ToString() + ", 4 < cost < 31 expected."); if (password == null) throw new ArgumentNullException("Missing password.", "password"); int num = bcryptString.LastIndexOf('$') + 1; int num2 = bcryptString.Length - 31; byte[] salt = DecodeSaltString(bcryptString.Substring(num, num2 - num)); string value = Generate(text, password, salt, result); return bcryptString.Equals(value); } private static void EncodeData(StringBuilder sb, byte[] data) { if (data.Length != 24 && data.Length != 16) throw new DataLengthException("Invalid length: " + data.Length.ToString() + ", 24 for key or 16 for salt expected"); bool flag = false; if (data.Length == 16) { flag = true; data = Arrays.CopyOf(data, 18); } else data[data.Length - 1] = 0; MemoryStream memoryStream = new MemoryStream(); for (int i = 0; i < data.Length; i += 3) { uint num = data[i]; uint num2 = data[i + 1]; uint num3 = data[i + 2]; memoryStream.WriteByte(EncodingTable[num >> 2]); memoryStream.WriteByte(EncodingTable[((num << 4) | (num2 >> 4)) & 63]); memoryStream.WriteByte(EncodingTable[((num2 << 2) | (num3 >> 6)) & 63]); memoryStream.WriteByte(EncodingTable[num3 & 63]); } byte[] buffer = memoryStream.GetBuffer(); int num4 = Convert.ToInt32(memoryStream.Length); int len = flag ? 22 : (num4 - 1); Strings.AppendFromByteArray(sb, buffer, 0, len); } private unsafe static byte[] DecodeSaltString(string saltString) { if (saltString.Length != 22) throw new DataLengthException("Invalid base64 salt length: " + saltString.Length.ToString() + " , 22 required."); Span<char> destination = new Span<char>(stackalloc byte[48], 24); saltString.CopyTo(destination); for (int i = 0; i < 22; i++) { int num = Convert.ToInt32(destination[i]); if (num > 122 || num < 46 || (num > 57 && num < 65)) throw new ArgumentException("Salt string contains invalid character: " + num.ToString(), "saltString"); } MemoryStream memoryStream = new MemoryStream(16); for (int j = 0; j < 24; j += 4) { byte b = DecodingTable[destination[j]]; byte b2 = DecodingTable[destination[j + 1]]; byte b3 = DecodingTable[destination[j + 2]]; byte b4 = DecodingTable[destination[j + 3]]; memoryStream.WriteByte((byte)((b << 2) | (b2 >> 4))); memoryStream.WriteByte((byte)((b2 << 4) | (b3 >> 2))); memoryStream.WriteByte((byte)((b3 << 6) | b4)); } return Arrays.CopyOf(memoryStream.GetBuffer(), 16); } } }