今年もアドベントカレンダーの季節がやって来た!!
去年も
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`)
参考になれば幸いです。