【Swift】CryptoKitフレームワークの使い方!データの暗号化・複合化
この記事からわかること
- SwiftのCryptoKitフレームワークの使い方
- データを暗号化・複合化する方法
- セキュリティ対策
- AES.GCM.SealedBox型とは?
- SymmetricKeyを永続化する方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
暗号化や複合化、ハッシュ化の種類や仕組み自体についてはCryptoSwift
の記事でまとめてありますのでそちらを参考にしてください。CryptoSwift
はCryptoKit
と同じような機能を提供するライブラリです。
CryptoKitフレームワークとは?
CryptoKit
はAppleが提供する暗号化および暗号関連の操作を実行するためのフレームワークです。
暗号化する
CryptoKit
で「共通鍵暗号方式」のAES-GCM方式で暗号化し、Base64エンコードされた文字列として返す処理を実装してみます。
import UIKit
import CryptoKit
class CryptoManager {
// 共通の暗号化キー
private let encryptionKey = SymmetricKey(size: .bits256)
func encryption(word: String) -> String {
do {
// UTF-8エンコーディングでバイト配列に変換
let data = Data(word.utf8)
// AES-GCM暗号化アルゴリズムを使用して暗号化
let sealedBox: AES.GCM.SealedBox = try AES.GCM.seal(data, using: encryptionKey)
// 暗号化されたデータをBase64エンコード
let base64EncodedData = sealedBox.combined?.base64EncodedData() ?? Data()
// Base64エンコードされたデータをUTF-8文字列に変換
let base64Str = String(data: base64EncodedData, encoding: .utf8) ?? "変換失敗"
return base64Str
} catch {
return "変換失敗"
}
}
}
「鍵(SymmetricKey)」の生成
暗号化・復号化を行うためには必要な鍵はSymmetricKey
型で用意します。SymmetricKey
は共通鍵暗号方式で使用される鍵を表すSwiftの構造体です。
引数size
に指定するのは鍵のビットサイズです。鍵のサイズは大きいほどセキュリティが高まりますが、処理が重くなるので注意が必要です。
// 共通の暗号化キー
private let encryptionKey = SymmetricKey(size: .bits256)
ここで生成する鍵は生成するごとに別の値になってしまうのでアプリ内で使用する場合は生成した鍵をローカルに保存して永続化しておく必要があります。永続化しておかないと暗号化したものを2度と複合化できなくなってしまうので注意してください。
暗号化:AES.GCM.sealメソッド
実際に暗号化を行なっているのはAES.GCM.seal
メソッドです。seal
メソッドは、与えられたデータと暗号化キーを使用してデータを暗号化し、結果としてAES.GCM.SealedBox
構造体を返します。
// AES-GCM暗号化アルゴリズムを使用して暗号化
let sealedBox: AES.GCM.SealedBox = try AES.GCM.seal(data, using: encryptionKey)
AES.GCM.SealedBox
型は暗号化されたデータとそれに関連する認証タグを含むデータオブジェクトです。combined
プロパティから暗号化されたデータとその認証タグを含む単一のバイト配列を取得できます。
暗号化された値を文字列に変換する
バイト配列を取得できたら扱いやすいように文字列に変換していきます。そのためにはData型に変換した後、Base64でエンコードして、String型まで変換していきます。
// 暗号化されたデータをBase64エンコード
let base64EncodedData = sealedBox.combined?.base64EncodedData() ?? Data()
// Base64エンコードされたData型をUTF-8文字列に変換
let base64Str = String(data: base64Data, encoding: String.Encoding.utf8) ?? "変換失敗"
公式リファレンス:base64EncodedDataメソッド
Base64とは?
Base64とはバイナリデータをテキスト形式に変換するためのエンコーディング手法の1つです。A-Z
、a-z
、0-9
、+/
の64種類で文字を表します。
(※正確には=
がパディング(余った部分を詰める用)として使用されるので65種類)
例えばHello World
を先ほどの共通鍵と初期化ベクトルで暗号化してBase64の文字列に変換すると以下の様になります。
G3+OszvIum5CR14z+6vv80mpLvx5CaUGOBAtmCW2FH5YW7zrmdDtfa8=
複合化する
CryptoKit
で「共通鍵暗号方式」のAES-GCM方式で暗号化されたデータを複合化するには暗号化で実装した流れとは逆のことを行っていきます。また使用する鍵とは同じものを使用してください。
func decryption(str: String) -> String {
do {
// 文字列をUTF-8エンコーディングしてバイトデータに変換
guard let byteData = str.data(using: String.Encoding.utf8) else { return "変換失敗" }
// Base64データをデコードして元のデータに戻す
guard let data = Data(base64Encoded: byteData) else { return "変換失敗" }
// AES.GCM.SealedBox型を取得する
let sealedBox = try AES.GCM.SealedBox(combined: data)
// 暗号化されたデータを複合化
let decryptedData = try AES.GCM.open(sealedBox, using: encryptionKey)
// 複合化されたデータをUTF-8エンコーディングで文字列に変換
let decryptedText = String(data: decryptedData, encoding: .utf8) ?? "変換失敗"
return decryptedText
} catch {
return "変換失敗"
}
}
鍵を永続化する
SymmetricKey
は永続化しておかないと暗号化・複合化をできなくなってしまうので永続化された鍵がない場合のみ鍵を新規で生成するような実装にする必要があります。本当はKeyChainなどに保存するのが正しいですがUserDefaults
で簡単に実装する場合は以下のようになります。
private let keyUserDefaultsKey = "encryptionKey"
private lazy var encryptionKey: SymmetricKey = {
if let savedKeyData = UserDefaults.standard.data(forKey: keyUserDefaultsKey),
let savedKey = try? SymmetricKey(data: savedKeyData) {
return savedKey
} else {
let newKey = SymmetricKey(size: .bits256)
let keyData = newKey.withUnsafeBytes { Data($0) }
UserDefaults.standard.set(keyData, forKey: keyUserDefaultsKey)
return newKey
}
}()
実装サンプル
SwiftUIで使用する場合の全体コードを載せておきます。
import SwiftUI
import CryptoKit
class CryptoManager {
private let keyUserDefaultsKey = "encryptionKey" // ユーザーデフォルトに鍵を保存するためのキー
private lazy var encryptionKey: SymmetricKey = {
// ユーザーデフォルトから保存された鍵を取得する
if let savedKeyData = UserDefaults.standard.data(forKey: keyUserDefaultsKey),
let savedKey = try? SymmetricKey(data: savedKeyData) {
return savedKey
} else {
// 初回のみ新しい鍵を生成してユーザーデフォルトに保存する
let newKey = SymmetricKey(size: .bits256)
let keyData = newKey.withUnsafeBytes { Data($0) }
UserDefaults.standard.set(keyData, forKey: keyUserDefaultsKey)
return newKey
}
}()
func encryption(word: String) -> String {
do {
let data = Data(word.utf8)
let sealedBox: AES.GCM.SealedBox = try AES.GCM.seal(data, using: encryptionKey)
let base64EncodedData = sealedBox.combined?.base64EncodedData() ?? Data()
let base64Str = String(data: base64EncodedData, encoding: .utf8) ?? "変換失敗"
return base64Str
} catch {
return "変換失敗"
}
}
func decryption(str: String) -> String {
do {
guard let byteData = str.data(using: String.Encoding.utf8) else { return "変換失敗" }
guard let data = Data(base64Encoded: byteData) else { return "変換失敗" }
let sealedBox = try AES.GCM.SealedBox(combined: data)
let decryptedData = try AES.GCM.open(sealedBox, using: encryptionKey)
let decryptedText = String(data: decryptedData, encoding: .utf8) ?? "変換失敗"
return decryptedText
} catch {
return "変換失敗"
}
}
}
struct ContentView: View {
private var cryptoManager = CryptoManager()
var body: some View {
VStack {
Button(action: {
let text = "Hello, World!"
let encryptedData = cryptoManager.encryption(word: text)
print("Encrypted: \(encryptedData)")
UserDefaults.standard.set(encryptedData, forKey: "encryptedData")
}) {
Text("暗号化して保存する")
}
Button(action: {
if let encryptedData = UserDefaults.standard.string(forKey: "encryptedData") {
let decryptedText = cryptoManager.decryption(str: encryptedData)
print("Decrypted: \(decryptedText)")
} else {
print("保存された暗号化データが見つかりません")
}
}) {
Text("取得して複合化")
}
}
}
}
Androidで暗号化・複合化を実装する
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。