Security & Privacy
GDPR準拠のエンドツーエンド暗号化チャットアプリ開発ガイド
49 分
gdpr準拠のチャットアプリの作り方 はじめに back4appコミュニティの皆さん、こんにちは! これは、 virgil security, inc https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat のチームからのゲストチュートリアルです。私たちは、 twilioのエンドツーエンド暗号化メッセージング https //www twilio com/blog/2016/05/introducing end to end encryption for twilio ip messaging with virgil security html の背後にある暗号技術です。back4appの友人たちが、back4appの上にエンドツーエンド暗号化チャットアプリを構築する方法を示してほしいと頼んできました。 この記事では、シンプルなback4app android messengerアプリをエンドツーエンド暗号化する手順を説明します!準備はいいですか?ps:詳細が気にならない場合は、投稿の最後にスキップして最終製品をダウンロードしてください。 エンドツーエンド暗号化とは? まず、e2ee(エンドツーエンド暗号化)が何であり、どのように機能するかを簡単におさらいしましょう。e2eeはシンプルです:チャットメッセージを入力すると、それはあなたのモバイルデバイス(またはブラウザ)で暗号化され、チャットパートナーが受信してチャットウィンドウに表示したいときにのみ復号化されます。 メッセージは、wi fiやインターネットを通じて、クラウド/ウェブサーバーを経由し、データベースに入り、再びチャットパートナーに戻る間、暗号化されたままです。言い換えれば、ネットワークやサーバーは、あなたたち二人が何をチャットしているのか全く分からないのです。 エンドツーエンド暗号化で難しいのは、チャットに関与するユーザーだけがアクセスできるように暗号化キーを管理することです。そして、私が「他の誰も」と書くとき、私は本当にそれを意味します:あなたのクラウドプロバイダーの内部者や、あなた自身、開発者でさえもアクセスできないのです; 偶発的なミス https //techcrunch com/2017/11/29/meet the man who deactivated trumps twitter account/ や法的に強制された覗き見は可能です。特に複数のプラットフォーム向けに暗号を作成するのは難しいです:真のランダム数を生成し、適切なアルゴリズムを選択し、適切な暗号化モードを選ぶことは、ほとんどの開発者が手を挙げて結局やらない理由のほんの一例です。 このブログ投稿では、これらの煩わしい詳細を無視し、virgilのsdkを使用して迅速かつ簡単にエンドツーエンド暗号化を行う方法を示します。 イントロとして、back4appのメッセンジャーアプリをエンドツーエンド暗号化にアップグレードする方法は次のとおりです: サインアップ時:新しいユーザーのために個別の秘密鍵と公開鍵を生成します(受信者の公開鍵がメッセージを暗号化し、対応する受信者の秘密鍵がそれを復号化します)。 メッセージを送信する前に、受信者の公開鍵でチャットメッセージを暗号化します。 メッセージを受信した後、受信者の秘密鍵でチャットメッセージを復号化します。 ユーザーの公開鍵をvirgilのカードサービスに公開し、チャットユーザーが互いに検索でき、メッセージを暗号化できるようにします。秘密鍵はユーザーのデバイスに留まります。 シンプルに保つ これはe2eeチャットの最もシンプルな実装であり、2人のユーザー間のシンプルなチャットアプリに完璧に機能します。 会話が短命であり、デバイスがプライベートキーを持って失われた場合にメッセージ履歴を失っても問題ない場合に適しています。 さて、話は十分です!コーディングに取り掛かりましょう。 まず、androidアプリのセットアップを案内します。 次に、アプリをエンドツーエンドで暗号化するためのコードを追加します。 前提条件 このチュートリアルを完了するには、次のものが必要です: android studio。 back4appで作成されたアプリ。 次の 新しいアプリを作成するチュートリアル をフォローして、back4appでアプリを作成する方法を学びます。 次に、 virgil securityアカウント にサインアップします(アプリは後で作成します)。 「クリーン」back4appメッセンジャーアプリを設定しましょう 1 アプリサーバーを設定する クラウド関数のデプロイから始めましょう。これには、次のことが必要です 「 main js main js 」と「 package json package json 」を「 scripts scripts ディレクトリ内で見つける; 「 main js main js 」をお気に入りのエディタで開く。 1 1) back4appの認証情報を取得する アプリの「 ダッシュボード ダッシュボード > 「 アプリ設定 アプリ設定 > 「 セキュリティとキー セキュリティとキー 「 main js main js 」を置き換えます parse app id parse app id をあなたの アプリケーションid アプリケーションid と parse rest api key parse rest api key をあなたの rest apiキー rest apiキー に置き換えます。 1 2) ヴィルジルの資格情報を取得する 「 virgil dashboard https //dashboard virgilsecurity com/ 」でアプリケーションを作成します。 新しいvirgilアプリケーションを開き、e3kitセクションに移動して、 env env ファイルを左側のサイドバーのe3kitセクションの下に生成します の値をコピーします app id app id , app key app key , および app key id app key id を env env ファイルから コピーした値を main js main js ファイルの対応するフィールドに置き換えます ( main js main js の scripts scripts ディレクトリ) 1 3) クラウドコード関数をデプロイする back4appの「ダッシュボード」を開く > 「コア」 > クラウドコード関数; クリックして +add を選択し、main js と package json(スクリプトディレクトリから)を選択します。その後、両方をクラウドフォルダに移動します; deploy をクリックします。 2 クリーンな back4app kotlin デモアプリを開始する まず、back4app クラウドコード機能を設定することを忘れないでください。これはこのデモの必須部分です。その後、次の手順に従ってください 2 1) android studio でプロジェクトをインポートする android studio を開く > ファイル ファイル > 新規 新規 > バージョン管理からプロジェクト バージョン管理からプロジェクト > git git git リポジトリ url https //github com/virgilsecurity/chat back4app android 次の clean chat kt clean chat kt ブランチをチェックアウトします 重要! 「プロジェクト」タイプのファイルツリーを選択してください。これはチュートリアル全体で使用されます 2 2) プロジェクトに back4app の資格情報を設定する back4app の「ダッシュボード」を開く > 「アプリ設定」 > 「セキュリティとキー」; 「 /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml 」ファイルに移動し、あなたの your back4app app id your back4app app id をあなたの アプリケーションid アプリケーションid に置き換え、 your back4app client key your back4app client key をあなたの クライアントキー クライアントキー に置き換えます。 2 3) dbを設定する back4appの「ダッシュボード」 > 「コア」 > 「データベースブラウザ」 > 「クラスを作成」 を開き、 カスタム カスタム タイプのクラス「 メッセージ メッセージ 」と「 チャットスレッド チャットスレッド 」を作成します。 2 4) ライブクエリの設定 あなたの back4appアカウント https //dashboard back4app com/apps/#!/admin アプリケーションの サーバー設定 サーバー設定 ボタンを押します。 「ウェブホスティングとライブクエリ」ブロックを見つけます。 ライブクエリ設定を開き、 ホスティングを有効化 ホスティングを有効化 オプションを確認します。 作成した2つのクラスのためにライブクエリを有効にするサブドメインの名前を選択します メッセージ メッセージ と チャットスレッド チャットスレッド 新しいサブドメイン名をコピーし、saveボタンをクリックします /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml に戻り、上記で入力した「サブドメイン名」を back4app live query url back4app live query url に「yoursubdomainname」の代わりに貼り付けます これらの手順を実行した後、android studioで実行ボタンを押してサンプルを動作させることができるようになります。エミュレーターまたは実際のデバイスを使用してテストしてください。 3 クリーンデモを実行する デモのクリーンバージョンを実行した結果を見るには、次のことを行う必要があります 2人のユーザーにサインアップする; 彼らの間で会話を始め、いくつかのメッセージを送信する; 3\ back4appの「ダッシュボード」 >「コア」 >「データベースブラウザ」 >「メッセージ」を開きます。 すべてがうまくいった場合、チャットメッセンジャーアプリが表示されるはずです。2人のユーザーを登録し、お互いにいくつかのメッセージを送信してください 新しいデータが メッセージ メッセージ クラスに表示されるはずです。 サーバー上でユーザーが何についてチャットしているかを見ることができることに注意してください 次 チャットインターフェースを閉じて、次のステップ – e2ee暗号化の追加に進んでください。 さあ、メッセージをエンドツーエンドで暗号化しましょう! この部分の終わりまでに、サーバー上でチャットメッセージがどのように見えるかは次の通りです:違いがわかりますか? どうやってそこにたどり着くのでしょうか?明らかに、エンドツーエンドの暗号化を実装する必要があります。つまり、私たちのアプリは次のことを行う必要があります: サインアップの一部として、プライベートキーとパブリックキーのペアを生成する ユーザーのデバイスのキーストレージにプライベートキーを保存する 他のユーザーがダウンロードしてメッセージを暗号化できるように、virgilのカードサービスにパブリックキーを「virgilカード」として公開する パブリックキーでメッセージを暗号化し、プライベートキーで署名する;プライベートキーでメッセージを復号化し、パブリックキーで検証する そのために、クリーンなデモアプリケーションにe3kitを追加し、上記で説明したすべてを実装するためのコードを追加する必要があります。 しかし、始める前に、あなたのために2つの重要な用語を明確にしましょう:virgilカードとは何か、プライベートキーとは何か? virgilカード virgilカードはユーザーのプライベートキーを保持します。virgilカードはvirgilのカードサービスに公開されます(このサービスは電話帳のようなものだと想像してください)他のユーザーがそれを取得するために:アリスはボブのパブリックキーを取得して、そのキーを使用してボブにメッセージを暗号化する必要があります。 プライベートキー 暗号化キーのプライベート部分。プライベートキーは、対応するパブリックキーを使用して暗号化されたデータを復号化できることを覚えておいてください。 1 クリーンなe3kit back4app kotlinデモにe3kitを追加する アプリレベルの ( モジュール app モジュール app ) gradle に /app/build gradle /app/build gradle を追加します(ただし、まだ gradle スクリプトを同期しないでください) プロジェクトレベルの次の内容を追加します /build gradle /build gradle ( プロジェクト chat back4app android プロジェクト chat back4app android ) これで gradle スクリプトを同期できます; 新しいフィールドを追加して appvirgil appvirgil クラスを更新します クラスに必要なライブラリをインポートするには、 alt+ enter alt+ enter を押してください。 2 back4app cloud codeでユーザーを認証する 次のディレクトリに、 /virgilsecurity/virgilback4app/model/ /virgilsecurity/virgilback4app/model/ データクラスを作成します。 authenticateresponse authenticateresponse と virgiljwtresponse virgiljwtresponse は、cloud code関数からの応答を表します。 次のディレクトリに、 /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ を作成し、 authrx authrx オブジェクトを作成して、cloud code関数への呼び出しを実装します(その後、必要なライブラリをすべてインポートするのを忘れないでください): 1 object authrx { 2 3 / 4 you can call it only after successful \[authenticate] 5 / 6 fun virgiljwt(sessiontoken string) = single create\<string> { emitter > 7 val requestparams = mutablemapof\<string, string>() apply { 8 put("sessiontoken", sessiontoken) 9 } 10 11 parsecloud callfunctioninbackground\<map\<string, any>>( 12 key virgil jwt, 13 requestparams 14 ) { virgiljwt, exception > 15 if (exception == null) 16 emitter onsuccess(virgiljwt\[key token] tostring()) 17 else 18 emitter onerror(exception) 19 20 } 21 } 22 23 private const val key virgil jwt = "virgil jwt" 24 private const val key token = "token" 25 } 3 ローカルにvirgil jwtを保存する cloud code関数から受け取ったvirgilトークンは、ローカルに保存する必要があります。 preferences preferences クラスを更新しましょう /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ 定数を定義する 関数を追加する setvirgiltoken setvirgiltoken , virgiltoken virgiltoken と clearvirgiltoken clearvirgiltoken kotlin fun setvirgiltoken(virgiltoken string) { with(sharedpreferences edit()) { putstring(key virgil token, virgiltoken) apply() } } fun virgiltoken() string? { with(sharedpreferences) { return getstring(key virgil token, null) } } fun clearvirgiltoken() { with(sharedpreferences edit()) { remove(key virgil token) apply() } } 1 virgil token should be reset on logout let's add `preferences instance(this) clearvirgiltoken()` line into `initdrawer` function of `threadslistactivity` class (in ` /virgilsecurity/virgilback4app/chat/contactslist/`) 2 kotlin 3 private fun initdrawer() { 4 5 nvnavigation setnavigationitemselectedlistener { item > 6 r id itemlogout > { 7 dldrawer closedrawer(gravitycompat start) 8 presenter disposeall() 9 showbaseloading(true) 10 // new code >> 11 preferences instance(this) clearvirgiltoken() 12 // << new code 13 14 } 15 } 16 } 4 ユーザー登録の修正 e3kitはあなたのプライベートキーとパブリックキーを管理します。登録プロセス中にそれらを生成するために、次のことを行う必要があります に /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ を作成します rxethree rxethree クラス 今、追加する initethree initethree 関数は、 rxethree rxethree クラスの e3kit インスタンスを初期化します 1 import com virgilsecurity android common model ethreeparams 2 import com virgilsecurity android ethree interaction ethree 3 4 5 fun initethree(identity string, verifyprivatekey boolean = false) 6single\<ethree> = single create { e > 6 val params = ethreeparams(identity, {preferences virgiltoken()!!}, context) 7 val ethree = ethree(params) 8 if (verifyprivatekey) { 9 if (ethree haslocalprivatekey()) { 10 e onsuccess(ethree) 11 } else { 12 e onerror(keyentrynotfoundexception()) 13 } 14 } else { 15 e onsuccess(ethree) 16 } 17 } 追加する registerethree registerethree 関数は、新しいユーザーを rxethree rxethree クラスに登録します。 e3kit はサインアップ中にキー ペアを生成します。生成されたプライベートキーはローカルストレージに保存され、公開鍵は virgil services に virgil card として公開されます。 1 import com android virgilsecurity virgilback4app appvirgil 2 import com virgilsecurity common callback oncompletelistener 3 import io reactivex completable 4 5 6 fun registerethree() completable = completable create { e > 7 appvirgil ethree register() addcallback(object oncompletelistener { 8 override fun onerror(throwable throwable) { 9 e onerror(throwable) 10 } 11 12 override fun onsuccess() { 13 e oncomplete() 14 } 15 16 }) 17 } いくつかの更新を行いましょう loginpresenter loginpresenter クラス( /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) ) フィールドを追加します rxethree rxethree 1 private val rxethree = rxethree(context) 次の requestsignup requestsignup 関数を更新して、e3kitで登録を実行します 1 fun requestsignup(identity string, onsuccess () > unit, onerror (throwable) > unit) { 2 val password = generatepassword(identity tobytearray()) 3 4 val disposable = rxparse signup(identity, password) 5 subscribeon(schedulers io()) 6 observeon(schedulers io()) 7 // new code >> 8 tosingle { parseuser getcurrentuser() } 9 flatmap { authrx virgiljwt(it sessiontoken) } 10 map { preferences setvirgiltoken(it) } 11 flatmap { rxethree initethree(identity) } 12 map { appvirgil ethree = it } 13 flatmap { rxethree registerethree() tosingle { unit } } 14 // << new code 15 observeon(androidschedulers mainthread()) 16 // updated code >> 17 subscribeby( 18 onsuccess = { 19 onsuccess() 20 }, 21 onerror = { 22 onerror(it) 23 } 24 ) 25 // << updated code 26 27 compositedisposable += disposable 28 } 5 サインイン関数を修正する さて、「 requestsignin requestsignin 」メソッドを loginpresenter loginpresenter クラス( /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) )に変更を加えましょう。 1 fun requestsignin(identity string, 2 onsuccess () > unit, 3 onerror (throwable) > unit) { 4 5 val password = generatepassword(identity tobytearray()) 6 7 val disposable = rxparse login(identity, password) 8 subscribeon(schedulers io()) 9 observeon(schedulers io()) 10 // new code >> 11 flatmap { authrx virgiljwt(it sessiontoken) } 12 map { preferences setvirgiltoken(it) } 13 flatmap { rxethree initethree(identity, true) } 14 map { appvirgil ethree = it } 15 // << new code 16 observeon(androidschedulers mainthread()) 17 // updated code >> 18 subscribeby( 19 onsuccess = { 20 onsuccess() 21 }, 22 onerror = { 23 onerror(it) 24 } 25 ) 26 // << updated code 27 28 compositedisposable += disposable 29 } 6 既存のチャットのリストを取得する 次に、e3kitの初期化を処理する関数を threadslistfragment threadslistfragment クラス( /virgilsecurity/virgilback4app/chat/contactslist/) /virgilsecurity/virgilback4app/chat/contactslist/) )に追加します。 1 private fun oninitethreesuccess() { 2 presenter requestthreads(parseuser getcurrentuser(), 3 20, 4 page, 5 const tablenames created at criteria, 6 ongetthreadssuccess, 7 ongetthreadserror) 8 } 9 10 private fun oninitethreeerror(throwable throwable) { 11 showprogress(false) 12 if (adapter itemcount == 0) 13 tverror visibility = view\ visible 14 15 utils toast(activity, utils resolveerror(throwable)) 16 } 更新 postcreateinit postcreateinit 関数を初期化するための e3kit 1 override fun postcreateinit() { 2 3 presenter = threadslistfragmentpresenter(activity) 4 5 showprogress(true) 6 // updated code >> 7 if (appvirgil isethreeinitialized()) { 8 presenter requestthreads(parseuser getcurrentuser(), 9 20, 10 page, 11 const tablenames created at criteria, 12 ongetthreadssuccess, 13 ongetthreadserror) 14 } else { 15 presenter requestethreeinit(parseuser getcurrentuser(), oninitethreesuccess, oninitethreeerror) 16 } 17 // << updated code 18 } 次のコードを追加します threadslistfragmentpresenter threadslistfragmentpresenter クラスに virgilsecurity virgilback4app chat contactslist/ virgilsecurity virgilback4app chat contactslist/ フィールドを追加 1 private val rxethree = rxethree(context) および関数 1 fun requestethreeinit(currentuser parseuser, onsuccess () > unit, onerror (throwable) > unit) { 2 val disposable = rxethree initethree(currentuser username) 3 subscribeon(schedulers io()) 4 observeon(androidschedulers mainthread()) 5 subscribeby( 6 onsuccess = { 7 appvirgil ethree = it 8 onsuccess() 9 }, 10 onerror = { 11 onerror(it) 12 } 13 ) 14 15 compositedisposable += disposable 16 } この時点で、ユーザーのサインアップ/サインインができ、他のユーザーとの新しいチャットを作成できます。 では、メッセージの暗号化を追加しましょう。 7 メッセージの暗号化と復号化 「 findcard findcard 」関数を rxethree rxethree クラスに追加します( /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ )それは、ユーザー名によって最新のvirgilカードを取得するのに役立ちます 1 fun findcard(identity string) single\<card> = single create { e > 2 appvirgil ethree finduser(identity) addcallback(object onresultlistener\<card> { 3 override fun onerror(throwable throwable) { 4 e onerror(throwable) 5 } 6 7 override fun onsuccess(result card) { 8 e onsuccess(result) 9 } 10 11 }) 12 } チャットが開かれると、受信者のvirgilカードを取得する必要があります。 postcreateinit postcreateinit の chatthreadfragment chatthreadfragment クラス( /virgilsecurity/virgilback4app/chat/thread/ /virgilsecurity/virgilback4app/chat/thread/ )を置き換えることによって 1 presenter requestmessages(thread, 2 50, 3 page, 4 const tablenames created at criteria, 5 ongetmessagessuccess, 6 ongetmessageserror) 新しいコードに置き換えます 1 presenter requestcard(recipientid, 2 ongetcardsuccess, 3 ongetcarderror) そして、2つの関数を追加します 1 private fun ongetcardsuccess(card card) { 2 showprogress(false) 3 adapter interlocutorcard = card 4 presenter requestmessages(thread, 5 50, 6 page, 7 const tablenames created at criteria, 8 ongetmessagessuccess, 9 ongetmessageserror) 10 } 11 12 private fun ongetcarderror(t throwable) { 13 if (t is virgilcardisnotfoundexception || t is virgilcardserviceexception) { 14 utils toast(this, 15 "virgil card is not found \nyou can not chat with user without virgil card") 16 activity onbackpressed() 17 } 18 showprogress(false) 19 srlrefresh isrefreshing = false 20 locksendui(lock = false, lockinput = false) 21 22 utils toast(this, utils resolveerror(t)) 23 } では、 chatthreadpresenter chatthreadpresenter を変更しましょう フィールドを追加します 1 private val ethree = appvirgil ethree 2 private lateinit var usercard card 3 private val rxethree = rxethree(context) 受取人のvirgilカードを取得する関数を追加します 1 fun requestcard(identity string, 2 onsuccess (card) > unit, 3 onerror (throwable) > unit) { 4 5 val disposable = rxethree findcard(identity) 6 subscribeon(schedulers io()) 7 observeon(androidschedulers mainthread()) 8 subscribeby( 9 onsuccess = { 10 usercard = it 11 onsuccess(it) 12 }, 13 onerror = { 14 onerror(it) 15 } 16 ) 17 18 compositedisposable += disposable 19 } 送信メッセージの暗号化を追加します requestsendmessage requestsendmessage 関数に 1 fun requestsendmessage(text string, 2 thread chatthread, 3 onsuccess () > unit, 4 onerror (throwable) > unit) { 5 6 val encryptedtext = ethree authencrypt(text, usercard) 7 val disposable = rxparse sendmessage(encryptedtext, thread) 8 9 } すべての受信メッセージの復号化を追加します chatthreadrvadapter chatthreadrvadapter クラスに ( /virgilsecurity/virgilback4app/chat/thread/) /virgilsecurity/virgilback4app/chat/thread/) フィールドを追加 1 private var ethree ethree = appvirgil ethree 2 lateinit var interlocutorcard card メッセージの復号化を実装する onbindviewholder onbindviewholder 関数 1 override fun onbindviewholder(viewholder recyclerview\ viewholder, position int) { 2 when (viewholder) { 3 is holdermessageme > { 4 val decryptedtext = ethree authdecrypt(items\[position] body) 5 viewholder bind(decryptedtext) 6 } 7 is holdermessageyou > { 8 val decryptedtext = ethree authdecrypt(items\[position] body, interlocutorcard) 9 viewholder bind(decryptedtext) 10 } 11 } 12 } 8 完全なエンドツーエンド暗号化デモを実行する 完全にエンドツーエンドで暗号化されたデモの結果を見るには、もう一度これらのステップを実行してください 前のユーザーからログアウトする 2人の新しいユーザーにサインアップする; 彼らの間で会話を始め、いくつかのメッセージを送信する; back4appの「ダッシュボード」 >「コア」 >「データベースブラウザ」 >「メッセージ」を開く。 重要! 現在のユーザーから ログアウト し、2人の新しいユーザーを登録する必要があります。その後、これらの2人の新しいユーザーとのe2eeチャットを開始できます。理由は、最初の2人のユーザーには virgil card virgil card がないため、彼らに対して暗号化\復号化を使用できないからです。{ blockquote tip} 完了! これで、ユーザーのメッセージが暗号化され、アプリ内でユーザー自身のみがアクセスできることがわかります。 hipaaおよびgdprのコンプライアンス エンドツーエンドの暗号化は、hipaa(1996年のアメリカ合衆国の健康保険の携帯性と説明責任に関する法律)およびgdpr(欧州連合の一般データ保護規則)の技術的要件を満たす方法です。詳細が必要な場合は、無料の virgilアカウント https //developer virgilsecurity com/account/signup?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat にサインアップし、私たちのslackコミュニティに参加して、そこで私たちに連絡してください あなたのプライバシーの状況について話し合い、hipaaおよびgdprの技術的要件を満たすために必要なことを理解する手助けを喜んで行います。 ここからどこに行くべきか? 最終プロジェクト https //github com/virgilsecurity/chat back4app android/ パズルのピースを見逃した場合は、e2eeプロジェクトブランチを開いてください。このコードにアプリケーションクレデンシャルを挿入し(記事中に行ったように)、プロジェクトを構築できます。 virgil securityで構築できることについての詳細情報は、 こちら https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat で見つけることができます。