AZUREソリューション部の土田です。
ブロックチェーン上にサービスを構築する場合、秘密鍵の管理は重要な要素です。
現状、ユーザーに秘密鍵を生成してもらい、各自で安全に管理してもらうといった方式は、現実的ではない場合が多いです。
何らかの手段で、秘密鍵を管理する必要があります。
今回は、Azure Key Vaultを使用した管理方法を紹介します。
Azure Key VaultをWebアプリケーションから使用するためには、
以下のものが必要です。
取得方法、アプリでの設定方法は公式ドキュメント、Azure Key Vaultの概要およびWebアプリからAzure Key Vaultの使用するを参照してください。
Key Vaultのクライアントクラスは以下の通りです。
public class KeyVault : ISecretsStore
{
public string Url { get; }
#region private members
private readonly KeyVaultClient m_kvClient;
private readonly string m_applicationId;
private readonly string m_applicationSecret;
#endregion
/// <summary>
/// Ctor for Key vault class
/// </summary>
/// <param name="kvUrl">The Azure keyvault url</param>
/// <param name="applicationId">The azure service principal application id</param>
/// <param name="applicationSecret">The azure service principal application secret</param>
public KeyVault(string kvUrl, string applicationId, string applicationSecret)
{
Url = kvUrl;
m_applicationId = applicationId;
m_applicationSecret = applicationSecret;
m_kvClient = new KeyVaultClient(GetAccessTokenAsync, new HttpClient());
}
/// <summary>
/// Gets the specified secret
/// </summary>
/// <returns>The secret</returns>
/// <param name="secretName">Secret identifier</param>
public async Task<string> GetSecretAsync(string secretName)
{
try
{
return (await m_kvClient.GetSecretAsync(Url, secretName)).Value;
}
catch (KeyVaultErrorException ex)
{
Console.WriteLine($"Exception while trying to get secret {secretName}, {ex}");
throw;
}
}
/// <summary>
/// Sets a secret in Azure keyvault
/// </summary>
/// <returns>The secret.</returns>
/// <param name="secretName">Secret identifier.</param>
/// <param name="value">The value to be stored.</param>
public async Task SetSecretAsync(string secretName, string value)
{
try
{
await m_kvClient.SetSecretAsync(Url, secretName, value);
}
catch (KeyVaultErrorException ex)
{
Console.WriteLine($"Exception while trying to set secret {secretName}, {ex}");
throw;
}
}
#region Private Methods
private async Task<string> GetAccessTokenAsync(
string authority,
string resource,
string scope)
{
var clientCredential = new ClientCredential(m_applicationId, m_applicationSecret);
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, clientCredential);
return result.AccessToken;
}
#endregion
}
このKey Vaultクライアントを使用して、秘密鍵を管理するAccountクラスを作成します。
Accountクラスには以下のような機能が実装されています。
サンプルはEthereum版ですが、以下のようになります。
public class EthereumAccount : IBlockchainAccount
{
private readonly Web3 m_web3;
private readonly ISecretsStore m_db;
#region Public Methods
/// <summary>
/// Ctor for EthereumAccount class
/// </summary>
/// <param name="database">The database which holds the clients' private keys.</param>
/// <param name="nodeUrl">The Ethereum node Url. If it's empty, it will work with the local Ethereum testnet.</param>
public EthereumAccount(ISecretsStore database, string nodeUrl = "")
{
m_db = database;
m_web3 = string.IsNullOrEmpty(nodeUrl) ? new Web3() : new Web3(nodeUrl);
}
/// <summary>
/// Creates blockchain account if needed and store the private key in Azure KeyVault
/// </summary>
/// <param name="identifier">key pair identifier.</param>
/// <param name="privateKey">The given private key to store, if not supplied a new private key will be generated</param>
public async Task<string> CreateAccountAsync(string identifier, string privateKey = "")
{
if (string.IsNullOrEmpty(privateKey))
{
var account = EthECKey.GenerateKey();
privateKey = account.GetPrivateKey();
}
await StoreAccountAsync(identifier, privateKey);
return new EthECKey(privateKey).GetPublicAddress();
}
/// <summary>
/// Returns the public key by the key vault identifier
/// </summary>
/// <param name="identifier">The user id</param>
/// <returns>The user's public address</returns>
public async Task<string> GetPublicAddressAsync(string identifier)
{
var privatekey = await GetPrivateKeyAsync(identifier);
return new EthECKey(privatekey).GetPublicAddress();
}
/// <summary>
/// Sign a blockchain transaction
/// </summary>
/// <param name="senderIdentifier">The sender identifier, as it saved in the Azure KeyVault (Id, name etc.)</param>
/// <param name="recieverAddress">The receiver public address</param>
/// <param name="amountInWei">The amount to send in Wei (ethereum units)</param>
/// <returns>The transaction hash</returns>
public async Task<string> SignTransactionAsync(string senderIdentifier, string recieverAddress,
BigInteger amountInWei)
{
var senderPrivateKey = await GetPrivateKeyAsync(senderIdentifier);
var senderEthKey = new EthECKey(senderPrivateKey);
var txCount =
await m_web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(senderEthKey.GetPublicAddress());
return Web3.OfflineTransactionSigner.SignTransaction(senderPrivateKey, recieverAddress, amountInWei,
txCount.Value);
}
/// <summary>
/// Send the transaction to the public node.
/// </summary>
/// <param name="hash">The transaction hash</param>
/// <returns>The transaction result</returns>
public async Task<string> SendRawTransactionAsync(string hash)
{
return await m_web3.Eth.Transactions.SendRawTransaction.SendRequestAsync(hash);
}
/// <summary>
/// Gets the balance of the provided account - if public address provided get balance by address
/// Otherwise get balance by identifier
/// </summary>
/// <param name="publicAddress">The public address of the account</param>
/// <returns>Returns the balance in ether.</returns>
public async Task<decimal> GetCurrentBalance(string publicAddress = "", string identifier = "")
{
if (string.IsNullOrEmpty(publicAddress) && string.IsNullOrEmpty(identifier))
{
throw new ArgumentNullException("public address or identifier should be provided");
}
if (string.IsNullOrEmpty(publicAddress))
{
publicAddress = await GetPublicAddressAsync(identifier);
}
var unitConverion = new UnitConversion();
return unitConverion.FromWei(await m_web3.Eth.GetBalance.SendRequestAsync(publicAddress));
}
#endregion
#region Private Methods
/// <summary>
/// Stores the account async.
/// </summary>
/// <returns>If the account was created successfully</returns>
/// <param name="identifier">Identifier.</param>
/// <param name="privateKey">The private key.</param>
private async Task StoreAccountAsync(string identifier, string privateKey)
{
await m_db.SetSecretAsync(identifier, privateKey);
}
/// <summary>
/// Returns the private key by the key vault identifier
/// </summary>
/// <param name="identifier">The user id</param>
/// <returns>The user's public key</returns>
private async Task<string> GetPrivateKeyAsync(string identifier)
{
return await m_db.GetSecretAsync(identifier);
}
#endregion
}
参考資料
サンプルリポジトリ
https://github.com/Azure/Secured-SaaS-Wallet
公式ドキュメント
https://docs.microsoft.com/ja-jp/azure/key-vault/key-vault-get-started
https://docs.microsoft.com/ja-jp/azure/key-vault/key-vault-use-from-web-application
公式ブログ