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);
}
}
}