RSA暗号
swift4
公開鍵暗号方式

PEM形式の公開鍵を使ってRSA暗号する処理をSwiftでやってみた

More than 1 year has passed since last update.

今年もアドベントカレンダーの季節がやって来た!!

去年も
https://qiita.com/advent-calendar/2016/digiq-tech
なんとかかんとか弊社の少ない戦力で書き切ることが出来たので、今年も頑張ります!

と、言うことで先ずはコチラの記事から!

PEM形式の公開鍵を使ってRSA暗号する処理をSwiftでやってみた

PEM形式の公開鍵とは

こんなやつ

pubkey.pem

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqFxh+KlEy3R6gaKVpaPv
Azy97HvNLPYY7ePMdUwxrMkjVoPZXSDselkvzcz8vlvBKrWgVcroaBWwRGtdtxkc
YOCWlTSex3Pb/geu6wceAMNsM2YAEeBcEL6CvmThq/u/C28szSNqNUZ8paSXQA8Q
A9OLjThuZeNIDznNu51/t9/IqByyVNde3DHvRnoFv3cnJ9NgZsICGVA8KxWpUJ8t
O4P5On3BQVL+ot5W+8JaSpH8yKaZiypIGNsTJfKa2npqc5R+5twN08qr1OhhYmey
bMiYvplXpTMajPQFqTVvALcID4pozKWkBHfrN8FslTHS4hQnM3CFND4Ju8qFuZFB
+QIDAQAB
-----END PUBLIC KEY-----

SSHとかSSLでよく使われているヤツですね。

よく見かける形式のヤツ!今回コレを使ってSwiftでRSA暗号化をしてみようと思います。

SwiftはRSA暗号の関数はあるけどPEMが使えない

Swiftには SecKeyEncrypt と言う便利な関数が用意されているのだけど
このよく見るPEM形式の鍵文字列を使っての暗号処理は

なんと!・・・出来ません。

SecKeyEncrypt には SecKey と言う型で鍵を渡す必要があります。

なので、PEM形式の鍵も SecKey 型に変換しないとならないのですが・・・

この方法が意外と分からなかったw

der形式の鍵ならSecCertificateCreateWithDataに鍵ファイルのパスを渡すことで SecKey に変換出来るのだけど
今回はなんとかpem形式のまま出来ないかやってみた。

結論:SecKeyCreateWithDataにData型の鍵を渡すと出来る!

ぶっちゃけると、SwiftRSAと言うライブラリで実現されているので

其れを使う!

のが早いと思いますw

・・・けど、一応SecKeyCreateWithDataにPEM形式の鍵をData型に変換して渡す部分だけを読み解いて
解釈して、関数化してみました。

    /// base64pem形式の鍵文字列をiOSで利用するSecKey形式に変換する
    ///
    /// - Parameters:
    ///   - argBase64Key: base64pem形式の公開鍵 あるいは秘密鍵
    ///   - keyType: kSecAttrKeyClassPublic|kSecAttrKeyClassPrivate
    /// - Returns: SecKey形式の鍵データ
    /// - Throws: RSAError
    static func convertSecKeyFromBase64Key(_ argBase64Key: String, _ keyType: CFString) throws -> SecKey {

        var keyData = Data(base64Encoded: argBase64Key, options: [.ignoreUnknownCharacters])!
        let keyClass = keyType

        // iOS10以上
        if #available(iOS 10.0, *), #available(watchOS 3.0, *), #available(tvOS 10.0, *) {
            let sizeInBits = keyData.count * 8
            let keyDict: [CFString: Any] = [
                kSecAttrKeyType: kSecAttrKeyTypeRSA,
                kSecAttrKeyClass: keyClass,
                kSecAttrKeySizeInBits: NSNumber(value: sizeInBits),
                kSecReturnPersistentRef: true
            ]
            var error: Unmanaged<CFError>?
            guard let key = SecKeyCreateWithData(keyData as CFData, keyDict as CFDictionary, &error) else {
                throw RSAError.keyCreateFailed(status: 0)
            }
            return key
            // iOS9以下
        } else {
            let keychainIdentifier = "SwiftRSA.lib"
1            let tagData = keychainIdentifier.data(using: .utf8, allowLossyConversion: false)!
            let persistKey = UnsafeMutablePointer<AnyObject?>(mutating: nil)
            let keyAddDict: [CFString: Any] = [
                kSecClass: kSecClassKey,
                kSecAttrApplicationTag: tagData,
                kSecAttrKeyType: kSecAttrKeyTypeRSA,
                kSecValueData: keyData,
                kSecAttrKeyClass: keyClass,
                kSecReturnPersistentRef: true,
                kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked
            ]
            let addStatus = SecItemAdd(keyAddDict as CFDictionary, persistKey)
            guard addStatus == errSecSuccess || addStatus == errSecDuplicateItem else {
                throw RSAError.keyAddFailed(status: 1)
            }
            let keyCopyDict: [CFString: Any] = [
                kSecClass: kSecClassKey,
                kSecAttrApplicationTag: tagData,
                kSecAttrKeyType: kSecAttrKeyTypeRSA,
                kSecAttrKeyClass: keyClass,
                kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
                kSecReturnRef: true,
                ]
            var keyRef: AnyObject? = nil
            let copyStatus = SecItemCopyMatching(keyCopyDict as CFDictionary, &keyRef)
            if copyStatus != errSecSuccess {
                // エラー
            }
            guard let unwrappedKeyRef = keyRef else {
                throw RSAError.keyCopyFailed(status: 1)
            }
            return unwrappedKeyRef as! SecKey
        }

    }

こんな感じで、Dataを SecKey に登録さえ出来れば、要は元の形式はなんでも良いようです。
SecCertificateCreateWithDataが特別簡単にファイルパスで渡せる使用になっている模様

ここまで回りくどい処理をしておいてなんですが、まぁぶっちゃけ・・・
PEM形式の証明書をDER形式に変換すりゃいいやんと言うパターンもあるかと思いますw

そんなあなたにはコチラ!

openssl x509 -in server.crt -inform PEM -out cert.der -outform DER

pemをderにするのもコマンド一発なので簡単だね!

本題:RSAは SecKeyEncrypt でスゴく普通に簡単に出来る

鍵さえ SecKey 型になってしまえばもう簡単!
SecKeyEncrypt を使えば暗号に出来ちゃいます。

    /// 公開鍵で暗号化を行う
    ///
    /// - Parameters:
    ///   - argBody: 対象文字列
    ///   - argBase64PublicKey: 公開鍵文字列(base64)
    /// - Returns: 暗号データ
    static func encrypt(_ argBody: String, _ argBase64PublicKey: String) -> Data {

        do {
            let pubKey = try self.convertSecKeyFromBase64Key(argBase64PublicKey, kSecAttrKeyClassPublic)
            let plainBuffer = [UInt8](argBody.utf8)
            var cipherBufferSize = Int(SecKeyGetBlockSize(pubKey))
            var cipherBuffer = [UInt8](repeating:0, count:Int(cipherBufferSize))
            // Encrypto  should less than key length
            let status = SecKeyEncrypt(pubKey, SecPadding.PKCS1, plainBuffer, plainBuffer.count, &cipherBuffer, &cipherBufferSize)
            if (status != errSecSuccess) {
                print("Failed Encryption")
            }
            return Data(bytes: cipherBuffer)
        }
        catch {
            // エラー処理
        }
        return Data()

    }

後はバイナリとなった暗号化データをBase64したりHex文字にしたりして、文字列でやりとりするもよし
バイナリのままPOSTしちゃったりしても良いと思います(´v`)

参考になれば幸いです。