Edited at

[JAVA][Android][セキュリティ]JAVAのCipherクラスが脆弱なECBモードをデフォルトにしてる件

More than 3 years have passed since last update.

Androidアプリ開発者の方への注意喚起の意味で情報展開します。

Androidが取り込んでいるJavaインターフェースでは、未だにデフォルトの「暗号利用モード」としてECBモードが利用されています。

ECBモードは旧知の脆弱性があり、日本政府が策定しているCRYPTREC推奨リスト( http://www.cryptrec.go.jp/images/cryptrec_ciphers_list_2013.pdf )でも「非推奨」の扱いになっています。

にも関わらず、JAVAのCipherクラスを利用して暗号をかけた場合、モードのデフォルト値がECBとなってしまっているために、開発者が意識せずにECBモードで暗号化を行っている事例が、多くのAndroidアプリに見受けられる状況です。


■ そもそも「暗号利用モード」って?

多くの共通鍵暗号では、ブロック暗号方式が採用されています。

ブロック暗号とは、その名の通りある平文を暗号化する際にブロック単位で暗号化を行っていく方式です。

ただ、そのままではブロックサイズ(AESは128Byte etc)以上のデータの暗号化が行えないため、ブロックサイズより大きいデータを暗号化する場合、ブロック単位でデータを繰り返し暗号化していきます。その繰り返しのパターンが暗号利用モードと呼ばれるものです。

例えばECBモードの場合は、下記のように順番に平文データをブロック毎に暗号化していくだけの非常にシンプルな方式で暗号化を行います。

ECB_from_wiki.png

※その他のモードについてはこちら


ECBモードの何が問題か?

ECBモードは上記図の通り、同じ平文のデータに対して、同一のKeyのみを指定するので同じ暗号文が生成されます。そのため、パターン推測により暗号文から元の平文データを推測し易いという脆弱性を内包しています。

また、平文データをただ順番に暗号化していくだけで改ざん検知機能もないため、データ内の一部分だけを任意の値に書き換えることも可能です。そのため、下記の攻撃に弱いことが分かっています。


(1) 暗号文一致攻撃

暗号文一致攻撃(ciphertext matching attack)は,同一の鍵によって生成された暗号文を大量に集め、それらの中から同一の暗号文となるものを探索し、平文や暗号鍵を推測する攻撃です。

例えば、あるパスワードを暗号化して保存した大量のデータが攻撃者によってサーバから盗まれ、最終的にECBモードの脆弱性を突かれることで、元のパスワード自体も解読され漏えいしてしまったというケースもあります。

ECBモードで暗号化した場合、同一のパスワードは同一の暗号文に変換されるので、例えば大量のデータの中で頻出するバイナリパターンは、よく利用されるパスワードだろうという推測を働かせることで、元のパスワードを探し当てることができてしまう可能性があります。


(2) 暗号文改ざん攻撃

暗号文の鍵を見つけたり、平文データをすべて解析できなかったとしても、データの中の一部を改ざんすれば意味のある攻撃を行えることがあります。それが暗号文の改ざんによる攻撃です。

例えば、ユーザーの認証用の情報の中で、ユーザの識別子部分だけをすり替えることができれば、そのユーザに成りすましたリクエストをサーバに送ることができてしまいます。

ECBモードは各ブロックを独立して暗号化するために、ブロック単位でのすり替え(改ざん)を検知できません。そのため、状況によっては攻撃者に有利な情報の改ざんを行われる可能性があります。


■ アプリの中で使ってたらどうするか?

順次、他のモードにリプレースしていくことをお薦めします。

実際には、(1)の脆弱性に対して単に1ブロック内に収まるような単一の値を暗号化した場合は攻撃を行うことができなかったり、(2)の脆弱性に対してもブロック単位のすり替えでは意味のある攻撃を行えない場合も多いため、アプリ内でECBモードを利用していたからといって、一概に利用を停止するべき危険な状況とは言えません。

例えば、アプリの固定のKey値をファイル保存するためにECBモードで暗号化するだけの用途であれば、殆ど問題は起きないでしょう。

ただ、脆弱性が既知のものとなっている技術を進んで利用するものでもありませんし、実際に危険性の伴うケースの場合もあります。アプリのこのケースであれば問題が起きないか、と詳細に検討するよりは、改修してしまう方が現実的なんじゃないかと思います。


■ 参考:モードの指定方法

では実際にどんな実装をすればよいかというのが下記になります。

この例ではCBCモードで暗号化しているため、初期化ベクトル(IV)(※)も利用しています。

(※) ブロック間の暗号文を疑似ランダム化させるような値です。

ポイントは、Cipher#getInstance()に"AES"だけでなく"AES/CBC/PKCS7Padding"と、モードまで指定する点です。


import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

import java.security.InvalidAlgorithmParameterException;

import javax.crypto.BadPaddingException;

import javax.crypto.Cipher;

import javax.crypto.IllegalBlockSizeException;

import javax.crypto.NoSuchPaddingException;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

// ■ CBCによる暗号化処理

// - buf :暗号化対象

// - key:共通鍵

// - iv :初期化ベクトル(IV)

public static byte[] cipherEncrypt(byte[] buf, String key, String iv){

 try {

  // 秘密鍵の生成

  SecretKeySpec sksSpec = new SecretKeySpec(key.getBytes(), "AES");

  // IVの生成

  IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());

  // Cipherクラスの初期化

  Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");←★ポイント

  cipher.init(Cipher.ENCRYPT_MODE, sksSpec, ivSpec);

  // 暗号化

  return cipher.doFinal(buf);

    } catch (NoSuchAlgorithmException e) {

    } catch (NoSuchPaddingException e) {

    } catch (InvalidKeyException e) {

    } catch (IllegalBlockSizeException e) {

    } catch (BadPaddingException e) {

    } catch (InvalidAlgorithmParameterException e) {

    } finally {

    }

  return null;

}



※この記事が参考になった!という方は是非「ストック」お願いします :ok_hand: