OpenBsdBCrypt.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.IO;
  5. using System.Text;
  6. using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  7. using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Collections;
  8. namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Generators
  9. {
  10. /**
  11. * Password hashing scheme BCrypt,
  12. * designed by Niels Provos and David Mazières, using the
  13. * String format and the Base64 encoding
  14. * of the reference implementation on OpenBSD
  15. */
  16. public class OpenBsdBCrypt
  17. {
  18. private static readonly byte[] EncodingTable = // the Bcrypts encoding table for OpenBSD
  19. {
  20. (byte)'.', (byte)'/', (byte)'A', (byte)'B', (byte)'C', (byte)'D',
  21. (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J',
  22. (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P',
  23. (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V',
  24. (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b',
  25. (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h',
  26. (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
  27. (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t',
  28. (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
  29. (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
  30. (byte)'6', (byte)'7', (byte)'8', (byte)'9'
  31. };
  32. /*
  33. * set up the decoding table.
  34. */
  35. private static readonly byte[] DecodingTable = new byte[128];
  36. private static readonly string DefaultVersion = "2y";
  37. private static readonly ISet AllowedVersions = new HashSet();
  38. static OpenBsdBCrypt()
  39. {
  40. // Presently just the Bcrypt versions.
  41. AllowedVersions.Add("2a");
  42. AllowedVersions.Add("2y");
  43. AllowedVersions.Add("2b");
  44. for (int i = 0; i < DecodingTable.Length; i++)
  45. {
  46. DecodingTable[i] = (byte)0xff;
  47. }
  48. for (int i = 0; i < EncodingTable.Length; i++)
  49. {
  50. DecodingTable[EncodingTable[i]] = (byte)i;
  51. }
  52. }
  53. public OpenBsdBCrypt()
  54. {
  55. }
  56. /**
  57. * Creates a 60 character Bcrypt String, including
  58. * version, cost factor, salt and hash, separated by '$'
  59. *
  60. * @param version the version, 2y,2b or 2a. (2a is not backwards compatible.)
  61. * @param cost the cost factor, treated as an exponent of 2
  62. * @param salt a 16 byte salt
  63. * @param password the password
  64. * @return a 60 character Bcrypt String
  65. */
  66. private static string CreateBcryptString(string version, byte[] password, byte[] salt, int cost)
  67. {
  68. if (!AllowedVersions.Contains(version))
  69. throw new ArgumentException("Version " + version + " is not accepted by this implementation.", "version");
  70. StringBuilder sb = new StringBuilder(60);
  71. sb.Append('$');
  72. sb.Append(version);
  73. sb.Append('$');
  74. sb.Append(cost < 10 ? ("0" + cost) : cost.ToString());
  75. sb.Append('$');
  76. sb.Append(EncodeData(salt));
  77. byte[] key = BCrypt.Generate(password, salt, cost);
  78. sb.Append(EncodeData(key));
  79. return sb.ToString();
  80. }
  81. /**
  82. * Creates a 60 character Bcrypt String, including
  83. * version, cost factor, salt and hash, separated by '$' using version
  84. * '2y'.
  85. *
  86. * @param cost the cost factor, treated as an exponent of 2
  87. * @param salt a 16 byte salt
  88. * @param password the password
  89. * @return a 60 character Bcrypt String
  90. */
  91. public static string Generate(char[] password, byte[] salt, int cost)
  92. {
  93. return Generate(DefaultVersion, password, salt, cost);
  94. }
  95. /**
  96. * Creates a 60 character Bcrypt String, including
  97. * version, cost factor, salt and hash, separated by '$'
  98. *
  99. * @param version the version, may be 2b, 2y or 2a. (2a is not backwards compatible.)
  100. * @param cost the cost factor, treated as an exponent of 2
  101. * @param salt a 16 byte salt
  102. * @param password the password
  103. * @return a 60 character Bcrypt String
  104. */
  105. public static string Generate(string version, char[] password, byte[] salt, int cost)
  106. {
  107. if (!AllowedVersions.Contains(version))
  108. throw new ArgumentException("Version " + version + " is not accepted by this implementation.", "version");
  109. if (password == null)
  110. throw new ArgumentNullException("password");
  111. if (salt == null)
  112. throw new ArgumentNullException("salt");
  113. if (salt.Length != 16)
  114. throw new DataLengthException("16 byte salt required: " + salt.Length);
  115. if (cost < 4 || cost > 31) // Minimum rounds: 16, maximum 2^31
  116. throw new ArgumentException("Invalid cost factor.", "cost");
  117. byte[] psw = Strings.ToUtf8ByteArray(password);
  118. // 0 termination:
  119. byte[] tmp = new byte[psw.Length >= 72 ? 72 : psw.Length + 1];
  120. int copyLen = System.Math.Min(psw.Length, tmp.Length);
  121. Array.Copy(psw, 0, tmp, 0, copyLen);
  122. Array.Clear(psw, 0, psw.Length);
  123. string rv = CreateBcryptString(version, tmp, salt, cost);
  124. Array.Clear(tmp, 0, tmp.Length);
  125. return rv;
  126. }
  127. /**
  128. * Checks if a password corresponds to a 60 character Bcrypt String
  129. *
  130. * @param bcryptString a 60 character Bcrypt String, including
  131. * version, cost factor, salt and hash,
  132. * separated by '$'
  133. * @param password the password as an array of chars
  134. * @return true if the password corresponds to the
  135. * Bcrypt String, otherwise false
  136. */
  137. public static bool CheckPassword(string bcryptString, char[] password)
  138. {
  139. // validate bcryptString:
  140. if (bcryptString.Length != 60)
  141. throw new DataLengthException("Bcrypt String length: " + bcryptString.Length + ", 60 required.");
  142. if (bcryptString[0] != '$' || bcryptString[3] != '$' || bcryptString[6] != '$')
  143. throw new ArgumentException("Invalid Bcrypt String format.", "bcryptString");
  144. string version = bcryptString.Substring(1, 2);
  145. if (!AllowedVersions.Contains(version))
  146. throw new ArgumentException("Bcrypt version '" + version + "' is not supported by this implementation", "bcryptString");
  147. int cost = 0;
  148. try
  149. {
  150. cost = Int32.Parse(bcryptString.Substring(4, 2));
  151. }
  152. catch (Exception nfe)
  153. {
  154. #if PORTABLE || NETFX_CORE
  155. throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString");
  156. #else
  157. throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString", nfe);
  158. #endif
  159. }
  160. if (cost < 4 || cost > 31)
  161. throw new ArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected.");
  162. // check password:
  163. if (password == null)
  164. throw new ArgumentNullException("Missing password.");
  165. int start = bcryptString.LastIndexOf('$') + 1, end = bcryptString.Length - 31;
  166. byte[] salt = DecodeSaltString(bcryptString.Substring(start, end - start));
  167. string newBcryptString = Generate(version, password, salt, cost);
  168. return bcryptString.Equals(newBcryptString);
  169. }
  170. /*
  171. * encode the input data producing a Bcrypt base 64 string.
  172. *
  173. * @param a byte representation of the salt or the password
  174. * @return the Bcrypt base64 string
  175. */
  176. private static string EncodeData(byte[] data)
  177. {
  178. if (data.Length != 24 && data.Length != 16) // 192 bit key or 128 bit salt expected
  179. throw new DataLengthException("Invalid length: " + data.Length + ", 24 for key or 16 for salt expected");
  180. bool salt = false;
  181. if (data.Length == 16)//salt
  182. {
  183. salt = true;
  184. byte[] tmp = new byte[18];// zero padding
  185. Array.Copy(data, 0, tmp, 0, data.Length);
  186. data = tmp;
  187. }
  188. else // key
  189. {
  190. data[data.Length - 1] = (byte)0;
  191. }
  192. MemoryStream mOut = new MemoryStream();
  193. int len = data.Length;
  194. uint a1, a2, a3;
  195. int i;
  196. for (i = 0; i < len; i += 3)
  197. {
  198. a1 = data[i];
  199. a2 = data[i + 1];
  200. a3 = data[i + 2];
  201. mOut.WriteByte(EncodingTable[(a1 >> 2) & 0x3f]);
  202. mOut.WriteByte(EncodingTable[((a1 << 4) | (a2 >> 4)) & 0x3f]);
  203. mOut.WriteByte(EncodingTable[((a2 << 2) | (a3 >> 6)) & 0x3f]);
  204. mOut.WriteByte(EncodingTable[a3 & 0x3f]);
  205. }
  206. string result = Strings.FromByteArray(mOut.ToArray());
  207. int resultLen = salt
  208. ? 22 // truncate padding
  209. : result.Length - 1;
  210. return result.Substring(0, resultLen);
  211. }
  212. /*
  213. * decodes the bcrypt base 64 encoded SaltString
  214. *
  215. * @param a 22 character Bcrypt base 64 encoded String
  216. * @return the 16 byte salt
  217. * @exception DataLengthException if the length
  218. * of parameter is not 22
  219. * @exception InvalidArgumentException if the parameter
  220. * contains a value other than from Bcrypts base 64 encoding table
  221. */
  222. private static byte[] DecodeSaltString(string saltString)
  223. {
  224. char[] saltChars = saltString.ToCharArray();
  225. MemoryStream mOut = new MemoryStream(16);
  226. byte b1, b2, b3, b4;
  227. if (saltChars.Length != 22)// bcrypt salt must be 22 (16 bytes)
  228. throw new DataLengthException("Invalid base64 salt length: " + saltChars.Length + " , 22 required.");
  229. // check string for invalid characters:
  230. for (int i = 0; i < saltChars.Length; i++)
  231. {
  232. int value = saltChars[i];
  233. if (value > 122 || value < 46 || (value > 57 && value < 65))
  234. throw new ArgumentException("Salt string contains invalid character: " + value, "saltString");
  235. }
  236. // Padding: add two '\u0000'
  237. char[] tmp = new char[22 + 2];
  238. Array.Copy(saltChars, 0, tmp, 0, saltChars.Length);
  239. saltChars = tmp;
  240. int len = saltChars.Length;
  241. for (int i = 0; i < len; i += 4)
  242. {
  243. b1 = DecodingTable[saltChars[i]];
  244. b2 = DecodingTable[saltChars[i + 1]];
  245. b3 = DecodingTable[saltChars[i + 2]];
  246. b4 = DecodingTable[saltChars[i + 3]];
  247. mOut.WriteByte((byte)((b1 << 2) | (b2 >> 4)));
  248. mOut.WriteByte((byte)((b2 << 4) | (b3 >> 2)));
  249. mOut.WriteByte((byte)((b3 << 6) | b4));
  250. }
  251. byte[] saltBytes = mOut.ToArray();
  252. // truncate:
  253. byte[] tmpSalt = new byte[16];
  254. Array.Copy(saltBytes, 0, tmpSalt, 0, tmpSalt.Length);
  255. saltBytes = tmpSalt;
  256. return saltBytes;
  257. }
  258. }
  259. }
  260. #pragma warning restore
  261. #endif