├── .gitignore ├── ConfigCrypter.Console ├── ConfigCrypter.Console.csproj ├── Options │ ├── CommandlineOptions.cs │ ├── DecryptOptions.cs │ └── EncryptOptions.cs ├── Program.cs ├── Properties │ └── launchSettings.json └── logo.png ├── ConfigCrypter.Tests ├── ConfigCrypter.Tests.csproj ├── ConfigCrypters │ └── Json │ │ └── JsonConfigCrypterTests.cs ├── ConfigProviders │ └── EncryptedJsonConfigSourceTests.cs ├── Crypters │ └── RSACrypterTests.cs ├── Mocks.cs ├── TestAppSettings.cs ├── appsettings.json ├── appsettings_decrypted.json ├── config.json ├── config_decrypted.json └── test-certificate.pfx ├── ConfigCrypter.sln ├── ConfigCrypter ├── CertificateLoaders │ ├── FilesystemCertificateLoader.cs │ ├── ICertificateLoader.cs │ └── StoreCertificateLoader.cs ├── ConfigCrypter.csproj ├── ConfigCrypters │ ├── IConfigCrypter.cs │ └── Json │ │ └── JsonConfigCrypter.cs ├── ConfigFileCrypter.cs ├── ConfigFileCrypterOptions.cs ├── ConfigProviders │ └── Json │ │ ├── EncryptedJsonConfigProvider.cs │ │ └── EncryptedJsonConfigSource.cs ├── Crypters │ ├── ICrypter.cs │ └── RSACrypter.cs ├── Extensions │ └── ConfigurationBuilderExtensions.cs ├── Properties │ └── launchSettings.json └── logo.png ├── Example.WebApp ├── Controllers │ └── ConfigController.cs ├── Example.WebApp.csproj ├── NestedSettings.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json ├── appsettings.Production.json ├── appsettings.json └── cert.pfx ├── LICENSE ├── README.md ├── cert.pfx └── devattic_configcrypter-normal256.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudio,csharp 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,csharp 4 | 5 | ### Csharp ### 6 | ## Ignore Visual Studio temporary files, build results, and 7 | ## files generated by popular Visual Studio add-ons. 8 | ## 9 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 10 | 11 | # User-specific files 12 | *.rsuser 13 | *.suo 14 | *.user 15 | *.userosscache 16 | *.sln.docstates 17 | 18 | # User-specific files (MonoDevelop/Xamarin Studio) 19 | *.userprefs 20 | 21 | # Mono auto generated files 22 | mono_crash.* 23 | 24 | # Build results 25 | [Dd]ebug/ 26 | [Dd]ebugPublic/ 27 | [Rr]elease/ 28 | [Rr]eleases/ 29 | x64/ 30 | x86/ 31 | [Aa][Rr][Mm]/ 32 | [Aa][Rr][Mm]64/ 33 | bld/ 34 | [Bb]in/ 35 | [Oo]bj/ 36 | [Ll]og/ 37 | [Ll]ogs/ 38 | 39 | # Visual Studio 2015/2017 cache/options directory 40 | .vs/ 41 | # Uncomment if you have tasks that create the project's static files in wwwroot 42 | #wwwroot/ 43 | 44 | # Visual Studio 2017 auto generated files 45 | Generated\ Files/ 46 | 47 | # MSTest test Results 48 | [Tt]est[Rr]esult*/ 49 | [Bb]uild[Ll]og.* 50 | 51 | # NUnit 52 | *.VisualState.xml 53 | TestResult.xml 54 | nunit-*.xml 55 | 56 | # Build Results of an ATL Project 57 | [Dd]ebugPS/ 58 | [Rr]eleasePS/ 59 | dlldata.c 60 | 61 | # Benchmark Results 62 | BenchmarkDotNet.Artifacts/ 63 | 64 | # .NET Core 65 | project.lock.json 66 | project.fragment.lock.json 67 | artifacts/ 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*[.json, .xml, .info] 147 | 148 | # Visual Studio code coverage results 149 | *.coverage 150 | *.coveragexml 151 | 152 | # NCrunch 153 | _NCrunch_* 154 | .*crunch*.local.xml 155 | nCrunchTemp_* 156 | 157 | # MightyMoose 158 | *.mm.* 159 | AutoTest.Net/ 160 | 161 | # Web workbench (sass) 162 | .sass-cache/ 163 | 164 | # Installshield output folder 165 | [Ee]xpress/ 166 | 167 | # DocProject is a documentation generator add-in 168 | DocProject/buildhelp/ 169 | DocProject/Help/*.HxT 170 | DocProject/Help/*.HxC 171 | DocProject/Help/*.hhc 172 | DocProject/Help/*.hhk 173 | DocProject/Help/*.hhp 174 | DocProject/Help/Html2 175 | DocProject/Help/html 176 | 177 | # Click-Once directory 178 | publish/ 179 | 180 | # Publish Web Output 181 | *.[Pp]ublish.xml 182 | *.azurePubxml 183 | # Note: Comment the next line if you want to checkin your web deploy settings, 184 | # but database connection strings (with potential passwords) will be unencrypted 185 | *.pubxml 186 | *.publishproj 187 | 188 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 189 | # checkin your Azure Web App publish settings, but sensitive information contained 190 | # in these scripts will be unencrypted 191 | PublishScripts/ 192 | 193 | # NuGet Packages 194 | *.nupkg 195 | # NuGet Symbol Packages 196 | *.snupkg 197 | # The packages folder can be ignored because of Package Restore 198 | **/[Pp]ackages/* 199 | # except build/, which is used as an MSBuild target. 200 | !**/[Pp]ackages/build/ 201 | # Uncomment if necessary however generally it will be regenerated when needed 202 | #!**/[Pp]ackages/repositories.config 203 | # NuGet v3's project.json files produces more ignorable files 204 | *.nuget.props 205 | *.nuget.targets 206 | 207 | # Microsoft Azure Build Output 208 | csx/ 209 | *.build.csdef 210 | 211 | # Microsoft Azure Emulator 212 | ecf/ 213 | rcf/ 214 | 215 | # Windows Store app package directories and files 216 | AppPackages/ 217 | BundleArtifacts/ 218 | Package.StoreAssociation.xml 219 | _pkginfo.txt 220 | *.appx 221 | *.appxbundle 222 | *.appxupload 223 | 224 | # Visual Studio cache files 225 | # files ending in .cache can be ignored 226 | *.[Cc]ache 227 | # but keep track of directories ending in .cache 228 | !?*.[Cc]ache/ 229 | 230 | # Others 231 | ClientBin/ 232 | ~$* 233 | *~ 234 | *.dbmdl 235 | *.dbproj.schemaview 236 | *.jfm 237 | *.pfx 238 | *.publishsettings 239 | orleans.codegen.cs 240 | 241 | # Including strong name files can present a security risk 242 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 243 | #*.snk 244 | 245 | # Since there are multiple workflows, uncomment next line to ignore bower_components 246 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 247 | #bower_components/ 248 | 249 | # RIA/Silverlight projects 250 | Generated_Code/ 251 | 252 | # Backup & report files from converting an old project file 253 | # to a newer Visual Studio version. Backup files are not needed, 254 | # because we have git ;-) 255 | _UpgradeReport_Files/ 256 | Backup*/ 257 | UpgradeLog*.XML 258 | UpgradeLog*.htm 259 | ServiceFabricBackup/ 260 | *.rptproj.bak 261 | 262 | # SQL Server files 263 | *.mdf 264 | *.ldf 265 | *.ndf 266 | 267 | # Business Intelligence projects 268 | *.rdl.data 269 | *.bim.layout 270 | *.bim_*.settings 271 | *.rptproj.rsuser 272 | *- [Bb]ackup.rdl 273 | *- [Bb]ackup ([0-9]).rdl 274 | *- [Bb]ackup ([0-9][0-9]).rdl 275 | 276 | # Microsoft Fakes 277 | FakesAssemblies/ 278 | 279 | # GhostDoc plugin setting file 280 | *.GhostDoc.xml 281 | 282 | # Node.js Tools for Visual Studio 283 | .ntvs_analysis.dat 284 | node_modules/ 285 | 286 | # Visual Studio 6 build log 287 | *.plg 288 | 289 | # Visual Studio 6 workspace options file 290 | *.opt 291 | 292 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 293 | *.vbw 294 | 295 | # Visual Studio LightSwitch build output 296 | **/*.HTMLClient/GeneratedArtifacts 297 | **/*.DesktopClient/GeneratedArtifacts 298 | **/*.DesktopClient/ModelManifest.xml 299 | **/*.Server/GeneratedArtifacts 300 | **/*.Server/ModelManifest.xml 301 | _Pvt_Extensions 302 | 303 | # Paket dependency manager 304 | .paket/paket.exe 305 | paket-files/ 306 | 307 | # FAKE - F# Make 308 | .fake/ 309 | 310 | # CodeRush personal settings 311 | .cr/personal 312 | 313 | # Python Tools for Visual Studio (PTVS) 314 | __pycache__/ 315 | *.pyc 316 | 317 | # Cake - Uncomment if you are using it 318 | # tools/** 319 | # !tools/packages.config 320 | 321 | # Tabs Studio 322 | *.tss 323 | 324 | # Telerik's JustMock configuration file 325 | *.jmconfig 326 | 327 | # BizTalk build output 328 | *.btp.cs 329 | *.btm.cs 330 | *.odx.cs 331 | *.xsd.cs 332 | 333 | # OpenCover UI analysis results 334 | OpenCover/ 335 | 336 | # Azure Stream Analytics local run output 337 | ASALocalRun/ 338 | 339 | # MSBuild Binary and Structured Log 340 | *.binlog 341 | 342 | # NVidia Nsight GPU debugger configuration file 343 | *.nvuser 344 | 345 | # MFractors (Xamarin productivity tool) working folder 346 | .mfractor/ 347 | 348 | # Local History for Visual Studio 349 | .localhistory/ 350 | 351 | # BeatPulse healthcheck temp database 352 | healthchecksdb 353 | 354 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 355 | MigrationBackup/ 356 | 357 | # Ionide (cross platform F# VS Code tools) working folder 358 | .ionide/ 359 | 360 | !ConfigCrypter.Tests/test-certificate.pfx 361 | 362 | ### VisualStudio ### 363 | 364 | # User-specific files 365 | 366 | # User-specific files (MonoDevelop/Xamarin Studio) 367 | 368 | # Mono auto generated files 369 | 370 | # Build results 371 | 372 | # Visual Studio 2015/2017 cache/options directory 373 | # Uncomment if you have tasks that create the project's static files in wwwroot 374 | 375 | # Visual Studio 2017 auto generated files 376 | 377 | # MSTest test Results 378 | 379 | # NUnit 380 | 381 | # Build Results of an ATL Project 382 | 383 | # Benchmark Results 384 | 385 | # .NET Core 386 | 387 | # StyleCop 388 | 389 | # Files built by Visual Studio 390 | 391 | # Chutzpah Test files 392 | 393 | # Visual C++ cache files 394 | 395 | # Visual Studio profiler 396 | 397 | # Visual Studio Trace Files 398 | 399 | # TFS 2012 Local Workspace 400 | 401 | # Guidance Automation Toolkit 402 | 403 | # ReSharper is a .NET coding add-in 404 | 405 | # TeamCity is a build add-in 406 | 407 | # DotCover is a Code Coverage Tool 408 | 409 | # AxoCover is a Code Coverage Tool 410 | 411 | # Coverlet is a free, cross platform Code Coverage Tool 412 | 413 | # Visual Studio code coverage results 414 | 415 | # NCrunch 416 | 417 | # MightyMoose 418 | 419 | # Web workbench (sass) 420 | 421 | # Installshield output folder 422 | 423 | # DocProject is a documentation generator add-in 424 | 425 | # Click-Once directory 426 | 427 | # Publish Web Output 428 | # Note: Comment the next line if you want to checkin your web deploy settings, 429 | # but database connection strings (with potential passwords) will be unencrypted 430 | 431 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 432 | # checkin your Azure Web App publish settings, but sensitive information contained 433 | # in these scripts will be unencrypted 434 | 435 | # NuGet Packages 436 | # NuGet Symbol Packages 437 | # The packages folder can be ignored because of Package Restore 438 | # except build/, which is used as an MSBuild target. 439 | # Uncomment if necessary however generally it will be regenerated when needed 440 | # NuGet v3's project.json files produces more ignorable files 441 | 442 | # Microsoft Azure Build Output 443 | 444 | # Microsoft Azure Emulator 445 | 446 | # Windows Store app package directories and files 447 | 448 | # Visual Studio cache files 449 | # files ending in .cache can be ignored 450 | # but keep track of directories ending in .cache 451 | 452 | # Others 453 | 454 | # Including strong name files can present a security risk 455 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 456 | 457 | # Since there are multiple workflows, uncomment next line to ignore bower_components 458 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 459 | 460 | # RIA/Silverlight projects 461 | 462 | # Backup & report files from converting an old project file 463 | # to a newer Visual Studio version. Backup files are not needed, 464 | # because we have git ;-) 465 | 466 | # SQL Server files 467 | 468 | # Business Intelligence projects 469 | 470 | # Microsoft Fakes 471 | 472 | # GhostDoc plugin setting file 473 | 474 | # Node.js Tools for Visual Studio 475 | 476 | # Visual Studio 6 build log 477 | 478 | # Visual Studio 6 workspace options file 479 | 480 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 481 | 482 | # Visual Studio LightSwitch build output 483 | 484 | # Paket dependency manager 485 | 486 | # FAKE - F# Make 487 | 488 | # CodeRush personal settings 489 | 490 | # Python Tools for Visual Studio (PTVS) 491 | 492 | # Cake - Uncomment if you are using it 493 | # tools/** 494 | # !tools/packages.config 495 | 496 | # Tabs Studio 497 | 498 | # Telerik's JustMock configuration file 499 | 500 | # BizTalk build output 501 | 502 | # OpenCover UI analysis results 503 | 504 | # Azure Stream Analytics local run output 505 | 506 | # MSBuild Binary and Structured Log 507 | 508 | # NVidia Nsight GPU debugger configuration file 509 | 510 | # MFractors (Xamarin productivity tool) working folder 511 | 512 | # Local History for Visual Studio 513 | 514 | # BeatPulse healthcheck temp database 515 | 516 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 517 | 518 | # Ionide (cross platform F# VS Code tools) working folder 519 | 520 | # End of https://www.toptal.com/developers/gitignore/api/visualstudio,csharp 521 | 522 | !cert.pfx 523 | -------------------------------------------------------------------------------- /ConfigCrypter.Console/ConfigCrypter.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | config-crypter 8 | ./nupkg 9 | logo.png 10 | DevAttic.ConfigCrypter.Console 11 | devattic 12 | DevAttic ConfigCrypter Console 13 | DevAttic ConfigCrypter Console is a dotnet tool used to encrypt and decrypt keys in configuration files. 14 | MIT 15 | 16 | https://github.com/devattic/ConfigCrypter 17 | DevAttic 18 | config appsettings encryption tool netcore 19 | 1.1.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | True 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ConfigCrypter.Console/Options/CommandlineOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace ConfigCrypter.Console.Options 4 | { 5 | public class CommandlineOptions 6 | { 7 | [Option('p', "path", Required = true, HelpText = "Path of the certificate.", Group = "CertLocation")] 8 | public string CertificatePath { get; set; } 9 | 10 | [Option('n', "name", Required = true, HelpText = "The subject name of the certificate (CN). This can only be used in Windows environments.", Group = "CertLocation")] 11 | public string CertSubjectName { get; set; } 12 | 13 | [Option('s', "password", Required = false, HelpText = "Password of the certificate (if available).", Default = null)] 14 | public string CertificatePassword { get; set; } 15 | 16 | [Option('k', "key", Required = true, HelpText = "The key to encrypt in the config file.")] 17 | public string Key { get; set; } 18 | 19 | [Option('f', "file", Required = true, HelpText = "The path to the config file.")] 20 | public string ConfigFile { get; set; } 21 | 22 | [Option('r', "replace", HelpText = "Replaces the original file if passed as parameter.", Default = false)] 23 | public bool Replace { get; set; } 24 | 25 | [Option("format", Default = ConfigFormat.Json, HelpText = "The format of the config file.")] 26 | public ConfigFormat ConfigFormat { get; set; } 27 | } 28 | 29 | public enum ConfigFormat 30 | { 31 | Json 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ConfigCrypter.Console/Options/DecryptOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace ConfigCrypter.Console.Options 4 | { 5 | [Verb("decrypt", HelpText = "Decrypts a key in the config file.")] 6 | class DecryptOptions : CommandlineOptions { } 7 | } 8 | -------------------------------------------------------------------------------- /ConfigCrypter.Console/Options/EncryptOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace ConfigCrypter.Console.Options 4 | { 5 | [Verb("encrypt", HelpText = "Encrypts a key in the config file.")] 6 | public class EncryptOptions : CommandlineOptions { } 7 | } 8 | -------------------------------------------------------------------------------- /ConfigCrypter.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using ConfigCrypter.Console.Options; 3 | using DevAttic.ConfigCrypter; 4 | using DevAttic.ConfigCrypter.CertificateLoaders; 5 | using DevAttic.ConfigCrypter.ConfigCrypters.Json; 6 | using DevAttic.ConfigCrypter.Crypters; 7 | 8 | namespace ConfigCrypter.Console 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | Parser.Default.ParseArguments(args) 15 | .WithParsed(opts => 16 | { 17 | var crypter = CreateCrypter(opts); 18 | crypter.EncryptKeyInFile(opts.ConfigFile, opts.Key); 19 | }) 20 | .WithParsed(opts => 21 | { 22 | var crypter = CreateCrypter(opts); 23 | crypter.DecryptKeyInFile(opts.ConfigFile, opts.Key); 24 | }); 25 | } 26 | 27 | private static ConfigFileCrypter CreateCrypter(CommandlineOptions options) 28 | { 29 | ICertificateLoader certLoader = null; 30 | 31 | if (!string.IsNullOrEmpty(options.CertificatePath)) 32 | { 33 | certLoader = new FilesystemCertificateLoader(options.CertificatePath, options.CertificatePassword); 34 | } 35 | else if (!string.IsNullOrEmpty(options.CertSubjectName)) 36 | { 37 | certLoader = new StoreCertificateLoader(options.CertSubjectName); 38 | } 39 | 40 | var configCrypter = new JsonConfigCrypter(new RSACrypter(certLoader)); 41 | 42 | var fileCrypter = new ConfigFileCrypter(configCrypter, new ConfigFileCrypterOptions() 43 | { 44 | ReplaceCurrentConfig = options.Replace 45 | }); 46 | 47 | return fileCrypter; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /ConfigCrypter.Console/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ConfigCrypter.Console": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /ConfigCrypter.Console/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/ConfigCrypter.Console/logo.png -------------------------------------------------------------------------------- /ConfigCrypter.Tests/ConfigCrypter.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | DevAttic.ConfigCrypter.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | all 36 | runtime; build; native; contentfiles; analyzers; buildtransitive 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers; buildtransitive 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/ConfigCrypters/Json/JsonConfigCrypterTests.cs: -------------------------------------------------------------------------------- 1 | using DevAttic.ConfigCrypter.ConfigCrypters.Json; 2 | using Newtonsoft.Json; 3 | using Xunit; 4 | 5 | namespace DevAttic.ConfigCrypter.Tests.ConfigCrypters.Json 6 | { 7 | public class JsonConfigCrypterTests 8 | { 9 | [Fact] 10 | public void EncryptKey_WithValidJson_CallsEncryptStringOnCrypter() 11 | { 12 | var crypterMock = Mocks.Crypter; 13 | var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); 14 | var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt" }); 15 | 16 | var encryptedJson = jsonCrypter.EncryptKey(json, "Key"); 17 | var parsedJson = JsonConvert.DeserializeObject(encryptedJson); 18 | 19 | crypterMock.Verify(crypter => crypter.EncryptString("ValueToEncrypt")); 20 | 21 | // Additionally we test if the test crypter does its job. 22 | Assert.Equal("ValueToEncrypt_encrypted", parsedJson.Key); 23 | } 24 | 25 | [Fact] 26 | public void DecryptKey_WithValidJson_CallsDecryptStringOnCrypter() 27 | { 28 | var crypterMock = Mocks.Crypter; 29 | var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); 30 | var json = JsonConvert.SerializeObject(new { Key = "ValueToEncrypt_encrypted" }); 31 | 32 | var decryptedJson = jsonCrypter.DecryptKey(json, "Key"); 33 | var parsedJson = JsonConvert.DeserializeObject(decryptedJson); 34 | 35 | crypterMock.Verify(crypter => crypter.DecryptString("ValueToEncrypt_encrypted")); 36 | Assert.Equal("ValueToEncrypt", parsedJson.Key); 37 | } 38 | 39 | [Fact] 40 | public void Dispose_CallsDisposeOnCrypter() 41 | { 42 | var crypterMock = Mocks.Crypter; 43 | var jsonCrypter = new JsonConfigCrypter(crypterMock.Object); 44 | 45 | jsonCrypter.Dispose(); 46 | 47 | crypterMock.Verify(crypter => crypter.Dispose()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/ConfigProviders/EncryptedJsonConfigSourceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DevAttic.ConfigCrypter.Extensions; 3 | using Microsoft.Extensions.Configuration; 4 | using Xunit; 5 | 6 | namespace DevAttic.ConfigCrypter.Tests.ConfigProviders 7 | { 8 | public class EncryptedJsonConfigSourceTests 9 | { 10 | [Fact] 11 | public void AddEncryptedAppSettings_DecryptsValuesOnTheFly() 12 | { 13 | var certLoaderMock = Mocks.CertificateLoader; 14 | var configBuilder = new ConfigurationBuilder(); 15 | configBuilder.AddEncryptedAppSettings(config => 16 | { 17 | config.KeysToDecrypt = new List { "Test:ToBeEncrypted" }; 18 | config.CertificateLoader = certLoaderMock.Object; 19 | }); 20 | var configuration = configBuilder.Build(); 21 | 22 | var decryptedValue = configuration["Test:ToBeEncrypted"]; 23 | 24 | Assert.Equal("This is going to be encrypted", decryptedValue); 25 | } 26 | 27 | [Fact] 28 | public void AddEncryptedJsonConfig_DecryptsValuesOnTheFly() 29 | { 30 | var certLoaderMock = Mocks.CertificateLoader; 31 | var configBuilder = new ConfigurationBuilder(); 32 | configBuilder.AddEncryptedJsonConfig(config => 33 | { 34 | config.KeysToDecrypt = new List { "KeyToEncrypt" }; 35 | config.CertificateLoader = certLoaderMock.Object; 36 | config.Path = "config.json"; 37 | }); 38 | var configuration = configBuilder.Build(); 39 | 40 | var decryptedValue = configuration["KeyToEncrypt"]; 41 | 42 | Assert.Equal("This will be encrypted.", decryptedValue); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/Crypters/RSACrypterTests.cs: -------------------------------------------------------------------------------- 1 | using DevAttic.ConfigCrypter.Crypters; 2 | using Xunit; 3 | 4 | namespace DevAttic.ConfigCrypter.Tests.Crypters 5 | { 6 | public class RSACrypterTests 7 | { 8 | [Fact] 9 | public void Constructor_CallsLoadCertificate() 10 | { 11 | var certificateLoaderMock = Mocks.CertificateLoader; 12 | 13 | var rsaCrypter = new RSACrypter(certificateLoaderMock.Object); 14 | 15 | certificateLoaderMock.Verify(loader => loader.LoadCertificate()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/Mocks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Security.Cryptography.X509Certificates; 5 | using DevAttic.ConfigCrypter.CertificateLoaders; 6 | using DevAttic.ConfigCrypter.Crypters; 7 | using Moq; 8 | 9 | namespace DevAttic.ConfigCrypter.Tests 10 | { 11 | public static class Mocks 12 | { 13 | public static Mock CertificateLoader 14 | { 15 | get 16 | { 17 | var certLoaderMock = new Mock(); 18 | certLoaderMock.Setup(loader => loader.LoadCertificate()).Returns(() => 19 | { 20 | using var certStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DevAttic.ConfigCrypter.Tests.test-certificate.pfx"); 21 | using var ms = new MemoryStream(); 22 | certStream!.CopyTo(ms); 23 | 24 | return new X509Certificate2(ms.ToArray()); 25 | }); 26 | 27 | return certLoaderMock; 28 | } 29 | } 30 | 31 | public static Mock Crypter 32 | { 33 | get 34 | { 35 | var crypterMock = new Mock(); 36 | crypterMock.Setup(crypter => crypter.EncryptString(It.IsAny())) 37 | .Returns(input => $"{input}_encrypted"); 38 | crypterMock.Setup(crypter => crypter.DecryptString(It.IsAny())) 39 | .Returns(input => 40 | { 41 | var encryptedIndex = input.LastIndexOf("_encrypted", StringComparison.Ordinal); 42 | 43 | return encryptedIndex > -1 44 | ? input.Substring(0, encryptedIndex) 45 | : input.Substring(0, input.Length); 46 | }); 47 | 48 | return crypterMock; 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/TestAppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace DevAttic.ConfigCrypter.Tests 2 | { 3 | public class TestAppSettings 4 | { 5 | public string Key { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Test": { 3 | "Encrypted": "", 4 | "ToBeEncrypted": "lCZp1TKiMk5zp+nMKs9FAmpzkrfrtB9mvHKg3FRNXpT0VNHx1CurOji2va3k159/LZY80/mYe1Crmh38WFZjxCuASinFHjsB3U0REmMmazsFr04qnxMRYDS+nOwphIK+mao0YNKajLq3WPh6voIFt0F8KBmYetSlGE3u6oztzDvalzDHQqupkI5qc/yFLdOHOfzpn5QkyE92kSXGiZyCkNkM/SJLJhlzf7SB538U2d7VGswy+3iJC0Rv10gjjHF0uzywIv8pxdpDd9rhSkS6x4x1XsDyUV/132Q0QGfBwvPramNrgbkgIePxwudTnrMQkaphblkyKOeThJHMUS2u1g==" 5 | } 6 | } -------------------------------------------------------------------------------- /ConfigCrypter.Tests/appsettings_decrypted.json: -------------------------------------------------------------------------------- 1 | { 2 | "Test": { 3 | "Encrypted": "", 4 | "ToBeEncrypted": "This is going to be encrypted" 5 | } 6 | } -------------------------------------------------------------------------------- /ConfigCrypter.Tests/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "KeyToEncrypt": "fvQIaoOW4jVGzNUs7QilDFSBWtjACTdxNTA/d8xF6MIwuKVjhkBKBAGL+FIXk+vA9YRqfLid/OvucaH3Bhs/9ur+9/dKGuCklFRYCWEo9sQoihBHDYZ/67YSCOVzZp2zvtJI3ZjRwefjHpOqSOjcAmgZQYF519tbLzLQOUK0DOEzC6hP+1pMt6qgVYBi1F0/gjUaqXSsiwmkZFmu1rgA9LfRY6RRi+Cy4OVfcXON4tYwiyAyYQ59bT2eggv5DqlrKGLYAZOHpmaCasjxMbyYuFrIg/uehErVtSMMVSq5qdaysxo7wFNuqbCrC/jPNkCMKE+c1+9JgVM52bmU01eg9A==" 3 | } -------------------------------------------------------------------------------- /ConfigCrypter.Tests/config_decrypted.json: -------------------------------------------------------------------------------- 1 | { 2 | "KeyToEncrypt": "This will be encrypted." 3 | } 4 | -------------------------------------------------------------------------------- /ConfigCrypter.Tests/test-certificate.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/ConfigCrypter.Tests/test-certificate.pfx -------------------------------------------------------------------------------- /ConfigCrypter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30309.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCrypter.Tests", "ConfigCrypter.Tests\ConfigCrypter.Tests.csproj", "{BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCrypter.Console", "ConfigCrypter.Console\ConfigCrypter.Console.csproj", "{A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCrypter", "ConfigCrypter\ConfigCrypter.csproj", "{AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.WebApp", "Example.WebApp\Example.WebApp.csproj", "{88CE2CCB-5A74-4400-8B00-04B1E10DE540}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {BF324722-EE3E-4143-8BCD-5FED5BBA04FD} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /ConfigCrypter/CertificateLoaders/FilesystemCertificateLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace DevAttic.ConfigCrypter.CertificateLoaders 4 | { 5 | /// 6 | /// Loader that loads a certificate from the filesystem. 7 | /// 8 | public class FilesystemCertificateLoader : ICertificateLoader 9 | { 10 | private readonly string _certificatePath; 11 | private readonly string _certificatePassword; 12 | 13 | /// 14 | /// Creates an instance of the certificate loader. 15 | /// 16 | /// Fully qualified path to the certificate (.pfx file). 17 | /// Password of the certificate, if available. 18 | public FilesystemCertificateLoader(string certificatePath, string certificatePassword = null) 19 | { 20 | _certificatePath = certificatePath; 21 | _certificatePassword = certificatePassword; 22 | } 23 | 24 | /// 25 | /// Loads a certificate from the given location on the filesystem. 26 | /// 27 | /// A X509Certificate2 instance. 28 | public X509Certificate2 LoadCertificate() 29 | { 30 | return string.IsNullOrEmpty(_certificatePassword) ? 31 | new X509Certificate2(_certificatePath) : 32 | new X509Certificate2(_certificatePath, _certificatePassword); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /ConfigCrypter/CertificateLoaders/ICertificateLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace DevAttic.ConfigCrypter.CertificateLoaders 4 | { 5 | /// 6 | /// Responsible for loading a certificate. 7 | /// 8 | /// Custom certificate loaders can be implemented by implementing this interface. 9 | public interface ICertificateLoader 10 | { 11 | /// 12 | /// Loads a certificate. 13 | /// 14 | /// A X509Certificate2 instance. 15 | X509Certificate2 LoadCertificate(); 16 | } 17 | } -------------------------------------------------------------------------------- /ConfigCrypter/CertificateLoaders/StoreCertificateLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Security.Cryptography.X509Certificates; 3 | 4 | namespace DevAttic.ConfigCrypter.CertificateLoaders 5 | { 6 | /// 7 | /// Loader that loads a certificate from the Windows certificate store. 8 | /// 9 | public class StoreCertificateLoader : ICertificateLoader 10 | { 11 | private readonly string _subjectName; 12 | 13 | public StoreCertificateLoader(string subjectName) 14 | { 15 | _subjectName = subjectName; 16 | } 17 | 18 | /// 19 | /// Loads a certificate by subject name from the store. 20 | /// 21 | /// A X509Certificate2 instance. 22 | /// The loader looks for the certificate in the own certificates of the local machine store. It uses the FindBySubjectName find type. 23 | public X509Certificate2 LoadCertificate() 24 | { 25 | using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) 26 | { 27 | store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); 28 | 29 | var certs = store.Certificates.Find(X509FindType.FindBySubjectName, _subjectName, false); 30 | var cert = certs.Cast().FirstOrDefault(); 31 | store.Close(); 32 | 33 | return cert; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /ConfigCrypter/ConfigCrypter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | DevAttic.ConfigCrypter 6 | devattic 7 | DevAttic 8 | DevAttic ConfigCrypter 9 | DevAttic.ConfigCrypter 10 | DevAttic.ConfigCrypter 11 | logo.png 12 | DevAttic ConfigCrypter is a library that enables the user to work with decrypted configuration files. 13 | 14 | https://github.com/devattic/ConfigCrypter 15 | netcore aspnetcore netstandard config appsettings encryption decryption 16 | MIT 17 | 1.1.0.0 18 | 1.1.0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | True 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ConfigCrypter/ConfigCrypters/IConfigCrypter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DevAttic.ConfigCrypter.ConfigCrypters 4 | { 5 | /// 6 | /// Encrypts/Decrypts keys in configuration files. 7 | /// 8 | public interface IConfigCrypter : IDisposable 9 | { 10 | /// 11 | /// Decrypts the key in the given content of a config file. 12 | /// 13 | /// String content of a config file. 14 | /// Key of the config entry. 15 | /// The content of the config file where the key has been decrypted. 16 | /// It up to the implementer how to interpret the format of the config key. 17 | string DecryptKey(string configFileContent, string configKey); 18 | 19 | /// 20 | /// Encrypts the key in the given content of a config file. 21 | /// 22 | /// String content of a config file. 23 | /// Key of the config entry. 24 | /// The content of the config file where the key has been encrypted. 25 | /// It up to the implementer how to interpret the format of the config key. 26 | string EncryptKey(string configFileContent, string configKey); 27 | } 28 | } -------------------------------------------------------------------------------- /ConfigCrypter/ConfigCrypters/Json/JsonConfigCrypter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DevAttic.ConfigCrypter.Crypters; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace DevAttic.ConfigCrypter.ConfigCrypters.Json 7 | { 8 | /// 9 | /// Config crypter that encrypts and decrypts keys in JSON config files. 10 | /// 11 | public class JsonConfigCrypter : IConfigCrypter 12 | { 13 | private readonly ICrypter _crypter; 14 | 15 | /// 16 | /// Creates an instance of the JsonConfigCrypter. 17 | /// 18 | /// An ICrypter instance. 19 | public JsonConfigCrypter(ICrypter crypter) 20 | { 21 | _crypter = crypter; 22 | } 23 | 24 | /// 25 | /// Decrypts the key in the given content of a config file. 26 | /// 27 | /// String content of a config file. 28 | /// Key of the config entry. The key has to be in JSONPath format. 29 | /// The content of the config file where the key has been decrypted. 30 | public string DecryptKey(string configFileContent, string configKey) 31 | { 32 | var (parsedConfig, settingsToken) = ParseConfig(configFileContent, configKey); 33 | 34 | var encryptedValue = _crypter.DecryptString(settingsToken.Value()); 35 | settingsToken.Replace(encryptedValue); 36 | var newConfigContent = parsedConfig.ToString(Formatting.Indented); 37 | 38 | return newConfigContent; 39 | } 40 | 41 | public void Dispose() 42 | { 43 | Dispose(true); 44 | GC.SuppressFinalize(this); 45 | } 46 | 47 | /// 48 | /// Encrypts the key in the given content of a config file. 49 | /// 50 | /// String content of a config file. 51 | /// Key of the config entry. The key has to be in JSONPath format. 52 | /// The content of the config file where the key has been encrypted. 53 | public string EncryptKey(string configFileContent, string configKey) 54 | { 55 | var (parsedConfig, settingsToken) = ParseConfig(configFileContent, configKey); 56 | 57 | var encryptedValue = _crypter.EncryptString(settingsToken.Value()); 58 | settingsToken.Replace(encryptedValue); 59 | var newConfigContent = parsedConfig.ToString(Formatting.Indented); 60 | 61 | return newConfigContent; 62 | } 63 | 64 | protected virtual void Dispose(bool disposing) 65 | { 66 | if (disposing) 67 | { 68 | _crypter?.Dispose(); 69 | } 70 | } 71 | 72 | private (JObject ParsedConfig, JToken Key) ParseConfig(string json, string configKey) 73 | { 74 | var parsedJson = JObject.Parse(json); 75 | var keyToken = parsedJson.SelectToken(configKey); 76 | 77 | if (keyToken == null) 78 | { 79 | throw new InvalidOperationException($"The key {configKey} could not be found."); 80 | } 81 | 82 | return (parsedJson, keyToken); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /ConfigCrypter/ConfigFileCrypter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using DevAttic.ConfigCrypter.ConfigCrypters; 3 | 4 | namespace DevAttic.ConfigCrypter 5 | { 6 | /// 7 | /// Configuration crypter that reads the configuration file from the filesystem. 8 | /// 9 | public class ConfigFileCrypter 10 | { 11 | private readonly IConfigCrypter _configCrypter; 12 | private readonly ConfigFileCrypterOptions _options; 13 | 14 | /// 15 | /// Creates an instance of the ConfigFileCrypter. 16 | /// 17 | /// A config crypter instance. 18 | /// Options used for encrypting and decrypting. 19 | public ConfigFileCrypter(IConfigCrypter configCrypter, ConfigFileCrypterOptions options) 20 | { 21 | _configCrypter = configCrypter; 22 | _options = options; 23 | } 24 | 25 | /// 26 | /// Decrypts the given key in the config file. 27 | /// 28 | /// If the "ReplaceCurrentConfig" setting has been set in the options the file is getting replaced. 29 | /// If the setting has not been set a new file with the "DecryptedConfigPostfix" appended to the current file name will be created. 30 | /// 31 | /// Path of the configuration file. 32 | /// Key to decrypt, passed in a format the underlying config crypter understands. 33 | public void DecryptKeyInFile(string filePath, string configKey) 34 | { 35 | var configContent = File.ReadAllText(filePath); 36 | var decryptedConfigContent = _configCrypter.DecryptKey(configContent, configKey); 37 | 38 | var targetFilePath = GetDestinationConfigPath(filePath, _options.DecryptedConfigPostfix); 39 | File.WriteAllText(targetFilePath, decryptedConfigContent); 40 | } 41 | 42 | /// 43 | /// Encrypts the given key in the config file. 44 | /// 45 | /// If the "ReplaceCurrentConfig" setting has been set in the options the file is getting replaced. 46 | /// If the setting has not been set a new file with the "EncryptedConfigPostfix" appended to the current file name will be created. 47 | /// 48 | /// Path of the configuration file. 49 | /// Key to encrypt, passed in a format the underlying config crypter understands. 50 | public void EncryptKeyInFile(string filePath, string configKey) 51 | { 52 | var configContent = File.ReadAllText(filePath); 53 | var encryptedConfigContent = _configCrypter.EncryptKey(configContent, configKey); 54 | 55 | var targetFilePath = GetDestinationConfigPath(filePath, _options.EncryptedConfigPostfix); 56 | File.WriteAllText(targetFilePath, encryptedConfigContent); 57 | } 58 | 59 | private string GetDestinationConfigPath(string currentConfigFilePath, string postfix) 60 | { 61 | if (_options.ReplaceCurrentConfig) 62 | { 63 | return currentConfigFilePath; 64 | } 65 | 66 | var currentConfigDirectory = Path.GetDirectoryName(currentConfigFilePath); 67 | var newFilename = 68 | $"{Path.GetFileNameWithoutExtension(currentConfigFilePath)}{postfix}{Path.GetExtension(currentConfigFilePath)}"; 69 | var targetFile = Path.Combine(currentConfigDirectory, newFilename); 70 | 71 | return targetFile; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /ConfigCrypter/ConfigFileCrypterOptions.cs: -------------------------------------------------------------------------------- 1 | namespace DevAttic.ConfigCrypter 2 | { 3 | /// 4 | /// Options to configure the ConfigFileCrypter. 5 | /// 6 | public class ConfigFileCrypterOptions 7 | { 8 | /// 9 | /// Name of the postfix that should be appended when a file has been decrypted and "ReplaceCurrentConfig" is set to true. 10 | /// 11 | public string DecryptedConfigPostfix { get; set; } = "_decrypted"; 12 | /// 13 | /// Name of the postfix that should be appended when a file has been encrypted and "ReplaceCurrentConfig" is set to true. 14 | /// 15 | public string EncryptedConfigPostfix { get; set; } = "_encrypted"; 16 | /// 17 | /// Defines if the original config file should be overriden or a new file should be created. 18 | /// 19 | public bool ReplaceCurrentConfig { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /ConfigCrypter/ConfigProviders/Json/EncryptedJsonConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration.Json; 2 | 3 | namespace DevAttic.ConfigCrypter.ConfigProviders.Json 4 | { 5 | /// 6 | /// JSON configuration provider that uses the underlying crypter to decrypt the given keys. 7 | /// 8 | public class EncryptedJsonConfigProvider : JsonConfigurationProvider 9 | { 10 | private readonly EncryptedJsonConfigSource _jsonConfigSource; 11 | 12 | /// 13 | /// Creates an instance of the EncryptedJsonConfigProvider. 14 | /// 15 | /// EncryptedJsonConfigSource that is used to configure the provider. 16 | public EncryptedJsonConfigProvider(EncryptedJsonConfigSource source) : base(source) 17 | { 18 | _jsonConfigSource = source; 19 | } 20 | 21 | /// 22 | /// Loads the JSON configuration file and decrypts all configured keys with the given crypter. 23 | /// 24 | public override void Load() 25 | { 26 | base.Load(); 27 | 28 | using (var crypter = _jsonConfigSource.CrypterFactory(_jsonConfigSource)) 29 | { 30 | foreach (var key in _jsonConfigSource.KeysToDecrypt) 31 | { 32 | if (Data.TryGetValue(key, out var encryptedValue)) 33 | { 34 | Data[key] = crypter.DecryptString(encryptedValue); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /ConfigCrypter/ConfigProviders/Json/EncryptedJsonConfigSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DevAttic.ConfigCrypter.CertificateLoaders; 4 | using DevAttic.ConfigCrypter.Crypters; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Configuration.Json; 7 | 8 | namespace DevAttic.ConfigCrypter.ConfigProviders.Json 9 | { 10 | /// 11 | /// ConfigurationSource for encrypted JSON config files. 12 | /// 13 | public class EncryptedJsonConfigSource : JsonConfigurationSource 14 | { 15 | /// 16 | /// A certificate loader instance. Custom loaders can be used. 17 | /// 18 | public ICertificateLoader CertificateLoader { get; set; } 19 | /// 20 | /// The fully qualified path of the certificate. 21 | /// 22 | public string CertificatePath { get; set; } 23 | /// 24 | /// The subject name of the certificate (Issued for). 25 | /// 26 | public string CertificateSubjectName { get; set; } 27 | /// 28 | /// The password of the certificate or null, if the certificate has no password. 29 | /// 30 | public string CertificatePassword { get; set; } = null; 31 | /// 32 | /// Factory function that is used to create an instance of the crypter. 33 | /// The default factory uses the RSACrypter and passes it the given certificate loader. 34 | /// 35 | public Func CrypterFactory { get; set; } = 36 | cfg => new RSACrypter(cfg.CertificateLoader); 37 | 38 | /// 39 | /// List of keys that should be decrypted. Hierarchical keys need to be separated by colon. 40 | /// Example: "Nested:Key" 41 | /// 42 | public List KeysToDecrypt { get; set; } = new List(); 43 | 44 | public EncryptedJsonConfigSource() 45 | { 46 | ReloadOnChange = true; 47 | } 48 | 49 | /// 50 | /// Creates an instance of the EncryptedJsonConfigProvider. 51 | /// 52 | /// IConfigurationBuilder instance. 53 | /// An EncryptedJsonConfigProvider instance. 54 | public override IConfigurationProvider Build(IConfigurationBuilder builder) 55 | { 56 | base.Build(builder); 57 | return new EncryptedJsonConfigProvider(this); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /ConfigCrypter/Crypters/ICrypter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DevAttic.ConfigCrypter.Crypters 4 | { 5 | /// 6 | /// A crypter that is used to encrypt and decrypt simple strings. 7 | /// 8 | public interface ICrypter : IDisposable 9 | { 10 | /// 11 | /// Decrypts the given string. 12 | /// 13 | /// String to decrypt. 14 | /// Encrypted string. 15 | string DecryptString(string value); 16 | 17 | /// 18 | /// Encrypts the given string. 19 | /// 20 | /// String to encrypt. 21 | /// Encrypted string. 22 | string EncryptString(string value); 23 | } 24 | } -------------------------------------------------------------------------------- /ConfigCrypter/Crypters/RSACrypter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Security.Cryptography.X509Certificates; 4 | using System.Text; 5 | using DevAttic.ConfigCrypter.CertificateLoaders; 6 | 7 | namespace DevAttic.ConfigCrypter.Crypters 8 | { 9 | /// 10 | /// RSA based crypter that uses the public and private key of a certificate to encrypt and decrypt strings. 11 | /// 12 | public class RSACrypter : ICrypter 13 | { 14 | private readonly ICertificateLoader _certificateLoader; 15 | private RSA _privateKey; 16 | private RSA _publicKey; 17 | 18 | /// 19 | /// Creates an instance of the RSACrypter. 20 | /// 21 | /// A certificate loader instance. 22 | public RSACrypter(ICertificateLoader certificateLoader) 23 | { 24 | _certificateLoader = certificateLoader; 25 | InitKeys(); 26 | } 27 | 28 | /// 29 | /// Encrypts the given string with the private key of the loaded certificate. 30 | /// 31 | /// String to decrypt. 32 | /// Encrypted string. 33 | public string DecryptString(string value) 34 | { 35 | var decodedBase64 = Convert.FromBase64String(value); 36 | var decryptedValue = _privateKey.Decrypt(decodedBase64, RSAEncryptionPadding.OaepSHA256); 37 | 38 | return Encoding.UTF8.GetString(decryptedValue); 39 | } 40 | 41 | /// 42 | /// Disposes the instance. 43 | /// 44 | public void Dispose() 45 | { 46 | Dispose(true); 47 | GC.SuppressFinalize(this); 48 | } 49 | 50 | /// 51 | /// Decrypts the given string with the public key of the loaded certificate. 52 | /// 53 | /// String to encrypt. 54 | /// Encrypted string. 55 | public string EncryptString(string value) 56 | { 57 | var encryptedValue = _publicKey.Encrypt(Encoding.UTF8.GetBytes(value), RSAEncryptionPadding.OaepSHA256); 58 | 59 | return Convert.ToBase64String(encryptedValue); 60 | } 61 | 62 | /// 63 | /// Disposes the underlying keys. 64 | /// 65 | /// True if called from user code, false if called by finalizer. 66 | protected virtual void Dispose(bool disposing) 67 | { 68 | if (disposing) 69 | { 70 | _privateKey?.Dispose(); 71 | _publicKey?.Dispose(); 72 | } 73 | } 74 | 75 | private void InitKeys() 76 | { 77 | using (var certificate = _certificateLoader.LoadCertificate()) 78 | { 79 | _privateKey = certificate.GetRSAPrivateKey(); 80 | _publicKey = certificate.GetRSAPublicKey(); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /ConfigCrypter/Extensions/ConfigurationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using DevAttic.ConfigCrypter.CertificateLoaders; 2 | using DevAttic.ConfigCrypter.ConfigProviders.Json; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Hosting; 5 | using System; 6 | 7 | namespace DevAttic.ConfigCrypter.Extensions 8 | { 9 | public static class ConfigurationBuilderExtensions 10 | { 11 | /// 12 | /// Adds a provider to decrypt keys in the appsettings.json file. 13 | /// 14 | /// A ConfigurationBuilder instance. 15 | /// An action used to configure the configuration source. 16 | /// The current ConfigurationBuilder instance. 17 | public static IConfigurationBuilder AddEncryptedAppSettings( 18 | this IConfigurationBuilder builder, Action configAction) 19 | 20 | { 21 | if (builder is null) 22 | { 23 | throw new ArgumentNullException(nameof(builder)); 24 | } 25 | 26 | var configSource = new EncryptedJsonConfigSource { Path = "appsettings.json" }; 27 | configAction?.Invoke(configSource); 28 | 29 | return AddEncryptedJsonConfig(builder, configSource); 30 | } 31 | 32 | /// 33 | /// Adds a provider to decrypt keys in the appsettings.json and the corresponding environment appsettings files. 34 | /// 35 | /// A ConfigurationBuilder instance. 36 | /// An action used to configure the configuration source. 37 | /// The current host environment. Used to add environment specific appsettings files. (appsettings.Development.json, appsettings.Production.json) 38 | /// The current ConfigurationBuilder instance. 39 | public static IConfigurationBuilder AddEncryptedAppSettings( 40 | this IConfigurationBuilder builder, IHostEnvironment hostEnvironment, Action configAction) 41 | { 42 | if (builder is null) 43 | { 44 | throw new ArgumentNullException(nameof(builder)); 45 | } 46 | 47 | var appSettingSource = new EncryptedJsonConfigSource { Path = "appsettings.json" }; 48 | var environmentSource = new EncryptedJsonConfigSource { Path = $"appsettings.{hostEnvironment.EnvironmentName}.json", Optional = true }; 49 | configAction?.Invoke(appSettingSource); 50 | configAction?.Invoke(environmentSource); 51 | 52 | AddEncryptedJsonConfig(builder, appSettingSource); 53 | AddEncryptedJsonConfig(builder, environmentSource); 54 | 55 | return builder; 56 | } 57 | 58 | /// 59 | /// Adds a provider to decrypt keys in the given json config file. 60 | /// 61 | /// A ConfigurationBuilder instance. 62 | /// An action used to configure the configuration source. 63 | /// The current ConfigurationBuilder instance. 64 | public static IConfigurationBuilder AddEncryptedJsonConfig( 65 | this IConfigurationBuilder builder, Action configAction) 66 | 67 | { 68 | if (builder is null) 69 | { 70 | throw new ArgumentNullException(nameof(builder)); 71 | } 72 | 73 | var configSource = new EncryptedJsonConfigSource(); 74 | configAction?.Invoke(configSource); 75 | 76 | InitializeCertificateLoader(configSource); 77 | 78 | builder.Add(configSource); 79 | return builder; 80 | } 81 | 82 | /// 83 | /// Adds a provider to decrypt keys in the given json config file by using the passed EncryptedJsonConfigSource. 84 | /// 85 | /// A ConfigurationBuilder instance. 86 | /// The fully configured config source. 87 | /// The current ConfigurationBuilder instance. 88 | public static IConfigurationBuilder AddEncryptedJsonConfig(this IConfigurationBuilder builder, EncryptedJsonConfigSource configSource) 89 | { 90 | InitializeCertificateLoader(configSource); 91 | builder.Add(configSource); 92 | 93 | return builder; 94 | } 95 | 96 | private static void InitializeCertificateLoader(EncryptedJsonConfigSource config) 97 | { 98 | if (!string.IsNullOrEmpty(config.CertificatePath)) 99 | { 100 | config.CertificateLoader = new FilesystemCertificateLoader(config.CertificatePath, config.CertificatePassword); 101 | } 102 | else if (!string.IsNullOrEmpty(config.CertificateSubjectName)) 103 | { 104 | config.CertificateLoader = new StoreCertificateLoader(config.CertificateSubjectName); 105 | } 106 | 107 | if (config.CertificateLoader == null) 108 | { 109 | throw new InvalidOperationException( 110 | "Either CertificatePath or CertificateSubjectName has to be provided if CertificateLoader has not been set manually."); 111 | } 112 | 113 | if (string.IsNullOrEmpty(config.Path)) 114 | { 115 | throw new InvalidOperationException( 116 | "The \"Path\" property has to be set to the path of a config file."); 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /ConfigCrypter/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ConfigFileEncrypter": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /ConfigCrypter/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/ConfigCrypter/logo.png -------------------------------------------------------------------------------- /Example.WebApp/Controllers/ConfigController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Example.WebApp.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ConfigController : ControllerBase 10 | { 11 | private readonly IConfiguration _configuration; 12 | private readonly NestedSettings _settingsSnapshot; 13 | 14 | public ConfigController(IConfiguration configuration, IOptionsSnapshot settingsSnapshot) 15 | { 16 | _configuration = configuration; 17 | _settingsSnapshot = settingsSnapshot.Value; 18 | } 19 | 20 | [HttpGet] 21 | public IActionResult GetEncryptedKey() 22 | { 23 | var content = new 24 | { 25 | FromSnapshot = _settingsSnapshot.KeyToEncrypt, 26 | FromConfiguration = _configuration["Nested:KeyToEncrypt"] 27 | }; 28 | 29 | return Ok(content); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example.WebApp/Example.WebApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Example.WebApp/NestedSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Example.WebApp 2 | { 3 | public class NestedSettings 4 | { 5 | public string KeyToEncrypt { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Example.WebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DevAttic.ConfigCrypter.Extensions; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace Example.WebApp 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | CreateHostBuilder(args).Build().Run(); 14 | } 15 | 16 | public static IHostBuilder CreateHostBuilder(string[] args) => 17 | Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder.UseStartup(); 21 | }) 22 | .ConfigureAppConfiguration((hostingContext, cfg) => 23 | { 24 | cfg.AddEncryptedAppSettings(hostingContext.HostingEnvironment, crypter => 25 | { 26 | crypter.CertificatePath = "cert.pfx"; 27 | crypter.KeysToDecrypt = new List { "Nested:KeyToEncrypt" }; 28 | }); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55213", 7 | "sslPort": 0 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "weatherforecast", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Example.WebApp": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/config", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "http://localhost:5000" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Example.WebApp/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace Example.WebApp 8 | { 9 | public class Startup 10 | { 11 | public Startup(IConfiguration configuration) 12 | { 13 | Configuration = configuration; 14 | } 15 | 16 | public IConfiguration Configuration { get; } 17 | 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | public void ConfigureServices(IServiceCollection services) 20 | { 21 | services.AddControllers(); 22 | services.Configure(Configuration.GetSection("Nested")); 23 | } 24 | 25 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 26 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 27 | { 28 | if (env.IsDevelopment()) 29 | { 30 | app.UseDeveloperExceptionPage(); 31 | } 32 | 33 | app.UseRouting(); 34 | 35 | app.UseAuthorization(); 36 | 37 | app.UseEndpoints(endpoints => 38 | { 39 | endpoints.MapControllers(); 40 | }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example.WebApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "Nested": { 10 | "KeyToEncrypt": "jQ42kYnVFiu2Fpod4jnaHWfDjhFbxqucGLlUi5HqSnNAVDhwwFLAwvoBQVLfwpN1TC8WBwbWynN2Ej7EO2uRA+cNZUCGxFj+LY+YGJnwT8u1a59gG4QDKgUHp53KtU/UoTWNxPiXWCCRoumvZt1vkPVzO9qYlhRCR6UAPMHQc6lu8UdakW8uCU1sitvtLOdiVGvIk3yLOFg3iJ4XfGn+f8Myvu773v9jLM6541x5Jsyv7mFyNklVBnihwsyBg3WzpjYicms2ioxWBsU+nPGZbX8fyzLTBVvEa1WxyAg8M2IbetdREEl5zwwnI0Ak1MR/lhXN9s9eks1paYloKh9lxg==" 11 | } 12 | } -------------------------------------------------------------------------------- /Example.WebApp/appsettings.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | "Nested": { 3 | "KeyToEncrypt": "lFd454PZda3cx/d5YUEKw/dt4yy0rN08niRqCIAmVU8vmPufuhkrTF1K4eyZGQAo1H8UsiQxQO+7CvOSEgODznm6hcO4TofOlyMbiBR/1xswZ0QVFtOpN6JWOtdFJcu/ROrV+T0jl/dcfB5JRLFSJvdsJRMPpwOCrkPeKJ9sQftoOt6V3M258lV5bItdwdYfqCUPS7lq1VPeT4i4HM9ZxqlNU3BxAaAm4YkyXIVpTvMwrvUU2QO7/jbASpWdRxBa1l9FHamhYbMcePinjj3v16MjptYyHl84Wc6XZBBXFm11QHTEAOcFmGEsoxrbDEB6ipGsC+xUoy6IwLtmOxpDTw==" 4 | } 5 | } -------------------------------------------------------------------------------- /Example.WebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Nested": { 11 | "KeyToEncrypt": "XPTvTMWCczyrVC3ZcyH3ZC91ueBI1a2FFjrs8pwDC47HRriqdV4tUthERw2WEji3XGQ5rJAy2J0fJz9GRUdxZ8TvSp2LqZi50tns7YkL1HLNVLG+h0BBOzOV9zW4EXZKyNgsjLCsnEQplqpe9dTKcRZLg+ATT17A3C92GP9TO4eoyAPQu5iBt/VVckjM/Cd727yfSBoMlgTx4GeUI+je+aWDSqCdC9m+Cn7wROaK3hp1CVtOsnOqAzwQUKychewMw9VNbzHxBgNaSQ3IqeVFwtL1G2vvE9/+MudXqhTUSRO2Nv7bcva9xPzybdKPUy4u7OIRM8F+WRGieP01nYU79A==" 12 | } 13 | } -------------------------------------------------------------------------------- /Example.WebApp/cert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/Example.WebApp/cert.pfx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 devattic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevAttic ConfigCrypter 2 | ![DevAttic ConfigCrypter Logo](devattic_configcrypter-normal256.png "DevAttic ConfigCrypter Logo") 3 | 4 | ## What is DevAttic ConfigCrypter 5 | The DevAttic ConfigCrypter makes it easy for you to encrypt and decrypt config files. It consists of two parts. A command line utility that lets you encrypt keys in your JSON configuration files and a library that decrypts them on the fly in your .NET Standard applications. 6 | 7 | ## WARNING 8 | Encrypted configuration files will not make your server infrastructure unhackable. Usually the certificate to decrypt is hosted on the same server as your web application. This means an attacker could decrypt your config if your server is not secure and the attacker gains access. 9 | 10 | Additional security could be achieved by: 11 | - Storing your certficate in the windows certificate store (supported by ConfigCrypter) and restricting access to it. 12 | - Protect your certificate with a password that is embedded in your source code (currently not supported, but could be easily implemented). 13 | 14 | Also these methods would not be perfectly safe. In fact it only makes things harder for an attacker. 15 | 16 | Other possibilities would be using Environment variables or the Secret Manager tool, but in these variants the settings are completely unencrypted. For production scenarios Microsoft recommends using the Azure Key Vault, but also this is not perfectly safe and forces you to use Azure. 17 | 18 | It is definitely required to secure your server infrastructure, as this is the only way to protect your connection strings and other sensitive configuration values. 19 | 20 | ## Advantages 21 | - Lets you encrypt only certain keys in your config, so the rest of the config is still readable. 22 | - Access the encrypted values the same way you are used to in your .NET Core applications. 23 | - Lets you share config files or even check them in in your VCS without the need to remove sensitive information. 24 | 25 | ## Command line tool usage 26 | To use DevAttic ConfigCrypter you will first need to create a self signed X509 certificate that is being used for the encryption and decryption. An easy way to do this is being described in this guide: [Link](https://www.claudiobernasconi.ch/2016/04/17/creating-a-self-signed-x509-certificate-using-openssl-on-windows/) 27 | 28 | In fact you can follow every guide as long as the result is a certificate in .pfx format containing a public and private key. 29 | 30 | If you now have your certificate you need to decide what keys you want to encrypt. Lets assume we have a JSON file that looks like this: 31 | 32 | ```json 33 | { 34 | "Nested": { 35 | "KeyToEncrypt": "This will be encrypted" 36 | } 37 | } 38 | ``` 39 | 40 | We want to encrypt the value with the key `Nested.KeyToEncrypt`. Notice the separation of the keys with a dot. How the key is interprated and what kind of syntax is used to define your key is up to the IConfigCrypter implementation. 41 | 42 | Currently only JSON is supported and the `JsonConfigCrypter` is using the JSONPath Syntax to define your keys ([Link](https://goessner.net/articles/JsonPath/)). Altough JSONPath usually needs a $ to define the root of the object you can leave it out here. 43 | 44 | To install the crypter [command line utility](https://www.nuget.org/packages/DevAttic.ConfigCrypter.Console/) just execute `dotnet tool install -g DevAttic.ConfigCrypter.Console`. After that you can use it with the command `config-crypter` from your command line. 45 | 46 | To encrypt our key from above we simple execute: 47 | `config-crypter encrypt -p c:\path\to\cert.pfx -f c:\path\to\config.json -k "Nested.KeyToEncrypt"`. 48 | After that a new files named `config_encrypted.json` should be created at the same folder as your original config file. This file is now the same as the original one except for the fact that the value for the passed key has been encrypted. 49 | 50 | If you want to prevent the creation of a new file you can simply pass --replace (-r) as additional paramter to the command and the original file will be replaced. 51 | 52 | To decrypt the file again you can simply execute: 53 | `config-crypter decrypt -p c:\path\to\cert.pfx -f c:\path\to\config_encrypted.json -k "Nested.KeyToEncrypt"` 54 | 55 | ## Command line arguments 56 | The following command line arguments can be passed for the encrypt and decrypt command. 57 | ``` 58 | -p, --path (Group: CertLocation) Path of the certificate. 59 | -n, --name (Group: CertLocation) The subject name of the certificate (CN). This can only be used in Windows environments. 60 | -k, --key Required. The key to encrypt in the config file. 61 | -f, --file Required. The path to the config file. 62 | -r, --replace (Default: false) Replaces the original file if passed as parameter. 63 | --format (Default: Json) The format of the config file. 64 | --help Display this help screen. 65 | --version Display version information. 66 | ``` 67 | 68 | ## .NET Core integration 69 | Install the nuget package [DevAttic.ConfigCrypter](https://www.nuget.org/packages/DevAttic.ConfigCrypter/) 70 | 71 | To use your encrypted configuration file in your .NET Core applications, DevAttic ConfigCrypter provides convenient extension methods to register the needed configuration providers. 72 | 73 | There are two extension methods that can be used to integrate encrypted configuration files. 74 | 75 | - `AddEncryptedAppSettings`: Adds a configuration provider that decrypts certain keys of the appsettings.json file. 76 | - `AddEncryptedJsonConfig`: Adds a configuration provider that decrypts certain keys of a given JSON configuration file. 77 | 78 | ## ASP.NET Core example 79 | If you encrypted a key in the appsettings.json file of your ASP.NET Core web application you can use the following to enable the decryption. 80 | 81 | The easiest way to enable decryption is to modify the `CreateHostBuilder` function in your `Program.cs` file. 82 | 83 | ```csharp 84 | public static IHostBuilder CreateHostBuilder(string[] args) => 85 | Host.CreateDefaultBuilder(args) 86 | .ConfigureWebHostDefaults(webBuilder => 87 | { 88 | webBuilder.UseStartup(); 89 | }) 90 | .ConfigureAppConfiguration(cfg => 91 | { 92 | cfg.AddEncryptedAppSettings(crypter => 93 | { 94 | crypter.CertificatePath = "cert.pfx"; 95 | crypter.KeysToDecrypt = new List { "Nested:KeyToEncrypt" }; 96 | }); 97 | }); 98 | ``` 99 | 100 | As you can see enabling decryption is as simple as adding a line of code. You just need to specify the path to the certificate and the keys that should be decrypted. 101 | 102 | If you want to also use the environment specific appsettings files (appsettings.Development.json, appsettings.Production.json) please use the overloaded method to configure as shown below: 103 | 104 | ```csharp 105 | public static IHostBuilder CreateHostBuilder(string[] args) => 106 | Host.CreateDefaultBuilder(args) 107 | .ConfigureWebHostDefaults(webBuilder => 108 | { 109 | webBuilder.UseStartup(); 110 | }) 111 | .ConfigureAppConfiguration((hostingContext, cfg) => 112 | { 113 | cfg.AddEncryptedAppSettings(hostingContext.HostingEnvironment, crypter => 114 | { 115 | crypter.CertificatePath = "cert.pfx"; 116 | crypter.KeysToDecrypt = new List { "Nested:KeyToEncrypt" }; 117 | }); 118 | }); 119 | ``` 120 | Notice how to `HostingEnvironment` property of the `hostingContext` variable is used. 121 | 122 | Notice that nested keys have to be separated with colons in this case, because this is the default way to access nested configuration properties in .NET Core ([Link](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#configuration-keys-and-values)). 123 | 124 | ## How to generate a certificate 125 | Certificates can be generated by using Openssl. 126 | An example certificate is already in the project and the encrypted string in the example appsettings files has been encrypted with it. 127 | To generate a certificate you could use the following commands: 128 | 129 | `openssl genrsa 2048 > private.key` 130 | 131 | `openssl req -new -x509 -nodes -sha1 -days 365 -key private.key > public.cer` 132 | 133 | `openssl pkcs12 -export -in public.cer -inkey private.key -out cert.pfx` 134 | -------------------------------------------------------------------------------- /cert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/cert.pfx -------------------------------------------------------------------------------- /devattic_configcrypter-normal256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/devattic_configcrypter-normal256.png --------------------------------------------------------------------------------