22using System . Security . Cryptography ;
33using System . Text ;
44using System . ComponentModel ;
5+ using System . Threading ;
56using Isopoh . Cryptography . Argon2 ;
67
78namespace SecurityHelperLibrary
@@ -16,17 +17,62 @@ public class SecurityHelper : ISecurityHelper
1617 private const int MinHashLengthBytes = 16 ;
1718 private const int MinArgon2Iterations = 3 ;
1819 private const int MinArgon2MemoryKb = 65536 ;
20+ private const int BaselineArgon2Iterations = 4 ;
21+ private const int BaselineArgon2MemoryKb = 131072 ;
22+ private const int BaselineArgon2DegreeOfParallelism = 4 ;
23+ private const int BaselineArgon2HashLength = 32 ;
1924 private const int MaxArgon2DegreeOfParallelism = 64 ;
2025 private readonly Action < string > _securityIncidentLogger ;
26+ private readonly int _defaultArgon2Iterations ;
27+ private readonly int _defaultArgon2MemoryKb ;
28+ private readonly int _defaultArgon2DegreeOfParallelism ;
29+ private readonly int _defaultArgon2HashLength ;
2130
2231 public SecurityHelper ( )
23- : this ( null )
32+ : this ( ( Action < string > ) null )
2433 {
2534 }
2635
2736 public SecurityHelper ( Action < string > securityIncidentLogger )
37+ : this ( securityIncidentLogger , null )
38+ {
39+ }
40+
41+ public SecurityHelper ( SecurityHelperOptions options )
42+ : this ( null , options )
43+ {
44+ }
45+
46+ public SecurityHelper ( Action < string > securityIncidentLogger , SecurityHelperOptions options )
2847 {
2948 _securityIncidentLogger = securityIncidentLogger ;
49+ if ( options == null )
50+ {
51+ _defaultArgon2Iterations = BaselineArgon2Iterations ;
52+ _defaultArgon2MemoryKb = BaselineArgon2MemoryKb ;
53+ _defaultArgon2DegreeOfParallelism = BaselineArgon2DegreeOfParallelism ;
54+ _defaultArgon2HashLength = BaselineArgon2HashLength ;
55+ return ;
56+ }
57+
58+ _defaultArgon2Iterations = options . Argon2DefaultIterations >= MinArgon2Iterations
59+ ? options . Argon2DefaultIterations
60+ : MinArgon2Iterations ;
61+
62+ _defaultArgon2MemoryKb = options . Argon2DefaultMemoryKb >= MinArgon2MemoryKb
63+ ? options . Argon2DefaultMemoryKb
64+ : MinArgon2MemoryKb ;
65+
66+ _defaultArgon2DegreeOfParallelism = options . Argon2DefaultDegreeOfParallelism >= 1
67+ ? options . Argon2DefaultDegreeOfParallelism
68+ : 1 ;
69+
70+ if ( _defaultArgon2DegreeOfParallelism > MaxArgon2DegreeOfParallelism )
71+ _defaultArgon2DegreeOfParallelism = MaxArgon2DegreeOfParallelism ;
72+
73+ _defaultArgon2HashLength = options . Argon2DefaultHashLength >= MinHashLengthBytes
74+ ? options . Argon2DefaultHashLength
75+ : BaselineArgon2HashLength ;
3076 }
3177
3278 // --- IMMUTABLE WORKING METHODS ---
@@ -234,6 +280,7 @@ private static HashAlgorithm GetHashAlgorithm(HashAlgorithmName hashAlgorithm)
234280#endif
235281 public string HashPasswordWithArgon2 ( string password , string salt , int iterations = 4 , int memoryKb = 131072 , int degreeOfParallelism = 4 , int hashLength = 32 )
236282 {
283+ ApplyArgon2Defaults ( ref iterations , ref memoryKb , ref degreeOfParallelism , ref hashLength ) ;
237284#if NET6_0_OR_GREATER
238285 if ( string . IsNullOrEmpty ( password ) )
239286 throw new ArgumentException ( "Password cannot be null or empty." , nameof ( password ) ) ;
@@ -290,6 +337,7 @@ public string HashPasswordWithArgon2(string password, string salt, int iteration
290337 /// </summary>
291338 public string HashPasswordWithArgon2 ( ReadOnlySpan < char > password , string salt , int iterations = 4 , int memoryKb = 131072 , int degreeOfParallelism = 4 , int hashLength = 32 )
292339 {
340+ ApplyArgon2Defaults ( ref iterations , ref memoryKb , ref degreeOfParallelism , ref hashLength ) ;
293341 if ( password . Length == 0 )
294342 throw new ArgumentException ( "Password cannot be empty." , nameof ( password ) ) ;
295343 if ( string . IsNullOrWhiteSpace ( salt ) )
@@ -689,17 +737,42 @@ private CryptographicException CreateInvalidSecurityParametersException(string i
689737 return new CryptographicException ( GenericSecurityErrorMessage ) ;
690738 }
691739
740+ private void ApplyArgon2Defaults ( ref int iterations , ref int memoryKb , ref int degreeOfParallelism , ref int hashLength )
741+ {
742+ if ( iterations == BaselineArgon2Iterations )
743+ iterations = _defaultArgon2Iterations ;
744+
745+ if ( memoryKb == BaselineArgon2MemoryKb )
746+ memoryKb = _defaultArgon2MemoryKb ;
747+
748+ if ( degreeOfParallelism == BaselineArgon2DegreeOfParallelism )
749+ degreeOfParallelism = _defaultArgon2DegreeOfParallelism ;
750+
751+ if ( hashLength == BaselineArgon2HashLength )
752+ hashLength = _defaultArgon2HashLength ;
753+ }
754+
692755 private void LogSecurityIncident ( string incidentCode , Exception exception )
693756 {
694757 if ( _securityIncidentLogger == null )
695758 return ;
696759
760+ string payload = exception == null
761+ ? $ "SEC_EVT|code={ incidentCode } |exception=None"
762+ : $ "SEC_EVT|code={ incidentCode } |exception={ exception . GetType ( ) . Name } ";
763+
697764 try
698765 {
699- if ( exception == null )
700- _securityIncidentLogger . Invoke ( incidentCode ) ;
701- else
702- _securityIncidentLogger . Invoke ( $ "{ incidentCode } |{ exception . GetType ( ) . Name } ") ;
766+ ThreadPool . QueueUserWorkItem ( _ =>
767+ {
768+ try
769+ {
770+ _securityIncidentLogger . Invoke ( payload ) ;
771+ }
772+ catch
773+ {
774+ }
775+ } ) ;
703776 }
704777 catch
705778 {
0 commit comments