SwiftでCCCryptでAES(256Bit)してみた話


# 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](https://qiita.com/skatata/items/1facd024d239b9545031)

SwiftAES-Bridging-Header.h

```Swift
#ifndef SwiftAES_Bridging_Header_h
#define SwiftAES_Bridging_Header_h


#endif /* SwiftAES_Bridging_Header_h */

#import <CommonCrypto/CommonCrypto.h>
```

勿論Security.frameworkもちゃんとリンクする

![ss01.png](https://qiita-image-store.s3.amazonaws.com/0/151630/2a8d96e3-232a-a13d-5483-933bfbee0cc7.png)

**これで無事にSwift上から馴染みのCommonCryptのCCCryptをSwiftから実行出来るようになる**

## CCCryptをSwiftから使ったAESの実装例

こんな感じの実装でSwiftでAESを実現出来る

```Swift
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`](https://ja.wikipedia.org/wiki/暗号論的擬似乱数生成器) なんだとか・・・


### 使う側の実装はこんな感じ

```Swift
// 暗号
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