Techioz Blog

Ruby: Java (AES/256/GCM) を使用して暗号化されたメッセージを復号化します。

概要

AES-256-GCM を使用してメッセージを暗号化する Java コードがあります。同じ暗号化と復号化を行うコードを Ruby で書きたいと考えています。

Java コード:

import java.util.Base64;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.spec.KeySpec;
import java.security.SecureRandom;
// ----------------------

String plainMessage = "Hello Java";
String password = "password";

byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);

KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey secretKey =  SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));

byte[] encryptedMessageByte = cipher.doFinal(plainMessage.getBytes(StandardCharsets.UTF_8));

byte[] cipherByte = ByteBuffer.allocate(iv.length + salt.length + encryptedMessageByte.length)
                .put(iv)
                .put(salt)
                .put(encryptedMessageByte)
                .array();

String encryptedMessageBase64 = Base64.getEncoder().encodeToString(cipherByte);

同じ実装を Ruby で書くのを手伝ってください (暗号化と復号化)

Rubyで復号化するために以下のコードを試してみました。これによりエラーが発生します。

Rubyで復号化するために以下のコードを試してみました。これによりエラーが発生します。

Rubyコードの復号化:

require 'base64'
require 'openssl'

cipher_message = "encrypted-message-base64"
password = "password"
decoded_cipher_byte = Base64.decode64(cipher_message)
byte_buffer = StringIO.new(decoded_cipher_byte)

iv = byte_buffer.read(12)
salt = byte_buffer.read(16)
encrypted_byte = byte_buffer.read

spec = OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: 65536, length: 32, hash: 'sha256')
secret_key = OpenSSL::Cipher.new('AES').tap { |c| c.key = spec }.random_key

cipher = OpenSSL::Cipher.new('AES-GCM')
cipher.decrypt
cipher.iv = iv
cipher.key = secret_key

decrypted_message_byte = cipher.update(encrypted_byte) + cipher.final
msg = decrypted_message_byte.force_encoding('UTF-8')

解決策

コメントで説明されているように、Ruby コードでは間違ったキーが使用されており、認証タグが分離されていません (Java とは異なり、Ruby/OpenSSL はタグと暗号文を個別に処理します)。

require 'base64'
require 'openssl'
require 'stringio'

cipher_message = "ngii4DerThzOaBfFt93zNgxY4kJVC7V+m92buC++OQC+OcKHA/FP1u0mwTIAOlexDJM/+xA0LRarIfXE7Qr7IkEfy/V8m37qqR3twrtKghexlV0oNOab" # generated with the Java code
password = "password"
decoded_cipher_byte = Base64.decode64(cipher_message)

# separate IV, salt, ciphertext and tag
byte_buffer = StringIO.new(decoded_cipher_byte)
ivSize = 12
saltSize = 16
tagSize = 16
ciphertextSize = decoded_cipher_byte.length - ivSize - saltSize - tagSize
iv = byte_buffer.read(ivSize)
salt = byte_buffer.read(saltSize)
encrypted_byte = byte_buffer.read(ciphertextSize)
tag = byte_buffer.read

# derive key
secret_key = OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: 65536, length: 32, hash: 'sha256')

# decrypt with AES-256, GCM
cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
cipher.iv = iv
cipher.key = secret_key # Fix 1: apply derived key
cipher.auth_tag = tag # Fix 2: apply detached tag
#cipher.auth_data = auth_data # no AAD
decrypted_message_byte = cipher.update(encrypted_byte) + cipher.final

puts decrypted_message_byte.force_encoding('UTF-8') # The quick brown fox jumps over the lazy dog

ここでは、AES/GCM を使用した認証暗号化の例を Ruby/OpenSSL ドキュメントから見つけることができます。