はじめに
最近なら AES256/GCM 使っておけば大丈夫な気がするので、その使い方。
バイト列を入力して暗号化されたバイト列を取得、復号してバイト列を得るまで。
JavaでCipherを使う場合、GCMのtagは暗号文に含まれる模様。このため、.NETなど、別のライブラリで復号する時はtagを分離しないといけないっぽい。
暗号鍵
AES256にするので、鍵長は当然256bitにする必要がある。
パスワードを入力して復号するなら、パスワードのSHA256を鍵に使えば良いかも。自動生成することもできる。
文字列からSHA256を取得して、鍵にするならこんな感じ。(例外処理は適当なので、使うときはちゃんと作る)
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
.
.
.
public SecretKey getKeyFromPassword(String pass)
{
try {
MessageDigest sha = MessageDigest.getInstance("SHA-256");
sha.update(pass.getBytes(StandardCharsets.UTF_8));
byte[] shakey = sha.digest();
SecretKey ret = new SecretKeySpec(shakey, "AES");
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
自動生成ならこんな感じ。生成した鍵を共有しないと復号できないので、SecretKey.getEncoded()でbyte配列を取得する。
byte配列から鍵を作るのが getKeyFromBytes(byte[] src); になっている。これを復号側で使う。
import javax.crypto.SecretKey;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
.
.
.
public SecretKey generateRandomKey()
{
try {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecretKey ret = keygen.generateKey();
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
public SecretKey getKeyFromBytes(byte[] src)
{
try {
SecretKey ret = new SecretKeySpec(src, "AES");
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
IVとGCMパラメータ生成
同じデータを同じキーで暗号化した時、必ず同じ出力になると平文が推測されやすくなる。これを防ぐために初期化ベクトル(IV)を使う。
GCMの場合、IVは12byteのランダムデータ(nonce)と4byteのカウンタで生成される。
カウンタは自動で処理されるので、nonceを与えて GCMParameterSpec を生成する。
復号する時にnonceが必要なので、これもbyte配列で取っておくこと。IVは第三者に知られても大丈夫なので、暗号文と一緒に送っても問題ない。
import java.security.SecureRandom;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
.
.
.
public byte[] generateNonce()
{
try {
byte[] ret = new byte[12];
SecureRandom randgen = new SecureRandom();
randgen.nextBytes(ret);
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
public GCMParameterSpec generateGCMParameter(byte[] nonce)
{
try {
GCMParameterSpec ret = new GCMParameterSpec(128, nonce);
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
AAD
GCMでは追加認証データを使って、改ざんされていないことを確認できる。
AADはbyte配列の適当なデータで良いが、復号する時にも必要。
サンプルでは「AADは追加認証データ、中身はなんでも良いが64kBytes以内にする」の文字列をUTF-8のバイト列として使っている。
あくまでも認証用なので、暗号強度が上がるわけではない。
暗号化
平文と鍵、GCMパラメータがあれば暗号化できる。
AADはオプションなので使わなくても良い。使わない場合はupdateAADの呼び出しが不要。
import java.nio.charset.StandardCharsets;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.Cipher;
.
.
.
public byte[] encrypt(byte[] src, SecretKey key, GCMParameterSpec param)
{
byte[] aad = "AADは追加認証データ、中身はなんでも良いが64kBytes以内にする".getBytes(StandardCharsets.UTF_8);
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, param);
cipher.updateAAD(aad);
byte[] ret = cipher.doFinal(src);
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
復号化
暗号文と鍵、GCMパラメータ、AADがあれば復号化できる。
import java.nio.charset.StandardCharsets;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.Cipher;
.
.
.
public byte[] decrypt(byte[] src, SecretKey key, GCMParameterSpec param)
{
byte[] aad = "AADは追加認証データ、中身はなんでも良いが64kBytes以内にする".getBytes(StandardCharsets.UTF_8);
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, param);
cipher.updateAAD(aad);
byte[] ret = cipher.doFinal(src);
return ret;
}
catch (Exception e) {
Log.warn(e.toString());
return null;
}
}
まとめ
実際に暗号化・復号化する時はこんな感じ。
// 暗号化
SecretKey key = getKeyFromPassword("P@ssW0rd");
byte[] nonce = generateNonce(); // 復号化で必要なので取っておく
GCMParameterSpec param = generateGCMParameter(nonce);
byte[] encdata = encrypt(srcdata, key, param);
// 復号化
SecretKey key = getKeyFromPassword("P@ssW0rd");
byte[] nonce = <取っておいたbyte配列>;
GCMParameterSpec param = generateGCMParameter(nonce);
byte[] decdata = decrypt(encdata, key, param);