├── CryptoHelper.snk
├── NuGet.Config
├── .appveyor.yml
├── .travis.yml
├── .gitignore
├── test
└── CryptoHelper.Tests
│ ├── CryptoHelper.Tests.csproj
│ └── CryptoHelperTests.cs
├── LICENSE
├── README.md
├── src
└── CryptoHelper
│ ├── CryptoHelper.csproj
│ └── Crypto.cs
├── CryptoHelper.sln
├── .gitattributes
└── .editorconfig
/CryptoHelper.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henkmollema/CryptoHelper/HEAD/CryptoHelper.snk
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | init:
2 | - git config --global core.autocrlf true
3 | build_script:
4 | - ps: .\build.ps1
5 | clone_depth: 1
6 | test: off
7 | deploy: off
8 | environment:
9 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
10 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
11 | os: Visual Studio 2017
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | mono: none
3 | dotnet: 2.1.4
4 | dist: trusty
5 | env:
6 | global:
7 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
8 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1
9 | os:
10 | - linux
11 | - osx
12 | osx_image: xcode8.3
13 | script:
14 | - ./build.sh
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj/
2 | [Bb]in/
3 | TestResults/
4 | .nuget/
5 | *.sln.ide/
6 | _ReSharper.*/
7 | packages/
8 | artifacts/
9 | PublishProfiles/
10 | .vs/
11 | bower_components/
12 | node_modules/
13 | **/wwwroot/lib/
14 | debugSettings.json
15 | project.lock.json
16 | *.user
17 | *.suo
18 | *.cache
19 | *.docstates
20 | _ReSharper.*
21 | nuget.exe
22 | *net45.csproj
23 | *net451.csproj
24 | *k10.csproj
25 | *.psess
26 | *.vsp
27 | *.pidb
28 | *.userprefs
29 | *DS_Store
30 | *.ncrunchsolution
31 | *.*sdf
32 | *.ipch
33 | .settings
34 | *.sln.ide
35 | node_modules
36 | **/[Cc]ompiler/[Rr]esources/**/*.js
37 | deploy/
38 | .build/
--------------------------------------------------------------------------------
/test/CryptoHelper.Tests/CryptoHelper.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | false
5 | false
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Henk Mollema
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CryptoHelper
2 | :key: Standalone password hasher for ASP.NET Core using a PBKDF2 implementation.
3 |
4 |
5 |
6 | | Windows | Linux | OS X |
7 | | --- | --- | --- |
8 | | [](https://ci.appveyor.com/project/henkmollema/cryptohelper) | [](https://travis-ci.org/henkmollema/CryptoHelper) | [](https://travis-ci.org/henkmollema/CryptoHelper) |
9 |
10 |
11 |
12 | This utility provides a standalone password hasher for ASP.NET Core without a dependency on ASP.NET Identity. The passwords are hashed using the new [Data Protection](https://github.com/aspnet/DataProtection) stack.
13 |
14 |
15 |
16 | ## Download
17 |
18 | CryptoHelper is available on [NuGet](https://www.nuget.org/packages/CryptoHelper).
19 |
20 |
21 |
22 | ## Usage
23 | ```csharp
24 | using CryptoHelper;
25 |
26 | // Hash a password
27 | public string HashPassword(string password)
28 | {
29 | return Crypto.HashPassword(password);
30 | }
31 |
32 | // Verify the password hash against the given password
33 | public bool VerifyPassword(string hash, string password)
34 | {
35 | return Crypto.VerifyHashedPassword(hash, password);
36 | }
37 | ```
38 |
--------------------------------------------------------------------------------
/src/CryptoHelper/CryptoHelper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Standalone password hasher for ASP.NET Core using a PBKDF2 implementation.
4 | Copyright 2018 Henk Mollema
5 | 4.0.0
6 | latest
7 | netstandard2.0;net8.0
8 | Henk Mollema
9 | true
10 | hashing;passwordhasher;crypto;core;PBKDF2;Rfc2898DeriveBytes
11 | https://github.com/henkmollema/CryptoHelper
12 | MIT
13 | git
14 | https://github.com/henkmollema/CryptoHelper
15 | true
16 | true
17 | embedded
18 | ../../CryptoHelper.snk
19 | true
20 | true
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/CryptoHelper.Tests/CryptoHelperTests.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace CryptoHelper.Tests;
4 |
5 | public class CryptoHelperTests
6 | {
7 | private const string Password = "VerySecurePassword";
8 | private const string HashedPassword = "AQAAAAEAACcQAAAAEMZ9/7LS/Ne7087ytPjCosYJbysRf7DwrKzQziuhtA84k78soJGX0hQzNsNdnIrTNg==";
9 |
10 | [Fact]
11 | public void HashPassword_Returns_HashedPassword()
12 | {
13 | var hashed = Crypto.HashPassword(Password);
14 | Assert.NotEmpty(hashed);
15 | }
16 |
17 | [Fact]
18 | public void VerifyHashedPasswordWithCorrectPassword_Returns_CorrectResult()
19 | {
20 | var hashed = Crypto.HashPassword(Password);
21 | var result = Crypto.VerifyHashedPassword(hashed, Password);
22 | Assert.True(result);
23 | }
24 |
25 | [Fact]
26 | public void VerifyStoredPassword_Returns_CorrectResult()
27 | {
28 | // Test that verifies a previously hashed and stored password can
29 | // still be verified for backwards compatibility.
30 | var result = Crypto.VerifyHashedPassword(HashedPassword, Password);
31 | Assert.True(result);
32 | }
33 |
34 | [Fact]
35 | public void VerifyHashedPasswordWithIncorrectPassword_Returns_CorrectResult()
36 | {
37 | var hashed = Crypto.HashPassword(Password);
38 | var result = Crypto.VerifyHashedPassword(hashed, "WrongPassword");
39 | Assert.False(result);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/CryptoHelper.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.8.34525.116
4 | MinimumVisualStudioVersion = 15.0.26124.0
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DD55BCD1-C3C3-4192-B4D4-2E6A19902525}"
6 | EndProject
7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CryptoHelper", "src\CryptoHelper\CryptoHelper.csproj", "{B636EF42-1BAB-4033-89BC-0DD9A695319E}"
8 | EndProject
9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{61A274A4-9A61-4C9F-AF4F-C4D7BAB76C84}"
10 | EndProject
11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CryptoHelper.Tests", "test\CryptoHelper.Tests\CryptoHelper.Tests.csproj", "{14697791-6DB8-4206-B68B-9E0ADE024554}"
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Release|Any CPU = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {B636EF42-1BAB-4033-89BC-0DD9A695319E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {B636EF42-1BAB-4033-89BC-0DD9A695319E}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {B636EF42-1BAB-4033-89BC-0DD9A695319E}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {B636EF42-1BAB-4033-89BC-0DD9A695319E}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {14697791-6DB8-4206-B68B-9E0ADE024554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {14697791-6DB8-4206-B68B-9E0ADE024554}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {14697791-6DB8-4206-B68B-9E0ADE024554}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {14697791-6DB8-4206-B68B-9E0ADE024554}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(SolutionProperties) = preSolution
29 | HideSolutionNode = FALSE
30 | EndGlobalSection
31 | GlobalSection(NestedProjects) = preSolution
32 | {B636EF42-1BAB-4033-89BC-0DD9A695319E} = {DD55BCD1-C3C3-4192-B4D4-2E6A19902525}
33 | {14697791-6DB8-4206-B68B-9E0ADE024554} = {61A274A4-9A61-4C9F-AF4F-C4D7BAB76C84}
34 | EndGlobalSection
35 | GlobalSection(ExtensibilityGlobals) = postSolution
36 | SolutionGuid = {DF33E925-8836-4498-8732-426E2B67CF84}
37 | EndGlobalSection
38 | EndGlobal
39 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/CryptoHelper/Crypto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Security.Cryptography;
4 | using Microsoft.AspNetCore.Cryptography.KeyDerivation;
5 |
6 | namespace CryptoHelper;
7 |
8 | ///
9 | /// Provides helper methods for hashing/salting and verifying passwords.
10 | ///
11 | public static class Crypto
12 | {
13 | /* =======================
14 | * HASHED PASSWORD FORMATS
15 | * =======================
16 | *
17 | * Version 3:
18 | * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 600.000 iterations.
19 | * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
20 | * (All UInt32s are stored big-endian.)
21 | */
22 |
23 | private const int PBKDF2IterCount = 600_000;
24 | private const int PBKDF2SubkeyLength = 256 / 8; // 256 bits
25 | private const int SaltSize = 128 / 8; // 128 bits
26 |
27 |
28 | ///
29 | /// Returns a hashed representation of the specified .
30 | ///
31 | /// The password to generate a hash value for.
32 | /// The hash value for as a base-64-encoded string.
33 | /// is null.
34 | public static string HashPassword(string password)
35 | {
36 | if (password == null)
37 | {
38 | throw new ArgumentNullException(nameof(password));
39 | }
40 |
41 | return HashPasswordInternal(password);
42 | }
43 |
44 | ///
45 | /// Determines whether the specified RFC 2898 hash and password are a cryptographic match.
46 | ///
47 | /// The previously-computed RFC 2898 hash value as a base-64-encoded string.
48 | /// The plaintext password to cryptographically compare with hashedPassword.
49 | /// true if the hash value is a cryptographic match for the password; otherwise, false.
50 | ///
51 | /// must be of the format of HashPassword (salt + Hash(salt+input).
52 | ///
53 | ///
54 | /// or is null.
55 | ///
56 | public static bool VerifyHashedPassword(string hashedPassword, string password)
57 | {
58 | if (hashedPassword == null)
59 | {
60 | throw new ArgumentNullException(nameof(hashedPassword));
61 | }
62 | if (password == null)
63 | {
64 | throw new ArgumentNullException(nameof(password));
65 | }
66 |
67 | return VerifyHashedPasswordInternal(hashedPassword, password);
68 | }
69 |
70 | private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
71 |
72 | private static string HashPasswordInternal(string password)
73 | {
74 | var bytes = HashPasswordInternal(password, KeyDerivationPrf.HMACSHA256, PBKDF2IterCount, SaltSize, PBKDF2SubkeyLength);
75 | return Convert.ToBase64String(bytes);
76 | }
77 |
78 | private static byte[] HashPasswordInternal(
79 | string password,
80 | KeyDerivationPrf prf,
81 | int iterCount,
82 | int saltSize,
83 | int numBytesRequested)
84 | {
85 | // Produce a version 3 (see comment above) text hash.
86 | var salt = new byte[saltSize];
87 | _rng.GetBytes(salt);
88 | var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
89 |
90 | var outputBytes = new byte[13 + salt.Length + subkey.Length];
91 |
92 | // Write format marker.
93 | outputBytes[0] = 0x01;
94 |
95 | // Write hashing algorithm version.
96 | WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
97 |
98 | // Write iteration count of the algorithm.
99 | WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
100 |
101 | // Write size of the salt.
102 | WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
103 |
104 | // Write the salt.
105 | Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
106 |
107 | // Write the subkey.
108 | Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
109 | return outputBytes;
110 | }
111 |
112 | private static bool VerifyHashedPasswordInternal(string hashedPassword, string password)
113 | {
114 | var decodedHashedPassword = Convert.FromBase64String(hashedPassword);
115 |
116 | if (decodedHashedPassword.Length == 0)
117 | {
118 | return false;
119 | }
120 |
121 | try
122 | {
123 | // Verify hashing format.
124 | if (decodedHashedPassword[0] != 0x01)
125 | {
126 | // Unknown format header.
127 | return false;
128 | }
129 |
130 | // Read hashing algorithm version.
131 | var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
132 |
133 | // Read iteration count of the algorithm.
134 | var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
135 |
136 | // Read size of the salt.
137 | var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);
138 |
139 | // Verify the salt size: >= 128 bits.
140 | if (saltLength < 128 / 8)
141 | {
142 | return false;
143 | }
144 |
145 | // Read the salt.
146 | var salt = new byte[saltLength];
147 | Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);
148 |
149 | // Verify the subkey length >= 128 bits.
150 | var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
151 | if (subkeyLength < 128 / 8)
152 | {
153 | return false;
154 | }
155 |
156 | // Read the subkey.
157 | var expectedSubkey = new byte[subkeyLength];
158 | Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
159 |
160 | // Hash the given password and verify it against the expected subkey.
161 | var actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength);
162 | return ByteArraysEqual(actualSubkey, expectedSubkey);
163 | }
164 | catch
165 | {
166 | // This should never occur except in the case of a malformed payload, where
167 | // we might go off the end of the array. Regardless, a malformed payload
168 | // implies verification failed.
169 | return false;
170 | }
171 | }
172 |
173 | private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
174 | {
175 | return ((uint)(buffer[offset + 0]) << 24)
176 | | ((uint)(buffer[offset + 1]) << 16)
177 | | ((uint)(buffer[offset + 2]) << 8)
178 | | ((uint)(buffer[offset + 3]));
179 | }
180 |
181 | private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
182 | {
183 | buffer[offset + 0] = (byte)(value >> 24);
184 | buffer[offset + 1] = (byte)(value >> 16);
185 | buffer[offset + 2] = (byte)(value >> 8);
186 | buffer[offset + 3] = (byte)(value >> 0);
187 | }
188 |
189 | // Compares two byte arrays for equality.
190 | // The method is specifically written so that the loop is not optimized.
191 | [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
192 | private static bool ByteArraysEqual(byte[] a, byte[] b)
193 | {
194 | if (ReferenceEquals(a, b))
195 | {
196 | return true;
197 | }
198 |
199 | if (a == null || b == null || a.Length != b.Length)
200 | {
201 | return false;
202 | }
203 |
204 | var areSame = true;
205 | for (var i = 0; i < a.Length; i++)
206 | {
207 | areSame &= (a[i] == b[i]);
208 | }
209 | return areSame;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | root = true
3 |
4 | # XML / csproj
5 | [*.{xml,csproj}]
6 | indent_size = 2
7 | indent_style = space
8 | tab_width = 2
9 |
10 | # C# files
11 | [*.cs]
12 |
13 | # Indentation and spacing
14 | indent_size = 4
15 | indent_style = space
16 | tab_width = 4
17 | end_of_line = crlf
18 |
19 | # New line preferences
20 | end_of_line = crlf
21 | insert_final_newline = false
22 |
23 | #### .NET Coding Conventions ####
24 |
25 | # Organize usings
26 | dotnet_separate_import_directive_groups = false
27 | dotnet_sort_system_directives_first = true
28 | file_header_template = unset
29 |
30 | # this. and Me. preferences
31 | dotnet_style_qualification_for_event = false:warning
32 | dotnet_style_qualification_for_field = false:silent
33 | dotnet_style_qualification_for_method = false:warning
34 | dotnet_style_qualification_for_property = false:warning
35 |
36 | # Language keywords vs BCL types preferences
37 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
38 | dotnet_style_predefined_type_for_member_access = true:warning
39 |
40 | # Parentheses preferences
41 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
42 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
43 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
44 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
45 |
46 | # Modifier preferences
47 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
48 |
49 | # Expression-level preferences
50 | dotnet_style_coalesce_expression = true:suggestion
51 | dotnet_style_collection_initializer = true:suggestion
52 | dotnet_style_explicit_tuple_names = true:suggestion
53 | dotnet_style_null_propagation = true:suggestion
54 | dotnet_style_object_initializer = true:suggestion
55 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
56 | dotnet_style_prefer_auto_properties = true:silent
57 | dotnet_style_prefer_compound_assignment = true:suggestion
58 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
59 | dotnet_style_prefer_conditional_expression_over_return = true:silent
60 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
61 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
62 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
63 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
64 | dotnet_style_prefer_simplified_interpolation = true:suggestion
65 |
66 | # Field preferences
67 | dotnet_style_readonly_field = true:suggestion
68 |
69 | # Parameter preferences
70 | dotnet_code_quality_unused_parameters = all:suggestion
71 |
72 | #### C# Coding Conventions ####
73 |
74 | # var preferences
75 | csharp_style_var_elsewhere = true:suggestion
76 | csharp_style_var_for_built_in_types = true:suggestion
77 | csharp_style_var_when_type_is_apparent = true:suggestion
78 |
79 | # Expression-bodied members
80 | csharp_style_expression_bodied_accessors = true:silent
81 | csharp_style_expression_bodied_constructors = false:silent
82 | csharp_style_expression_bodied_indexers = true:silent
83 | csharp_style_expression_bodied_lambdas = true:silent
84 | csharp_style_expression_bodied_local_functions = when_on_single_line:silent
85 | csharp_style_expression_bodied_methods = when_on_single_line:silent
86 | csharp_style_expression_bodied_operators = when_on_single_line:silent
87 | csharp_style_expression_bodied_properties = true:silent
88 |
89 | # Pattern matching preferences
90 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
91 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
92 | csharp_style_prefer_switch_expression = true:suggestion
93 |
94 | # Null-checking preferences
95 | csharp_style_conditional_delegate_call = true:suggestion
96 |
97 | # Modifier preferences
98 | csharp_prefer_static_local_function = true:suggestion
99 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
100 |
101 | # Code-block preferences
102 | csharp_prefer_braces = true:suggestion
103 | csharp_prefer_simple_using_statement = true:suggestion
104 |
105 | # Expression-level preferences
106 | csharp_prefer_simple_default_expression = true:suggestion
107 | csharp_style_deconstructed_variable_declaration = true:suggestion
108 | csharp_style_inlined_variable_declaration = true:suggestion
109 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
110 | csharp_style_prefer_index_operator = true:suggestion
111 | csharp_style_prefer_range_operator = true:suggestion
112 | csharp_style_throw_expression = true:suggestion
113 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
114 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
115 |
116 | # 'using' directive preferences
117 | csharp_using_directive_placement = outside_namespace:silent
118 |
119 | # Namespace declarations
120 | csharp_style_namespace_declarations = file_scoped:suggestion
121 |
122 | #### C# Formatting Rules ####
123 |
124 | # New line preferences
125 | csharp_new_line_before_catch = true
126 | csharp_new_line_before_else = true
127 | csharp_new_line_before_finally = true
128 | csharp_new_line_before_members_in_anonymous_types = true
129 | csharp_new_line_before_members_in_object_initializers = true
130 | csharp_new_line_before_open_brace = all
131 | csharp_new_line_between_query_expression_clauses = true
132 |
133 | # Indentation preferences
134 | csharp_indent_block_contents = true
135 | csharp_indent_braces = false
136 | csharp_indent_case_contents = true
137 | csharp_indent_case_contents_when_block = false
138 | csharp_indent_labels = one_less_than_current
139 | csharp_indent_switch_labels = true
140 |
141 | # Space preferences
142 | csharp_space_after_cast = false
143 | csharp_space_after_colon_in_inheritance_clause = true
144 | csharp_space_after_comma = true
145 | csharp_space_after_dot = false
146 | csharp_space_after_keywords_in_control_flow_statements = true
147 | csharp_space_after_semicolon_in_for_statement = true
148 | csharp_space_around_binary_operators = before_and_after
149 | csharp_space_around_declaration_statements = false
150 | csharp_space_before_colon_in_inheritance_clause = true
151 | csharp_space_before_comma = false
152 | csharp_space_before_dot = false
153 | csharp_space_before_open_square_brackets = false
154 | csharp_space_before_semicolon_in_for_statement = false
155 | csharp_space_between_empty_square_brackets = false
156 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
157 | csharp_space_between_method_call_name_and_opening_parenthesis = false
158 | csharp_space_between_method_call_parameter_list_parentheses = false
159 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
160 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
161 | csharp_space_between_method_declaration_parameter_list_parentheses = false
162 | csharp_space_between_parentheses = false
163 | csharp_space_between_square_brackets = false
164 |
165 | # Wrapping preferences
166 | csharp_preserve_single_line_blocks = true
167 | csharp_preserve_single_line_statements = true
168 |
169 | #### Naming styles ####
170 |
171 | # Naming rules
172 |
173 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
174 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
175 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
176 |
177 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
178 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
179 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
180 |
181 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
182 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
183 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
184 |
185 | dotnet_naming_rule.private_constant_should_be_pascal_case.severity = suggestion
186 | dotnet_naming_rule.private_constant_should_be_pascal_case.symbols = private_constant
187 | dotnet_naming_rule.private_constant_should_be_pascal_case.style = pascal_case
188 |
189 | dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.severity = suggestion
190 | dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.symbols = private_or_internal_static_field
191 | dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.style = pascal_case
192 |
193 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.severity = suggestion
194 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.symbols = private_or_internal_field
195 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.style = begins_with__
196 |
197 | # Symbol specifications
198 |
199 | dotnet_naming_symbols.interface.applicable_kinds = interface
200 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
201 | dotnet_naming_symbols.interface.required_modifiers =
202 |
203 | dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
204 | dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
205 | dotnet_naming_symbols.private_or_internal_field.required_modifiers =
206 |
207 | dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field
208 | dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private
209 | dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static
210 |
211 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
212 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
213 | dotnet_naming_symbols.types.required_modifiers =
214 |
215 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
216 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
217 | dotnet_naming_symbols.non_field_members.required_modifiers =
218 |
219 | dotnet_naming_symbols.private_constant.applicable_kinds = field, local
220 | dotnet_naming_symbols.private_constant.applicable_accessibilities = private, local
221 | dotnet_naming_symbols.private_constant.required_modifiers = const
222 |
223 | # Naming/code styles
224 |
225 | dotnet_naming_style.pascal_case.required_prefix =
226 | dotnet_naming_style.pascal_case.required_suffix =
227 | dotnet_naming_style.pascal_case.word_separator =
228 | dotnet_naming_style.pascal_case.capitalization = pascal_case
229 |
230 | dotnet_naming_style.begins_with_i.required_prefix = I
231 | dotnet_naming_style.begins_with_i.required_suffix =
232 | dotnet_naming_style.begins_with_i.word_separator =
233 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
234 |
235 | dotnet_naming_style.begins_with__.required_prefix = _
236 | dotnet_naming_style.begins_with__.required_suffix =
237 | dotnet_naming_style.begins_with__.word_separator =
238 | dotnet_naming_style.begins_with__.capitalization = camel_case
239 |
240 | csharp_style_prefer_method_group_conversion = true:silent
241 | csharp_style_prefer_pattern_matching = true:silent
242 | csharp_style_prefer_not_pattern = true:suggestion
243 | csharp_style_prefer_extended_property_pattern = true:suggestion
244 | csharp_style_prefer_null_check_over_type_check = true:suggestion
245 | csharp_style_prefer_top_level_statements = true:silent
246 |
247 | # .NET styles
248 | dotnet_style_namespace_match_folder= false:suggestion
249 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
250 | dotnet_style_coalesce_expression = true:suggestion
251 | dotnet_style_null_propagation = true:suggestion
252 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
253 | dotnet_style_prefer_auto_properties = true:silent
254 | dotnet_style_object_initializer = true:suggestion
255 | dotnet_style_collection_initializer = true:suggestion
256 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
257 | dotnet_style_prefer_conditional_expression_over_assignment = false:silent
258 | dotnet_style_prefer_conditional_expression_over_return = false:silent
259 | dotnet_style_explicit_tuple_names = true:suggestion
260 | dotnet_style_prefer_inferred_tuple_names = true:none
261 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:none
262 | dotnet_style_prefer_compound_assignment = true:suggestion
263 | dotnet_style_prefer_simplified_interpolation = true:suggestion
264 |
265 | # .NET diagnostics
266 | dotnet_diagnostic.CS8509.severity = error # Missing switch case for named enum value
267 | dotnet_diagnostic.CS8524.severity = none # Missing switch case for unnamed enum value
268 | dotnet_diagnostic.CA2016.severity = none # Forward the 'CancellationToken' parameter to methods
269 | dotnet_diagnostic.CA1067.severity = none # Override Object.Equals(object) when implementing IEquatable
270 | dotnet_diagnostic.CA1050.severity = silent
271 | dotnet_diagnostic.CA1860.severity = silent # Prefer Length/Count over Any() -> I find Any() clearer
272 | dotnet_code_quality_unused_parameters = all:suggestion
273 |
274 | # .NET analyzers
275 | dotnet_analyzer_diagnostic.category-GeneratedRegex.severity = silent # Default severity for analyzer diagnostics with category 'GeneratedRegex'
276 |
277 | # .NET diagnostics
278 | dotnet_diagnostic.IDE0037.severity = none # Use inferred member name
279 | csharp_style_prefer_primary_constructors = true:suggestion
280 |
281 | [*.{cs,vb}]
282 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
283 | tab_width = 4
284 | indent_size = 4
285 | end_of_line = crlf
286 | dotnet_style_coalesce_expression = true:suggestion
287 | dotnet_style_null_propagation = true:suggestion
288 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
289 | dotnet_style_prefer_auto_properties = true:silent
290 | dotnet_style_object_initializer = true:suggestion
291 | dotnet_style_prefer_collection_expression = true:suggestion
292 | dotnet_style_collection_initializer = true:suggestion
293 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
294 | dotnet_style_prefer_conditional_expression_over_assignment = false:silent
295 | dotnet_style_prefer_conditional_expression_over_return = false:silent
296 | dotnet_style_explicit_tuple_names = true:suggestion
297 | dotnet_style_prefer_inferred_tuple_names = true:none
298 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:none
299 | dotnet_style_prefer_compound_assignment = true:suggestion
300 | dotnet_style_prefer_simplified_interpolation = true:suggestion
301 | dotnet_style_namespace_match_folder = false:suggestion
--------------------------------------------------------------------------------