iOS
Swift
RSA

[iOS] SwCryptを用いたRSA式暗号化

背景

iOSアプリにおいて、何らかのデータをローカル(端末)側に保存したいという時があります。例えば端末がオフラインの状態でサーバと通信が行えない時は一時的にローカルに情報を保存しておき、通信が可能になったタイミングでサーバ側と同期を図るといった場合です。そのような場合、ローカルに保存する情報を暗号化し、使用時に復号するといった処理を実装しておくと不正な改竄を防ぐのに効果的です。

またiOSはAndroidと比べて堅牢とはいえ、特殊な手法を使えばソースコード内の文字列を覗き見ることは不可能ではありません。ソースコードの中の文字列(APIのkey等)を見られたくないという場合などにも、暗号化をしておくことでクラッキングのリスクを軽減することが可能です。

SwCryptとは

SwCryptはRSAやAES方式の暗号をサポートするSwiftライブラリです。このライブラリを使用することで、鍵の作成や暗号化/復号といった処理を任せることができます。詳しくは下記の公式リポジトリを参照してください。

https://github.com/soyersoyer/SwCrypt

使用例

SwCrypt.swiftのインポート

READMEにもあるように、まずライブラリ内のSwCrypt.swiftを自身のプロジェクトに取り込みます。

Wrapperの作成

SwCryptが用意しているencrypt, decryptメソッドを直接使用しても良いのですが、ここではWrapperクラスを用意し、Stringの暗号化、Dataの復号、private keyの暗号化/復号といった処理用のメソッドを実装しておくことにします。

※"YourPrivateKeyEncryptionPasscode"の箇所を自身のパスコードに修正してください

encrypt/decrypt用のWrapperクラス
class RSAWrapper {
    func encryptStr(dataStr: String) -> Data? {
        if let data = dataStr.data(using: String.Encoding.utf8), let publicKey = RSAKeyStore.sharedInstance.getPublicKey() {
            do {
                // Encrypt
                let encryptedData = try CC.RSA.encrypt(data,
                                                       derKey: publicKey,
                                                       tag: "TAG".data(using: String.Encoding.utf8)!,
                                                       padding: .oaep,
                                                       digest: .sha1)

                return encryptedData
            } catch {
                print("Failed to encrypt.")
                return nil
            }
        } else {
            return nil
        }
    }

    func decryptData(data: Data) -> String? {
        if let privateKey = RSAKeyStore.sharedInstance.getPrivateKey() {
            do {
                // Decrypt
                let (decryptedData, _) = try CC.RSA.decrypt(data,
                                                            derKey: privateKey,
                                                            tag: "TAG".data(using: String.Encoding.utf8)!,
                                                            padding: .oaep,
                                                            digest: .sha1)

                if let decryptedStr = String(data: decryptedData, encoding: String.Encoding.utf8) {
                    return decryptedStr
                } else {
                    return nil
                }
            } catch {
                print("Failed to decrypt.")
                return nil
            }
        } else {
            return nil
        }
    }

    func encryptPrivateKey(privateKeyDER: Data) -> Data? {
        do {
            // Convert private key to pem
            let privateKeyPEM = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKeyDER)

            // Encrypt private key
            let privateKeyEncrypted = try SwKeyConvert.PrivateKey.encryptPEM(privateKeyPEM, passphrase: "YourPrivateKeyEncryptionPasscode", mode: .aes256CBC)

            return  privateKeyEncrypted.data(using: String.Encoding.utf8)
        } catch {
            print("Failed to encrypt private key.")
            return nil
        }
    }

    func decryptPrivateKey(privateKeyEncrypted: Data) -> Data? {
        if let privateKeyEncryptedStr = String(data: privateKeyEncrypted, encoding: String.Encoding.utf8) {
            do {
                // Decrypt private key
                let privateKeyDecryptedPEM = try SwKeyConvert.PrivateKey.decryptPEM(privateKeyEncryptedStr, passphrase: "YourPrivateKeyEncryptionPasscode")

                // Convert private key to DER and return
                return try SwKeyConvert.PrivateKey.pemToPKCS1DER(privateKeyDecryptedPEM)
            } catch {
                print("Failed to decrypt private key.")
                return nil
            }
        } else {
            return nil
        }
    }
}

公開鍵・秘密鍵管理用クラスの作成

また、公開鍵と秘密鍵を生成してローカルに保存し、必要に応じて取り出すためのクラスも作成しておきます。

final class RSAKeyStore {
    static let sharedInstance = RSAKeyStore()

    private var privateKey: Data?
    private var publicKey: Data?

    private init() {
        if (privateKey == nil || publicKey == nil) {
            // Retrieve key pair and set to property.
            if var rsaKeyPairDic: Dictionary<String, Data> = UserDefaults.standard.object(forKey: myRSAKeyPair) as? Dictionary<String, Data> {
                self.publicKey = rsaKeyPairDic[myPublicKey]

                if let privateKeyEncrypted = rsaKeyPairDic[myPrivateKey] {
                    if let privateKeyDecrypted = RSAWrapper().decryptPrivateKey(privateKeyEncrypted: privateKeyEncrypted) {
                        self.privateKey = privateKeyDecrypted
                    }
                }
            } else { // If key pair is not found, generate and save in local.
                do {
                    let (privateKeyNew, publicKeyNew) = try CC.RSA.generateKeyPair(2048)

                    self.privateKey = privateKeyNew
                    self.publicKey = publicKeyNew

                    if let privateKeyEncrypted = RSAWrapper().encryptPrivateKey(privateKeyDER: privateKeyNew) {
                        var rsaKeyPairDic = Dictionary<String, Data>()

                        rsaKeyPairDic[myPrivateKey] = privateKeyEncrypted
                        rsaKeyPairDic[myPublicKey] = publicKeyNew

                        UserDefaults.standard.set(rsaKeyPairDic, forKey: myRSAKeyPair)
                        UserDefaults.standard.synchronize()
                    }
                } catch {
                    print("Error for generating key pair.")
                }
            }
        }
    }

    func getPrivateKey() -> Data? {
        return self.privateKey
    }

    func getPublicKey() -> Data? {
        return self.publicKey
    }
}

使用方法

下記のようにWrapperクラスのメソッドを用いて暗号化/復号を行うことができます。

let encryptedData = RSAWrapper().encryptStr(dataStr: "PLAIN TEXT")
let decryptedText = RSAWrapper().decryptData(data: encryptedData)