12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

株式会社デジタルクエスト エンジニアAdvent Calendar 2017

Day 3

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

Last updated at Posted at 2017-12-02

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

12
6
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?