├── .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
--------------------------------------------------------------------------------