Edited at

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`)

参考になれば幸いです。