Ruby on RailsでHMAC-SHA256を使ったリクエスト署名を検証する方法
Posted by Kohei Hayashi on
ウェブフックなどを使って自社のウェブAPIでHTTPリクエストを受信する際、そのリクエストが本当に正規の相手から送信されたかどうかを検証する必要があります。
この記事ではRuby on Railsを使ってリクエストに含まれた署名を検証する方法を紹介します。
前提
- 署名にはSHA256ハッシュ関数を使ったHMACアルゴリズムが使われている。
- 署名ダイジェストはHTTPリクエストボディを元に生成される。
- 署名ダイジェストはHTTPヘッダーにてbase64エンコードされて送信される。
このようなリクエストは、RailsのControllerで以下のようなコードを書くことによって検証することが出来ます。
class ExamplesController < ActionController::API
before_action :verify_signature
private
def verify_signature
body = request.body.read
digest = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', ENV['SHARED_KEY'], body))
return if digest == request.headers['X-Signature']
render status: 403, json: { message: 'Forbidden' }
end
end
シンプルなコードですが、一行ずつ解説をしていきます。
まず、RailsのcontrollerではrequestオブジェクトにHTTPリクエストの情報が格納されています。署名の対象がrequest.bodyなので、その情報を取得しますが、StringIOオブジェクトのままだと使えないため、readメソッドを使って読み込む必要があります。
次に、リクエストボディのダイジェスト値を計算します。リクエストの送信側がHMAC-SHA256を使って署名を付与しているので、まったく同じ方法でダイジェスト値を求めます。
環境変数のSHARED_KEYはリクエストの送信側と受信側で事前に共有した鍵となります。(rubyのSecureRandom.hex()メソッドなどの乱数ジェネレーターを使って簡単に作成できます。)
作られたダイジェストはバイナリーのため、ヘッダーの署名と同様にbase64でエンコードします。ここでBase64.encode64()ではなくstrict_encode64()を使っているのは、前者はMIME用のメソッドのため、文字列の最後に改行コード(\n)が入ってしまうからです。後者を使うと改行コードなしのbase64文字列を生成できます。
生成されたbase64ダイジェストを、ヘッダーに含まれる署名と比較します。値が同一ならばリクエストが鍵の持ち主によって送られ、通信中の改竄も行われていないことが証明出来るので、early returnをしてリクエストの処理を続けます。
もしダイジェスト値が一致しない、もしくはヘッダーが存在しない場合は、悪意のある第三者によるリクエストの可能性が高いので、HTTPステータス403(Forbidden)を返しリクエスト処理をストップします。(Railsではbefore_action等のリクエストフィルターでrenderもしくはredirectを行うと、リクエスト処理が停止されます。)
いかがでしたでしょうか。もしリクエストの署名検証なしにリクエストを処理している場合、URLがわかっただけで第三者が自社アプリケーションにアクセス出来てしまうため、ぜひ署名検証の仕組みを導入してみてください。