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