Skip to content

Commit

Permalink
feat(butil): add Crypto encrypt/decrypt to Butil #6437 (#6461)
Browse files Browse the repository at this point in the history
  • Loading branch information
msynk authored Jan 2, 2024
1 parent a2f080f commit 9839591
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/Butil/Bit.Butil/BitButil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static IServiceCollection AddBitButilServices(this IServiceCollection ser
services.AddTransient<Location>();
services.AddTransient<Screen>();
services.AddTransient<Cookie>();
services.AddTransient<Crypto>();

return services;
}
Expand Down
79 changes: 79 additions & 0 deletions src/Butil/Bit.Butil/Internals/JsInterops/CryptoJsInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Bit.Butil;

internal static class CryptoJsInterop
{
internal static ValueTask<byte[]> CryptoEncrypt<T>(this IJSRuntime js, T algorithm, byte[] key, byte[] data, CryptoKeyHash? keyHash) where T : ICryptoAlgorithmParams
{
if (algorithm.GetType() == typeof(RsaOaepCryptoAlgorithmParams))
{
var keyHashString = keyHash switch
{
CryptoKeyHash.Sha384 => "SHA-384",
CryptoKeyHash.Sha512 => "SHA-512",
_ => "SHA-256",
};

return js.InvokeAsync<byte[]>("BitButil.crypto.encryptRsaOaep", algorithm, key, data, keyHashString);
}

if (algorithm.GetType() == typeof(AesCtrCryptoAlgorithmParams))
{
return js.InvokeAsync<byte[]>("BitButil.crypto.encryptAesCtr", algorithm, key, data);
}

if (algorithm.GetType() == typeof(AesCbcCryptoAlgorithmParams))
{
return js.InvokeAsync<byte[]>("BitButil.crypto.encryptAesCbc", algorithm, key, data);
}


return js.InvokeAsync<byte[]>("BitButil.crypto.encryptAesGcm", algorithm, key, data);
}
internal static ValueTask<byte[]> CryptoEncrypt(this IJSRuntime js, CryptoAlgorithm algorithm, byte[] key, byte[] data, byte[]? iv, CryptoKeyHash? keyHash)
=> algorithm switch
{
CryptoAlgorithm.AesCtr => CryptoEncrypt(js, new AesCtrCryptoAlgorithmParams { Counter = iv }, key, data, null),
CryptoAlgorithm.AesCbc => CryptoEncrypt(js, new AesCbcCryptoAlgorithmParams { Iv = iv }, key, data, null),
CryptoAlgorithm.AesGcm => CryptoEncrypt(js, new AesGcmCryptoAlgorithmParams { Iv = iv }, key, data, null),
_ => CryptoEncrypt(js, new RsaOaepCryptoAlgorithmParams(), key, data, keyHash),
};

internal static ValueTask<byte[]> CryptoDecrypt<T>(this IJSRuntime js, T algorithm, byte[] key, byte[] data, CryptoKeyHash? keyHash) where T : ICryptoAlgorithmParams
{
if (algorithm.GetType() == typeof(RsaOaepCryptoAlgorithmParams))
{
var keyHashString = keyHash switch
{
CryptoKeyHash.Sha384 => "SHA-384",
CryptoKeyHash.Sha512 => "SHA-512",
_ => "SHA-256",
};

return js.InvokeAsync<byte[]>("BitButil.crypto.decryptRsaOaep", algorithm, key, data, keyHashString);
}

if (algorithm.GetType() == typeof(AesCtrCryptoAlgorithmParams))
{
return js.InvokeAsync<byte[]>("BitButil.crypto.decryptAesCtr", algorithm, key, data);
}

if (algorithm.GetType() == typeof(AesCbcCryptoAlgorithmParams))
{
return js.InvokeAsync<byte[]>("BitButil.crypto.decryptAesCbc", algorithm, key, data);
}


return js.InvokeAsync<byte[]>("BitButil.crypto.decryptAesGcm", algorithm, key, data);
}
internal static ValueTask<byte[]> CryptoDecrypt(this IJSRuntime js, CryptoAlgorithm algorithm, byte[] key, byte[] data, byte[]? iv, CryptoKeyHash? keyHash)
=> algorithm switch
{
CryptoAlgorithm.AesCtr => CryptoDecrypt(js, new AesCtrCryptoAlgorithmParams { Counter = iv }, key, data, null),
CryptoAlgorithm.AesCbc => CryptoDecrypt(js, new AesCbcCryptoAlgorithmParams { Iv = iv }, key, data, null),
CryptoAlgorithm.AesGcm => CryptoDecrypt(js, new AesGcmCryptoAlgorithmParams { Iv = iv }, key, data, null),
_ => CryptoDecrypt(js, new RsaOaepCryptoAlgorithmParams(), key, data, keyHash),
};
}
43 changes: 43 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Bit.Butil;

/// <summary>
/// The Crypto interface represents basic cryptography features available in the current context.
/// It allows access to a cryptographically strong random number generator and to cryptographic primitives.
/// <br />
/// More info: <see href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto">https://developer.mozilla.org/en-US/docs/Web/API/Crypto</see>
/// </summary>
public class Crypto(IJSRuntime js)
{
/// <summary>
/// The Encrypt method of the Crypto interface that encrypts data.
/// <br />
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt">https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt</see>
/// </summary>
public ValueTask<byte[]> Encrypt<T>(T algorithm, byte[] key, byte[] data, CryptoKeyHash? keyHash = null) where T : ICryptoAlgorithmParams
=> js.CryptoEncrypt(algorithm, key, data, keyHash);
/// <summary>
/// The Encrypt method of the Crypto interface that encrypts data.
/// <br />
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt">https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt</see>
/// </summary>
public ValueTask<byte[]> Encrypt(CryptoAlgorithm algorithm, byte[]key, byte[]data, byte[]? iv = null, CryptoKeyHash? keyHash = null)
=> js.CryptoEncrypt(algorithm, key, data, iv, keyHash);

/// <summary>
/// The Decrypt method of the Crypto interface that decrypts data.
/// <br />
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt">https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt</see>
/// </summary>
public ValueTask<byte[]> Decrypt<T>(T algorithm, byte[] key, byte[] data, CryptoKeyHash? keyHash = null) where T : ICryptoAlgorithmParams
=> js.CryptoDecrypt(algorithm, key, data, keyHash);
/// <summary>
/// The Decrypt method of the Crypto interface that decrypts data.
/// <br />
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt">https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt</see>
/// </summary>
public ValueTask<byte[]> Decrypt(CryptoAlgorithm algorithm, byte[] key, byte[] data, byte[]? iv = null, CryptoKeyHash? keyHash = null)
=> js.CryptoDecrypt(algorithm, key, data, iv, keyHash);
}
16 changes: 16 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/AesCbcCryptoAlgorithmParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Bit.Butil;

/// <summary>
/// Represents the object that should be passed as the algorithm parameter into Crypto APIs when using the AES-CBC algorithm.
/// <br/>
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/AesCbcParams">https://developer.mozilla.org/en-US/docs/Web/API/AesCbcParams</see>
/// </summary>
public class AesCbcCryptoAlgorithmParams : ICryptoAlgorithmParams
{
public string Name => "AES-CBC";

/// <summary>
/// The initialization vector. Must be 16 bytes (128 bits), unpredictable, and preferably cryptographically random.
/// </summary>
public byte[] Iv { get; set; } = default!;
}
21 changes: 21 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/AesCtrCryptoAlgorithmParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Bit.Butil;

/// <summary>
/// Represents the object that should be passed as the algorithm parameter into Crypto APIs when using the AES-CTR algorithm.
/// <br/>
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/AesCtrParams">https://developer.mozilla.org/en-US/docs/Web/API/AesCtrParams</see>
/// </summary>
public class AesCtrCryptoAlgorithmParams : ICryptoAlgorithmParams
{
public string Name => "AES-CTR";

/// <summary>
/// The initial value of the counter block. This must be 16 bytes (128 bits) long (the AES block size).
/// </summary>
public byte[] Counter { get; set; } = default!;

/// <summary>
/// The number of bits in the counter block that are used for the actual counter.
/// </summary>
public int Length { get; set; }
}
30 changes: 30 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/AesGcmCryptoAlgorithmParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Bit.Butil;

/// <summary>
/// Represents the object that should be passed as the algorithm parameter into Crypto APIs when using the AES-GCM algorithm.
/// <br/>
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/AesCbcParams">https://developer.mozilla.org/en-US/docs/Web/API/AesCbcParams</see>
/// </summary>
public class AesGcmCryptoAlgorithmParams : ICryptoAlgorithmParams
{
public string Name => "AES-GCM";

/// <summary>
/// The initialization vector. This must be unique for every encryption operation carried out with a given key.
/// Put another way: never reuse an IV with the same key.
/// The AES-GCM specification recommends that the IV should be 96 bits long,
/// and typically contains bits from a random number generator.
/// </summary>
public byte[] Iv { get; set; } = default!;

/// <summary>
/// This contains additional data that will not be encrypted but will be authenticated along with the encrypted data.
/// </summary>
public byte[] AdditionalData { get; set; } = default!;

/// <summary>
/// This determines the size in bits of the authentication tag generated in the encryption operation and
/// used for authentication in the corresponding decryption.
/// </summary>
public AesGcmTagLength TagLength { get; set; } = AesGcmTagLength.Sixteen;
}
15 changes: 15 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/AesGcmTagLength.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Bit.Butil;

/// <summary>
/// The recommended tagLength number values in Bytes with the corresponding bits value.
/// </summary>
public enum AesGcmTagLength
{
Four = 32,
Eight = 64,
Twelve = 96,
Thirteen = 104,
Fourteen = 112,
Fifteen = 120,
Sixteen = 128
}
9 changes: 9 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/CryptoAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Bit.Butil;

public enum CryptoAlgorithm
{
RsaOaem,
AesCtr,
AesCbc,
AesGcm
}
8 changes: 8 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/CryptoKeyHash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Bit.Butil;

public enum CryptoKeyHash
{
Sha256,
Sha384,
Sha512,
}
6 changes: 6 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/ICryptoAlgorithmParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Bit.Butil;

public interface ICryptoAlgorithmParams
{
string Name { get; }
}
17 changes: 17 additions & 0 deletions src/Butil/Bit.Butil/Publics/Crypto/RsaOaepCryptoAlgorithmParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Bit.Butil;

/// <summary>
/// Represents the object that should be passed as the algorithm parameter into Crypto APIs when using the RSA_OAEP algorithm.
/// <br/>
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/RsaOaepParams">https://developer.mozilla.org/en-US/docs/Web/API/RsaOaepParams</see>
/// </summary>
public class RsaOaepCryptoAlgorithmParams : ICryptoAlgorithmParams
{
public string Name => "RSA-OAEP";

/// <summary>
/// An array of bytes that does not itself need to be encrypted but which should be bound to the ciphertext.
/// A digest of the label is part of the input to the encryption operation.
/// </summary>
public byte[] Label { get; set; } = default!;
}
75 changes: 75 additions & 0 deletions src/Butil/Bit.Butil/Scripts/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
var BitButil = BitButil || {};

(function (butil: any) {
crypto.subtle.generateKey
butil.crypto = {
encryptRsaOaep(algorithm, key, data, keyHash) { return endecryptRsaOaep(algorithm, key, data, keyHash, "encrypt") },
decryptRsaOaep(algorithm, key, data, keyHash) { return endecryptRsaOaep(algorithm, key, data, keyHash, "decrypt") },

encryptAesCtr(algorithm, key, data) { return endecryptAesCtr(algorithm, key, data, "encrypt") },
decryptAesCtr(algorithm, key, data) { return endecryptAesCtr(algorithm, key, data, "decrypt") },

encryptAesCbc(algorithm, key, data) { return endecryptAesCbc(algorithm, key, data, "encrypt") },
decryptAesCbc(algorithm, key, data) { return endecryptAesCbc(algorithm, key, data, "decrypt") },

encryptAesGcm(algorithm, key, data) { return endecryptAesGcm(algorithm, key, data, "encrypt") },
decryptAesGcm(algorithm, key, data) { return endecryptAesGcm(algorithm, key, data, "decrypt") },
};

async function endecryptRsaOaep(algorithm, key, data, keyHash, func) {
const cryptoAlgorithm = {
name: algorithm.name,
label: algorithm.label?.buffer
}

const keyAlgorithm = { name: "RSA-OAEP", hash: keyHash ?? "SHA-256" };

return await endecrypt(cryptoAlgorithm, key, data, keyAlgorithm, func);
}

async function endecryptAesCtr(algorithm, key, data, func) {
const cryptoAlgorithm = {
name: algorithm.name,
counter: algorithm.counter?.buffer,
length: algorithm.length
}

const keyAlgorithm = { name: "AES-CTR" };

return await endecrypt(cryptoAlgorithm, key, data, keyAlgorithm, func);
}

async function endecryptAesCbc(algorithm, key, data, func) {
const cryptoAlgorithm = {
name: algorithm.name,
iv: algorithm.iv?.buffer,
}

const keyAlgorithm = { name: "AES-CBC" };

return await endecrypt(cryptoAlgorithm, key, data, keyAlgorithm, func);
}

async function endecryptAesGcm(algorithm, key, data, func) {
const cryptoAlgorithm = {
name: algorithm.name,
iv: algorithm.iv?.buffer,
additionalData: algorithm.additionalData?.buffer,
tagLength: algorithm.tagLength,
}

const keyAlgorithm = { name: "AES-GCM" };

return await endecrypt(cryptoAlgorithm, key, data, keyAlgorithm, func);
}

async function endecrypt(cryptoAlgorithm, key, data, keyAlgorithm, func) {
const cryptoKey = await crypto.subtle.importKey("raw", key.buffer, keyAlgorithm, false, ["encrypt", "decrypt"]);

const buffer = await window.crypto.subtle[func](cryptoAlgorithm, cryptoKey, data.buffer);

return new Uint8Array(buffer);
}


}(BitButil));
Loading

0 comments on commit 9839591

Please sign in to comment.