├── .editorconfig ├── .gitattributes ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── Example.FieldLevel ├── Example.FieldLevel.csproj └── Program.cs ├── Example.XSerializer ├── Example.XSerializer.csproj └── Program.cs ├── Examples └── Example.Symmetric │ ├── EncryptionService.cs │ ├── Example.Symmetric.csproj │ ├── Program.cs │ └── appsettings.json ├── LICENSE.md ├── README.md ├── RockLib.Encryption.All.sln ├── RockLib.Encryption.XSerializer ├── CHANGELOG.md ├── FieldLevelEncryptionExtensions.cs ├── RockLib.Encryption.XSerializer.csproj ├── RockLib.Encryption.XSerializer.sln ├── SerializingCrypto.cs ├── XSerializer │ └── CryptoEncryptionMechanism.cs └── appveyor.yml ├── RockLib.Encryption ├── AssemblyAttributes.cs ├── CHANGELOG.md ├── CompositeCrypto.cs ├── Crypto.cs ├── DependencyInjection │ └── DependencyInjectionExtensions.cs ├── FieldLevel │ └── FieldLevelEncryptionExtensions.cs ├── ICrypto.cs ├── ICryptoExtensions.cs ├── IDecryptor.cs ├── IEncryptor.cs ├── RockLib.Encryption.csproj ├── RockLib.Encryption.sln ├── Symmetric │ ├── Credential.cs │ ├── DependencyInjection │ │ ├── SymmetricCredentialOptions.cs │ │ ├── SymmetricCryptoBuilder.cs │ │ └── SymmetricCryptoExtensions.cs │ ├── ICredentialRepository.cs │ ├── InMemoryCredentialRepository.cs │ ├── ProtocolExtensions.cs │ ├── SymmetricAlgorithm.cs │ ├── SymmetricAlgorithmExtensions.cs │ ├── SymmetricCrypto.cs │ ├── SymmetricDecryptor.cs │ └── SymmetricEncryptor.cs ├── Testing │ └── FakeCrypto.cs └── appveyor.yml ├── Tests ├── RockLib.Encryption.Tests │ ├── AssemblySettings.cs │ ├── CompositeCryptoTests.cs │ ├── CredentialTests.cs │ ├── CryptoReset.cs │ ├── CryptoTests.cs │ ├── DependencyInjection │ │ └── DependencyInjectionExtensionsTests.cs │ ├── FakeCryptoTests.cs │ ├── FieldLevel │ │ └── FieldLevelEncryptionExtensionsTests.cs │ ├── MultiFactory.json │ ├── NoFactories.json │ ├── RockLib.Encryption.Tests.csproj │ ├── SingleFactory.json │ ├── SymmetricCryptoTests.cs │ ├── SymmetricDecryptorTests.cs │ ├── SymmetricEncryptorTests.cs │ └── SymmetricRoundTripTests.cs └── RockLib.Encryption.XSerializer.Tests │ ├── CryptoEncryptionMechanismTests.cs │ ├── RockLib.Encryption.XSerializer.Tests.csproj │ └── SerializingCryptoTests.cs ├── docs ├── Concepts.md ├── Crypto.md ├── DependencyInjection.md ├── FieldLevelEncryption.md ├── GettingStarted.md └── Implementations.md └── icon.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 12 | 13 | ## Type of change: 14 | 15 | 1. Non-functional change (e.g. documentation changes, removing unused `using` directives, renaming local variables, etc) 16 | 2. Bug fix (non-breaking change that fixes an issue) 17 | 3. New feature (non-breaking change that adds functionality) 18 | 4. Breaking change (fix or feature that could cause existing functionality to not work as expected) 19 | 20 | ## Checklist: 21 | 22 | - Have you reviewed your own code? Do you understand every change? 23 | - Are you following the [contributing guidelines](../blob/main/CONTRIBUTING.md)? 24 | - Have you added tests that prove your fix is effective or that this feature works? 25 | - New and existing unit tests pass locally with these changes? 26 | - Have you made corresponding changes to the documentation? 27 | - Will this change require an update to an example project? (if so, create an issue and link to it) 28 | 29 | --- 30 | 31 | _[Reviewer guidelines](../blob/main/CONTRIBUTING.md#reviewing-changes)_ 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at RockLibSupport@quickenloans.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RockLib 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue, assessing 9 | changes, and helping you finalize your pull requests. 10 | 11 | As for everything else in the project, the contributions to RockLib are governed by our [Code of Conduct](CODE_OF_CONDUCT.md). 12 | 13 | 14 | ## Using the issue tracker 15 | 16 | First things first: **Do NOT report security vulnerabilities in public issues!** Please disclose responsibly by letting [the RockLib team](mailto:RockLibSupport@quickenloans.com?subject=Security) know upfront. We will assess the issue as soon as possible on a best-effort basis and will give you an estimate for when we have a fix and release available for an eventual public disclosure. 17 | 18 | The GitHub issue tracker is the preferred channel for [bug reports](#bugs), 19 | [features requests](#features) and [submitting pull 20 | requests](#pull-requests). 21 | 22 | 23 | ## Bug reports 24 | 25 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 26 | Good bug reports are extremely helpful - thank you! 27 | 28 | Guidelines for bug reports: 29 | 30 | 1. **Use the GitHub issue search** — check if the issue has already been 31 | reported. 32 | 33 | 2. **Check if the issue has been fixed** — try to reproduce it using the 34 | latest `master` branch in the repository. 35 | 36 | 3. **Isolate the problem** — ideally create a reduced test case. 37 | 38 | A good bug report shouldn't leave others needing to chase you up for more 39 | information. Please try to be as detailed as possible in your report. What is 40 | your environment? What steps will reproduce the issue? What OS experiences the 41 | problem? What would you expect to be the outcome? All these details will help 42 | people to fix any potential bugs. 43 | 44 | Example: 45 | 46 | > Short and descriptive example bug report title 47 | > 48 | > A summary of the issue and the browser/OS environment in which it occurs. If 49 | > suitable, include the steps required to reproduce the bug. 50 | > 51 | > 1. This is the first step 52 | > 2. This is the second step 53 | > 3. Further steps, etc. 54 | > 55 | > `` - a link to the reduced test case 56 | > 57 | > Any other information you want to share that is relevant to the issue being 58 | > reported. This might include the lines of code that you have identified as 59 | > causing the bug, and potential solutions (and your opinions on their 60 | > merits). 61 | 62 | 63 | ## Feature requests 64 | 65 | Feature requests are welcome. But take a moment to find out whether your idea 66 | fits with the scope and aims of the project. It's up to *you* to make a strong 67 | case to convince the project's developers of the merits of this feature. Please 68 | provide as much detail and context as possible. 69 | 70 | 71 | ## Pull requests 72 | 73 | Good pull requests - patches, improvements, new features - are a fantastic 74 | help. They should remain focused in scope and avoid containing unrelated 75 | commits. 76 | 77 | **Please ask first** before embarking on any significant pull request (e.g. 78 | implementing features, refactoring code), otherwise you risk spending a lot of 79 | time working on something that the project's developers might not want to merge 80 | into the project. 81 | 82 | 83 | ### For Contributors 84 | 85 | If you have never created a pull request before, welcome :tada: :smile: [Here is a great tutorial](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 86 | on how to create a pull request.. 87 | 88 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 89 | and configure the remotes: 90 | 91 | ```bash 92 | # Clone your fork of the repo into the current directory 93 | git clone https://github.com// 94 | # Navigate to the newly cloned directory 95 | cd 96 | # Assign the original repo to a remote called "upstream" 97 | git remote add upstream https://github.com/RockLib/ 98 | ``` 99 | 100 | 2. If you cloned a while ago, get the latest changes from upstream: 101 | 102 | ```bash 103 | git checkout master 104 | git pull upstream master 105 | ``` 106 | 107 | 3. Create a new topic branch (off the main project development branch) to 108 | contain your feature, change, or fix: 109 | 110 | ```bash 111 | git checkout -b 112 | ``` 113 | 114 | 4. Please follow Chris Beams' [seven rules of a great Git commit message](https://chris.beams.io/posts/git-commit/#seven-rules): 115 | 116 | 1. [Separate subject from body with a blank line](https://chris.beams.io/posts/git-commit/#separate) 117 | 2. [Limit the subject line to 50 characters](https://chris.beams.io/posts/git-commit/#limit-50) 118 | 3. [Capitalize the subject line](https://chris.beams.io/posts/git-commit/#capitalize) 119 | 4. [Do not end the subject line with a period](https://chris.beams.io/posts/git-commit/#end) 120 | 5. [Use the imperative mood in the subject line](https://chris.beams.io/posts/git-commit/#imperative) 121 | 6. [Wrap the body at 72 characters](https://chris.beams.io/posts/git-commit/#wrap-72) 122 | 7. [Use the body to explain what and why vs. how](https://chris.beams.io/posts/git-commit/#why-not-how) 123 | 124 | 5. Make sure to update, or add to the tests when appropriate. 125 | 126 | 6. If you added or changed a feature, make sure to document it accordingly in 127 | the `README.md` file. 128 | 129 | 7. Push your topic branch up to your fork: 130 | 131 | ```bash 132 | git push origin 133 | ``` 134 | 135 | 8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 136 | with a clear title and description. 137 | 138 | 139 | **IMPORTANT**: By submitting a patch, you agree to license your work under the 140 | same license as that used by the project. 141 | 142 | ## Maintainers 143 | 144 | If you have commit access, please follow this process for merging patches and cutting new releases. 145 | 146 | ### Reviewing changes 147 | 148 | 1. Check that a change is within the scope and philosophy of the component. 149 | 2. Check that a change has any necessary tests. 150 | 3. Check that a change has any necessary documentation. 151 | 4. If there is anything you don’t like, leave a comment below the respective 152 | lines and submit a "Request changes" review. Repeat until everything has 153 | been addressed. 154 | 5. If you are not sure about something, mention specific people for help in a 155 | comment. 156 | 6. If there is only a tiny change left before you can merge it and you think 157 | it’s best to fix it yourself, do so and leave a comment about it so the 158 | author and others will know. 159 | 7. Once everything looks good, add an "Approve" review. Don’t forget to say 160 | something nice 👏🐶💖✨ 161 | 8. If the commit messages follow Chris Beams' [seven rules of a great Git commit 162 | message](https://chris.beams.io/posts/git-commit/#seven-rules): 163 | 164 | 1. Use the "Merge pull request" button to merge the pull request. 165 | 2. Done! You are awesome! Thanks so much for your help 🤗 166 | 167 | 9. If the commit messages _do not_ follow our conventions: 168 | 169 | 1. Use the "Squash and merge" button to clean up the commits and merge at 170 | the same time: ✨🎩 171 | 2. Add a new commit subject and body. 172 | 173 | --- 174 | 175 | *This document is based on the [contributing](https://github.com/hoodiehq/hoodie/blob/master/CONTRIBUTING.md) document from the [Hoodie](https://github.com/hoodiehq/hoodie) project.* 176 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | AllEnabledByDefault 7 | Rocket Mortgage 8 | Copyright 2022 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net6.0;netcoreapp3.1;net48 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Example.FieldLevel/Example.FieldLevel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Example.FieldLevel/Program.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RockLib.Encryption; 3 | using RockLib.Encryption.FieldLevel; 4 | using RockLib.Encryption.Symmetric; 5 | using RockLib.Encryption.Testing; 6 | using System; 7 | using System.Text; 8 | using System.Xml; 9 | using System.Xml.Serialization; 10 | 11 | #pragma warning disable CA1303 // Do not pass literals as localized parameters 12 | namespace Example.FieldLevel; 13 | 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | ICrypto crypto = 19 | new SymmetricCrypto(new[] { new Credential(() => Convert.FromBase64String("1J9Og/OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=")) }); 20 | 21 | Person person = new Person 22 | { 23 | FirstName = "J", 24 | LastName = "Public", 25 | SSN = "123-45-6789" 26 | }; 27 | 28 | string xml = SerializeXml(person); 29 | Console.WriteLine("Original XML:"); 30 | Console.WriteLine(xml); 31 | Console.WriteLine(); 32 | 33 | string encryptedXml = crypto.EncryptXml(xml, "/Person/SSN"); 34 | Console.WriteLine("Encrypted XML:"); 35 | Console.WriteLine(encryptedXml); 36 | Console.WriteLine(); 37 | 38 | string decryptedXml = crypto.DecryptXml(encryptedXml, "/Person/SSN"); 39 | Console.WriteLine("Decrypted XML:"); 40 | Console.WriteLine(decryptedXml); 41 | Console.WriteLine(); 42 | 43 | string json = SerializeJson(person); 44 | Console.WriteLine("Original JSON:"); 45 | Console.WriteLine(json); 46 | Console.WriteLine(); 47 | 48 | string encryptedJson = crypto.EncryptJson(json, "$.SSN"); 49 | Console.WriteLine("Encrypted JSON:"); 50 | Console.WriteLine(encryptedJson); 51 | Console.WriteLine(); 52 | 53 | string decryptedJson = crypto.DecryptJson(encryptedJson, "$.SSN"); 54 | Console.WriteLine("Decrypted JSON:"); 55 | Console.WriteLine(decryptedJson); 56 | Console.WriteLine(); 57 | } 58 | 59 | private static string SerializeXml(Person person) 60 | { 61 | var serializer = new XmlSerializer(typeof(Person)); 62 | var ns = new XmlSerializerNamespaces(); 63 | ns.Add("", ""); 64 | var sb = new StringBuilder(); 65 | using (var writer = XmlWriter.Create(sb, new XmlWriterSettings { Indent = false, OmitXmlDeclaration = true })) 66 | serializer.Serialize(writer, person, ns); 67 | return sb.ToString(); 68 | } 69 | 70 | private static string SerializeJson(Person person) => 71 | JsonConvert.SerializeObject(person, Newtonsoft.Json.Formatting.None); 72 | } 73 | 74 | public class Person 75 | { 76 | public string? FirstName { get; set; } 77 | public string? LastName { get; set; } 78 | public string? SSN { get; set; } 79 | } 80 | #pragma warning restore CA1303 // Do not pass literals as localized parameters 81 | -------------------------------------------------------------------------------- /Example.XSerializer/Example.XSerializer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Example.XSerializer/Program.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Encryption; 2 | using RockLib.Encryption.Symmetric; 3 | using System; 4 | using XSerializer.Encryption; 5 | 6 | namespace Example.XSerializer; 7 | 8 | #pragma warning disable CA1303 // Do not pass literals as localized parameters 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | ICrypto crypto = 14 | new SymmetricCrypto(new[] { new Credential(() => Convert.FromBase64String("3x6ZwzENlKLrQB3fSuuM4+8z1OYjygDAlKZNmSBXASQ=")) }); 15 | 16 | var person = new Person 17 | { 18 | FirstName = "J", 19 | LastName = "Public", 20 | SSN = "123-45-6789" 21 | }; 22 | 23 | Console.WriteLine("Initial:"); 24 | Console.WriteLine($"FirstName: {person.FirstName}, LastName: {person.LastName}, SSN: {person.SSN}"); 25 | Console.WriteLine(); 26 | 27 | var encryptedXml = crypto.ToXml(person); 28 | Console.WriteLine("XML serialized:"); 29 | Console.WriteLine(encryptedXml); 30 | Console.WriteLine(); 31 | 32 | person = crypto.FromXml(encryptedXml); 33 | Console.WriteLine("XML deserialized:"); 34 | Console.WriteLine($"FirstName: {person.FirstName}, LastName: {person.LastName}, SSN: {person.SSN}"); 35 | Console.WriteLine(); 36 | 37 | var encryptedJson = crypto.ToJson(person); 38 | Console.WriteLine("JSON serialized:"); 39 | Console.WriteLine(encryptedJson); 40 | Console.WriteLine(); 41 | 42 | person = crypto.FromJson(encryptedJson); 43 | Console.WriteLine("JSON deserialized:"); 44 | Console.WriteLine($"FirstName: {person.FirstName}, LastName: {person.LastName}, SSN: {person.SSN}"); 45 | Console.WriteLine(); 46 | } 47 | } 48 | 49 | public class Person 50 | { 51 | public string? FirstName { get; set; } 52 | public string? LastName { get; set; } 53 | [Encrypt] public string? SSN { get; set; } 54 | } 55 | #pragma warning restore CA1303 // Do not pass literals as localized parameters 56 | -------------------------------------------------------------------------------- /Examples/Example.Symmetric/EncryptionService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using RockLib.Encryption; 3 | using RockLib.Encryption.Symmetric; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | #pragma warning disable CA1303 // Do not pass literals as localized parameters 11 | namespace Example.Symmetric; 12 | 13 | public class EncryptionService : IHostedService 14 | { 15 | private readonly ICrypto _crypto; 16 | private readonly Thread _executionThread; 17 | 18 | public EncryptionService(ICrypto crypto) 19 | { 20 | _crypto = crypto; 21 | _executionThread = new Thread(RunEncryptionPrompt) { IsBackground = true }; 22 | } 23 | 24 | public Task StartAsync(CancellationToken cancellationToken) 25 | { 26 | _executionThread.Start(); 27 | return Task.CompletedTask; 28 | } 29 | 30 | public Task StopAsync(CancellationToken cancellationToken) 31 | { 32 | return Task.CompletedTask; 33 | } 34 | 35 | private void RunEncryptionPrompt() 36 | { 37 | Thread.Sleep(1000); 38 | Console.WriteLine(); 39 | Console.WriteLine($"Starting..."); 40 | Console.WriteLine(); 41 | 42 | while (true) 43 | { 44 | try 45 | { 46 | Console.Write("Please enter the text you would like to encrypt: "); 47 | 48 | var input = Console.ReadLine(); 49 | 50 | var credentialNames = GetCredentialNames(); 51 | 52 | var selectedIndex = Prompt("Select the credential to encrypt the text:", credentialNames); 53 | 54 | if (selectedIndex == -1) 55 | return; 56 | 57 | var encryptCredentialName = credentialNames[selectedIndex]; 58 | 59 | string encrypted; 60 | #pragma warning disable CA1031 // Do not catch general exception types 61 | try 62 | { 63 | encrypted = _crypto.Encrypt(input!, encryptCredentialName); 64 | } 65 | catch (Exception ex) 66 | { 67 | Console.WriteLine($"Unable to encrypt using '{encryptCredentialName}'. Error: {ex.Message}"); 68 | continue; 69 | } 70 | #pragma warning restore CA1031 // Do not catch general exception types 71 | 72 | Console.WriteLine("Encryption successful: " + encrypted); 73 | Console.WriteLine(); 74 | 75 | selectedIndex = Prompt("Select the credential to decrypt the text:", credentialNames); 76 | 77 | if (selectedIndex == -1) 78 | return; 79 | 80 | var decryptCredentialName = credentialNames[selectedIndex]; 81 | 82 | string decrypted; 83 | #pragma warning disable CA1031 // Do not catch general exception types 84 | try 85 | { 86 | decrypted = _crypto.Decrypt(encrypted, decryptCredentialName); 87 | } 88 | catch (Exception ex) 89 | { 90 | Console.WriteLine($"Unable to decrypt using '{decryptCredentialName}'. Error: {ex.Message}"); 91 | continue; 92 | } 93 | #pragma warning restore CA1031 // Do not catch general exception types 94 | 95 | Console.WriteLine("Decryption successful: " + decrypted); 96 | } 97 | finally 98 | { 99 | Console.WriteLine("Restarting..."); 100 | Console.WriteLine(); 101 | Console.WriteLine(); 102 | } 103 | } 104 | } 105 | 106 | private static int Prompt(string prompt, IReadOnlyList credentialNames) 107 | { 108 | Console.WriteLine(prompt); 109 | for (var i = 0; i < credentialNames.Count; i++) 110 | Console.WriteLine($"{i + 1}) {credentialNames[i]}"); 111 | 112 | Console.Write(">"); 113 | 114 | while (true) 115 | { 116 | var line = Console.ReadLine(); 117 | if (line is null) 118 | return -1; 119 | if (int.TryParse(line, out var selection) 120 | && selection > 0 && selection <= credentialNames.Count) 121 | return selection - 1; 122 | Console.WriteLine($" Invalid input. Enter a number between 1 and {credentialNames.Count}."); 123 | } 124 | } 125 | 126 | private IReadOnlyList GetCredentialNames() 127 | { 128 | // The ICrypto interface doesn't provide a mechanism to enumerate the credential names 129 | // that it supports. But the expected type of ICrypto, SymmetricCrypto, has a public 130 | // CredentialRepository property. And the expected type of that property, 131 | // InMemoryCredentialRepository, has a public Credentials property, and this property 132 | // allows us to enumerate the credential names. Dynamic is the easiest way to accomplish 133 | // this task. 134 | dynamic crypto = _crypto; 135 | IReadOnlyCollection credentials = crypto.CredentialRepository.Credentials; 136 | return credentials.Select(credential => credential.Name).ToList(); 137 | } 138 | } 139 | #pragma warning restore CA1303 // Do not pass literals as localized parameters 140 | -------------------------------------------------------------------------------- /Examples/Example.Symmetric/Example.Symmetric.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Always 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Examples/Example.Symmetric/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using RockLib.Encryption; 4 | using RockLib.Encryption.Symmetric.DependencyInjection; 5 | using System; 6 | 7 | namespace Example.Symmetric; 8 | 9 | #pragma warning disable CA1303 // Do not pass literals as localized parameters 10 | class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | CreateHostBuilder(args).Build().Run(); 15 | } 16 | 17 | static IHostBuilder CreateHostBuilder(string[] args) 18 | { 19 | var hostBuilder = Host.CreateDefaultBuilder(args); 20 | 21 | hostBuilder.ConfigureServices(services => services.AddHostedService()); 22 | 23 | Console.WriteLine("Register ICrypto..."); 24 | Console.WriteLine("1) Programmatically"); 25 | Console.WriteLine("2) With configuration"); 26 | 27 | while (true) 28 | { 29 | Console.Write(">"); 30 | 31 | switch (Console.ReadLine()) 32 | { 33 | case "1": 34 | // Configuring a symmetric crypto programmatically 35 | return hostBuilder.ConfigureServices(services => 36 | services.AddSymmetricCrypto() 37 | .AddCredential("Aes", "1J9Og/OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=")); 38 | case "2": 39 | return hostBuilder.ConfigureServices(services => 40 | services.AddSingleton(Crypto.Current)); 41 | default: 42 | Console.WriteLine(" Invalid input. Valid values are '1' and '2'."); 43 | break; 44 | } 45 | } 46 | } 47 | } 48 | #pragma warning restore CA1303 // Do not pass literals as localized parameters 49 | -------------------------------------------------------------------------------- /Examples/Example.Symmetric/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rocklib.encryption": { 3 | "Type": "RockLib.Encryption.Symmetric.SymmetricCrypto, RockLib.Encryption", 4 | "Value": { 5 | "Credentials": [ 6 | { 7 | "Name": "Aes", 8 | "Algorithm": "Aes", 9 | "IVSize": "16", 10 | "Key": "1J9Og/OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=" 11 | }, 12 | { 13 | "Name": "DES", 14 | "Algorithm": "DES", 15 | "IVSize": "8", 16 | "Key": "2LQliivTtNo=" 17 | }, 18 | { 19 | "Name": "RC2", 20 | "Algorithm": "RC2", 21 | "IVSize": "8", 22 | "Key": "v6/CJsBIAK4rKs0hJR+JoA==" 23 | }, 24 | { 25 | "Name": "Rijndael", 26 | "Algorithm": "Rijndael", 27 | "IVSize": "16", 28 | "Key": "CQSImVlbvJMZcnrkzT3/ouW1klt6STljrDjRiBzIsSk=" 29 | }, 30 | { 31 | "Name": "TripleDES", 32 | "Algorithm": "TripleDES", 33 | "IVSize": "8", 34 | "Key": "qHIqXosjnkzkUM9yp2aE+J+aey9w53+B" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2021 Rocket Mortgage 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :warning: Deprecation Warning :warning: 2 | 3 | This library has been deprecated and will no longer receive updates. 4 | 5 | --- 6 | 7 | RockLib has been a cornerstone of our open source efforts here at Rocket Mortgage, and it's played a significant role in our journey to drive innovation and collaboration within our organization and the open source community. It's been amazing to witness the collective creativity and hard work that you all have poured into this project. 8 | 9 | However, as technology relentlessly evolves, so must we. The decision to deprecate this library is rooted in our commitment to staying at the cutting edge of technological advancements. While this chapter is ending, it opens the door to exciting new opportunities on the horizon. 10 | 11 | We want to express our heartfelt thanks to all the contributors and users who have been a part of this incredible journey. Your contributions, feedback, and enthusiasm have been invaluable, and we are genuinely grateful for your dedication. 🚀 12 | 13 | --- 14 | 15 | # RockLib.Encryption 16 | 17 | *An easy-to-use, easy-to-configure crypto API.* 18 | 19 | Note: 3.0.0 is the last major version release for the `RockLib.Encryption` and `RockLib.Encryption.XSerializer` packages. 20 | 21 | ### RockLib.Encryption 22 | 23 | ### RockLib.Encryption.XSerializer 24 | 25 | --- 26 | 27 | - [Getting started](docs/GettingStarted.md) 28 | - [Concepts](docs/Concepts.md) 29 | - [`ICrypto` interface](docs/Concepts.md#icrypto-interface) 30 | - [`CanEncrypt` / `CanDecrypt` methods](docs/Concepts.md#canencrypt--candecrypt-methods) 31 | - [`Encrypt` / `Decrypt` methods](docs/Concepts.md#encrypt--decrypt-methods) 32 | - [`GetEncryptor` / `GetDecryptor` methods](docs/Concepts.md#getencryptor--getdecryptor-methods) 33 | - [Dependency injection](docs/DependencyInjection.md) 34 | - [Crypto static class](docs/Crypto.md) 35 | - [Configuration](docs/Crypto.md#configuration) 36 | - [ICrypto implementations](docs/Implementations.md) 37 | - [SymmetricCrypto](docs/Implementations.md#symmetriccrypto-class) 38 | - [CompositeCrypto](docs/Implementations.md#compositecrypto-class) 39 | - [Field-level encryption](docs/FieldLevelEncryption.md) 40 | - [XPath / JSONPath](docs/FieldLevelEncryption.md#xpath--jsonpath) 41 | - [RockLib.Encryption.XSerializer](docs/FieldLevelEncryption.md#rocklibencryptionxserializer) 42 | -------------------------------------------------------------------------------- /RockLib.Encryption.All.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption", "RockLib.Encryption\RockLib.Encryption.csproj", "{6E80E23F-3AA5-43DB-ADEC-2918E1787062}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption.XSerializer", "RockLib.Encryption.XSerializer\RockLib.Encryption.XSerializer.csproj", "{B5D1A707-704C-4F76-A6FF-E0B379A79CE3}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Symmetric", "Examples\Example.Symmetric\Example.Symmetric.csproj", "{14ECC4B2-5A8E-4DFD-9CFD-1F786C669D95}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C80B1383-FEEE-4CA9-8146-CC69F84523F4}" 13 | ProjectSection(SolutionItems) = preProject 14 | docs\Concepts.md = docs\Concepts.md 15 | docs\Crypto.md = docs\Crypto.md 16 | docs\DependencyInjection.md = docs\DependencyInjection.md 17 | docs\FieldLevelEncryption.md = docs\FieldLevelEncryption.md 18 | docs\GettingStarted.md = docs\GettingStarted.md 19 | docs\Implementations.md = docs\Implementations.md 20 | README.md = README.md 21 | EndProjectSection 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption.Tests", "Tests\RockLib.Encryption.Tests\RockLib.Encryption.Tests.csproj", "{0D62277C-E658-4954-AC93-FD4791C8C809}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption.XSerializer.Tests", "Tests\RockLib.Encryption.XSerializer.Tests\RockLib.Encryption.XSerializer.Tests.csproj", "{7683AC91-4683-44DF-9DC5-D88407EABE74}" 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8C67D4FD-11A4-4C2E-AF8D-8844C1AFC211}" 28 | EndProject 29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{10C9A292-2CE2-4F61-B071-2B6D7121825E}" 30 | EndProject 31 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.FieldLevel", "Example.FieldLevel\Example.FieldLevel.csproj", "{F7F40AD8-EC79-4E8B-89A4-FC01BEE7A46D}" 32 | EndProject 33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.XSerializer", "Example.XSerializer\Example.XSerializer.csproj", "{3430499B-FE41-4BCE-BE11-C210E0387DF0}" 34 | EndProject 35 | Global 36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 37 | Debug|Any CPU = Debug|Any CPU 38 | Release|Any CPU = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 41 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {14ECC4B2-5A8E-4DFD-9CFD-1F786C669D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {14ECC4B2-5A8E-4DFD-9CFD-1F786C669D95}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {14ECC4B2-5A8E-4DFD-9CFD-1F786C669D95}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {14ECC4B2-5A8E-4DFD-9CFD-1F786C669D95}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {0D62277C-E658-4954-AC93-FD4791C8C809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {0D62277C-E658-4954-AC93-FD4791C8C809}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {0D62277C-E658-4954-AC93-FD4791C8C809}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {0D62277C-E658-4954-AC93-FD4791C8C809}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {7683AC91-4683-44DF-9DC5-D88407EABE74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {7683AC91-4683-44DF-9DC5-D88407EABE74}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {7683AC91-4683-44DF-9DC5-D88407EABE74}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {7683AC91-4683-44DF-9DC5-D88407EABE74}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {F7F40AD8-EC79-4E8B-89A4-FC01BEE7A46D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {F7F40AD8-EC79-4E8B-89A4-FC01BEE7A46D}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {F7F40AD8-EC79-4E8B-89A4-FC01BEE7A46D}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {F7F40AD8-EC79-4E8B-89A4-FC01BEE7A46D}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {3430499B-FE41-4BCE-BE11-C210E0387DF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {3430499B-FE41-4BCE-BE11-C210E0387DF0}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {3430499B-FE41-4BCE-BE11-C210E0387DF0}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {3430499B-FE41-4BCE-BE11-C210E0387DF0}.Release|Any CPU.Build.0 = Release|Any CPU 69 | EndGlobalSection 70 | GlobalSection(SolutionProperties) = preSolution 71 | HideSolutionNode = FALSE 72 | EndGlobalSection 73 | GlobalSection(NestedProjects) = preSolution 74 | {14ECC4B2-5A8E-4DFD-9CFD-1F786C669D95} = {10C9A292-2CE2-4F61-B071-2B6D7121825E} 75 | {0D62277C-E658-4954-AC93-FD4791C8C809} = {8C67D4FD-11A4-4C2E-AF8D-8844C1AFC211} 76 | {7683AC91-4683-44DF-9DC5-D88407EABE74} = {8C67D4FD-11A4-4C2E-AF8D-8844C1AFC211} 77 | {F7F40AD8-EC79-4E8B-89A4-FC01BEE7A46D} = {10C9A292-2CE2-4F61-B071-2B6D7121825E} 78 | {3430499B-FE41-4BCE-BE11-C210E0387DF0} = {10C9A292-2CE2-4F61-B071-2B6D7121825E} 79 | EndGlobalSection 80 | GlobalSection(ExtensibilityGlobals) = postSolution 81 | SolutionGuid = {68B67557-20B2-4F40-9FFB-19BCDBB9AA17} 82 | EndGlobalSection 83 | EndGlobal 84 | -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Encryption.XSerializer Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 3.0.0 - 2022-07-19 9 | 10 | #### Added 11 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 12 | 13 | #### Changed 14 | - Supported targets: net6.0, netcoreapp3.1, and net48. 15 | - Updated all NuGet package dependencies to their latest versions. 16 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 17 | - The following types have changed: 18 | - `CryptoEncryptionMechanism` 19 | - The constructor require the `ICrypto` parameter to be non-`null` and will throw an `ArgumentNullException` if it is `null`. 20 | - The `Encrypt()` and `Decrypt()` methods require the text and state parameters to be non-`null` and contain valid values - the code will throw an `ArgumentNullException` or an `ArgumentException`. 21 | 22 | ## 2.1.5 - 2021-08-12 23 | 24 | #### Changed 25 | 26 | - Changes "Quicken Loans" to "Rocket Mortgage". 27 | - Updates RockLib.Encryption to latest version, [2.3.3](https://github.com/RockLib/RockLib.Encryption/blob/main/RockLib.Encryption/CHANGELOG.md#233---2021-08-12). 28 | 29 | ## 2.1.4 - 2021-05-06 30 | 31 | #### Added 32 | 33 | - Adds SourceLink to nuget package. 34 | 35 | #### Changed 36 | 37 | - Updates RockLib.Encryption package to latest versions, which include SourceLink. 38 | 39 | ---- 40 | 41 | **Note:** Release notes in the above format are not available for earlier versions of 42 | RockLib.Secrets. What follows below are the original release notes. 43 | 44 | ---- 45 | 46 | ## 2.1.3 47 | 48 | Adds net5.0 target 49 | 50 | ## 2.1.2 51 | 52 | Adds icon to project and nuget package. 53 | 54 | ## 2.1.1 55 | 56 | Updates to align with nuget conventions. 57 | 58 | ## 2.1.0 59 | 60 | - Adds extension methods off of the ICrypto interface for field-level encryption. 61 | - Updates RockLib.Encryption dependency to the latest version, 2.2.0. 62 | 63 | ## 2.0.0 64 | 65 | - Updates to the latest RockLib.Encryption package. 66 | - Changes the object parameter named keyIdentifier to be of type string and named credentialName. 67 | 68 | ## 1.0.4 69 | 70 | Updates the libsodium and RockLib.Encryption packages to latest. 71 | 72 | ## 2.0.0-alpha04 73 | 74 | Updates RockLib.Encryption version to support RockLib_Encryption 75 | 76 | ## 2.0.0-alpha03 77 | 78 | Updates RockLib.Encryption package to 2.0.0-alpha05. 79 | 80 | ## 2.0.0-alpha02 81 | 82 | Updates RockLib.Encryption package to 2.0.0-alpha04. 83 | 84 | ## 2.0.0-alpha01 85 | 86 | Initial prerelease of 2.0.0 version. 87 | 88 | ## 1.0.3 89 | 90 | Updated RockLib.Encryption package. 91 | 92 | ## 1.0.2 93 | 94 | Updates RockLib.Encryption and XSerializer dependencies to latest versions. 95 | 96 | ## 1.0.1 97 | 98 | SerializingCrypto can deserialize/decrypt payloads with properties marked with [Encrypt] where the actual property value is not encrypted. This allows for safer deployment strategies when existing properties have been identified as sensitive and in need of encryption. Receiving applications can be deployed first, able to handle both the current plain-text property values and also the encrypted values when sending systems are deployed with the matching change. 99 | 100 | ## 1.0.0 101 | 102 | - Adds RockLib.Encryption.XSerializer.CryptoEncryptionMechanism, an adapter class. 103 | - Allows any implementation of ICrypto to used in XSerializer's field-level encryption mechanism. 104 | - Implements the XSerializer.Encryption.IEncryptionMechanism interface. 105 | - Behavior delegated to the instance of RockLib.Encryption.ICrypto passed to its constructor. 106 | - Adds RockLib.Encryption.SerializingCrypto static class. 107 | - Defines convenient methods for field-level encryption: ToXml, FromXml, ToJson, FromJson. 108 | - Each method serializes/deserializes with XSerializer. 109 | - Injects the value of its EncryptionMechanism property into each serialization operation. 110 | - public static CryptoEncryptionMechanism EncryptionMechanism { get; } 111 | - Default value is new RockLib.Encryption.XSerializer.CryptoEncryptionMechanism(RockLib.Encryption.Crypto.Current). 112 | - Call the SetEncryptionMechanism method at beginning of an application to change the value. 113 | - After this property is accessed, its value can no longer be changed. -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/FieldLevelEncryptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Encryption.XSerializer; 2 | using XSerializer; 3 | 4 | namespace RockLib.Encryption; 5 | 6 | /// 7 | /// Provides a set of static methods used for doing field-level encryption 8 | /// during a serialization operation. 9 | /// 10 | public static class FieldLevelEncryptionExtensions 11 | { 12 | /// 13 | /// Serializes the specified instance to XML, encrypting any properties marked 14 | /// with the [Encrypt] attribute. 15 | /// 16 | /// The type of the instance 17 | /// The instance of that will perform encryption operations. 18 | /// The instance to serialize. 19 | /// 20 | /// The name of the credential to use for this encryption operation, 21 | /// or null to use the default credential. 22 | /// 23 | /// An XML document that represents the instance. 24 | public static string ToXml(this ICrypto crypto, T instance, string? credentialName = null) 25 | { 26 | var serializer = new XmlSerializer(x => x 27 | .WithEncryptionMechanism(new CryptoEncryptionMechanism(crypto)) 28 | .WithEncryptKey(credentialName)); 29 | return serializer.Serialize(instance); 30 | } 31 | 32 | /// 33 | /// Deserializes the specified XML to an object, decrypting any properties marked 34 | /// with the [Encrypt] attribute. 35 | /// 36 | /// The type to deserialize into. 37 | /// The instance of that will perform decryption operations. 38 | /// The XML to deserialize. 39 | /// 40 | /// The name of the credential to use for this encryption operation, 41 | /// or null to use the default credential. 42 | /// 43 | /// The deserialized object. 44 | public static T FromXml(this ICrypto crypto, string? xml, string? credentialName = null) 45 | { 46 | var serializer = new XmlSerializer(x => x 47 | .WithEncryptionMechanism(new CryptoEncryptionMechanism(crypto)) 48 | .WithEncryptKey(credentialName)); 49 | return serializer.Deserialize(xml); 50 | } 51 | 52 | /// 53 | /// Serializes the specified instance to JSON, encrypting any properties marked 54 | /// with the [Encrypt] attribute. 55 | /// 56 | /// The type of the instance 57 | /// The instance of that will perform encryption operations. 58 | /// The instance to serialize. 59 | /// 60 | /// The name of the credential to use for this encryption operation, 61 | /// or null to use the default credential. 62 | /// 63 | /// A JSON document that represents the instance. 64 | public static string ToJson(this ICrypto crypto, T instance, string? credentialName = null) 65 | { 66 | var serializer = 67 | new JsonSerializer(new JsonSerializerConfiguration 68 | { 69 | EncryptionMechanism = new CryptoEncryptionMechanism(crypto), 70 | EncryptKey = credentialName 71 | }); 72 | 73 | return serializer.Serialize(instance); 74 | } 75 | 76 | /// 77 | /// Deserializes the specified JSON to an object, decrypting any properties marked 78 | /// with the [Encrypt] attribute. 79 | /// 80 | /// The type to deserialize into. 81 | /// The instance of that will perform decryption operations. 82 | /// The JSON to deserialize. 83 | /// 84 | /// The name of the credential to use for this encryption operation, 85 | /// or null to use the default credential. 86 | /// 87 | /// The deserialized object. 88 | public static T FromJson(this ICrypto crypto, string? json, string? credentialName = null) 89 | { 90 | var serializer = 91 | new JsonSerializer(new JsonSerializerConfiguration 92 | { 93 | EncryptionMechanism = new CryptoEncryptionMechanism(crypto), 94 | EncryptKey = credentialName 95 | }); 96 | 97 | return serializer.Deserialize(json); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/RockLib.Encryption.XSerializer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Extension to RockLib.Encryption - allows properties marked with the [Encrypt] attribute to be encrypted during an XSerializer serialization operation. 5 | True 6 | True 7 | icon.png 8 | RockLib.Encryption.XSerializer 9 | LICENSE.md 10 | https://github.com/RockLib/RockLib.Encryption 11 | false 12 | A changelog is available at https://github.com/RockLib/RockLib.Encryption/blob/main/RockLib.Encryption.XSerializer/CHANGELOG.md. 13 | rocklib encryption crypto field-level 14 | 3.0.0 15 | True 16 | 3.0.0 17 | 18 | 19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/RockLib.Encryption.XSerializer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption.XSerializer", "RockLib.Encryption.XSerializer.csproj", "{B5D1A707-704C-4F76-A6FF-E0B379A79CE3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption.XSerializer.Tests", "..\Tests\RockLib.Encryption.XSerializer.Tests\RockLib.Encryption.XSerializer.Tests.csproj", "{E3D02A94-6CF2-4BEC-8A5E-62B969568B5C}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B5D1A707-704C-4F76-A6FF-E0B379A79CE3}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {E3D02A94-6CF2-4BEC-8A5E-62B969568B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {E3D02A94-6CF2-4BEC-8A5E-62B969568B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {E3D02A94-6CF2-4BEC-8A5E-62B969568B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {E3D02A94-6CF2-4BEC-8A5E-62B969568B5C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {68B67557-20B2-4F40-9FFB-19BCDBB9AA17} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/SerializingCrypto.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Encryption.XSerializer; 2 | using RockLib.Immutable; 3 | using XSerializer; 4 | 5 | namespace RockLib.Encryption; 6 | 7 | /// 8 | /// Provides a set of static methods used for doing field-level encryption 9 | /// during a serialization operation. 10 | /// 11 | public static class SerializingCrypto 12 | { 13 | private static readonly Semimutable _encryptionMechanism = new(GetDefaultCryptoEncryptionMechanism); 14 | 15 | /// 16 | /// Gets the current instance of . 17 | /// 18 | /// 19 | /// Each method of the class ultimately uses the value 20 | /// of this property and calls one of its methods. 21 | /// 22 | public static CryptoEncryptionMechanism EncryptionMechanism => _encryptionMechanism.Value; 23 | 24 | /// 25 | /// Sets the value of the property. 26 | /// 27 | /// The new value of the property. 28 | /// 29 | /// Each method of the class ultimately uses the value 30 | /// of this property and calls one of its methods. 31 | /// 32 | public static void SetEncryptionMechanism(CryptoEncryptionMechanism cryptoEncryptionMechanism) 33 | { 34 | _encryptionMechanism.SetValue(() => cryptoEncryptionMechanism ?? GetDefaultCryptoEncryptionMechanism()); 35 | } 36 | 37 | /// 38 | /// Serializes the specified instance to XML, encrypting any properties marked 39 | /// with the [Encrypt] attribute. 40 | /// 41 | /// The type of the instance 42 | /// The instance to serialize. 43 | /// An XML document that represents the instance. 44 | public static string ToXml(T instance) 45 | { 46 | return ToXml(instance, null); 47 | } 48 | 49 | /// 50 | /// Serializes the specified instance to XML, encrypting any properties marked 51 | /// with the [Encrypt] attribute. 52 | /// 53 | /// The type of the instance 54 | /// The instance to serialize. 55 | /// 56 | /// The name of the credential to use for this encryption operation, 57 | /// or null to use the default credential. 58 | /// 59 | /// An XML document that represents the instance. 60 | public static string ToXml(T instance, string? credentialName) 61 | { 62 | var serializer = new XmlSerializer(x => x 63 | .WithEncryptionMechanism(EncryptionMechanism) 64 | .WithEncryptKey(credentialName)); 65 | return serializer.Serialize(instance); 66 | } 67 | 68 | /// 69 | /// Deserializes the specified XML to an object, decrypting any properties marked 70 | /// with the [Encrypt] attribute. 71 | /// 72 | /// The type to deserialize into. 73 | /// The XML to deserialize. 74 | /// The deserialized object. 75 | public static T FromXml(string xml) 76 | { 77 | return FromXml(xml, null); 78 | } 79 | 80 | /// 81 | /// Deserializes the specified XML to an object, decrypting any properties marked 82 | /// with the [Encrypt] attribute. 83 | /// 84 | /// The type to deserialize into. 85 | /// The XML to deserialize. 86 | /// 87 | /// The name of the credential to use for this encryption operation, 88 | /// or null to use the default credential. 89 | /// 90 | /// The deserialized object. 91 | public static T FromXml(string xml, string? credentialName) 92 | { 93 | var serializer = new XmlSerializer(x => x 94 | .WithEncryptionMechanism(EncryptionMechanism) 95 | .WithEncryptKey(credentialName)); 96 | return serializer.Deserialize(xml); 97 | } 98 | 99 | /// 100 | /// Serializes the specified instance to JSON, encrypting any properties marked 101 | /// with the [Encrypt] attribute. 102 | /// 103 | /// The type of the instance 104 | /// The instance to serialize. 105 | /// A JSON document that represents the instance. 106 | public static string ToJson(T instance) 107 | { 108 | return ToJson(instance, null); 109 | } 110 | 111 | /// 112 | /// Serializes the specified instance to JSON, encrypting any properties marked 113 | /// with the [Encrypt] attribute. 114 | /// 115 | /// The type of the instance 116 | /// The instance to serialize. 117 | /// 118 | /// The name of the credential to use for this encryption operation, 119 | /// or null to use the default credential. 120 | /// 121 | /// A JSON document that represents the instance. 122 | public static string ToJson(T instance, string? credentialName) 123 | { 124 | var serializer = 125 | new JsonSerializer(new JsonSerializerConfiguration 126 | { 127 | EncryptionMechanism = EncryptionMechanism, 128 | EncryptKey = credentialName 129 | }); 130 | 131 | return serializer.Serialize(instance); 132 | } 133 | 134 | /// 135 | /// Deserializes the specified JSON to an object, decrypting any properties marked 136 | /// with the [Encrypt] attribute. 137 | /// 138 | /// The type to deserialize into. 139 | /// The JSON to deserialize. 140 | /// The deserialized object. 141 | public static T FromJson(string json) 142 | { 143 | return FromJson(json, null); 144 | } 145 | 146 | /// 147 | /// Deserializes the specified JSON to an object, decrypting any properties marked 148 | /// with the [Encrypt] attribute. 149 | /// 150 | /// The type to deserialize into. 151 | /// The JSON to deserialize. 152 | /// 153 | /// The name of the credential to use for this encryption operation, 154 | /// or null to use the default credential. 155 | /// 156 | /// The deserialized object. 157 | public static T FromJson(string json, string? credentialName) 158 | { 159 | var serializer = 160 | new JsonSerializer(new JsonSerializerConfiguration 161 | { 162 | EncryptionMechanism = EncryptionMechanism, 163 | EncryptKey = credentialName 164 | }); 165 | 166 | return serializer.Deserialize(json); 167 | } 168 | 169 | private static CryptoEncryptionMechanism GetDefaultCryptoEncryptionMechanism() 170 | { 171 | return new CryptoEncryptionMechanism(Crypto.Current); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/XSerializer/CryptoEncryptionMechanism.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using XSerializer; 3 | using XSerializer.Encryption; 4 | 5 | namespace RockLib.Encryption.XSerializer; 6 | 7 | /// 8 | /// An implementation of XSerializer's interface 9 | /// that uses an instance of to perform the encryption and 10 | /// decryption operations. 11 | /// 12 | public sealed class CryptoEncryptionMechanism : IEncryptionMechanism 13 | { 14 | private readonly ICrypto _crypto; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// 20 | /// The instance of that is responsible for encryption 21 | /// and decryption operations. 22 | /// 23 | /// Thrown if is null. 24 | public CryptoEncryptionMechanism(ICrypto crypto) 25 | { 26 | _crypto = crypto ?? throw new ArgumentNullException(nameof(crypto)); 27 | } 28 | 29 | /// 30 | /// Gets the instance of that is responsible for encryption 31 | /// and decryption operations. 32 | /// 33 | public ICrypto Crypto => _crypto; 34 | 35 | /// 36 | /// Encrypts the specified plain text. 37 | /// 38 | /// The plain text. 39 | /// 40 | /// The name of the credential to use for this encryption operation, 41 | /// or null to use the default credential. 42 | /// 43 | /// 44 | /// An object that holds an arbitrary value that is passed to one or more encrypt 45 | /// operations within a single serialization operation. 46 | /// 47 | /// The encrypted text. 48 | /// Thrown if is null. 49 | /// Thrown if is null or empty. 50 | public string Encrypt(string plainText, string? credentialName, SerializationState serializationState) 51 | { 52 | if (string.IsNullOrEmpty(plainText)) { throw new ArgumentException($"'{nameof(plainText)}' cannot be null or empty.", nameof(plainText)); } 53 | if (serializationState is null) { throw new ArgumentNullException(nameof(serializationState)); } 54 | 55 | var encryptor = serializationState.Get(() => _crypto.GetEncryptor(credentialName?.ToString())); 56 | var cipherText = encryptor.Encrypt(plainText); 57 | return cipherText; 58 | } 59 | 60 | string IEncryptionMechanism.Encrypt(string plainText, object? encryptKey, SerializationState serializationState) => 61 | Encrypt(plainText, encryptKey?.ToString(), serializationState); 62 | 63 | /// 64 | /// Decrypts the specified cipher text. 65 | /// 66 | /// The cipher text. 67 | /// 68 | /// The name of the credential to use for this encryption operation, 69 | /// or null to use the default credential. 70 | /// 71 | /// 72 | /// An object that holds an arbitrary value that is passed to one or more decrypt 73 | /// operations within a single serialization operation. 74 | /// 75 | /// The decrypted text. 76 | /// Thrown if is null. 77 | /// Thrown if is null or empty. 78 | public string Decrypt(string cipherText, string? credentialName, SerializationState serializationState) 79 | { 80 | if (string.IsNullOrEmpty(cipherText)) { throw new ArgumentException($"'{nameof(cipherText)}' cannot be null or empty.", nameof(cipherText)); } 81 | if (serializationState is null) { throw new ArgumentNullException(nameof(serializationState)); } 82 | 83 | var decryptor = serializationState.Get(() => _crypto.GetDecryptor(credentialName?.ToString())); 84 | var plainText = decryptor.Decrypt(cipherText); 85 | return plainText; 86 | } 87 | 88 | string IEncryptionMechanism.Decrypt(string cipherText, object? encryptKey, SerializationState serializationState) => 89 | Decrypt(cipherText, encryptKey?.ToString(), serializationState); 90 | } 91 | -------------------------------------------------------------------------------- /RockLib.Encryption.XSerializer/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 'RockLib.Encryption.XSerializer.{build}.0.0-ci' 2 | image: Visual Studio 2019 3 | configuration: Release 4 | only_commits: 5 | files: 6 | - RockLib.Encryption.XSerializer/ 7 | - Tests/RockLib.Encryption.XSerializer.Tests/ 8 | before_build: 9 | - ps: | 10 | # The path to the solution to restore. 11 | $sln_path = "RockLib.Encryption.XSerializer\RockLib.Encryption.XSerializer.sln" 12 | 13 | # The path to the main csproj file. It will be patched during the build. 14 | $csproj_path = "RockLib.Encryption.XSerializer\RockLib.Encryption.XSerializer.csproj" 15 | 16 | # The version of the build artifact's nuget package when created by CI builds. 17 | $ci_package_version = "$Env:appveyor_build_number.0.0-ci" 18 | 19 | # This before_build script does three things: 20 | # 21 | # 1) Synchronize the AppVeyor build version and the csproj's package version. 22 | # a) If the current build is a deploy build*, update the AppVeyor build version to match 23 | # the csproj's nuget package version. 24 | # b) If the current build is a CI build*, update the csproj's package version to match 25 | # the AppVeyor build version. 26 | # 2) Set an AppVeyor build variable named 'csproj_build_version' to the csproj's package version. 27 | # This value is used by deploy providers to determine whether the current commit should be deployed. 28 | # 3) Restore packages for the sln. .NET Standard libraries won't build without restoring first. 29 | # 30 | # *The current build is a deploy build if the 'appveyor_repo_tag' variable is 'true' and the 31 | # 'appveyor_repo_tag_name' variable is the the value of the 'csproj_build_version' variable, as set in 32 | # #2 above. Otherwise, the current build is a CI build. 33 | 34 | function Get-Csproj-Build-Version ($csproj) 35 | { 36 | $package_id = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageId").InnerText 37 | $package_version = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageVersion").InnerText 38 | Return "$package_id.$package_version" 39 | } 40 | 41 | function Synchronize-AppVeyor-And-Csproj-Versions ($csproj) 42 | { 43 | $csproj_build_version = Get-Csproj-Build-Version $csproj 44 | 45 | If ($Env:appveyor_repo_tag -eq "true" -AND $Env:appveyor_repo_tag_name -eq $csproj_build_version) { 46 | # If this is a deploy build, update the AppVeyor build version to match the csproj's package version. 47 | Update-AppVeyorBuild -Version $csproj_build_version 48 | } else { 49 | # Else, update the csproj's package version to match the AppVeyor build version. 50 | $package_version_node = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageVersion") 51 | $package_version_node.InnerText = $ci_package_version 52 | } 53 | } 54 | 55 | function Set-Csproj-Build-Version-Variable ($csproj) 56 | { 57 | $csproj_build_version = Get-Csproj-Build-Version $csproj 58 | Set-AppVeyorBuildVariable -Name "csproj_build_version" -Value $csproj_build_version 59 | } 60 | 61 | # The $csproj xml object contains the contents of the csproj file. 62 | $csproj = [xml](Get-Content $csproj_path) 63 | 64 | Synchronize-AppVeyor-And-Csproj-Versions $csproj 65 | Set-Csproj-Build-Version-Variable $csproj 66 | 67 | # Patch the csproj file with the modified xml object after all changes have been made. 68 | $csproj.Save((Get-Item $csproj_path)) 69 | 70 | nuget restore $sln_path 71 | build: 72 | project: RockLib.Encryption.XSerializer\RockLib.Encryption.XSerializer.sln 73 | verbosity: minimal 74 | artifacts: 75 | - path: '**/$(csproj_build_version).nupkg' 76 | deploy: 77 | - provider: GitHub 78 | tag: $(appveyor_repo_tag_name) 79 | release: $(appveyor_repo_commit_message) 80 | description: 'A changelog is available at https://github.com/RockLib/RockLib.Encryption/blob/main/RockLib.Encryption.XSerializer/CHANGELOG.md.' 81 | auth_token: 82 | secure: gglgEqQVi2MuIUl8g8rS6jb5r8sgr0PYa4qXq9XaAGeAJ+oAEFmlNFFw/jgX8hQr 83 | on: 84 | appveyor_repo_tag: true 85 | appveyor_repo_tag_name: $(csproj_build_version) 86 | - provider: NuGet 87 | api_key: 88 | secure: d662rvY3udud0hnjkQv3mXwAvidAylYGnl8biGLb7/5Fb3mD+tJIfkY7/Y74cEMR 89 | on: 90 | appveyor_repo_tag: true 91 | appveyor_repo_tag_name: $(csproj_build_version) 92 | notifications: 93 | - provider: Email 94 | to: 95 | - rocklibsupport@rocketmortgage.com 96 | on_build_success: false 97 | on_build_failure: false 98 | on_build_status_changed: true 99 | -------------------------------------------------------------------------------- /RockLib.Encryption/AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Configuration.ObjectFactory; 2 | using RockLib.Encryption; 3 | using System.Collections.Generic; 4 | 5 | [assembly: ConfigSection("RockLib.Encryption", typeof(List))] -------------------------------------------------------------------------------- /RockLib.Encryption/CompositeCrypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace RockLib.Encryption; 6 | 7 | /// 8 | /// An composite implementation of that delegates logic to an arbitrary 9 | /// number of other instances. 10 | /// 11 | public class CompositeCrypto : ICrypto 12 | { 13 | private readonly IEnumerable _cryptos; 14 | 15 | /// 16 | /// Initializes a new instance of the class. Note that the order 17 | /// of items in the parameter is significant: the first one to 18 | /// match a credential name will "win". 19 | /// 20 | /// 21 | /// The instances of that this delegates 22 | /// logic to. 23 | /// 24 | public CompositeCrypto(IEnumerable cryptos) 25 | { 26 | if (cryptos is null) throw new ArgumentNullException(nameof(cryptos)); 27 | 28 | _cryptos = cryptos as List ?? cryptos.ToList(); 29 | } 30 | 31 | /// 32 | /// Gets the instances of that this 33 | /// delegates logic to. 34 | /// 35 | public IEnumerable Cryptos => _cryptos; 36 | 37 | /// 38 | /// Encrypts the specified plain text. 39 | /// 40 | /// The plain text. 41 | /// 42 | /// The name of the credential to use for this encryption operation, 43 | /// or null to use the default credential. 44 | /// 45 | /// The encrypted value as a string. 46 | public string Encrypt(string plainText, string? credentialName) => 47 | GetEncryptCrypto(credentialName).Encrypt(plainText, credentialName); 48 | 49 | /// 50 | /// Decrypts the specified cipher text. 51 | /// 52 | /// The cipher text. 53 | /// 54 | /// The name of the credential to use for this encryption operation, 55 | /// or null to use the default credential. 56 | /// 57 | /// The decrypted value as a string. 58 | public string Decrypt(string cipherText, string? credentialName) => 59 | GetDecryptCrypto(credentialName).Decrypt(cipherText, credentialName); 60 | 61 | /// 62 | /// Encrypts the specified plain text. 63 | /// 64 | /// The plain text. 65 | /// 66 | /// The name of the credential to use for this encryption operation, 67 | /// or null to use the default credential. 68 | /// 69 | /// The encrypted value as a byte array. 70 | public byte[] Encrypt(byte[] plainText, string? credentialName) => 71 | GetEncryptCrypto(credentialName).Encrypt(plainText, credentialName); 72 | 73 | /// 74 | /// Decrypts the specified cipher text. 75 | /// 76 | /// The cipher text. 77 | /// 78 | /// The name of the credential to use for this encryption operation, 79 | /// or null to use the default credential. 80 | /// 81 | /// The decrypted value as a byte array. 82 | public byte[] Decrypt(byte[] cipherText, string? credentialName) => 83 | GetDecryptCrypto(credentialName).Decrypt(cipherText, credentialName); 84 | 85 | /// 86 | /// Gets an instance of for the provided credential name. 87 | /// 88 | /// 89 | /// The name of the credential to use for this encryption operation, 90 | /// or null to use the default credential. 91 | /// 92 | /// An object that can be used for encryption operations. 93 | public IEncryptor GetEncryptor(string? credentialName) => 94 | GetEncryptCrypto(credentialName).GetEncryptor(credentialName); 95 | 96 | /// 97 | /// Gets an instance of for the provided credential name. 98 | /// 99 | /// 100 | /// The name of the credential to use for this encryption operation, 101 | /// or null to use the default credential. 102 | /// 103 | /// An object that can be used for decryption operations. 104 | public IDecryptor GetDecryptor(string? credentialName) => 105 | GetDecryptCrypto(credentialName).GetDecryptor(credentialName); 106 | 107 | 108 | /// 109 | /// Returns a value indicating whether this instance of 110 | /// is able to handle the provided credential name for an encrypt operation. 111 | /// 112 | /// 113 | /// The credential name to check, or null to check if the default credential exists. 114 | /// 115 | /// 116 | /// True, if this instance can handle the credential name for an encrypt operation. 117 | /// Otherwise, false. 118 | /// 119 | public bool CanEncrypt(string? credentialName) 120 | { 121 | try 122 | { 123 | GetEncryptCrypto(credentialName); 124 | } 125 | catch (KeyNotFoundException) 126 | { 127 | return false; 128 | } 129 | return true; 130 | } 131 | 132 | /// 133 | /// Returns a value indicating whether this instance of 134 | /// is able to handle the provided credential name for an decrypt operation. 135 | /// 136 | /// 137 | /// The credential name to check, or null to check if the default credential exists. 138 | /// 139 | /// 140 | /// True, if this instance can handle the credential name for an encrypt operation. 141 | /// Otherwise, false. 142 | /// 143 | public bool CanDecrypt(string? credentialName) 144 | { 145 | try 146 | { 147 | GetDecryptCrypto(credentialName); 148 | } 149 | catch (KeyNotFoundException) 150 | { 151 | return false; 152 | } 153 | return true; 154 | } 155 | 156 | private ICrypto GetEncryptCrypto(string? credentialName) 157 | { 158 | return GetCrypto(credentialName, (c, k) => c.CanEncrypt(k)); 159 | } 160 | 161 | private ICrypto GetDecryptCrypto(string? credentialName) 162 | { 163 | return GetCrypto(credentialName, (c, k) => c.CanDecrypt(k)); 164 | } 165 | 166 | private ICrypto GetCrypto(string? credentialName, Func canGet) 167 | { 168 | var crypto = _cryptos.FirstOrDefault(c => canGet(c, credentialName)); 169 | 170 | if (crypto is null) 171 | { 172 | throw new KeyNotFoundException($"Unable to locate implementation of ICrypto that can locate a credential using credentialName: {credentialName}"); 173 | } 174 | 175 | return crypto; 176 | } 177 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Crypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RockLib.Configuration.ObjectFactory; 3 | using RockLib.Configuration; 4 | using RockLib.Immutable; 5 | using System.Collections.Generic; 6 | 7 | namespace RockLib.Encryption; 8 | 9 | /// 10 | /// Provides a set of static methods used for encryption and decryption 11 | /// operations. 12 | /// 13 | public static class Crypto 14 | { 15 | private static readonly Semimutable _current = new Semimutable(GetDefaultCrypto); 16 | 17 | /// 18 | /// Gets the current instance of . 19 | /// 20 | /// 21 | /// Each method of the class ultimately uses the value 22 | /// of this property and calls one of its methods. 23 | /// 24 | public static ICrypto Current => _current.Value!; 25 | 26 | /// 27 | /// Sets the value of the property. 28 | /// 29 | /// 30 | /// The new value for the property, or null to set 31 | /// to the default . 32 | /// 33 | /// 34 | /// Each method of the class ultimately uses the value 35 | /// of the property and calls one of its methods. 36 | /// 37 | public static void SetCurrent(ICrypto crypto) => _current.SetValue(() => crypto ?? GetDefaultCrypto()); 38 | 39 | /// 40 | /// Encrypts the specified plain text. 41 | /// 42 | /// The plain text. 43 | /// 44 | /// The name of the credential to use for this encryption operation, 45 | /// or null to use the default credential. 46 | /// 47 | /// The encrypted value as a string. 48 | public static string Encrypt(string plainText, string? credentialName = null) => Current.Encrypt(plainText, credentialName); 49 | 50 | /// 51 | /// Decrypts the specified cipher text. 52 | /// 53 | /// The cipher text. 54 | /// 55 | /// The name of the credential to use for this encryption operation, 56 | /// or null to use the default credential. 57 | /// 58 | /// The decrypted value as a string. 59 | public static string Decrypt(string cipherText, string? credentialName = null) => Current.Decrypt(cipherText, credentialName); 60 | 61 | /// 62 | /// Encrypts the specified plain text. 63 | /// 64 | /// The plain text. 65 | /// 66 | /// The name of the credential to use for this encryption operation, 67 | /// or null to use the default credential. 68 | /// 69 | /// The encrypted value as a byte array. 70 | public static byte[] Encrypt(byte[] plainText, string? credentialName = null) => Current.Encrypt(plainText, credentialName); 71 | 72 | /// 73 | /// Decrypts the specified cipher text. 74 | /// 75 | /// The cipher text. 76 | /// 77 | /// The name of the credential to use for this encryption operation, 78 | /// or null to use the default credential. 79 | /// 80 | /// The decrypted value as a byte array. 81 | public static byte[] Decrypt(byte[] cipherText, string? credentialName = null) => Current.Decrypt(cipherText, credentialName); 82 | 83 | /// 84 | /// Gets an instance of for the provided key identifier. 85 | /// 86 | /// 87 | /// The name of the credential to use for this encryption operation, 88 | /// or null to use the default credential. 89 | /// 90 | /// An object that can be used for encryption operations. 91 | public static IEncryptor GetEncryptor(string? credentialName = null) => Current.GetEncryptor(credentialName); 92 | 93 | /// 94 | /// Gets an instance of for the provided key identifier. 95 | /// 96 | /// 97 | /// The name of the credential to use for this encryption operation, 98 | /// or null to use the default credential. 99 | /// 100 | /// An object that can be used for decryption operations. 101 | public static IDecryptor GetDecryptor(string? credentialName = null) => Current.GetDecryptor(credentialName); 102 | 103 | private static ICrypto GetDefaultCrypto() 104 | { 105 | var cryptos = Config.Root!.GetCompositeSection("rocklib_encryption", "rocklib.encryption").Create>(); 106 | 107 | if (cryptos is null || cryptos.Count == 0) 108 | { 109 | throw new InvalidOperationException("No crypto implementations found in config. See the Readme.md file for details on how to setup the configuration."); 110 | } 111 | 112 | if (cryptos.Count == 1) 113 | { 114 | return cryptos[0]; 115 | } 116 | 117 | return new CompositeCrypto(cryptos); 118 | } 119 | } -------------------------------------------------------------------------------- /RockLib.Encryption/DependencyInjection/DependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace RockLib.Encryption.DependencyInjection; 5 | 6 | /// 7 | /// Defines extension methods related to dependency injection and encryption. 8 | /// 9 | public static class DependencyInjectionExtensions 10 | { 11 | /// 12 | /// Adds the to the service collection. is 13 | /// created using configuration or set directly by using . 14 | /// 15 | /// The . 16 | /// The same . 17 | public static IServiceCollection AddCrypto(this IServiceCollection services) 18 | { 19 | if (services is null) 20 | { 21 | throw new ArgumentNullException(nameof(services)); 22 | } 23 | 24 | services.AddSingleton(_ => Crypto.Current); 25 | return services; 26 | } 27 | 28 | /// 29 | /// Adds the specified to the service collection. 30 | /// 31 | /// The . 32 | /// The to add to the service collection. 33 | /// The same . 34 | public static IServiceCollection AddCrypto(this IServiceCollection services, ICrypto crypto) 35 | { 36 | if (services is null) 37 | { 38 | throw new ArgumentNullException(nameof(services)); 39 | } 40 | if (crypto is null) 41 | { 42 | throw new ArgumentNullException(nameof(crypto)); 43 | } 44 | 45 | services.AddSingleton(crypto); 46 | return services; 47 | } 48 | 49 | /// 50 | /// Adds the specified to the service collection. 51 | /// 52 | /// The . 53 | /// A func that returns an when given an . 54 | /// The same . 55 | public static IServiceCollection AddCrypto(this IServiceCollection services, Func cryptoFactory) 56 | { 57 | if (services is null) 58 | { 59 | throw new ArgumentNullException(nameof(services)); 60 | } 61 | if (cryptoFactory is null) 62 | { 63 | throw new ArgumentNullException(nameof(cryptoFactory)); 64 | } 65 | 66 | services.AddSingleton(cryptoFactory); 67 | return services; 68 | } 69 | } -------------------------------------------------------------------------------- /RockLib.Encryption/ICrypto.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Encryption; 2 | 3 | /// 4 | /// Defines various encryption and decryption methods. 5 | /// 6 | public interface ICrypto 7 | { 8 | /// 9 | /// Encrypts the specified plain text. 10 | /// 11 | /// The plain text. 12 | /// 13 | /// The name of the credential to use for this encryption operation, 14 | /// or null to use the default credential. 15 | /// 16 | /// The encrypted value as a string. 17 | string Encrypt(string plainText, string? credentialName); 18 | 19 | /// 20 | /// Decrypts the specified cipher text. 21 | /// 22 | /// The cipher text. 23 | /// 24 | /// The name of the credential to use for this encryption operation, 25 | /// or null to use the default credential. 26 | /// 27 | /// The decrypted value as a string. 28 | string Decrypt(string cipherText, string? credentialName); 29 | 30 | /// 31 | /// Encrypts the specified plain text. 32 | /// 33 | /// The plain text. 34 | /// 35 | /// The name of the credential to use for this encryption operation, 36 | /// or null to use the default credential. 37 | /// 38 | /// The encrypted value as a byte array. 39 | byte[] Encrypt(byte[] plainText, string? credentialName); 40 | 41 | /// 42 | /// Decrypts the specified cipher text. 43 | /// 44 | /// The cipher text. 45 | /// 46 | /// The name of the credential to use for this encryption operation, 47 | /// or null to use the default credential. 48 | /// 49 | /// The decrypted value as a byte array. 50 | byte[] Decrypt(byte[] cipherText, string? credentialName); 51 | 52 | /// 53 | /// Gets an instance of for the provided credential name. 54 | /// 55 | /// 56 | /// The name of the credential to use for this encryption operation, 57 | /// or null to use the default credential. 58 | /// 59 | /// An object that can be used for encryption operations. 60 | IEncryptor GetEncryptor(string? credentialName); 61 | 62 | /// 63 | /// Gets an instance of for the provided credential name. 64 | /// 65 | /// 66 | /// The name of the credential to use for this encryption operation, 67 | /// or null to use the default credential. 68 | /// 69 | /// An object that can be used for decryption operations. 70 | IDecryptor GetDecryptor(string? credentialName); 71 | 72 | /// 73 | /// Returns a value indicating whether this instance of 74 | /// is able to handle the provided credential name for an encrypt operation. 75 | /// 76 | /// 77 | /// The credential name to check, or null to check if the default credential exists. 78 | /// 79 | /// 80 | /// True, if this instance can handle the credential name for an encrypt operation. 81 | /// Otherwise, false. 82 | /// 83 | bool CanEncrypt(string? credentialName); 84 | 85 | /// 86 | /// Returns a value indicating whether this instance of 87 | /// is able to handle the provided credential name for an decrypt operation. 88 | /// 89 | /// 90 | /// The credential name to check, or null to check if the default credential exists. 91 | /// 92 | /// 93 | /// True, if this instance can handle the credential name for an encrypt operation. 94 | /// Otherwise, false. 95 | /// 96 | bool CanDecrypt(string? credentialName); 97 | } -------------------------------------------------------------------------------- /RockLib.Encryption/ICryptoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Encryption; 4 | 5 | /// 6 | /// Defines extension methods for the interface that allow 7 | /// the user to omit the credentialName parameter from its methods. 8 | /// 9 | public static class ICryptoExtensions 10 | { 11 | /// 12 | /// Encrypts the specified plain text using the default credential. 13 | /// 14 | /// An . 15 | /// The plain text. 16 | /// The encrypted value as a string. 17 | public static string Encrypt(this ICrypto crypto, string plainText) => 18 | crypto?.Encrypt(plainText, null) ?? throw new ArgumentNullException(nameof(crypto)); 19 | 20 | /// 21 | /// Decrypts the specified cipher text using the default credential. 22 | /// 23 | /// An . 24 | /// The cipher text. 25 | /// The decrypted value as a string. 26 | public static string Decrypt(this ICrypto crypto, string cipherText) => 27 | crypto?.Decrypt(cipherText, null) ?? throw new ArgumentNullException(nameof(crypto)); 28 | 29 | /// 30 | /// Encrypts the specified plain text using the default credential. 31 | /// 32 | /// An . 33 | /// The plain text. 34 | /// The encrypted value as a byte array. 35 | public static byte[] Encrypt(this ICrypto crypto, byte[] plainText) => 36 | crypto?.Encrypt(plainText, null) ?? throw new ArgumentNullException(nameof(crypto)); 37 | 38 | /// 39 | /// Decrypts the specified cipher text using the default credential. 40 | /// 41 | /// An . 42 | /// The cipher text. 43 | /// The decrypted value as a byte array. 44 | public static byte[] Decrypt(this ICrypto crypto, byte[] cipherText) => 45 | crypto?.Decrypt(cipherText, null) ?? throw new ArgumentNullException(nameof(crypto)); 46 | 47 | /// 48 | /// Gets an instance of using the default credential. 49 | /// 50 | /// An . 51 | /// An object that can be used for encryption operations. 52 | public static IEncryptor GetEncryptor(this ICrypto crypto) => 53 | crypto?.GetEncryptor(null) ?? throw new ArgumentNullException(nameof(crypto)); 54 | 55 | /// 56 | /// Gets an instance of using the default credential. 57 | /// 58 | /// An . 59 | /// An object that can be used for decryption operations. 60 | public static IDecryptor GetDecryptor(this ICrypto crypto) => 61 | crypto?.GetDecryptor(null) ?? throw new ArgumentNullException(nameof(crypto)); 62 | } -------------------------------------------------------------------------------- /RockLib.Encryption/IDecryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Encryption; 4 | 5 | /// 6 | /// Defines methods for decryption. 7 | /// 8 | public interface IDecryptor : IDisposable 9 | { 10 | /// 11 | /// Decrypts the specified cipher text. 12 | /// 13 | /// The cipher text. 14 | /// The decrypted value as a string. 15 | string Decrypt(string cipherText); 16 | 17 | /// 18 | /// Decrypts the specified cipher text. 19 | /// 20 | /// The cipher text. 21 | /// The decrypted value as a byte array. 22 | byte[] Decrypt(byte[] cipherText); 23 | } -------------------------------------------------------------------------------- /RockLib.Encryption/IEncryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Encryption; 4 | 5 | /// 6 | /// Defines methods for encryption. 7 | /// 8 | public interface IEncryptor : IDisposable 9 | { 10 | /// 11 | /// Encrypts the specified plain text. 12 | /// 13 | /// The plain text. 14 | /// The encrypted value as a string. 15 | string Encrypt(string plainText); 16 | 17 | /// 18 | /// Encrypts the specified plain text. 19 | /// 20 | /// The plain text. 21 | /// The encrypted value as a byte array. 22 | byte[] Encrypt(byte[] plainText); 23 | } -------------------------------------------------------------------------------- /RockLib.Encryption/RockLib.Encryption.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | An easy-to-use crypto API. 5 | True 6 | True 7 | icon.png 8 | RockLib.Encryption 9 | LICENSE.md 10 | https://github.com/RockLib/RockLib.Encryption 11 | A changelog is available at https://github.com/RockLib/RockLib.Encryption/blob/main/RockLib.Encryption/CHANGELOG.md. 12 | false 13 | rocklib encryption crypto 14 | 3.1.0 15 | True 16 | 3.1.0 17 | 18 | 19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /RockLib.Encryption/RockLib.Encryption.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption", "RockLib.Encryption.csproj", "{6E80E23F-3AA5-43DB-ADEC-2918E1787062}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Encryption.Tests", "..\Tests\RockLib.Encryption.Tests\RockLib.Encryption.Tests.csproj", "{15273990-AECE-4767-A97D-611CBBA04F30}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6E80E23F-3AA5-43DB-ADEC-2918E1787062}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {15273990-AECE-4767-A97D-611CBBA04F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {15273990-AECE-4767-A97D-611CBBA04F30}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {15273990-AECE-4767-A97D-611CBBA04F30}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {15273990-AECE-4767-A97D-611CBBA04F30}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {68B67557-20B2-4F40-9FFB-19BCDBB9AA17} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/Credential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Encryption.Symmetric; 4 | 5 | /// 6 | /// Defines a credential for symmetric encryption. 7 | /// 8 | public sealed class Credential 9 | { 10 | /// 11 | /// Defines the default value of . 12 | /// 13 | public const SymmetricAlgorithm DefaultAlgorithm = SymmetricAlgorithm.Aes; 14 | 15 | /// 16 | /// Defines the default initialization vector size. 17 | /// 18 | public const ushort DefaultIVSize = 16; 19 | private readonly Func _key; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// A function that returns the symmetric key to be returned by the method. 25 | /// The that will be used for a symmetric encryption or decryption operation. 26 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 27 | /// The name of this credential. 28 | /// Whether to cache the value of the function. 29 | public Credential(Func key, SymmetricAlgorithm algorithm = DefaultAlgorithm, ushort ivSize = DefaultIVSize, string? name = null, bool cacheKeyValue = false) 30 | { 31 | if (key is null) 32 | { 33 | throw new ArgumentNullException(nameof(key)); 34 | } 35 | if (!Enum.IsDefined(typeof(SymmetricAlgorithm), algorithm)) 36 | { 37 | // TODO: Throw different exception for invalid enum 38 | throw new ArgumentOutOfRangeException(nameof(algorithm), $"{nameof(algorithm)} value is not defined: {algorithm}."); 39 | } 40 | if (ivSize <= 0) 41 | { 42 | throw new ArgumentOutOfRangeException(nameof(ivSize), $"{nameof(ivSize)} must be greater than 0."); 43 | } 44 | 45 | Algorithm = algorithm; 46 | IVSize = ivSize; 47 | Name = name; 48 | 49 | if (!cacheKeyValue) 50 | { 51 | _key = key; 52 | } 53 | else 54 | { 55 | var k = new Lazy(key); 56 | _key = () => 57 | { 58 | if (k.Value is null) 59 | { 60 | return Array.Empty(); 61 | } 62 | 63 | var copy = new byte[k.Value.Length]; 64 | k.Value.CopyTo(copy, 0); 65 | return copy; 66 | }; 67 | } 68 | } 69 | 70 | /// 71 | /// Gets the name of the credential. 72 | /// 73 | public string? Name { get; } 74 | 75 | /// 76 | /// Gets the that is used for symmetric 77 | /// encryption or decryption operations. 78 | /// 79 | public SymmetricAlgorithm Algorithm { get; } 80 | 81 | /// 82 | /// Gets the size of the initialization vector that is used to add entropy to 83 | /// encryption or decryption operations. 84 | /// 85 | public ushort IVSize { get; } 86 | 87 | /// 88 | /// Gets the plain-text value of the symmetric key that is used for encryption 89 | /// or decryption operations. 90 | /// 91 | /// The symmetric key. 92 | public byte[] GetKey() 93 | { 94 | var key = _key(); 95 | 96 | if (key is null || key.Length == 0) 97 | { 98 | throw new InvalidOperationException("The value returned from the key function must not be null or empty."); 99 | } 100 | 101 | return key; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/DependencyInjection/SymmetricCredentialOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Encryption.Symmetric.DependencyInjection; 4 | 5 | /// 6 | /// Defines a symmetric credential set of options. 7 | /// 8 | public class SymmetricCredentialOptions 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The name of the credential. 14 | /// A symmetric key. 15 | /// The that will be used for a symmetric encryption or decryption operation. 16 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 17 | public SymmetricCredentialOptions(string? credentialName, byte[] key, SymmetricAlgorithm algorithm = Credential.DefaultAlgorithm, ushort ivSize = Credential.DefaultIVSize) 18 | { 19 | CredentialName = credentialName; 20 | Key = key ?? throw new ArgumentNullException(nameof(key)); 21 | Algorithm = algorithm; 22 | IvSize = ivSize; 23 | } 24 | 25 | /// 26 | /// The name of the credential. 27 | /// 28 | public string? CredentialName { get; } 29 | 30 | /// 31 | /// A symmetric key. 32 | /// 33 | #pragma warning disable CA1819 // Properties should not return arrays 34 | public byte[] Key { get; } 35 | #pragma warning restore CA1819 // Properties should not return arrays 36 | 37 | /// 38 | /// The that will be used for a symmetric encryption or decryption operation. 39 | /// 40 | public SymmetricAlgorithm Algorithm { get; } 41 | 42 | /// 43 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 44 | /// 45 | public ushort IvSize { get; } 46 | } 47 | -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/DependencyInjection/SymmetricCryptoBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RockLib.Encryption.Symmetric.DependencyInjection; 5 | 6 | /// 7 | /// A builder used to register a . 8 | /// 9 | public class SymmetricCryptoBuilder 10 | { 11 | private readonly List _credentialOptions = new(); 12 | 13 | /// 14 | /// Adds a credential with the default name to the builder. 15 | /// 16 | /// A symmetric key. 17 | /// The that will be used for a symmetric encryption or decryption operation. 18 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 19 | /// The same . 20 | public SymmetricCryptoBuilder AddCredential(string key, SymmetricAlgorithm algorithm = Credential.DefaultAlgorithm, ushort ivSize = Credential.DefaultIVSize) 21 | { 22 | if (key is null) 23 | { 24 | throw new ArgumentNullException(nameof(key)); 25 | } 26 | 27 | _credentialOptions.Add(new SymmetricCredentialOptions(null, Convert.FromBase64String(key), algorithm, ivSize)); 28 | return this; 29 | } 30 | 31 | /// 32 | /// Adds a credential with the specified name to the builder. 33 | /// 34 | /// The name of this credential. 35 | /// A symmetric key. 36 | /// The that will be used for a symmetric encryption or decryption operation. 37 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 38 | /// The same . 39 | public SymmetricCryptoBuilder AddCredential(string? credentialName, string key, SymmetricAlgorithm algorithm = Credential.DefaultAlgorithm, ushort ivSize = Credential.DefaultIVSize) 40 | { 41 | if (key is null) 42 | { 43 | throw new ArgumentNullException(nameof(key)); 44 | } 45 | 46 | _credentialOptions.Add(new SymmetricCredentialOptions(credentialName, Convert.FromBase64String(key), algorithm, ivSize)); 47 | return this; 48 | } 49 | 50 | /// 51 | /// Adds a credential with the default name to the builder. 52 | /// 53 | /// A symmetric key. 54 | /// The that will be used for a symmetric encryption or decryption operation. 55 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 56 | /// The same . 57 | public SymmetricCryptoBuilder AddCredential(byte[] key, SymmetricAlgorithm algorithm = Credential.DefaultAlgorithm, ushort ivSize = Credential.DefaultIVSize) 58 | { 59 | if (key is null) 60 | { 61 | throw new ArgumentNullException(nameof(key)); 62 | } 63 | 64 | _credentialOptions.Add(new SymmetricCredentialOptions(null, key, algorithm, ivSize)); 65 | return this; 66 | } 67 | 68 | /// 69 | /// Adds a credential with the specified name to the builder. 70 | /// 71 | /// The name of this credential. 72 | /// A symmetric key. 73 | /// The that will be used for a symmetric encryption or decryption operation. 74 | /// The size of the initialization vector that is used to add entropy to encryption or decryption operations. 75 | /// The same . 76 | public SymmetricCryptoBuilder AddCredential(string? credentialName, byte[] key, SymmetricAlgorithm algorithm = Credential.DefaultAlgorithm, ushort ivSize = Credential.DefaultIVSize) 77 | { 78 | if (key is null) 79 | { 80 | throw new ArgumentNullException(nameof(key)); 81 | } 82 | 83 | _credentialOptions.Add(new SymmetricCredentialOptions(credentialName, key, algorithm, ivSize)); 84 | return this; 85 | } 86 | 87 | /// 88 | /// Creates an instance of using the registered credentials. 89 | /// 90 | /// 91 | /// The that retrieves the services required to create the . 92 | /// 93 | /// An instance of . 94 | public SymmetricCrypto Build(IServiceProvider serviceProvider) 95 | { 96 | var credentials = new List(); 97 | 98 | foreach(var option in _credentialOptions) 99 | { 100 | credentials.Add(new Credential(() => option.Key, option.Algorithm, option.IvSize, option.CredentialName)); 101 | } 102 | 103 | return new SymmetricCrypto(credentials); 104 | } 105 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/DependencyInjection/SymmetricCryptoExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace RockLib.Encryption.Symmetric.DependencyInjection; 5 | 6 | /// 7 | /// Defines extension methods for depency injection with . 8 | /// 9 | public static class SymmetricCryptoExtensions 10 | { 11 | /// 12 | /// Adds a to the service collection. 13 | /// 14 | /// The . 15 | /// The . 16 | /// 17 | public static SymmetricCryptoBuilder AddSymmetricCrypto(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton) 18 | { 19 | if (services is null) 20 | { 21 | throw new ArgumentNullException(nameof(services)); 22 | } 23 | 24 | var builder = new SymmetricCryptoBuilder(); 25 | services.Add(new ServiceDescriptor(typeof(ICrypto), builder.Build, lifetime)); 26 | return builder; 27 | } 28 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/ICredentialRepository.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Encryption.Symmetric; 2 | 3 | /// 4 | /// Defines an interface for retrieving symmetric credentials. 5 | /// 6 | public interface ICredentialRepository 7 | { 8 | /// 9 | /// Determines whether the given credential name is found in this repository. 10 | /// 11 | /// The credential name to check. 12 | /// 13 | /// , if the credential exists; otherwise . 14 | /// 15 | bool ContainsCredential(string? credentialName); 16 | 17 | /// 18 | /// Gets the credential by name. 19 | /// 20 | /// The name of the credential to retrieve. 21 | /// The matching credential. 22 | Credential GetCredential(string? credentialName); 23 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/InMemoryCredentialRepository.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Collections; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace RockLib.Encryption.Symmetric; 6 | 7 | /// 8 | /// An implementation of the interface that has 9 | /// a finite collection of objects from which to choose. 10 | /// 11 | public class InMemoryCredentialRepository : ICredentialRepository 12 | { 13 | private readonly NamedCollection _credentials; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// 19 | /// The credentials available for encryption or decryption operations. 20 | /// 21 | public InMemoryCredentialRepository(IEnumerable credentials) => 22 | _credentials = credentials?.ToNamedCollection(c => c.Name!) ?? throw new ArgumentNullException(nameof(credentials)); 23 | 24 | /// 25 | /// Gets the credentials available for encryption or decryption operations. 26 | /// 27 | public IReadOnlyCollection Credentials => _credentials; 28 | 29 | /// 30 | /// Determines whether the given credential name is found in this repository. 31 | /// 32 | /// The credential name to check. 33 | /// 34 | /// , if the credential exists; otherwise . 35 | /// 36 | public bool ContainsCredential(string? credentialName) => _credentials.Contains(credentialName!); 37 | 38 | /// 39 | /// Gets the credential by name. 40 | /// 41 | /// The name of the credential to retrieve. 42 | /// The matching credential. 43 | public Credential GetCredential(string? credentialName) => 44 | _credentials.TryGetValue(credentialName!, out var credential) 45 | ? credential 46 | : throw CredentialNotFound(credentialName!); 47 | 48 | private Exception CredentialNotFound(string credentialName) => 49 | new KeyNotFoundException( 50 | _credentials.IsDefaultName(credentialName) 51 | ? "No default credential was found." 52 | : $"The specified credential was not found: {credentialName}."); 53 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/ProtocolExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RockLib.Encryption.Symmetric; 5 | 6 | internal static class ProtocolExtensions 7 | { 8 | /* Crypto Protocol v1 9 | +---------------+----------------------+---------------------+ 10 | | byte# | size | field | 11 | |---------------+----------------------+---------------------| 12 | | 0 | 1 | version (value = 1) | 13 | | 1 | 2 | iv length | 14 | | 3 | iv length | iv | 15 | | 3 + iv Length | payload size - byte# | ciphertext | 16 | +---------------+----------------------+---------------------+ */ 17 | 18 | public static void WriteCipherTextHeader(this Stream stream, byte[] iv) 19 | { 20 | stream.WriteByte(1); 21 | stream.WriteByte((byte)(iv.Length & 0xFF)); 22 | stream.WriteByte((byte)(iv.Length >> 8)); 23 | stream.Write(iv, 0, iv.Length); 24 | } 25 | 26 | public static byte[] ReadIVFromCipherTextHeader(this Stream stream) 27 | { 28 | var protocolVersion = stream.ReadByte(); 29 | 30 | if (protocolVersion != 1) 31 | { 32 | throw new InvalidOperationException("Unknown protocol version (only version 1 is supported): " + protocolVersion); 33 | } 34 | 35 | var ivSize = (ushort)(stream.ReadByte() | (stream.ReadByte() << 8)); 36 | var iv = new byte[ivSize]; 37 | stream.Read(iv, 0, ivSize); 38 | return iv; 39 | } 40 | 41 | public static bool IsEncrypted(this byte[] cipherText) 42 | { 43 | if (cipherText.Length < 3 || cipherText[0] != 1) 44 | { 45 | return false; 46 | } 47 | 48 | var ivSize = (ushort)(cipherText[1] | (cipherText[2] << 8)); 49 | 50 | return ivSize switch 51 | { 52 | 8 or 16 => cipherText.Length >= 3 + ivSize, 53 | _ => false, 54 | }; 55 | } 56 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/SymmetricAlgorithm.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Encryption.Symmetric; 2 | 3 | /// 4 | /// Defines the supported symmetric algorithms. 5 | /// 6 | public enum SymmetricAlgorithm 7 | { 8 | /// 9 | /// The Advanced Encryption Standard (AES) symmetric algorithm. Corresponds to the 10 | /// class. 11 | /// 12 | Aes, 13 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/SymmetricAlgorithmExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Cryptography; 4 | 5 | namespace RockLib.Encryption.Symmetric; 6 | 7 | internal static class SymmetricAlgorithmExtensions 8 | { 9 | public static System.Security.Cryptography.SymmetricAlgorithm CreateSymmetricAlgorithm( 10 | this SymmetricAlgorithm algorithm) 11 | { 12 | return algorithm switch 13 | { 14 | SymmetricAlgorithm.Aes => Aes.Create(), 15 | _ => throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm, 16 | $@"Invalid SymmetricAlgorithm. Valid values are: { 17 | string.Join(", ", Enum.GetValues(typeof(SymmetricAlgorithm)) 18 | .Cast().Select(x => x.ToString()))}."), 19 | }; 20 | } 21 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/SymmetricCrypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace RockLib.Encryption.Symmetric; 6 | 7 | /// 8 | /// An implementation of that uses the symmetric encryption 9 | /// algorithms that are in the .NET base class library. 10 | /// 11 | public class SymmetricCrypto : ICrypto 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// 17 | /// A collection of credentials that will be available for encryption or decryption 18 | /// operations. 19 | /// 20 | /// 21 | /// The to be used for string/binary conversions. 22 | /// 23 | public SymmetricCrypto(IEnumerable credentials, Encoding? encoding = null) 24 | : this(new InMemoryCredentialRepository(credentials ?? throw new ArgumentNullException(nameof(credentials))), encoding) 25 | { 26 | } 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// 32 | /// The credential repository that determines which credentials will be available for 33 | /// encryption or decryption operations. 34 | /// 35 | /// 36 | /// The to be used for string/binary conversions. 37 | /// 38 | public SymmetricCrypto(ICredentialRepository credentialRepository, Encoding? encoding = null) 39 | { 40 | CredentialRepository = credentialRepository ?? throw new ArgumentNullException(nameof(credentialRepository)); 41 | Encoding = encoding ?? Encoding.UTF8; 42 | } 43 | 44 | /// 45 | /// Gets the credential repository that determines which credentials will be available for 46 | /// encryption or decryption operations. 47 | /// 48 | public ICredentialRepository CredentialRepository { get; } 49 | 50 | /// 51 | /// Gets the to be used for string/binary conversions. 52 | /// 53 | public Encoding Encoding { get; } 54 | 55 | /// 56 | /// Encrypts the specified plain text. 57 | /// 58 | /// The plain text. 59 | /// 60 | /// The name of the credential to use for this encryption operation, 61 | /// or null to use the default credential. 62 | /// 63 | /// The encrypted value as a string. 64 | public string Encrypt(string plainText, string? credentialName) 65 | { 66 | using var encryptor = GetEncryptor(credentialName); 67 | return encryptor.Encrypt(plainText); 68 | } 69 | 70 | /// 71 | /// Decrypts the specified cipher text. 72 | /// 73 | /// The cipher text. 74 | /// 75 | /// The name of the credential to use for this encryption operation, 76 | /// or null to use the default credential. 77 | /// 78 | /// The decrypted value as a string. 79 | public string Decrypt(string cipherText, string? credentialName) 80 | { 81 | using var decryptor = GetDecryptor(credentialName); 82 | return decryptor.Decrypt(cipherText); 83 | } 84 | 85 | /// 86 | /// Encrypts the specified plain text. 87 | /// 88 | /// The plain text. 89 | /// 90 | /// The name of the credential to use for this encryption operation, 91 | /// or null to use the default credential. 92 | /// 93 | /// The encrypted value as a byte array. 94 | public byte[] Encrypt(byte[] plainText, string? credentialName) 95 | { 96 | using var encryptor = GetEncryptor(credentialName); 97 | return encryptor.Encrypt(plainText); 98 | } 99 | 100 | /// 101 | /// Decrypts the specified cipher text. 102 | /// 103 | /// The cipher text. 104 | /// 105 | /// The name of the credential to use for this encryption operation, 106 | /// or null to use the default credential. 107 | /// 108 | /// The decrypted value as a byte array. 109 | public byte[] Decrypt(byte[] cipherText, string? credentialName) 110 | { 111 | using var decryptor = GetDecryptor(credentialName); 112 | return decryptor.Decrypt(cipherText); 113 | } 114 | 115 | /// 116 | /// Gets an instance of for the provided credential name. 117 | /// 118 | /// 119 | /// The name of the credential to use for this encryption operation, 120 | /// or null to use the default credential. 121 | /// 122 | /// An object that can be used for encryption operations. 123 | public IEncryptor GetEncryptor(string? credentialName) => 124 | new SymmetricEncryptor(CredentialRepository.GetCredential(credentialName), Encoding); 125 | 126 | /// 127 | /// Gets an instance of for the provided credential name. 128 | /// 129 | /// 130 | /// The name of the credential to use for this encryption operation, 131 | /// or null to use the default credential. 132 | /// 133 | /// An object that can be used for decryption operations. 134 | public IDecryptor GetDecryptor(string? credentialName) => 135 | new SymmetricDecryptor(CredentialRepository.GetCredential(credentialName), Encoding); 136 | 137 | /// 138 | /// Returns a value indicating whether this instance of 139 | /// is able to handle the provided credential name for an encrypt operation. 140 | /// 141 | /// 142 | /// The credential name to check, or null to check if the default credential exists. 143 | /// 144 | /// 145 | /// True, if this instance can handle the credential name for an encrypt operation. 146 | /// Otherwise, false. 147 | /// 148 | public bool CanEncrypt(string? credentialName) => 149 | CredentialRepository.ContainsCredential(credentialName); 150 | 151 | /// 152 | /// Returns a value indicating whether this instance of 153 | /// is able to handle the provided credential name for an decrypt operation. 154 | /// 155 | /// 156 | /// The credential name to check, or null to check if the default credential exists. 157 | /// 158 | /// 159 | /// True, if this instance can handle the credential name for an encrypt operation. 160 | /// Otherwise, false. 161 | /// 162 | public bool CanDecrypt(string? credentialName) => 163 | CredentialRepository.ContainsCredential(credentialName); 164 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/SymmetricDecryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace RockLib.Encryption.Symmetric; 10 | 11 | /// 12 | /// Defines an object that is capable of decrypting string values and 13 | /// byte[] values using all of the encryption algorithms that are native 14 | /// to the .NET Framework. 15 | /// 16 | public sealed class SymmetricDecryptor : IDecryptor 17 | { 18 | private readonly Credential _credential; 19 | private readonly Encoding _encoding; 20 | private readonly System.Security.Cryptography.SymmetricAlgorithm _algorithm; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// 26 | /// The that determines what kind of encryption operations 27 | /// are to be performed. 28 | /// 29 | /// 30 | /// The that is used to convert a string object to a 31 | /// byte[] value. 32 | /// 33 | public SymmetricDecryptor(Credential credential, Encoding encoding) 34 | { 35 | _credential = credential ?? throw new ArgumentNullException(nameof(credential)); 36 | _encoding = encoding; 37 | _algorithm = credential.Algorithm.CreateSymmetricAlgorithm(); 38 | } 39 | 40 | /// 41 | /// Releases all resources used by the current instance of the 42 | /// class. 43 | /// 44 | public void Dispose() 45 | { 46 | _algorithm.Dispose(); 47 | } 48 | 49 | /// 50 | /// Decrypts the specified cipher text. 51 | /// 52 | /// The cipher text. 53 | /// The decrypted value as a string. 54 | public string Decrypt(string cipherText) 55 | { 56 | if(cipherText is null) 57 | { 58 | throw new ArgumentNullException(nameof(cipherText)); 59 | } 60 | 61 | if (!IsBase64(cipherText)) 62 | { 63 | return cipherText; 64 | } 65 | 66 | var cipherTextData = Convert.FromBase64String(cipherText); 67 | var plainTextData = Decrypt(cipherTextData); 68 | return _encoding.GetString(plainTextData); 69 | } 70 | 71 | /// 72 | /// Decrypts the specified cipher text. 73 | /// 74 | /// The cipher text. 75 | /// The decrypted value as a byte array. 76 | public byte[] Decrypt(byte[] cipherText) 77 | { 78 | if (cipherText is null) 79 | { 80 | throw new ArgumentNullException(nameof(cipherText)); 81 | } 82 | 83 | if (!cipherText.IsEncrypted()) 84 | { 85 | return cipherText; 86 | } 87 | 88 | var decrypted = new List(cipherText.Length); 89 | 90 | using (var stream = new MemoryStream(cipherText)) 91 | { 92 | var decryptor = _algorithm.CreateDecryptor( 93 | _credential.GetKey(), stream.ReadIVFromCipherTextHeader()); 94 | 95 | using var cryptoStream = new CryptoStream(stream, decryptor, CryptoStreamMode.Read); 96 | const int bufferSize = 256; 97 | var buffer = new byte[bufferSize]; 98 | 99 | while (true) 100 | { 101 | var readBytes = cryptoStream.Read(buffer, 0, buffer.Length); 102 | 103 | if (readBytes > 0) 104 | { 105 | decrypted.AddRange(buffer.Take(readBytes)); 106 | } 107 | else 108 | { 109 | break; 110 | } 111 | } 112 | } 113 | 114 | return decrypted.ToArray(); 115 | } 116 | 117 | private static readonly Regex SpaceRegex = new(@"\s"); 118 | private static readonly Regex Base64Regex = new(@"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"); 119 | 120 | private static bool IsBase64(string base64) 121 | { 122 | if (SpaceRegex.IsMatch(base64)) 123 | { 124 | base64 = SpaceRegex.Replace(base64, ""); 125 | } 126 | 127 | if (base64.Length % 4 != 0) 128 | { 129 | return false; 130 | } 131 | 132 | return Base64Regex.IsMatch(base64); 133 | } 134 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Symmetric/SymmetricEncryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | 6 | namespace RockLib.Encryption.Symmetric; 7 | 8 | /// 9 | /// Defines an object that is capable of encrypting string values and 10 | /// byte[] values using all of the encryption algorithms that are native 11 | /// to the .NET Framework. 12 | /// 13 | public sealed class SymmetricEncryptor : IEncryptor 14 | { 15 | private readonly Credential _credential; 16 | private readonly Encoding _encoding; 17 | private readonly System.Security.Cryptography.SymmetricAlgorithm _algorithm; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// 23 | /// The that determines what kind of encryption operations 24 | /// are to be performed. 25 | /// 26 | /// 27 | /// The that is used to convert a string object to a 28 | /// byte[] value. 29 | /// 30 | public SymmetricEncryptor(Credential credential, Encoding encoding) 31 | { 32 | _encoding = encoding; 33 | _credential = credential ?? throw new ArgumentNullException(nameof(credential)); 34 | _algorithm = credential.Algorithm.CreateSymmetricAlgorithm(); 35 | } 36 | 37 | /// 38 | /// Releases all resources used by the current instance of the 39 | /// class. 40 | /// 41 | public void Dispose() 42 | { 43 | _algorithm.Dispose(); 44 | } 45 | 46 | /// 47 | /// Encrypts the specified plain text. 48 | /// 49 | /// The plain text. 50 | /// The encrypted value as a string. 51 | public string Encrypt(string plainText) 52 | { 53 | var plainTextData = _encoding.GetBytes(plainText); 54 | var cipherTextData = Encrypt(plainTextData); 55 | return Convert.ToBase64String(cipherTextData); 56 | } 57 | 58 | /// 59 | /// Encrypts the specified plain text. 60 | /// 61 | /// The plain text. 62 | /// The encrypted value as a byte array. 63 | public byte[] Encrypt(byte[] plainText) 64 | { 65 | if (plainText is null) 66 | { 67 | throw new ArgumentNullException(nameof(plainText)); 68 | } 69 | 70 | using var stream = new MemoryStream(); 71 | var iv = new byte[_credential.IVSize]; 72 | using var random = RandomNumberGenerator.Create(); 73 | random.GetBytes(iv); 74 | 75 | // We know the IV is generated in a non-repeatable way. 76 | #pragma warning disable CA5401 // Do not use CreateEncryptor with non-default IV 77 | var encryptor = _algorithm.CreateEncryptor(_credential.GetKey(), iv); 78 | #pragma warning restore CA5401 // Do not use CreateEncryptor with non-default IV 79 | 80 | stream.WriteCipherTextHeader(iv); 81 | 82 | using (var cryptoStream = new CryptoStream(stream, encryptor, CryptoStreamMode.Write)) 83 | { 84 | cryptoStream.Write(plainText, 0, plainText.Length); 85 | } 86 | 87 | return stream.ToArray(); 88 | } 89 | } -------------------------------------------------------------------------------- /RockLib.Encryption/Testing/FakeCrypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace RockLib.Encryption.Testing; 5 | 6 | /// 7 | /// An implementation of the interface meant for application testing. To 8 | /// encrypt, it surrounds the plain text with [[double square brackets]]. To decrypt, it 9 | /// removes the surrounding double square brackets from the fake cipher text. 10 | /// 11 | public sealed class FakeCrypto : ICrypto 12 | { 13 | /// 14 | /// An implementation of the interface meant for application testing. 15 | /// To encrypt, it surrounds the plain text with [[double square brackets]]. To decrypt, it 16 | /// removes the surrounding double square brackets from the fake cipher text. 17 | /// 18 | public FakeCrypto() { } 19 | 20 | string ICrypto.Encrypt(string plainText, string? credentialName) => Encrypt(plainText); 21 | 22 | string ICrypto.Decrypt(string cipherText, string? credentialName) => Decrypt(cipherText); 23 | 24 | byte[] ICrypto.Encrypt(byte[] plainText, string? credentialName) => Encrypt(plainText); 25 | 26 | byte[] ICrypto.Decrypt(byte[] cipherText, string? credentialName) => Decrypt(cipherText); 27 | 28 | IEncryptor ICrypto.GetEncryptor(string? credentialName) => new FakeEncryptor(); 29 | 30 | IDecryptor ICrypto.GetDecryptor(string? credentialName) => new FakeDecryptor(); 31 | 32 | bool ICrypto.CanDecrypt(string? credentialName) => true; 33 | 34 | bool ICrypto.CanEncrypt(string? credentialName) => true; 35 | 36 | private static string Decrypt(string cipherText) => 37 | Regex.Replace(cipherText, @"^\[\[(.*)\]\]$", match => match.Groups[1].Value); 38 | 39 | private static byte[] Decrypt(byte[] cipherText) 40 | { 41 | if (cipherText.Length >= 4 42 | && cipherText[0] == '[' && cipherText[1] == '[' 43 | && cipherText[cipherText.Length - 2] == ']' && cipherText[cipherText.Length - 1] == ']') 44 | { 45 | var plainText = new byte[cipherText.Length - 4]; 46 | Array.Copy(cipherText, 2, plainText, 0, plainText.Length); 47 | return plainText; 48 | } 49 | 50 | return cipherText; 51 | } 52 | 53 | private static string Encrypt(string plainText) => "[[" + plainText + "]]"; 54 | 55 | private static byte[] Encrypt(byte[] plainText) 56 | { 57 | var cipherText = new byte[plainText.Length + 4]; 58 | 59 | Array.Copy(plainText, 0, cipherText, 2, plainText.Length); 60 | cipherText[0] = cipherText[1] = (int)'['; 61 | cipherText[cipherText.Length - 2] = cipherText[cipherText.Length - 1] = (int)']'; 62 | 63 | return cipherText; 64 | } 65 | 66 | private class FakeDecryptor : IDecryptor 67 | { 68 | string IDecryptor.Decrypt(string cipherText) => Decrypt(cipherText); 69 | 70 | byte[] IDecryptor.Decrypt(byte[] cipherText) => Decrypt(cipherText); 71 | 72 | void IDisposable.Dispose() { } 73 | } 74 | 75 | private class FakeEncryptor : IEncryptor 76 | { 77 | void IDisposable.Dispose() { } 78 | 79 | string IEncryptor.Encrypt(string plainText) => Encrypt(plainText); 80 | 81 | byte[] IEncryptor.Encrypt(byte[] plainText) => Encrypt(plainText); 82 | } 83 | } -------------------------------------------------------------------------------- /RockLib.Encryption/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 'RockLib.Encryption.{build}.0.0-ci' 2 | image: Visual Studio 2022 3 | configuration: Release 4 | only_commits: 5 | files: 6 | - RockLib.Encryption/ 7 | - Tests/RockLib.Encryption.Tests/ 8 | before_build: 9 | - ps: | 10 | # The path to the solution to restore. 11 | $sln_path = "RockLib.Encryption\RockLib.Encryption.sln" 12 | 13 | # The path to the main csproj file. It will be patched during the build. 14 | $csproj_path = "RockLib.Encryption\RockLib.Encryption.csproj" 15 | 16 | # The version of the build artifact's nuget package when created by CI builds. 17 | $ci_package_version = "$Env:appveyor_build_number.0.0-ci" 18 | 19 | # This before_build script does three things: 20 | # 21 | # 1) Synchronize the AppVeyor build version and the csproj's package version. 22 | # a) If the current build is a deploy build*, update the AppVeyor build version to match 23 | # the csproj's nuget package version. 24 | # b) If the current build is a CI build*, update the csproj's package version to match 25 | # the AppVeyor build version. 26 | # 2) Set an AppVeyor build variable named 'csproj_build_version' to the csproj's package version. 27 | # This value is used by deploy providers to determine whether the current commit should be deployed. 28 | # 3) Restore packages for the sln. .NET Standard libraries won't build without restoring first. 29 | # 30 | # *The current build is a deploy build if the 'appveyor_repo_tag' variable is 'true' and the 31 | # 'appveyor_repo_tag_name' variable is the the value of the 'csproj_build_version' variable, as set in 32 | # #2 above. Otherwise, the current build is a CI build. 33 | 34 | function Get-Csproj-Build-Version ($csproj) 35 | { 36 | $package_id = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageId").InnerText 37 | $package_version = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageVersion").InnerText 38 | Return "$package_id.$package_version" 39 | } 40 | 41 | function Synchronize-AppVeyor-And-Csproj-Versions ($csproj) 42 | { 43 | $csproj_build_version = Get-Csproj-Build-Version $csproj 44 | 45 | If ($Env:appveyor_repo_tag -eq "true" -AND $Env:appveyor_repo_tag_name -eq $csproj_build_version) { 46 | # If this is a deploy build, update the AppVeyor build version to match the csproj's package version. 47 | Update-AppVeyorBuild -Version $csproj_build_version 48 | } else { 49 | # Else, update the csproj's package version to match the AppVeyor build version. 50 | $package_version_node = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageVersion") 51 | $package_version_node.InnerText = $ci_package_version 52 | } 53 | } 54 | 55 | function Set-Csproj-Build-Version-Variable ($csproj) 56 | { 57 | $csproj_build_version = Get-Csproj-Build-Version $csproj 58 | Set-AppVeyorBuildVariable -Name "csproj_build_version" -Value $csproj_build_version 59 | } 60 | 61 | # The $csproj xml object contains the contents of the csproj file. 62 | $csproj = [xml](Get-Content $csproj_path) 63 | 64 | Synchronize-AppVeyor-And-Csproj-Versions $csproj 65 | Set-Csproj-Build-Version-Variable $csproj 66 | 67 | # Patch the csproj file with the modified xml object after all changes have been made. 68 | $csproj.Save((Get-Item $csproj_path)) 69 | 70 | nuget restore $sln_path 71 | build: 72 | project: RockLib.Encryption\RockLib.Encryption.sln 73 | verbosity: minimal 74 | artifacts: 75 | - path: '**/$(csproj_build_version).nupkg' 76 | deploy: 77 | - provider: GitHub 78 | tag: $(appveyor_repo_tag_name) 79 | release: $(appveyor_repo_commit_message) 80 | description: 'A changelog is available at https://github.com/RockLib/RockLib.Encryption/blob/main/RockLib.Encryption/CHANGELOG.md.' 81 | auth_token: 82 | secure: gglgEqQVi2MuIUl8g8rS6jb5r8sgr0PYa4qXq9XaAGeAJ+oAEFmlNFFw/jgX8hQr 83 | on: 84 | appveyor_repo_tag: true 85 | appveyor_repo_tag_name: $(csproj_build_version) 86 | - provider: NuGet 87 | api_key: 88 | secure: d662rvY3udud0hnjkQv3mXwAvidAylYGnl8biGLb7/5Fb3mD+tJIfkY7/Y74cEMR 89 | on: 90 | appveyor_repo_tag: true 91 | appveyor_repo_tag_name: $(csproj_build_version) 92 | notifications: 93 | - provider: Email 94 | to: 95 | - rocklibsupport@rocketmortgage.com 96 | on_build_success: false 97 | on_build_failure: false 98 | on_build_status_changed: true 99 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/CredentialTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using RockLib.Encryption.Symmetric; 4 | using Xunit; 5 | 6 | namespace RockLib.Encryption.Tests; 7 | 8 | public static class CredentialTests 9 | { 10 | [Fact] 11 | public static void CanGetKey() 12 | { 13 | var credential = new Credential( 14 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s="), SymmetricAlgorithm.Aes, 16); 15 | 16 | var key = credential.GetKey(); 17 | 18 | key.Should().NotBeNull(); 19 | key.Length.Should().BeGreaterThan(0); 20 | } 21 | 22 | [Fact] 23 | public static void CanGetAlgorithm() 24 | { 25 | var credential = new Credential( 26 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s="), SymmetricAlgorithm.Aes, 16); 27 | 28 | var algorithm = credential.Algorithm; 29 | 30 | algorithm.Should().Be(SymmetricAlgorithm.Aes); 31 | } 32 | 33 | [Fact] 34 | public static void CanGetIVSize() 35 | { 36 | var credential = new Credential( 37 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s="), SymmetricAlgorithm.Aes, 32); 38 | 39 | var ivSize = credential.IVSize; 40 | 41 | ivSize.Should().Be(32); 42 | } 43 | 44 | [Fact] 45 | public static void DefaultAlgorithmIsCorrect() 46 | { 47 | var credential = new Credential( 48 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=")); 49 | 50 | var algorithm = credential.Algorithm; 51 | 52 | algorithm.Should().Be(Credential.DefaultAlgorithm); 53 | } 54 | 55 | [Fact] 56 | public static void DefaultIVSizeIsCorrect() 57 | { 58 | var credential = new Credential( 59 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=")); 60 | 61 | var ivSize = credential.IVSize; 62 | 63 | ivSize.Should().Be(Credential.DefaultIVSize); 64 | } 65 | 66 | [Fact] 67 | public static void NullKeyThrowsArgumentNullException() 68 | { 69 | var newCredential = () => new Credential(null!, SymmetricAlgorithm.Aes, 16); 70 | newCredential.Should().Throw(); 71 | } 72 | 73 | [Fact] 74 | public static void NullKeyValueThrowsInvalidOperationException() 75 | { 76 | var credential = new Credential(() => null!, SymmetricAlgorithm.Aes, 16); 77 | var getKey = () => credential.GetKey(); 78 | getKey.Should().Throw().WithMessage("The value returned from the key function must not be null or empty."); 79 | } 80 | 81 | [Fact] 82 | public static void EmptyKeyValueThrowsInvalidOperationException() 83 | { 84 | var credential = new Credential(() => Array.Empty(), SymmetricAlgorithm.Aes, 16); 85 | Action getKey = () => credential.GetKey(); 86 | getKey.Should().Throw().WithMessage("The value returned from the key function must not be null or empty."); 87 | } 88 | 89 | [Fact] 90 | public static void UndefinedAlgorithmThrowsArgumentOutOfRangeException() 91 | { 92 | var newCredential = () => new Credential( 93 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s="), (SymmetricAlgorithm)(-1), 16); 94 | newCredential.Should().Throw().WithMessage("algorithm value is not defined: -1.*Parameter*algorithm*"); 95 | } 96 | 97 | [Fact] 98 | public static void InvalidIvSizeThrowsArgumentOutOfRangeException() 99 | { 100 | var newCredential = () => new Credential( 101 | () => Convert.FromBase64String("1J9Og / OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s="), SymmetricAlgorithm.Aes, 0); 102 | 103 | newCredential.Should().Throw().WithMessage("ivSize must be greater than 0.*Parameter*ivSize*"); 104 | } 105 | } -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/CryptoReset.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Encryption.Tests; 2 | 3 | internal static class CryptoReset 4 | { 5 | internal static readonly object Locker = new(); 6 | } -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/CryptoTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using FluentAssertions; 4 | using Microsoft.Extensions.Configuration; 5 | using Moq; 6 | using RockLib.Configuration; 7 | using RockLib.Encryption.Symmetric; 8 | using RockLib.Immutable; 9 | using Xunit; 10 | 11 | namespace RockLib.Encryption.Tests; 12 | 13 | public static class CryptoTests 14 | { 15 | [Fact] 16 | public static void CurrentReturnsSameAsSetCurrent() 17 | { 18 | lock (CryptoReset.Locker) 19 | { 20 | ResetCrypto(); 21 | 22 | var crypto = new Mock().Object; 23 | 24 | Crypto.SetCurrent(crypto); 25 | 26 | Crypto.Current.Should().BeSameAs(crypto); 27 | } 28 | } 29 | 30 | [Fact] 31 | public static void MissingConfigThrowsWhenUsingDefaultCrypto() 32 | { 33 | lock (CryptoReset.Locker) 34 | { 35 | ResetConfig(); 36 | 37 | var configBuilder = new ConfigurationBuilder(); 38 | configBuilder.AddJsonFile("NoFactories.json"); 39 | Config.SetRoot(configBuilder.Build()); 40 | 41 | ResetCrypto(); 42 | 43 | Crypto.SetCurrent(null!); 44 | var action = () => { var current = Crypto.Current; }; 45 | 46 | action.Should().Throw() 47 | .WithMessage("No crypto implementations found in config. See the Readme.md file for details on how to setup the configuration."); 48 | } 49 | } 50 | 51 | [Fact] 52 | public static void SingleFactoryCreatesSpecificCrypto() 53 | { 54 | lock (CryptoReset.Locker) 55 | { 56 | ResetConfig(); 57 | 58 | var configBuilder = new ConfigurationBuilder(); 59 | configBuilder.AddJsonFile("SingleFactory.json"); 60 | Config.SetRoot(configBuilder.Build()); 61 | 62 | ResetCrypto(); 63 | 64 | Crypto.SetCurrent(null!); 65 | 66 | Crypto.Current.Should().BeAssignableTo(); 67 | } 68 | } 69 | 70 | [Fact] 71 | public static void MultipleFactoriesCreatesCompositeCrypto() 72 | { 73 | lock (CryptoReset.Locker) 74 | { 75 | ResetConfig(); 76 | 77 | var configBuilder = new ConfigurationBuilder(); 78 | configBuilder.AddJsonFile("MultiFactory.json"); 79 | Config.SetRoot(configBuilder.Build()); 80 | 81 | ResetCrypto(); 82 | 83 | Crypto.SetCurrent(null!); 84 | 85 | Crypto.Current.Should().BeAssignableTo(); 86 | } 87 | } 88 | 89 | [Fact] 90 | public static void GetEncryptorCallsCryptoGetEncryptor() 91 | { 92 | lock (CryptoReset.Locker) 93 | { 94 | var cryptoMock = new Mock(); 95 | 96 | ResetCrypto(); 97 | Crypto.SetCurrent(cryptoMock.Object); 98 | 99 | var credentialName = "foo"; 100 | 101 | Crypto.GetEncryptor(); 102 | Crypto.GetEncryptor(credentialName); 103 | 104 | cryptoMock.Verify(cm => cm.GetEncryptor(null)); 105 | cryptoMock.Verify(cm => cm.GetEncryptor(It.Is(o => o == credentialName))); 106 | } 107 | } 108 | 109 | [Fact] 110 | public static void GetDecryptorCallsCryptoGetDecryptor() 111 | { 112 | lock (CryptoReset.Locker) 113 | { 114 | var cryptoMock = new Mock(); 115 | 116 | ResetCrypto(); 117 | Crypto.SetCurrent(cryptoMock.Object); 118 | 119 | var credentialName = "foo"; 120 | 121 | Crypto.GetDecryptor(); 122 | Crypto.GetDecryptor(credentialName); 123 | 124 | cryptoMock.Verify(cm => cm.GetDecryptor(null)); 125 | cryptoMock.Verify(cm => cm.GetDecryptor(It.Is(o => o == credentialName))); 126 | } 127 | } 128 | 129 | [Fact] 130 | public static void EncryptByStringCallsCryptoEncryptByString() 131 | { 132 | lock (CryptoReset.Locker) 133 | { 134 | var cryptoMock = new Mock(); 135 | 136 | ResetCrypto(); 137 | Crypto.SetCurrent(cryptoMock.Object); 138 | 139 | var stringToEncrypt = "Something to encrypt"; 140 | var credentialName = "foo"; 141 | 142 | Crypto.Encrypt(stringToEncrypt); 143 | Crypto.Encrypt(stringToEncrypt, credentialName); 144 | 145 | cryptoMock.Verify(cm => cm.Encrypt(It.Is(s => s == stringToEncrypt), null)); 146 | cryptoMock.Verify(cm => cm.Encrypt(It.Is(s => s == stringToEncrypt), It.Is(o => o == credentialName))); 147 | } 148 | } 149 | 150 | [Fact] 151 | public static void EncryptByByteArrayCallsCryptoEncryptByByteArray() 152 | { 153 | lock (CryptoReset.Locker) 154 | { 155 | var cryptoMock = new Mock(); 156 | 157 | ResetCrypto(); 158 | Crypto.SetCurrent(cryptoMock.Object); 159 | 160 | var byteArrayToEncrypt = Array.Empty(); 161 | var credentialName = "foo"; 162 | 163 | Crypto.Encrypt(byteArrayToEncrypt); 164 | Crypto.Encrypt(byteArrayToEncrypt, credentialName); 165 | 166 | cryptoMock.Verify(cm => cm.Encrypt(It.Is(s => s == byteArrayToEncrypt), null)); 167 | cryptoMock.Verify(cm => cm.Encrypt(It.Is(s => s == byteArrayToEncrypt), It.Is(o => o == credentialName))); 168 | } 169 | } 170 | 171 | [Fact] 172 | public static void DecryptByStringCallsCryptoDecryptByString() 173 | { 174 | lock (CryptoReset.Locker) 175 | { 176 | var cryptoMock = new Mock(); 177 | 178 | ResetCrypto(); 179 | Crypto.SetCurrent(cryptoMock.Object); 180 | 181 | var stringToDecrypt = "Something to encrypt"; 182 | var credentialName = "foo"; 183 | 184 | Crypto.Decrypt(stringToDecrypt); 185 | Crypto.Decrypt(stringToDecrypt, credentialName); 186 | 187 | cryptoMock.Verify(cm => cm.Decrypt(It.Is(s => s == stringToDecrypt), null)); 188 | cryptoMock.Verify(cm => cm.Decrypt(It.Is(s => s == stringToDecrypt), It.Is(o => o == credentialName))); 189 | } 190 | } 191 | 192 | [Fact] 193 | public static void DecryptByByteArrayCallsCryptoDecryptByByteArray() 194 | { 195 | lock (CryptoReset.Locker) 196 | { 197 | var cryptoMock = new Mock(); 198 | 199 | ResetCrypto(); 200 | Crypto.SetCurrent(cryptoMock.Object); 201 | 202 | var byteArrayToDecrypt = Array.Empty(); 203 | var credentialName = "foo"; 204 | 205 | Crypto.Decrypt(byteArrayToDecrypt); 206 | Crypto.Decrypt(byteArrayToDecrypt, credentialName); 207 | 208 | cryptoMock.Verify(cm => cm.Decrypt(It.Is(s => s == byteArrayToDecrypt), null)); 209 | cryptoMock.Verify(cm => cm.Decrypt(It.Is(s => s == byteArrayToDecrypt), It.Is(o => o == credentialName))); 210 | } 211 | } 212 | 213 | private static void ResetConfig() 214 | { 215 | var rootField = typeof(Config).GetField("_root", BindingFlags.NonPublic | BindingFlags.Static)!; 216 | var root = (Semimutable)rootField.GetValue(null!)!; 217 | root.GetUnlockValueMethod().Invoke(root, null); 218 | } 219 | 220 | internal static void ResetCrypto() 221 | { 222 | var currentField = typeof(Crypto).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static)!; 223 | var current = (Semimutable)currentField.GetValue(null!)!; 224 | current.GetUnlockValueMethod().Invoke(current, null); 225 | } 226 | } -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/DependencyInjection/DependencyInjectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Moq; 4 | using RockLib.Encryption.DependencyInjection; 5 | using System; 6 | using Xunit; 7 | 8 | namespace RockLib.Encryption.Tests.DependencyInjection; 9 | 10 | public static class DependencyInjectionExtensionsTests 11 | { 12 | [Fact(DisplayName = "AddCrypto Extension 1 throws when the services parameter is null")] 13 | public static void AddCryptoExtension1SadPath() 14 | { 15 | IServiceCollection services = null!; 16 | 17 | Action act = () => services.AddCrypto(); 18 | 19 | act.Should().ThrowExactly().Which.Message.Should().Contain("services"); 20 | } 21 | 22 | [Fact(DisplayName = "AddCrypto Extension 2 adds the crypto parameter to the service collection")] 23 | public static void AddCryptoExtension2HappyPath() 24 | { 25 | var mockCrypto = new Mock(); 26 | 27 | var services = new ServiceCollection(); 28 | 29 | services.AddCrypto(mockCrypto.Object); 30 | 31 | var provider = services.BuildServiceProvider(); 32 | 33 | var crypto = provider.GetRequiredService(); 34 | 35 | crypto.Should().BeSameAs(mockCrypto.Object); 36 | } 37 | 38 | [Fact(DisplayName = "AddCrypto Extension 2 throws when the services parameter is null")] 39 | public static void AddCryptoExtension2SadPath1() 40 | { 41 | var mockCrypto = new Mock(); 42 | 43 | IServiceCollection services = null!; 44 | 45 | Action act = () => services.AddCrypto(mockCrypto.Object); 46 | 47 | act.Should().ThrowExactly().Which.Message.Should().Contain("services"); 48 | } 49 | 50 | [Fact(DisplayName = "AddCrypto Extension 2 throws when the crypto parameter is null")] 51 | public static void AddCryptoExtension2SadPath2() 52 | { 53 | ICrypto crypto = null!; 54 | 55 | var services = new ServiceCollection(); 56 | 57 | Action act = () => services.AddCrypto(crypto); 58 | 59 | act.Should().ThrowExactly().Which.Message.Should().Contain("crypto"); 60 | } 61 | 62 | [Fact(DisplayName = "AddCrypto Extension 3 adds the ICrypto returned by the cryptoFactory parameter to the service collection")] 63 | public static void AddCryptoExtension3HappyPath() 64 | { 65 | var mockCrypto = new Mock(); 66 | Func cryptoFactory = sp => mockCrypto.Object; 67 | 68 | var services = new ServiceCollection(); 69 | 70 | services.AddCrypto(cryptoFactory); 71 | 72 | var provider = services.BuildServiceProvider(); 73 | 74 | var crypto = provider.GetRequiredService(); 75 | 76 | crypto.Should().BeSameAs(mockCrypto.Object); 77 | } 78 | 79 | [Fact(DisplayName = "AddCrypto Extension 3 throws when the services parameter is null")] 80 | public static void AddCryptoExtension3SadPath1() 81 | { 82 | var mockCrypto = new Mock(); 83 | Func cryptoFactory = provider => mockCrypto.Object; 84 | 85 | IServiceCollection services = null!; 86 | 87 | Action act = () => services.AddCrypto(cryptoFactory); 88 | 89 | act.Should().ThrowExactly().Which.Message.Should().Contain("services"); 90 | } 91 | 92 | [Fact(DisplayName = "AddCrypto Extension 3 throws when the cryptoFactory parameter is null")] 93 | public static void AddCryptoExtension3SadPath2() 94 | { 95 | Func cryptoFactory = null!; 96 | 97 | var services = new ServiceCollection(); 98 | 99 | Action act = () => services.AddCrypto(cryptoFactory); 100 | 101 | act.Should().ThrowExactly().Which.Message.Should().Contain("cryptoFactory"); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/FakeCryptoTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using RockLib.Encryption.Testing; 3 | using Xunit; 4 | 5 | namespace RockLib.Encryption.Tests; 6 | 7 | public static class FakeCryptoTests 8 | { 9 | [Fact] 10 | public static void EncryptStringAddsDoubleSquareBrackets() 11 | { 12 | var crypto = new FakeCrypto(); 13 | 14 | var plainText = "Hello, world!"; 15 | 16 | var cipherText = crypto.Encrypt(plainText); 17 | 18 | cipherText.Should().Be("[[Hello, world!]]"); 19 | } 20 | 21 | [Fact] 22 | public static void DecryptStringRemovesDoubleSquareBrackets() 23 | { 24 | var crypto = new FakeCrypto(); 25 | 26 | var cipherText = "[[Hello, world!]]"; 27 | 28 | var plainText = crypto.Decrypt(cipherText); 29 | 30 | plainText.Should().Be("Hello, world!"); 31 | } 32 | 33 | [Fact] 34 | public static void DecryptStringReturnsTheCipherTextIfNotSurroundedByDoubleSquareBrackets() 35 | { 36 | var crypto = new FakeCrypto(); 37 | 38 | var cipherText = "Hello, world!"; 39 | 40 | var plainText = crypto.Decrypt(cipherText); 41 | 42 | plainText.Should().BeSameAs(cipherText); 43 | } 44 | 45 | [Fact] 46 | public static void EncryptBinaryAddsDoubleSquareBrackets() 47 | { 48 | var crypto = new FakeCrypto(); 49 | 50 | var plainText = new byte[] { 1, 2, 3 }; 51 | 52 | var cipherText = crypto.Encrypt(plainText); 53 | 54 | cipherText.Should().BeEquivalentTo(new byte[] { (int)'[', (int)'[', 1, 2, 3, (int)']', (int)']' }); 55 | } 56 | 57 | [Fact] 58 | public static void DecryptBinaryRemovesDoubleSquareBrackets() 59 | { 60 | var crypto = new FakeCrypto(); 61 | 62 | var cipherText = new byte[] { (int)'[', (int)'[', 1, 2, 3, (int)']', (int)']' }; 63 | 64 | var plainText = crypto.Decrypt(cipherText); 65 | 66 | plainText.Should().BeEquivalentTo(new byte[] { 1, 2, 3 }); 67 | } 68 | 69 | [Fact] 70 | public static void DecryptBinaryReturnsTheCipherTextIfNotSurroundedByDoubleSquareBrackets() 71 | { 72 | var crypto = new FakeCrypto(); 73 | 74 | var cipherText = new byte[] { 1, 2, 3 }; 75 | 76 | var plainText = crypto.Decrypt(cipherText); 77 | 78 | plainText.Should().BeSameAs(cipherText); 79 | } 80 | 81 | [Fact] 82 | public static void GetEncryptorReturnsInstanceThatAddsDoubleSquareBrackets() 83 | { 84 | var crypto = new FakeCrypto(); 85 | 86 | var encryptor = crypto.GetEncryptor(); 87 | 88 | var plainTextString = "Hello, world!"; 89 | var plainTextBinary = new byte[] { 1, 2, 3 }; 90 | 91 | var cipherTextString = encryptor.Encrypt(plainTextString); 92 | var cipherTextBinary = encryptor.Encrypt(plainTextBinary); 93 | 94 | cipherTextString.Should().Be("[[Hello, world!]]"); 95 | cipherTextBinary.Should().BeEquivalentTo(new byte[] { (int)'[', (int)'[', 1, 2, 3, (int)']', (int)']' }); 96 | } 97 | 98 | [Fact] 99 | public static void GetDecryptorReturnsInstanceThatRemovesDoubleSquareBrackets() 100 | { 101 | var crypto = new FakeCrypto(); 102 | 103 | var decryptor = crypto.GetDecryptor(); 104 | 105 | var cipherTextString = "[[Hello, world!]]"; 106 | var cipherTextBinary = new byte[] { (int)'[', (int)'[', 1, 2, 3, (int)']', (int)']' }; 107 | 108 | var plainTextString = decryptor.Decrypt(cipherTextString); 109 | var plainTextBinary = decryptor.Decrypt(cipherTextBinary); 110 | 111 | plainTextString.Should().Be("Hello, world!"); 112 | plainTextBinary.Should().BeEquivalentTo(new byte[] { 1, 2, 3 }); 113 | } 114 | 115 | [Theory] 116 | [InlineData("default")] 117 | [InlineData("literally any string")] 118 | [InlineData("")] 119 | [InlineData(null)] 120 | public static void CanEncryptReturnsTrue(string credentialName) 121 | { 122 | ICrypto crypto = new FakeCrypto(); 123 | 124 | crypto.CanEncrypt(credentialName).Should().BeTrue(); 125 | } 126 | 127 | [Theory] 128 | [InlineData("default")] 129 | [InlineData("literally any string")] 130 | [InlineData("")] 131 | [InlineData(null)] 132 | public static void CanDecryptReturnsTrue(string credentialName) 133 | { 134 | ICrypto crypto = new FakeCrypto(); 135 | 136 | crypto.CanDecrypt(credentialName).Should().BeTrue(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/MultiFactory.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Encryption": [ 3 | { 4 | "Type": "RockLib.Encryption.Symmetric.SymmetricCrypto, RockLib.Encryption", 5 | "Value": { 6 | "Encoding": "ascii", 7 | "Credentials": { 8 | "Name": "Default", 9 | "Algorithm": "Aes", 10 | "IVSize": "16", 11 | "Key": "1J9Og/OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=" 12 | } 13 | } 14 | }, 15 | { 16 | "Type": "RockLib.Encryption.Symmetric.SymmetricCrypto, RockLib.Encryption", 17 | "Value": { 18 | "Encoding": "utf-16", 19 | "Credentials": { 20 | "Name": "Default", 21 | "Algorithm": "Aes", 22 | "IVSize": "16", 23 | "Key": "1J9Og/OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=" 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/NoFactories.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Encryption": [] 3 | } 4 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/RockLib.Encryption.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | PreserveNewest 32 | 33 | 34 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/SingleFactory.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Encryption": { 3 | "Type": "RockLib.Encryption.Symmetric.SymmetricCrypto, RockLib.Encryption", 4 | "Value": { 5 | "Encoding": "ascii", 6 | "Credentials": { 7 | "Name": "Default", 8 | "Algorithm": "Aes", 9 | "IVSize": "16", 10 | "Key": "1J9Og/OaZKWdfdwM6jWMpvlr3q3o7r20xxFDN7TEj6s=" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/SymmetricCryptoTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using FluentAssertions; 5 | using RockLib.Encryption.Symmetric; 6 | using Xunit; 7 | 8 | namespace RockLib.Encryption.Tests; 9 | 10 | public static class SymmetricCryptoTests 11 | { 12 | private static readonly byte[] Key = GetSequentialByteArray(16); 13 | 14 | [Fact] 15 | public static void CanEncryptDecryptAes() 16 | { 17 | var credential = new Credential(() => Key, SymmetricAlgorithm.Aes, 16); 18 | var crypto = new SymmetricCrypto(new[] { credential }); 19 | 20 | var plainText = "This is just some random text to encrypt/decrypt"; 21 | var encrypted = crypto.Encrypt(plainText, null); 22 | var decrypted = crypto.Decrypt(encrypted, null); 23 | 24 | encrypted.Should().NotBe(plainText); 25 | decrypted.Should().NotBe(encrypted); 26 | decrypted.Should().Be(plainText); 27 | } 28 | 29 | [Fact] 30 | public static void CanGetSpecificEncryptorAndDecryptorWhenMultipleCredentialsExist() 31 | { 32 | var defaultCredential = new Credential(() => Key); 33 | var credential1 = new Credential(() => Key, name: "encryptor1"); 34 | var credential2 = new Credential(() => Key, name: "encryptor2"); 35 | 36 | var crypto = new SymmetricCrypto(new[] { defaultCredential, credential1, credential2 }); 37 | 38 | crypto.CanEncrypt(null).Should().Be(true); 39 | crypto.CanEncrypt("encryptor1").Should().Be(true); 40 | crypto.CanEncrypt("encryptor2").Should().Be(true); 41 | crypto.CanEncrypt("encryptor3").Should().Be(false); 42 | crypto.CanEncrypt("something").Should().Be(false); 43 | 44 | crypto.GetEncryptor(null).Should().NotBe(null); 45 | crypto.GetEncryptor("encryptor1").Should().NotBe(null); 46 | crypto.GetEncryptor("encryptor2").Should().NotBe(null); 47 | crypto.Invoking(c => c.GetEncryptor("encryptor3")).Should().Throw().WithMessage("The specified credential was not found: encryptor3."); 48 | crypto.Invoking(c => c.GetEncryptor("something")).Should().Throw().WithMessage("The specified credential was not found: something."); 49 | 50 | crypto.CanDecrypt(null).Should().Be(true); 51 | crypto.CanDecrypt("encryptor1").Should().Be(true); 52 | crypto.CanDecrypt("encryptor2").Should().Be(true); 53 | crypto.CanDecrypt("encryptor3").Should().Be(false); 54 | crypto.CanDecrypt("something").Should().Be(false); 55 | 56 | crypto.GetDecryptor(null).Should().NotBe(null); 57 | crypto.GetDecryptor("encryptor1").Should().NotBe(null); 58 | crypto.GetDecryptor("encryptor2").Should().NotBe(null); 59 | crypto.Invoking(c => c.GetDecryptor("encryptor3")).Should().Throw().WithMessage("The specified credential was not found: encryptor3."); 60 | crypto.Invoking(c => c.GetDecryptor("something")).Should().Throw().WithMessage("The specified credential was not found: something."); 61 | } 62 | 63 | [Fact] 64 | public static void EncodingIsSetCorrectly() 65 | { 66 | var crypto = new SymmetricCrypto(Array.Empty(), Encoding.ASCII); 67 | crypto.Encoding.Should().Be(Encoding.ASCII); 68 | } 69 | 70 | private static byte[] GetSequentialByteArray(int size) 71 | { 72 | #if NET6_0 73 | return System.Security.Cryptography.RandomNumberGenerator.GetBytes(size); 74 | #else 75 | var data = new byte[size]; 76 | using var random = System.Security.Cryptography.RandomNumberGenerator.Create(); 77 | random.GetBytes(data); 78 | return data; 79 | #endif 80 | } 81 | } -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/SymmetricDecryptorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using FluentAssertions; 4 | using RockLib.Encryption.Symmetric; 5 | using Xunit; 6 | 7 | namespace RockLib.Encryption.Tests; 8 | 9 | public static class SymmetricDecryptorTests 10 | { 11 | [Fact] 12 | public static void CanDecryptByString() 13 | { 14 | var credential = new Credential( 15 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 16 | SymmetricAlgorithm.Aes, 16); 17 | 18 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 19 | 20 | var encrypted = "ARAAR0wt0bewMNdNByQ5OuJmKj6AfWMNWYSIrPaLR0h/bBF4fcSjCXwJrxZ1upPDByFp"; 21 | var decrypted = symmetricDecryptor.Decrypt(encrypted); 22 | 23 | decrypted.Should().NotBeNullOrEmpty(); 24 | decrypted.Should().NotBe(encrypted); 25 | } 26 | 27 | [Fact] 28 | public static void CanDecryptByByteArray() 29 | { 30 | var credential = new Credential( 31 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 32 | SymmetricAlgorithm.Aes, 16); 33 | 34 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 35 | 36 | var encryptedString = "ARAAR0wt0bewMNdNByQ5OuJmKj6AfWMNWYSIrPaLR0h/bBF4fcSjCXwJrxZ1upPDByFp"; 37 | var encrypted = Convert.FromBase64String(encryptedString); 38 | var decrypted = symmetricDecryptor.Decrypt(encrypted); 39 | var decryptedString = Encoding.UTF8.GetString(decrypted); 40 | 41 | decrypted.Should().NotBeEmpty(); 42 | decryptedString.Should().NotBeNullOrEmpty(); 43 | decryptedString.Should().NotBe(encryptedString); 44 | } 45 | 46 | [Fact] 47 | public static void DecryptByStringReturnsTheCipherTextParameterWhenItIsNotBase64Encoded() 48 | { 49 | var credential = new Credential( 50 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 51 | SymmetricAlgorithm.Aes, 16); 52 | 53 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 54 | 55 | var plaintext = "This is not a base-64 encoded string."; 56 | var decrypted = symmetricDecryptor.Decrypt(plaintext); 57 | 58 | decrypted.Should().BeSameAs(plaintext); 59 | } 60 | 61 | [Fact] 62 | public static void DecryptByByteArrayReturnsTheCipherTextParameterWhenItIsNotLongEnoughForTheHeader() 63 | { 64 | var credential = new Credential( 65 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 66 | SymmetricAlgorithm.Aes, 16); 67 | 68 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 69 | 70 | var plaintext = new byte[] { 1, 16 }; 71 | var decrypted = symmetricDecryptor.Decrypt(plaintext); 72 | 73 | decrypted.Should().BeSameAs(plaintext); 74 | } 75 | 76 | [Fact] 77 | public static void DecryptByByteArrayReturnsTheCipherTextParameterWhenTheVersionIsNot1() 78 | { 79 | var credential = new Credential( 80 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 81 | SymmetricAlgorithm.Aes, 16); 82 | 83 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 84 | 85 | for (int i = 0; i < 256; i++) 86 | { 87 | if (i == 1) continue; 88 | 89 | var plaintext = new byte[] { (byte)i, 16, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; 90 | var decrypted = symmetricDecryptor.Decrypt(plaintext); 91 | 92 | decrypted.Should().BeSameAs(plaintext); 93 | } 94 | } 95 | 96 | [Fact] 97 | public static void DecryptByByteArrayReturnsTheCipherTextParameterWhenTheIVSizeIsNot8Or16() 98 | { 99 | var credential = new Credential( 100 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 101 | SymmetricAlgorithm.Aes, 16); 102 | 103 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 104 | 105 | for (int i = 0; i < 256; i++) 106 | { 107 | for (int j = 0; j < 256; j++) 108 | { 109 | if ((i == 8 || i == 16) && j == 0) continue; 110 | 111 | var plaintext = new byte[] { 1, (byte)i, (byte)j, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; 112 | var decrypted = symmetricDecryptor.Decrypt(plaintext); 113 | 114 | decrypted.Should().BeSameAs(plaintext); 115 | } 116 | } 117 | } 118 | 119 | [Fact] 120 | public static void DecryptByByteArrayReturnsTheCipherTextParameterWhenItIsNotLongEnoughForTheHeaderAndIV() 121 | { 122 | var credential = new Credential( 123 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 124 | SymmetricAlgorithm.Aes, 16); 125 | 126 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 127 | 128 | var plaintext = new byte[] { 1, 16, 0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; 129 | var decrypted = symmetricDecryptor.Decrypt(plaintext); 130 | 131 | decrypted.Should().BeSameAs(plaintext); 132 | 133 | plaintext = new byte[] { 1, 8, 0, 6, 5, 4, 3, 2, 1, 0 }; 134 | decrypted = symmetricDecryptor.Decrypt(plaintext); 135 | 136 | decrypted.Should().BeSameAs(plaintext); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/SymmetricEncryptorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using FluentAssertions; 3 | using RockLib.Encryption.Symmetric; 4 | using Xunit; 5 | 6 | namespace RockLib.Encryption.Tests; 7 | 8 | public static class SymmetricEncryptorTests 9 | { 10 | [Fact] 11 | public static void CanEncryptByString() 12 | { 13 | var credential = new Credential( 14 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 15 | SymmetricAlgorithm.Aes, 16); 16 | 17 | using var symmetricEncryptor = new SymmetricEncryptor(credential, Encoding.UTF8); 18 | 19 | var unencrypted = "This is some string"; 20 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 21 | 22 | encrypted.Should().NotBeNullOrEmpty(); 23 | encrypted.Should().NotBe(unencrypted); 24 | } 25 | 26 | [Fact] 27 | public static void CanEncryptByByteArray() 28 | { 29 | var credential = new Credential( 30 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 31 | SymmetricAlgorithm.Aes, 16); 32 | 33 | using var symmetricEncryptor = new SymmetricEncryptor(credential, Encoding.UTF8); 34 | 35 | var unencryptedString = "This is some string"; 36 | var unencrypted = Encoding.UTF8.GetBytes(unencryptedString); 37 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 38 | var encryptedString = Encoding.UTF8.GetString(encrypted); 39 | 40 | encrypted.Should().NotBeEmpty(); 41 | encryptedString.Should().NotBeNullOrEmpty(); 42 | encryptedString.Should().NotBe(unencryptedString); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.Tests/SymmetricRoundTripTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using RockLib.Encryption.Symmetric; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using Xunit; 6 | using SymmetricAlgorithm = RockLib.Encryption.Symmetric.SymmetricAlgorithm; 7 | 8 | namespace RockLib.Encryption.Tests; 9 | 10 | public static class SymmetricRoundTripTests 11 | { 12 | [Fact] 13 | public static void CanRoundTripByString() 14 | { 15 | var credential = new Credential( 16 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 17 | SymmetricAlgorithm.Aes, 16); 18 | 19 | using var symmetricEncryptor = new SymmetricEncryptor(credential, Encoding.UTF8); 20 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 21 | 22 | var unencrypted = "This is some string"; 23 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 24 | var decrypted = symmetricDecryptor.Decrypt(encrypted); 25 | 26 | encrypted.Should().NotBe(unencrypted); 27 | decrypted.Should().NotBe(encrypted); 28 | decrypted.Should().Be(unencrypted); 29 | } 30 | 31 | [Fact] 32 | public static void CanRoundTripByByteArray() 33 | { 34 | var credential = new Credential( 35 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 36 | SymmetricAlgorithm.Aes, 16); 37 | 38 | using var symmetricEncryptor = new SymmetricEncryptor(credential, Encoding.UTF8); 39 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF8); 40 | 41 | var unencryptedString = "This is some string"; 42 | var unencrypted = Encoding.UTF8.GetBytes(unencryptedString); 43 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 44 | var decrypted = symmetricDecryptor.Decrypt(encrypted); 45 | 46 | encrypted.Should().NotEqual(unencrypted); 47 | decrypted.Should().NotEqual(encrypted); 48 | decrypted.Should().Equal(unencrypted); 49 | } 50 | 51 | [Fact] 52 | public static void CannotRoundTripByStringWithMismatchedCredentials() 53 | { 54 | var credential1 = new Credential( 55 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 56 | SymmetricAlgorithm.Aes, 16); 57 | 58 | var credential2 = new Credential( 59 | () => new byte[] { 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0 }, 60 | SymmetricAlgorithm.Aes, 16); 61 | 62 | using var symmetricEncryptor = new SymmetricEncryptor(credential1, Encoding.UTF8); 63 | using var symmetricDecryptor = new SymmetricDecryptor(credential2, Encoding.UTF8); 64 | 65 | var unencrypted = "This is some string"; 66 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 67 | 68 | var action = () => symmetricDecryptor.Decrypt(encrypted); 69 | 70 | action.Should().Throw(); 71 | } 72 | 73 | [Fact] 74 | public static void CannotRoundTripByByteArrayWithMismatchedCredentials() 75 | { 76 | var credential1 = new Credential( 77 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 78 | SymmetricAlgorithm.Aes, 16); 79 | 80 | var credential2 = new Credential( 81 | () => new byte[] { 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0 }, 82 | SymmetricAlgorithm.Aes, 16); 83 | 84 | using var symmetricEncryptor = new SymmetricEncryptor(credential1, Encoding.UTF8); 85 | using var symmetricDecryptor = new SymmetricDecryptor(credential2, Encoding.UTF8); 86 | 87 | var unencryptedString = "This is some string"; 88 | var unencrypted = Encoding.UTF8.GetBytes(unencryptedString); 89 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 90 | 91 | var action = () => symmetricDecryptor.Decrypt(encrypted); 92 | 93 | action.Should().Throw(); 94 | } 95 | 96 | [Fact] 97 | public static void MismatchedEncodingCausesEncodingDiscrepency() 98 | { 99 | var credential = new Credential( 100 | () => new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }, 101 | SymmetricAlgorithm.Aes, 16); 102 | 103 | using var symmetricEncryptor = new SymmetricEncryptor(credential, Encoding.UTF8); 104 | using var symmetricDecryptor = new SymmetricDecryptor(credential, Encoding.UTF32); 105 | 106 | var unencrypted = "This is some string. 😂🤣"; 107 | var encrypted = symmetricEncryptor.Encrypt(unencrypted); 108 | var decrypted = symmetricDecryptor.Decrypt(encrypted); 109 | 110 | encrypted.Should().NotBe(unencrypted); 111 | decrypted.Should().NotBe(encrypted); 112 | decrypted.Should().NotBe(unencrypted); 113 | } 114 | } -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.XSerializer.Tests/CryptoEncryptionMechanismTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using XSerializer; 4 | using Xunit; 5 | 6 | namespace RockLib.Encryption.XSerializer.Tests; 7 | 8 | public static class CryptoEncryptionMechanismTests 9 | { 10 | [Fact] 11 | public static void CryptoPropertyIsSetFromConstructorParameter() 12 | { 13 | var mockCrypto = new Mock(); 14 | 15 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 16 | 17 | encryptionMechanism.Crypto.Should().BeSameAs(mockCrypto.Object); 18 | } 19 | 20 | [Fact] 21 | public static void EncryptCallsCryptoGetEncryptorWhenSerializationStateIsEmpty() 22 | { 23 | var mockCrypto = new Mock(); 24 | var mockEncryptor = new Mock(); 25 | 26 | mockCrypto.Setup(c => c.GetEncryptor(It.IsAny())).Returns(() => mockEncryptor.Object); 27 | 28 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 29 | 30 | var credentialName = "foobar"; 31 | var serializationState = new SerializationState(); 32 | 33 | encryptionMechanism.Encrypt("foo", credentialName, serializationState); 34 | 35 | mockCrypto.Verify(c => c.GetEncryptor(It.Is(obj => obj == credentialName)), Times.Once()); 36 | } 37 | 38 | [Fact] 39 | public static void GetEncryptorIsNotCalledWhenSerializationStateIsNotEmpty() 40 | { 41 | var mockCrypto = new Mock(); 42 | var mockEncryptor = new Mock(); 43 | 44 | mockCrypto.Setup(c => c.GetEncryptor(It.IsAny())).Returns(() => mockEncryptor.Object); 45 | 46 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 47 | 48 | var credentialName = "foobar"; 49 | var serializationState = new SerializationState(); 50 | 51 | // Force the mock encryptor to be cached in the serialization state. 52 | serializationState.Get(() => mockEncryptor.Object); 53 | 54 | encryptionMechanism.Encrypt("foo", credentialName, serializationState); 55 | 56 | mockCrypto.Verify(c => c.GetEncryptor(It.Is(obj => obj == credentialName)), Times.Never()); 57 | } 58 | 59 | [Fact] 60 | public static void TheCachedEncryptorReturnsTheReturnValue() 61 | { 62 | var mockCrypto = new Mock(); 63 | var mockEncryptor = new Mock(); 64 | 65 | mockCrypto.Setup(c => c.GetEncryptor(It.IsAny())).Returns(() => mockEncryptor.Object); 66 | mockEncryptor.Setup(e => e.Encrypt(It.IsAny())).Returns("bar"); 67 | 68 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 69 | 70 | var credentialName = "foobar"; 71 | var serializationState = new SerializationState(); 72 | 73 | // Force the mock encryptor to be cached in the serialization state. 74 | serializationState.Get(() => mockEncryptor.Object); 75 | 76 | var encrypted = encryptionMechanism.Encrypt("foo", credentialName, serializationState); 77 | 78 | encrypted.Should().Be("bar"); 79 | } 80 | 81 | [Fact] 82 | public static void DecryptCallsCryptoGetDecryptorWhenSerializationStateIsEmpty() 83 | { 84 | var mockCrypto = new Mock(); 85 | var mockDecryptor = new Mock(); 86 | 87 | mockCrypto.Setup(c => c.GetDecryptor(It.IsAny())).Returns(() => mockDecryptor.Object); 88 | 89 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 90 | 91 | var credentialName = "foobar"; 92 | var serializationState = new SerializationState(); 93 | 94 | encryptionMechanism.Decrypt("foo", credentialName, serializationState); 95 | 96 | mockCrypto.Verify(c => c.GetDecryptor(It.Is(obj => obj == credentialName)), Times.Once()); 97 | } 98 | 99 | [Fact] 100 | public static void GetDecryptorIsNotCalledWhenSerializationStateIsNotEmpty() 101 | { 102 | var mockCrypto = new Mock(); 103 | var mockDecryptor = new Mock(); 104 | 105 | mockCrypto.Setup(c => c.GetDecryptor(It.IsAny())).Returns(() => mockDecryptor.Object); 106 | 107 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 108 | 109 | var credentialName = "foobar"; 110 | var serializationState = new SerializationState(); 111 | 112 | // Force the mock decryptor to be cached in the serialization state. 113 | serializationState.Get(() => mockDecryptor.Object); 114 | 115 | encryptionMechanism.Decrypt("foo", credentialName, serializationState); 116 | 117 | mockCrypto.Verify(c => c.GetDecryptor(It.Is(obj => obj == credentialName)), Times.Never()); 118 | } 119 | 120 | [Fact] 121 | public static void TheCachedDecryptorReturnsTheReturnValue() 122 | { 123 | var mockCrypto = new Mock(); 124 | var mockDecryptor = new Mock(); 125 | 126 | mockCrypto.Setup(c => c.GetDecryptor(It.IsAny())).Returns(() => mockDecryptor.Object); 127 | mockDecryptor.Setup(e => e.Decrypt(It.IsAny())).Returns("bar"); 128 | 129 | var encryptionMechanism = new CryptoEncryptionMechanism(mockCrypto.Object); 130 | 131 | var credentialName = "foobar"; 132 | var serializationState = new SerializationState(); 133 | 134 | // Force the mock decryptor to be cached in the serialization state. 135 | serializationState.Get(() => mockDecryptor.Object); 136 | 137 | var decrypted = encryptionMechanism.Decrypt("foo", credentialName, serializationState); 138 | 139 | decrypted.Should().Be("bar"); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Tests/RockLib.Encryption.XSerializer.Tests/RockLib.Encryption.XSerializer.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | all 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/Concepts.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | sidebar_label: Concepts 4 | --- 5 | 6 | # :warning: Deprecation Warning :warning: 7 | 8 | This library has been deprecated and will no longer receive updates. 9 | 10 | --- 11 | 12 | ## Concepts 13 | 14 | ## `ICrypto` interface 15 | 16 | `ICrypto` is the main abstraction in RockLib.Encryption. Here is its definition: 17 | 18 | ```csharp 19 | public interface ICrypto 20 | { 21 | string Encrypt(string plainText, string credentialName); 22 | string Decrypt(string cipherText, string credentialName); 23 | byte[] Encrypt(byte[] plainText, string credentialName); 24 | byte[] Decrypt(byte[] cipherText, string credentialName); 25 | IEncryptor GetEncryptor(string credentialName); 26 | IDecryptor GetDecryptor(string credentialName); 27 | bool CanEncrypt(string credentialName); 28 | bool CanDecrypt(string credentialName); 29 | } 30 | ``` 31 | 32 | ## Methods 33 | 34 | ### `CanEncrypt` / `CanDecrypt` methods 35 | 36 | These methods each take a string `credentialName` as their parameter and return a boolean value indicating whether the instance of `ICrypto` is able to "recognize" the specified `credentialName`. A `credentialName` is also used by each other `ICrypto` method to retrieve the key for the given encryption operation. For example, an implementation of the `ICrypto` interface might have, as part of its implementation, a symmetric key registered with `"foo"` as the credential name. In that case, we would expect that `CanEncrypt` and `CanDecrypt` would return true if we passed them a parameter with a value of `"foo"`. 37 | 38 | ```csharp 39 | ICrypto crypto = // Assume you have an implementation of ICrypto that recognizes "foo" but not "bar". 40 | bool canEncryptFoo = crypto.CanEncrypt("foo"); // Should return true. 41 | bool canEncryptBar = crypto.CanEncrypt("bar"); // Should return false. 42 | ``` 43 | 44 | NOTE: If we want use the "default" key (as defined by the particular implementation of `ICrypto`), pass `null` as the value for the `credentialName` parameter. 45 | 46 | ```csharp 47 | ICrypto cryptoA = // Assume you have an implementation of ICrypto that has a default key. 48 | ICrypto cryptoB = // Assume you have an implementation of ICrypto that does NOT have a default key. 49 | bool canEncryptFoo = cryptoA.CanEncrypt(null); // Should return true. 50 | bool canEncryptBar = cryptoB.CanEncrypt(null); // Should return false. 51 | ``` 52 | 53 | ### `Encrypt` / `Decrypt` methods 54 | 55 | These are the main methods of the interface. Each of the `Encrypt` and `Decrypt` methods take two parameters. The first one is the value to be operated upon, and has a type of either `string` or `byte[]`. Note that this type determines the return type of the method. The second parameter is a `credentialName`, as described above. 56 | 57 | ```csharp 58 | ICrypto crypto = // Assume you have an implementation of ICrypto that has a default key. 59 | 60 | // String-based API 61 | string plainTextString = "Hello, world!"; 62 | string cipherTextString = crypto.Encrypt(plainTextString, null); 63 | string decryptedString = crypto.Decrypt(cipherTextString, null); 64 | 65 | // Binary-based API 66 | byte[] plainTextByteArray = Encoding.UTF8.GetBytes(plainTextString); 67 | byte[] cipherTextByteArray = crypto.Encrypt(plainTextByteArray, null); 68 | byte[] decryptedByteArray = crypto.Decrypt(cipherTextByteArray, null); 69 | ``` 70 | 71 | ### `GetEncryptor` / `GetDecryptor` methods 72 | 73 | These methods are intended to be used when the lookup and/or retrieval of a key is expensive, and multiple related encryption operations need to be performed. In this situation, the result of the lookup/retrieval is cached in the returned `IEncryptor` or `IDecryptor` object. The object can then be used for multiple encryption operations. 74 | 75 | ```csharp 76 | ICrypto crypto = // Assume you have an implementation of ICrypto that has a default key. 77 | 78 | IEncryptor encryptor = crypto.GetEncryptor(); 79 | string fooEncrypted = encryptor.Encrypt("foo"); 80 | string barEncrypted = encryptor.Encrypt("bar"); 81 | byte[] bazEncrypted = encryptor.Encrypt(new byte[] { 1, 2, 3 }); 82 | 83 | IDecryptor decryptor = crypto.GetDecryptor(); 84 | string foo = decryptor.Decrypt(fooEncrypted); 85 | string bar = decryptor.Decrypt(barEncrypted); 86 | byte[] baz = decryptor.Decrypt(bazEncrypted); 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/Crypto.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | sidebar_label: Crypto static class 4 | --- 5 | 6 | # :warning: Deprecation Warning :warning: 7 | 8 | This library has been deprecated and will no longer receive updates. 9 | 10 | --- 11 | 12 | ## Crypto static class 13 | 14 | The `Crypto` static class provides simple access to a single instance of `ICrypto`, defined by its `Current` property. For convenience, it includes synchronous and asynchronous methods for encryption and decryption, each of which uses the `Current` property. 15 | 16 | To programmatically change the value of the `Current` property, call the `SetCurrent` method at the very beginning of an application: 17 | 18 | ```csharp 19 | ICrypto crypto = // TODO: instantiate 20 | 21 | Crypto.SetCurrent(crypto); 22 | 23 | // TODO: The rest of the application 24 | ``` 25 | 26 | ## Configuration 27 | 28 | By default, the `Crypto` static class sets its `Current` property using the `RockLib.Configuration.Config` static class. Specifically, it loads the instance (or instances) of the `ICrypto` interface specified in this configuration section: `Config.Root.GetSection("rocklib.encryption")`. This is an example `appsettings.json` file, for .NET Core projects: 29 | 30 | ```json 31 | { 32 | "rocklib.encryption": { 33 | "type": "Some.Assembly.Qualified.Name, SomeAssembly", 34 | "value": { 35 | "settings": { 36 | "specific": "to", 37 | "the": "type" 38 | }, 39 | "specified": "above" 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | This is an equivalent app.config/web.config (applicable only to .NET Framework applications): 46 | 47 | ```xml 48 | 49 | 50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | above 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 | Your configuration can define one or more implementation/instance of the `ICrypto` in config. Each of these elements describe an implementation of the `ICrypto` interface, and will be transformed into an item in the collection of an instance of `CompositeCrypto`. 66 | 67 | `appsettings.json` 68 | 69 | ```json 70 | { 71 | "rocklib.encryption": [ 72 | { 73 | "type": "Some.Assembly.Qualified.Name, SomeAssembly", 74 | "value": { 75 | "setting1": 123, 76 | "setting2": false 77 | } 78 | }, 79 | { 80 | "type": "Another.Assembly.Qualified.Name, AnotherAssembly", 81 | "value": { 82 | "settingA": "abc", 83 | "settingB": 123.45 84 | } 85 | } 86 | ] 87 | } 88 | ``` 89 | 90 | `app.config/web.config` 91 | 92 | ```xml 93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 123 103 | false 104 | 105 | 106 | 107 | 108 | abc 109 | 123.45 110 | 111 | 112 | 113 | 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/DependencyInjection.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | sidebar_label: Dependency Injection 4 | --- 5 | 6 | # :warning: Deprecation Warning :warning: 7 | 8 | This library has been deprecated and will no longer receive updates. 9 | 10 | --- 11 | 12 | ## Dependency Injection 13 | 14 | To register an implementation of `ICrypto` with the Microsoft.Extensions.DependencyInjection container, the RockLib.Encryption package provides a `AddCrypto` extension method with several overloads. Each of the methods registers both the `ICrypto` and `IAsyncCrypto` interfaces. 15 | 16 | Implemenations of the `ICrypto` interface *should* also define dependency injection extension methods specific to the implemenation. See [SymmetricCrypto](Implementations.md#symmetriccrypto-class) for an example. 17 | 18 | --- 19 | 20 | The overload with no additional parameters registers (as singleton) the `ICrypto` implementation that is returned by the `Crypto.Current` static property. By default, this instance is defined in [configuration](Crypto.md#configuration). 21 | 22 | ```csharp 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddCrypto(); 26 | } 27 | ``` 28 | 29 | --- 30 | 31 | The overload with an `ICrypto` parameter registers the specified instance as singleton. 32 | 33 | ```csharp 34 | public void ConfigureServices(IServiceCollection services) 35 | { 36 | ICrypto crypto = // TODO: instantiate an ICrypto implementation. 37 | 38 | services.AddCrypto(crypto); 39 | } 40 | ``` 41 | 42 | --- 43 | 44 | The overload with a `Func` parameter registers the `ICrypto` returned by the `cryptoFactory` function as singleton. 45 | 46 | ```csharp 47 | public void ConfigureServices(IServiceCollection services) 48 | { 49 | services.AddSingleton(); 50 | 51 | services.AddCrypto(serviceProvider => 52 | { 53 | IMyDependency myDependency = serviceProvider.GetRequiredService(); 54 | return new MyCrypto(myDependency); 55 | }); 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/FieldLevelEncryption.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | sidebar_label: Field-Level Encryption 4 | --- 5 | 6 | # :warning: Deprecation Warning :warning: 7 | 8 | This library has been deprecated and will no longer receive updates. 9 | 10 | --- 11 | 12 | ## Field-Level Encryption 13 | 14 | Sometime sensitive information exists within an XML or JSON document in specific fields. For example the following documents contain a clear text SSN: 15 | 16 | ```xml 17 | 18 | John 19 | Q 20 | Public 21 | 123-45-6789 22 | 23 | ``` 24 | 25 | ```json 26 | { 27 | "first_name": "Public", 28 | "middle_initial": "Q", 29 | "last_name": "Public", 30 | "ssn": "123-45-6789" 31 | } 32 | ``` 33 | 34 | This is what we want - to keep most of the document plain-text while encrypting just the sensitive fields: 35 | 36 | ```xml 37 | 38 | John 39 | Q 40 | Public 41 | MTIzLTQ1LTY3ODk= 42 | 43 | ``` 44 | 45 | ```json 46 | { 47 | "first_name": "Public", 48 | "middle_initial": "Q", 49 | "last_name": "Public", 50 | "ssn": "MTIzLTQ1LTY3ODk=" 51 | } 52 | ``` 53 | 54 | There are two mechanisms for identifying and then encrypting/decrypting just the fields that are sensitive: [XPath/JSONPath extension methods](#xpath--jsonpath) and [the RockLib.Encryption.XSerializer package](#rocklibencryptionxserializer). 55 | 56 | ## XPath / JSONPath 57 | 58 | The main RockLib.Encryption package provides extension methods under the `RockLib.Encryption.FieldLevel` namespace that use either [XPath](https://msdn.microsoft.com/en-us/library/ms256086.aspx) or [JSONPath](http://goessner.net/articles/JsonPath/) to identify which fields need to be encrypted. There are many overloads with the same variation: take a string containing an XML/JSON document and provide one or more XPath/JSONPath strings, and they encrypt/decrypt just the fields specified by XPath/JSONPath. 59 | 60 | The easiest extensions to use extend `string` and use `Crypto.Current` as the backing source of encryption: 61 | 62 | ```csharp 63 | string xml = // Use first XML example above 64 | string xmlWithSsnEncrypted = xml.EncryptXml("/client/ssn"); // Should be similar to second XML example above 65 | string xmlWithSsnDecrypted = xml.DecryptXml("/client/ssn"); // Round-trip should be same as original 66 | 67 | string json = // Use first JSON example above 68 | string jsonWithSsnEncrypted = json.EncryptJson("$.ssn"); // Should be similar to second JSON example above 69 | string jsonWithSsnDecrypted = json.DecryptJson("$.ssn"); // Round-trip should be same as original 70 | ``` 71 | 72 | There are also extension methods that extend `ICrypto` - these are useful if your app is injecting instances of `ICrypto` (i.e. it isn't using the static `Crypto` class). 73 | 74 | ```csharp 75 | ICrypto crypto = // Get from somewhere 76 | 77 | string xml = // Use first XML example above 78 | string xmlWithSsnEncrypted = crypto.EncryptXml(xml, "/client/ssn"); // Should be similar to second XML example above 79 | string xmlWithSsnDecrypted = crypto.DecryptXml(xml, "/client/ssn"); // Round-trip should be same as original 80 | 81 | string json = // Use first JSON example above 82 | string jsonWithSsnEncrypted = crypto.EncryptJson(json, "$.ssn"); // Should be similar to second JSON example above 83 | string jsonWithSsnDecrypted = crypto.DecryptJson(json, "$.ssn"); // Round-trip should be same as original 84 | ``` 85 | 86 | ## RockLib.Encryption.XSerializer 87 | 88 | [XSerializer](https://github.com/QuickenLoans/XSerializer) includes a feature where it encrypts/decrypts properties marked with its `[Encrypt]` attribute in-line during JSON and XML serialization operations. RockLib.Encryption.XSerializer marries XSerializer's field-level encryption mechanism with RockLib.Encryption's standardized crypto API. 89 | 90 | ### ICrypto extension methods 91 | 92 | The may way of using this package is through its extension methods off of the `ICrypto` interface. These extension methods (`ToXml`, `FromXml`, `ToJson`, `FromJson`) enable seamless field-level encryption and decryption during serialization and deserialization with XSerializer by using its `[Encrypt]` attribute. 93 | 94 | ### Usage 95 | 96 | ```csharp 97 | static void Main(string[] args) 98 | { 99 | ICrypto crypto = // Get from somewhere 100 | 101 | string json = crypto.ToJson(new Foo { Bar = 123, Baz = 456 }); 102 | Console.WriteLine(json); 103 | Foo fooFromJson = crypto.FromJson(json); 104 | Console.WriteLine($"fooFromJson.Bar:{fooFromJson.Bar}, fooFromJson.Baz:{fooFromJson.Baz}"); 105 | 106 | Console.WriteLine(); 107 | 108 | string xml = crypto.ToXml(new Foo { Bar = 123, Baz = 456 }); 109 | Console.WriteLine(xml); 110 | Foo fooFromXml = crypto.FromXml(xml); 111 | Console.WriteLine($"fooFromXml.Bar:{fooFromXml.Bar}, fooFromXml.Baz:{fooFromXml.Baz}"); 112 | } 113 | 114 | public class Foo 115 | { 116 | public int Bar { get; set; } 117 | 118 | [Encrypt] 119 | public int Baz { get; set; } 120 | } 121 | ``` 122 | 123 | ### SerializingCrypto class 124 | 125 | This class has the same named methods and behaves exactly the same as the above `ICrypto` extension methods. The only difference is that the `SerializingCrypto` class holds on to a single reference of `CryptoEncryptionMechanism` (which references an `ICrypto`), where the extension methods create a new instance each time using the supplied `ICrypto`. 126 | 127 | ### Usage 128 | 129 | Start by configuring your application as usual for use with RockLib.Encryption, then add the RockLib.Encryption.XSerializer nuget package. Then access the serializing crypto functionality through the `SerializingCrypto` class directly or by using its `ICrypto` extension methods. 130 | 131 | ```csharp 132 | static void Main(string[] args) 133 | { 134 | string json = SerializingCrypto.ToJson(new Foo { Bar = 123, Baz = 456 }); 135 | Console.WriteLine(json); 136 | Foo fooFromJson = SerializingCrypto.FromJson(json); 137 | Console.WriteLine($"fooFromJson.Bar:{fooFromJson.Bar}, fooFromJson.Baz:{fooFromJson.Baz}"); 138 | 139 | Console.WriteLine(); 140 | 141 | string xml = SerializingCrypto.ToXml(new Foo { Bar = 123, Baz = 456 }); 142 | Console.WriteLine(xml); 143 | Foo fooFromXml = SerializingCrypto.FromXml(xml); 144 | Console.WriteLine($"fooFromXml.Bar:{fooFromXml.Bar}, fooFromXml.Baz:{fooFromXml.Baz}"); 145 | } 146 | 147 | public class Foo 148 | { 149 | public int Bar { get; set; } 150 | 151 | [Encrypt] 152 | public int Baz { get; set; } 153 | } 154 | ``` 155 | -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: Getting Started 4 | --- 5 | 6 | # :warning: Deprecation Warning :warning: 7 | 8 | This library has been deprecated and will no longer receive updates. 9 | 10 | --- 11 | 12 | ## Getting Started 13 | 14 | RockLib.Encryption provides a simple API for encryption and decryption text and binary data. In this tutorial, we will be building a console application that encrypts and decrypts sample text using the Rijndael algorithm. 15 | 16 | --- 17 | 18 | Create a new .NET Core 2.0 (or above) console application named "EncryptionApp". 19 | 20 | --- 21 | 22 | Add a nuget references for "RockLib.Encryption" and "Microsoft.Extensions.Hosting" to the project. 23 | 24 | --- 25 | 26 | Add a class named "EncryptionService" to the project. Replace the default code with the following: 27 | 28 | ```csharp 29 | using Microsoft.Extensions.Hosting; 30 | using RockLib.Encryption; 31 | using System; 32 | using System.Threading; 33 | using System.Threading.Tasks; 34 | 35 | namespace EncryptionApp 36 | { 37 | public class EncryptionService : IHostedService 38 | { 39 | private readonly ICrypto _crypto; 40 | 41 | public EncryptionService(ICrypto crypto) 42 | { 43 | _crypto = crypto; 44 | } 45 | 46 | public Task StartAsync(CancellationToken cancellationToken) 47 | { 48 | // Work should not be done in the actual StartAsync method of an IHostedService. 49 | ThreadPool.QueueUserWorkItem(DoEncryptionWork); 50 | return Task.CompletedTask; 51 | } 52 | 53 | public Task StopAsync(CancellationToken cancellationToken) 54 | { 55 | // Nothing to do. 56 | return Task.CompletedTask; 57 | } 58 | 59 | private void DoEncryptionWork(object state) 60 | { 61 | // Slight delay to allow the app starting log messages to be written. 62 | Thread.Sleep(500); 63 | 64 | Console.WriteLine(); 65 | 66 | // Our sensitive data is a "social security number". 67 | string ssn = "123-45-6789"; 68 | Console.WriteLine($"Original SSN: {ssn}"); 69 | 70 | // Encrypt the SSN. The resulting value should be different from the original SSN. 71 | string encryptedSsn = _crypto.Encrypt(ssn); 72 | Console.WriteLine($"Encrypted SSN: {encryptedSsn}"); 73 | 74 | // Decrypt the SSN. The resulting value should be the same as the original SSN. 75 | string decryptedSsn = _crypto.Decrypt(encryptedSsn); 76 | Console.WriteLine($"Decrypted SSN: {decryptedSsn}"); 77 | 78 | Console.WriteLine(); 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | --- 85 | 86 | Replace the contents of Program.cs with the following: 87 | 88 | ```csharp 89 | using Microsoft.Extensions.DependencyInjection; 90 | using Microsoft.Extensions.Hosting; 91 | using RockLib.Encryption.Symmetric; 92 | using RockLib.Encryption.Symmetric.DependencyInjection; 93 | 94 | namespace EncryptionApp 95 | { 96 | public class Program 97 | { 98 | public static void Main(string[] args) 99 | { 100 | CreateHostBuilder(args).Build().Run(); 101 | } 102 | 103 | private static IHostBuilder CreateHostBuilder(string[] args) 104 | { 105 | return Host.CreateDefaultBuilder(args) 106 | .ConfigureServices(services => 107 | { 108 | services.AddSymmetricCrypto() 109 | .AddCredential("CQSImVlbvJMZcnrkzT3/ouW1klt6STljrDjRiBzIsSk=", SymmetricAlgorithm.Rijndael); 110 | 111 | // ExampleService has a dependency on ICrypto. 112 | services.AddHostedService(); 113 | }); 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | --- 120 | 121 | Start the application. The output should look something like this (press Ctrl+C to exit): 122 | 123 | ```powershell 124 | info: Microsoft.Hosting.Lifetime[0] 125 | Application started. Press Ctrl+C to shut down. 126 | info: Microsoft.Hosting.Lifetime[0] 127 | Hosting environment: Production 128 | info: Microsoft.Hosting.Lifetime[0] 129 | Content root path: C:\Users\bfriesen\source\repos\ConsoleApp24\EncryptionApp\bin\Debug\netcoreapp3.1 130 | 131 | Original SSN: 123-45-6789 132 | Encrypted SSN: ARAAq19rPjvpARsGunkHfYjxxG+NJ/1BlnZSklYBGcr6McA= 133 | Decrypted SSN: 123-45-6789 134 | 135 | info: Microsoft.Hosting.Lifetime[0] 136 | Application is shutting down... 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/Implementations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | sidebar_label: 'ICrypto implementations' 4 | --- 5 | 6 | # :warning: Deprecation Warning :warning: 7 | 8 | This library has been deprecated and will no longer receive updates. 9 | 10 | --- 11 | 12 | ## ICrypto implementations 13 | 14 | ## SymmetricCrypto class 15 | 16 | RockLib.Encryption provides an implementation of `ICrypto`, `SymmetricCrypto`, that uses the various symmetric encryption implementations that are provided by .NET. The supported algorithms are: `AES`, `DES`, `RC2`, `Rijndael`, and `Triple DES`. This class has public constructors and can be instantiated directly. 17 | 18 | --- 19 | 20 | To register a `SymmetricCrypto` with the Microsoft.Extensions.DependencyInjection container, use the `AddSymmetricCrypto` extension method, and add credentials with the `AddCredential` method: 21 | 22 | ```csharp 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddSymmetricCrypto() 26 | .AddCredential("CQSImVlbvJMZcnrkzT3/ouW1klt6STljrDjRiBzIsSk=", SymmetricAlgorithm.Rijndael) // This is the default (unnamed) credential. 27 | .AddCredential("MyDESCredential", "2LQliivTtNo=", SymmetricAlgorithm.DES, 8); // This credential is named "MyDESCredential". 28 | } 29 | ``` 30 | 31 | --- 32 | 33 | If using the default value for the static `Crypto` class, it can be configured as follows: 34 | 35 | `appsettings.json` 36 | 37 | ```json 38 | { 39 | "rocklib.encryption": { 40 | "type": "RockLib.Encryption.Symmetric.SymmetricCrypto, RockLib.Encryption", 41 | "value": { 42 | "credentials": [ 43 | { 44 | "name": "default", 45 | "algorithm": "Rijndael", 46 | "ivsize": 16, 47 | "key": "bo3Vtyg4uBhcKgQKQ6H9LmeYXF+7BG42XMoS7AgZFz4=" 48 | }, 49 | { 50 | "name": "triple_des", 51 | "algorithm": "TripleDES", 52 | "ivsize": 8, 53 | "key": "bNYqGfSV6xqgoucDMqwGWFRZ8KHFXe+m" 54 | } 55 | ] 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | `app.config/web.config` 62 | 63 | ```xml 64 | 65 | 66 | 67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 78 | 82 | 83 | 84 | 85 | 86 | 87 | ``` 88 | 89 | _Note that it is a bad idea to store symmetric keys in configuration plaintext as shown above._ 90 | 91 | ## CompositeCrypto class 92 | 93 | If your application needs to support more than one implementation of the `ICrypto` interface, you can use the `CompositeCrypto` class. 94 | It does so by implementing the [_composite_](http://www.blackwasp.co.uk/Composite.aspx) pattern. The constructor of this class takes a collection of `ICrypto` objects. Each method of the `CompositeCrypto` class is implemented by iterating through that collection. The first item in the collection that returns `true` from its `CanEncrypt` or `CanDecrypt` method is the `ICrypto` that is used for the current encryption operation. 95 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockLib/RockLib.Encryption/592caaabb591348f18633ae8482b5ab246ca951d/icon.png --------------------------------------------------------------------------------