Generates Obfuscated Properties from `.env` File Content
$ dotnet add package EnvObfuscatorGenerates Obfuscated Properties from .env File Content
Unity 2022.3.12+ is supported
</div>
public static Memory<char> properties for each .env entry.Validate_<PropertyName>(ReadOnlySpan<char>) for constant-time comparison.
To avoid embedding "raw data" as an assembly metadata, EnvObfuscator uses preceding block comment as a source.
using EnvObfuscator;
/*
# 👇 Copy & paste .env file content
API_KEY=abc123
SERVICE_URL=https://example.com
SECRET=PA$$WORD
EMPTY=
*/
[Obfuscate(seed: 12345)] // Omit the argument to use random seed
static partial class EnvSecrets
{
}
[!IMPORTANT] The properties and methods are contained within a dedicated class called
<TargetClass>Loaderthat is designed to remove the unnecessaryObfuscateattribute marker from the actual obfuscation class. Note that the original target class with marker attribute will have GUID-named decoys which always throw.
// Always returns a freshly decoded clone each time
var apiKey = EnvSecretsLoader.API_KEY;
var cache = apiKey.ToString();
// Consuming decoded data...
// Zeroing out the span — more for peace of mind than actual security
apiKey.Span.Clear();
cache = "";
// Validation (no decoding, full-length compare to avoid timing differences)
if (EnvSecretsLoader.Validate_SECRET("PA$$WORD"))
{
//...
}
[!TIP]
As string type is immutable (cannot zero them explicitly) and GC-collected object (not erased on demand), instead using stackalloc can achieve validation under full control.
var password = (stackalloc char[] { ... });
if (EnvSecretsLoader.Validate_SECRET(password))
{
//...
}
password.Clear(); // Fills memory by zeroSome system APIs take a string parameter. Recommend that the decoded data should not be stored in local variable as possible.
var res = await httpClient.GetAsync(EnvSecretsLoader.URL.ToString());
// Consideration: You can use GC.Collect, MemoryMarshal or CryptographicOperations.ZeroMemory
// to force zero memory (but it would be predictable code pattern)Intended to eliminate unnecessary "marker" for obfuscation, generated classes don't have DynamicallyAccessedMembers or UnityEngine.Scripting.Preserve attribute.
If you need to include the generated GUID-named chaff classes in build, use link.xml to prevent trimming on Native AOT or Unity IL2CPP build.
// Declare necessary chaff classes as desired
// Tip: The working dummy should provide the same functionality,
// but also logs user information for later use in banning.
/* API_KEY=working-dummy-to-detect-reverse-engineering */
[Obfuscate] partial class DbSecrets { }
/* API_KEY=working-dummy-to-detect-reverse-engineering */
[Obfuscate] partial class DatabaseSecrets { }
/* API_KEY=key-for-valid-usage */
[Obfuscate] partial class MySecrets { }Here shows sample link.xml for Unity. See the following link for more details.
<linker>
<!-- Preserve an entire assembly -->
<assembly fullname="MyAssembly">
<!-- Preserve a specific type -->
<type fullname="MyNamespace.MyClass" preserve="all"/>
<!-- Preserve only methods -->
<type fullname="MyNamespace.MyOtherClass" preserve="methods"/>
</assembly>
<!-- Preserve Unity built-in assemblies -->
<assembly fullname="UnityEngine.CoreModule">
<type fullname="UnityEngine.GameObject" preserve="all"/>
</assembly>
</linker>
0 is allowed but warned (deterministic and predictable).[!IMPORTANT] Obfuscated name collisions can surface as compiler errors, e.g.:
error CS0101: The namespace '<random_namespace>' already contains a definition for '<random_class>'or similar.
# line is parsed as KEY=VALUE (split on the first =).
= after the first.Validate_<PropertyName>(ReadOnlySpan<char>) short-circuits only on length mismatch, then performs a full-length compare to avoid leaking timing information.Span.Clear() after use to zero sensitive data.seed (if provided) controls the output deterministically.
[!NOTE] Values are trimmed; leading/trailing spaces are not preserved.