Question

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.

Was it helpful?

Solution

Let's go digging!

['des-cbc', 'des', 'des-cfb', 'des-ofb', 'des-ecb',
 'des-ede-cbc', 'des-ede', 'des-ede-cfb', 'des-ede-ofb', 
 'des-ede3-cbc', 'des-ede3', 'des3', 'des-ede3-cfb', 
 'des-ede3-ofb', 'desx'].each do |flavour|
  begin
    c = OpenSSL::Cipher.new flavour
    c.encrypt
    c.key = '16_byte_string_k'
    c.iv = 'xxxxxxxx'
    str = 'abcdefgh'
    enc = c.update(str) + c.final
    puts "#{flavour} gives us #{enc.unpack('H*')}"
  rescue => e
    puts "#{flavour} didn't work because #{e.message}"
  end
end

The results:

des-cbc gives us ["a5cfc96485d7203eb929c28ceb9fcd53"]
des gives us ["a5cfc96485d7203eb929c28ceb9fcd53"]
des-cfb gives us ["d898369e91589ae8"]
des-ofb gives us ["d898369e91589ae8"]
des-ecb gives us ["de8579b342a528b6143594946045d91a"]
des-ede-cbc gives us ["23ff779e88e2dbe1c009dc3105d8ff88"]
des-ede gives us ["0e589e3d85ac83efbb271a2e4a77cf4e"]
des-ede-cfb gives us ["1618988004b6a948"]
des-ede-ofb gives us ["1618988004b6a948"]
des-ede3-cbc didn't work because key length too short
des-ede3 didn't work because key length too short
des3 didn't work because key length too short
des-ede3-cfb didn't work because key length too short
des-ede3-ofb didn't work because key length too short
desx didn't work because key length too short

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.

['des-cbc', 'des', 'des-cfb', 'des-ofb', 'des-ecb',
 'des-ede-cbc', 'des-ede', 'des-ede-cfb', 'des-ede-ofb', 
 'des-ede3-cbc', 'des-ede3', 'des3', 'des-ede3-cfb', 
 'des-ede3-ofb', 'desx'].each do |flavour|
  begin
    c = OpenSSL::Cipher.new flavour
    c.encrypt
    c.key = '16_byte_string_k'
    c.iv = 'xxxxxxxx'
    c.padding = 0 # This is the important part!
    str = 'abcdefgh'
    enc = c.update(str) + c.final
    puts "#{flavour} gives us #{enc.unpack('H*')}"
  rescue => e
    puts "#{flavour} didn't work because #{e.message}"
  end
end

...
des-ede-cbc gives us ["23ff779e88e2dbe1"]
...

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

OTHER TIPS

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:

['des-cbc', 'des', 'des-cfb', 'des-ofb', 'des-ecb',
 'des-ede-cbc', 'des-ede', 'des-ede-cfb', 'des-ede-ofb', 
 'des-ede3-cbc', 'des-ede3', 'des3', 'des-ede3-cfb', 
 'des-ede3-ofb', 'desx'].each do |flavour|
  begin
    c = OpenSSL::Cipher.new flavour
    c.encrypt
    c.key = '16_byte_string_k'
    c.iv = ['0123456789abcdef'].pack('H*') # Required if no IV is set in Oracle!
    c.padding = 0
    str = 'abcdefgh'
    enc = c.update(str) + c.final
    puts "#{flavour} gives us #{enc.unpack('H*')}"
  rescue => e
    puts "#{flavour} didn't work because #{e.message}"
  end
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top