2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

続・今時の暗号通信

Posted at

はじめに

前回の続きで、鍵交換までできたので暗号通信してみようという話
まあ、AES-GCM使っておけば良いでしょうという雰囲気なのでその通りにする

鍵交換

で鍵交換しているので、これを AES の共通鍵として使う

コードは前回同様 Java と C#(.NET 8)

暗号化(Java)

処理としては以下の手順になる

  1. IVを生成
  2. GCMParameterSpecを生成
  3. 暗号化
  4. IVとつなげて送信

重要なのは同じ鍵で同じIVを使わないこと、必ず暗号文生成のたびにIVを作る
同じIVを使いまわすと秘密鍵漏洩の可能性がある

今回は暗号文とIVをつなげてやり取りするようにしているので、暗号文は平文より必ず12Bytes多くなってしまう(タグもあるのでもっと増えるけど)
これがキツいような用途では前のIVから決まった変化をするような仕組みを用意しても良いが、一度出た値が二度と出ないように注意しないといけないことと、ズレると復号できなくなるので、エラー処理とか面倒になる可能性が高いことを考慮しないといけない

あと、Javaではタグがつながって返ってくるので、C#で使うときは分離する必要がある(後ろの16Bytesがタグ)

コードとしては単純でこれだけ、aesKey は前回の鍵交換で取得したもの

public byte[] encryptAes(byte[] src)
{
    try {
        // IV を生成(IVは12バイトが推奨されているらしい)
        byte[] nonce = new byte[12];
        new SecureRandom().nextBytes(nonce);
        
        // GCMParameterSpec を生成(128はタグの長さ)
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
        // 暗号化
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), spec);
        // IV とつなげて返す(これを送信すれば良い)
        return concatBuffer(nonce, cipher.doFinal(src));
    }
    catch (Exception e) {
        Log.write("encryptAes exception: " + e.toString());
        return null;
    }
}

// 2つの byte[] を連結
// なんか分かりやすくてお気に入りなので ByteArrayOutputStreamを使っているけど、多分 System.arraycopy とかの方が速い
private static byte[] concatBuffer(byte[] src1, byte[] src2)
{
    try {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(src1.length + src2.length);
        bos.write(src1);
        bos.write(src2);
        return bos.toByteArray();
    }
    catch (Exception e) {
        Log.write("concatBuffer exception: " + e.toString());
        return null;
    }
}

暗号化(C#)

こちらもIVを生成して暗号化、つなげるところは同様
ただ、C#だとタグは別扱いしないといけない
aesKey は Java の時と同じ

public byte[] EncryptAes(byte[] src)
{
    // IV を生成
    byte[] nonce = new byte[12];
    RandomNumberGenerator.Fill(nonce);

    // 暗号文用のバッファとタグ用のバッファを別々に用意する
    byte[] ciphertext = new byte[src.Length];
    byte[] tag = new byte[16];

    // 暗号化(16はタグの長さ)
    using var aes = new AesGcm(aesKey, 16);
    aes.Encrypt(nonce, src, ciphertext, tag);

    // 全部つなげて返す(本筋では無いのでパフォーマンス無視なやり方、気になるなら変えましょう)
    byte[] ret_temp = ConcatBuffer(nonce, ciphertext);
    byte[] ret = ConcatBuffer(ret_temp, tag);
    return ret;
}

private static byte[] ConcatBuffer(byte[] src1, byte[] src2)
{
    byte[] result = new byte[src1.Length + src2.Length];
    Buffer.BlockCopy(src1, 0, result, 0, src1.Length);
    Buffer.BlockCopy(src2, 0, result, src1.Length, src2.Length);
    return result;
}

復号化(Java)

IV を分離して、GCMParameterSpecを生成、そして復号という流れ
というわけで、渡す enc_data は上で作ったように IV が先に入っている必要あり
encryptAes の返値をそのまま渡す感じになる

aesKey は暗号化と同じく、鍵交換で取得したもの

public byte[] decryptAes(byte[] enc_data)
{
    try {
        // IV と暗号文を分離
        byte[] nonce = new byte[12];
        System.arraycopy(enc_data, 0, nonce, 0, nonce.length);
        byte[] enc_raw = new byte[enc_data.length - nonce.length];
        System.arraycopy(enc_data, nonce.length, enc_raw, 0, enc_raw.length);

        // GCMParameterSpec を生成して復号化
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(aesKey, "AES"), spec);
        return cipher.doFinal(enc_raw);
    }
    catch (Exception e) {
        Log.write("decryptAes exception: " + e.toString());
        return null;
    }
}

復号化(C#)

.NET ではタグを分離しないといけない、というわけで、IV, 暗号文, タグに分離して使う

public byte[] DecryptAes(byte[] src)
{
    // IV, 暗号文, タグに分離
    byte[] nonce = new byte[12];
    byte[] tag = new byte[16];
    int cipherLen = src.Length - nonce.Length - tag.Length;
    byte[] cipherText = new byte[cipherLen];
    Buffer.BlockCopy(src, 0, nonce, 0, nonce.Length);
    Buffer.BlockCopy(src, nonce.Length, cipherText, 0, cipherLen);
    Buffer.BlockCopy(src, src.Length - tag.Length, tag, 0, tag.Length);

    // 復号化(16はタグの長さ)
    byte[] ret = new byte[cipherLen];
    using var aes = new AesGcm(aesKey, 16);
    aes.Decrypt(nonce, cipherText, tag, ret);
    return ret;
}
2
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?