アプリを作る際にLaravelをAPIサーバーとして使っている方なら一度は暗号化について考えた事があると思います。
- サーバー側で暗号化した値をアプリ側で復号したい
- アプリ側で暗号化した値をサーバー側で復号したい
今回は上記問題を解決するための方法を紹介します。
はじめに
Laravelで暗号化・復号できる文字列を生成するのが目的です。
Laravelの導入は公式サイトに譲ります。
参考にしたライブラリ
AES-CBCの暗号化にはCryptoSwiftを使わせていただきました。
暗号化手順はLaraCryptを参考にしました。
Cryptoクラスのソース
暗号化・復号手順は省かせていただきます。
Laravelでは平文を単純に暗号化しているわけではなく、JSON形式に変換して改竄チェックも含まれています。
そこを考慮して出来上がったソースは以下です。
“enter your app key”の部分は環境に合わせて変更してください。
(.envのAPP_KEYの値から”base64:”を除いた値です。)
コピペで使える、と思います。。。
(2019年9月6日 追記)LaravelCryptoDataが抜けていたので追加しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | import Foundation import CryptoSwift // JSON用の構造体 struct LaravelCryptoData: Codable { let iv: String let value: String let mac: String } // 暗号化・復号処理を行うクラス class LaravelCrypto { static let shared = LaravelCrypto() private let secretKeySpec: Array<UInt8> private init() { // Laravel Appキー let base64Key = "enter your app key" let data = Data(base64Encoded: base64Key)! secretKeySpec = data.bytes } // 文字列の暗号化処理 func encrypto(_ text: String) -> String { do { let iv: Array<UInt8> = AES.randomIV(AES.blockSize) let ivBase64 = iv.toBase64()! let cbc = CBC(iv: iv) let cryptor = try AES(key: secretKeySpec, blockMode: cbc, padding: .pkcs5) let val = stringSerializer(text) let valEnctypted = try cryptor.encrypt(Array(val.utf8)) let valBase64 = valEnctypted.toBase64()! let mixStr = String(format: "%@%@", ivBase64, valBase64) let macHex = try HMAC(key: secretKeySpec, variant: .sha256).authenticate(mixStr.bytes).toHexString() let data = LaravelCryptoData(iv: ivBase64, value: valBase64, mac: macHex) let json = try JSONEncoder().encode(data) return json.base64EncodedString() } catch let error { print(error) } fatalError("Encode Error") } // 文字列の復号処理 func decrypto(_ str64: String) -> String { do { let json = try JSONDecoder().decode(LaravelCryptoData.self, from: Data(base64Encoded: str64)!) let ivData = Data(base64Encoded: json.iv)! let iv = ivData.bytes let cbc = CBC(iv: iv) let cryptor = try AES(key: secretKeySpec, blockMode: cbc, padding: .pkcs5) let valData = Data(base64Encoded: json.value)! let valDecrypted = try cryptor.decrypt(valData.bytes) let result = String(data: Data(hex: valDecrypted.toHexString()), encoding: .utf8)! // 改竄チェック let mixStr = String(format: "%@%@", json.iv, json.value) let macValHex = try HMAC(key: secretKeySpec, variant: .sha256).authenticate(mixStr.bytes).toHexString() let macHex = json.mac if macValHex == macHex { return stringDeserializer(result) } else { print("データ改ざん") } } catch let error { print(error) } fatalError("Decode Error") } private func stringSerializer(_ str: String) -> String { // Laravel 5.5.*, 5.6.* で動作確認済み return str // Laravel 5.5.*, 5.6.* 以外は下記の対応が必要な場合があります /* return "s:\(str.bytes.count):\"\(str)\";" */ } private func stringDeserializer(_ str: String) -> String { // Laravel 5.5.*, 5.6.* で動作確認済み return str // Laravel 5.5.*, 5.6.* 以外は下記の対応が必要な場合あり /* let start = str.firstIndex(of: "\"")! let end = str.lastIndex(of: "\"")! return String(str[str.index(after: start)...str.index(before: end)]) */ } } |
躓いたポイント
stringSerializerとstringDeserializer内の処理がLaravelのバージョンによって異なる点です。
Laravelのバージョンが5.4系以前では上記メソッド内のコメントにしてある処理が必要になる可能性があります。動作確認はしていないので適宜修正してお使いください。
LaravelCryptoDataの各変数名は変更しない様にお願い致します。Laravel側での復号時にエラーが発生致します。(コメントから発覚しました。ありがとうございました!)(2019年9月9日 追記)
使い方
Laravel側はシリアライズしない暗号化方法のみ使用できます。
1 2 | $encrypted = Crypt::encryptString('Hello world.'); $decrypted = Crypt::decryptString($encrypted); |
Swift側は各関数を呼び出すだけです。
1 2 | let encrypted = LaravelCrypto.shared.encrypto("Hello world.") let decrypted = LaravelCrypto.shared.decrypto(encrypted) |
Laravel側で暗号化した文字列をSwift側のdecryptoメソッドで復号できます。逆も然りです。
最後に
いかがでしたでしょうか?
Laravel APIとの連携で暗号化文字列を使用できるようになりました。
結構めんどくさいですよね〜
快適な開発ライフが送れますように。
【動作確認環境】
Swift 4
Laravel 5.5.*
ご返信ありがとうございます。
無事解決できました、色々とお手数おかけしましたが、ご相談に乗っていただきありがとうございました!
こちらこそ、ありがとうございました。
プロジェクトの成功をお祈りしております。
返信とご確認ありがとうございます。
> stringDeserializerの中でコメントアウトしてある文字列操作を使用する必要があることがわかりました。
暗号化する際にデシリアライズするのでしょうか?
返信ありがとうございます。
メールで詳細ご返信いたしました。
ご確認よろしくお願いいたします。
> Laravel側で復号する際は「Crypt::decryptString」を使用していますでしょうか?
こちらLaravel側の復号化は「Crypt::decrypt」を使用しております。こちらの復号化でAndroid側は正常に動作しております。(Android側で暗号化 -> Laravel側で復号化)
https://www.villness.com/2627
コメントありがとうございます。
Laravel 5.7.28で動作確認を行いました。
Laravel側で「Crypt::crypt」「Crypt::decrypt」を使用する場合は、
stringDeserializerの中でコメントアウトしてある文字列操作を使用する必要があることがわかりました。
そちらで確認をお願いいたします。
よろしくお願いします。
返信ありがとうございます。
Swift内で暗号化 -> 復号化した場合、元の文字列に復号できました。Laravelのバージョンは5.7でシリアライズはする必要がないため文字列そのままで暗号化しておりますが、Laravel側で失敗してしまいます。
ちなみになのですが、「LarabelCryptoData」は以下のような構造体で間違い無いでしょうか?
struct LaravelCryptoData: Codable {
let iv: String
let value: String
let mac: String
}
コメントありがとうございます。
構造体はあっています。
記事から漏れていたので追記させていただきました。
ご指摘ありがとうございます。
Laravel側で復号する際は「Crypt::decryptString」を使用していますでしょうか?
使用している場合は5.7で暗号化処理に変更が入った可能性があります。
Laravel 5.5, 5.6では動作確認済みですが、5.7は確認しておりません。。。
申し訳ございません。
初めまして、私iOSエンジニアとしてアプリ開発に従事しております。
今回Laravelの暗号化に伴い本記事の実装を行ったのですが、うまくいきませんでした。
具体的には、Laravel Appキーを用いて文字列の暗号化を記事に書いてある通りに試したのですが、Laravel側での復号で失敗します。アプリ側での暗号化にミスがありそうなのですが、調査してみても分からずご相談させていただければと思いコメントさせていただきました。
何卒よろしくお願いいたします。
コメントありがとうございます。
Swift内で作成した暗号化文字列をSwift内で復号することは可能でしょうか?
可能な場合はLaravelのバージョンに合わせて、stringSerializer関数の中身のコメント部分を入れ替えてみてください。
よろしくお願いします。