How to Create a Reproducible
Opaquer Release Build
Establish deterministic, production-grade .NET release workflows combining XML project configurations (.xml), command-line CLI execution, strong-name signing, and automated symbol rename map archiving.
Direct Answer
To create a reproducible Opaquer release build, version-control a declarative XML project settings file (.xml) containing fixed random seeds and protection rules, enable MSBuild deterministic compilation (<Deterministic>true</Deterministic>), and execute protection via the Opaquer CLI in your CI/CD pipeline. Always perform strong-name key re-signing inside Opaquer during obfuscation and archive the resulting XML symbol map (.map.xml) as a build artifact for production crash log de-obfuscation.
1 The Problem with Ad-Hoc Obfuscation Builds
In many organizations, assembly obfuscation is performed ad-hoc: a developer opens an obfuscator GUI, loads binaries built locally on their workstation, tweaks settings manually, runs protection, and uploads the resulting output to distribution servers.
- Dynamic seed generates random identifier tokens per run
- Strong-name signature broken after post-processing
- Rename maps overwriting locally and lost forever
- Inability to trace production stack trace exceptions
- Local workstation drift vs CI server binaries
- Fixed seed guarantees identical binary hashes
- Automated key re-signing via secure CI secrets
- Version-controlled XML project configuration (.xml)
- Automated renaming map (.map.xml) build artifact storage
- 100% auditable release provenance (SLSA compliant)
When a crash occurs in production, troubleshooting an ad-hoc build is nearly impossible. Without the exact matching symbol rename map (.map.xml) generated for that specific byte-for-byte binary compilation, stack traces contain un-decodable obfuscated symbols like a.b.c().
2 Automation & Integration Configurations
A reproducible build relies on three core artifacts: the MSBuild project settings, the version-controlled Opaquer XML config file, and the CI/CD execution pipeline.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<!-- 1. Enable Deterministic MSBuild Compilation -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>
<!-- 2. Post-Build Automated Opaquer Execution -->
<Target Name="OpaquerReleaseObfuscation" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
<Exec Command=""$(OpaquerCLIPath)\Opaquer.Console.exe" /i:"$(TargetPath)" /o:"$(TargetDir)Protected\$(TargetFileName)" /config:"$(ProjectDir)opaquer.xml" /map:"$(TargetDir)Protected\$(TargetName).map.xml" /log:"$(TargetDir)Protected\opaquer_build.log"" />
</Target>
</Project>
# azure-pipelines.yml - Deterministic Opaquer Release Pipeline
trigger:
branches:
include:
- main
- release/*
pool:
vmImage: 'windows-latest'
steps:
- task: DotNetCoreCLI@2
displayName: 'Build Release Assemblies'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '-c Release -p:Deterministic=true'
- script: |
Opaquer.Console.exe /i:"$(Build.SourcesDirectory)/bin/Release/net8.0/MyApp.dll" ^
/o:"$(Build.ArtifactStagingDirectory)/Protected/MyApp.dll" ^
/config:"$(Build.SourcesDirectory)/opaquer.xml" ^
/snk:"$(StrongNameKey.secureFilePath)" ^
/map:"$(Build.ArtifactStagingDirectory)/Protected/MyApp.map.xml" ^
/log:"$(Build.ArtifactStagingDirectory)/Protected/opaquer.log"
displayName: 'Run Opaquer Obfuscation & Re-signing'
- task: PublishBuildArtifacts@1
displayName: 'Publish Protected Binaries & Rename Map'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/Protected'
ArtifactName: 'protected-release-package'
# PowerShell Release Build Script with SHA256 Verification
Param(
[string]$AssemblyPath = ".\bin\Release\net8.0\MyApp.dll",
[string]$OutDir = ".\bin\Release\Protected\",
[string]$ConfigFile = ".\opaquer.xml"
)
# Ensure Output directory exists
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
# Execute Opaquer CLI
$opaquerExe = "C:\Program Files (x86)\Rustemsoft\Opaquer\Opaquer.Console.exe"
$mapPath = Join-Path $OutDir "MyApp.map.xml"
$logPath = Join-Path $OutDir "opaquer_build.log"
$outputPath = Join-Path $OutDir "MyApp.dll"
& $opaquerExe /i:$AssemblyPath /o:$outputPath /config:$ConfigFile /map:$mapPath /log:$logPath
# Compute SHA256 Checksum for Release Provenance Logging
$hash = (Get-FileHash -Path $outputPath -Algorithm SHA256).Hash
Write-Host "Protected Binary SHA256: $hash" -ForegroundColor Green
3 Risks of Non-Determinism in Production Releases
Failing to enforce reproducible release builds introduces critical operational vulnerabilities across deployment environments:
If the build system re-generates random identifier tokens every time a hotfix or re-build occurs, production error logs cannot be mapped back to C# line numbers using older map files.
Re-compiling assemblies without re-signing post-obfuscation causes the .NET CLR to reject the assembly at load time with a FileLoadException or signature failure.
Modern software supply chain standards (e.g. SLSA Level 3) require verifiable byte-for-byte reproducibility from source commit tag to protected binary.
4 Step-by-Step Configuration Workflow
Follow this six-step engineering workflow to configure an end-to-end reproducible build pipeline with Opaquer.
Enable MSBuild Determinism in .csproj
Set <Deterministic>true</Deterministic> in your project file. This prevents MSBuild from embedding local file system paths or build timestamps into module headers, ensuring identical pre-obfuscation IL binaries across workstations.
Export & Version-Control Opaquer XML Settings (.xml)
Configure your protection options (renaming exemptions, string encryption, control flow) in the Opaquer GUI once, then save the configuration as opaquer.xml. Enable a fixed pseudo-random seed in the XML settings and commit the file to Git.
Configure Strong-Name Key Re-Signing in Opaquer
Because obfuscation alters assembly metadata, pre-signed assemblies become invalid. Configure Opaquer to sign protected assemblies automatically during processing using a designated strong-name key (.snk or .pfx).
Integrate Opaquer CLI into CI/CD Pipeline
Incorporate Opaquer.Console.exe into Azure DevOps, GitHub Actions, or Jenkins pipelines using explicit parameters for input, output, XML config, map file, and log paths.
Archive Rename Map (.map.xml) & Build Logs
Configure your pipeline to archive MyApp.map.xml and opaquer.log alongside release binaries. Store these artifacts in secure build storage indexed by build number or Git tag.
Verify Protected Output Binaries
Run automated post-build validation: verify strong-name signature validity via sn -v, log SHA-256 binary checksums, and execute integration tests directly on protected assemblies.
5 Pipeline Architecture Comparison
Contrast the fragility of manual builds against the security and auditability of a deterministic Opaquer release pipeline:
Release Pipeline Flow Visual Comparison
6 Artifact Verification & Testing
Verify build integrity before releasing obfuscated packages to internal feeds or public distribution:
Run .NET Strong Name tool to verify signature integrity:
sn -v Protected\MyApp.dll
Run two pipeline builds from identical commits; verify SHA-256 output checksums match byte-for-byte.
Execute regression unit/integration test suites against protected binaries to ensure runtime functionality remains unbroken.
7 Common Build Failures & Troubleshooting
| Symptom / Error | Root Cause | Solution |
|---|---|---|
| FileLoadException: Strong name validation failed | Assembly was signed pre-obfuscation; IL modification invalidated hash. | Configure key re-signing inside Opaquer using /snk command argument. |
| CLI Exit Code 1: Missing Dependency Assembly | Opaquer could not resolve dependent framework or third-party DLLs. | Pass library search paths via /lib:".\bin\Release\net8.0" argument. |
| Production crash stack trace contains un-decodable symbols | Symbol map file (.map.xml) was not archived or map belonged to another build. | Enforce automated publishing of $(Build.BuildNumber).map.xml as a pipeline artifact. |
| SHA-256 output hash differs between CI runs | MSBuild deterministic mode disabled or seed setting omitted in .xml config. | Set <Deterministic>true</Deterministic> and fix random seed in XML config. |
8 Production Recommendations
Treat opaquer.xml as infrastructure code. Review configuration changes via Pull Requests to prevent accidental omission of protection rules.
Do not store .snk or .pfx files in Git repositories. Fetch private keys dynamically from Key Vaults during pipeline execution.
Store all release rename maps in a central symbol server or artifact store to allow fast incident triage with Opaquer's Stack Trace De-obfuscator tool.
9 Frequently Asked Questions
<Deterministic>true</Deterministic>) is enabled alongside a fixed random seed in Opaquer's XML project file (.xml), identical source code and assembly inputs will yield byte-for-byte identical protected binaries.
.map.xml). Store this map file as a CI/CD build artifact linked to your release version tag. You can load this map file into Opaquer's Stack Trace De-obfuscator tool to decode scrambled symbols back to original source method names.
.snk/.pfx) configuration settings or post-obfuscation via the sn.exe utility.