Skip to content

SAIF.NetCore.HashUtil architecture

SAIF.NetCore.HashUtil is a leaf utility library in the Framework.NCore implementation layer that provides reversible, non-cryptographic obfuscation of a create timestamp inside an existing hash string (SaltShaker) plus the SystemHashExpiredException type used by callers to signal hash expiry; it takes no package dependency on SAIF.NetCore.ServiceContracts or SAIF.NetCore.Common, and it is consumed, via a NuGet package reference, only by SAIF.NetCore.SystemHash, which is one of the three documented implementation-to-implementation exceptions in Architecture.md.

Responsibilities

  • Salt a caller-supplied hash string by hex-encoding a create date into seven positionally-random insertion points and returning a location map prefixed to the salted body; the map is a fixed 42 characters only while each entry's unpadded body-length field stays two digits, a condition SaltHash does not validate (SaltShaker.SaltHash, see BR-HU-01).
  • De-salt a previously salted hash string by walking the location map in reverse insertion order, removing and decoding each date part, and recovering the original create date (SaltShaker.BlandHash).
  • Define SystemHashExpiredException, the exception type a consumer throws when it decides a recovered create date has expired.
  • Provide these primitives as a stateful, dependency-free building block — each SaltShaker instance retains its constructor-supplied _dateParts and a per-instance Random (_random) across calls — with no DI registration, no hosting, and no configuration surface. The observed in-repo consumer constructs a fresh SaltShaker per salt/de-salt operation rather than reusing or sharing an instance.*

Component boundaries

  • SaltShaker: owns the salting/de-salting algorithm, the per-instance Random used for insertion positions and pad characters, and the internal hex encode/decode helpers. It never throws SystemHashExpiredException and never compares dates itself*.
  • SystemHashExpiredException: a plain System.Exception subtype that only carries constructor parameters (nowDate, hashDate); it has no serialization constructor and no parameterless/message constructors.
  • Boundary with SAIF.NetCore.SystemHash: SystemHash produces the SHA-256/Base64 hash, supplies DateTime.Now as the create date, invokes SaltShaker to salt/de-salt, and owns the lifetime/expiry decision and the throw of SystemHashExpiredException; HashUtil owns only the obfuscation mechanics.

Interaction model

  • HashUtil exposes no service interfaces and performs no dependency injection; the sole in-repo consumer (SystemHashService) instantiates SaltShaker directly with new — once with a DateTime (to salt) and once parameterless (to de-salt).
  • There is no inbound or outbound network call anywhere in this library; all behavior is in-process BCL computation (System.Collections.Generic, System.Linq, System.Text, System.Random, System.Convert).
  • The only architectural relationship is the inbound NuGet package reference from SAIF.NetCore.SystemHash, which reaches applications transitively through SAIF.NetCore.ServiceExtensions → SAIF.NetCore.SystemHash → SAIF.NetCore.HashUtil.

Architecture diagram

The following diagram shows HashUtil's position relative to its sole consumer and the rest of the Framework.NCore dependency graph.

flowchart TB
    HASH["SAIF.NetCore.HashUtil<br>(SaltShaker, SystemHashExpiredException)"]
    SYSHASH["SAIF.NetCore.SystemHash<br>(SystemHashService)"]
    SE["SAIF.NetCore.ServiceExtensions"]
    APP["Consuming application"]

    SYSHASH -.->|NuGet ref| HASH
    SE -.->|NuGet ref| SYSHASH
    APP -.->|NuGet ref| SE

Sequence diagram

The following diagram shows the primary salt/de-salt round trip as invoked by the sole in-repo consumer, SystemHashService.

sequenceDiagram
    participant Caller
    participant SystemHashService
    participant SaltShaker

    Caller->>SystemHashService: Get(systemType)
    SystemHashService->>SystemHashService: Compute SHA-256/Base64 hash
    SystemHashService->>SaltShaker: new SaltShaker(DateTime.Now)
    SystemHashService->>SaltShaker: SaltHash(systemHash)
    SaltShaker-->>SystemHashService: salted hash string
    SystemHashService-->>Caller: salted hash string

    Caller->>SystemHashService: RemoveSystemHashSaltAndValidateLifetime(saltedHash)
    SystemHashService->>SaltShaker: new SaltShaker()
    SystemHashService->>SaltShaker: BlandHash(saltedHash, out createDate)
    SaltShaker-->>SystemHashService: original hash + createDate
    SystemHashService->>SystemHashService: Compare createDate against lifetime
    SystemHashService-->>Caller: value, or throw SystemHashExpiredException

Design constraints

  • No cryptographic guarantee: salting is positional obfuscation with a non-cryptographic System.Random, not encryption, and the location map discloses every insertion index*.
  • Correct round-tripping depends on Dictionary<string,string> (_dateParts) and Dictionary<string,int> (_datePartLength) enumeration order being stable, which is not a documented contract of Dictionary<TKey,TValue>*.
  • The fixed 42-character map width (MAP_LENGTH) holds only when the salted body length after each insertion stays within two digits and indices stay below 1000; this is satisfied for the only observed input shape (44-char Base64 SHA-256) but is not enforced*.
  • A SaltShaker built with the parameterless constructor has a null _dateParts and is usable only for BlandHash; nothing enforces this split at compile time*.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs
  • src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SystemHashExpiredException.cs
  • src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil.csproj
  • src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
  • Architecture.md