背景
iOSアプリにおいて、何らかのデータをローカル(端末)側に保存したいという時があります。例えば端末がオフラインの状態でサーバと通信が行えない時は一時的にローカルに情報を保存しておき、通信が可能になったタイミングでサーバ側と同期を図るといった場合です。そのような場合、ローカルに保存する情報を暗号化し、使用時に復号するといった処理を実装しておくと不正な改竄を防ぐのに効果的です。
またiOSはAndroidと比べて堅牢とはいえ、特殊な手法を使えばソースコード内の文字列を覗き見ることは不可能ではありません。ソースコードの中の文字列(APIのkey等)を見られたくないという場合などにも、暗号化をしておくことでクラッキングのリスクを軽減することが可能です。
SwCryptとは
SwCryptはRSAやAES方式の暗号をサポートするSwiftライブラリです。このライブラリを使用することで、鍵の作成や暗号化/復号といった処理を任せることができます。詳しくは下記の公式リポジトリを参照してください。
使用例
SwCrypt.swiftのインポート
READMEにもあるように、まずライブラリ内のSwCrypt.swiftを自身のプロジェクトに取り込みます。
Wrapperの作成
SwCryptが用意しているencrypt, decryptメソッドを直接使用しても良いのですが、ここではWrapperクラスを用意し、Stringの暗号化、Dataの復号、private keyの暗号化/復号といった処理用のメソッドを実装しておくことにします。
※"YourPrivateKeyEncryptionPasscode"の箇所を自身のパスコードに修正してください
class RSAWrapper {
func encryptStr(dataStr: String) -> Data? {
if let data = dataStr.data(using: String.Encoding.utf8), let publicKey = RSAKeyStore.sharedInstance.getPublicKey() {
do {
// Encrypt
let encryptedData = try CC.RSA.encrypt(data,
derKey: publicKey,
tag: "TAG".data(using: String.Encoding.utf8)!,
padding: .oaep,
digest: .sha1)
return encryptedData
} catch {
print("Failed to encrypt.")
return nil
}
} else {
return nil
}
}
func decryptData(data: Data) -> String? {
if let privateKey = RSAKeyStore.sharedInstance.getPrivateKey() {
do {
// Decrypt
let (decryptedData, _) = try CC.RSA.decrypt(data,
derKey: privateKey,
tag: "TAG".data(using: String.Encoding.utf8)!,
padding: .oaep,
digest: .sha1)
if let decryptedStr = String(data: decryptedData, encoding: String.Encoding.utf8) {
return decryptedStr
} else {
return nil
}
} catch {
print("Failed to decrypt.")
return nil
}
} else {
return nil
}
}
func encryptPrivateKey(privateKeyDER: Data) -> Data? {
do {
// Convert private key to pem
let privateKeyPEM = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKeyDER)
// Encrypt private key
let privateKeyEncrypted = try SwKeyConvert.PrivateKey.encryptPEM(privateKeyPEM, passphrase: "YourPrivateKeyEncryptionPasscode", mode: .aes256CBC)
return privateKeyEncrypted.data(using: String.Encoding.utf8)
} catch {
print("Failed to encrypt private key.")
return nil
}
}
func decryptPrivateKey(privateKeyEncrypted: Data) -> Data? {
if let privateKeyEncryptedStr = String(data: privateKeyEncrypted, encoding: String.Encoding.utf8) {
do {
// Decrypt private key
let privateKeyDecryptedPEM = try SwKeyConvert.PrivateKey.decryptPEM(privateKeyEncryptedStr, passphrase: "YourPrivateKeyEncryptionPasscode")
// Convert private key to DER and return
return try SwKeyConvert.PrivateKey.pemToPKCS1DER(privateKeyDecryptedPEM)
} catch {
print("Failed to decrypt private key.")
return nil
}
} else {
return nil
}
}
}
公開鍵・秘密鍵管理用クラスの作成
また、公開鍵と秘密鍵を生成してローカルに保存し、必要に応じて取り出すためのクラスも作成しておきます。
final class RSAKeyStore {
static let sharedInstance = RSAKeyStore()
private var privateKey: Data?
private var publicKey: Data?
private init() {
if (privateKey == nil || publicKey == nil) {
// Retrieve key pair and set to property.
if var rsaKeyPairDic: Dictionary<String, Data> = UserDefaults.standard.object(forKey: myRSAKeyPair) as? Dictionary<String, Data> {
self.publicKey = rsaKeyPairDic[myPublicKey]
if let privateKeyEncrypted = rsaKeyPairDic[myPrivateKey] {
if let privateKeyDecrypted = RSAWrapper().decryptPrivateKey(privateKeyEncrypted: privateKeyEncrypted) {
self.privateKey = privateKeyDecrypted
}
}
} else { // If key pair is not found, generate and save in local.
do {
let (privateKeyNew, publicKeyNew) = try CC.RSA.generateKeyPair(2048)
self.privateKey = privateKeyNew
self.publicKey = publicKeyNew
if let privateKeyEncrypted = RSAWrapper().encryptPrivateKey(privateKeyDER: privateKeyNew) {
var rsaKeyPairDic = Dictionary<String, Data>()
rsaKeyPairDic[myPrivateKey] = privateKeyEncrypted
rsaKeyPairDic[myPublicKey] = publicKeyNew
UserDefaults.standard.set(rsaKeyPairDic, forKey: myRSAKeyPair)
UserDefaults.standard.synchronize()
}
} catch {
print("Error for generating key pair.")
}
}
}
}
func getPrivateKey() -> Data? {
return self.privateKey
}
func getPublicKey() -> Data? {
return self.publicKey
}
}
使用方法
下記のようにWrapperクラスのメソッドを用いて暗号化/復号を行うことができます。
let encryptedData = RSAWrapper().encryptStr(dataStr: "PLAIN TEXT")
let decryptedText = RSAWrapper().decryptData(data: encryptedData)