Blazor wasm で System.Security.Cryptography API の代わりに使えそうな暗号ライブラリ

2021/07/11DevelopmentBlazor

.NET 5 から Blazor WebAssembly では System.Security.Cryptography API がサポートされません。代わりに使えそうな暗号ライブラリを試してみました。

Bouncy Castle

Bouncy Castle は、Java と C# で提供されている暗号ライブラリ。GitHub リポジトリ bcgit/bc-csharp を fork して、個人が NuGet BouncyCastle.NetCore を公開されています。

※ Blazor で下記の AES・HMAC のコードを動作確認済み

CryptographyHelpers (CryptHash.NET)

CryptHash.NET は、Windows/Linux/Mac で動作するよう設計された .NET Standard 2.0/2.1 の暗号ライブラリ。現在、.NET 5 ライブラリ CryptographyHelpers として移行しています。NuGet でも公開されています。

※ Blazor で動作未確認です

比較: AES

必要だったのが AES と HMAC-SHA 関連のみだったので、それ以外は調べていません。

System.Security.Cryptography

まずは、Blazor WebAssembly で使うには、移行が必要なコードサンプル。Aes コンストラクター (System.Security.Cryptography) | Microsoft Docs のサンプルコードを元にしたコード。

// Encrypt
static byte[] EncryptStringToBytes_Aes(string plainText, byte[] key, byte[] iv)
{
// Create an Aes object
// with the specified key and IV.
using var aesAlg = Aes.Create();
aesAlg.Key = key;
aesAlg.IV = iv;
// Create an encryptor to perform the stream transform.
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using var msEncrypt = new MemoryStream();
using var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
using (var swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
var encrypted = msEncrypt.ToArray();
// Return the encrypted bytes from the memory stream.
return encrypted;
}
// Decrypt
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] key, byte[] iv)
{
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an Aes object
// with the specified key and IV.
using var aesAlg = Aes.Create();
aesAlg.Key = key;
aesAlg.IV = iv;
// Create a decryptor to perform the stream transform.
var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using var msDecrypt = new MemoryStream(cipherText);
using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
using var srDecrypt = new StreamReader(csDecrypt);
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
return plaintext;
}
view raw Aes.cs hosted with ❤ by GitHub

Bouncy Castle

Bouncy Castle を用いたコード。c# - Encrypt string with Bouncy Castle AES/CBC/PKCS7 - Stack Overflow を参考にしています。

// Encrypt
static byte[] EncryptStringToBytes_Aes_BouncyCastle(string plainText, byte[] key, byte[] iv)
{
var input = Encoding.UTF8.GetBytes(plainText);
var engine = new AesEngine();
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher); // PKCS5/7 padding
var keyParam = new KeyParameter(key);
var keyParamWithIV = new ParametersWithIV(keyParam, iv);
// Encrypt
cipher.Init(true, keyParamWithIV);
var encrypted = new byte[cipher.GetOutputSize(input.Length)];
var length = cipher.ProcessBytes(input, encrypted, 0);
cipher.DoFinal(encrypted, length);
return encrypted;
}
// Decrypt
static string DecryptStringFromBytes_Aes_BouncyCastle(byte[] cipherText, byte[] key, byte[] iv)
{
var engine = new AesEngine();
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher); // PKCS5/7 padding
var keyParam = new KeyParameter(key);
var keyParamWithIV = new ParametersWithIV(keyParam, iv);
// Decrypt
cipher.Init(false, keyParamWithIV);
var decrypted = new byte[cipher.GetOutputSize(cipherText.Length)];
var length = cipher.ProcessBytes(cipherText, decrypted, 0);
cipher.DoFinal(decrypted, length);
return Encoding.UTF8.GetString(decrypted).TrimEnd('\0');
}
view raw Aes.cs hosted with ❤ by GitHub

CryptHash.NET

CryptHash.NET は、AES の基本の暗号化・複合処理は、抽象クラスの internal メソッドのため全く同じことはできません。パスワードの暗号化や簡潔に書ける便利そうなオーバーロードが、いろいろあるのでオリジナルのコードを読んでみてください。

static byte[] EncryptStringToBytes_Aes_CryptHashNet(string plainText, byte[] key, byte[] iv, out byte[] authenticationKey, out byte[] tag)
{
var aes = new AE_AES_128_CBC_HMAC_SHA_256();
var result = aes.EncryptString(Encoding.UTF8.GetBytes(plainText), key, iv);
authenticationKey = result.AuthenticationKey;
tag = result.Tag;
return result.EncryptedDataBytes;
}
static string DecryptStringFromBytes_Aes_CryptHashNet(byte[] cipherText, byte[] key, byte[] iv, byte[] authenticationKey, byte[] sentTag)
{
var aes = new AE_AES_128_CBC_HMAC_SHA_256();
var result = aes.DecryptString(cipherText, key, iv, authenticationKey, sentTag);
return result.DecryptedDataString;
}
view raw Aes.cs hosted with ❤ by GitHub

呼び出しサンプル

上記メソッドを呼び出す部分のコードです。

static void Main(string[] args)
{
var text = "山路を登りながら、こう考えた。";
var key = Encoding.UTF8.GetBytes("xxxxxxxxxxxxxxxx");
var iv = Encoding.UTF8.GetBytes("0123456789abcdef");
// System.Security.Cryptography
{
var cipherText = EncryptStringToBytes_Aes(text, key, iv);
Console.WriteLine($"cipherText: {Convert.ToBase64String(cipherText)}");
var plainText = DecryptStringFromBytes_Aes(cipherText, key, iv);
Console.WriteLine($"plainText: {plainText}");
}
// Bouncy Castle
{
var cipherText = EncryptStringToBytes_Aes_BouncyCastle(text, key, iv);
Console.WriteLine($"cipherText: {Convert.ToBase64String(cipherText)}");
var plainText = DecryptStringFromBytes_Aes_BouncyCastle(cipherText, key, iv);
Console.WriteLine($"plainText: {plainText}");
}
// CryptHash.Net
{
var cipherText = EncryptStringToBytes_Aes_CryptHashNet(text, key, iv, out var authenticationKey, out var tag);
Console.WriteLine($"cipherText: {Convert.ToBase64String(cipherText)}");
var plainText = DecryptStringFromBytes_Aes_CryptHashNet(cipherText, key, iv, authenticationKey, tag);
Console.WriteLine($"plainText: {plainText}");
}
}

比較: HMAC

HMAC-SHA-1 の処理比較です。

static void Main(string[] args)
{
var text = "山路を登りながら、こう考えた。";
var key = Encoding.UTF8.GetBytes("xxxxxxxxxxxxxxxx");
{
var hash = HmacSha1(key, Encoding.UTF8.GetBytes(text));
Console.WriteLine($"hash: {Convert.ToBase64String(hash)}");
}
{
var hash = HmacSha1_BouncyCastle(key, Encoding.UTF8.GetBytes(text));
Console.WriteLine($"hash(BouncyCastle): {Convert.ToBase64String(hash)}");
}
{
var hash = HmacSha1_CryptHashNet(key, Encoding.UTF8.GetBytes(text));
Console.WriteLine($"hash(CryptHashNet): {Convert.ToBase64String(hash)}");
}
}
// System.Security.Cryptography
static byte[] HmacSha1(byte[] key, byte[] message)
{
using var hmac = new HMACSHA1(key);
return hmac.ComputeHash(message);
}
// Bouncy Castle
static byte[] HmacSha1_BouncyCastle(byte[] key, byte[] message)
{
var hmac = new HMac(new Sha1Digest());
var output = new byte[hmac.GetMacSize()];
hmac.Init(new KeyParameter(key));
hmac.BlockUpdate(message, 0, message.Length);
hmac.DoFinal(output, 0);
return output;
}
// CryptHash.Net
static byte[] HmacSha1_CryptHashNet(byte[] key, byte[] message)
{
var hmac = new HMAC_SHA_1();
var result = hmac.ComputeHMAC(message, key);
return result.HashBytes;
}
view raw Hmac.cs hosted with ❤ by GitHub

DevelopmentBlazor

Posted by jz5