【Swift】CryptoSwiftの使い方!データの暗号化・複合化とハッシュ値生成
この記事からわかること
- SwiftのCryptoSwiftライブラリの使い方
- データを暗号化する方法
- ハッシュ値や署名の実装方法
- セキュリティ対策
- 初期化ベクトル(Initialization Vector)とは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
暗号化と複合化について
データをやり取りする際に悪意を持った第三者にデータを盗聴された際にデータがそのままの形式(平文という)だと中身を見られた場合に情報が全て筒抜けになってしまいます。見られることを意図していないデータであれば「暗号化」を行うことでデータの解読を困難にすることがセキュリティ対策の基本となります。
「暗号化」とは元のデータをそのままでは理解不能な形式に変換するセキュリティ手法の1つです。特定の暗号化アルゴリズムに従い、正規の手順を踏まないと複合化(暗号文も元の形式に戻すこと)できないようにすることで、万が一盗聴されても情報の流出を避けることができます。
暗号化・複合化するためには鍵が必要になります。鍵の方式にも「公開鍵暗号方式」や「共通鍵暗号方式」などの種類があり、両者の違いは名前の通り、ざっくりいうと同じ鍵を使用するかしないかです。それに伴い「公開鍵暗号方式」は安全性が高く、「共通鍵暗号方式」は処理速度が速いのが特徴です。さらにその中に暗号化アルゴリズムで種類が分かれています。
暗号化アルゴリズムの種類
「公開鍵暗号方式」と「共通鍵暗号方式」では使用される暗号化アルゴリズムが異なります。よく使われているものだけ紹介しておきます。
RSA:公開鍵暗号方式
メジャーなアルゴリズム。暗号化と同時に電子署名を行えるのが特徴で素数による素因数分解を使用した数学的アルゴリズムを使用することで高い安全性がある。
AES:共通鍵暗号方式
「Advanced Encryption Standard」の略称。データの入替や排他的論理和の計算、行列変換などの処理を組み合わせてデータを128・192・256ビットの長さに暗号化。
ハッシュ化について
暗号化と似たようなものに「ハッシュ化」があります。これは平文を異なる形式に変換するという点では同じですが、暗号化と異なり複合化(元のデータに戻すこと)できない不可逆の性質となっています。
元のデータをハッシュ化して生成される値は元のデータが同じなら同じハッシュ値になるという特徴があります。この特徴を利用してデータの整合性を担保するための活用されます。
例えばパスワード情報をハッシュ化して保存しておくことで、万が一保存されている場所からデータが流出しても、得られるのは意味のない文字の羅列のみでパスワード自体の流出を防ぐことができます。同じパスワードからは同じハッシュ値が生成されるのでパスワードの確認に影響もありません。
ハッシュ規格
元のデータをハッシュ化するためにはハッシュ関数と呼ばれるアルゴリズムを使用します。規格がいくつかあるのでまとめておきます。
MD5
メジャーなハッシュ関数。128ビットの長さのハッシュ値を高速に出力できる
SHA-1
160ビットのハッシュ値を生成する。現在は利用が推奨されていない。
SHA-2
SHA-1を改良した規格。ハッシュ値の長さに応じて、SHA-224、SHA-256、SHA-384、SHA-512が定義。
CRC32
データに対して巡回冗長検査を行い固定長(32ビット)のハッシュ値を生成する規格。
CryptoSwiftライブラリ
CryptoSwift
はSwiftでデータの暗号化やハッシュ関連の機能を提供するライブラリです。「Crypto(クリプト)」は日本語で「暗号」という意味になります。
導入にはSwift Package Manager、Cocoa Pods、Carthageに対応しています。例えばCocoa Podsなら以下をPodFileに記述します。
pod 'CryptoSwift', '~> 1.8.0'
導入が完了したらimport
して使用します。
import CryptoSwift
ライブラリを導入せずともAppleか提供されているCryptoKitフレームワークを使用することでデータの暗号化・複合化を実装することも可能です。
続いてハッシュ値の生成方法と暗号化・複合化の方法をまとめていきます。
ハッシュ値を生成する
CryptoSwift
では簡単にそれぞれの規格のハッシュ値を取得することが可能です。先ほどの紹介した規格のハッシュ値を取得するには以下のようになります。
print("Hello World".md5()) // b10a8db164e0754105b7a99be72e3fe5
print("Hello World".sha1()) // 0a4d55a8d778e5022fab701977c5d840bbc486d0
print("Hello World".sha512()) // 2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b
print("Hello World".crc32()) // 4a17b156
もちろん同じデータ(文字列)からは同じハッシュ値を取得することが可能です。
暗号化する
CryptoSwift
で「共通鍵暗号方式」のAESで暗号化するにAESインスタンスを生成してエンコード、キャストすることで暗号化された文字列を取得することができます。
import UIKit
import CryptoSwift
class CryptoManager {
private let key = "1234567890123456"
private let iv = "1234567890123456"
func encryption(word: String) -> String {
do {
// AESキーと初期ベクトルを使用してAESインスタンスを生成
let aes = try AES(key: key, iv: iv)
// 渡された文字列をUTF-8のバイト配列に変換し、AESで暗号化
let encrypted = try aes.encrypt(Array(word.utf8))
// 暗号化されたデータをData型に変換
let data = Data(encrypted)
// Data型をBase64エンコード
let base64Data: Data = data.base64EncodedData() as Data
// Base64エンコードされたData型をUTF-8文字列に変換
let base64Str = String(data: base64Data, encoding: String.Encoding.utf8) ?? "変換失敗"
return base64Str
} catch {
return "変換失敗"
}
}
}
「鍵」と「初期化ベクトル」
AESインスタンスの生成
AES
インスタンスを生成するためには「鍵」と「初期化ベクトル」を用意して引数に渡す必要があります。
ここで利用する「鍵」は暗号化・複合化時に実際に利用される「共通鍵」です。共通の鍵を利用するため、管理方法には注意が必要です。
「初期化ベクトル(Initialization Vector)」とは同じ文字列から同じ暗号化文が生まれないようにするためのビット列のことを指します。ハッシュ生成の際にも同じような目的でソルト(文字の前につける文字列)が利用されますがソルトは不可逆なのに対し、IVは複合化できるのが大きな違いです。
// AESキーと初期ベクトルを使用してAESインスタンスを生成
let aes = try AES(key: key, iv: iv)
なのでここで指定する「鍵」と「初期化ベクトル」の文字列はなんでも良いかと思います。
暗号化:encryptメソッド
public func encrypt(_ bytes: Array<UInt8>) throws -> Array<UInt8> {
try self.encrypt(bytes.slice)
}
実際に暗号化するにはencrypt
メソッドを使用します。引数bytes
にはArray<UInt8>
型(つまりバイト配列)で対象のデータを渡す必要があります。そのためにString.UTF8View
型をまず取得し配列へ変換します。
// 渡された文字列をUTF-8のバイト配列に変換し、AESで暗号化
let encrypted = try aes.encrypt(Array("Hello World".utf8))
String.UTF8View
型は文字列をUTF-8エンコーディングで表現した場合のバイトのコレクションを取得できます。これをArray
型にキャストすることで引数に渡せる形に成形しています。
print(Array("Hello".utf8)) // [72, 101, 108, 108, 111]
またencrypt
メソッドの返り値は暗号化されたArray<UInt8>
型(つまりバイト配列)になります。ここで暗号化までのステップは完了です。
暗号化された値を文字列に変換する
暗号化された値は今のところバイト配列なので扱いやすいように文字列に変換していきます。そのためにはData型に変換した後、Base64でエンコードして、String型まで変換していきます。
// 暗号化されたデータをData型に変換
let data = Data(encrypted)
// Data型をBase64エンコード
let base64Data: Data = data.base64EncodedData() as Data
// Base64エンコードされたData型をUTF-8文字列に変換
let base64Str = String(data: base64Data, encoding: String.Encoding.utf8) ?? "変換失敗"
公式リファレンス:base64EncodedDataメソッド
Base64とは?
Base64とはバイナリデータをテキスト形式に変換するためのエンコーディング手法の1つです。A-Z
、a-z
、0-9
、+/
の64種類で文字を表します。
(※正確には=
がパディング(余った部分を詰める用)として使用されるので65種類)
例えばHello World
を先ほどの共通鍵と初期化ベクトルで暗号化してBase64の文字列に変換すると以下の様になります。
ZyODokM33Io1ZKIA8h7owA==
複合化する
CryptoSwift
で「共通鍵暗号方式」のAESで暗号化されたデータを複合化するには暗号化で実装した流れとは逆のことを行っていきます。また使用する鍵と初期化ベクトルは同じものを使用してください。
func decryption(str: String) -> String {
do {
// AESキーと初期ベクトルを使用してAESインスタンスを生成
let aes = try AES(key: key, iv: iv)
// 文字列をUTF-8エンコーディングしてバイトデータに変換
guard let byteData = str.data(using: String.Encoding.utf8) else { return "変換失敗" }
// Base64データをデコードして元のデータに戻す
guard let data = Data(base64Encoded: byteData) else { return "変換失敗" }
// データをUInt8の配列に変換
let aBuffer = Array<UInt8>(data)
// AESで復号化
let decrypted = try aes.decrypt(aBuffer)
// 復号化されたデータをUTF-8文字列に変換
let decryptedStr = String(data: Data(decrypted), encoding: .utf8) ?? "変換失敗"
// 結果を返す
return decryptedStr
} catch {
// エラー処理
return "変換失敗"
}
}
Androidで暗号化・複合化を実装する
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。