iPhone・iPadのSafariでもあらゆるページで自動入力したい
前回までにMacBookにおいては、手軽に、安全で、確実なSafariの自動入力環境になった。
しかし、本来はiPhoneやiPadでこそ、自動入力したいのではないか?MacBookならキーチェーン.appの自分のメモを見れば、どのページであっても手入力して、どうにかログインはできる。ところが、iPhone・iPadにはキーチェーン.appがないので、ID・パスワードを確認する術がないのだ。
出先でちょこっと金融機関のページにログインしたいと思っても、自動入力できず、ID・パスワードもあやふやでログインできず、諦めた経験はないだろうか。結局、家に帰って、MacBookの前に座って、ようやくログイン。そんな経験が自分には少なからずある。また、iPhoneの小さなソフトウェアキーボードで入力するのも面倒くさい。
自動入力は、iPhone・iPadで使えてこそ、その真価を発揮するのだ。但し、ロック解除の動作中に無制限に自動入力されてしまうのは問題である。また、ID・パスワードを単純なテキストとして保存しておくのも問題である。例えiPhoneを落としたとしても、そして悪意のある第三者に拾われたとしても、自動入力やファイル解析で簡単にアクセスされないセキュリティも必要である。
そんなiPhone・iPadの自動入力環境を目指して、いろいろやってみた。
プラットホームの違い
MacBookでは...
- AppleScriptを実行できる。
- Safariの機能拡張を利用できる。
- また、AppleScript経由で、標準インストールされるUNIX由来の環境やスクリプト言語など、あらゆる環境にアクセスして活用できる。
iPhone・iPadでは...
- AppleScriptは実行できない。
- Safariの機能拡張は使えない。
- Mobile Safariで手軽に活用できるのは、JavaScriptのみである。
方針
- つまり、自分にとっての選択肢はJavaScriptのみ。
- 前回AppleScriptで作った自動入力環境を、今度はJavaScriptで実装すれば良いはず。
- 但し、JavaScriptはブラウザの中の世界しかアクセスできないと思っているので、opensslコマンドに頼っていたこともすべてJavaScriptで実装する必要あり。
- 具体的には、SHA-1ハッシュの生成と、aes_128_cbcによる暗号化も、JavaScriptで実装するのだ。
- しかし、自分にはその知識も技術もないので、ライブラリ的な何かを探す。
- きっと、世界のどこかで、その道の達人が、素晴らしい実装をしてくれているはず。
- JavaScriptはブックマークレットとして保存して活用する。
- マスターパスワードはSHA-1ハッシュにして、ログイン情報はaes_128_cbcで暗号化して、すべてJavaScriptコードに含める。
- 自分が実装するのは、マスターパスワード認証と、復号したログイン情報をフォームに入力する部分。
- 自動入力以外の処理は、MacBookのauto_login環境(AppleScriptのスクリプト)を利用する。
- MacBookのauto_login環境のマスターパスワード、ログイン情報を利用して、JavaScriptコードを生成する。
- 生成したJavaScriptコードをSafariでブックマークして、ブックマークレットとして活用する。
利用させて頂いたコード
以下の素晴らしいコードを使わせて頂きました。感謝です!
- AES 128ビット暗号化・復号化
上記JavaScriptコードをClosure Compiler Serviceを利用してコード圧縮して、AppleScriptに取り込んだ。
property aes_code : "var Aes={};Aes.cipher=function(b,e){for(var a=e.length/4-1,d=[[],[],[],[]],c=0;c<16;c++)d[c%4][Math.floor(c/4)]=b[c];d=Aes.addRoundKey(d,e,0,4);for(c=1;c<a;c++)d=Aes.subBytes(d,4),d=Aes.shiftRows(d,4),d=Aes.mixColumns(d,4),d=Aes.addRoundKey(d,e,c,4);d=Aes.subBytes(d,4);d=Aes.shiftRows(d,4);d=Aes.addRoundKey(d,e,a,4);a=Array(16);for(c=0;c<16;c++)a[c]=d[c%4][Math.floor(c/4)];return a};Aes.keyExpansion=function(b){for(var e=b.length/4,a=e+6,d=Array(4*(a+1)),c=Array(4),f=0;f<e;f++)d[f]=[b[4*f],b[4*f+1],b[4*f+2],b[4*f+3]];for(f=e;f<4*(a+1);f++){d[f]=Array(4);for(b=0;b<4;b++)c[b]=d[f-1][b];if(f%e==0){c=Aes.subWord(Aes.rotWord(c));for(b=0;b<4;b++)c[b]^=Aes.rCon[f/e][b]}else e>6&&f%e==4&&(c=Aes.subWord(c));for(b=0;b<4;b++)d[f][b]=d[f-e][b]^c[b]}return d};Aes.subBytes=function(b,e){for(var a=0;a<4;a++)for(var d=0;d<e;d++)b[a][d]=Aes.sBox[b[a][d]];return b};Aes.shiftRows=function(b,e){for(var a=Array(4),d=1;d<4;d++){for(var c=0;c<4;c++)a[c]=b[d][(c+d)%e];for(c=0;c<4;c++)b[d][c]=a[c]}return b};Aes.mixColumns=function(b){for(var e=0;e<4;e++){for(var a=Array(4),d=Array(4),c=0;c<4;c++)a[c]=b[c][e],d[c]=b[c][e]&128?b[c][e]<<1^283:b[c][e]<<1;b[0][e]=d[0]^a[1]^d[1]^a[2]^a[3];b[1][e]=a[0]^d[1]^a[2]^d[2]^a[3];b[2][e]=a[0]^a[1]^d[2]^a[3]^d[3];b[3][e]=a[0]^d[0]^a[1]^a[2]^d[3]}return b};Aes.addRoundKey=function(b,e,a,d){for(var c=0;c<4;c++)for(var f=0;f<d;f++)b[c][f]^=e[a*4+f][c];return b};Aes.subWord=function(b){for(var e=0;e<4;e++)b[e]=Aes.sBox[b[e]];return b};Aes.rotWord=function(b){for(var e=b[0],a=0;a<3;a++)b[a]=b[a+1];b[3]=e;return b};Aes.sBox=[99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22];Aes.rCon=[[0,0,0,0],[1,0,0,0],[2,0,0,0],[4,0,0,0],[8,0,0,0],[16,0,0,0],[32,0,0,0],[64,0,0,0],[128,0,0,0],[27,0,0,0],[54,0,0,0]];Aes.Ctr={};Aes.Ctr.encrypt=function(b,e,a){if(!(a==128||a==192||a==256))return'';for(var b=Utf8.encode(b),e=Utf8.encode(e),d=a/8,c=Array(d),a=0;a<d;a++)c[a]=isNaN(e.charCodeAt(a))?0:e.charCodeAt(a);for(var c=Aes.cipher(c,Aes.keyExpansion(c)),c=c.concat(c.slice(0,d-16)),e=Array(16),a=(new Date).getTime(),d=a%1E3,f=Math.floor(a/1E3),i=Math.floor(Math.random()*65535),a=0;a<2;a++)e[a]=d>>>a*8&255;for(a=0;a<2;a++)e[a+2]=i>>>a*8&255;for(a=0;a<4;a++)e[a+4]=f>>>a*8&255;d='';for(a=0;a<8;a++)d+=String.fromCharCode(e[a]);for(var c=Aes.keyExpansion(c),f=Math.ceil(b.length/16),i=Array(f),j=0;j<f;j++){for(a=0;a<4;a++)e[15-a]=j>>>a*8&255;for(a=0;a<4;a++)e[15-a-4]=j/4294967296>>>a*8;for(var g=Aes.cipher(e,c),k=j<f-1?16:(b.length-1)%16+1,h=Array(k),a=0;a<k;a++)h[a]=g[a]^b.charCodeAt(j*16+a),h[a]=String.fromCharCode(h[a]);i[j]=h.join('')}b=d+i.join('');return b=Base64.encode(b)};Aes.Ctr.decrypt=function(b,e,a){if(!(a==128||a==192||a==256))return'';for(var b=Base64.decode(b),e=Utf8.encode(e),d=a/8,c=Array(d),a=0;a<d;a++)c[a]=isNaN(e.charCodeAt(a))?0:e.charCodeAt(a);c=Aes.cipher(c,Aes.keyExpansion(c));c=c.concat(c.slice(0,d-16));e=Array(8);ctrTxt=b.slice(0,8);for(a=0;a<8;a++)e[a]=ctrTxt.charCodeAt(a);for(var d=Aes.keyExpansion(c),c=Math.ceil((b.length-8)/16),a=Array(c),f=0;f<c;f++)a[f]=b.slice(8+f*16,f*16+24);for(var b=a,i=Array(b.length),f=0;f<c;f++){for(a=0;a<4;a++)e[15-a]=f>>>a*8&255;for(a=0;a<4;a++)e[15-a-4]=(f+1)/4294967296-1>>>a*8&255;for(var j=Aes.cipher(e,d),g=Array(b[f].length),a=0;a<b[f].length;a++)g[a]=j[a]^b[f].charCodeAt(a),g[a]=String.fromCharCode(g[a]);i[f]=g.join('')}b=i.join('');return b=Utf8.decode(b)};var Base64={code:'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',encode:function(b,e){var a,d,c,f,i=[],j='',g,k,h=Base64.code;k=(typeof e=='undefined'?0:e)?b.encodeUTF8():b;g=k.length%3;if(g>0)for(;g++<3;)j+='=',k+='\\x00';for(g=0;g<k.length;g+=3)a=k.charCodeAt(g),d=k.charCodeAt(g+1),c=k.charCodeAt(g+2),f=a<<16|d<<8|c,a=f>>18&63,d=f>>12&63,c=f>>6&63,f&=63,i[g/3]=h.charAt(a)+h.charAt(d)+h.charAt(c)+h.charAt(f);i=i.join('');return i=i.slice(0,i.length-j.length)+j},decode:function(b,e){var e=typeof e=='undefined'?!1:e,a,d,c,f,i,j=[],g,k=Base64.code;g=e?b.decodeUTF8():b;for(var h=0;h<g.length;h+=4)a=k.indexOf(g.charAt(h)),d=k.indexOf(g.charAt(h+1)),f=k.indexOf(g.charAt(h+2)),i=k.indexOf(g.charAt(h+3)),c=a<<18|d<<12|f<<6|i,a=c>>>16&255,d=c>>>8&255,c&=255,j[h/4]=String.fromCharCode(a,d,c),i==64&&(j[h/4]=String.fromCharCode(a,d)),f==64&&(j[h/4]=String.fromCharCode(a));f=j.join('');return e?f.decodeUTF8():f}},Utf8={encode:function(b){b=b.replace(/[\\u0080-\\u07ff]/g,function(b){b=b.charCodeAt(0);return String.fromCharCode(192|b>>6,128|b&63)});return b=b.replace(/[\\u0800-\\uffff]/g,function(b){b=b.charCodeAt(0);return String.fromCharCode(224|b>>12,128|b>>6&63,128|b&63)})},decode:function(b){b=b.replace(/[\\u00e0-\\u00ef][\\u0080-\\u00bf][\\u0080-\\u00bf]/g,function(b){b=(b.charCodeAt(0)&15)<<12|(b.charCodeAt(1)&63)<<6|b.charCodeAt(2)&63;return String.fromCharCode(b)});return b=b.replace(/[\\u00c0-\\u00df][\\u0080-\\u00bf]/g,function(b){b=(b.charCodeAt(0)&31)<<6|b.charCodeAt(1)&63;return String.fromCharCode(b)})}};"
property sha1_code : "sha1=new function(){var l=[1732584193,4023233417,2562383102,271733878,3285377520],k=l.length;this.hex=function(c){var c=g(c),a,e='';for(a=0;a<c.length;a++)e+=(c[a]>15?'':'0')+c[a].toString(16);return e};this.dec=function(c){return g(c)};this.bin=function(c){var c=g(c),a,e='';for(a in c)e+=String.fromCharCode(c[a]);return e};var g=function(c){var o;var a=[];if(c&&c.constructor===[].constructor)a=c;else if(typeof c=='string'){var e,f,b=[];for(e=a=0;a<c.length;a++)f=c.charCodeAt(a),f<=255?b[e++]=f:(b[e++]=f>>>8,b[e++]=f&255);a=b}c=a;e=a=c.length;for(c[e++]=128;e%64!=56;)c[e++]=0;a*=8;o=a=c.concat(0,0,0,0,m([a])),c=o;a=[];e=[];for(var h,d=[],b=0;b<k;b++)a[b]=l[b];for(f=0;f<c.length;f+=64){for(b=0;b<k;b++)e[b]=a[b];b=c.slice(f,f+64);h=[];for(var g=d=void 0,g=d=0;d<b.length;d+=4,g++)h[g]=b[d]<<24|b[d+1]<<16|b[d+2]<<8|b[d+3];d=h;for(b=16;b<80;b++)d[b]=(d[b-3]^d[b-8]^d[b-14]^d[b-16])<<1|(d[b-3]^d[b-8]^d[b-14]^d[b-16])>>>31;for(b=0;b<80;b++)h=b<20?(a[1]&a[2]^~a[1]&a[3])+j[0]:b<40?(a[1]^a[2]^a[3])+j[1]:b<60?(a[1]&a[2]^a[1]&a[3]^a[2]&a[3])+j[2]:(a[1]^a[2]^a[3])+j[3],h+=(a[0]<<5|a[0]>>>27)+d[b]+a[4],a[4]=a[3],a[3]=a[2],a[2]=a[1]<<30|a[1]>>>2,a[1]=a[0],a[0]=h;for(b=0;b<k;b++)a[b]+=e[b]}return m(a)},m=function(c){var a=[];for(n=i=0;i<c.length;i++)a[n++]=c[i]>>>24&255,a[n++]=c[i]>>>16&255,a[n++]=c[i]>>>8&255,a[n++]=c[i]&255;return a},j=[1518500249,1859775393,2400959708,3395469782]};"
JavaScriptコードを生成する
- MacBookの自動入力環境auto_loginを使って、自動ログイン.scptと同様の動作をするJavaScriptコードを生成する。
set BS to load script POSIX file ((do shell script "dirname " & quoted form of ((path to me)'s POSIX path)) & "/_login_base.scpt")
tell BS
authenticate("自動ログインのブックマークレットを生成します。" & return) try
openssl_decode(MASTER_PASS, login_info_path("/"), login_tmp_path("/")) set current_record to read_file(login_tmp_path("")) end try
remove_file(login_tmp_path("/")) set json_text to json_from(text_from(current_record)) end tell
--ライブラリ関数的コード
aes_code & sha1_code
--マスターパスワード認証
result & "var passwd_sha1=" & quoted form of sha1(MASTER_PASS) & ";"
result & "var passwd=prompt('自動ログインのマスターパスワードを入力してください。','');"
result & "if(sha1.hex(passwd) != passwd_sha1){alert('パスワードが一致しません。');}"
--aes_128暗号化したログイン情報を設定
result & "var json_text_cipher=" & quoted form of encode_aes_128(json_text, MASTER_PASS) & ";"
--ログイン情報を復号化
result & "var json_text=" & "Aes.Ctr.decrypt(json_text_cipher, passwd, 128);"
result & "var json=eval('('+json_text+')');"
--URLキーを取得する
result & "var url_key=document.location.protocol.replace(/[^A-Za-z]/g,'_')+'__'+document.location.hostname.replace(/[^A-Za-z]/g,'_')+document.location.pathname.replace(/\\W/g,'_');"
--URLキーからログイン情報を取得する
result & "var j=json[url_key];"
--ログイン情報を自動入力する
result & "for(i=0;i<j.length;i++){if(j[i].type=='text'||j[i].type=='password'){document.getElementsByName(j[i].name)[0].value=j[i].value}else if(j[i].type=='radio'||j[i].type=='checkbox'){document.getElementsByTagName('input')[j[i].index].checked=j[i].checked}else if(j[i].type=='option'){if(document.getElementsByTagName('option')[j[i].index].value==j[i].value){document.getElementsByTagName('option')[j[i].index].selected=j[i].selected}}}"
result & "alert('入力完了');"
--エスケープ処理(encodeURIではエラーになってしまう)
BS's replace(result, "%", "%25")
--生成したJavaScriptコードをクリップボードに保存する
set the clipboard to bookmarklet(result)
"クリップボードにJavaScriptをコピーしました。\nブックマークレットとして、保存してください。"
display dialog result buttons {"OK"} default button "OK" giving up after 5
on sha1(str) tell application "Safari"
do JavaScript sha1_code & "sha1.hex(" & quoted form of str & ");" in document 1
end tell
end sha1
on encode_aes_128(plain_text, passwd) tell application "Safari"
do JavaScript aes_code & "Aes.Ctr.encrypt(\"" & my escape_dbqt(plain_text) & "\", " & quoted form of passwd & ", 128);" in document 1
end tell
end encode_aes_128
on decode_aes_128(cipher_text, passwd) tell application "Safari"
do JavaScript aes_code & "Aes.Ctr.decrypt(\"" & cipher_text & "\", " & quoted form of passwd & ", 128);" in document 1
end tell
end decode_aes_128
on escape_dbqt(str) do shell script "echo " & quoted form of str & "|sed -e 's/\\\"/\\\\\\\"/g'"
end escape_dbqt
on bookmarklet(str) "javascript:" & "(function(){" & str & "})()"
end bookmarklet
on encode_uri(str) tell application "Safari"
do JavaScript "encodeURI(\"" & str & "\")" in document 1
end tell
end encode_uri
ブックマークレットを登録する
- 上記スクリプトを実行すると、JavaScriptコードがクリップボードにコピーされる。
- ブックマークを何か一つ追加して、ブックマーク管理ページからアドレス欄にペーストして、ブックマークレットとする。
auto_loginのダウンロード
- ブックマークレット作成機能の追加にあたり、従来のコードも整合性を合わせるため若干修正した。
- 最新のauto_loginをダウンロードする。
- 以前のログイン情報は auto_login/_login.info に保存されているので、ファイルをコピーすると引き継げる。
- マスターパスワードも、以前と同じに設定する必要あり。
スクリプトの役割
- _で始まるファイル名は、通常は触らないファイル。(auto_login内部、あるいは開発で利用している)
- 日本語名のファイルを実行して、各種操作を行う。
- _edit_login_info.scpt
- DB編集スクリプト生成.scptが利用する雛形。ロックされたファイル。
- _login_base.scpt
- 各スクリプトから参照する共通のコードライブラリ。
- _login.info
- ログイン情報(Webページのフォームの内容)を暗号化して保存している。
- _show_login_info.scpt
- ログイン情報_login.infoの内容をレコード形式で表示する。
- パスワードリセット.scpt
- パスワードの変更を行う。
- ブックマークレット作成.scpt
- 自動入力するJavaScriptコードをクリップボードにコピーする。
- ログイン情報取得.scpt
- webページからフォームの内容を取り込み、保存する。
- 自動ログイン.scpt
- 自動入力を実行する。
iPhone・iPad専用ページで自動入力するには?
- 例えば、三井住友ダイレクトのページでは、URLが以下のように変化する。
- その問題を解決するには、DB編集スクリプト生成で、ログイン情報の編集を行う。
- DB編集スクリプト生成を実行すると、現在のログイン情報をlogin_info_data()に持ったスクリプト編集プログラムが起動する。
- レコードの中から、対応するURLキーを見つけて、その部分をコピーして追加する。
- そして、一方の..._aibgsjsw5001_jspの部分を..._aibgsjsw1001_jspに変更する。
- 太字の部分が追加・変更したレコード。
on login_info_data()
{https___direct_smbc_co_jp_aib_aibgsjsw1001_jsp:{{checked:false, value:"01234", type:"text", |name|:"USRID1", |index|:"12"}, {checked:false, value:"56789", type:"text", |name|:"USRID2", |index|:"13"}, {checked:false, value:"1234", type:"password", |name|:"PASSWORD", |index|:"14"}, {checked:false, value:"ログイン", type:"submit", |name|:"bLogon.y", |index|:"21"}}, https___direct_smbc_co_jp_aib_aibgsjsw5001_jsp:{{checked:false, value:"01234", type:"text", |name|:"USRID1", |index|:"12"}, {checked:false, value:"56789", type:"text", |name|:"USRID2", |index|:"13"}, {checked:false, value:"1234", type:"password", |name|:"PASSWORD", |index|:"14"}, {checked:false, value:"ログイン", type:"submit", |name|:"bLogon.y", |index|:"21"}}}
end login_info_data
- この状態でAppleScriptエディタの実行ボタンを押すと、
- 確認されるので、OKボタンを押すと、変更した内容で書き込まれる。
これでiPhoneでもログインできるようになった!
- AppleScriptエディタには、重要なログインパスワードが単純なテキストで表示されている。
- セキュリティ確保のため、くれぐれも保存しないで終了するべき。