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

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

去年も
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` 型に変換しないとならないのですが・・・  
この方法が意外と分からなかったｗ


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


## 結論：SecKeyCreateWithDataにData型の鍵を渡すと出来る！

ぶっちゃけると、[SwiftRSA](https://github.com/TakeScoop/SwiftyRSA)と言うライブラリで実現されているので

### 其れを使う！

のが早いと思いますｗ

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

```Swift
    /// 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形式に変換すりゃいいやん**と言うパターンもあるかと思いますｗ

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

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

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


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

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

```Swift
    /// 公開鍵で暗号化を行う
    ///
    /// - 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｀)

参考になれば幸いです。