今週から仕事始めの方も多いことでしょう。
連休終わりの気怠さを感じると「特に連休なんていらないな」という気持ちになりますよね。
飲みすぎた次の日の「もうお酒なんて飲まない」に似てるなと思います。
でも翌々日くらいには体が欲するんですよねー休日も酒も。
さて前回は通信について色々調べました。
そして今回は通信にも多分に使用される暗号について考えてみます!
暗号といえば自来也が死の間際にフカサク様の背中に刻んだモノが思い出されて涙がちょちょぎれますね!
暗号(あんごう)とは、セキュア通信の手法の種類で、第三者が通信文を見ても特別な知識なしでは読めないように変換する、というような手法をおおまかには指す。いわゆる「通信」(telecommunications)に限らず、記録媒体への保存などにも適用できる。
暗号 – Wikipedia https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7
暗号なんて小難しそうなモノですが「通信文を第三者が簡単にはわからないようにする手法」なんて簡単に書いてくれちゃってます。
停車中にブレーキランプを5回点滅させると日本人には「アイシテル」と伝わるこれも暗号ということになりますね。
暗号のハードルがガックリ下がったところで入門にピッタリの暗号「シーザー暗号」についてみてみよー。
「ブルータスお前もか」でおなじみのシーザー(ユリウス・カエサルの英語読み)が使用した暗号です。
この暗号化は簡単で「アルファベットを何文字か後ろ(前)にずらす」だけです。例えば「HELLO」を「KHOOR」のように表したりします。
「KHOOR」から元の単語(HELLOのことは忘れてください)を導きたいと思います。このような暗号化された文字列を元に戻すことを復号と呼びます。
この暗号の優れている点は、以下の2つの事柄が復号のために必要なことです。
いきなり友達から「KHOOR」が送られてきて、「暗号だ!」ってなる人は相当おかしい人ですよね。普通の人はこれを暗号と認識することから難しいのです。ってか私ならまずアナグラム(文字を入れ替えて単語にするやつ)を疑いますし。
さて、これがシーザー暗号っぽいなと思ったとして、問題は何文字ずらしているかです。アルファベットは全部で26文字なので25回試行すれば絶対に答えに行き当たるはずです。
じゃあ1つずつ前にずらしてみましょうか。
「KHOOR」=>「JGNNQ」=>「IFMMP」=>「HELLO」
お?3回ずらしたら単語になりました!おそらく「HELLO」で正解でしょう!
もし1つずつ後ろにずらしてたら23回もやるところでしたよ。
暗号化というのは突き詰めれば文字列の変換です。その変換によって生み出されたモノは復号という作業によって元に戻すことができます。
このように「元に戻せる変換」のことを「可逆変換」と呼びます。
「当たり前じゃん!暗号が元に戻せなかったら迷宮入りになっちゃうじゃん!」
えぇ。わかります。でも元に戻せるというのは同時に弱点でもあるのです。
みなさんも会員制サイトに登録する時にパスワードを設定しますよね?
そのパスワードってどう管理されてると思います?
極稀にニュースで「パスワードを平文でデータベースに保存・・・」みたいなの見かけませんか?平文ってのは何も加工されていない生の文字列のことです。
ってことは「info@air-h.jp,airHakodate1234」みたいに保存されてるわけです。
もし、そのサイトが乗っ取られてパスワードが流出したらどうなると思います?
多くの人は1つのパスワードを複数のサイトで使いまわしています。
このメールアドレスとパスワードのセットを色んなサイトで試されれば、別のサイトのアカウントを乗っ取られちゃうかもしれないんです!
元の文字列を暗号化して保存すれば大丈夫だと思います?
じゃあ暗号化しました。「info@air-h.jp,zkfNz,lczgdqwer」
これが流出しても元の文字列は分からないだろうと思ったら大間違い。
「キーボードのホームポジションを一段下げて元の文字列と同じストロークで入力した感じっぽい。」ってバレたら「zkfNz,lczgdqwer = airHakodate1234」さっきと同じ末路です。
今の技術の暗号化はもっと高度でバレづらくなっています。
でも後述する「ハッシュ化」に比べると元に戻せてしまうのは危険すぎます。
みなさんもパスワードをデータベースに保存する際は暗号化だけはしないでくださいね。
これも暗号化のように文字列を別の文字列へ変換することを指します。が、こちらは「不可逆変換」となっています。
つまり「airHakodate1234」は「asli3hLiuqywDG23howfa86oDFjf(適当)」に変換されるが「asli3hLiuqywDG23howfa86oDFjf」から「airHakodate1234」には元に戻せないのです。
先ほどのパスワードのような案件には絶対ハッシュ化を使用してくださいね。
流出したところで元の文字列に戻せないんですから、安全安心です。
ハッシュ関数という「入力に対してユニークなハッシュ値を出力する計算」を使用して文字列を不可逆変換することです。(この表現はあまり正しく無いです)
このハッシュ関数には以下の特性があります(あることが求められる)。
1. ハッシュ値から、そのようなハッシュ値となるメッセージを得ることが(事実上)不可能であること(原像計算困難性、弱衝突耐性)。
2. 同じハッシュ値となる、異なる2つのメッセージのペアを求めることが(事実上)不可能であること(強衝突耐性)。
3. メッセージをほんの少し変えたとき、ハッシュ値は大幅に変わり、元のメッセージのハッシュ値とは相関がないように見えること。
暗号学的ハッシュ関数- Wikipedia https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%AD%A6%E7%9A%84%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0#%E7%89%B9%E6%80%A7
1.が不可逆であることの説明ですね!
2.は入力に対してユニークなハッシュ値を出力しますよーってことです。
入力1と入力2で同じハッシュ値が出力されることを衝突と呼びます。
3.が重要で、相関があったらなんとなく近いとかわかっちゃいますからね。
ハッシュ関数と聞けばSHAが挙げられます。
Secure Hash Algorithm(セキュア ハッシュ アルゴリズム)の略でして、通信やら認証やら色んなところで使われています。
ビットコインはこれが無いと成り立ちません。正しくはビットコインを根元から支える技術、ブロックチェーンはSHAによって成り立っています。
最初のバージョンはSHA-1と呼ばれていて、もうほとんど使用されていないかと思われます。というのも衝突が出たんです。
そこから一気にSHA-1の信頼は地に落ちてSHA-2へバトンタッチしたわけです。
そのSHA-2は今の主流のハッシュ関数です。
SHA-1の強化版って感じです。計算方法もよく似ています。
SHA256とSHA512なんかがよく使われていますね。
そしてそしてSHA-3です。
SHA-1とSHA-2はほぼ同じようなアルゴリズムだったのに対して、全く別のアプローチの仕方をとりました。
今のところSHA-2が破られていないので、まだ息を潜めています。
計算方法は大っぴらに公開されています。(FIPS180-4)
計算方法は解っても攻撃方法が分からないのが強みですよね。
コードの全貌
仕様書の通りに作りました。ちょっとずつ追いかけてみますか。
ハッシュの初期値です。
入力byte配列を使用してこの値を変えていきます。
演算に使用する定数です。
64個の素数の立方根の少数部分とかを表しているとかなんとか。
仕様書にある関数を素直に作りました。
Goはこの辺の演算が楽で助かります。
受け取った入力(string)はbyte配列にキャストします。
(入力bytes+以下の9bytes)が64 bytesに満たない場合には、0x00で埋めて64bytesにします。64bytesを超える場合は(64の倍数)bytesになるように調整します。
「aiueo」という入力をした場合には、以下のbyte配列となります。
入力の5bytes直後に0x80がつき、入力が5bytes = 40bitsなので末尾に0x28 = 40が配列末尾に0x28がつきます。ほかは0で埋めて64bytesの配列になりました。
パディング処理したメッセージを64bytes毎の配列に分割します。
パディング処理したメッセージが128bytesなら2つのメッセージブロックになります。
「aiueo」なら1つのメッセージブロックです。
ここからメッセージブロックを1つずつ使ってハッシュ値を演算していきます。
メッセージブロック(64bytes)からメッセージスケジュール(uint32 × 64 = 256bytes)を作成します。
まずはbytes配列をuint32の配列に変換します。
指定の計算方法でその配列に要素数が64個になるまで値を追加していきます。
これがメッセージスケジュールになります。
a〜hの値を現在のハッシュ値で初期化し、先ほどのメッセージスケジュールのワードたちで演算していきます。
d = c, c = b, b = a…みたいな感じで値がローテーションするので、ローテーション処理とか呼ばれてます。aとeのところで演算が加わるのでa~hの値がだんだん変わっていく感じです。
左からa b c d e f g hの値の変遷です。
同じ値が斜めに走ってるのがわかります。
先ほどローテーション処理して計算したa〜hをH[0〜7]にそれぞれ足して次のメッセージブロックでも同様に行います。
全てのメッセージブロックでのローテーション処理によって更新されたハッシュの値を16進数で出力します。
入力が「aiueo」なら「fa06926df12aec4356890d4847d43f79101c93548a6b65e4b57bcb651294beef」が出力されて、これで出来上がりになります。
何がどうしてこんな計算方法が生まれたんでしょうね?
そしてSHA-3は計算方法がガラッと変わったようなので、その辺りも見てみたいですね!
何はともあれ日頃使ってるハッシュがどういう計算を経て出力されているかを知ることができました。
そもそもGoには暗号・ハッシュのパッケージcryptoがあります。また、安心安全のためには自家製SHA256は使用しないことをお勧めします。
勉強のための実装で留めておいた方が良いですよ!
あとパスワードは最低でも16文字!
英子文字、英大文字、数字、記号を入れること!
使いまわさないこと!
私はパスワードを覚えるのに脳のリソースを使いたく無いので1Passwordを導入しています。
コータ=ザッカーバーグ
@kota_zuckerberg
バイクとプログラミングをこよなく愛する編集部の後方支援担当。 愛車はSUZUKI GSR250。 Illustratorの自動化からWEB制作、インフラの整備などをこなしていくうちに いつの間にかフルスタックエンジニアになっちゃった。 主な使用言語はphp, javascript, go, applescript。最近はjsに傾倒ぎみ。