質問内容
Secure ASP.NET Web API using API Key Authentication – HMAC Authentication
上記の記事を見てHMACを使用したAPI認証というものを知りました。
これに関して2つの質問があります。
質問1
まずはこの認証方法についてそもそもの理解ができているかの確認したく、
記事のサンプルとは異なりますが自分の理解を兼ねて仕組みを以下に記載します。
※元記事に記載があるナンスやタイムスタンプは省いています
以下の内容に誤りなどありましたらご指摘いただけますでしょうか。
サンプル API Client | | ユーザ識別IDと秘密鍵を共有 |------------------------- | | | | | | | 以下のデータから署名を作成 | | - リクエストURL | | - リクエストBODY | | - 秘密鍵 | | | リクエストにユーザ識別ID,署名を含めて送信 | ここでは仮にHTTP Headerに入れるとする,クエリストリングでも何でもいい | X-Access-Id: ... | | X-Signature: ... | |<-------------------------| | |
- API側とClientはユーザの識別を行うためのユーザ識別IDと、HMACの秘密鍵を共有する。
- Client側はAPIへのリクエストURLとリクエストBodyの2つの文字列を連結し、1で共有された秘密鍵を使用してHMACで署名を作成してBase64でエンコードする
- Clientは1で共有したユーザ識別IDとBase64でエンコードされた署名をリクエストに同封して送信する
- APIはリクエストのユーザ識別IDから対応する秘密鍵を探し、その鍵とリクエストのURLとBodyから署名を作成、送信されてきた署名と比較を行い等しければ認証成功とする
質問2
自分でコードを書いてみましたので、セキュリティの観点からレビューをいただきたいです。
Ruby on Railsで使用することを想定しています。
ruby
1class HmacAuthentificator 2 # アクセスIDヘッダ ユーザの識別を行う 3 X_ACCESS_ID_HEADER = 'X-Access-Id' 4 5 # 署名ヘッダ リクエストの正当性検証を担う 6 X_SIGNATURE_HEADER = 'X-Signature' 7 8 # タイムスタンプヘッダ replay attacks攻撃防止用 9 X_TIMESTAMP_HEADER = 'X-Timestamp' 10 11 # 署名作成のハッシュ関数 12 DIGEST_METHOD = 'sha256' 13 14 # タイムスタンプの期限切れ時間(秒) 15 TIMESTAMP_EXPIRATION = 10.0 16 17 # @param [ActionDispatch::Request] request 18 def initialize(request) 19 @request = request 20 end 21 22 # リクエストの正当性検証 23 # @return [Boolean] 24 def valid? 25 # 26 # タイムスタンプ 27 # 28 timestamp = @request.headers[X_TIMESTAMP_HEADER] 29 return false unless timestamp 30 parsed_timestamp = ::Time.zone.parse(timestamp) 31 return false unless parsed_timestamp 32 # タイムスタンプの有効期限検証,制限時間を超えているものと未来のものは拒否する 33 now = ::Time.zone.now 34 return false if now - parsed_timestamp > TIMESTAMP_EXPIRATION || parsed_timestamp > now 35 36 # 37 # アクセスID 38 # 39 access_id = @request.headers[X_ACCESS_ID_HEADER] 40 return false unless access_id 41 42 # 43 # 秘密鍵 44 # 45 secret_token = find_secret_token(access_id) 46 return false unless secret_token 47 48 # 49 # 署名 50 # 51 requested_signature = @request.headers[X_SIGNATURE_HEADER] 52 return false unless requested_signature 53 54 # 55 # 署名の再作成と検証 56 # 57 regenerated_signature = create_signature(secret_token, @request.original_url, @request.raw_post, timestamp) 58 # MITIGATE TIMING ATTACK 59 return false unless ::ActiveSupport::SecurityUtils.variable_size_secure_compare(regenerated_signature, requested_signature) 60 return false unless ::ActiveSupport::SecurityUtils.secure_compare(regenerated_signature, requested_signature) 61 62 return true 63 end 64 65 # @return [String|nil] 66 def find_secret_token(access_id) 67 # secret_tokenはDBに保存しておく 68 end 69 70 # リクエストから署名を作成 71 # 72 # @param [String] token 鍵文字列 73 # @param [String] url リクエストURL 74 # @param [String|nil] body リクエストボディ 75 # @param [String] timestamp - タイムスタンプ(ISO 8601) 76 # @return [String] 77 def create_signature(token, url, body, timestamp) 78 body = '' if body.nil? 79 content = [url, body, timestamp].map(&:strip).join 80 ::Base64.strict_encode64(::OpenSSL::HMAC.digest(DIGEST_METHOD, token, content)) 81 end 82end 83 84# Railsコントローラ 85class ExampleController < ApiBaseController 86 before_action :authentificate_api! 87 88 def index 89 render plain: 'ok' 90 end 91 92 def authentificate_api! 93 authentificator = HmacAuthentificator.new(request) 94 return true if @authentificator.valid? 95 96 fail(AuthentificationError, msg) 97 end 98end
あなたの回答
tips
プレビュー