javarubyencryptionaes-gcm

Ruby: Decrypt a message, which is Encrypted using Java (AES/256/GCM),


I have a code in Java that encrypts a message using AES-256-GCM. I want to write a code in Ruby that does the same encryption and also the decryption.

Java Code:

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);

Please help me to write the same implementation in Ruby (Encryption and Decryption)

I tried the below code for decryption in Ruby. This gives the error,

(irb):99:in final': OpenSSL::Cipher::CipherError`

Ruby Code Decryption:

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')

Solution

  • As explained in the comments, the Ruby code uses the wrong key and lacks the detachment of the authentication tag (unlike Java, Ruby/OpenSSL processes tag and ciphertext separately):

    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
    

    Here you can find an example from the Ruby/OpenSSL documentation for authenticated encryption with AES/GCM.