You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
310 lines
12 KiB
310 lines
12 KiB
11 months ago
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||
|
#pragma warning disable
|
||
|
using System;
|
||
|
using System.IO;
|
||
|
using System.Text;
|
||
|
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Collections;
|
||
|
|
||
|
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Generators
|
||
|
{
|
||
|
/**
|
||
|
* Password hashing scheme BCrypt,
|
||
|
* designed by Niels Provos and David Mazières, using the
|
||
|
* String format and the Base64 encoding
|
||
|
* of the reference implementation on OpenBSD
|
||
|
*/
|
||
|
public class OpenBsdBCrypt
|
||
|
{
|
||
|
private static readonly byte[] EncodingTable = // the Bcrypts encoding table for OpenBSD
|
||
|
{
|
||
|
(byte)'.', (byte)'/', (byte)'A', (byte)'B', (byte)'C', (byte)'D',
|
||
|
(byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J',
|
||
|
(byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P',
|
||
|
(byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V',
|
||
|
(byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b',
|
||
|
(byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h',
|
||
|
(byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
|
||
|
(byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t',
|
||
|
(byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
|
||
|
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
|
||
|
(byte)'6', (byte)'7', (byte)'8', (byte)'9'
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* set up the decoding table.
|
||
|
*/
|
||
|
private static readonly byte[] DecodingTable = new byte[128];
|
||
|
private static readonly string DefaultVersion = "2y";
|
||
|
private static readonly ISet AllowedVersions = new HashSet();
|
||
|
|
||
|
static OpenBsdBCrypt()
|
||
|
{
|
||
|
// Presently just the Bcrypt versions.
|
||
|
AllowedVersions.Add("2a");
|
||
|
AllowedVersions.Add("2y");
|
||
|
AllowedVersions.Add("2b");
|
||
|
|
||
|
for (int i = 0; i < DecodingTable.Length; i++)
|
||
|
{
|
||
|
DecodingTable[i] = (byte)0xff;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < EncodingTable.Length; i++)
|
||
|
{
|
||
|
DecodingTable[EncodingTable[i]] = (byte)i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public OpenBsdBCrypt()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a 60 character Bcrypt String, including
|
||
|
* version, cost factor, salt and hash, separated by '$'
|
||
|
*
|
||
|
* @param version the version, 2y,2b or 2a. (2a is not backwards compatible.)
|
||
|
* @param cost the cost factor, treated as an exponent of 2
|
||
|
* @param salt a 16 byte salt
|
||
|
* @param password the password
|
||
|
* @return a 60 character Bcrypt String
|
||
|
*/
|
||
|
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");
|
||
|
|
||
|
StringBuilder sb = new StringBuilder(60);
|
||
|
sb.Append('$');
|
||
|
sb.Append(version);
|
||
|
sb.Append('$');
|
||
|
sb.Append(cost < 10 ? ("0" + cost) : cost.ToString());
|
||
|
sb.Append('$');
|
||
|
sb.Append(EncodeData(salt));
|
||
|
|
||
|
byte[] key = BCrypt.Generate(password, salt, cost);
|
||
|
|
||
|
sb.Append(EncodeData(key));
|
||
|
|
||
|
return sb.ToString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a 60 character Bcrypt String, including
|
||
|
* version, cost factor, salt and hash, separated by '$' using version
|
||
|
* '2y'.
|
||
|
*
|
||
|
* @param cost the cost factor, treated as an exponent of 2
|
||
|
* @param salt a 16 byte salt
|
||
|
* @param password the password
|
||
|
* @return a 60 character Bcrypt String
|
||
|
*/
|
||
|
public static string Generate(char[] password, byte[] salt, int cost)
|
||
|
{
|
||
|
return Generate(DefaultVersion, password, salt, cost);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a 60 character Bcrypt String, including
|
||
|
* version, cost factor, salt and hash, separated by '$'
|
||
|
*
|
||
|
* @param version the version, may be 2b, 2y or 2a. (2a is not backwards compatible.)
|
||
|
* @param cost the cost factor, treated as an exponent of 2
|
||
|
* @param salt a 16 byte salt
|
||
|
* @param password the password
|
||
|
* @return a 60 character Bcrypt String
|
||
|
*/
|
||
|
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);
|
||
|
|
||
|
if (cost < 4 || cost > 31) // Minimum rounds: 16, maximum 2^31
|
||
|
throw new ArgumentException("Invalid cost factor.", "cost");
|
||
|
|
||
|
byte[] psw = Strings.ToUtf8ByteArray(password);
|
||
|
|
||
|
// 0 termination:
|
||
|
|
||
|
byte[] tmp = new byte[psw.Length >= 72 ? 72 : psw.Length + 1];
|
||
|
int copyLen = System.Math.Min(psw.Length, tmp.Length);
|
||
|
Array.Copy(psw, 0, tmp, 0, copyLen);
|
||
|
|
||
|
Array.Clear(psw, 0, psw.Length);
|
||
|
|
||
|
string rv = CreateBcryptString(version, tmp, salt, cost);
|
||
|
|
||
|
Array.Clear(tmp, 0, tmp.Length);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if a password corresponds to a 60 character Bcrypt String
|
||
|
*
|
||
|
* @param bcryptString a 60 character Bcrypt String, including
|
||
|
* version, cost factor, salt and hash,
|
||
|
* separated by '$'
|
||
|
* @param password the password as an array of chars
|
||
|
* @return true if the password corresponds to the
|
||
|
* Bcrypt String, otherwise false
|
||
|
*/
|
||
|
public static bool CheckPassword(string bcryptString, char[] password)
|
||
|
{
|
||
|
// validate bcryptString:
|
||
|
if (bcryptString.Length != 60)
|
||
|
throw new DataLengthException("Bcrypt String length: " + bcryptString.Length + ", 60 required.");
|
||
|
if (bcryptString[0] != '$' || bcryptString[3] != '$' || bcryptString[6] != '$')
|
||
|
throw new ArgumentException("Invalid Bcrypt String format.", "bcryptString");
|
||
|
|
||
|
string version = bcryptString.Substring(1, 2);
|
||
|
if (!AllowedVersions.Contains(version))
|
||
|
throw new ArgumentException("Bcrypt version '" + version + "' is not supported by this implementation", "bcryptString");
|
||
|
|
||
|
int cost = 0;
|
||
|
try
|
||
|
{
|
||
|
cost = Int32.Parse(bcryptString.Substring(4, 2));
|
||
|
}
|
||
|
catch (Exception nfe)
|
||
|
{
|
||
|
#if PORTABLE || NETFX_CORE
|
||
|
throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString");
|
||
|
#else
|
||
|
throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString", nfe);
|
||
|
#endif
|
||
|
}
|
||
|
if (cost < 4 || cost > 31)
|
||
|
throw new ArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected.");
|
||
|
|
||
|
// check password:
|
||
|
if (password == null)
|
||
|
throw new ArgumentNullException("Missing password.");
|
||
|
|
||
|
int start = bcryptString.LastIndexOf('$') + 1, end = bcryptString.Length - 31;
|
||
|
byte[] salt = DecodeSaltString(bcryptString.Substring(start, end - start));
|
||
|
|
||
|
string newBcryptString = Generate(version, password, salt, cost);
|
||
|
|
||
|
return bcryptString.Equals(newBcryptString);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* encode the input data producing a Bcrypt base 64 string.
|
||
|
*
|
||
|
* @param a byte representation of the salt or the password
|
||
|
* @return the Bcrypt base64 string
|
||
|
*/
|
||
|
private static string EncodeData(byte[] data)
|
||
|
{
|
||
|
if (data.Length != 24 && data.Length != 16) // 192 bit key or 128 bit salt expected
|
||
|
throw new DataLengthException("Invalid length: " + data.Length + ", 24 for key or 16 for salt expected");
|
||
|
|
||
|
bool salt = false;
|
||
|
if (data.Length == 16)//salt
|
||
|
{
|
||
|
salt = true;
|
||
|
byte[] tmp = new byte[18];// zero padding
|
||
|
Array.Copy(data, 0, tmp, 0, data.Length);
|
||
|
data = tmp;
|
||
|
}
|
||
|
else // key
|
||
|
{
|
||
|
data[data.Length - 1] = (byte)0;
|
||
|
}
|
||
|
|
||
|
MemoryStream mOut = new MemoryStream();
|
||
|
int len = data.Length;
|
||
|
|
||
|
uint a1, a2, a3;
|
||
|
int i;
|
||
|
for (i = 0; i < len; i += 3)
|
||
|
{
|
||
|
a1 = data[i];
|
||
|
a2 = data[i + 1];
|
||
|
a3 = data[i + 2];
|
||
|
|
||
|
mOut.WriteByte(EncodingTable[(a1 >> 2) & 0x3f]);
|
||
|
mOut.WriteByte(EncodingTable[((a1 << 4) | (a2 >> 4)) & 0x3f]);
|
||
|
mOut.WriteByte(EncodingTable[((a2 << 2) | (a3 >> 6)) & 0x3f]);
|
||
|
mOut.WriteByte(EncodingTable[a3 & 0x3f]);
|
||
|
}
|
||
|
|
||
|
string result = Strings.FromByteArray(mOut.ToArray());
|
||
|
int resultLen = salt
|
||
|
? 22 // truncate padding
|
||
|
: result.Length - 1;
|
||
|
|
||
|
return result.Substring(0, resultLen);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* decodes the bcrypt base 64 encoded SaltString
|
||
|
*
|
||
|
* @param a 22 character Bcrypt base 64 encoded String
|
||
|
* @return the 16 byte salt
|
||
|
* @exception DataLengthException if the length
|
||
|
* of parameter is not 22
|
||
|
* @exception InvalidArgumentException if the parameter
|
||
|
* contains a value other than from Bcrypts base 64 encoding table
|
||
|
*/
|
||
|
private static byte[] DecodeSaltString(string saltString)
|
||
|
{
|
||
|
char[] saltChars = saltString.ToCharArray();
|
||
|
|
||
|
MemoryStream mOut = new MemoryStream(16);
|
||
|
byte b1, b2, b3, b4;
|
||
|
|
||
|
if (saltChars.Length != 22)// bcrypt salt must be 22 (16 bytes)
|
||
|
throw new DataLengthException("Invalid base64 salt length: " + saltChars.Length + " , 22 required.");
|
||
|
|
||
|
// check string for invalid characters:
|
||
|
for (int i = 0; i < saltChars.Length; i++)
|
||
|
{
|
||
|
int value = saltChars[i];
|
||
|
if (value > 122 || value < 46 || (value > 57 && value < 65))
|
||
|
throw new ArgumentException("Salt string contains invalid character: " + value, "saltString");
|
||
|
}
|
||
|
|
||
|
// Padding: add two '\u0000'
|
||
|
char[] tmp = new char[22 + 2];
|
||
|
Array.Copy(saltChars, 0, tmp, 0, saltChars.Length);
|
||
|
saltChars = tmp;
|
||
|
|
||
|
int len = saltChars.Length;
|
||
|
|
||
|
for (int i = 0; i < len; i += 4)
|
||
|
{
|
||
|
b1 = DecodingTable[saltChars[i]];
|
||
|
b2 = DecodingTable[saltChars[i + 1]];
|
||
|
b3 = DecodingTable[saltChars[i + 2]];
|
||
|
b4 = DecodingTable[saltChars[i + 3]];
|
||
|
|
||
|
mOut.WriteByte((byte)((b1 << 2) | (b2 >> 4)));
|
||
|
mOut.WriteByte((byte)((b2 << 4) | (b3 >> 2)));
|
||
|
mOut.WriteByte((byte)((b3 << 6) | b4));
|
||
|
}
|
||
|
|
||
|
byte[] saltBytes = mOut.ToArray();
|
||
|
|
||
|
// truncate:
|
||
|
byte[] tmpSalt = new byte[16];
|
||
|
Array.Copy(saltBytes, 0, tmpSalt, 0, tmpSalt.Length);
|
||
|
saltBytes = tmpSalt;
|
||
|
|
||
|
return saltBytes;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#pragma warning restore
|
||
|
#endif
|