どうも、フリーランスのITエンジニア兼ブロガー兼投資家のKerubitoです。
Swiftで暗号化や復号をさくっとやりたい。
ありますよね。
手段としてはCryptKitとSecurity Frameworkの2択が思い浮かぶかと思いますが、今回はSecurity Frameworkを使ってみました。
・Swiftバージョン:5
・Xcodeバージョン:13.0
・使用デバイス:iPhoneX(15.0)
Security Frameworkとは?
Security Frameworkはアプリが管理するデータを保護してくれるフレームワークです。
機能が多岐にわたるためここでは割愛しますが、サーバとの公開鍵、秘密鍵、証明書のやりとりでよく使われます。
今回はちょっと特殊な使い方にはなりますが、テキストを暗号化し、それを復号することをやってみたいと思います。
CryptKitでもできるのですが、公開鍵暗号方式に対応しているのはSecurity Frameworkだけ。
なので、やってみる価値ありかと。
流れとしては、
①秘密鍵と公開鍵を作る
②①で作った公開鍵で暗号化
③①で作った秘密鍵で復号
といった感じです。
秘密鍵と公開鍵を作る
まずは秘密鍵と公開鍵を作ります。
はじめに秘密鍵を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private static func createPrivateKey() { let tagForPrivateKey = "com.example.keys.privatekey".data(using: .utf8)! let attributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeRSA, kSecAttrKeySizeInBits as String: 2048, kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: true, kSecAttrApplicationTag as String: tagForPrivateKey] ] var error: Unmanaged? guard let generatedPrivateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { return } } |
次に秘密鍵から公開鍵を作ります。
1 2 3 4 5 6 | private static func getPublicKey() -> SecKey?{ guard let privateKey = getPrivateKey() else { return nil } return SecKeyCopyPublicKey(privateKey) } |
公開鍵を作る際にすでに作成済みの秘密鍵を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private static func getPrivateKey() -> SecKey?{ let tagForPrivateKey = "com.example.keys.privatekey".data(using: .utf8)! let getquery: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: tagForPrivateKey, kSecAttrKeyType as String: kSecAttrKeyTypeRSA, kSecReturnRef as String: true ] var item: CFTypeRef? let status = SecItemCopyMatching(getquery as CFDictionary, &item) guard status == errSecSuccess else { return nil } let retrievedPrivateKey = item as! SecKey return retrievedPrivateKey } |
テキストを暗号化する
暗号化の本体です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | internal static func encrypt(str: String) -> String { createPrivateKey() let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA512 guard let publickey = getPublicKey() else { return "" } guard SecKeyIsAlgorithmSupported(publickey, .encrypt, algorithm) else { return "" } guard (str.count < (SecKeyGetBlockSize(publickey)-130)) else { return "" } let plainTextData = str.data(using: .utf8) guard let cipherText = SecKeyCreateEncryptedData( publickey, algorithm, plainTextData! as CFData, nil ) else { return "" } let data = cipherText as Data return data.base64EncodedString() } |
暗号化されたテキストを復号する
次は復号です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | internal static func decrypt(str: String) -> String { let decryptedCipherText = Data(base64Encoded: str, options: []) let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA512 guard let privateKey = getPrivateKey() else { return "" } guard SecKeyIsAlgorithmSupported(privateKey, .decrypt, algorithm) else { return "" } guard let text = SecKeyCreateDecryptedData( privateKey, algorithm, decryptedCipherText! as CFData, nil ) else { return "" } return String(data: text as Data, encoding: .utf8) ?? "" } |
暗号化&復号のメインの処理は以上です。
画面と呼び出し部分
おまけとして、利用する側がこちら。
まずSwifitUIで作られた画面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import SwiftUI struct ContentView: View { @ObservedObject private var model = ContentModel() var body: some View { VStack(spacing: 15) { TextField("", text: $model.str) .textFieldStyle(RoundedBorderTextFieldStyle()) .frame(width: 300) Button(action: { model.onCrypto() }) { if model.isEnrypto { Text("復号") .frame(width: 300, height: 45) } else { Text("暗号化") .frame(width: 300, height: 45) } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } |
適当に作ったmodel。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import Foundation import SwiftUI internal class ContentModel: ObservableObject { @Published public var str: String = "" @Published public var isEnrypto: Bool = false internal init() { } internal func onCrypto() { if isEnrypto { str = Crypto.decrypt(str: str) isEnrypto = false } else { str = Crypto.encrypt(str: str) isEnrypto = true } } } |
実行結果
実行するとこのようになります。
ソースはこちら。
https://github.com/Kerubito0/Crypto.git
以上、楽しい開発ライフを!