本気出して考えた。
サンプル実装もした。
クライアントプログラムはPHPで作ったものを公開。
サーバ側はPython。
↓これ。
OAuth Encryption
http://oauth-encryption.appspot.com/
OAuth Encryptionとは
2-Legged OAuth認証(RSA-SHA1方式)に暗号化と改ざんチェックの機能を追加した、OAuthの拡張プロトコル。
HTTP上でSSLのようなやり取りをし、データの暗号化と改ざんチェックを行う。
すべてHTTPで通信しても、暗号化と改ざんチェックはSSL(TLS 1.2)と同等の強度。
自己署名証明書を使うSSLよりも人聞きがいい。
自力でソケットに読み書きが出来るから、作る側にしてみればとても嬉しい。
3-Legged OAuth認証にも応用可能。
やりたい事・目的
プロバイダがHTTPSに対応してなくても、ユーザ登録からAPI利用まで、1度も生のパスワードをやり取りしない。
APIでやり取りする実データを暗号化する。(双方向)
やり取りしたデータが改ざんされていないかチェックする。(双方向)
仕様
OAuth認証でハンドシェイクを行い、共通鍵を取得。
取得した共通鍵を使用して保護されたリソースへアクセス。
ハンドシェイク
コンシューマはプロバイダに対して通常のOAuth 1.0認証に沿った署名付きリクエストを送り、
データ暗号化のために使用するトークン・トークンシークレット(共通鍵)とトークンIV(初期ベクトル)を発行してもらう。
## リクエスト
通常のOAuth 1.0認証でリクエストを行う。
パラメータはAuthorizationヘッダ、POST、GETで渡す。
# URL(例)
http://example.com/oauth_encryption_handshake
# 認証
OAuth 1.0
# 必須パラメータ
OAuthパラメータ
・oauth_consumer_key
・oauth_signature_method
・oauth_timestamp
・oauth_nonce
・oauth_version
=> 1.0
・oauth_signature
OAuth Encryptionパラメータ
・oauth_encryption_method
=> 使用する暗号化方式
(例)AES ARC2 Blowfish CAST DES DES3
# フォーマット例
POST /oauth_encryption_token HTTP/1.1 Host: example.com Authorization: OAuth realm〜〜〜 Content-Length: 〜 Content-Type: application/x-www-form-urlencoded oauth_encryption_method=〜〜〜
## レスポンス
実データの暗号化に使う共通鍵を返す。
フォーマットはapplication/x-www-form-urlencoded。
返ってきたoauth_encryption_token_secretを秘密鍵で復号化して、暗号化に使用する共通鍵とする。
返ってきたoauth_encryption_token_ivを秘密鍵で復号化して、暗号化に使用する初期ベクトルとする。
# パラメータ
oauth_encryption_method_confirmed
=> 送った暗号化方式に問題なく、正しくトークンが返せたかのBoolean値
oauth_encryption_token
=> 生成されたトークン
oauth_encryption_token_secret
=> 生成されたトークンシークレット(暗号化に使用する共通鍵)をコンシューマの公開鍵で暗号化した文字列
oauth_encryption_token_iv
=> 生成されたトークンIV(暗号化に使用する初期ベクトル)をコンシューマの公開鍵で暗号化した文字列
# フォーマット例
HTTP/1.1 200 OK Content-Length: 〜 Content-Type: application/x-www-form-urlencoded oauth_encryption_method_confirmed=true&oauth_encryption_token=〜〜〜&oauth_encryption_token_secret=〜〜〜&oauth_encryption_token_iv=〜〜〜
保護されたリソースへアクセス
コンシューマはプロバイダに対してOAuth Encryption 1.0認証に沿った署名付きリクエストを送り、
保護されたリソースにデータを暗号化してアクセスする。
共通鍵と初期ベクトルを使用し、共通鍵取得で送信したoauth_encryption_methodを暗号化方式としてCBCモードで暗号化する。
## 暗号化する内容
暗号化する内容は、HTTP1.0リクエストのフォーマットとする。
RESTfulプロトコルなど、URLに重要な意味を持つので暗号化対象となる。
RPCプロトコルなど、どんなデータか判断出来るようにContent-Typeなどのヘッダもつける。
ヘッダ部分の改行コードはCR+LFを推奨する。
暗号化の際に、暗号化方式によりデータの長さが合わない場合がある。
長さが足りない時は、後方をnull(0x00)で埋める。
復号化後は、暗号化方式によりデータの後方にnull(0x00)がついている場合がある。
復号化されたデータ内にContent-Lengthヘッダがある場合、その値に沿って後方のバイトを削除する。
Content-Lengthヘッダが存在しない場合は、後方のnull(0x00)を削除する。
# 暗号化するフォーマット例(リクエスト)
POST /protected_resource/aaa HTTP/1.0 Content-Type: application/x-www-form-urlencoded Content-Length: 〜 aaa=aaaaaaaaaaaa&bbb=bbbbbbbbbbbbbbbbbb
# 暗号化するフォーマット例(レスポンス)
HTTP/1.0 200 OK Content-Type: 〜〜〜 Content-Length: 〜 〜〜〜
## リクエスト
送信するデータを共通鍵と初期ベクトルで、共通鍵取得で送信したoauth_encryption_methodにあわせて実データを暗号化する。
# URL(例)
=> http://example.com/protected_resource
# 必須パラメータ
・OAuthパラメータ
oauth_consumer_key
oauth_signature_method
oauth_timestamp
oauth_nonce
oauth_version
=> encryption1.0
oauth_signature
oauth_body_hash
=> application/x-www-form-urlencoded以外で送信する場合に必須
・OAuth Encryptionパラメータ
oauth_encryption_token
=> 送られてきたトークン
# ボディ
共通鍵と初期ベクトルでデータを暗号化したバイナリ文字列
# フォーマット
以下のいずれかの方法で送信する。
application/octed-streamまたはmultipart/form-dataを使用する場合はバイナリで送信するため、
application/x-www-form-urlencodedを使用する場合に比べて効率良くデータを送る事が出来る。
application/octed-streamを使用する場合、OAuth・OAuth EncryptionパラメータはすべてGETまたはAuthorizationヘッダに入れる必要がある。
・application/octed-stream
POST /protected_resource HTTP/1.1 Host: example.com Authorization: OAuth realm〜〜〜 Content-Type: application/octed-stream Content-Length: 〜 〜〜〜
・multipart/form-data
POST /protected_resource HTTP/1.1 Host: example.com Authorization: OAuth realm〜〜〜 Content-Type: multipart/form-data; boundary=---------------------------41184676334 Content-Length: 241 -----------------------------41184676334 Content-Disposition: form-data; name="oauth_encryption_data" 〜〜〜 -----------------------------41184676334--
・application/x-www-form-urlencoded
POST /protected_resource HTTP/1.1 Host: example.com Authorization: OAuth realm〜〜〜 Content-Type: application/x-www-form-urlencoded Content-Length: 〜 oauth_encryption_data=〜〜〜
## レスポンス
リソースデータを共通鍵で暗号化したものをベタで返す。
認証に失敗した場合や、oauth_encryption_dataの復号化やレスポンスボディの暗号化に失敗した場合は、
エラーステータスコードを返し、レスポンスボディは平文で返す。
エラーが起きている場合でも、レスポンスボディを暗号化して返せる場合は、
常にステータスコード200を返し、実際のエラーステータスコードは暗号化されたレスポンスボディ内に記述する。
改ざんチェックとして、ヘッダにoauth_encryption_signatureを追加する。
# oauth_encryption_signature計算方法
HMAC-SHA1方式のシグネチャ生成と手法は同じ。
oauth_encryption_tokenをURLエンコードしたものとoauth_encryption_token_secretをURLエンコードしたものを、&で繋いでkeyとする。
共通鍵で暗号化済みのデータをdataとする。
# フォーマット
HTTP/1.1 200 OK oauth_encryption_signature: 〜〜〜 Content-Length: 〜 〜〜〜
ベースにした・インスパイアしたプロトコル
OAuth(これを拡張)
HTTP(この上で動く)
SSL(ハンドシェイク)
AES / Blowfish / DES / Triple DES など(暗号化)
HMAC / SHA1 など(ハッシュ)
デモAPI
ハンドシェイク
http://oauth-encryption.appspot.com/oauth_encryption_handshake
保護されたリソース
http://oauth-encryption.appspot.com/protected_resource
クライアントデモソース
PHP (base:http://code.google.com/p/oauth/)
oauth_encryption_client_for_php-1.0.zip
$ sudo yum -y install libmcrypt libmcrypt-devel php-mcrypt $ wget http://oauth-encryption.appspot.com/files/oauth_encryption_client_for_php-1.0.zip $ unzip oauth_encryption_client_for_php-1.0.zip $ cd oauth_encryption_client_for_php-1.0 $ php oauth_encryption_test.php
実行結果
$ time php oauth_encryption_test.php === OAuthSignatureMethod_RSA_SHA1 -> build_signature === basestring: GET&http%3A%2F%2Foauth-encryption.appspot.com%2Foauth_encryption_handshake&oauth_consumer_key%3Dexample.com%26oauth_encryption_method%3DAES%26oauth_nonce%3D9f0ddc9bfc754864e59a1af672c66ccf%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1272821512%26oauth_version%3D1.0 signature: SQIlke53KCf+eR2T8ugmzQPGJXq/1CBzp7MIl+B05aFbZHUshZGwJioiUZjovtT6U2zx4nzN+4ZINzXyvcxPlSWCu09ibDDWc0J4e7Hv+itB5OH6oqFnlFpBvmGXwE11CDcDDA5igmN+hRHlFKy6TnGEw86VJ1D0gVSSfJad6IU= === Handshake Request === url: http://oauth-encryption.appspot.com/oauth_encryption_handshake?oauth_consumer_key=example.com&oauth_encryption_method=AES&oauth_nonce=9f0ddc9bfc754864e59a1af672c66ccf&oauth_signature=SQIlke53KCf%2BeR2T8ugmzQPGJXq%2F1CBzp7MIl%2BB05aFbZHUshZGwJioiUZjovtT6U2zx4nzN%2B4ZINzXyvcxPlSWCu09ibDDWc0J4e7Hv%2BitB5OH6oqFnlFpBvmGXwE11CDcDDA5igmN%2BhRHlFKy6TnGEw86VJ1D0gVSSfJad6IU%3D&oauth_signature_method=RSA-SHA1&oauth_timestamp=1272821512&oauth_version=1.0 === Handshake Response === Array ( [url] => http://oauth-encryption.appspot.com/oauth_encryption_handshake?oauth_consumer_key=example.com&oauth_encryption_method=AES&oauth_nonce=9f0ddc9bfc754864e59a1af672c66ccf&oauth_signature=SQIlke53KCf%2BeR2T8ugmzQPGJXq%2F1CBzp7MIl%2BB05aFbZHUshZGwJioiUZjovtT6U2zx4nzN%2B4ZINzXyvcxPlSWCu09ibDDWc0J4e7Hv%2BitB5OH6oqFnlFpBvmGXwE11CDcDDA5igmN%2BhRHlFKy6TnGEw86VJ1D0gVSSfJad6IU%3D&oauth_signature_method=RSA-SHA1&oauth_timestamp=1272821512&oauth_version=1.0 [content_type] => text/html; charset=utf-8 [http_code] => 200 [header_size] => 214 [request_size] => 477 [filetime] => -1 [ssl_verify_result] => 0 [redirect_count] => 0 [total_time] => 0.395411 [namelookup_time] => 0.000899 [connect_time] => 0.040381 [pretransfer_time] => 0.040453 [size_upload] => 0 [size_download] => 766 [speed_download] => 1937 [speed_upload] => 0 [download_content_length] => 0 [upload_content_length] => 0 [starttransfer_time] => 0.395347 [redirect_time] => 0 ) result: oauth_encryption_method_confirmed=true&oauth_encryption_token=bl9fdbzIlWjJ6rHh9mBaXSjQCHRc8ERN&oauth_encryption_token_iv=Q8y%20%FFFt%87%7B%9A%AD%BC%E7%AFS%8A%01%AA.%B3Y%90%F4pVl%3A%DA%EFh%EA%3C%00%FD%88%8C%AFu%04N7%12hHD.%89f%A5%B1%93%D5%2C%F6fB%93%CE%2B%18g%B1%FC%1B%0C%9C%5C%80G%95%0B%A8%40cW3%EC%F8%20%16x%E8%17%C8%88%FA%B7%3EM%ABUY%FC%F1%1C%C6%9FW%A270%F5%87%B8%E1%C7%FF%23%9AP%8F%3ENG%DE%A4%01%E8%87z%0D%A19%EE%FCp%0EL&oauth_encryption_token_secret=t%AD%A4%E9V%F0%D6a%BA%E9O%85r%60%E8%0D%ED3%DE%A7%224%8A7%01%22%F36%7B%F3%C8%C6%0D%A1%28%3E%97%7F%B1%81%BD%CBm%E3q%94%93%1D%1DW%A7%04s%FD%BA%1B4%F6%BEr%0EYu%3A%1B%83F%24a%CB%AB%DBrK%E4%C3%8D%B2z%FEM%F2%F6%D9%D4r%FB%AA%98P%D1%CA%D9%FD%A1%CF%EC%D24KC%EF%C6%E1J%F3E%0F%3F%29%27O%00%0F%8F%9B%AA%A1z%E35A%0A%7E%F2Q8%CB === OAuthEncryptionMethod_AES -> set_token === token_secret len: 32 token_iv len: 16 time: 0.0035860538482666 === OAuthEncryptionMethod_AES -> encrypt === block_size: 16 data size: 128 time: 0.00022196769714355 === OAuthSignatureMethod_RSA_SHA1 -> build_signature === basestring: POST&http%3A%2F%2Foauth-encryption.appspot.com%2Fprotected_resource&oauth_body_hash%3DuGV%252BNMrEoigcG%252FeTC3FsG8gaLf8%253D%26oauth_consumer_key%3Dexample.com%26oauth_encryption_token%3Dbl9fdbzIlWjJ6rHh9mBaXSjQCHRc8ERN%26oauth_nonce%3Dd4293a4fdd46473a4a895a949f6963c3%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1272821512%26oauth_version%3Dencryption1.0 signature: YZrue5eGo4jMhDf0A26gL+z9ZtKkZgRLdFKz4yS/JOoGtzC/314Of7rlHvtJNGWr6l32UFUasNms2bXqHik6LwJUJUfJQQ1tUkuumOi/oV7h2u1cKkc2gSUK2KuJcApe67ZqDmDYd2UuDDuBW4QvQgosVjW2HFnPIiW/2p79Hos= === Encrypted Request === URL: http://oauth-encryption.appspot.com/protected_resource?oauth_body_hash=uGV%2BNMrEoigcG%2FeTC3FsG8gaLf8%3D&oauth_consumer_key=example.com&oauth_encryption_token=bl9fdbzIlWjJ6rHh9mBaXSjQCHRc8ERN&oauth_nonce=d4293a4fdd46473a4a895a949f6963c3&oauth_signature=YZrue5eGo4jMhDf0A26gL%2Bz9ZtKkZgRLdFKz4yS%2FJOoGtzC%2F314Of7rlHvtJNGWr6l32UFUasNms2bXqHik6LwJUJUfJQQ1tUkuumOi%2FoV7h2u1cKkc2gSUK2KuJcApe67ZqDmDYd2UuDDuBW4QvQgosVjW2HFnPIiW%2F2p79Hos%3D&oauth_signature_method=RSA-SHA1&oauth_timestamp=1272821512&oauth_version=encryption1.0 Header: Array ( [0] => Content-Type: application/octed-stream ) Encrypted body: 3df3c522aa24c0a45610d8417d79bb797e28a53127b93906e343a4f64697635360fec90098b69b148d0cd77ec64d0db1b9b955917107d0fa7960c23abc9ae6b9f89a28acbdc854414fbff2c4d2b5171f3202966c50ad43d7fa19db3d8d7b1bda2dec1ca26d3bfeea7abeafa23304dd623801191c22514a5e7632907a3c145eff === Encrypted Response === Array ( [url] => http://oauth-encryption.appspot.com/protected_resource?oauth_body_hash=uGV%2BNMrEoigcG%2FeTC3FsG8gaLf8%3D&oauth_consumer_key=example.com&oauth_encryption_token=bl9fdbzIlWjJ6rHh9mBaXSjQCHRc8ERN&oauth_nonce=d4293a4fdd46473a4a895a949f6963c3&oauth_signature=YZrue5eGo4jMhDf0A26gL%2Bz9ZtKkZgRLdFKz4yS%2FJOoGtzC%2F314Of7rlHvtJNGWr6l32UFUasNms2bXqHik6LwJUJUfJQQ1tUkuumOi%2FoV7h2u1cKkc2gSUK2KuJcApe67ZqDmDYd2UuDDuBW4QvQgosVjW2HFnPIiW%2F2p79Hos%3D&oauth_signature_method=RSA-SHA1&oauth_timestamp=1272821512&oauth_version=encryption1.0 [content_type] => text/html; charset=utf-8 [http_code] => 200 [header_size] => 272 [request_size] => 746 [filetime] => -1 [ssl_verify_result] => 0 [redirect_count] => 0 [total_time] => 0.345228 [namelookup_time] => 2.7E-5 [connect_time] => 0.039408 [pretransfer_time] => 0.03946 [size_upload] => 0 [size_download] => 464 [speed_download] => 1344 [speed_upload] => 0 [download_content_length] => 0 [upload_content_length] => 0 [starttransfer_time] => 0.345191 [redirect_time] => 0 ) === OAuthEncryptionMethod_AES -> decrypt === data size: 464 time: 0.00020503997802734 Array ( [protocol] => HTTP/1.0 [status] => 200 [status_message] => OK [header] => Array ( [Content-Length] => 419 ) [body] => Hello OAuth Encryption world !! This message is encrypted response data. Consumer info oauth_consumer_key: example.com Authentication info oauth_encryption_method: aes oauth_encryption_token: bl9fdbzIlWjJ6rHh9mBaXSjQCHRc8ERN Request info Method: GET Path: /protected_resource/aaa Protocol: HTTP/1.0 Header: {'Content-Length': '39', 'Content-Type': 'text/plain'} Body: This message is encrypted request data. ) real 0m0.790s user 0m0.012s sys 0m0.020s
OAuth WRAP
最近世の中にはOAuth WRAPというものがあるらしい。
クライアント側でJavaScriptを使った3-Legged OAuth認証の代わりになるものらしい。
OAuth WRAPとOAuth Encryptionは、用途が違う全くの別物。
詳しくは知らないけど。
OAuth WRAP - Codin’ In The Free World
http://d.hatena.ne.jp/lyokato/20091118/1258524429
The Introduction of OAuth WRAP vol.2 : JavaScript Profile on FriendFeed - r-weblife
http://d.hatena.ne.jp/ritou/20100116/1263575962
OAuth WRAP について今北産業に答える - 知らないけどきっとそう。
http://d.hatena.ne.jp/asannou/20100410
今 OAuth に何が起こっているのか。 - nobnakの日記
http://d.hatena.ne.jp/nobnak/20100423/1272022402