iPhone・iPadのSafariでもあらゆるページで自動入力したい

前回までにMacBookにおいては、手軽に、安全で、確実なSafariの自動入力環境になった。

しかし、本来はiPhoneiPadでこそ、自動入力したいのではないか?MacBookならキーチェーン.appの自分のメモを見れば、どのページであっても手入力して、どうにかログインはできる。ところが、iPhoneiPadにはキーチェーン.appがないので、ID・パスワードを確認する術がないのだ。

出先でちょこっと金融機関のページにログインしたいと思っても、自動入力できず、ID・パスワードもあやふやでログインできず、諦めた経験はないだろうか。結局、家に帰って、MacBookの前に座って、ようやくログイン。そんな経験が自分には少なからずある。また、iPhoneの小さなソフトウェアキーボードで入力するのも面倒くさい。

自動入力は、iPhoneiPadで使えてこそ、その真価を発揮するのだ。但し、ロック解除の動作中に無制限に自動入力されてしまうのは問題である。また、ID・パスワードを単純なテキストとして保存しておくのも問題である。例えiPhoneを落としたとしても、そして悪意のある第三者に拾われたとしても、自動入力やファイル解析で簡単にアクセスされないセキュリティも必要である。

そんなiPhoneiPadの自動入力環境を目指して、いろいろやってみた。

プラットホームの違い

MacBookでは...
iPhoneiPadでは...

方針

  • つまり、自分にとっての選択肢はJavaScriptのみ。
  • 前回AppleScriptで作った自動入力環境を、今度はJavaScriptで実装すれば良いはず。
  • 但し、JavaScriptはブラウザの中の世界しかアクセスできないと思っているので、opensslコマンドに頼っていたこともすべてJavaScriptで実装する必要あり。
  • 具体的には、SHA-1ハッシュの生成と、aes_128_cbcによる暗号化も、JavaScriptで実装するのだ。
  • しかし、自分にはその知識も技術もないので、ライブラリ的な何かを探す。
    • きっと、世界のどこかで、その道の達人が、素晴らしい実装をしてくれているはず。

利用させて頂いたコード

以下の素晴らしいコードを使わせて頂きました。感謝です!


上記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

ブックマークレットを登録する

  • Safariのブックマークは、iPhoneiPadと同期する設定にしておく。
  • iTunesとの接続による同期、あるいはMobileMeによる同期を実行。

ブックマークレットを使ってみる

  • ちなみに、prompt()関数を使っているので、入力中の文字はしっかり見える。(黒丸にならない)


見事に自動入力された!

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
    • webページからフォームの内容を取り込み、保存する。
  • 自動ログイン.scpt
    • 自動入力を実行する。

iPhoneiPad専用ページで自動入力するには?

  • 最近はiPhoneiPad専用のページが用意されている場合が多い。
  • iPhoneiPad専用ページのURLは、MacBookとは異なっている。
  • auto_loginでは、ログイン情報の取得は、MacBookしかできない仕様なので、
  • そのままでは、iPhoneiPad専用ページのログインが永遠にできない。
  • その問題を解決するには、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エディタには、重要なログインパスワードが単純なテキストで表示されている。
  • セキュリティ確保のため、くれぐれも保存しないで終了するべき。