15
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-11-30

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

去年も
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`)

参考になれば幸いです。

15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8