私はAndroidユーザーログイン認証情報を保存するためのMySQLデータベースを備えたアプリを開発しています。データベースに保存する前にパスワードをハッシュするために jBCrypt を使用しています。登録時、パスワードは次のようにクライアント側でハッシュされます。
_String salt = BCrypt.gensalt();
String hash = BCrypt.hashpw("password", salt).split("\\$")[3];
salt = salt.split("\\$")[3];
hash = hash.substring(salt.length(), hash.length());
_
この場合、BCrypt.hashpw()
はハッシュを私に与えます
_
$2a$10$rrll.6qqZFLPe8.usJj.je0MayttjWiUuw/x3ubsHCivFsPIKsPgq
_
次に、パラメーター(_$2a$10$
_)を削除し、最初の22文字をソルトとして、最後の31文字をハッシュとしてデータベースに保存します。
_------------------------------------------------------------------------
| uid | salt | hash |
------------------------------------------------------------------------
| 1 | rrll.6qqZFLPe8.usJj.je | 0MayttjWiUuw/x3ubsHCivFsPIKsPgq |
------------------------------------------------------------------------
_
これで、クライアントがログインするたびに、ユーザー名とパスワードを入力すると、データベースからsaltのみが返されます。クライアントは、saltを指定してBCrypt.hashpw()
を呼び出すことにより、ハッシュを計算します。
_String salt = "$2a$10$" + returnedSalt;
String hash = BCrypt.hashpw(“password”, salt).split("\\$")[3];
hash = hash.substring(salt.length(), hash.length());
_
私に与える:
_
hash = "0MayttjWiUuw/x3ubsHCivFsPIKsPgq"
_
これは、データベースに保存されているハッシュと同じです。次に、クライアントはユーザー名と計算されたハッシュをサーバーに送り返します。一致する場合、ユーザーはログインします。
BCryptハッシュ全体を直接フェッチして、指定したパスワードと比較することで、このプロセスを簡略化できることを知っています。
_if (BCrypt.checkpw(“password”, bCryptHash))
// match
_
しかし、チェックを実行するためにハッシュされたパスワード全体をユーザーに送信するのは間違っていると感じます。
サーバー側でパスワードをハッシュすることが望ましいと理解していますが、このソリューションに問題がありますか?何か不足していますか?
電話とサーバーの間に暗号化されていないHTTP接続があるとします。これは安全なソリューションでしょうか?
クライアントは攻撃者です。その文を144回唱えながらオフィスを歩き回ります。小さなドラムであなたの辞書を句読点にしてください。そうすれば、あなたはそれを覚えているでしょう。
サーバーでJavaコードをクライアントで実行するために送信します。正直なクライアントがあなたのコードを実行します。攻撃者に強制的に実行させる人は誰もいません。クライアントの認証を使用する理由は、クライアントが、通常のユーザーになりすまそうとしている他の誰かであるのではないかと恐れる。サーバーの観点からは、クライアントに送信したバイトと戻ってきたバイトのみが表示されます。これらのバイトを確認することはできません。悪意のある悪党である攻撃者は、コードの実行をしないと完全に想定し、代わりにサーバーが期待する応答を送信することができます。
ここで、ユーザーのパスワードをデータベースに格納するだけの単純なパスワード認証スキームを考えてみましょう。認証するには、ユーザーにパスワードを送信するよう依頼し、それを保存したものと比較します。これはとても簡単です。これも非常に悪いです:plaintext passwordsと呼ばれます。問題は、データベースでの単純な読み取り専用の垣間(SQLインジェクション攻撃、盗まれたバックアップテープ、収集された古いハードディスクなど)が攻撃者にすべてのパスワードを与えることです。明確に述べると、ここでの間違いは、クライアントから送信されたときにアクセスを許可する正確な値をデータベースに格納することです。
そしてあなたの提案した計画は?まったく同じです。ハッシュ値を「そのまま」データベースに保存します。クライアントがその正確な値を送信すると、アクセスが許可されます。もちろん、honestクライアントはパスワードをハッシュして値を送信します。しかし、それに直面しましょう:多くの攻撃者は正直な人々ではありません。
これで、クライアント側でハッシュのpartを行うことに価値があります。確かに、良い password hashing は武力競争であり、ハッシュは意図的に高価になっています。クライアントでの作業の一部をオフロードするのは良いことです。クライアントが貧弱な場合など、機能しません。 Java、またはさらに悪いことに、Javascript(名前の類似性にもかかわらず、これは完全に異なるものです)を備えたスマートフォン。
その場合、クライアントでbcryptを実行し、bcrypt出力ではなくサーバーに保存する必要がありますが、bcrypt出力のhashを適切なハッシュ関数(a SHA-256のような高速なもので十分です)。パスワード[〜#〜] p [〜#〜]の処理は、クライアントではbcrypt、次にサーバーで計算される結果のSHA-256になります。 。これにより、クライアントのCPUコストのほとんどがプッシュされ、何をすべきかについての生のbcryptと同じくらい安全になります(以下を参照)。
プレーンHTTPを使用したいので、パスワードを「暗号化」したい(ハッシュは暗号化ではない!)とのことですが、他の人と同じ理由でHTTPSが好きではありません。これは恐ろしいSSL証明書です。 SSL証明書に年間20ドルを支払うのは、ジャガイモの皮をむいてレモンジュースをふりかけ、皮を剥がすようなものです。
残念ながら、皮むき器からの脱出はありません。他の人が述べたように、堅固な認証メカニズムがあっても、生のHTTPは保護されておらず、アクティブな攻撃者はユーザーが認証するのを待って、その時点から接続を乗っ取ることができます。 「アクティブな攻撃者」の非常に一般的な例は、偽のWiFiアクセスポイント(つまり、完全に本物のWiFiアクセスポイント)を実行するだけの人ですが、接続をいつでもハイジャックするオプションを保持します。これは一種の攻撃モデルであり、初期の認証方法だけでなく、データ全体全体に及ぶ包括的な暗号化プロトコルがなければ対応できません。このようなプロトコルの最も単純な種類はSSL/TLSです。絶対に必要な同じ保証を提供するプロトコルも、SSL/TLSと同じくらい複雑であり、SSL/TLSとは異なり、クライアントソフトウェアにまだ実装されていないため、実装がはるかに困難になります。
SSLがあります。使用してください。レモンは吸い上げます。
(経済的コストが障壁である場合、 Let's Encrypt や pki.io などの無料の代替手段があります。それらがあなたの請求書に合うかどうかはまだ不明ですが、本当に現金が足りない場合は検討する価値があります。)
保護された接続(https)を使用していない場合、スキーマにいくつかの欠陥があります。
安全なログインが必要な場合は、クライアント側のコードが正しいこと、およびネットワーク上で盗聴する価値のあるものがないことを保証できる必要があります。
ほとんどすべての状況で、これに対する最善の解決策は、SSL/TLSで保護された接続を使用することです。この安全な接続を介してハッシュされていないパスワードを送信し、サーバー側でハッシュを行う必要があります。
また、$2a$10$
一部、繰り返しカウントを将来的に増加させないようにします。繰り返しカウントを保存しないため、繰り返しカウントを増やすことを決定した後、古いハッシュを新しいハッシュから識別する方法はありません。
(Ifクライアントコードを保証できる場合、理論的にはを使用できます- [〜#〜] srp [〜#〜] しかし、正しく理解するのは複雑な作業であり、失敗する可能性があります)。
暗号化されていないHTTP接続を介して何かを送信している場合、それが監視されていて、MITMで侵害される可能性があると想定します。
誰かがMITMをしている場合、彼らは自分のソルトを提供し、自分のハッシュで応答することができます。したがって、攻撃者は認証プロセスを完全に偽装する可能性があります。
パスワードサーバー側をハッシュし、安全な接続を使用する必要があります。