#概要
サンドボックス内に保存しているファイルについて、iOS13から追加されたフレームワーク「CryptoKit」を使って「暗号化・復号」する方法を紹介したいと思います。
暗号化・復号というとパディングやらなんやらで面倒な印象を受けますが、
CryptoKitを使うと簡単に相互に変換出来てしまいます。
参考プロジェクトを作ったので、こちらを元に紹介していきます。
https://github.com/o-mo-te/Swift_CryptoKit
※ CryptoKitはiOS13以降でしか動作しないのでご注意を!
#環境
Xcode: 11.3.1
Swift: Swift5
#SymmetricKeyの生成
まず暗号化・復号を行うには鍵が必要です。
鍵の生成は以下のコードで生成できます。
(今回はSymmetricKeySizeを256にしました。)
// 鍵の生成
let encryptionKey: SymmetricKey = SymmetricKey(size: .bits256)
#暗号化を実行
生成したSymmetricKeyで暗号化を行います。
CryptoKitは「AES-GCM」と「ChaChaPoly」の両方のアルゴリズムをサポートしています。
この記事では「AES-GCM」を使用しています。
※「ChaChaPoly」の方が一般的には高速だそうです!
暗号化・復号の動作確認のため、サンドボックス内、ドキュメントディレクトリ配下に「Sample.text」というファイルを配置しています。
// 鍵の生成
let symmetricKey: SymmetricKey = SymmetricKey(size: .bits256)
// ファイルパスを取得
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath = "\(documentDirectory)/Sample.text"
// ファイルパスからデータを取得
guard let encryptData = FileManager.default.contents(atPath: filePath) else {
return
}
// 暗号化の実行
do {
let sealedBox = try AES.GCM.seal(encryptData, using: symmetricKey)
guard let data = sealedBox.combined else {
return
}
// ここで取れる"data"が暗号化された「Sample.text」
} catch _ {
return
}
#復号を実行
続いては復号です。
// 鍵の生成
let symmetricKey: SymmetricKey = SymmetricKey(size: .bits256)
// ファイルパスを取得
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath = "\(documentDirectory)/Sample.text"
// ファイルパスからデータを取得
guard let decryptData = FileManager.default.contents(atPath: filePath) else {
return
}
// 復号を実行
do {
let sealedBox = try AES.GCM.SealedBox(combined: decryptData)
let data = try AES.GCM.open(sealedBox, using: encryptionKey)
// ここで取れる"data"が復号された「Sample.text」
} catch _ {
return
}
#SymmetricKeyを永続化するには?
さて、暗号化・復号はできましたが、SymmetricKeyを永続化しないと、鍵の情報が変わってしまい、
二度と暗号化・復号ができなくなってしまいます。
そこで、SymmetricKeyからBase64EncodedStringを生成し、文字列としてローカルに保存する方法を取りました。
また、Base64EncodedStringからSymmetricKeyのインスタンスを返却するInitializerも用意します。
SymmetricKeyのExtensionを作り関数を追加します。
extension SymmetricKey {
// Create symetricKey with base64Encoded string
init?(base64EncodedString string: String) {
guard let data = Data(base64Encoded: string) else {
return nil
}
self.init(data: data)
}
// CreateBase64String from symetricKey
func serialize() -> String {
return self.withUnsafeBytes { body in
Data(body).base64EncodedString()
}
}
}
以下のように使用します。
let symmetricKey = SymmetricKey(size: .bits256)
// Base64EncodedStringになったSymmetricKey
let stringValue = symmetricKey.serialize()
// Base64EncodedStringからSymmetricKeyに戻す
let key = SymmetricKey(base64EncodedString: stringValue)
#こんな感じのもの作りました
指定したファイルの暗号化・復号を行い、任意のディレクトリに書き出すための共通クラスを作りました。
コード自体は概要欄に書きました以下のリポジトリにあります。
https://github.com/o-mo-te/Swift_CryptoKit
import Foundation
import CryptoKit
extension SymmetricKey {
// Create symetricKey with base64Encoded string
init?(base64EncodedString string: String) {
guard let data = Data(base64Encoded: string) else {
return nil
}
self.init(data: data)
}
// CreateBase64String from symetricKey
func serialize() -> String {
return self.withUnsafeBytes { body in
Data(body).base64EncodedString()
}
}
}
import Foundation
import CryptoKit
enum AppError: Error {
case error
}
enum KeyType {
case encryptionKey
var name: String {
switch self {
case .encryptionKey:
return "encryptionKey"
}
}
}
final class CryptoManager {
// MARK: - Property
static let shared = CryptoManager()
private var symmetricKey: SymmetricKey!
// MARK: - private function
private init() {
defer {
if symmetricKey == nil {
fatalError()
}
}
// If userDefaults has symmetricKey, get value and set to property named "symmetricKey"
if
let value = UserDefaults.standard.string(forKey: KeyType.encryptionKey.name),
let key = SymmetricKey(base64EncodedString: value) {
symmetricKey = key
return
}
// If userDefaults doesn't have symmetricKey, create value and set to userDefaults
let key = SymmetricKey(size: .bits256)
symmetricKey = key
let value = symmetricKey.serialize()
UserDefaults.standard.set(value, forKey: KeyType.encryptionKey.name)
}
// MARK: - Public function
/// 指定したパスに暗号化したデータを書き込む
/// - Parameter path: 書き込み先のパス
/// - Parameter data: 暗号化して書き込むデータ
@discardableResult
func writeEncryptedData(to path: String, data: Data) -> Bool {
do {
let data = try encrypt(data: data)
FileManager.default.createFile(atPath: path, contents: data, attributes: [:])
return true
} catch _ {
return false
}
}
/// 指定したパスに復号したデータを書き込む
/// - Parameter path: 書き込み先のパス
/// - Parameter data: 復号して書き込むデータ
@discardableResult
func writeDecryptedData(to path: String, data: Data) -> Bool {
do {
let data = try decrypt(data: data)
FileManager.default.createFile(atPath: path, contents: data, attributes: [:])
return true
} catch _ {
return false
}
}
// MARK: - Private function
/// 暗号化
/// - Parameter data: 暗号化するデータ
private func encrypt(data: Data) throws -> Data {
do {
let sealedBox = try AES.GCM.seal(data, using: symmetricKey)
guard let data = sealedBox.combined else {
throw AppError.error
}
return data
} catch _ {
throw AppError.error
}
}
/// 復号
/// - Parameter data: 復号するデータ
private func decrypt(data: Data) throws -> Data {
do {
let sealedBox = try AES.GCM.SealedBox(combined: data)
return try AES.GCM.open(sealedBox, using: symmetricKey)
} catch _ {
throw AppError.error
}
}
}
DB作るのが面倒だったため、UserDefaultsで鍵の保持しちゃってます、、、
#最後に
CryptoKitの出始めの頃に実装してみて、情報が少なく苦労したことを思い出しました。
誰かの助けになれば幸いです、、!
#参考
https://developer.apple.com/videos/play/wwdc2019/709/
https://developer.apple.com/documentation/cryptokit