【Swift】EllipticCurveKeyPairライブラリを使って暗号化と復号化をやってみた

## はじめに
EllipticCurveKeyPairライブラリを使うと、Swiftで簡単に暗号化・復号化や公開鍵の取得が可能になります。この記事では、その基本的な使い方を初心者向けに解説します。今回は、テキストを暗号化・復号化する機能と、公開鍵を取得する機能を持つシンプルなアプリを作成します。

https://github.com/agens-no/EllipticCurveKeyPair

## 環境
開発には以下の環境を使用しています。

- Apple M2 Pro
- macOS Sonoma 14.2.1
- Xcode Version 15.0
- Swift version 5.9

## 実装するアプリの概要
今回作成するアプリは、以下の機能を持ちます。

- テキストの暗号化と復号化
- 公開鍵の取得
アプリのUIはシンプルで、テキストを入力し「暗号化」ボタンを押すと暗号化されたテキストが表示され、「復号化」ボタンを押すと元のテキストが表示されます。また、「公開鍵取得」ボタンを押すと、使用している公開鍵がログに表示されます。


<img width="260" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658262/a4dc794e-d539-fa86-9f40-93bac5fcdbe5.gif">

## 実装
### 1. ライブラリをインストール
今回はCocoaPodsでライブラリをインストールしていきます。

#### ① Podfile作成
プロジェクトのルートディレクトリでターミナルを開き、以下のコマンドを実行してPodfileを作成します。
```zsh
pod init
```
#### ② Podfileの編集
次に、生成されたPodfileを編集して、EllipticCurveKeyPairライブラリをプロジェクトに追加します。ターミナルで以下のコマンドを実行してPodfileを開きます。

```zsh
vi Podfile
```
`i`を押して、INSERTモードにし`pod 'EllipticCurveKeyPair'`を追加してください。

```diff
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'EllipticCurveKeyPair_sample' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

+ pod 'EllipticCurveKeyPair'

  # Pods for EllipticCurveKeyPair_sample

  target 'EllipticCurveKeyPair_sampleTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'EllipticCurveKeyPair_sampleUITests' do
    # Pods for testing
  end

end
```
編集が完了したら、まず、`esc`キーを押してインサートモードを終了します。
次に、`:wq`と入力してEnterキーを押します。これにより、変更が保存されVimエディタが閉じます。

#### ③ ライブラリインストール
以下のコマンドを実行してEllipticCurveKeyPairライブラリをインストールします。
```zsh
pod install
```

ライブラリを追加してからは、.xcworkspaceの方を開いて開発していきましょう。

### 2. UIの構築
暗号化するテキストを表示するUILabelと、暗号化、復号化、公開鍵取得の機能を実行するためのUIButtonを3つ定義します。Outlet接続を忘れずに。
```ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var originalTextLabel: UILabel!
    @IBOutlet weak var encryptButton: UIButton!
    @IBOutlet weak var decryptButton: UIButton!
    @IBOutlet weak var publicKeyButton: UIButton!
    
    let originalText = "これは秘密のメッセージです"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // オリジナルテキスト
        originalTextLabel.text = originalText
        // ボタンのテキスト
        encryptButton.setTitle("暗号化", for: .normal)
        decryptButton.setTitle("復号化", for: .normal)
        publicKeyButton.setTitle("公開鍵取得", for: .normal)
    }   
}
```

### 3. EllipticCurveKeyPairの設定
EllipticCurveKeyPairライブラリを使用して、iOSアプリ内で安全に鍵ペア（公開鍵と秘密鍵）を生成し、管理する方法について解説します。このライブラリは、特にセキュアな環境での鍵の生成、保存、および使用を容易にするために設計されています。

```ViewController.swift
import UIKit

import EllipticCurveKeyPair

struct KeyPair {
    static let manager: EllipticCurveKeyPair.Manager = {
        let publicAccessControl = EllipticCurveKeyPair.AccessControl(
            protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
            flags: [])
        let privateAccessControl = EllipticCurveKeyPair.AccessControl(
            protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
            flags: {
                return EllipticCurveKeyPair.Device.hasSecureEnclave
                ? [.userPresence, .privateKeyUsage]
                : [.userPresence]
            }()
        )
        let config = EllipticCurveKeyPair.Config(
            publicLabel: "payment.sign.public",
            privateLabel: "payment.sign.private",
            operationPrompt: "Confirm payment",
            publicKeyAccessControl: publicAccessControl,
            privateKeyAccessControl: privateAccessControl,
            token: .secureEnclaveIfAvailable)
        return EllipticCurveKeyPair.Manager(config: config)
    }()
}

class ViewController: UIViewController {
  // 略 
}
```
#### ① 公開鍵のアクセス制御
```swift
let publicAccessControl = EllipticCurveKeyPair.AccessControl(
    protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
    flags: [])
```
公開鍵に対するアクセス制御を定義します。ここでは、デバイスが最初にアンロックされた後にのみアクセス可能な設定（kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly）を使用しています。これは、デバイスが再起動された後でも、ユーザーが一度デバイスをアンロックすれば、アプリが公開鍵にアクセスできることを意味します。

https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly

#### ② 秘密鍵のアクセス制御
```swift
let privateAccessControl = EllipticCurveKeyPair.AccessControl(
    protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
    flags: {
        return EllipticCurveKeyPair.Device.hasSecureEnclave
        ? [.userPresence, .privateKeyUsage]
        : [.userPresence]
    }()
)
```
秘密鍵のアクセス制御を定義します。ここでは、公開鍵と同じくデバイスが最初にアンロックされた後にのみアクセス可能な設定を使用していますが、さらにEllipticCurveKeyPair.Device.hasSecureEnclaveをチェックして、デバイスがSecure Enclaveを持っている場合は追加のフラグ（.userPresenceと.privateKeyUsage）を設定しています。

:::note info
EllipticCurveKeyPair.Device.hasSecureEnclaveについて
このプロパティがtrueを返す場合、アプリケーションはSecure Enclaveを利用して鍵をより安全に管理することができます。具体的には、秘密鍵をSecure Enclave内に保存し、秘密鍵の使用時にはデバイスのユーザー認証（パスコード入力やTouch ID、Face IDによる認証）を要求することができます。
:::

`.userPresence`と`.privateKeyUsage`は、EllipticCurveKeyPairライブラリにおけるアクセス制御フラグです。

:::note info
userPresenceは、秘密鍵を使用する前に、デバイスのユーザーが物理的に存在していることを確認するために使用されます。これは通常、デバイスのロック解除に使用される手段（パスコード、Touch ID、Face IDなどの生体認証）を通じて行われます。このフラグが設定されている場合、秘密鍵の操作（例えば、データの暗号化や署名生成）を行う際にユーザー認証が必要になります。
:::

https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/1392879-userpresence

:::note info
privateKeyUsageは、秘密鍵の使用目的を限定し、ユーザーにその使用目的を明示するために使用されます。このフラグが設定されている場合、秘密鍵を使用する際には、その操作が秘密鍵の使用を意図したものであることをユーザーに示すための追加的な確認（例えば、支払いの承認や文書の署名確認など）が求められます。
:::

https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/1617983-privatekeyusage

### 4. 暗号化の実装
encryptButtonTappedメソッドを実装し、テキストの暗号化機能を追加します。
```ViewController.swift
// 暗号化されたデータを保存する
var encryptedData: Data?

@IBAction func encryptButtonTapped(_ sender: UIButton) {
    guard let textData = originalText.data(using: .utf8) else { return }
    do {
        let encryptedData = try KeyPair.manager.encrypt(textData, hash: .sha256)
        // 暗号化されたデータを保存
        self.encryptedData = encryptedData
        // 表示用にBase64エンコード
        let encryptedString = encryptedData.base64EncodedString()
        // 暗号化されたテキストを表示
        originalTextLabel.text = encryptedString
    } catch {
        print("暗号化に失敗しました: \(error)")
    }
}
```
#### ① テキストデータの準備
暗号化したいテキスト（originalText）を、data(using: .utf8)メソッドを使用してData型に変換します。これにより、テキストがバイトデータに変換され、暗号化処理の準備が整います。

#### ② 暗号化処理
KeyPair.manager.encryptメソッドを使用して、準備したテキストデータを暗号化します。
このメソッドは、EllipticCurveKeyPairライブラリが提供する機能で、秘密鍵を使用してデータを暗号化します。hash: .sha256パラメータは、暗号化の際に使用するハッシュアルゴリズムを指定します。

#### ③ 暗号化データの保存
暗号化されたデータ（encryptedData）を変数に保存します。これにより、後で復号化する際に使用できます。

#### ④ 暗号化データの表示
暗号化されたデータをBase64エンコードして、ユーザーにわかりやすい形で表示します。Base64エンコードは、バイナリデータをテキスト形式に変換するエンコーディング方式です。

### 5. 復号化機能の実装
decryptButtonTappedメソッドを実装し、復号化の機能を追加します。
```ViewController.swift
@IBAction func decryptButtonTapped(_ sender: UIButton) {
    guard let encryptedData = self.encryptedData else { return }
    // バックグラウンドスレッドで復号化処理を実行
    DispatchQueue.global(qos: .userInitiated).async {
        do {
            let decryptedData = try KeyPair.manager.decrypt(encryptedData, hash: .sha256)
            let decryptedString = String(data: decryptedData, encoding: .utf8)
            // メインスレッドでUIを更新(復号化されたテキストを表示)
            DispatchQueue.main.async {
                self.originalTextLabel.text = decryptedString
            }
        } catch {
            print("復号化に失敗しました: \(error)")
        }
    }
}
```
#### ① 暗号化データの取得
以前に暗号化して保存したデータ（encryptedData）を取得します。

#### ② 復号化処理
KeyPair.manager.decryptメソッドを使用して、暗号化されたデータを復号化します。
この処理は、秘密鍵を使用して暗号化されたデータを元の平文データに戻します。
復号化処理はリソースを多く使用する可能性があるため、バックグラウンドスレッドで実行することが推奨されます。

https://developer.apple.com/documentation/dispatch/dispatchqueue

https://zenn.dev/yumemi_inc/articles/ios-ui-thread

#### ③ 復号化データの表示
復号化されたデータ（decryptedData）を文字列に変換し、UIに表示します。
この処理はUIの更新を伴うため、メインスレッドで実行する必要があります。

### 6. 公開鍵取得機能の実装
getPublicKeyButtonTappedメソッドを実装し、公開鍵を取得して表示する機能を追加します。
```ViewController.swift
@IBAction func getPublicKeyButtonTapped(_ sender: UIButton) {
    do {
        // 公開鍵を取得
        let publicKey = try KeyPair.manager.publicKey().data().PEM
        // デバッグでは、公開鍵をPEM形式で取得する
        print("Public Key:", publicKey)
    } catch {
        print("公開鍵の取得に失敗しました: \(error)")
    }
}
```
#### ① 公開鍵の取得
KeyPair.manager.publicKey()を呼び出すことで、EllipticCurveKeyPairライブラリが管理する公開鍵にアクセスします。

#### ② データ形式の変換
取得した公開鍵は、.data()メソッドを使用してData型に変換します。

#### ③ PEM形式への変換
.PEMプロパティを使用して、公開鍵をPEM形式（Privacy Enhanced Mail）に変換します。
PEM形式は、公開鍵をテキストベースの形式で表現する標準的な方法であり、人が読める形式で公開鍵を表示または共有する際に便利です。

#### ④ 公開鍵の表示
最終的に変換された公開鍵をコンソールに出力します。

## まとめ
EllipticCurveKeyPairライブラリを使用することで、Swiftで簡単に高度なセキュリティ機能を実装することができます。今回は基本的な使い方を紹介しましたが、さらに深く掘り下げて学ぶことで、よりセキュアなアプリケーションを開発することが可能です。