Got Some \W+ech?

Could be Japanese. Could be English. Android, セキュリティ, 機械学習などをメインに、たまにポエムったり雑感記載したりします。

もしも社長がセキュリティ対策を聞いてきたら

上記のメモといっても、自分が流し読みした本に対するいいまとめがあったのでそれを元に構成変更・追記を実施。

・セキュリティは「監視的コスト」

上流の話

セキュリティ対策の優先度付け -> FMEA

リスク優先度算出時の構成要素:

  • 発生頻度、影響度、防御困難度
  • それぞれに対して4段階で点数化し、
  • 優先度の高い内容から対策を施していく。
  • 場合によっては、影響度のポイントを上げる。

セキュリティ対策の種別 -> Get Secure And Stay Secure

  • Get Secure:セキュアにする
  • システム化、教育といった対策
  • Stay Secure:セキュアに保つ
  • 検知、可視化、改善

セキュリティ対策による部分最適にならないためには

  • 運用管理しやすい仕組み
  • 他の階層と連携(認証基盤・アクセス制限・ファイアウォールなどの連携)
  • 認証基盤の統合
  • CISOの設置

セキュリティ対策のコスパ

アプローチ

  • 守備範囲の限定
  • 標準機能の活用
  • アリモノを更新
  • 多層防御

ソリューション選定 -> コンジョイント分析

  • 対策から各アプローチから選定されたソリューションの特徴を抽出し、比較検討する

特長例

  • ウイルス対策: 検出率、IT基盤統合、管理工数、ライセンス費用
  • 出口対策: 提供形態、レポート、リセンス費用、認証ユーザー

多層防御

セキュリティ対策の効果測定 -> PDCA

中長期: 計画達成のPDCA

  • Plan
  • Do
  • Check
  • Action

短期: 問題解決のPDCA

  • Problem-Finding:問題発見
  • Display:可視化
  • Clear:問題解決
  • Acknowledge:確認

セキュリティ対策の必要性や脅威の動向を経営者に説明する -> PEST分析

  • Political:政治的環境要因
  • Economic:経済的環境要因
  • Social:社会的環境要因
  • Technology:技術的環境要因

セキュリティ対策の4段階 -> NIST SP800-61

  • 準備 → 検知・分析 → 根絶・復旧・封じ込め → 事件発生後の対応
  • 検知・分析部分の強化が早期発見への近道。

セキュリティルール作りの際の考え方

  • ポリシー:情報セキュリティに関する基本方針
  • スタンダード:実施する対策
  • プロシージャ:利用する製品や管理手順

ルール実施までの流れ

  • ルール作成・みなおしの体制創り -> CFT(Cross Functional Team)
  • ルール切り分け -> 技術的な強制ルール vs 強制しないルール
  • ルールに応じた仕組みの実装 -> ペナルティや損害イメージ

ルール切り分けの方針 -> MMOから考える

  • Measure/Motivation/Opportunity(MMO)から考える
  • MotivationとOpportunityを下げることで発生頻度を下げるのがよい。

ルールの効果測定方法 -> DAGMARモデル

  • 5段階の達成度合いを計測:
  • 未知: まだ知られていない状態
  • 認知: 存在を認知
  • 理解: ルールの内容・背景を理解
  • 確信: 良いモノ(重要性)だと確信
  • 行動: 順守

企業文化・人材も視野に入れた包括的なセキュリティ対策: -> マッキンゼーの7S

  • Shared Value(価値観):ソフト <- 他のSにも影響
  • Strategy(戦略):ハード
  • Structure(組織):ハード
  • System(システム):ハード
  • Skill(スキル):ソフト
  • Staff(人材):ソフト
  • Style(スタイル・社風):ソフト

要素技術

認証基盤構築のポイント

  • IDはユニークに
  • パスワード管理ポリシーの矯正
  • 管理者IDは強く(ICカードなどの利用)
  • 認証基盤は統合

エンドポイントの一般的対策

  • ウイルス対策ソフトの導入と定義ファイルの常時最新化
  • パッチ(セキュリティ更新プログラム)の適用と更新
  • 不正プログラムの起動防止(起動可能なソフトをホワイトリスト方式で管理)
  • 不正通信の禁止
  • 盗難・紛失による情報漏えいを防止(FDE/リモートワイプ)

安全・柔軟なIT基盤 -> 3A(AnyTime, AnyWhere, AnyDevice)

  • AnyTime: 社内のどこでも -> 無線LAN, デジタル証明書(or パスワードとMACアドレス制限)
  • AnyWhere: リモートワーク(部分アクセス(クラウド) or 完全アクセス(VPN))
  • AnyDevice: BYOD(検疫ネットワーク)

セキュリティ対策を支援する機能

  • OS標準イメージ
  • インベントリ収集
  • アプリやセキュリティ更新プログラムの配布(サイレントインストールとスケジュール)
  • バックアップ
  • 特定の実行ファイル・起動時間の把握(利用頻度のか把握や感染端末の特定など)
  • ヘルプデスク支援
  • 安全な構成
  • 管理者権限のコントロール

  • 操作ログを取得する
  • 操作ログを分析する
  • 管理者権限は申請時しか利用させない
  • セキュアな領域での作業は必ず2人1組

大陽日酸に対するサイバー攻撃についてまとめてみた

イントロ

  • 工場や医療現場で使われる酸素や窒素、二酸化炭素などを製造、販売する国内最大手の大陽日酸サイバー攻撃に合っていた模様。報道された2017/1/1には対応済みっぽい。

タイムライン

日時 出来事 備考
2015/11 最遅でもこの時点で侵入済み
2016/3/11 外部からの不正接続(海外?)を確認。ウイルス感染を確認。サイバー攻撃と認知
2016/3下旬 ウイルスを駆除
2016/4/28 警視庁に被害相談
2016/4/29 流出範囲(可能性)の特定
2017/1/1 公開報道。ホームページには未記載
2017/1/5 ホームページに公式発表公開。

攻撃の概要

ぱっと見、標的型攻撃っぽいところはある

攻撃の影響と成否

以下の事実が確認されている。

  • 管理者権限の奪取

  • システム内の情報が外部から見れる状態。600台に対して、外部から管理者権限によるログインが可能だった。

  • 情報の流出は未確認、1万人の社員情報が流出した可能性あり

武器化

Delivery

Exploit

Install

成功 -> 最終的に4種類のマルウェアに感染していたところから。

C&C

(2015年11月までに)成功 -> 外部からの管理者権限を使った遠隔操作で、システム内の大半にあたる6百数十台のサーバーに接続できる状態。サーバーの一つから2回にわたり外部と不正通信が発生。

データの破壊・盗難

成功(可能性が高い) -> グループ国内従業員ら11,105人分の個人情報(社名・氏名・職位・メアド)など内部情報を複数の圧縮ファイルにまとめていた痕跡。サーバーの一つから2回にわたり外部と不正通信が発生。

被害額

窃取された情報から、直接的な被害額は発生していない。 ちなみに株価(1/4)時点では下がるどころか上がってる。世間が気づいてないか、気づいてるけど大して重要視してないか、それともトランプ効果があるからか、これから下がるのか。 f:id:kengoscal:20170104110344p:plain

原因

対策

  • サイバー攻撃早期検知のための監視体制強化
  • 管理者権限の搾取対策今日か
  • 不正遠隔操作を某氏するための対策強化
  • 大陽日酸ではないが、窃取された社員情報を利用した標的型攻撃に気をつける必要はありそう。
  • 例えば「Title: [大陽日酸からの重要なお知らせ] Body: Hoge部長のFugaです。この度は云々カンヌン」みたいな。

所感

  • piyologはやすぎぃ! やっぱりpiyologはすごい。必ず1歩先をいかれている。
  • 後、具体的な数字(600台とか1万人の社員とかサーバ内情報とか)が出てて凄い。どこにあったんだろう 公開情報。
  • 2017/1/1に報道公開した意図は不明。
  • 株価への影響をみたい。1/4に要チェキ
  • これ以上、続報でなさそう。
  • 最初に報道したところが、次の報道も早いと考えたほうがいいかも?
  • JTBの時もそうだが、nikkeiは遅いかも。
  • "当社の生産設備制御系システムは、基本的には今回サイバー攻撃を受けた情報系ネットワークおよびインターネットとは接続されておらず、今後もサイバー攻撃の影響を受けないと考えます。" おっ、そうだな。
  • 警視庁公安部が不正アクセス容疑で操作...あっ(察し)

反省

更新内容

  • 2017/1/1: 初版
  • 2017/1/3: 多数のニュースサイトで取り上げられるも、新しい情報なし。ホームページへの記載もまだ。
  • 2017/1/4: 太陽日酸 -> 大陽日酸。ワロッシュ。どうりで検索も引っかからないはずやで。
  • 2017/1/5: ホームページ上で公式発表。

Ref

ガス大手にサイバー攻撃 警視庁が捜査 - 産経ニュース

ガス大手にサイバー攻撃 ウイルス感染、警視庁捜査 | どうしんウェブ/電子版(社会)

ガス大手にサイバー攻撃、管理者権限奪われる : 社会 : 読売新聞(YOMIURI ONLINE)

ガス大手にサイバー攻撃 | ロイター

http://www.tn-sanso.co.jp/jp/_documents/info_07830547.pdf

通信記録の保存不十分…サイバー攻撃調査できず : 社会 : 読売新聞(YOMIURI ONLINE)

2016年の振り返りと2017年の目標

振返り

Keep

  • ジムにコンスタントにいくこと
  • 登壇すること
  • 勉強会にもよく参加してた
  • Docker/SIEM/AWS/GCPなどに取り組んだのは良かった。
  • Goを始めたのもよかった。
  • 本は結構読んだ

Problem

  • やる気にムラがあった
  • お金系(401k, NISA使い切らない)などがなかった
  • ニュースソース・新聞を積極的に読んでなかった。忙しいとめんどくさがる
  • ブログ/Qiita投稿などのアウトプットが2〜11月までなかった。
  • 実家に中々帰れない。
  • 将棋を8月頃に一回手放す。
  • 個人Android/Webアプリを作れてません
  • 機械学習も結局てを動かせず

Try

  • 自分のスケジュールをもっとタイトに切る(コントロールする)。家が近いので深夜遅くまで働いてしまい、結果パフォーマンスとペースを崩してしまった。具体的には24時までに就寝し、6時起きを心がける。翌日のスケジュールは24時までに。
  • 実家にコンスタントに帰るのは諦め、記念日を大切にするスタイルにする
  • 持久力をつける(走ることを始める)
  • 1〜2ヶ月に1つのテーマで技術勉強にとりくみ、それを少しづつアウトプットするといいかもしれない
  • CISSPをやるか、機械学習をがんばるか、自分のアプリを作るか。毎日やれることと、週末などそれなりにまとまった時間が必要そうなもので区切れそう。
  • 新聞を定期的に読む
  • CISSP <- とる。
  • 機械学習 <- 作る。自分の業務を楽にするものを作る。
  • chatbot <- 作る。自分の業務を楽にするものを作る。
  • サービス <- 作る(アイディアはあるけど手を動かせて無い状態)。クローラー
  • AR <- やりたい

Googleが買収したセキュリティ企業

2016年12月までにGoogleが買収したセキュリティ企業を調べてみた。きれいな案件だけでなく、潰しにかかってるっぽいものもあって面白かった(小並感。企業買収をExitとするなら、対象企業がカバーできてないソリューションを開発するのがいいという事だけは分かった。アタリマエ。

要約

企業名 種別 買収日 調達額
GreenBorder Technologies エンドポイント防御・仮想化 2007/5/10 $18M
Impermium アンチスパム・不正アクセス対策 2014/1/16
Interactive Security Group レピュテーションススコアリング 2012/6/3 $9M
Postini エンドポイント防御 2007/9/10 $16M
reCAPTCHA 不正アクセス対策 2009/8/6 ??
SlickLogin 不正アクセス対策 2014/2/17 $20K
Spider.io マルウェア検知 2014/2/21 ??
Widevine Technologies 著作権保護 2010/12/3 $44M
Zynamics OSS 2011/3/2 ??
VirusTotal データマイニング 2012/9/9 ??

GreenBorder Technologies

Imperium

  • アンチスパム・オンラインのアカウント防御サービス
  • 不正登録、不正ログイン、アカウントハイジャック、ソーシャルスパムから防御する
  • 買収前はPinterestTumblrが使ってたが、今はサービスを停止している。
    • Google内のアンチスパムグループに統合されたらしい

Interactive Security Group(KikScore)

Postini

  • ウイルス・スパム・フィッシングから、メール・インスタントメッセージ・ウェブ上のメッセージを暗号化することでプライバシーを保護する
  • 又、通信をアーカイブすることでコンプライアンスを保つ
  • postini.comにアクセスするとGsuiteにリダイレクトされるので、Gmailやチャットをアーカイブする機能に統合されたっぽい。

reCAPTCHA

  • 説明不要。これ。
  • 企業名継続案件

SlickLogin

  • 高周波音を使ってウェブサービスのログインをセキュア化するもの。謎。
    • 少なくともGoogleの2段階認証にはまだ登場していない...
  • slicklogin.comにアクセスしようとすると403エラーになる。謎

Spider.io

  • 不正なオンライン広告を経由して感染するマルウェアに対するソリューション
    • 感染したPCを検知することで、オンライン広告が世にばまかれることを防ぐ

Widevine

VirusTotal

  • え、Googleが買収してたの?
  • 説明いる?要らない気がする。これ。

  • 企業名継続案件その3

Zynamics

AndroidのConcealライブラリの中身を読む

色々あれがこれなので、Facebookが出している暗号化ライブラリ「Conceal」のソースコードを読んでみました。

  • 使い方については、こちらの記事を参照いただくといいと思います。

Concealとは

Concealライブラリの中身

Concealのgithubページ・上記Qiita記事に載っているので実装は割愛しますが、大きく4ステップがあります。

  1. KeyChainインスタンスの生成
  2. Cryptoインスタンスの生成
  3. ライブラリのロード状況チェック
  4. 暗号化

それでは各ステップでConcealが何をしているか読み込んでいきましょう。

KeyChainインスタンスの生成

KeyChainは共通鍵や関係するデータのライフサイクルに関わるメソッド群を定義したインターフェースです。これを実装したSharedPrefsBackedKeyChainインスタンスがまず生成されます。ファイル名の通り、SharedPrefをベースにしていますが、暗号化に欠かせない疑似乱数生成器(PRNG)の生成と、暗号化時の鍵長を設定します。

PRNGの生成 と 既知の脆弱性対応

  • 該当コード
# SharedPrefesBackedKeyChain.java
SecureRandomFix.createLocalSecureRandom()

PRNGはOpenSSLを利用して生成されますが、Jelly Bean(16~18)には初期化プロセスに脆弱性がありました。 この脆弱性をつくことで暗号文の強度が低下し、BitCoinアプリ内のウォレットの盗難などができてしまう模様です。Googleはこれへの対処方をDeveloper Blogに公開していますが、これをConcealがカバーしてくれています。

private static void tryApplyOpenSSLFix() {
  try {
    // Mix in the device- and invocation-specific seed.
    Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
        .getMethod("RAND_seed", byte[].class)
        .invoke(null, generateSeed());
    // Mix output of Linux PRNG into OpenSSL's PRNG
    int bytesRead = (Integer) Class.forName(
        "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
        .getMethod("RAND_load_file", String.class, long.class)
        .invoke(null, DEV_URANDOM, 1024);
    if (bytesRead != 1024) {
      throw new IOException(
          "Unexpected number of bytes read from Linux PRNG: "
              + bytesRead);
    }
  } catch (Exception e) {
    throw new SecurityException("Failed to seed OpenSSL PRNG", e);
  }
}

暗号化時の鍵長を設定

crypto\CryptoConfig.javaで、鍵長・初期化ベクトル長・タグ長の組み合わせがenumで定義されています。とはいえ、その組み合わせは2通りしかなく、しかも鍵長にしか違いはありません。2016年4月にリリースされたv1.1から、デフォルト推奨の鍵長が256bitになりましたので、基本はCryptoConfig.KEY_256を使いましょう。

  • デフォルトenum:
    • cipherID: 2
    • 鍵長: 256bits
    • 初期化ベクトル長: 12bits
    • タグ長: 16bits

Cryptoインスタンスの生成

前述のKeyChainをセットしAndoidConceal().get().createDefaultCrypto(keyChain)で、生成しています。AndroidConceal().get()内でまた SecureRandomFix.createLocalSecureRandom()を呼び出しています。おそらくこれは旧バージョンと新バージョンでの使用方法の差異に発する実装かと思われます。また、Cryptoインスタンス生成時に暗号化アルゴリズム(AES-GCM)が決定されます。今後、これが拡張されて他のアルゴリズムや暗号利用モードが利用できるようになる...ことはないでしょう。多分。

ライブラリのロード状況チェック

ConcealのAESアルゴリズム・GCMはCで書かれているため、Conceal内でNativeライブラリ"crypto"がロードされます。このチェックをcrypto.isAvailable()がしています

暗号化

Concealで暗号化は、平文を書き込む出力ストリームをラップすることで実現されます。デフォルトの実装では、Cryptoインスタンスを通じてBufferedOutputSreamをラップし、そのストリームに平文を書き込む形になります。

出力ストリームのラップ

実際のラップは、Cryptoインスタンス内のmCryptoAlgo変数が担当します。

# Crypto.java
    public OutputStream getCipherOutputStream(OutputStream cipherStream, Entity entity, byte[] encryptBuffer)
      throws IOException, CryptoInitializationException, KeyChainException {
        return mCryptoAlgo.wrap(cipherStream, entity, encryptBuffer);
    }

wrap内では、KeyChain内のPRNGから初期化ベクトルが生成され、他のメタデータと一緒に、ラップされる前の出力ストリームに書き込まれます。また、共有鍵もこのタイミングで生成されます。KeyChainのgetCipherKey()は、 maybeGenerateKey()を呼び出し、鍵を生成(もしくは取得)し、SharedPreferenceに保存します。

# CryptoAlgoGcm.java
    @Override
    public OutputStream wrap(OutputStream cipherStream, Entity entity, byte[] buffer)
            throws IOException, CryptoInitializationException, KeyChainException {
        cipherStream.write(VersionCodes.CIPHER_SERIALIZATION_VERSION);
        cipherStream.write(mConfig.cipherId);

        byte[] iv = mKeyChain.getNewIV();
        NativeGCMCipher gcmCipher = new NativeGCMCipher(mNativeLibrary);
        gcmCipher.encryptInit(mKeyChain.getCipherKey(), iv);
        cipherStream.write(iv);
    }
    
# SharedPrefsBackedKeyChain.java
  @Override
  public synchronized byte[] getCipherKey() throws KeyChainException {
    if (!mSetCipherKey) {
      mCipherKey = maybeGenerateKey(CIPHER_KEY_PREF, mCryptoConfig.keyLength);
    }
    mSetCipherKey = true;
    return mCipherKey;
  }
  
    private byte[] generateAndSaveKey(String pref, int length) throws KeyChainException {
    byte[] key = new byte[length];
    mSecureRandom.nextBytes(key);
    // Store the session key.
    SharedPreferences.Editor editor = mSharedPreferences.edit();
    editor.putString(
        pref,
        encodeForPrefs(key));
    editor.commit();
    return key;
  }

その後にGCM特有のデータと一緒に、AES-GCMで入力データを暗号化されるNativeGCMCipherOutputStreamが生成されます。

# CryptoAlgoGcm.java
    @Override
    public OutputStream wrap(OutputStream cipherStream, Entity entity, byte[] buffer)
            throws IOException, CryptoInitializationException, KeyChainException {
        byte[] entityBytes = entity.getBytes();
        computeCipherAad(gcmCipher, VersionCodes.CIPHER_SERIALIZATION_VERSION, mConfig.cipherId, entityBytes);
        return new NativeGCMCipherOutputStream(cipherStream, gcmCipher, buffer, mConfig.tagLength);
    }

暗号化

上記で生成したNativeGCMCipherOutputStreamに平文がwriteされると、NativeGCMCipher.updateが呼ばれ、gcm.c内のJava_com_facebook_crypto_cipher_NativeGCMCipher_nativeUpdateAadで暗号化されるのかと思われます。AndroidのNDKの仕組みに詳しくないので、間違っていたら教えて下さい。

# NativeGCMCipherOutputStream.java

@Override
  public void write(byte[] buffer, int offset, int count)
      throws IOException {
    if (buffer.length < offset + count) {
      throw new ArrayIndexOutOfBoundsException(offset + count);
    }

    int times = count / mUpdateBufferChunkSize;
    int remainder = count % mUpdateBufferChunkSize;

    for (int i = 0; i < times; ++i) {
      int written = mCipher.update(buffer, offset, mUpdateBufferChunkSize, mUpdateBuffer, 0);
      mCipherDelegate.write(mUpdateBuffer, 0, written);
      offset += mUpdateBufferChunkSize;
    }

    if (remainder > 0) {
      int written = mCipher.update(buffer, offset, remainder, mUpdateBuffer, 0);
      mCipherDelegate.write(mUpdateBuffer, 0, written);
    }
  }

以上が、一般的なConcealの暗号化が行われるステップになります。

Conceal - 他の機能

より単純な暗号化の実装

Cryptoインスタンスの生成までは同じですが、そのあとはただ一行「encrypt()」でおわる実装方法です。もう片方との違いは、出力ストリームが受け取れるバイト長が固定であることです。一回生成したら変更がないイミュータブルなデータ、もしくはハッシュ化したデータを暗号化したい時に使えるかもしれません。

KeyChain keyChain = new SharedPrefsBackedKeyChain(context,CryptoConfig.KEY_256);
Crypto crypto = AndroidConceal.get().createDefaultCrypto(keyChain);
crypto.encrypt(plainTextInByte, Entity.create("unique_id"))

パスワードベースの鍵の生成

Concealは基本的に鍵の生成・初期化ベクトルの作成など、暗号化で必要な処理は全てやってくれます。もし、そういった利便性を投げ捨ててオリジナルな鍵を作りたいのであれば、パスワードから生成することができます。しかし、あくまでも鍵を生成するところまでで、そのあとの暗号化処理、初期化ベクトルの作成、鍵の保存を全て自前で実装する必要があります。

AndroidConceal.get().createPasswordBasedKeyDerivation()
     .setIterations(10000)
     .setPassword("P4$$word")
     .setSalt(buffer)
     .setKeyLengthInBytes(16) // in bytes
     .generate();

MAC値の取得

Concealを利用すれば、デフォルトの暗号利用モードがGCMになってます。GCMは認証付きアルゴリズムなので、MAC値の計算は必要ありません。しかし、パスワードベースの鍵の生成をした場合、複合時に完全性・認証をしたほうがいいので、その場合はこれをつかうといいと思います。なお、複合に完全性・認証チェックがなぜ必要なのかを知りたい場合は、オラクルパディング攻撃でggrと良いでしょう。

OutputStream fileStream = new BufferedOutputStream(new FileOutputStream(file));
OutputStream outputStream = crypto.getCipherOutputStream(fileStream,Entity.create("entity_id"));
crypto.getMacOutputStream(outputStream,Entity.create("unique_mac_id")); // probably
outputStream.write(plainTextBytes);
outputStream.close();

Concealを使うにあたって気をつけたいこと

最後にConcealを使うにあたって気をつけたいことを書きましょう。

  • 上述しましたが、ver1.1から256bit鍵長が推奨されています。記事の中にはver1.1以前のもあるので気をつけましょう。
  • 鍵をSharedPreferenceに保存しているので、Root化された端末では抜かれてしまいます。
  • API18以降を対象とするならおとなしくKeyStoreに保存しましょう。その場合ユーザーが入力するパスワードがなければ開けない
  • というより、大事な情報は基本サーバに起き、Android端末には保存しないようにしましょう。必要な時にだけfetchしに行きましょう。
  • もし、サーバにおけない場合は...いい案があったら教えてください。Root化された端末では利用できないようにする...とか?

参考

GoでAESアルゴリズム(GCMモード)を使った実装をする

GoでAESアルゴリズム(CBCモード)+PKCS7パディング+HMACを使った実装をするの続きです

さて、前回はHMACを組み合わせることで、暗号化における機密性(Confidentiality)だけでなく、完全性(Integrity)からなる認証まで実現することができました。しかし、これには1つ問題があります。面倒くさい! 面倒くさければ、手間を省こうと実装しないかもしれないし、よしんばしても実装ミスが発生するおそれがあります。その問題に対応するために出てきたのが、認証付き暗号(Authentication Encryption)です。

GCMはその1つで、アメリカ国立標準技術研究所(NIST)にも標準として認められています。また、暗号処理を並列化することで高速な処理を可能としています。そのためなのか、最新ブラウザ <--> サーバ間通信であれば、デフォルトでAES-GCMのTLS通信として選択されています。

image

また、GCMはパディングが不要なストリーム型の暗号です。

GoにおけるAES-GMCの実装

なんと、Goの"crypto/cipher"パッケージにデフォルトで入ってました。hmacみたいに独立してないし、Paddingのように自前実装が必要ないので、暗に「お前ら、うだうだ言わずGCM使っとけ?」と言われている気がします。勿論、私はそうさせて頂きますよ、Google様。

// GCM encryption
func EncryptByGCM(key []byte, plainText string) ([]byte, error) {
    block, err := aes.NewCipher(key); if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(block); if err != nil {
        return nil, err
    }

    nonce := make([]byte, gcm.NonceSize())// Unique nonce is required(NonceSize 12byte)
    _, err = rand.Read(nonce); if err != nil {
        return nil, err
    }

    cipherText := gcm.Seal(nil, nonce, []byte(plainText), nil)
    cipherText = append(nonce, cipherText...)

    return cipherText, nil
}

// Decrypt by GCM
func DecryptByGCM(key []byte, cipherText []byte) (string, error) {
    block, err := aes.NewCipher(key); if err != nil {
        return "", err
    }
    gcm, err := cipher.NewGCM(block); if err != nil {
        return "", err
    }

    nonce := cipherText[:gcm.NonceSize()]
    plainByte, err := gcm.Open(nil, nonce, cipherText[gcm.NonceSize():], nil); if err != nil {
        return "", err
    }

    return string(plainByte), nil
}

func main() {
    cipherText, _ = EncryptByGCM(key, "12345")
    decryptedText, _ = DecryptByGCM(key, cipherText)
    fmt.Printf("Decrypted Text: %v\n ", decryptedText)
        // Decrypted Text: 12345
}

以上。取り急ぎ、これでGoにおけるAESアルゴリズムを用いた実装は一通り紹介出来たかと思います。

NONCE, 初期化ベクトルについて

これらは各データで一度きりしか使われないことを担保しなければいけません。元々の存在理由がリプレイ攻撃への対策です。 リプレイ攻撃は、例えばEC2サイトに注文した暗号通信を盗聴し、再度おなじ暗号通信を再送(リプレイ)することで2重の発注をさせてしまう様な攻撃です。もしくは、自分の口座に対する送金をする通信を盗聴し、それをリプレイすることで、何回も振り込ませてしまうような攻撃です。

これを使い捨てのナンスを使い暗号文を検証することで防御できるので、使いまわしてはいけません。なので、毎回ランダムに作り直しましょう。

GoでAESアルゴリズム(CBCモード)+PKCS7パディング+HMACを使った実装をする

GoでAESアルゴリズム(CBCモード)+PKCS7パディング+HMACを使った実装をするの続きです。

さて、前回はAES+CBC+PKCSパディングを使った実装例を紹介した。パディングを使って、任意の平文をブロック型暗号で暗号化することが可能になったが、一方で脆弱性がうまれてしまった。暗号文+パディングを繰り返し送ることで平文を一部推測できてしまうような脆弱性だ。この脆弱性の根本原因は、誰もが復号できてしまう部分にある。従って、復号処理のための暗号文を入力するものを認証することで解決できる。その仕組がMAC(Message Authentication Code)であり、今回はHMAC(keyed-hash MAC)を使った実装例が今回のものになる。

AES暗号化によるHMACとは

暗号文をHMAC用の共有鍵とハッシュ関数で導きだされるもの。復号をするまえにHMACを検証することで、共有鍵を持っているサブジェクトを認証することができる。

GoにおけるHMACは?

標準パッケージ内のcrypt/hmacで実装されている。パディングは実装されていなかったのに、なぜなのか。謎である。実装自体はシンプルになる。

func EncryptByCBCMode(key []byte, plainText string) ([]byte, error) {
    block, err := aes.NewCipher(key); if err != nil {
        return nil, err
    }

    paddedPlaintext := PadByPkcs7([]byte(plainText))
    cipherText := make([]byte, len(paddedPlaintext)) // cipher text must be larger than plaintext
    iv := make([]byte, aes.BlockSize)// Unique iv is required
    _, err = rand.Read(iv); if err != nil {
        return nil, err
    }

    cbc := cipher.NewCBCEncrypter(block, iv)
    cbc.CryptBlocks(cipherText, paddedPlaintext)
    cipherText = append(iv, cipherText...)

        // MAC作成
    mac := hmac.New(sha256.New, []byte("12345678912345678912345678912345")) // sha256のhmac_key(32 byte)
    mac.Write(cipherText)
    cipherText = mac.Sum(cipherText)
    return []byte(cipherText), nil
func DecryptByCBCMode(key []byte, cipherText []byte) (string, error) {
    if len(cipherText) < aes.BlockSize + sha256.Size {
        panic("cipher text must be longer than blocksize")
    } else if len(cipherText) % aes.BlockSize != 0 {
        panic("cipher text must be multiple of blocksize(128bit)")
    }

    // macの取り出し
    macSize := len(cipherText) - sha256.Size
    macMessage := cipherText[macSize:]

        // 暗号文から想定macを計算
    mac := hmac.New(sha256.New, []byte("12345678912345678912345678912345")) // sha256のhmac_key(32 byte)
    mac.Write(cipherText[:macSize])
    expectedMAC := mac.Sum(nil)

        // MACによる認証
    if !hmac.Equal(macMessage, expectedMAC) {
        return "", errors.New("Failed Decrypting")
    }

    iv := cipherText[:aes.BlockSize]
    plainText := make([]byte, len(cipherText[aes.BlockSize:macSize]))
    block, err := aes.NewCipher(key); if err != nil {
        return "", err
    }
    cbc := cipher.NewCBCDecrypter(block, iv)
    cbc.CryptBlocks(plainText, cipherText[aes.BlockSize:macSize])

    return string(UnPadByPkcs7(plainText)), nil
}

その他

AES +CBC +パディング + hmacでためしたが、 AES +GCMで全てやりたかったことが出来るみたいなので、次回はそちらを試してみる.