Edited at

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

More than 1 year has passed since last update.


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