HashSP800Drbg.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.Collections.Generic;
  5. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
  6. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  7. namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Prng.Drbg
  8. {
  9. /**
  10. * A SP800-90A Hash DRBG.
  11. */
  12. public sealed class HashSP800Drbg
  13. : ISP80090Drbg
  14. {
  15. private readonly static byte[] ONE = { 0x01 };
  16. private readonly static long RESEED_MAX = 1L << (48 - 1);
  17. private readonly static int MAX_BITS_REQUEST = 1 << (19 - 1);
  18. private static readonly IDictionary<string, int> SeedLens =
  19. new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  20. static HashSP800Drbg()
  21. {
  22. SeedLens.Add("SHA-1", 440);
  23. SeedLens.Add("SHA-224", 440);
  24. SeedLens.Add("SHA-256", 440);
  25. SeedLens.Add("SHA-512/256", 440);
  26. SeedLens.Add("SHA-512/224", 440);
  27. SeedLens.Add("SHA-384", 888);
  28. SeedLens.Add("SHA-512", 888);
  29. }
  30. private readonly IDigest mDigest;
  31. private readonly IEntropySource mEntropySource;
  32. private readonly int mSecurityStrength;
  33. private readonly int mSeedLength;
  34. private byte[] mV;
  35. private byte[] mC;
  36. private long mReseedCounter;
  37. /**
  38. * Construct a SP800-90A Hash DRBG.
  39. * <p>
  40. * Minimum entropy requirement is the security strength requested.
  41. * </p>
  42. * @param digest source digest to use for DRB stream.
  43. * @param securityStrength security strength required (in bits)
  44. * @param entropySource source of entropy to use for seeding/reseeding.
  45. * @param personalizationString personalization string to distinguish this DRBG (may be null).
  46. * @param nonce nonce to further distinguish this DRBG (may be null).
  47. */
  48. public HashSP800Drbg(IDigest digest, int securityStrength, IEntropySource entropySource, byte[] personalizationString, byte[] nonce)
  49. {
  50. if (securityStrength > DrbgUtilities.GetMaxSecurityStrength(digest))
  51. throw new ArgumentException("Requested security strength is not supported by the derivation function");
  52. if (entropySource.EntropySize < securityStrength)
  53. throw new ArgumentException("Not enough entropy for security strength required");
  54. mDigest = digest;
  55. mEntropySource = entropySource;
  56. mSecurityStrength = securityStrength;
  57. mSeedLength = SeedLens[digest.AlgorithmName];
  58. // 1. seed_material = entropy_input || nonce || personalization_string.
  59. // 2. seed = Hash_df (seed_material, seedlen).
  60. // 3. V = seed.
  61. // 4. C = Hash_df ((0x00 || V), seedlen). Comment: Preceed V with a byte
  62. // of zeros.
  63. // 5. reseed_counter = 1.
  64. // 6. Return V, C, and reseed_counter as the initial_working_state
  65. byte[] entropy = GetEntropy();
  66. byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalizationString);
  67. mV = new byte[(mSeedLength + 7) / 8];
  68. DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV);
  69. byte[] subV = new byte[mV.Length + 1];
  70. Array.Copy(mV, 0, subV, 1, mV.Length);
  71. mC = new byte[(mSeedLength + 7) / 8];
  72. DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC);
  73. mReseedCounter = 1;
  74. }
  75. /**
  76. * Return the block size (in bits) of the DRBG.
  77. *
  78. * @return the number of bits produced on each internal round of the DRBG.
  79. */
  80. public int BlockSize
  81. {
  82. get { return mDigest.GetDigestSize() * 8; }
  83. }
  84. /**
  85. * Populate a passed in array with random data.
  86. *
  87. * @param output output array for generated bits.
  88. * @param additionalInput additional input to be added to the DRBG in this step.
  89. * @param predictionResistant true if a reseed should be forced, false otherwise.
  90. *
  91. * @return number of bits generated, -1 if a reseed required.
  92. */
  93. public int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput,
  94. bool predictionResistant)
  95. {
  96. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  97. return additionalInput == null
  98. ? Generate(output.AsSpan(outputOff, outputLen), predictionResistant)
  99. : GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant);
  100. #else
  101. // 1. If reseed_counter > reseed_interval, then return an indication that a
  102. // reseed is required.
  103. // 2. If (additional_input != Null), then do
  104. // 2.1 w = Hash (0x02 || V || additional_input).
  105. // 2.2 V = (V + w) mod 2^seedlen
  106. // .
  107. // 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
  108. // 4. H = Hash (0x03 || V).
  109. // 5. V = (V + H + C + reseed_counter) mod 2^seedlen
  110. // .
  111. // 6. reseed_counter = reseed_counter + 1.
  112. // 7. Return SUCCESS, returned_bits, and the new values of V, C, and
  113. // reseed_counter for the new_working_state.
  114. int numberOfBits = outputLen * 8;
  115. if (numberOfBits > MAX_BITS_REQUEST)
  116. throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
  117. if (mReseedCounter > RESEED_MAX)
  118. return -1;
  119. if (predictionResistant)
  120. {
  121. Reseed(additionalInput);
  122. additionalInput = null;
  123. }
  124. // 2.
  125. if (additionalInput != null)
  126. {
  127. byte[] newInput = new byte[1 + mV.Length + additionalInput.Length];
  128. newInput[0] = 0x02;
  129. Array.Copy(mV, 0, newInput, 1, mV.Length);
  130. Array.Copy(additionalInput, 0, newInput, 1 + mV.Length, additionalInput.Length);
  131. byte[] w = Hash(newInput);
  132. AddTo(mV, w);
  133. }
  134. // 3.
  135. byte[] rv = Hashgen(mV, outputLen);
  136. // 4.
  137. byte[] subH = new byte[mV.Length + 1];
  138. Array.Copy(mV, 0, subH, 1, mV.Length);
  139. subH[0] = 0x03;
  140. byte[] H = Hash(subH);
  141. // 5.
  142. AddTo(mV, H);
  143. AddTo(mV, mC);
  144. byte[] c = new byte[4];
  145. Pack.UInt32_To_BE((uint)mReseedCounter, c);
  146. AddTo(mV, c);
  147. mReseedCounter++;
  148. Array.Copy(rv, 0, output, outputOff, outputLen);
  149. return numberOfBits;
  150. #endif
  151. }
  152. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  153. public int Generate(Span<byte> output, bool predictionResistant)
  154. {
  155. // 1. If reseed_counter > reseed_interval, then return an indication that a
  156. // reseed is required.
  157. // 2. If (additional_input != Null), then do
  158. // 2.1 w = Hash (0x02 || V || additional_input).
  159. // 2.2 V = (V + w) mod 2^seedlen
  160. // .
  161. // 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
  162. // 4. H = Hash (0x03 || V).
  163. // 5. V = (V + H + C + reseed_counter) mod 2^seedlen
  164. // .
  165. // 6. reseed_counter = reseed_counter + 1.
  166. // 7. Return SUCCESS, returned_bits, and the new values of V, C, and
  167. // reseed_counter for the new_working_state.
  168. int numberOfBits = output.Length * 8;
  169. if (numberOfBits > MAX_BITS_REQUEST)
  170. throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
  171. if (mReseedCounter > RESEED_MAX)
  172. return -1;
  173. if (predictionResistant)
  174. {
  175. Reseed(ReadOnlySpan<byte>.Empty);
  176. }
  177. return ImplGenerate(output);
  178. }
  179. public int GenerateWithInput(Span<byte> output, ReadOnlySpan<byte> additionalInput, bool predictionResistant)
  180. {
  181. // 1. If reseed_counter > reseed_interval, then return an indication that a
  182. // reseed is required.
  183. // 2. If (additional_input != Null), then do
  184. // 2.1 w = Hash (0x02 || V || additional_input).
  185. // 2.2 V = (V + w) mod 2^seedlen
  186. // .
  187. // 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
  188. // 4. H = Hash (0x03 || V).
  189. // 5. V = (V + H + C + reseed_counter) mod 2^seedlen
  190. // .
  191. // 6. reseed_counter = reseed_counter + 1.
  192. // 7. Return SUCCESS, returned_bits, and the new values of V, C, and
  193. // reseed_counter for the new_working_state.
  194. int numberOfBits = output.Length * 8;
  195. if (numberOfBits > MAX_BITS_REQUEST)
  196. throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
  197. if (mReseedCounter > RESEED_MAX)
  198. return -1;
  199. if (predictionResistant)
  200. {
  201. Reseed(additionalInput);
  202. }
  203. else
  204. {
  205. // 2.
  206. mDigest.Update(0x02);
  207. mDigest.BlockUpdate(mV);
  208. mDigest.BlockUpdate(additionalInput);
  209. int digestSize = mDigest.GetDigestSize();
  210. Span<byte> w = digestSize <= 128
  211. ? stackalloc byte[digestSize]
  212. : new byte[digestSize];
  213. mDigest.DoFinal(w);
  214. AddTo(mV, w);
  215. }
  216. return ImplGenerate(output);
  217. }
  218. private int ImplGenerate(Span<byte> output)
  219. {
  220. // 3.
  221. Hashgen(mV, output);
  222. // 4.
  223. mDigest.Update(0x03);
  224. mDigest.BlockUpdate(mV);
  225. int digestSize = mDigest.GetDigestSize();
  226. Span<byte> H = digestSize <= 128
  227. ? stackalloc byte[digestSize]
  228. : new byte[digestSize];
  229. mDigest.DoFinal(H);
  230. // 5.
  231. AddTo(mV, H);
  232. AddTo(mV, mC);
  233. Span<byte> c = stackalloc byte[4];
  234. Pack.UInt32_To_BE((uint)mReseedCounter, c);
  235. AddTo(mV, c);
  236. mReseedCounter++;
  237. return output.Length * 8;
  238. }
  239. #endif
  240. private byte[] GetEntropy()
  241. {
  242. byte[] entropy = mEntropySource.GetEntropy();
  243. if (entropy.Length < (mSecurityStrength + 7) / 8)
  244. throw new InvalidOperationException("Insufficient entropy provided by entropy source");
  245. return entropy;
  246. }
  247. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  248. private int GetEntropy(Span<byte> output)
  249. {
  250. int length = mEntropySource.GetEntropy(output);
  251. if (length < (mSecurityStrength + 7) / 8)
  252. throw new InvalidOperationException("Insufficient entropy provided by entropy source");
  253. return length;
  254. }
  255. private int GetEntropyLength()
  256. {
  257. return (mEntropySource.EntropySize + 7) / 8;
  258. }
  259. #endif
  260. // this will always add the shorter length byte array mathematically to the
  261. // longer length byte array.
  262. // be careful....
  263. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  264. private void AddTo(Span<byte> longer, ReadOnlySpan<byte> shorter)
  265. #else
  266. private void AddTo(byte[] longer, byte[] shorter)
  267. #endif
  268. {
  269. int off = longer.Length - shorter.Length;
  270. uint carry = 0;
  271. int i = shorter.Length;
  272. while (--i >= 0)
  273. {
  274. carry += (uint)longer[off + i] + shorter[i];
  275. longer[off + i] = (byte)carry;
  276. carry >>= 8;
  277. }
  278. i = off;
  279. while (--i >= 0)
  280. {
  281. carry += longer[i];
  282. longer[i] = (byte)carry;
  283. carry >>= 8;
  284. }
  285. }
  286. /**
  287. * Reseed the DRBG.
  288. *
  289. * @param additionalInput additional input to be added to the DRBG in this step.
  290. */
  291. public void Reseed(byte[] additionalInput)
  292. {
  293. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  294. Reseed(Spans.FromNullableReadOnly(additionalInput));
  295. #else
  296. // 1. seed_material = 0x01 || V || entropy_input || additional_input.
  297. //
  298. // 2. seed = Hash_df (seed_material, seedlen).
  299. //
  300. // 3. V = seed.
  301. //
  302. // 4. C = Hash_df ((0x00 || V), seedlen).
  303. //
  304. // 5. reseed_counter = 1.
  305. //
  306. // 6. Return V, C, and reseed_counter for the new_working_state.
  307. //
  308. // Comment: Precede with a byte of all zeros.
  309. byte[] entropy = GetEntropy();
  310. byte[] seedMaterial = Arrays.ConcatenateAll(ONE, mV, entropy, additionalInput);
  311. DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV);
  312. byte[] subV = new byte[mV.Length + 1];
  313. subV[0] = 0x00;
  314. Array.Copy(mV, 0, subV, 1, mV.Length);
  315. DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC);
  316. mReseedCounter = 1;
  317. #endif
  318. }
  319. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  320. public void Reseed(ReadOnlySpan<byte> additionalInput)
  321. {
  322. // 1. seed_material = 0x01 || V || entropy_input || additional_input.
  323. //
  324. // 2. seed = Hash_df (seed_material, seedlen).
  325. //
  326. // 3. V = seed.
  327. //
  328. // 4. C = Hash_df ((0x00 || V), seedlen).
  329. //
  330. // 5. reseed_counter = 1.
  331. //
  332. // 6. Return V, C, and reseed_counter for the new_working_state.
  333. //
  334. // Comment: Precede with a byte of all zeros.
  335. int entropyLength = GetEntropyLength();
  336. int seedMaterialLength = 1 + mV.Length + entropyLength + additionalInput.Length;
  337. Span<byte> seedMaterial = seedMaterialLength <= 256
  338. ? stackalloc byte[seedMaterialLength]
  339. : new byte[seedMaterialLength];
  340. seedMaterial[0] = 0x01;
  341. mV.CopyTo(seedMaterial[1..]);
  342. GetEntropy(seedMaterial[(1 + mV.Length)..]);
  343. additionalInput.CopyTo(seedMaterial[(1 + mV.Length + entropyLength)..]);
  344. DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV);
  345. int subVLength = 1 + mV.Length;
  346. Span<byte> subV = subVLength <= 128
  347. ? stackalloc byte[subVLength]
  348. : new byte[subVLength];
  349. subV[0] = 0x00;
  350. mV.CopyTo(subV[1..]);
  351. DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC);
  352. mReseedCounter = 1;
  353. }
  354. #endif
  355. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  356. private void DoHash(ReadOnlySpan<byte> input, Span<byte> output)
  357. {
  358. mDigest.BlockUpdate(input);
  359. mDigest.DoFinal(output);
  360. }
  361. #else
  362. private void DoHash(byte[] input, byte[] output)
  363. {
  364. mDigest.BlockUpdate(input, 0, input.Length);
  365. mDigest.DoFinal(output, 0);
  366. }
  367. private byte[] Hash(byte[] input)
  368. {
  369. byte[] hash = new byte[mDigest.GetDigestSize()];
  370. DoHash(input, hash);
  371. return hash;
  372. }
  373. #endif
  374. // 1. m = [requested_number_of_bits / outlen]
  375. // 2. data = V.
  376. // 3. W = the Null string.
  377. // 4. For i = 1 to m
  378. // 4.1 wi = Hash (data).
  379. // 4.2 W = W || wi.
  380. // 4.3 data = (data + 1) mod 2^seedlen
  381. // .
  382. // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W.
  383. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  384. private void Hashgen(ReadOnlySpan<byte> input, Span<byte> output)
  385. {
  386. int digestSize = mDigest.GetDigestSize();
  387. int m = output.Length / digestSize;
  388. int dataSize = input.Length;
  389. Span<byte> data = dataSize <= 256
  390. ? stackalloc byte[input.Length]
  391. : new byte[input.Length];
  392. input.CopyTo(data);
  393. Span<byte> dig = digestSize <= 128
  394. ? stackalloc byte[digestSize]
  395. : new byte[digestSize];
  396. for (int i = 0; i <= m; i++)
  397. {
  398. DoHash(data, dig);
  399. int bytesToCopy = System.Math.Min(digestSize, output.Length - i * digestSize);
  400. dig[..bytesToCopy].CopyTo(output[(i * digestSize)..]);
  401. AddTo(data, ONE);
  402. }
  403. }
  404. #else
  405. private byte[] Hashgen(byte[] input, int length)
  406. {
  407. int digestSize = mDigest.GetDigestSize();
  408. int m = length / digestSize;
  409. byte[] data = (byte[])input.Clone();
  410. byte[] W = new byte[length];
  411. byte[] dig = new byte[digestSize];
  412. for (int i = 0; i <= m; i++)
  413. {
  414. DoHash(data, dig);
  415. int bytesToCopy = System.Math.Min(digestSize, length - i * digestSize);
  416. Array.Copy(dig, 0, W, i * digestSize, bytesToCopy);
  417. AddTo(data, ONE);
  418. }
  419. return W;
  420. }
  421. #endif
  422. }
  423. }
  424. #pragma warning restore
  425. #endif