strongSwan と iOS (13+) 端末間で IKEv2 に ChaCha20-Poly1305 を使って遅いラズパイ VPN サーバーを高速化

iOS 13 から IKEv2 の暗号化アルゴリズムに ChaCha20-Poly1305 を使うことができるようになりました。特に Raspberry Pi などの AES 専用命令を積んでいない ARM プロセッサなどで VPN サーバーを構築している場合、ChaCha20-Poly1305 を使うことで著しいスループットの向上が期待できます。
今回は暗号化に ChaCha20-Poly1305、鍵導出に SHA-512、鍵交換に X25519 を使うように設定してみます。

## strongSwan 側の設定
https://wiki.strongswan.org/projects/strongswan/wiki/IKEv2CipherSuites

strongSwan で使える暗号化アルゴリズムについてはこちらに網羅されています。

### swanctl を使う場合
```/etc/swanctl/swanctl.conf
connections {
    <コネクション名> {
        ...
        proposals = chacha20poly1305-prfsha512-curve25519, default
        ...
    }

    ...

    children {
        <子SA名> {
            ...
            esp_proposals = chacha20poly1305-curve25519, default
            ...
        }
    }
}
```

### 旧来の ipsec.conf を使う場合
```/etc/ipsec.conf
conn <コネクション名>
  ...
  ike=chacha20poly1305-prfsha512-curve25519
  esp=chacha20poly1305-curve25519
  ...
```

## iOS 側の設定
XML (plist) で VPN プロファイルを記述します。
ここではサーバーの認証に PSK ではなく証明書を、クライアントの認証に EAP-MSCHAPv2 を使用します。
プロファイルの書き方については下記にドキュメントがまとまっており、strongSwan の Wiki にはクライアント証明書認証にする場合の例も載っています。

- [VPN.IKEv2 | Apple Developer Documentation](https://developer.apple.com/documentation/devicemanagement/vpn/ikev2)
- [IKEv2 Configuration Profile for Apple iOS 8 and newer - IKEv2 Configuration Profile for Apple iOS 8 and newer - strongSwan](https://wiki.strongswan.org/projects/strongswan/wiki/AppleIKEv2Profile)

```xml:VPN.mobileconfig
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- プロファイル名 -->
    <key>PayloadDisplayName</key>
    <string>自宅用VPNプロファイル</string>
    <!-- プロファイルの識別子。適当なFQDNを逆向きにした形で書く -->
    <key>PayloadIdentifier</key>
    <string>com.example.profile</string>
    <!-- uuidgenなどで生成したユニークな UUID -->
    <key>PayloadUUID</key>
    <string>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</string>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
    <key>PayloadContent</key>
    <array>
        <dict>
            <!-- VPN プロファイルの識別子 -->
            <key>PayloadIdentifier</key>
            <string>com.example.profile.vpn</string>
            <!-- これも uuidgen などで適当に生成 -->
            <key>PayloadUUID</key>
            <string>yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy</string>
            <key>PayloadType</key>
            <string>com.apple.vpn.managed</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <!-- VPN 名 -->
            <key>UserDefinedName</key>
            <string>自宅VPN</string>
            <key>VPNType</key>
            <string>IKEv2</string>
            <key>IKEv2</key>
            <dict>
                <!-- VPN サーバーのアドレス -->
                <key>RemoteAddress</key>
                <string>vpn.example.com</string>
                <!-- サーバーの証明書の CN または subjectAltName -->
                <key>RemoteIdentifier</key>
                <string>vpn.example.com</string>
                <!-- クライアント証明書認証は使わないので空 -->
                <key>LocalIdentifier</key>
                <string></string>
                <!-- サーバーの証明書の発行者の CN。自己署名証明書を使う場合は RemoteIdentifier と同じになる -->
                <key>ServerCertificateIssuerCommonName</key>
                <string>vpn.example.com</string>
                <!-- これもサーバーの証明書の CN または subjectAltName -->
                <key>ServerCertificateCommonName</key>
                <string>vpn.example.com</string>
                <!-- サーバーは証明書認証を使う -->
                <key>AuthenticationMethod</key>
                <string>Certificate</string>
                <!-- クライアントは EAP-MSCHAPv2 で認証 -->
                <key>ExtendedAuthEnabled</key>
                <integer>1</integer>
                <!-- EAP のユーザー名 -->
                <key>AuthName</key>
                <string>user_name</string>
                <!-- EAP のパスワード -->
                <key>AuthPassword</key>
                <string>my_password</string>
                <!-- PFS を有効にする -->
                <key>EnablePFS</key>
                <integer>1</integer>
                <!-- IKE SA の暗号化パラメータ -->
                <key>IKESecurityAssociationParameters</key>
                <dict>
                    <key>EncryptionAlgorithm</key>
                    <string>ChaCha20Poly1305</string>
                    <key>IntegrityAlgorithm</key>
                    <string>SHA2-512</string>
                    <!-- DHグループ31 = X25519 -->
                    <key>DiffieHellmanGroup</key>
                    <integer>31</integer>
                </dict>
                <!-- Child SA の暗号化パラメータ -->
                <key>ChildSecurityAssociationParameters</key>
                <dict>
                    <key>EncryptionAlgorithm</key>
                    <string>ChaCha20Poly1305</string>
                    <key>IntegrityAlgorithm</key>
                    <string>SHA2-512</string>
                    <key>DiffieHellmanGroup</key>
                    <integer>31</integer>
                </dict>
            </dict>
        </dict>
    </array>
</dict>
</plist>
```

このファイルを .mobileconfig という拡張子で保存し、メールか iCloud Drive、あるいは NAS で共有して iOS 端末に送ります。受信したファイルを iOS 側で開くと設定画面から VPN プロファイルをインストールできるようになります。

## 速度測定
Raspberry Pi 4 (64ビット) の strongSwan で立てた VPN サーバーで測定した結果です。
スピードテストサーバーは OPEN Project (via 20G SINET) を使用しました。
上から順に VPN なし、ChaCha20-Poly1305、AES_CBC_256/HMAC_SHA2_256_128 です。ChaCha20-Poly1305 を使うと VPN なしとほぼ変わらない速度が出ています。
![IMG_1778.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677297/1d25a2de-1949-f03d-85ae-f2848cbbcbaa.jpeg)


## まとめ
AES 専用命令のないサーバーで ChaCha20-Poly1305 を使うとパフォーマンスが圧倒的に向上することがわかりました。
VPN の速度が遅い場合、サーバーや端末が対応している場合は ChaCha20-Poly1305 を使うようにしてみるといいかもしれません。