Camelliaはブロック暗号

NTTの研究所がCamelliaという名前のブロック暗号を作っており、「次世代SSLのデフォルト規格になった!」だとか「AESの2倍は強い!」とかいう猛者ぶりを発揮しているそうで、しかも調べたところJava実装がオープンソースになっていたので、大至急ダウンロードしてみた。


ダウンロードしてみると、中には「Camellia.java」というファイルが1つしか入っておらず、

public void Camellia_Ekeygen(int[] rawKey, int[] keyTable)
public void Camellia_EncryptBlock(int plaintext[], int keyTable[],int ciphertext[])
public void Camellia_DecryptBlock(int ciphertext[], int keyTable[],int plaintext[])

のpublicメソッドが定義されているだけだった。


これだけのメソッドで暗号化できるなんてCamellia恐るべし、と思ったが、JavaDocはおろか、チュートリアル的な資料すら見つからず、あったのはCの実装のサンプルコーディングだけ。
このAPI自体、Cから直でトランスレートしたような感じだし。

正直、よくわからん。

で、結局Cソースの解説文を見たり、このページを見たりして、なんとか動くようになったのでまとめてみる。

ブロック暗号

ブロック暗号とは、元のデータを一定の長さ(ブロック長)に分割し、そのブロックごとに暗号化をする暗号のこと。
Camelliaの場合は128bit、194bit、256bitが使えるみたいだが、オープンソースになってるやつはソースを見る限り128bitしか使えない模様。
従って、Camelliaは128bitの長さを1ブロックとみなして処理を行う。

暗号モード

ブロック暗号には「暗号モード」というのがある。
暗号モードは、各ブロックに対して暗号化処理を行う場合の処理方法のことで、この暗号モードは、暗号アルゴリズムとは別のもの。
ECB、CBC、CFB、OFBといった種類のモードがある。
詳しくはhttp://www.ss.iij4u.or.jp/~somali/web/_block_mode.htmlを参照。
例えば、「Camelliaを使ってCBCで暗号化する」、というパターンとか、「Camelliaを使ってOFBで復号化する」というパターンとかがありえる。

パディング

ブロック暗号の場合、ブロックごとに処理をする都合上、暗号化対象のデータはブロック長の整数倍でないといけない。
例えばbyte[10]のデータを128bit(16バイト)ブロック長のブロック暗号で暗号化する場合、ブロック長に6バイト分足りないため、その分を特定の文字列で埋める必要がある。
一般的なのがPKCS#5/PKCS#7のパディング方法で、足りないバイト数のサイズで、足りない部分を埋める方式。
上記だと、byte[16]のブロックを作成し、先頭10バイトをbyte[10]からコピーし、残りを「(byte)6」で埋める。
また、データサイズがブロック長の整数倍の場合、中身が全部「(byte)16」の、byte[16]のデータを付与する。
復号時は、復号化したデータの最後のbyteを取り、その数値分のバイトを後ろから削り、パディングを消す処理を行う。

で、Camellia

結局NTTが出してるソースは、「1ブロックに対して暗号or復号化処理を行う」というところしか実装されていない。つまりパディングとか暗号モードとかは実装が無い。
従って、これをそのまま使うことは無理。
(ここがハマったポイント。NTTのAPIは暗号化から復号化まで全部やってくれると思った時期が僕にもありました、ということ)
ということで、ひとしきり使えそうなところまで実装してみた。
http://hatena.souko105.net/20080430/Camellia.zip
暗号モードはECBとCBC、パディングはPKCS#5で実装した。


実行するとこんな具合。

    String password = "testpassword";

    byte[] keyBytes = CamelliaHelper.generateKey128(password);

    byte[] planeDataBytes = "かめりあであんごうか".getBytes();

    byte[] iv = CamelliaHelper.generateIV();
    
    byte[] cryptBytes = CamelliaHelper.cryptCBC(iv,keyBytes, planeDataBytes);

    byte[] decryptBytes = CamelliaHelper.decryptCBC(iv,keyBytes, cryptBytes);

    System.out.println(new String(decryptBytes));

まず初めに、暗号化のパスワード文字列から暗号化キーを生成する。
generateKey128は、パスワードをシードにしてSHA1PRNGで128bit分のバイト配列を生成するメソッド
(このやり方でいいのかわからなかったが、シードが同じ場合は再現性があるので、よしとする。というかJDK1.2だとこれしか。)


次にIVを生成する。IVとは初期ベクターのことで、暗号モードがCBCやCFBの時に必要になるデータのこと。(ECBの場合は不要)
IVもSHA1PRNGから生成。IVは暗号化と復号化で同一であればよいので、毎回ランダムに生成するほうがよいらしい。


最後に、cryptCBCかcryptECBで暗号化。復号はdecryptCBC or decryptECBで。暗号化時と復号化時の暗号モードが異なると、エラーになります。


で、最初の話に戻って、NTTのAPI。今回の経験を元のJavaDocを書くとするとこうなる。

/**
 * 拡張鍵の生成を行う。rawKeyを元に拡張鍵を生成し、keyTableに値をセットする。
 * @param rawKey 暗号化キー(128bit)
 * @param keyTable 拡張鍵(1792bit)
 */
public void Camellia_Ekeygen(int[] rawKey, int[] keyTable)
/**
 * plaintextを暗号化し、ciphertextに値をセットする。
 * @param plaintext 平文のブロック(128bit)
 * @param keyTable 拡張鍵
 * @param ciphertext 暗号化されたブロック(128bit)
 */
public void Camellia_EncryptBlock(int plaintext[], int keyTable[],int ciphertext[])
/**
 * ciphertextを復号化し、plaintextに値をセットする。
 * @param ciphertext 暗号化されたブロック(128bit)
 * @param keyTable 拡張鍵
 * @param plaintext 平文のブロック(128bit)
 */
public void Camellia_DecryptBlock(int ciphertext[], int keyTable[],int plaintext[])

拡張鍵というのは、「暗号強度を高めるために元の鍵から生成する鍵」だそうだ。
拡張鍵の長さはソースから読み取ったため、これであってるかどうかは不明(だが動いてはいる)
メソッドの引数でbit指定のあるところは、intを32ビットとみなしたint配列をセットする。
(例えば128bitだったらint[4]をセット。これがぜんぜん分からんかった)

1ブロックしか処理してくれないので、データをブロックに分けたりループさせたりする部分は自前になる。

結論

自分なりには勉強になりましたが、JDK1.4以降ならJCEが使えるので、素直にそれを利用するのがよいと思います。

追記

ここに詳しい記事があった。
http://www.atmarkit.co.jp/fsecurity/rensai/crypt03/crypt01.html