LoginSignup
2
0

More than 1 year has passed since last update.

Javaで暗号化

Last updated at Posted at 2022-11-23

はじめに

最近なら 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);
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0