├── 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 | | [![Build status](https://ci.appveyor.com/api/projects/status/hai0kndijmx6xb9d?svg=true)](https://ci.appveyor.com/project/henkmollema/cryptohelper) | [![Build Status](https://travis-ci.org/henkmollema/CryptoHelper.svg)](https://travis-ci.org/henkmollema/CryptoHelper) | [![Build Status](https://travis-ci.org/henkmollema/CryptoHelper.svg)](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 --------------------------------------------------------------------------------