セキュリティ対策
セキュリティ
バッファオーバーフロー
クラッカーがサーバを攻撃する場合、サーバ上のプログラムの脆弱性を利用する。
サーバプログラムの脆弱性を利用する最も典型的な攻撃方法には、バッファオーバーフローがある。
こういった問題が含まれるアプリケーションは危険であり、こういった脆弱性が見つかったらプログラムの更新が重要である。
広く利用されているソフトウェアでも日々脆弱性が見つかる。(JVNなどのサイトでは脆弱性情報を収集・公開してくれている)
マルウェア
ウィルスとは、パソコン利用者の上で動く、感染能力のある悪意のあるプログラム。機械語で書かれたものや、オフィスソフトのマクロ機能で動くものもある。パソコン内の情報を利用して、ウィルス付きメールを自動的に送ることが多い。(メールソフトを使うなど、人の操作が必要なもの)
ウィルスは元々、愉快犯によるものが一般的であったが、感染したパソコンのファイルを暗号化し、暗号化を復元するために、ネットバンキングへのお金の振り込みを要求(身代金=ransom)するようなランサムウェアが増えている。
ウォームとは、脆弱性のあるネットワークプログラムに、バッファオーバーフローを引き起こすようなデータを送りつけて、ウィルスを送りつけたり、そのコンピュータを踏み台にしてネットワークを利用した攻撃をさらに行うもの。(ネットワークを介して悪意のあるプログラムを起動させるもの)
通常、インターネットからの攻撃を防ぐために、各組織ではFireWall(後述)を設置している。一方、FireWallの内側では、防御されていることから内部のコンピュータからの攻撃に甘く、無防備であることが多い。そこで、FireWall の内側のコンピュータに、メールなどの添付ファイルでマルウェアを送付・感染させることで、FireWall内で被害が拡大することもある。
このような、FireWall 内部での感染・被害拡大を狙ったマルウェアは、トロイの木馬型と呼ばれる。
ネットワークを介した攻撃では、攻撃対象のコンピュータを乱数で得られたIPアドレスや、そのアドレスを1つづつ増やしながら攻撃を行うことが多い。こういった攻撃は絨毯攻撃と呼ぶ。
ボットとはロボットを略した単語で、ウォームの中で「外部からの命令で動くもの」を指すことが多い。マルウェアのボットの中には感染しても表面上は何もせず、クラッカーの動かすインターネットの掲示板などを監視し、そこに書かれた命令を見て spam 送信や、DoS攻撃を行うものがある。
DoS攻撃(Denial of Service attack) – サーバなどに大量のデータを送りつけたりすることで、サーバがその処理に手間取り、他の利用者のサービスに悪影響を引き起こさせる攻撃。ボットからのDoS攻撃は、インターネットの様々なIPアドレスから攻撃を受けるためFireWallで防ぐことも困難である。分散DoS攻撃(Distributed DoS Attack)
最近では、ウィルスやウォームの区別が難しいため、マルウェアと呼ぶ。
ファイアウォール
サーバで動かしているプログラムにバッファオーバーフローのような不備が残っていて、全世界のどこからでもこういった不備があるプログラムに簡単に接続できたとしたら、極めて危険である。
サーバで動くプログラムは、接続するためのポート番号が決まっているので、相手のコンピュータのIPアドレスが分かったら攻撃を仕掛けてくるかもしれない。
FireWall は、これらの接続をできなくするための方法で、例えば学内のWebサーバへの攻撃を防ぎたいのなら、ルータで「宛先ポート番号が80のパケットは廃棄」といった設定をすればよい。また、危険な攻撃を加えてくるコンピュータのIPアドレスがわかっている場合は、「送信元IPアドレスXX.XX.XX.XXのパケットは廃棄」という設定をすればよい。こういった、ポート番号やIPアドレスを見てパケットを遮断するルータは、FireWall(防火壁)と呼ばれる。
よくある設定であれば、ポート番号23(telnet),137,139(Windows ファイル共有),513(リモートデスクトップ)を禁止など(拒否リスト型/ブラックリスト型)、基本は全面禁止だけどポート番号22(ssh)は許可(許可リスト型/ホワイトリスト型)など。
セキュリティ対策
- OSの更新・インストールアプリケーションの更新
バッファオーバーフローのような脆弱性が無いようにソフトウェアを更新することが重要。
Windows で、インストールされているソフトの更新では、winget が便利!!
- 不審なメールは開かない
添付ファイルにマルウェアがしかけられている可能性。リンクや画像ファイルを開くと、実際に使われているメールアドレスとして迷惑メールが増える可能性がある。 - 危険なWebサイトをアクセスしない
OSやブラウザの脆弱性から、マルウェア被害の可能性。 - パソコンで不要なサービスを動かさない
ファイル共有や、リモート接続のサーバを不用意に動かさない。 - ウィルス対策ソフトをインストール&更新
ウィルス対策ソフトは、新しく発生したマルウェアの命令などのパターンを保存しておき、同じパターンのものをマルウェアとして判定する。- マルウェアは日々新しいものが作られるため、ウィルス対策ソフトのメーカーから、常に新しいマルウェアのパターンをダウンロード&更新が重要。
- OSの脆弱性が見つかった場合、ウィルス対策ソフトのメーカーがマルウェアパターンを登録する前にマルウェアが届く場合がある。ゼロディ攻撃
- 特定の企業を攻撃する場合は、その企業専用のウィルスを作る場合もある。このためマルウェアパターンが無いため、ウィルス感染の可能性がある。標的型攻撃
- 最近では、ブラウザによるWebアクセスからの感染を防ぐために危険なURLへのアクセスを監視したり、危険なIPアドレス・ポート番号へのアクセスを監視する機能も含まれている。パーソナルファイアウォール機能
- このパソコンは重要な情報が入っていないから、ウィルスに感染しても放置するのは危険。他のコンピュータを攻撃する踏み台、DoS攻撃のボット、トロイの木馬となって危険の元となる。
一般的に、Apple社のiPhone iOS では、ウィルス対策ソフトは不要である。これは、App Store でアプリを公開するためには、プログラムのソースコードを提出した上での審査があり、デバイスも、App Store 以外からのアプリをインストールできないため、マルウェアのインストールがほぼ不可能なためである。ただし、欧州連合(EU)では 、EU域内のデジタル市場の公正性を確保し競争力を高めるためのデジタル市場法(DMA)によりに、App Store 以外からのソフトウェアダウンロードが可能。
一方、Google 社の Android は、アプリの審査が甘く、Google Play アプリ以外からのソフトのインストールも可能であり、ウィルス対策ソフトが必要である。
理解度確認
- Formsによる理解度確認テスト
- 標的型攻撃メールがウィルス対策ソフトでは防ぐことが難しい理由を述べよ。
- ファイアウォールでは、どういった処理を行うのか説明せよ。
Javaでラムダ式の呼び出し
Javaでは、Stremクラスでよく使われる関数インタフェースは以下の通り。これらの関数インタフェースを経由してラムダ式を使う必要があるが、クラスの型推論があるため、Predicate などの関数インタフェース名や呼び出しメソッドを自分で書くことはない。
- Predicate<T> – 引数Tでboolean型を返す test(T) -> boolean
- Supplier<R> – 引数なし で R型を返す get() -> R
- Consumer<T> – 引数T で void型 accept(T) -> void
- BiConsumer<T,U> – 引数T,U で void型 accept(T,U) -> void
- Function<T,R> – 引数T で R型を返す apply(T) -> R
- BiFunction<T,U,R> – 引数T,U で R型を返す apply(T,U) -> R
import java.util.*; import java.util.function.Predicate ; import java.util.function.Supplier ; import java.util.function.Consumer ; import java.util.function.BiConsumer ; import java.util.function.Function ; import java.util.function.BiFunction ; public class Main { public static void main( String[] args ) { Predicate<Integer> even = (Integer x) -> { return x % 2 == 0 ; } ; System.out.println( even.test( 10 ) ) ; // ...filter( x -> x % 2 == 0 )... Supplier<String> greet = () -> "Hello" ; System.out.println( greet.get() ) ; // 1引数の Consumer<T> f = (T t) -> 式 ; // Consumer は accept で呼び出す Consumer<Integer> foo = (Integer x) -> System.out.println( x ) ; foo.accept( 10 ) ; // ...forEach( x -> System.out.println( x ) ) ; // 2引数の BiConsumer<T,U> f = (T t , U u) -> 式 BiConsumer<Integer,Integer> bar = (Integer x , Integer y) -> System.out.println( x * y ) ; bar.accept( 10 , 20 ) ; // 1引数で値を返す Function<T,R> f = (T t) -> { return 式 } // Function は apply で呼び出す Function<Integer,Double> baz = (Integer x) -> Math.sqrt( x ) ; System.out.println( baz.apply( 5 ) ) ; // ...map( x -> x*x )... // 2引数で値を返す BiFunction BiFunction<Integer,Integer,Double> piyo = (Integer x , Integer y) -> Math.sqrt( x * y ) ; System.out.println( piyo.apply( 5 , 10 ) ) ; } }
レポート課題(後期期末) ハッシュ法
課題内容
以下の内容の中から1つを選びハッシュ法でデータを登録・検索するプログラムを作成せよ。
- 名前と電話番号 – 名前で検索、電話番号で検索などができること。
- 名前とメールアドレス – メールアドレスで検索し、名前を表示… などができること。
- 名前と生年月日 – 名前で誕生日を検索などができること。
ハッシュ衝突が発生しているかどうかを確認し、ハッシュ関数を変更することでハッシュ衝突がどうなるかの違いなどを検証すること。
レポート提出先
暗号化とパスワード
前回授業で説明の足りなかったクラウドの形態とパスワード対策などの説明の後、公開鍵暗号などの説明予定。
暗号化
有線LANで1本のケーブルを共有したり無線でデータをやりとりする場合、通信の盗聴が行われると危険である。
前回の授業で紹介したように、簡単な置換式暗号などでは暗号の解読ができてしまう。
暗号化アルゴリズム
1980年頃には DES(Data Encryption Standard) がアメリカでの標準的な暗号化として使われていたが、コンピュータの性能があがると共に解読される危険性が高まってきた。そこで2000年頃にはAES(Advanced Encryption Standard) が使われるようになった。どちらも共通鍵を用いてデータをブロック(固定長のデータ)単位で暗号化する共通鍵ブロック暗号方式であり、暗号の鍵の長いものは暗号解読が困難となっている。
1980年頃に開発された RSA 暗号(開発者の名前より) は、巨大な数字の素因数分解が困難なことを利用した、公開鍵暗号方式の1つである。
最近、量子コンピュータを用いた暗号解析が話題となっている。量子力学の原理を計算に応用したコンピュータで、スーパーコンピュータで1万年かかる暗号解読のような処理が200秒で終わってしまうかもしれないと言われている。
公開鍵暗号方式とは…
以前に使われていた暗号化の方式は、暗号化の鍵と復号化の鍵に同じものを用いる共通鍵方式であった。
しかし、この鍵をどうやって相手に渡すか…が問題となっていた。(鍵を相手に渡す瞬間のデータを盗聴されると危険)
このため、最近では公開鍵暗号方式が中心となっている。この方式は「暗号化するため専用の公開鍵」と、「暗号化を復号するための秘密鍵」をペアにして用いる。公開鍵は、暗号化するため専用なので、この鍵が他の人に見られても、暗号を復号することはできない。
公開鍵だけでは成り済ました別人と通信してしまう可能性[1]がある。そこで通信相手が本物かどうかを、認証局とよばれる第三者機関によって証明書で確認する。HTTPを暗号化したHTTPSでは、SSL証明書と呼ばれる。最近のブラウザでは、URLの左側の鍵マーク🔒からSSL証明書を確認することができる。
[1] DNSサーバの脆弱性を利用して、間違ったIPアドレスを教えさせる DNSポイズニング が行われると、利用者を間違ったサーバに接続させることが可能。
パスワード解読方法
ログインなどで使われるパスワードは、どのように破られるのだろうか?
- ブルートフォース攻撃:単純に全ての文字を試す方式。文字の組み合わせ問題なので、パスワード文字列長をNとした場合、数字だけ(10N)とか英字だけ(26N)といった組み合わせでは、短時間に解読されてしまう。数字,大文字,小文字,記号などを交えたパスワードが理想。
- 英単語辞書を用いた辞書攻撃:パスワードが長い場合、文字列の全ての組み合わせを試すには長い時間が必要となる。しかし、パスワードはユーザが記憶して使うことから覚えやすい単語が使われる。このため英単語辞書の文字を組み合わせることで、解読時間を短くできる場合がある。
- 漏えいパスワードによる辞書攻撃:サーバへのリモート接続などができてしまった場合、パスワード情報が盗まれる場合がある。この時、別なサイトに同じパスワードを使っていると、その漏えいしたパスワードで別のサイトも接続ができてしまう。これらのことから、同じパスワードを使いまわすことは避けるべきである。
- ソーシャル攻撃:パスワードには、簡単に覚えられるように自宅の電話番号、誕生日、家族の名前といったものを使う人が多い。このため、SNS で相手に友達登録をしてもうことで、こういった情報を手に入れ、パスワードを破る方法。最近の有名人の個人情報漏洩はこの手の攻撃が多い。
ソーシャル攻撃は、”元クラッカー” ケビン・ミトニックが有名
- パスワードスプレー攻撃:login 画面などにブルートフォース攻撃を加えて簡単にパスワードが破られるのは問題となる。このため、短時間に何度も login 操作をできないように、数回のパスワード入力に失敗すると一定時間 login ができなくなるような対策が取られる。しかし、プログラムを使ってブルートフォース攻撃をするのであれば、攻撃間隔を空ければいい。攻撃者は、その代わりにネットワークの別のコンピュータでブルートフォース攻撃をすればいい。時間当たりの攻撃回数が少ないため、通常ユーザのパスワード間違いと区別ができないので、気づかないうちにパスワード破りに成功するかもしれない。このため、長期間パスワードを変更しないユーザは、不正利用被害が発生する可能性がある。
攻撃が難しいパスワードへ
先に述べたような、login に使うパスワードなどは、ブルートフォース攻撃をうけると解読は時間の問題となる。これらの対策として毎回違う鍵(パスワード)を使えばいい。
- ワンタイムパスワード:使い捨てのパスワードをあらかじめ沢山作っておき、接続の度に次のパスワードを用いる方式。あるいは、時間から特殊な計算方法で生成されるパスワード。時間と共に変化するのでその度毎に違うパスワードとなる。毎回違うパスワードを入力するため、パスワード表を常に持ち歩いたり、入力が面倒なので数字だけを使うことが多く、この方法だけでは使いにくい。(次週に多要素認証などの解説も行う)
多要素認証
パスワードはブルートフォース攻撃をうければ、いつかはパスワードが破られる危険性がある。こういった対策で最も重要な方法が、多要素認証(2段階認証)である。
この方式では、通常のパスワード入力の後に、以下の様な方式でワンタイムパスワードを入力することでログインが可能となる。
- 携帯電話にテキストメッセージ(SMS)でワンタイムパスワードを送る。
- かかってきた電話の機械音声のワンタイムパスワードを伝える。
- 時間で変化するワンタイムパスワード生成アプリの数字を入力する。
- メールで送られてきたワンタイムパスワードを含んだURLにアクセスする。
- 認証画面に表示されたQRコードのURLにアクセスする。
SMSやワンタイムパスワードアプリは、携帯電話などを常に持ち歩いていることが本人確認となっている。このような方式では、携帯電話などを失くすとシステムが使えなくなるので、バックアップコード(非常時用のパスワード)の保存や、login先とは別のメールアドレスを登録してあることが重要となる。
CAPTCHA
最近では、フリーで取得できるメールアドレスをプログラムで動くロボットで自動生成し、そのアカウントを使ってspamを送るなどの手口が問題となっている。このため、接続してきた相手が本当に人間か判定することがある。判定には、読みづらく加工された英字を入力させたり、パズルを解かせるといった方法が使われる。
元々は、読みづらい文字はコンピュータでは画像解析しづらいことから、CAPTCHA が使われるが、最近では機械学習によって解析ができるようになってきたので使われる機会も減ってきている。
セキュリティキー
SMSやメールを使ったワンタイムパスワードによる多要素認証も、間にネットワークを挟むと多要素認証では問題となる。そこで、もっと複雑な暗号で多要素認証を行うために、セキュリティキーが使われる。
多要素認証が必要になると、パソコンにセキュリティキーを差し込むことで認証が行われる。現在では FIDO(Fast IDentity Online)や FIDO2 といった規格のものが普及している。
パスキー
セキュリティキーなどは専用のデバイスを持つ必要があることから、インターネットを介した認証では最近はパスキーが普及してきた。パスキー認証では、公開鍵暗号方式を用いた署名がユーザー側で行われ、秘密鍵をスマートフォンやパソコンの中に保存し、利用するWebサイトには公開鍵を登録しておく。ログイン時には、公開鍵と秘密鍵を用いた検証がウェブサイトやアプリのサーバー側で行われることで認証が完了する。署名時は指紋認証、顔認証、またはデバイスの画面ロック解除でユーザーの当人認証を行う。(Wikipedia)
ハッキング
インターネットの用語でハッカー(Hacker)は、コンピュータを使って悪いことをする人という意味でよく使われている。しかし元々は「主にコンピュータや電気回路一般について常人より深く高度な技術的知識を持ち、その知識を利用して技術的な課題をクリアする人々のこと」(Wikipedia引用)という意味で使われていた。このため本来は「優秀なエンジニアへの最大級の誉め言葉」として使われており、ハッカー以上の技術者を ウィザード/wizard や グル/guru と呼称することもある。
しかしながら一部のハッカーの中で、その技術を悪用する人も出てきたことから、インターネットで悪いことをする人という意味で使われることも増えてきた。そこで、悪いことをする人は別な呼び方をしようということで、攻撃を加えるひとはクラッカーと呼ぶことが多い。最近では、正義のためのハッカー = ホワイト・ハッカー、悪いハッカー = ブラック・ハッカー という表現も使われるが、黒人差別主義につながる用語ということで、正義のためのハッカーには最近はエシカル・ハッカー(高い倫理観と道徳心を兼ね備えている高い技術を持ったハッカー)という言葉を使う。
理解度確認
- Formsによる理解度確認テスト
- 標的型攻撃メールがウィルス対策ソフトでは防ぐことが難しい理由を述べよ。
- ファイアウォールでは、どういった処理を行うのか説明せよ。
NoSQLと Google Firestore
データベースシステムとして、最近は NoSQL (Not Only SQL) が注目されている。この中で、広く使われている物として、Google Firestore などが有名である。教科書以外の最近のデータベースの動向ということで、最後に NoSQL の説明を行う。
RAIDと分散
ハードディスクの故障対策などで、複数の HDD にデータを保存することで、データのアクセス速度や故障時のリカバリ対策をする方式として RAID (Redundant Array of Inexpensive Disks) がある。
RAID0 – データを複数の HDD に分散して保存する方式。読み書き速度の向上のために用いる。
RAID1 – データを複数の HDD に同じように保存する方式。(ミラーリング) 壊れたディスクを交換するだけでリカバリができる。
RAID5 – データを複数の HDD に分散して保存。冗長性のための Parity 情報も分散して保存することで、HDD の 1つが壊れても復帰することができる。分散保存による読み書き速度向上も得られる。
RAID6 – データ誤り補正のデータを複数もたせて、分散保存
RAID0
RAID1
RAID5
リレーショナルデータベースシステムの問題
リレーショナルデータベースのシステムでは、大量の問い合わせに対応する場合、データのマスターとなるプライマリサーバに、そのデータの複製を持つ複数のセカンダリサーバを接続させる方式がとられる。しかしながら、この方式ではセカンダリサーバへのデータ更新を速やかにプライマリサーバに反映させる、さらにその結果が他のセカンダリサーバに反映させる必要があることから、大量のデータに大量の問い合わせがあるようなシステムでは、これらのデータ同期の性能が求められる。しかも、プライマリサーバが故障した場合の復旧なども考えると、こういったプライマリ・セカンダリ・サーバ構成での運用・管理は大変である。
NoSQLの利点
NoSQLのデータベースでは、すべてのデータを複数のサーバ(別のデバイス,ネットワーク)に冗長化したうえで分散して保存する。この際に、どのサーバがプライマリサーバといった概念はない。もし1つのサーバが故障したとしても、分散して保存されたデータから元のデータを自動的に修復できるような構造となっている。
リレーショナルデータベースで大量のユーザからアクセスされる場合、データが安全に取り扱うことができたり、システムに障害が発生した時の対応や、システムのスケーラビリティ(利用状況に応じて処理するプロセッサなどを増やしたり減らしたりする機能)が重要となる。NoSQLのシステムでは、中心となるプライマリサーバを作るのではなく、データを複数のシステムに分散して保存し、障害が発生しても、分散したデータから自動的にデータを修復できるような構成とする。
NoSQLのシステムでは、データ格納形式から、キーバリューストア型、カラムストア型、ドキュメントデータベース、グラフデータベースに分類される。最も代表的なものは、保存するデータ(Value)に対し検索するためのキー(Key)だけの基本的なデータ検索だけを提供する キーバリューストア(Key-Value store)である。こういった構成ではSQLとは違い、複数のテーブルをまたがった検索などができない(サブコレクションなどを使えば代用可能)。
Google の Firestore
NoSQLのデータベースを構築したのは、Google が先駆けであった。現在、このGoogle の NoSQL のシステムは、Firestore として利用されている。(データベースはFireBase)
Firestore は、ドキュメントモデルデータベースの一種であり、すべてのデータはドキュメントとコレクションに保存される。ドキュメントは、データベースでのレコードに相当するが、属性名とそれに対応したデータの JSON オブジェクトである。コレクションは、キーにより対応するドキュメントを取り出せるデータ群である。ドキュメントの中に、サブコレクションを保存することもできる。
リモート接続と暗号化
前回の講義の、サーチエンジンの説明の後、リモート接続と暗号化の説明を行う。
リモート接続
サーバなどの管理をしていると、インターネットの先にあるコンピュータを操作したい場合が多い。こういった場合には、リモート接続機能を用いる。
リモート接続による相手側のコンピュータを操作する場合、相手側のコンピュータには リモート接続 用のサーバプログラムを起動しておく。こういったリモート接続を利用するのは、”unix” の利用者が多いが、”unix” では、サーバ のプログラムは、一般的にデーモン(daemon/守護神)と呼ばれる。[daemonとdemonの違い]
telnet と rlogin
telnet は、最も基本的なリモート接続の方法であり、TCP の 23 番ポートを使う。telnetのサーバ(telnetd – telnet daemon)は、送られてくるタイプされた文字を unix の shell (キーボードでの命令を実行するプログラム) に渡し、shell の実行結果の文字を接続元に送り返す。
telnet のクライアントの基本的動作は、タイプされた文字を送って、受信した文字データを表示するだけなので、通信の動作の確認にもよく使われる。
例えば、Webサーバは、80番ポートに”GET /ページの場所”を送ると、HTMLデータが受信できる。この手順を telnet で行う場合は、以下の様に行う。
rlogin は、TCP の 513 番ポートを使うリモート接続用のソフトで、サーバで rlogind を起動しておく。unix で rlogin クライアントを使うと、リモート側で命令を実行したりファイルをコピーすることができる。
こういったリモート接続ができると、ネットワークの向こう側のコンピュータを自由に操作できる一方で、login のパスワードが破られるとコンピュータを悪用されたり情報を盗まれる可能性がある。
特に、telnet , rlogin では、通信の内容が暗号化されないため、パケット盗聴(後述)されると、サーバを悪用されてしまう。このため telnet や rlogin による遠隔処理は、使うべきではない。
どうしても使うのであれば、ルータや firewall で、ポート番号 23 , 513 などは、遮断し接続するネットワークを限定するのが一般的である。
ssh(secure shell)
暗号化されない rlogin の通信を暗号化により安全に実行できるようにしたものが、ssh (secure shell) である。
ssh は、通常では TCP の 22 番ポートを使う。しかし、暗号化されていたとしてもパスワード破りなどの危険性があるため、ポート番号を変更したり、特定のコンピュータに対してのみ接続許可を与え、安全対策を行う。
リモートデスクトップ
Windows では、コンピュータの操作では、マウス操作が中心(GUI: Graphical User Interface)となる。これに比べ、telnet,rlogin,ssh などの方法では、キーボードによる操作が中心(CUI: Character User Interface)であり、初心者には難しい。遠隔地のコンピュータの操作においてマウス操作などが必要であれば、リモートデスクトップ(remote desktop)が用いられる。
remote desktop では、サーバのディスプレイ画面の情報をクライアントに送り、クライアントの操作(キーボード入力やマウス操作)がサーバに送られ、サーバのコンピュータを自由に操作ができる。
VPN
VPN(Virtual Private Network)とは、物理的に離れた場所にある拠点間を暗号化通信をつかって、仮想的に同じネットワーク内で繋がっているような安全なデータ通信を作るもの。「仮想プライベート・ネットワーク」と呼ばれ、ルータにVPN機能が内蔵されていたり、パソコンではVPNに接続するためのソフトウェアが内蔵されている。VPNで利用されるプロトコルには、SSH/TLS(SSL)/IPsec/PPTP/L2TP/L2F/MPLSなどの種類がある。
[Wikipediaより引用]
バックドア
ssh, リモートデスクトップ, VPN といったリモート接続は、遠隔地のコンピュータを自由に操作できることから、様々なコンピュータを管理している場合、広く使われている。しかしながら、クラッキングなどの悪用の危険があるため、sshサーバ、リモートデスクトップサーバなどのソフトは、通常利用者は起動しないこと。
クラッキングなどを行う場合、ウィルスを使ってリモート接続のためのソフトを動かされると、相手のコンピュータを自由に使える。このような、本来の使い方ではない侵入経路は、バックドアなどと呼ばれる。
クラウド・コンピュータ
インターネットのサービスを構築する時には、自分のコンピュータをインターネットからアクセスさせる(オンプレミス)のではなく、企業などがリモート接続機能を使って、貸し出し用に提供されているコンピュータを利用する場合も多い。こういうコンピュータを利用してサービスを提供する場合、利用者にしてみればどこにあるかよくわからないコンピュータ資源を使うことから、クラウド・コンピューティングと呼ばれる。有名なクラウド サービスとしては、Amazon AWS, Microsoft Azure, Google Cloud がある。
[Wikipediaより引用]
クラウドコンピュータを借りる場合、以下のような形態がある。
- SaaS (Software as a Service) : リモートサーバ上でアプリケーションを事業者側が用意してあるものを借りる
- PaaS (Platform as a Service) : 事業者側が準備した Web サーバなどを借りて、サービス提供者がソフトウェアなどを準備してサービスを提供する
- IaaS (Infrastucture as a Service) : 事業者側がコンピュータやネットワークなどの提供する環境(インフラ)を借りて、その上に利用者が OS, Webサーバ(ミドルウェア), アプリケーションを準備してサービスを提供する
形態 | コンピュータ, ネットワーク |
OS (Linux,Windows…) |
ミドルウェア (Web,DB…) |
アプリケーション | ユーザ |
---|---|---|---|---|---|
SaaS | クラウド事業者が提供 | ||||
PaaS | クラウド事業者が提供 | サービス提供者が準備 | |||
IaaS | クラウド事業者が提供 | サービス提供者が準備 |
暗号化
Ethernet では1本の通信線を共有したり、WiFiのような無線通信では、通信データの盗聴が簡単にできてしまう。クラッカーは、通信データの中から”login, password” といった文字を検索し、その近辺の文字を探すことでパスワードを盗み出す。
このようなことを防ぐために通信データの暗号化は重要な方法である。
暗号化アルゴリズム
暗号化の最も原始的な方法が、置換式 と呼ばれる方法で、特定の文字を別な文字に変更する。rot13は、A→N,B→Oに置き換える暗号。コナン・ドイル原作のシャーロック・ホームズに出てくる踊る人形などもこれに相当する。これらの方法では、アルファベットの文字の出現頻度から元の文を想像することで解読されてしまう。
エニグマ(Enigma)は、第2次世界大戦でナチス・ドイツが用いたロータ式暗号機であり、置換式の解読方法が不可能であった。しかし、イギリスのアラン・チューリングが電気式の解読器(ボンブ)を開発することで暗号解読が可能となった。この解読器が現在のコンピュータの原型となっている。
チューリングによる暗号解読は、映画「イミテーションゲーム」を参照。
最近では、様々な暗号化アルゴリズムが開発されており、古くは “DES, AES“といったアルゴリズムが使われていたが、コンピュータの性能の向上と共に、解読に必要な時間が短くなったことから、RSA といった新しい暗号化方式が考えられ、さらに暗号化の鍵を長くすることで解読に要する時間を長くするようになっている。
(暗号化アルゴリズムについては、次週の講義でもう少し詳しく解説する。)
パスワード解読方法
ログインなどで使われるパスワードは、どのように破られるのだろうか?
- ブルートフォース攻撃:単純に全ての文字を試す方式。文字の組み合わせ問題なので、パスワード文字列長をNとした場合、数字だけ(10N)とか英字だけ(26N)といった組み合わせでは、短時間に解読されてしまう。数字,大文字,小文字,記号などを交えたパスワードが理想。
- 英単語辞書を用いた辞書攻撃:パスワードが長い場合、文字列の全ての組み合わせを試すには長い時間が必要となる。しかし、パスワードはユーザが記憶して使うことから覚えやすい単語が使われる。このため英単語辞書の文字を組み合わせることで、解読時間を短くできる場合がある。
- 漏えいパスワードによる辞書攻撃:サーバへのリモート接続などができてしまった場合、パスワード情報が盗まれる場合がある。この時、別なサイトに同じパスワードを使っていると、その漏えいしたパスワードで別のサイトも接続ができてしまう。これらのことから、同じパスワードを使いまわすことは避けるべきである。
- ソーシャル攻撃:パスワードには、簡単に覚えられるように自宅の電話番号、誕生日、家族の名前といったものを使う人が多い。このため、SNS で相手に友達登録をしてもうことで、こういった情報を手に入れ、パスワードを破る方法。最近の有名人の個人情報漏洩はこの手の攻撃が多い。
ソーシャル攻撃は、”元クラッカー” ケビン・ミトニックが有名
攻撃が難しい暗号化へ
先に述べたような、login に使うパスワードなどは、ブルートフォース攻撃をうけると解読は時間の問題となる。これらの対策として毎回違う鍵(パスワード)を使えばいい。
-
- 暗号表:置換式で読み取られるのを防ぐために、置換する文字の表を沢山作っておき、別の方法でその度毎に置換表を変更する
- ワンタイムパスワード:使い捨てのパスワードをあらかじめ沢山作っておき、接続の度に次のパスワードを用いる方式。あるいは、時間から特殊な計算方法で生成されるパスワード。時間と共に変化するのでその度毎に違うパスワードとなる。毎回違うパスワードを入力するため、パスワード表を常に持ち歩いたり、入力が面倒なので数字だけを使うことが多く、この方法だけでは使いにくい。(次週に多要素認証などの解説も行う)
理解度確認
- ファイアウォールの仕組みを説明せよ。
- つぎの利用形態は、PaaS, SaaS, IaaS のどれにあたるか?
- Microsoft が準備した Teams を使って、授業アンケートを取る。
- 福井高専は、Microsoft Azure 環境の上に、Linux, Webサーバ, PHP, MySQL, WordPress をインストールして HP を運営。
関数ポインタとラムダ式
関数ポインタとコールバック関数
JavaScript のプログラムで、以下のようなコーディングがよく使われる。このプログラムでは、3と4を加えた結果が出てくるが、関数の引数の中に関数宣言で使われるfunctionキーワードが出てきているが、この意味を正しく理解しているだろうか?
このような (function()…)は、無名関数と呼ばれている。(=>を使った書き方はアロー関数と呼ばれている) これは「関数を引数として渡す機能」と、「一度しか使わないような関数にいちいち名前を付けないで関数を使うための機能」であり、このような機能は、関数を引数で渡す機能はC言語では関数ポインタと呼ばれたり、新しいプログラム言語では一般的にラムダ式などと呼ばれる。
// JavaScriptの無名関数の例 3+4=7 を表示 console.log( (function( x , y ) { return x + y ; })( 3 , 4 ) ) ; // 無名関数 console.log( ((x,y) => { return x + y ; })( 3 , 4 ) ) ; // アロー関数(ラムダ式)
C言語の関数ポインタの仕組みを理解するために、以下のプログラムを示す。
int add( int x , int y ) { return x + y ; } int mul( int x , int y ) { return x * y ; } void main() { int (*f)( int , int ) ; // fは2つのintを引数とする関数へのポインタ f = add ; // f = add( ... ) ; ではないことに注意 printf( "%d¥n" , (*f)( 3 , 4 ) ) ; // 3+4=7 // f( 3 , 4 ) と書いてもいい f = mul ; printf( "%d¥n" , (*f)( 3 , 4 ) ) ; // 3*4=12 }
このプログラムでは、関数ポインタの変数 f を定義している。「 int (*f)( int , int ) ; 」 は、“int型の引数を2つ持つ、返り値がint型の関数”へのポインタであり、「 f = add ; 」では、f に加算する関数addを覚えている。add に実引数を渡す()がないことに注目。C言語であれば、関数ポインタ変数 f には、関数 add の機械語の先頭番地が代入される。
そして、「 (*f)( 3 , 4 ) ; 」により、実引数を3,4にて f の指し示す add を呼び出し、7 が答えとして求まる。
こういう、関数に「自分で作った関数ポインタ」を渡し、その相手側の関数の中で自分で作った関数を呼び出してもらうテクニックは、コールバックとも呼ばれる。コールバック関数を使うC言語の関数で分かり易い物は、クイックソートを行う qsort() 関数だろう。qsort 関数は、引数にデータを比較するための関数を渡すことで、様々な型のデータの並び替えができる。
#include <stdio.h> #include <stdlib.h> // 整数を比較するコールバック関数 int cmp_int( int* a , int* b ) { return *a - *b ; } // 実数を比較するコールバック関数 int cmp_double( double* a , double* b ) { double ans = *a - *b ; if ( ans == 0.0 ) return 0 ; else if ( ans > 0.0 ) return 1 ; else return -1 ; } // ソート対象の配列 int array_int[ 5 ] = { 123 , 23 , 45 , 11 , 53 } ; double array_double[ 4 ] = { 1.23 , 12.3 , 32.1 , 3.21 } ; void main() { // 整数配列をソート qsort( array_int , 5 , sizeof( int ) , (int(*)(const void*,const void*))cmp_int ) ; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~この分かりにくい型キャストが必要なのがC言語の面倒な所 for( int i = 0 ; i < 5 ; i++ ) printf( "%d\n" , array_int[ i ] ) ; // 実数配列をソート qsort( array_double , 4 , sizeof( double ) , (int(*)(const void*,const void*))cmp_double ) ; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for( int i = 0 ; i < 5 ; i++ ) printf( "%f\n" , array_double[ i ] ) ; }
無名関数
コールバック関数を使っていると、データを比較するだけの関数とか簡単な短い処理が使われることが多い。こういった処理を実際に使われる処理と離れた別の場所に記述すると、プログラムが読みづらくなる。この場合には、その場で関数の名前を持たない関数(無名関数)を使用する。(C++の無名関数機能は、最近のC++の文法なのでテストには出さない)
void main() { int (*f)( int , int ) ; // fは2つのintを引数とする関数へのポインタ f = []( int x , int y ) { return x + y ; } ; // add を無名関数化 printf( "%d¥n" , (*f)( 3 , 4 ) ) ; // 3+4=7 // mul を無名関数にしてすぐに呼び出す3*4=12 printf( "%d¥n" , []( int x , int y ) { return x * y ; }( 3 , 4 ) ) ; // メモ:C++11では、ラムダ式=関数オブジェクト // C++14以降は、変数キャプチャなどの機能が追加されている。 }
C++の変数キャプチャとJavaScriptのクロージャ
JavaScript のクロージャ
JavaScriptにおいて、関数オブジェクトの中で、その周囲(レキシカル環境)の局所変数を参照できる機能をクロージャと呼ぶ。クロージャを使うことでグローバルな変数や関数の多用を押さえ、カプセル化ができることから、保守性が高まる。
// JavaScriptにおけるクロージャ function foo() { let a = 12 ; // 局所変数 console.log( (function( x , y ) { return a + x + y ; // 無名関数の外側の局所変数aを参照できる })( 3 , 4 ) ) ; } foo() ;C++の変数キャプチャ
C++でも無名関数などでクロージャと同様の処理を書くことができるようにするために変数キャプチャという機能がC++14以降で使うことができる。
// C++のラムダ関数における変数キャプチャ void main() { int a = 12 ; printf( "%d\n" , [a]( int x , int y ) { // 変数キャプチャ[a]の部分 return a + x + y ; // 局所変数aをラムダ関数内で参照できる。 }( 3 , 4 ) ) ; return 0 ; }
Javaでラムダ式を使う例
Javaでも Java8 以降でラムダ式の機能が使えるようになっている。以下のように、ArrayList や Array の全要素に対して処理を行う forEach() メソッドでは、各要素に対して実行する関数を、無名関数として渡すことで、ループを回す繰り返し文を使わずにプログラムを記述できる。
// ArrayList<> の forEach() メソッド (Google Geminiの生成した例) import java.util.List ; import java.util.ArrayList ; public class ForEachExample { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); names.add("Charlie"); // forEachを使ってリストの要素を出力 names.forEach( name -> System.out.println(name) ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 無名関数(ラムダ式) } // 型推論のおかげで name の変数宣言も書く必要がない... // names.forEach( (String name) -> System.out.println( name ) ) ; ------------------------------------------------------------------------------------- // Array の forEach() メソッド import java.util.Arrays ; public class ForEachExample2 { public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 5}; // 配列の要素を2倍にして出力 Arrays.stream(numbers).forEach( number -> System.out.println(number * 2) ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 無名関数(ラムダ式) }
Javaでは、ラムダ式は 関数型インタフェースと組み合わせて使う必要がある。forEach() メソッドは、Consumer という関数型インタフェースを持ち、引数に書いたラムダ式は、関数型インタフェースの実装として扱われている。
(JavaScriptみたいに、let f = (x) => { return x*x ; } のような単純なラムダ式は使えない)
B木とB+木とハッシュ法
データベースでは、キーなどの値を高速に探し出すために、単純なデータが並んだだけのテーブルとは別に、検索専用のデータ構造を別に持たせることが多い。これらの検索用のデータは、インデックスファイルと呼ばれる。また、データベースのテーブルのデータも、高速に検索する機能とすべてのデータを順次取り扱うための機能が求められる。これらの機能を実現するための仕組みを以下に説明する。
B木
データベースのデータを扱う場合には、B木を用いることが多い。(4年の情報構造論で説明済み)
複数のデータを格納するノードは、位数Nであれば、2✕N個のデータと、その間のデータを持つノードへの2N+1個のポインタで構成される。
ノードにデータを加える場合(あるいは削除する場合)は、頻繁にノードのポインタの付け替えが発生しないように、データがN個を下回った時や、2N個を超える場合に以下のような処理を行う。ノード内のデータ数が2Nを超える場合は、均等に木構造が成長するように、中央値を上のノードに移動し、ノードを2分割する。
データを削除することでN個を下回る場合は、隣接するノードからデータを移動する。(上図の緑部分のように上位ノードの値を交えながら移動する)
このような処理を行うことで、極力不均一に成長した木構造が発生しないようにB木は管理されている。
B+木とシーケンスセット
再帰的な木構造のB木では、特定のデータを探す場合には、O(log N)で検索が可能である。
しかしながら、直積のようなすべてのデータを対象とする処理を行う場合、単純なB木では再帰呼出しをしながらの処理を必要とすることから、複雑な処理が発生する。そこで、データ列を横方向にアクセスするための単純リストであるシーケンスセットをB木と並行して管理するデータ構造がB+木である。
データを検索する場合は、B木構造部を用い、全データ処理は、シーケンスセットを用いる。
ハッシュ法
ハッシュ表は、データの一部をとりだしてハッシュ値を求め、そのハッシュ値を番地とする場所にデータを保存する方法である。しかし、データの一部を取り出すため、異なるデータに対して同じハッシュ値となる場合がある。これをハッシュ衝突とよぶ。この際のデータの保存の方法から、2つの方式がある。
- オープンアドレス法
ハッシュ表がすでに埋まっていたら、別の保存場所を探す方式。 - チェイン法
同じハッシュ値となるデータをリスト構造で保存する方法。
ガベージコレクタとヒープ管理
前回の授業では、共有のあるデータ構造では、データの解放などで問題が発生することを示し、その解決法として参照カウンタ法などを紹介した。今日は、参照カウンタ法の問題を示した上で、ガベージコレクタなどの説明を行う。
共有のあるデータの取扱の問題
前回の講義を再掲となるが、リスト構造で集合計算おこなう場合の和集合を求める処理を考える。
struct List* list_union( struct List* a , struct List* b ) { struct List* ans = b ; for( ; a != NULL ; a = a->next ) if ( !find( ans , a->data ) ) ans = cons( a->data , ans ) ; return ans ; } void list_del( struct List* p ) { // ダメなプログラムの例 while( p != NULL ) { // for( ; p != NULL ; p = p->next ) struct List* d = p ; // free( p ) ; p = p->next ; free( d ) ; } } void main() { // リストの生成 struct List* a = cons( 1 , cons( 2 , cons( 3 , NULL ) ) ) ; struct List* b = cons( 2 , cons( 3 , cons( 4 , NULL ) ) ) ; struct List* c = list_union( a , b ) ; // c = { 1, 1, 2, 3 } // ~~~~~~~ ここは b // a,b,cを使った処理 // 処理が終わったのでa,b,cを捨てる list_del( c ) ; list_del( b ) ; list_del( a ) ; // list_del(c)ですでに消えている } // このためメモリー参照エラー発生
このようなプログラムでは、下の図のようなデータ構造が生成されるが、処理が終わってリスト廃棄を行おうとすると、bの先のデータは廃棄済みなのに、list_del(c)の実行時に、その領域を触ろうとして異常が発生する。
参照カウンタ法
上記の問題は、b の先のリストが c の一部とデータを共有しているために発生する。この解決方法として簡単な方法では、参照カウンタ法が用いられる。
参照カウンタ法では、データを参照するポインタの数をデータと共に保存する。
- データの中にポインタ数を覚える参照カウンタを設け、データを生成した時に1とする。
- 処理の中で共有が発生すると、参照カウンタをカウントアップする。
- データを捨てる際には、参照カウンタをカウントダウンし、0になったら本当にそのデータを消す。
struct List { int refc ; // 参照カウンタ int data ; // データ struct List* next ; // 次のポインタ } ; void list_del( strcut List* p ) { // 再帰で全廃棄 if ( p != NULL && --(p->refc) <= 0 ) { // 参照カウンタを減らし list_del( p->next ) ; // 0ならば本当に消す free( p ) ; } }
ただし、参照カウンタ法は、循環リストではカウンタが0にならないので、取扱いが苦手。
ガベージコレクタ
では、循環リストの発生するようなデータで、共有が発生するような場合には、どのようにデータを管理すれば良いだろうか?
最も簡単な方法は、「処理が終わっても使い終わったメモリを返却しない」方法である。ただし、このままでは、メモリリークの発生でメモリを無駄に使ってしまう。
そこで、廃棄処理をしないまま、ゴミだらけになってしまったメモリ空間を再利用するのが、ガベージコレクタである。
ガベージコレクタは、貸し出すメモリ空間が無くなった時に起動され、
- すべてのメモリ空間に、「不要」の目印をつける。(mark処理)
- 変数に代入されているデータが参照している先のデータは「使用中」の目印をつける。(mark処理)
- その後、「不要」の目印がついている領域は、だれも使っていないので回収する。(sweep処理)
この方式は、マークアンドスイープ法と呼ばれる。ただし、このようなガベージコレクタが動く場合は、他の処理ができず処理が中断されるので、コンピュータの操作性という点では問題となる。
最近のプログラミング言語では、参照カウンタとガベージコレクタを取り混ぜた方式でメモリ管理をする機能が組み込まれている。このようなシステムでは、局所変数のような関数に入った時点で生成され関数終了ですぐに不要となる領域は、参照カウンタで管理し、大域変数のような長期間保管するデータはガベージコレクタで管理される。
大量のメモリ空間で、メモリが枯渇したタイミングでガベージコレクタを実行すると、長い待ち時間となることから、ユーザインタフェースの待ち時間に、ガベージコレクタを少しづつ動かすなどの方式もとることもある。
ガベージコレクタが利用できる場合、メモリ管理を気にする必要はなくなってくる。しかし、初心者が何も気にせずプログラムを書くと、使われないままのメモリがガベージコレクタの起動まで放置され、場合によっては想定外のタイミングでのメモリ不足による処理速度低下の原因となる場合もある。手慣れたプログラマーであれば、素早くメモリを返却するために、使われなくなった変数には積極的に null を代入するなどのテクニックを使う。
動的メモリ領域とフリーリスト
動的なメモリ領域(ヒープ領域)は、malloc()関数で処理用のメモリを借り、free()関数で使わなくなったメモリを返却する。
この返却されたメモリ領域は、改めて malloc() が呼び出されたときに再利用を行う。この再利用するメモリ領域は、簡単に扱えるようにリスト構造にして保存する。この free された再利用候補のリスト構造は、free_list と呼ばれる。
mallocが一定サイズの場合
仕組みを理解する第1歩として、free_list の考え方を説明するために、malloc() でのメモリサイズが一定として説明を行う。free_list には、貸し出すためのメモリ空間をリスト構造で繋がった状態にしておく。
malloc() が呼び出される度に、free_list の先頭から貸し出すメモリを取り出し(a=malloc(),b=malloc(),c=malloc()まで)、free() が呼び出されると、返却されたメモリは、free_list の先頭につないでおく。
任意サイズのメモリ確保の場合
最初のステップでの説明は、mallocのメモリサイズを一定としていたが、本来は確保するメモリサイズが指定する。この場合は、以下の様に管理されている。mallocで貸し出されるメモリ空間には、ヒープメモリの利用者が使うブロックの前に、次のメモリブロックへのポインタとブロックサイズを記憶する領域をつけておく。こういったメモリブロックを free_list の考え方と同じようにリスト構造となるようにつないで保存されている。
この図の一番下の赤部分は、次のメモリブロックへのポインタとブロックサイズの大きさが20byteの場合の例。(説明を簡単化するためにポインタとメモリブロックサイズの部分は多少いい加減)
malloc() で、指定されたサイズのものが、free_list の中にあれば、それを使う。malloc(40)
丁度いいサイズが無い場合は、それより大きいメモリブロックの後半を切り分けて、貸し出す。malloc(60)
free()の処理とメモリブロックの併合
この例の最後の処理では、20byte,60byte,40byte,50byteが併合された例。併合後のブロックサイズは、すこしいい加減に書いてある。
使用されていたメモリブロックが free() で返却された場合は、free_list につないでいく。ただし、単純にリストに繋ぐだけであれば、malloc(),free() を繰り返すと、小さなメモリブロックばかりになってしまい、大きいメモリのmalloc()ができなくなる。また、free_list のリストが長いと malloc() の処理でジャストサイズのメモリブロック検索などの処理効率が悪くなる。
そこで、free() で返却される際には、隣り合うメモリブロックと併合できるかを確認し、大きなメモリブロックになるような処理を行う。さらに、併合によりより free_list の長さも短くできる。
また、隣り合うメモリブロックが併合できるかの判定が簡単になるように、free_listにつなぐ際は、次のメモリブロックへのポインタは、昇順となるように並べる。
一般的には、上記のようにmalloc(),free()を行うが(K&R の mallocアルゴリズム)、mallocのサイズが小さい場合には小さいメモリブロック毎にnextブロックポインタやブロックサイズを記憶する場合、メモリのムダが多い。
そこで、最初に説明した一定サイズのmalloc()の手法で、8byte専用のfreelist,16byte専用のfreelist,32byte専用のfreelistのように2Nbyteのfreelistで管理する。10byteといった中途半端なサイズの時は、それより大きい16byteのfreelistを使う。
ヒープメモリの断片化
ヒープメモリの malloc() , free() を繰り返すと、最悪、以下の図の様に、使用中領域(赤)とfreeされた未使用領域(黒)が交互に並ぶ状態が発生するかもしれない。この場合、全体の未使用領域の合計では十分なサイズでも、小さなメモリブロックばかりとなって、大きなメモリブロックを要求されても十分な大きさのメモリが見つからない状態が発生する場合がある。
この状態をヒープメモリの断片化といい、使用しづらい小さなメモリブロックはヒープホールと呼ばれる。
(補足) 断片化
断片化というと、OSではハードディスクの断片化(フラグメンテーション)を思い浮かべるかもしれない。ハードディスクの断片化とは、ファイル領域の割り当てとファイルの削除を繰り返すことで、ファイルのセクタが不連続となり、アクセス効率が悪くなる現象。OSによっては、ファイル実体の位置を動かすことで断片化を改善できる。以下の図のようにフラグメンテーションを防ぐための実体の移動を行う最適化はデフラグと呼ばれる。
上記の図では、上の青の図が断片化が発生している事例で、a1→a2,a2→a3の時にヘッド移動(シーク時間)が発生する。下の赤の図のように、デフラグ処理を施すことでシーク時間が減らせる。
Windows が 95,98,Me といった時代ではOSが不安定で、フラグメントが多く発生する場合Windowsがフリーズすることが多く、OSが不安定になったらデフラグを実行する…というテクニックが定番だった。最新のWindowsでは、デフラグが自動的に実行されるのでユーザが意識的に実行する機会はほぼなくなった。
WWWとhttpとサーチエンジン
最初に、前回講義で説明が不十分だったメールの機能について説明のあと、Web の説明を行う。
WWWとhttp
WWWとは、ティム・バーナーズ=リーによって作られたサービスであり、元々は研究データの論文やデータの共有のために作られた。この際のWebサーバのデータのやり取りのためのプロトコルがhttp(Hyper Text Transfer Protocol)であり、ポート番号80のTCPを用いたものであり、最近では通信を暗号化したhttps(ポート番号443)も多く使われる。
httpでは、文字データの中に画像や音声といった情報に加え、他のデータへのリンクを埋め込むことができる HTML(Hyper Text Markup Language) のデータがやりとりされる。このHTML形式のデータを表示するためのソフトは、ブラウザと呼ばれる。
URL
WWWのデータの場所を示すものが、URL(Uniformed Resource Locator)であるが、最近ではインターネットが複雑化しLocator という表現が難しいため、URI(Uniformed Resource Identifier)と呼ぶようになってきた。
URLは基本的に、スキーマ://コンピュータ名/サーバ内ファイル位置 といった文字で構成される。URL は、HTTP だけでなく、インターネットの情報の場所を記述するために使われており、httpやhttps以外にも使う。
最近のブラウザは、スキーマ欄の”https://”やコンピュータ名の先頭の”www.”を省略することができる。また http は暗号通信を使わず危険であることから、警告メッセージが表示されたり、可能であれば https の通信に切り替えを試みられる。
http (Hyper Text Transfer Protocol) の流れ
httpのサーバ(Webサーバ)とブラウザでは、以下のような手順で処理が行われる。例えば http://www.ei.fukui-nct.ac.jp/~t-saitoh/index.html のページが表示されるまでを考えると、
- ブラウザのURL欄に、目的サイトのURLを入力。
- 基本的には、スキーマ欄に記載されたプロトコル(http)名から、ポート番号と通信方法(http)を決める。一般的な http 通信では、ポート番号には 80 を使う。
- コンピュータ名部分(www.ei.fukui-nct.ac.jp)を DNS に問合せして、得られたIPアドレスのコンピュータに接続。
- httpの最も簡単な GET メソッドでは、Webサーバに、サーバ内のファイル位置(/~t-saitoh/index.html)を伝えると、Webサーバは応答ヘッダ情報と応答本文の指定された場所のファイルの内容を返送する。(下図参照)
- HTML形式のデータが指定された場合、ブラウザはその HTML をどの様に表示するか判断しながら表示する。
このような予め保存されているWebページを返送する場合は静的ページと呼ばれる。サーバのデータベースなどを参照しながらページ内容を返送する場合は、動的ページと呼ばれ、Webサーバ内部でプログラムを動作させ、その結果のデータをブラウザに返す。
動的ページを生成するためのプログラム言語としては、様々な方法がある。(バックエンド言語)
- 言語 Perl による CGI(Common Gateway Interface)
- Webに特化した言語PHP
- サーバで 言語 Java を使ってページデータを生成(Apache Tomcat)
- サーバで 言語 JavaScript を使ってページデータを生成(Node.js)
また、最近のブラウザでは JavaScript を使って、Webページに表示される内容を動的に変化させることが多い。(フロントエンド)
https
httpでは、通信が平文で行われるため、同じサブネット内であれば通信内容を盗み見られる可能性がある。この通信を暗号化しながら行われるものが https である。ポート番号には一般的に 443 が使われる。暗号化通信は次週以降に説明を行う。
サーチエンジン
インターネットでは、大量のWebページが出現してきたため、自分の目的に応じてWebページを探す機能が必要となってきた。このような目的のWebページを検索してくれるシステムは、サーチエンジンと呼ばれる。
ディレクトリ型
最初に現れた検索システム(1994年)は、ページ作者が自分のページのURLと内容となるキーワードをサーチエンジンに登録しておき、内容のカテゴリー別に、ページの紹介文章が表示されるディレクトリ型であった。(初期のYahoo)
しかし、登録するキーワード以外の文字で探そうとすると、情報を見つけることができない。
ロボット型
これらの問題を解決すべく登場したのが、Google のようなロボット型サーチエンジン(1997年)である。
ロボット型の検索システムでは、クローラーとかロボット(あるいはボット)とか呼ばれるプログラムを使い、Webページの内容をダウンロードし、そこに記載された文字を使ってURLのデータベースを作成する。
- 与えられた URL の先のページをダウンロードする。
- ページ内の文字を単語に切り分けして、それぞれの単語とURLを関連付けてデータベースに保存
- ページ内にリンクが含まれていたら、そのURLで、この作業を再帰的に繰り返す。
サーチエンジンで検索が行われると、クローラーの処理で作られたデータベースに問い合わせ、見つかったURLの情報を表示する。
Googleなどでは、多くのユーザが探したいページを提供するために、たくさん使われている単語を重要語としたり、たくさんのページからリンクされているページを表示順上位に表示するような工夫をしている。
ページランキングを上げるためのWebページの工夫をすることを、SEO (Search Engine Optimization) という。しかし逆にページランキングを不当に上げようと特殊なテクニックのページ作りをする人もいるが、最近では不当なページ作りは逆にランキングが落とされるようになっている。
理解度確認
- URLが与えられてページが見れるまでに行われることを説明せよ。
- サーチエンジンのディレクトリ型とロボット型の違いを説明せよ。