/*
The MIT License (MIT)
Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt)
Copyright (c) 2013 Ryan D. Emerle (.Net port)
Copyright (c) 2016/2021 Chris McKee (.Net-core port / patches / new features)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
namespace BCrypt.Net
{
/// BCrypt implementation.
///
///
/// BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in
/// "A Future-
/// Adaptable Password Scheme" by Niels Provos and David Mazieres.
///
///
/// This password hashing system tries to thwart off-line password cracking using a
/// computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher.
/// The work factor of the algorithm is parameterised, so it can be increased as computers
/// get faster.
///
///
/// To hash a password using the defaults, call the (which will generate a random salt and hash at default cost), like this:
///
/// string pw_hash = BCrypt.HashPassword(plain_password);
///
/// To hash a password using SHA384 pre-hashing for increased entropy call
/// (which will generate a random salt and hash at default cost), like this:
///
/// string pw_hash = BCrypt.EnhancedHashPassword(plain_password);
///
/// To check whether a plaintext password matches one that has been hashed previously,
/// use the method:
/// (To validate an enhanced hash you can pass true as the last parameter of Verify or use )
///
///
/// if (BCrypt.Verify(candidate_password, stored_hash))
/// Console.WriteLine("It matches");
/// else
/// Console.WriteLine("It does not match");
///
///
/// The method takes an optional parameter (workFactor) that
/// determines the computational complexity of the hashing:
///
///
/// string strong_salt = BCrypt.GenerateSalt(10);
/// string stronger_salt = BCrypt.GenerateSalt(12);
///
///
/// The amount of work increases exponentially (2^workFactor), so each increment is twice
/// as much work. The default workFactor is 10, and the valid range is 4 to 31.
///
///
public sealed class BCrypt
{
// BCrypt parameters
///
/// Default Work Factor
///
private const int DefaultRounds = 11;
private const int BCryptSaltLen = 128 / 8; // 128 bits
private static readonly Encoding SafeUTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private const HashType DefaultEnhancedHashType = HashType.SHA384;
// Blowfish parameters
private const int BlowfishNumRounds = 16;
///
/// RandomNumberGenerator.Create calls RandomNumberGenerator.Create("System.Security.Cryptography.RandomNumberGenerator"), which will create an instance of RNGCryptoServiceProvider.
/// https://msdn.microsoft.com/en-us/library/42ks8fz1
///
private static readonly RandomNumberGenerator RngCsp = RandomNumberGenerator.Create(); // secure PRNG
#region Initial contents of key schedule
private static readonly uint[] POrig = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static readonly uint[] SOrig = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};
#endregion Initial contents of key schedule
// BCrypt IV: "OrpheanBeholderScryDoubt"
private static readonly uint[] BfCryptCiphertext = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
private static readonly char[] Base64Code = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
private static readonly int[] Index64 = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Fixed configuration
private const string EmptyString = "";
private const char DefaultHashVersion = 'a';
private const string Nul = "\0";
private const short MinRounds = 4;
private const short MaxRounds = 31;
// Expanded Blowfish key
private uint[] _p;
private uint[] _s;
///
/// Validate existing hash and password,
///
/// Current password / string
/// Current hash to validate password against
/// NEW password / string to be hashed
/// The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2^workFactor. Default is 11
/// By default this method will not accept a work factor lower
/// than the one set in the current hash and will set the new work-factor to match.
/// returned if the users hash and current pass doesn't validate
/// returned if the salt is invalid in any way
/// returned if the hash is invalid
/// returned if the user hash is null
/// New hash of new password
public static string ValidateAndReplacePassword(string currentKey, string currentHash, string newKey, int workFactor = DefaultRounds, bool forceWorkFactor = false) =>
ValidateAndReplacePassword(currentKey, currentHash, false, HashType.None, newKey, false, HashType.None, workFactor, forceWorkFactor);
///
/// Validate existing hash and password,
///
/// Current password / string
/// Current hash to validate password against
/// Set to true,the string will undergo SHA384 hashing to make
/// use of available entropy prior to bcrypt hashing
/// HashType used (default SHA384)
///
/// NEW password / string to be hashed
/// Set to true,the string will undergo SHA384 hashing to make
/// use of available entropy prior to bcrypt hashing
/// HashType to use (default SHA384)
/// The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2^workFactor. Default is 11
/// By default this method will not accept a work factor lower
/// than the one set in the current hash and will set the new work-factor to match.
/// returned if the users hash and current pass doesn't validate
/// returned if the salt is invalid in any way
/// returned if the hash is invalid
/// returned if the user hash is null
/// New hash of new password
public static string ValidateAndReplacePassword(string currentKey, string currentHash, bool currentKeyEnhancedEntropy, HashType oldHashType,
string newKey, bool newKeyEnhancedEntropy = false, HashType newHashType = DefaultEnhancedHashType, int workFactor = DefaultRounds, bool forceWorkFactor = false)
{
if (currentKey == null)
{
throw new ArgumentNullException(nameof(currentKey));
}
if (string.IsNullOrEmpty(currentHash))
{
throw new ArgumentException("Invalid Hash", nameof(currentHash));
}
if (Verify(currentKey, currentHash, currentKeyEnhancedEntropy, oldHashType))
{
// Determine the starting offset and validate the salt
int startingOffset;
if (currentHash[0] != '$' || currentHash[1] != '2')
{
throw new SaltParseException("Invalid bcrypt version");
}
else if (currentHash[2] == '$')
{
startingOffset = 3;
}
else
{
char minor = currentHash[2];
if (minor != 'a' && minor != 'b' && minor != 'x' && minor != 'y' || currentHash[3] != '$')
{
throw new SaltParseException("Invalid bcrypt revision");
}
startingOffset = 4;
}
// Extract number of rounds
if (currentHash[startingOffset + 2] > '$')
{
throw new SaltParseException("Missing work factor");
}
// Extract details from salt
int currentWorkFactor = Convert.ToInt16(currentHash.Substring(startingOffset, 2));
// Throw if log rounds are out of range on hash, deals with custom salts
if (workFactor < 1 || workFactor > 31)
{
throw new SaltParseException("Work factor out of range");
}
// Never downgrade workfactor (unless forced)
if (!forceWorkFactor && currentWorkFactor > workFactor)
{
workFactor = currentWorkFactor;
}
return HashPassword(newKey, GenerateSalt(workFactor), newKeyEnhancedEntropy, newHashType);
}
throw new BcryptAuthenticationException("Current credentials could not be authenticated");
}
///
/// Hash a string using the OpenBSD BCrypt scheme and a salt generated by .
///
/// Just an alias for HashPassword.
/// The string to hash.
/// The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2^workFactor. Default is 11
/// The hashed string.
/// Thrown when the salt could not be parsed.
[Obsolete("Replace with HashPassword, this method will be removed at a later date")]
public static string HashString(string inputKey, int workFactor = DefaultRounds) => HashPassword(inputKey, GenerateSalt(workFactor));
///
/// Hash a password using the OpenBSD BCrypt scheme and a salt generated by .
///
/// The password to hash.
/// The hashed password.
/// Thrown when the salt could not be parsed.
public static string HashPassword(string inputKey) => HashPassword(inputKey, GenerateSalt());
///
/// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
///
/// The password to hash.
/// The hashed password.
/// Thrown when the salt could not be parsed.
public static string EnhancedHashPassword(string inputKey) => HashPassword(inputKey, GenerateSalt(), true);
///
/// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
///
/// The password to hash.
///
/// The hashed password.
/// Thrown when the salt could not be parsed.
public static string EnhancedHashPassword(string inputKey, int workFactor) => HashPassword(inputKey, GenerateSalt(workFactor), true);
///
/// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
///
/// The password to hash.
///
/// Configurable hash type for enhanced entropy
/// The hashed password.
/// Thrown when the salt could not be parsed.
public static string EnhancedHashPassword(string inputKey, int workFactor, HashType hashType) => HashPassword(inputKey, GenerateSalt(workFactor), true, hashType);
///
/// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
///
/// The password to hash.
/// Defaults to 11
/// Configurable hash type for enhanced entropy
/// The hashed password.
/// Thrown when the salt could not be parsed.
public static string EnhancedHashPassword(string inputKey, HashType hashType, int workFactor = DefaultRounds) => HashPassword(inputKey, GenerateSalt(workFactor), true, hashType);
///
/// Hash a password using the OpenBSD BCrypt scheme and a salt generated by using the given .
///
/// The password to hash.
/// The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2^workFactor. Default is 11
/// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing
/// The hashed password.
/// Thrown when the salt could not be parsed.
public static string HashPassword(string inputKey, int workFactor, bool enhancedEntropy = false) => HashPassword(inputKey, GenerateSalt(workFactor), enhancedEntropy);
/// Hash a password using the OpenBSD BCrypt scheme.
/// Thrown when one or more arguments have unsupported or illegal values.
/// The password or string to hash.
/// the salt to hash with (best generated using ).
/// The hashed password
/// Thrown when the could not be parsed.
public static string HashPassword(string inputKey, string salt) => HashPassword(inputKey, salt, false);
/// Hash a password using the OpenBSD BCrypt scheme.
/// Thrown when one or more arguments have unsupported or illegal values.
/// The password or string to hash.
/// the salt to hash with (best generated using ).
/// Set to true,the string will undergo hashing (defaults to SHA384 then base64 encoding) to make use of available entropy prior to bcrypt hashing
/// Configurable hash type for enhanced entropy
/// The hashed password
/// Thrown when the is null.
/// Thrown when the could not be parsed.
public static string HashPassword(string inputKey, string salt, bool enhancedEntropy, HashType hashType = DefaultEnhancedHashType)
{
if (inputKey == null)
{
throw new ArgumentNullException(nameof(inputKey));
}
if (string.IsNullOrEmpty(salt))
{
throw new ArgumentException("Invalid salt: salt cannot be null or empty", nameof(salt));
}
if (enhancedEntropy && hashType == HashType.None)
{
throw new ArgumentException("Invalid HashType, You can't have an enhanced hash with type none. HashType.None is used for internal clarity only.", nameof(hashType));
}
// Determine the starting offset and validate the salt
int startingOffset;
char bcryptMinorRevision = (char)0;
if (salt[0] != '$' || salt[1] != '2')
{
throw new SaltParseException("Invalid salt version");
}
else if (salt[2] == '$')
{
startingOffset = 3;
}
else
{
bcryptMinorRevision = salt[2];
if (bcryptMinorRevision != 'a' && bcryptMinorRevision != 'b' && bcryptMinorRevision != 'x' && bcryptMinorRevision != 'y' || salt[3] != '$')
{
throw new SaltParseException("Invalid salt revision");
}
startingOffset = 4;
}
// Extract number of rounds
if (salt[startingOffset + 2] > '$')
{
throw new SaltParseException("Missing salt rounds");
}
// Extract details from salt
int workFactor = Convert.ToInt16(salt.Substring(startingOffset, 2));
// Throw if log rounds are out of range on hash, deals with custom salts
if (workFactor < 1 || workFactor > 31)
{
throw new SaltParseException("Salt rounds out of range");
}
string extractedSalt = salt.Substring(startingOffset + 3, 22);
byte[] inputBytes;
if (enhancedEntropy)
{
inputBytes = EnhancedHash(SafeUTF8.GetBytes(inputKey), bcryptMinorRevision, hashType);
}
else
{
inputBytes = SafeUTF8.GetBytes(inputKey + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
}
byte[] saltBytes = DecodeBase64(extractedSalt, BCryptSaltLen);
BCrypt bCrypt = new BCrypt();
byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, workFactor);
// Generate result string
var result = new StringBuilder(60);
result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$');
result.Append(EncodeBase64(saltBytes, saltBytes.Length));
result.Append(EncodeBase64(hashed, (BfCryptCiphertext.Length * 4) - 1));
return result.ToString();
}
///
/// Hashes key, base64 encodes before returning byte array
///
///
///
///
///
private static byte[] EnhancedHash(byte[] inputBytes, char bcryptMinorRevision, HashType hashType)
{
switch (hashType)
{
case HashType.SHA256:
using (var sha = SHA256.Create())
inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
break;
case HashType.SHA384:
using (var sha = SHA384.Create())
inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
break;
case HashType.SHA512:
using (var sha = SHA512.Create())
inputBytes = SafeUTF8.GetBytes(Convert.ToBase64String(sha.ComputeHash(inputBytes)) + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
break;
default:
throw new ArgumentOutOfRangeException(nameof(hashType), hashType, null);
}
return inputBytes;
}
///
/// Generate a salt for use with the method.
///
/// The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2**workFactor.
///
/// Work factor must be between 4 and 31
/// A base64 encoded salt value.
/// BCrypt Revision should be a, b, x or y
public static string GenerateSalt(int workFactor, char bcryptMinorRevision = DefaultHashVersion)
{
if (workFactor < MinRounds || workFactor > MaxRounds)
{
throw new ArgumentOutOfRangeException(nameof(workFactor), workFactor, $"The work factor must be between {MinRounds} and {MaxRounds} (inclusive)");
}
if (bcryptMinorRevision != 'a' && bcryptMinorRevision != 'b' && bcryptMinorRevision != 'x' && bcryptMinorRevision != 'y')
{
throw new ArgumentException("BCrypt Revision should be a, b, x or y", nameof(bcryptMinorRevision));
}
byte[] saltBytes = new byte[BCryptSaltLen];
RngCsp.GetBytes(saltBytes);
var result = new StringBuilder(29);
result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$');
result.Append(EncodeBase64(saltBytes, saltBytes.Length));
return result.ToString();
}
///
/// Based on password_needs_rehash in PHP this method will return true
/// if the work factor (logrounds) set on the hash is lower than the new minimum workload passed in
///
/// full bcrypt hash
/// target workload
/// true if new work factor is higher than the one in the hash
/// throws if the current hash workload (logrounds) can not be parsed
///
public static bool PasswordNeedsRehash(string hash, int newMinimumWorkLoad)
{
int currentWorkLoad = HashParser.GetWorkFactor(hash);
return currentWorkLoad < newMinimumWorkLoad;
}
///
/// Takes a valid hash and outputs its component parts
///
///
///
public static HashInformation InterrogateHash(string hash)
{
try
{
return HashParser.GetHashInformation(hash);
}
catch (Exception ex)
{
throw new HashInformationException("Error handling string interrogation", ex);
}
}
///
/// Generate a salt for use with the method
/// selecting a reasonable default for the number of hashing rounds to apply.
///
/// A base64 encoded salt value.
public static string GenerateSalt()
{
return GenerateSalt(DefaultRounds);
}
///
/// Verifies that the hash of the given matches the provided
/// ; the string will undergo SHA384 hashing to maintain the enhanced entropy work done during hashing
///
/// The text to verify.
/// The previously-hashed password.
/// HashType used (default SHA384)
/// true if the passwords match, false otherwise.
public static bool EnhancedVerify(string text, string hash, HashType hashType = DefaultEnhancedHashType) => Verify(text, hash, true, hashType);
///
/// Verifies that the hash of the given matches the provided
///
///
/// The text to verify.
/// The previously-hashed password.
/// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing
/// HashType used (default SHA384)
/// true if the passwords match, false otherwise.
/// Thrown when one or more arguments have unsupported or illegal values.
/// Thrown when the salt could not be parsed.
public static bool Verify(string text, string hash, bool enhancedEntropy = false, HashType hashType = DefaultEnhancedHashType)
{
return SecureEquals(SafeUTF8.GetBytes(hash), SafeUTF8.GetBytes(HashPassword(text, hash, enhancedEntropy, hashType)));
}
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimised.
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static bool SecureEquals(byte[] a, byte[] b)
{
if (a == null && b == null)
{
return true;
}
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
int diff = 0;
for (var i = 0; i < a.Length; i++)
{
diff |= (a[i] ^ b[i]);
}
return diff == 0;
}
///
/// Encode a byte array using BCrypt's slightly-modified base64 encoding scheme. Note that this
/// is *not* compatible with the standard MIME-base64 encoding.
///
/// Thrown when one or more arguments have unsupported or
/// illegal values.
/// The byte array to encode.
/// The number of bytes to encode.
/// Base64-encoded string.
internal static char[] EncodeBase64(byte[] byteArray, int length)
{
if (length <= 0 || length > byteArray.Length)
{
throw new ArgumentException("Invalid length", nameof(length));
}
int encodedSize = (int)Math.Ceiling((length * 4D) / 3);
char[] encoded = new char[encodedSize];
int pos = 0;
int off = 0;
while (off < length)
{
int c1 = byteArray[off++] & 0xff;
encoded[pos++] = Base64Code[(c1 >> 2) & 0x3f];
c1 = (c1 & 0x03) << 4;
if (off >= length)
{
encoded[pos++] = Base64Code[c1 & 0x3f];
break;
}
int c2 = byteArray[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
encoded[pos++] = Base64Code[c1 & 0x3f];
c1 = (c2 & 0x0f) << 2;
if (off >= length)
{
encoded[pos++] = Base64Code[c1 & 0x3f];
break;
}
c2 = byteArray[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
encoded[pos++] = Base64Code[c1 & 0x3f];
encoded[pos++] = Base64Code[c2 & 0x3f];
}
return encoded;
}
///
/// Decode a string encoded using BCrypt's base64 scheme to a byte array.
/// Note that this is *not* compatible with the standard MIME-base64 encoding.
///
/// Thrown when one or more arguments have unsupported or
/// illegal values.
/// The string to decode.
/// The maximum bytes to decode.
/// The decoded byte array.
internal static byte[] DecodeBase64(string encodedString, int maximumBytes)
{
int sourceLength = encodedString.Length;
int outputLength = 0;
if (maximumBytes <= 0)
{
throw new ArgumentException("Invalid maximum bytes value", nameof(maximumBytes));
}
byte[] result = new byte[maximumBytes];
int position = 0;
while (position < sourceLength - 1 && outputLength < maximumBytes)
{
int c1 = Char64(encodedString[position++]);
int c2 = Char64(encodedString[position++]);
if (c1 == -1 || c2 == -1)
{
break;
}
result[outputLength] = (byte)((c1 << 2) | ((c2 & 0x30) >> 4));
if (++outputLength >= maximumBytes || position >= sourceLength)
{
break;
}
int c3 = Char64(encodedString[position++]);
if (c3 == -1)
{
break;
}
result[outputLength] = (byte)(((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2));
if (++outputLength >= maximumBytes || position >= sourceLength)
{
break;
}
int c4 = Char64(encodedString[position++]);
result[outputLength] = (byte)(((c3 & 0x03) << 6) | c4);
++outputLength;
}
return result;
}
///
/// Look up the 3 bits base64-encoded by the specified character, range-checking against
/// conversion table.
///
/// The base64-encoded value.
/// The decoded value of x.
private static int Char64(char character)
{
return character < 0 || character > Index64.Length ? -1 : Index64[character];
}
/// Blowfish encipher a single 64-bit block encoded as two 32-bit halves.
/// An array containing the two 32-bit half blocks.
/// The position in the array of the blocks.
#if HAS_SPAN
private void Encipher(Span blockArray, int offset)
#else
private void Encipher(uint[] blockArray, int offset)
#endif
{
uint block = blockArray[offset];
uint r = blockArray[offset + 1];
block ^= _p[0];
unchecked
{
uint round;
for (round = 0; round <= BlowfishNumRounds - 2;)
{
// Feistel substitution on left word
uint n = _s[(block >> 24) & 0xff];
n += _s[0x100 | ((block >> 16) & 0xff)];
n ^= _s[0x200 | ((block >> 8) & 0xff)];
n += _s[0x300 | (block & 0xff)];
r ^= n ^ _p[++round];
// Feistel substitution on right word
n = _s[(r >> 24) & 0xff];
n += _s[0x100 | ((r >> 16) & 0xff)];
n ^= _s[0x200 | ((r >> 8) & 0xff)];
n += _s[0x300 | (r & 0xff)];
block ^= n ^ _p[++round];
}
blockArray[offset] = r ^ _p[BlowfishNumRounds + 1];
blockArray[offset + 1] = block;
}
}
/// Cyclically extract a word of key material.
/// The string to extract the data from.
/// [in,out] The current offset.
/// The next word of material from data.
#if HAS_SPAN
private static uint StreamToWord(ReadOnlySpan data, ref int offset)
#else
private static uint StreamToWord(byte[] data, ref int offset)
#endif
{
int i;
uint word = 0;
for (i = 0; i < 4; i++)
{
word = (word << 8) | (uint)(data[offset] & 0xff);
offset = (offset + 1) % data.Length;
}
return word;
}
/// Initializes the Blowfish key schedule.
private void InitializeKey()
{
_p = new uint[POrig.Length];
_s = new uint[SOrig.Length];
Array.Copy(POrig, _p, POrig.Length);
Array.Copy(SOrig, _s, SOrig.Length);
}
/// Key the Blowfish cipher.
/// The key byte array.
#if HAS_SPAN
private void Key(ReadOnlySpan keyBytes)
#else
private void Key(byte[] keyBytes)
#endif
{
int i;
int koffp = 0;
#if HAS_SPAN
Span lr = stackalloc uint[2] { 0, 0 };
#else
uint[] lr = { 0, 0 };
#endif
int plen = _p.Length, slen = _s.Length;
for (i = 0; i < plen; i++)
{
_p[i] = _p[i] ^ StreamToWord(keyBytes, ref koffp);
}
for (i = 0; i < plen; i += 2)
{
Encipher(lr, 0);
_p[i] = lr[0];
_p[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2)
{
Encipher(lr, 0);
_s[i] = lr[0];
_s[i + 1] = lr[1];
}
}
///
/// Perform the "enhanced key schedule" step described by Provos and Mazieres in
/// "A Future Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps.
///
/// Salt byte array.
/// Input byte array.
// ReSharper disable once InconsistentNaming
#if HAS_SPAN
private void EKSKey(ReadOnlySpan saltBytes, ReadOnlySpan inputBytes)
#else
private void EKSKey(byte[] saltBytes, byte[] inputBytes)
#endif
{
int i;
int passwordOffset = 0;
int saltOffset = 0;
#if HAS_SPAN
Span lr = stackalloc uint[2] { 0, 0 };
#else
uint[] lr = { 0, 0 };
#endif
int plen = _p.Length, slen = _s.Length;
for (i = 0; i < plen; i++)
{
_p[i] = _p[i] ^ StreamToWord(inputBytes, ref passwordOffset);
}
for (i = 0; i < plen; i += 2)
{
lr[0] ^= StreamToWord(saltBytes, ref saltOffset);
lr[1] ^= StreamToWord(saltBytes, ref saltOffset);
Encipher(lr, 0);
_p[i] = lr[0];
_p[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2)
{
lr[0] ^= StreamToWord(saltBytes, ref saltOffset);
lr[1] ^= StreamToWord(saltBytes, ref saltOffset);
Encipher(lr, 0);
_s[i] = lr[0];
_s[i + 1] = lr[1];
}
}
/// Perform the central hashing step in the BCrypt scheme.
/// Thrown when one or more arguments have unsupported or
/// illegal values.
/// The input byte array to hash.
/// The salt byte array to hash with.
/// The binary logarithm of the number of rounds of hashing to apply.
/// A byte array containing the hashed result.
#if HAS_SPAN
internal byte[] CryptRaw(ReadOnlySpan inputBytes, ReadOnlySpan saltBytes, int workFactor)
#else
internal byte[] CryptRaw(byte[] inputBytes, byte[] saltBytes, int workFactor)
#endif
{
int i;
int j;
#if HAS_SPAN
Span cdata = stackalloc uint[BfCryptCiphertext.Length];
BfCryptCiphertext.CopyTo(cdata);
#else
uint[] cdata = new uint[BfCryptCiphertext.Length];
Array.Copy(BfCryptCiphertext, cdata, BfCryptCiphertext.Length);
#endif
int clen = cdata.Length;
if (workFactor < MinRounds || workFactor > MaxRounds)
{
throw new ArgumentException("Bad number of rounds", nameof(workFactor));
}
if (saltBytes.Length != BCryptSaltLen)
{
throw new ArgumentException("Bad salt Length", nameof(saltBytes));
}
uint rounds = 1u << workFactor;
// We overflowed rounds at 31 - added safety check
if (rounds < 1)
{
throw new ArgumentException("Bad number of rounds", nameof(workFactor));
}
InitializeKey();
EKSKey(saltBytes, inputBytes);
for (i = 0; i != rounds; i++)
{
Key(inputBytes);
Key(saltBytes);
}
for (i = 0; i < 64; i++)
{
for (j = 0; j < (clen >> 1); j++)
{
Encipher(cdata, j << 1);
}
}
byte[] ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++)
{
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
return ret;
}
}
}