SwiftでAES(256Bit)してみた話
Objective-Cで馴染みだったCCCryptをやっぱり使う
OpenSSLを使う方法もあるのだけれど、今回はObjCでおなじみだったCCCryptを使ってSwiftでAESを実現する話
CCCryptをSwiftで使うにはObjCのCommonCryptライブラリをImportする必要がある
Security.frameworkのCommonCryptはObjC用のAPIしか用意されていないので
BridgingHeaderを使ってSwift側で使えるようにObjCのライブラリを読み込んで使う必要がある
ちょっと古いですが、こちらの記事が参考になりました!
https://qiita.com/skatata/items/1facd024d239b9545031
SwiftAES-Bridging-Header.h
#ifndef SwiftAES_Bridging_Header_h
#define SwiftAES_Bridging_Header_h
#endif /* SwiftAES_Bridging_Header_h */
#import <CommonCrypto/CommonCrypto.h>
勿論Security.frameworkもちゃんとリンクする
これで無事にSwift上から馴染みのCommonCryptのCCCryptをSwiftから実行出来るようになる
CCCryptをSwiftから使ったAESの実装例
こんな感じの実装でSwiftでAESを実現出来る
import Foundation
public class Chiper {
enum AESError : Error {
case encryptFailed(String, Any)
case decryptFailed(String, Any)
case otherFailed(String, Any)
}
public class AES {
/// 暗号
public static func encrypt(plainString: String, sharedKey: String, iv: String) throws -> Data {
guard let initialzeVector = (iv.data(using: .utf8)) else {
throw Chiper.AESError.otherFailed("Encrypt iv failed", iv)
}
guard let keyData = sharedKey.data(using: .utf8) else {
throw Chiper.AESError.otherFailed("Encrypt sharedkey failed", sharedKey)
}
guard let data = plainString.data(using: .utf8) else {
throw Chiper.AESError.otherFailed("Encrypt plainString failed", plainString)
}
// 暗号化後のデータのサイズを計算
let cryptLength = size_t(Int(ceil(Double(data.count / kCCBlockSizeAES128)) + 1.0) * kCCBlockSizeAES128)
var cryptData = Data(count:cryptLength)
var numBytesEncrypted: size_t = 0
// 暗号化
let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
initialzeVector.withUnsafeBytes {ivBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
keyBytes, keyData.count,
ivBytes,
dataBytes, data.count,
cryptBytes, cryptLength,
&numBytesEncrypted)
}
}
}
}
if UInt32(cryptStatus) != UInt32(kCCSuccess) {
throw Chiper.AESError.encryptFailed("Encrypt Failed", kCCSuccess)
}
return cryptData
}
/// 復号
public static func decrypt(encryptedData: Data, sharedKey: String, iv: String) throws -> String {
guard let initialzeVector = (iv.data(using: .utf8)) else {
throw Chiper.AESError.otherFailed("Encrypt iv failed", iv)
}
guard let keyData = sharedKey.data(using: .utf8) else {
throw Chiper.AESError.otherFailed("Encrypt sharedKey failed", sharedKey)
}
let clearLength = size_t(encryptedData.count + kCCBlockSizeAES128)
var clearData = Data(count:clearLength)
var numBytesEncrypted :size_t = 0
// 復号
let cryptStatus = clearData.withUnsafeMutableBytes {clearBytes in
initialzeVector.withUnsafeBytes {ivBytes in
encryptedData.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
keyBytes, keyData.count,
ivBytes,
dataBytes, encryptedData.count,
clearBytes, clearLength,
&numBytesEncrypted)
}
}
}
}
if UInt32(cryptStatus) != UInt32(kCCSuccess) {
throw Chiper.AESError.decryptFailed("Decrypt Failed", kCCSuccess)
}
// パディングされていた文字数分のデータを捨てて文字列変換を行う
guard let decryptedStr = String(data: clearData.prefix(numBytesEncrypted), encoding: .utf8) else {
throw Chiper.AESError.decryptFailed("PKSC Unpad Failed", clearData)
}
return decryptedStr
}
/// ランダムIV生成
public static func generateRandamIV() throws -> String {
// CSPRNGから乱数取得
var randData = Data(count: 8)
let result = randData.withUnsafeMutableBytes {mutableBytes in
SecRandomCopyBytes(kSecRandomDefault, 16, mutableBytes)
}
if result != errSecSuccess {
// SecRandomCopyBytesに失敗(本来はあり得ない)
throw Chiper.AESError.otherFailed("SecRandomCopyBytes Failed GenerateRandam IV", result)
}
// 16進数文字列化
let ivStr = Chiper.convetHexString(frombinary: randData)
return ivStr
}
}
}
Swift4になって、 clearData.withUnsafeMutableBytes
とかやってByteを順次処理していかないと
コンパイルで怒られるようになってしまった事に注意!
あと、付属ですがランダムなIVを生成する処理を追加して置きました!
iOSでは SecRandomCopyBytes
と言うのがセキュアな乱数を返す擬似乱数生成機
いわゆる CSPRNG
なんだとか・・・
使う側の実装はこんな感じ
// 暗号
do {
let ivStr = try Chiper.AES.generateRandamIV()
let encryptedData = try Chiper.AES.encrypt(plainString: "平文", sharedKey: "共通鍵", iv: ivStr)
}
catch Chiper.AESError.encryptFailed(let key, let anyVal) {
// エラーハンドリング
return
}
catch Chiper.AESError.otherFailed(let key, let anyVal) {
// エラーハンドリング
return
}
catch {
// その他エラーハンドリング
return
}
// 復号
do {
let decryptedString = try Chiper.AES.decrypt(encryptedData: encryptedData, sharedKey: "共通鍵", iv: ivStr)
}
catch Chiper.AESError.decryptFailed(let key, let anyVal) {
// エラーハンドリング
return
}
catch Chiper.AESError.otherFailed(let key, let anyVal) {
// エラーハンドリング
return
}
catch {
// その他エラーハンドリング
return
}
なるべくシンプルにまとめたつもり
category等にしても良いかも知れません(´v`)
何かのお役に立ったら幸いですm(__)m