/
StrongBackupEncryption.cs
132 lines (105 loc) 路 4.65 KB
/
StrongBackupEncryption.cs
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright (C) 2023 jmh
// SPDX-License-Identifier: GPL-3.0-only
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Konscious.Security.Cryptography;
using Newtonsoft.Json;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
namespace AuthenticatorPro.Core.Backup.Encryption
{
public class StrongBackupEncryption : IBackupEncryption
{
private const string Header = "AUTHENTICATORPRO";
// Key derivation
private const int Parallelism = 4;
private const int Iterations = 3;
private const int MemorySize = 65536;
private const int SaltLength = 16;
private const int KeyLength = 32;
// Encryption
private const string BaseAlgorithm = "AES";
private const string Mode = "GCM";
private const string Padding = "NoPadding";
private const string AlgorithmDescription = BaseAlgorithm + "/" + Mode + "/" + Padding;
private const int IvLength = 12;
private const int TagLength = 16;
public async Task<byte[]> EncryptAsync(Backup backup, string password)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Cannot encrypt without a password");
}
var random = new SecureRandom();
var salt = random.GenerateSeed(SaltLength);
var iv = random.GenerateSeed(IvLength);
var key = await DeriveKeyAsync(password, salt);
var parameters = new AeadParameters(new KeyParameter(key), TagLength * 8, iv);
var cipher = CipherUtilities.GetCipher(AlgorithmDescription);
cipher.Init(true, parameters);
var json = JsonConvert.SerializeObject(backup);
var unencryptedData = Encoding.UTF8.GetBytes(json);
var encryptedData = await Task.Run(() => cipher.DoFinal(unencryptedData));
var headerBytes = Encoding.UTF8.GetBytes(Header);
var output = new byte[Header.Length + SaltLength + IvLength + encryptedData.Length];
Buffer.BlockCopy(headerBytes, 0, output, 0, headerBytes.Length);
Buffer.BlockCopy(salt, 0, output, headerBytes.Length, SaltLength);
Buffer.BlockCopy(iv, 0, output, headerBytes.Length + SaltLength, IvLength);
Buffer.BlockCopy(encryptedData, 0, output, headerBytes.Length + SaltLength + IvLength,
encryptedData.Length);
return output;
}
public async Task<Backup> DecryptAsync(byte[] data, string password)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Cannot decrypt without a password");
}
if (!CanBeDecrypted(data))
{
throw new ArgumentException("Header does not match");
}
await using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream);
reader.ReadBytes(Header.Length);
var salt = reader.ReadBytes(SaltLength);
var iv = reader.ReadBytes(IvLength);
var encryptedData = reader.ReadBytes(data.Length - Header.Length - SaltLength - IvLength);
var key = await DeriveKeyAsync(password, salt);
var parameters = new AeadParameters(new KeyParameter(key), TagLength * 8, iv);
var cipher = CipherUtilities.GetCipher(AlgorithmDescription);
cipher.Init(false, parameters);
byte[] unencryptedData;
try
{
unencryptedData = await Task.Run(() => cipher.DoFinal(encryptedData));
}
catch (InvalidCipherTextException e)
{
throw new ArgumentException("Invalid password", e);
}
var json = Encoding.UTF8.GetString(unencryptedData);
return JsonConvert.DeserializeObject<Backup>(json);
}
public bool CanBeDecrypted(byte[] data)
{
var foundHeader = data.Take(Header.Length).ToArray();
var headerBytes = Encoding.UTF8.GetBytes(Header);
return headerBytes.SequenceEqual(foundHeader);
}
private static Task<byte[]> DeriveKeyAsync(string password, byte[] salt)
{
var passwordBytes = Encoding.UTF8.GetBytes(password);
var argon2 = new Argon2id(passwordBytes);
argon2.DegreeOfParallelism = Parallelism;
argon2.Iterations = Iterations;
argon2.MemorySize = MemorySize;
argon2.Salt = salt;
return argon2.GetBytesAsync(KeyLength);
}
}
}