Revitアドインで機密情報を安全に保存する方法

# はじめに
RevitアドインでパスワードやAPIキーなどの機密情報を扱う際に、ローカルへ安全に保存する方法を説明します。コードをコピペしたい型は [コード全体](#コード全体) を確認してください。

# プロジェクトにApp.configを追加
App.configとは、アプリケーション固有の設定を記述するXML形式のファイルです。Visual Studioの「新しい項目の追加」ダイアログで __アプリケーション構成ファイル__ を選択して追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2059946/d3da5226-f28e-7624-ba15-b8de601a033e.png)

必要に応じてkey=value形式で属性を記述します。
```XML:App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<appSettings>
		<add key="Token" value="" />
		<add key="UserName" value=""/>
	</appSettings>
</configuration>
```

# System.Configuration を参照
「参照マネージャー」ダイアログで __System.Configuration__ にチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2059946/6aa356f0-99ed-28dd-b1f0-ff7b534dfbdb.png)


# App.configへの読み書き
ConfigurationManager.OpenExeConfiguration()メソッドでApp.configを操作できます。メソッドの引数として現在実行中のアセンブリのパス（このアドインのパス）を渡します。

:::note warn
RevitアドインはRevit.exeプロセス内でホストされて実行されます。そのため、パスを指定せずに設定ファイルを読み込むと、アドイン自体のApp.configではなくホストプロセス（Revit.exe）のApp.configが参照されてしまいます。
:::

https://learn.microsoft.com/ja-jp/dotnet/api/system.configuration.configurationmanager.openexeconfiguration?view=windowsdesktop-8.0

```C#:App.configへ文字列を保存するメソッド
public void Save(string key, string value)
{
    var config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
    config.AppSettings.Settings[key].Value = value;
    config.Save();
}
```

```C#:App.configから文字列を取得するメソッド
public static string Get(string key)
{
    var config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
    var value = config.AppSettings.Settings[key].Value;
    return value;
}
```

# 暗号化
パスワードやAPIキーなどの機密情報を平文のままApp.configに保存すると、ファイルがハッキングされたりPCが売却された際に、機密情報が流出してしまう恐れがあります。
そこで、機密情報をApp.configへ保存する前にDPAPIを用いて暗号化します。DPAPIはWindows OSに標準で組み込まれているデータ保護機能です。

https://learn.microsoft.com/ja-jp/dotnet/standard/security/how-to-use-data-protection

```C#:文字列を暗号化するメソッド
public static string Encrypt(string plainText)
{
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
    byte[] encryptedBytes = ProtectedData.Protect(plainTextBytes, null, DataProtectionScope.CurrentUser);
    return Convert.ToBase64String(encryptedBytes);
}
```

```C#:文字列を復号化するメソッド
public static string Decrypt(string encryptedText)
{
    byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
    byte[] plainTextBytes = ProtectedData.Unprotect(encryptedBytes, null, DataProtectionScope.CurrentUser);
    return Encoding.UTF8.GetString(plainTextBytes);
}
```
Saveメソッド・Getメソッドを以下のように修正します。

```diff_c_sharp:App.configへ文字列を保存するメソッド
public void Save(string key, string value)
{
    var config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
+    config.AppSettings.Settings[key].Value = DPAPI.Encrypt(value);
    config.Save();
}
```

```diff_c_sharp:App.configへ文字列を保存するメソッド（修正版）
public static string Get(string key)
{
    var config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
    var value = config.AppSettings.Settings[key].Value;
+    return DPAPI.Decrypt(value);
}
```

# コード全体
```C#
using System;
using System.Configuration;
using System.Security.Cryptography;
using System.Text;

namespace Addin
{
    /// <summary>
    /// DPAPIを使って文字列を暗号化・復号化するクラス
    /// </summary>
    public static class DPAPI
    {
        // 文字列を暗号化するメソッド
        public static string Encrypt(string plainText)
        {
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            byte[] encryptedBytes = ProtectedData.Protect(plainTextBytes, null, DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedBytes);
        }

        // 文字列を復号化するメソッド
        public static string Decrypt(string encryptedText)
        {
            byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
            byte[] plainTextBytes = ProtectedData.Unprotect(encryptedBytes, null, DataProtectionScope.CurrentUser);
            return Encoding.UTF8.GetString(plainTextBytes);
        }
    }

    /// <summary>
    /// App.configの設定値を取得・保存するクラス
    /// </summary>
    public static class ConfigManager
    {
        // 設定値を暗号化して保存するメソッド
        public static void Save(string key, string value)
        {
            var config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            config.AppSettings.Settings[key].Value = DPAPI.Encrypt(value);
            config.Save();
        }

        // 設定値を復号化して取得するメソッド
        public static string Get(string key)
        {
            var config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            var value = config.AppSettings.Settings[key].Value;
            return DPAPI.Decrypt(value);
        }
    }
}
```