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もちゃんとリンクする

ss01.png

これで無事に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

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.