HTTP上で暗号化・改ざんチェックが出来るOAuth「OAuth Encryption」を考案してみた

本気出して考えた。
サンプル実装もした。
クライアントプログラムはPHPで作ったものを公開。
サーバ側はPython
↓これ。
 
OAuth Encryption
http://oauth-encryption.appspot.com/
 

OAuth Encryptionとは

2-Legged OAuth認証RSA-SHA1方式)に暗号化と改ざんチェックの機能を追加した、OAuthの拡張プロトコル
HTTP上でSSLのようなやり取りをし、データの暗号化と改ざんチェックを行う。
すべてHTTPで通信しても、暗号化と改ざんチェックはSSLTLS 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 など(ハッシュ)
 

クライアントデモソース

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