Skip to content

Commit

Permalink
Pull encryption operations into the thread pool
Browse files Browse the repository at this point in the history
  • Loading branch information
LiamMorrow committed Jan 10, 2024
1 parent a2af1df commit a9eb625
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 117 deletions.
14 changes: 4 additions & 10 deletions LiftLog.Lib/Services/IEncryptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface IEncryptionService
/// <param name="key"></param>
/// <param name="publicKey"></param>
/// <returns></returns>
ValueTask<byte[]> DecryptAesCbcAndVerifyRsa256PssAsync(
Task<byte[]> DecryptAesCbcAndVerifyRsa256PssAsync(
AesEncryptedAndRsaSignedData data,
AesKey key,
RsaPublicKey publicKey
Expand All @@ -26,7 +26,7 @@ RsaPublicKey publicKey
/// CBC does not guarantee integrity, so we need to supply a signature with the data.
/// The signature is verified on decryption. Note the signature is also encrypted.
/// </summary>
public ValueTask<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsync(
public Task<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsync(
byte[] data,
AesKey key,
RsaPrivateKey rsaPrivateKey,
Expand All @@ -35,15 +35,9 @@ public ValueTask<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsyn

ValueTask<AesKey> GenerateAesKeyAsync();

public ValueTask<byte[]> DecryptRsaOaepSha256Async(
RsaEncryptedData data,
RsaPrivateKey privateKey
);
public Task<byte[]> DecryptRsaOaepSha256Async(RsaEncryptedData data, RsaPrivateKey privateKey);

public ValueTask<RsaEncryptedData> EncryptRsaOaepSha256Async(
byte[] data,
RsaPublicKey publicKey
);
public Task<RsaEncryptedData> EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey);

public ValueTask<RsaKeyPair> GenerateRsaKeysAsync();
}
Expand Down
167 changes: 93 additions & 74 deletions LiftLog.Lib/Services/OsEncryptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,100 @@

namespace LiftLog.Lib.Services;

/// <summary>
/// This class provides encryption services using the OS's native crypto libraries.
/// It will run all encryption operations on the thread pool.
/// </summary>
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public class OsEncryptionService : IEncryptionService
{
public ValueTask<byte[]> DecryptAesCbcAndVerifyRsa256PssAsync(
public Task<byte[]> DecryptAesCbcAndVerifyRsa256PssAsync(
AesEncryptedAndRsaSignedData encryptedPayload,
AesKey key,
RsaPublicKey publicKey
)
{
var aes = Aes.Create();
return Task.Run(() =>
{
var aes = Aes.Create();
aes.IV = encryptedPayload.IV.Value;
aes.Key = key.Value;
aes.IV = encryptedPayload.IV.Value;
aes.Key = key.Value;
using var decryptor = aes.CreateDecryptor();
using var decryptor = aes.CreateDecryptor();
var decrypted = decryptor.TransformFinalBlock(
encryptedPayload.EncryptedPayload,
0,
encryptedPayload.EncryptedPayload.Length
);
var decrypted = decryptor.TransformFinalBlock(
encryptedPayload.EncryptedPayload,
0,
encryptedPayload.EncryptedPayload.Length
);
var data = decrypted[..^IEncryptionService.RsaHashLength];
var signature = decrypted[^IEncryptionService.RsaHashLength..];
var data = decrypted[..^IEncryptionService.RsaHashLength];
var signature = decrypted[^IEncryptionService.RsaHashLength..];
var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _);
var dataHash = SHA256.HashData(data);
var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _);
var dataHash = SHA256.HashData(data);
if (!rsa.VerifyData(dataHash, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss))
{
throw new SignatureMismatchException("Signature verification failed");
}
if (
!rsa.VerifyData(
dataHash,
signature,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pss
)
)
{
throw new SignatureMismatchException("Signature verification failed");
}
return ValueTask.FromResult(data);
return data;
});
}

public ValueTask<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsync(
public Task<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsync(
byte[] data,
AesKey key,
RsaPrivateKey rsaPrivateKey,
AesIV? iv = null
)
{
var rsa = RSA.Create(2048);
return Task.Run(() =>
{
var rsa = RSA.Create(2048);
rsa.ImportPkcs8PrivateKey(rsaPrivateKey.Pkcs8PrivateKeyBytes, out _);
rsa.ImportPkcs8PrivateKey(rsaPrivateKey.Pkcs8PrivateKeyBytes, out _);
var dataHash = SHA256.HashData(data);
var signature = rsa.SignData(dataHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
var dataHash = SHA256.HashData(data);
var signature = rsa.SignData(
dataHash,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pss
);
var dataAndSignature = new byte[data.Length + signature.Length];
data.CopyTo(dataAndSignature, 0);
signature.CopyTo(dataAndSignature, data.Length);
var dataAndSignature = new byte[data.Length + signature.Length];
data.CopyTo(dataAndSignature, 0);
signature.CopyTo(dataAndSignature, data.Length);
var aes = Aes.Create();
var aes = Aes.Create();
if (iv is not null)
{
aes.IV = iv.Value;
}
else
{
aes.GenerateIV();
}
aes.Key = key.Value;
if (iv is not null)
{
aes.IV = iv.Value;
}
else
{
aes.GenerateIV();
}
aes.Key = key.Value;
using var encryptor = aes.CreateEncryptor();
using var encryptor = aes.CreateEncryptor();
return ValueTask.FromResult(
new AesEncryptedAndRsaSignedData(
return new AesEncryptedAndRsaSignedData(
encryptor.TransformFinalBlock(dataAndSignature, 0, dataAndSignature.Length),
new AesIV(iv?.Value ?? aes.IV)
)
);
);
});
}

public ValueTask<AesKey> GenerateAesKeyAsync()
Expand All @@ -88,46 +107,46 @@ public ValueTask<AesKey> GenerateAesKeyAsync()
return ValueTask.FromResult(new AesKey(aes.Key));
}

public ValueTask<RsaEncryptedData> EncryptRsaOaepSha256Async(
byte[] data,
RsaPublicKey publicKey
)
public Task<RsaEncryptedData> EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey)
{
var rsa = RSA.Create(2048);

rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _);

// encrypt one chunk at a time, as RSA has a size limit of 122ish bytes
var encryptedChunks = new byte[(data.Length / 122) + 1][];
for (var i = 0; i < encryptedChunks.Length; i++)
return Task.Run(() =>
{
encryptedChunks[i] = rsa.Encrypt(
data[(i * 122)..Math.Min((i + 1) * 122, data.Length)],
RSAEncryptionPadding.OaepSHA256
);
}

return ValueTask.FromResult(new RsaEncryptedData(encryptedChunks));
var rsa = RSA.Create(2048);
rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _);
// encrypt one chunk at a time, as RSA has a size limit of 122ish bytes
var encryptedChunks = new byte[(data.Length / 122) + 1][];
for (var i = 0; i < encryptedChunks.Length; i++)
{
encryptedChunks[i] = rsa.Encrypt(
data[(i * 122)..Math.Min((i + 1) * 122, data.Length)],
RSAEncryptionPadding.OaepSHA256
);
}
return new RsaEncryptedData(encryptedChunks);
});
}

public ValueTask<byte[]> DecryptRsaOaepSha256Async(
RsaEncryptedData data,
RsaPrivateKey privateKey
)
public Task<byte[]> DecryptRsaOaepSha256Async(RsaEncryptedData data, RsaPrivateKey privateKey)
{
var rsa = RSA.Create();
return Task.Run(() =>
{
var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(privateKey.Pkcs8PrivateKeyBytes, out _);
rsa.ImportPkcs8PrivateKey(privateKey.Pkcs8PrivateKeyBytes, out _);
// decrypt one chunk at a time
// decrypt one chunk at a time
var decrypted = new byte[data.DataChunks.Length][];
for (var i = 0; i < data.DataChunks.Length; i++)
{
decrypted[i] = rsa.Decrypt(data.DataChunks[i], RSAEncryptionPadding.OaepSHA256);
}
var decrypted = new byte[data.DataChunks.Length][];
for (var i = 0; i < data.DataChunks.Length; i++)
{
decrypted[i] = rsa.Decrypt(data.DataChunks[i], RSAEncryptionPadding.OaepSHA256);
}
return ValueTask.FromResult(decrypted.SelectMany(x => x).ToArray());
return decrypted.SelectMany(x => x).ToArray();
});
}

public ValueTask<RsaKeyPair> GenerateRsaKeysAsync()
Expand Down
60 changes: 27 additions & 33 deletions LiftLog.Web/Services/JsEncryptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,57 @@ namespace LiftLog.Web.Services;
// Dotnet AES-GCM implementation is not compatible with the JS implementation
public class JsEncryptionService(IJSRuntime jSRuntime) : IEncryptionService
{
public ValueTask<byte[]> DecryptAesCbcAndVerifyRsa256PssAsync(
public Task<byte[]> DecryptAesCbcAndVerifyRsa256PssAsync(
AesEncryptedAndRsaSignedData data,
AesKey key,
RsaPublicKey publicKey
)
{
return jSRuntime.InvokeAsync<byte[]>(
"CryptoUtils.decryptAesCbcAndVerifyRsa256PssAsync",
data,
key,
publicKey
);
return jSRuntime
.InvokeAsync<byte[]>(
"CryptoUtils.decryptAesCbcAndVerifyRsa256PssAsync",
data,
key,
publicKey
)
.AsTask();
}

public ValueTask<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsync(
public Task<AesEncryptedAndRsaSignedData> SignRsa256PssAndEncryptAesCbcAsync(
byte[] data,
AesKey key,
RsaPrivateKey rsaPrivateKey,
AesIV? iv = null
)
{
return jSRuntime.InvokeAsync<AesEncryptedAndRsaSignedData>(
"CryptoUtils.signRsa256PssAndEncryptAesCbcAsync",
data,
key,
rsaPrivateKey,
iv
);
return jSRuntime
.InvokeAsync<AesEncryptedAndRsaSignedData>(
"CryptoUtils.signRsa256PssAndEncryptAesCbcAsync",
data,
key,
rsaPrivateKey,
iv
)
.AsTask();
}

public ValueTask<AesKey> GenerateAesKeyAsync()
{
return jSRuntime.InvokeAsync<AesKey>("CryptoUtils.generateAesKey");
}

public ValueTask<RsaEncryptedData> EncryptRsaOaepSha256Async(
byte[] data,
RsaPublicKey publicKey
)
public Task<RsaEncryptedData> EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey)
{
return jSRuntime.InvokeAsync<RsaEncryptedData>(
"CryptoUtils.encryptRsaOaepSha256Async",
data,
publicKey
);
return jSRuntime
.InvokeAsync<RsaEncryptedData>("CryptoUtils.encryptRsaOaepSha256Async", data, publicKey)
.AsTask();
}

public ValueTask<byte[]> DecryptRsaOaepSha256Async(
RsaEncryptedData data,
RsaPrivateKey privateKey
)
public Task<byte[]> DecryptRsaOaepSha256Async(RsaEncryptedData data, RsaPrivateKey privateKey)
{
return jSRuntime.InvokeAsync<byte[]>(
"CryptoUtils.decryptRsaOaepSha256Async",
data,
privateKey
);
return jSRuntime
.InvokeAsync<byte[]>("CryptoUtils.decryptRsaOaepSha256Async", data, privateKey)
.AsTask();
}

public ValueTask<RsaKeyPair> GenerateRsaKeysAsync()
Expand Down

0 comments on commit a9eb625

Please sign in to comment.