|
| 1 | +# .NET 每周分享第 30 期 |
| 2 | + |
| 3 | +## 卷首语 |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +Jetbrains 公司发起的 `.NET` 生态开发者的调查问卷结果,主要包含下面的问题: |
| 8 | + |
| 9 | +- C# 使用的版本 |
| 10 | +- .NET 项目使用的语言 |
| 11 | +- 开发项目的类型 |
| 12 | +- CLR 使用的版本 |
| 13 | +- IDE 或者编辑器使用 |
| 14 | +- VS 插件使用 |
| 15 | +- VS Code 插件使用 |
| 16 | +- 单元测试框架 |
| 17 | +- 性能调试频率 |
| 18 | + |
| 19 | +## 行业资讯 |
| 20 | + |
| 21 | +1、[VS 上搜索体验提升](https://devblogs.microsoft.com/visualstudio/new-better-search-in-visual-studio/) |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +Visual Studio 提升了搜索的体验,主要分为了代码搜索和功能搜索。选择 `Ctrl + T` 快捷键进行搜索,而且可以使用 `f:` , `t:` 和 `m:` 前缀分别表示文件,类型和成员搜索。 |
| 26 | + |
| 27 | +## 文章推荐 |
| 28 | + |
| 29 | +1、[C# 代码加密算法汇总](https://code-maze.com/dotnet-cryptography-implementations/) |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +数据加密是现在计算机网络的基础,通过加密可以确保我们数据的安全性,那么在 `C#` 中如何使用这些加密算法呢? |
| 34 | + |
| 35 | +1. Hash 加密 |
| 36 | + |
| 37 | +Hash 加密是一种单向加密,也就是说将一个输入转换到一个特定空间的数据。通常不能进行反向操作,通过这种方法可以判断输入的内容事否一致。通常有三种类别 |
| 38 | + |
| 39 | +- MD5 |
| 40 | + |
| 41 | +该算法已经被标记为不安全算法。 |
| 42 | + |
| 43 | +```csharp |
| 44 | +var strStreamOne = new MemoryStream(Encoding.UTF8.GetBytes("This is my password! Dont read me!")); |
| 45 | +byte[] hashOne; |
| 46 | +using (var hasher = MD5.Create()) |
| 47 | +{ |
| 48 | + hashOne = await hasher.ComputeHashAsync(strStreamOne); |
| 49 | +} |
| 50 | +var hashAsString = Convert.ToHexString(hashOne); |
| 51 | +Console.WriteLine("Hash Value:\n" + hashAsString) |
| 52 | +``` |
| 53 | + |
| 54 | +- SHA 族 |
| 55 | + |
| 56 | +通常有 `Sha-1`, `sha2` 这样的算法 |
| 57 | + |
| 58 | +```csharp |
| 59 | +var strStreamOne = new MemoryStream(Encoding.UTF8.GetBytes("This is my password! Dont read me!")); |
| 60 | +byte[] hashOne; |
| 61 | +using (var sha256 = SHA256.Create()) |
| 62 | +{ |
| 63 | + hashOne = await sha256.ComputeHashAsync(strStreamOne); |
| 64 | +} |
| 65 | +var hashAsString = Convert.ToHexString(hashOne); |
| 66 | +``` |
| 67 | + |
| 68 | +- HMAC |
| 69 | + |
| 70 | +这是一个需要密钥才能生成的哈希算法 |
| 71 | + |
| 72 | +```csharp |
| 73 | +var strStreamOne = new MemoryStream(Encoding.UTF8.GetBytes("This is my password! Dont read me!")); |
| 74 | +byte[] hashOne; |
| 75 | +byte[] key = Encoding.UTF8.GetBytes("superSecretH4shKey1!"); |
| 76 | +using (var hmac = new HMACSHA256(key)) |
| 77 | +{ |
| 78 | + hashOne = await hmac.ComputeHashAsync(strStreamOne); |
| 79 | +} |
| 80 | +var hashAsString = Convert.ToHexString(hashOne); |
| 81 | +``` |
| 82 | + |
| 83 | +2. 对称加密 |
| 84 | + |
| 85 | +这是一个可以进行加密和解密的算法,两端使用相同的密钥进行工作,通常使用 `AES` 算法 |
| 86 | + |
| 87 | +```csharp |
| 88 | +var dataStr = "This is corporate research! Dont read me!"; |
| 89 | +var data = Encoding.UTF8.GetBytes(dataStr); |
| 90 | +var key = GenerateAESKey(); |
| 91 | +var encryptedData = Encrypt(data, key, out var iv); |
| 92 | +var encryptedDataAsString = Convert.ToHexString(encryptedData); |
| 93 | + |
| 94 | +public static byte[] Encrypt(byte[] data, byte[] key, out byte[] iv) |
| 95 | +{ |
| 96 | + using (var aes = Aes.Create()) |
| 97 | + { |
| 98 | + aes.Mode = CipherMode.CBC; // better security |
| 99 | + aes.Key = key; |
| 100 | + aes.GenerateIV(); // IV = Initialization Vector |
| 101 | +
|
| 102 | + using (var encryptor = aes.CreateEncryptor()) |
| 103 | + { |
| 104 | + iv = aes.IV; |
| 105 | + return encryptor.TransformFinalBlock(data, 0, data.Length); |
| 106 | + } |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +public static byte[] Decrypt(byte[] data, byte[] key, byte[] iv) |
| 111 | +{ |
| 112 | + using (var aes = Aes.Create()) |
| 113 | + { |
| 114 | + aes.Key = key; |
| 115 | + aes.IV = iv; |
| 116 | + aes.Mode = CipherMode.CBC; // same as for encryption |
| 117 | +
|
| 118 | + using (var decryptor = aes.CreateDecryptor()) |
| 119 | + { |
| 120 | + return decryptor.TransformFinalBlock(data, 0, data.Length); |
| 121 | + } |
| 122 | + } |
| 123 | +} |
| 124 | +public static byte[] GenerateAESKey() |
| 125 | +{ |
| 126 | + var rnd = new RNGCryptoServiceProvider(); |
| 127 | + var b = new byte[16]; |
| 128 | + rnd.GetNonZeroBytes(b); |
| 129 | + return b; |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +3. 非对称加密 |
| 134 | + |
| 135 | +就是加密方和解密方使用不同的密钥来操作,通常分为公钥和私钥,公钥可以公开,任何人都可以加密。但是只有私钥才能解开,反之亦然,私钥加密的内容,只有公钥才能解开。这样可以保证私钥的安全性。最著名的就是 `RSA` 算法,这也是 `HTTPS` 协议的基石。 |
| 136 | + |
| 137 | +```csharp |
| 138 | +var dataStr = "This is corporate research! Dont read me!"; |
| 139 | +var data = Encoding.UTF8.GetBytes(dataStr); |
| 140 | +var keyLength = 2048; // size in bits |
| 141 | +GenerateKeys(keyLength , out var publicKey, out var privateKey); |
| 142 | +var encryptedData = Encrypt(data, publicKey); |
| 143 | +var encryptedDataAsString = Convert.ToHexString(encryptedData); |
| 144 | +public void GenerateKeys(int keyLength, out RSAParameters publicKey, out RSAParameters privateKey) |
| 145 | +{ |
| 146 | + using (var rsa = RSA.Create()) |
| 147 | + { |
| 148 | + rsa.KeySize = keyLength; |
| 149 | + publicKey = rsa.ExportParameters(includePrivateParameters: false); |
| 150 | + privateKey = rsa.ExportParameters(includePrivateParameters: true); |
| 151 | + } |
| 152 | +} |
| 153 | +public byte[] Encrypt(byte[] data, RSAParameters publicKey) |
| 154 | +{ |
| 155 | + using (var rsa = RSA.Create()) |
| 156 | + { |
| 157 | + rsa.ImportParameters(publicKey); |
| 158 | + |
| 159 | + var result = rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256); |
| 160 | + return result; |
| 161 | + } |
| 162 | +} |
| 163 | +public byte[] Decrypt(byte[] data, RSAParameters privateKey) |
| 164 | +{ |
| 165 | + using (var rsa = RSA.Create()) |
| 166 | + { |
| 167 | + rsa.ImportParameters(privateKey); |
| 168 | + return rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256); |
| 169 | + } |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +另外一种是 `DSA` 算法,用来进行数字签名,因为它只需要校验事否匹配,性能上有优势。 |
| 174 | + |
| 175 | +```csharp |
| 176 | +var dsa = DSA.Create(); |
| 177 | +var dataStr = "This is corporate research! Dont read me!"; |
| 178 | +var data = Encoding.UTF8.GetBytes(dataStr); |
| 179 | +var signedData = Sign(dsa, data); |
| 180 | +dsa.Dispose(); |
| 181 | +public byte[] Sign(DSA dsa, byte[] data) |
| 182 | +{ |
| 183 | + if(dsa is null) |
| 184 | + throw new NullReferenceException(nameof(dsa)); |
| 185 | + var result = dsa.SignData(data, HashAlgorithmName.SHA256); |
| 186 | + return result; |
| 187 | +} |
| 188 | +public bool VerifySignature(DSA dsa, byte[] data, byte[] signedData) |
| 189 | +{ |
| 190 | + if (dsa is null) |
| 191 | + throw new NullReferenceException(nameof(dsa)); |
| 192 | + return dsa.VerifyData(data, signedData, HashAlgorithmName.SHA256); |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +2、[C# 如何混淆反编译器](https://washi.dev/blog/posts/debugger-proxy-objects/) |
| 197 | + |
| 198 | +这是一篇有意思的文章,作者是一名逆向工程师。我们都知道 `C#` 作为一个包含中间状态的编程语言(IL), 通常使用反编译工具就能得到源代码。那么如何做到混淆反编译工具,让它无法得到源代码呢? |
| 199 | + |
| 200 | +1. Proxy Object |
| 201 | + |
| 202 | +首先通过是 `Proxy Object` 来封装真正的类,而且 C#执行隐式操作符重载,比如创建 `PersonProxy` 类 |
| 203 | + |
| 204 | +```csharp |
| 205 | +public sealed class PersonProxy |
| 206 | +{ |
| 207 | + private readonly Person _value; |
| 208 | + public Person(Person A_1) |
| 209 | + { |
| 210 | + _value = A_1; |
| 211 | + } |
| 212 | + // Implicit conversion operators: |
| 213 | + public static implicit operator PersonProxy(Person A_0) => new(A_0); |
| 214 | + public static implicit operator Person(PersonProxy A_0) => A_0._value; |
| 215 | +} |
| 216 | +``` |
| 217 | + |
| 218 | +这样任何可以使用 `Person` 的地方都可以使用 `PersonProxy`。同理可以为系统类型进行代理。 |
| 219 | + |
| 220 | +2. Display |
| 221 | + |
| 222 | +C# 中的 `DebuggerDisplay` 注解可以方便调试,如何通过反编译工具对程序进行调试,那么可以在 `DebuggerDisplay` 中展示错误信息。 |
| 223 | + |
| 224 | +```csharp |
| 225 | +using System; |
| 226 | +using System.Diagnostics; |
| 227 | +namespace System; |
| 228 | +[DebuggerDisplay("{Display}")] |
| 229 | +public struct Int32 |
| 230 | +{ |
| 231 | + [CompilerGenerated] |
| 232 | + [DebuggerBrowsable(DebuggerBrowsableState.Never)] |
| 233 | + private readonly int <0this>k__BackingField; // Renamed by our obfuscator |
| 234 | +
|
| 235 | + public Int32(int A_1) |
| 236 | + { |
| 237 | + this.<0this>k__BackingField = A_1; |
| 238 | + } |
| 239 | + |
| 240 | + // Implicit conversion operators: |
| 241 | + public static implicit operator int(int A_0) => new(A_0); |
| 242 | + public static implicit operator int(int A_0) => A_0.<0this>k__BackingField; |
| 243 | + |
| 244 | + // Random display object: |
| 245 | + [DebuggerBrowsable(DebuggerBrowsableState.Never)] |
| 246 | + public int Display => 31; // Randomly selected by our obfuscator. |
| 247 | +} |
| 248 | +``` |
| 249 | + |
| 250 | +这里 `Display` 使用了一个 `31` 固定值,这样在调试 `Int32` 类型的时候,总是得到一个错误的值。或者可以抛出一个未知的异常。 |
| 251 | + |
| 252 | +```csharp |
| 253 | +[DebuggerBrowsable(DebuggerBrowsableState.Never)] |
| 254 | +public string Display |
| 255 | +{ |
| 256 | + get |
| 257 | + { |
| 258 | + Environment.FailFast("The CLR encountered an internal limitation."); |
| 259 | + return null; |
| 260 | + } |
| 261 | +} |
| 262 | + |
| 263 | +[DebuggerBrowsable(DebuggerBrowsableState.Never)] |
| 264 | +public string Display => Display |
| 265 | +``` |
| 266 | + |
| 267 | +亦或者在 Debug 的时候,修改程序的状态 |
| 268 | + |
| 269 | +```csharp |
| 270 | +[DebuggerBrowsable(DebuggerBrowsableState.Never)] |
| 271 | +public string Display |
| 272 | +{ |
| 273 | + get |
| 274 | + { |
| 275 | + // Change the state of the actual object with random values selected by our obfuscator. |
| 276 | + // No luck for the reverse engineer to ever find the original values back! |
| 277 | + this.<0this>k__BackingField.FirstName = "???"; |
| 278 | + this.<0this>k__BackingField.LastName = "???"; |
| 279 | + this.<0this>k__BackingField.CoolnessFactor = 0.0324466228f; |
| 280 | + return null; |
| 281 | + } |
| 282 | +} |
| 283 | +``` |
| 284 | + |
| 285 | +3、C# 构造函数简化 |
| 286 | + |
| 287 | + |
| 288 | +
|
| 289 | +```csharp |
| 290 | +class Person |
| 291 | +{ |
| 292 | + public int Age { get; set; } |
| 293 | + |
| 294 | + public string Name { get; set; } |
| 295 | + |
| 296 | + public Person(string name, int age) => (Name, Age) = (name, age); |
| 297 | +} |
| 298 | +``` |
| 299 | + |
| 300 | +## 开源项目 |
| 301 | + |
| 302 | +1、[bflat](https://github.com/bflattened/bflat) |
| 303 | +
|
| 304 | + |
| 305 | +
|
| 306 | +bflat 是一个开源的 `C#` 编译工具,它可以将 `C#` 代码编译成可执行并且 NativeAOT 的执行文件。它和官方的编译工具的区别在于它可以运行在 `UEFI` 中,而且编译出来的文件体积小。 |
| 307 | + |
| 308 | +2、[.NET 平台上的 COBOL 编译器](https://github.com/otterkit/otterkit) |
| 309 | +
|
| 310 | + |
| 311 | +
|
| 312 | +`Cobol` 是一门古老的编程语言,至今仍然有不少机器任然运行者 Cobol 编写的程序。`.NET` 由于开放性,任何编程语言都可以在上面运行。`otterkit` 即使在 `.NET` 上实现了 `Cobol` 编译器。 |
| 313 | + |
| 314 | +3、[QuestPDF](https://github.com/QuestPDF/QuestPDF) |
| 315 | +
|
| 316 | + |
| 317 | +
|
| 318 | +`QuestPDF` 是一个开源的 `.NET` PDF 生成器,它可以按照指定的内容生成相应的 PDF 文件。而且它内置了实时浏览工具,可以动态查看生成的 PDF 内容。 |
| 319 | + |
| 320 | +4、[MethodTimer](https://github.com/Fody/MethodTimer) |
| 321 | +
|
| 322 | + |
| 323 | +
|
| 324 | +我们常常需要测量一个方法执行时间,最直接的方法是使用 `Stopwatch` 测量,比如说 |
| 325 | + |
| 326 | +```csharp |
| 327 | +var stopwatch = Stopwatch.StartNew(); |
| 328 | +try |
| 329 | +{ |
| 330 | + // Your method |
| 331 | +} |
| 332 | +finally |
| 333 | +{ |
| 334 | + stopwatch.Stop(); |
| 335 | +} |
| 336 | +``` |
| 337 | + |
| 338 | +这样做肯定是正确的,那么有什么办法简化这个流程呢?就跟 `Python` 中的中装饰器一样呢?`MethodTimer` 库可以做到,它可以通过 `C#` 的 `Source Generation` 方式生成上述的类。而且还提供了 `MethodTimerLogger` 这个类定义耗时操作的日志输出。 |
| 339 | + |
| 340 | +```csharp |
| 341 | +public static class MethodTimeLogger |
| 342 | +{ |
| 343 | + public static void Log(MethodBase methodBase, long milliseconds, string message) |
| 344 | + { |
| 345 | + //Do some logging here |
| 346 | + } |
| 347 | +} |
| 348 | +``` |
0 commit comments