MailSharp / MailSharp.MailClient / Services / IPasswordProtector.cs
Code · 62 lines · 1885 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62using System.Security.Cryptography;

namespace MailSharp.MailClient.Services;

public interface IPasswordProtector
{
	string Protect(string plainText);
	string Unprotect(string protectedText);
}

// AES-GCM encryption using a key material file kept outside wwwroot.
// Not a substitute for a real secrets manager, but avoids storing plaintext passwords in LiteDB.
public class AesPasswordProtector : IPasswordProtector
{
	private readonly byte[] _key;

	public AesPasswordProtector(IWebHostEnvironment env)
	{
		var keyPath = Path.Combine(env.ContentRootPath, "App_Data", "protect.key");
		Directory.CreateDirectory(Path.GetDirectoryName(keyPath)!);
		if (File.Exists(keyPath))
		{
			_key = Convert.FromBase64String(File.ReadAllText(keyPath));
		}
		else
		{
			_key = RandomNumberGenerator.GetBytes(32);
			File.WriteAllText(keyPath, Convert.ToBase64String(_key));
		}
	}

	public string Protect(string plainText)
	{
		var nonce = RandomNumberGenerator.GetBytes(12);
		var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
		var cipher = new byte[plainBytes.Length];
		var tag = new byte[16];
		using (var aes = new AesGcm(_key, 16))
		{
			aes.Encrypt(nonce, plainBytes, cipher, tag);
		}
		var result = new byte[nonce.Length + tag.Length + cipher.Length];
		Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length);
		Buffer.BlockCopy(tag, 0, result, nonce.Length, tag.Length);
		Buffer.BlockCopy(cipher, 0, result, nonce.Length + tag.Length, cipher.Length);
		return Convert.ToBase64String(result);
	}

	public string Unprotect(string protectedText)
	{
		var data = Convert.FromBase64String(protectedText);
		var nonce = data[..12];
		var tag = data[12..28];
		var cipher = data[28..];
		var plain = new byte[cipher.Length];
		using (var aes = new AesGcm(_key, 16))
		{
			aes.Decrypt(nonce, cipher, tag, plain);
		}
		return System.Text.Encoding.UTF8.GetString(plain);
	}
}