I have a legacy application written in PL/SQL that encrypts and decrypts data using 3DES. Now I need to perform similar encryption from a ruby app. Eventually the resulting hash will need to be decrypted by the same PL/SQL application using its existing algorithm.
The problem is that I'm obtaining different encrypted results in PL/SQL and Ruby and I don't know why.
First here is exactly how the PL/SQL encryption works:
From Oracle's docs about DBMS_OBFUSCATION_TOOLKIT http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_obtool.htm
"Oracle's implementation of 3DES supports either a 2-key or 3-key implementation, in outer cipher-block-chaining (CBC) mode."
Function signature:
DBMS_OBFUSCATION_TOOLKIT.DES3Encrypt(
input_string IN VARCHAR2,
key_string IN VARCHAR2,
encrypted_string OUT VARCHAR2,
which IN PLS_INTEGER DEFAULT TwoKeyMode
iv_string IN VARCHAR2 DEFAULT NULL);
Note about the parameter which: "If = 0, (default), then TwoKeyMode is used. If = 1, then ThreeKeyMode is used." This helped me choose the cipher in the ruby version.
Here is how the application makes that call:
set serveroutput on;
declare
v_encrypted varchar2(100);
begin
dbms_obfuscation_toolkit.des3encrypt(
input_string => 'abcdefgh', -- data to encrypt
key_string => '16_byte_string_k', -- 16 byte = 128 bit key needed by DES3Encrypt
encrypted_string => v_encrypted,
iv_string => 'xxxxxxxx'); -- initialization vector
dbms_output.put_line( lower(utl_raw.cast_to_raw(v_encrypted)) );
-- prints 23ff779e88e2dbe1
end;
Second here is what I'm trying in Ruby:
OpenSSL::Cipher docs: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html
OpenSSL docs to give me the cipher name: From http://www.openssl.org/docs/apps/enc.html "des-ede-cbc Two key triple DES EDE in CBC mode"
require 'openssl'
cipher = OpenSSL::Cipher.new('des-ede-cbc')
cipher.encrypt
input = 'abcdefgh'
cipher.key = '16_byte_string_k'
cipher.iv = 'xxxxxxxx'
# i noticed that cipher.update returns same length hash as PL/SQL
# if called without cipher.final, but you are not supposed to do that
#encrypted = cipher.update(input)
encrypted = cipher.update(input) + cipher.final
hex_representation = encrypted.unpack("H*")
puts hex_representation
# prints a5cfc96485d7203eb929c28ceb9fcd53
As shown in the code the ruby version computes a different hash value. Why? What needs to change to make them consistent?
Points I'm unsure about:
- Whether des-ede-cbc is in fact the same as what Oracle does.
- Whether utl_raw.cast_to_raw and unpack("H*") will do the same thing to the encrypted binary data.
- What exactly cipher.final appends and if there's any equivalent way to append that data in PL/SQL.
Note: I am aware that DES is insecure and that AES has superseded it. My use case does not require these hashes to be unbreakable. The important requirement is to make the hashes consistent so that the PL/SQL app can decrypt hashes generated by the ruby app.
Let's go digging!
The results:
des-ede-cbc
gives you a match--at least the first part matches. The question is, why is the encrypted body longer? I'm going to bet this is the correct content and the PL/SQL version is truncated somehow--I'll see if I can figure it out.Edit: nope, it's the padding. When you set the padding to 0 on the cipher, you get the same results as the PL/SQL version, e.g.
You will need to compare the two algorithms with different lengths of input string now. Take a look at the documentation for the padding method here: http://www.ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html
We had the same problem with one important difference: Our database procedure did not specify an initialization vector (IV) when encrypting the passwords. Omitting the IV in ruby did not lead to the same result as omitting it in the Oracle procedure call, so both seemed to use different "default" IVs.
The default Oracle IV is "0123456789abcdef" hex decoded as some guys figured out here: https://community.oracle.com/thread/1528090
In Ruby you can set it like this: