views:

175

answers:

1

Hey all,

I am a bit stuck with this. I have to interface with an api that uses a version of an encryption algorithm that they seem to have ripped from Typo3 written by Ari Kuorikoski.

I need to create a ruby lib to interface with their api, so have to port their algorithm into ruby, and I am a bit out of my depth when it comes to encryption.

This is the code:

private function keyED($txt) { 
$encrypt_key = md5($this->encrypt_key); 
$ctr=0; 
$tmp = ""; 
for ($i=0;$i<strlen($txt);$i++) { 
   if ($ctr==strlen($encrypt_key)) $ctr=0; 
   $tmp.= substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1); 
   $ctr++; 
} 
return $tmp; 

}

private function encrypt($txt){ 
srand((double)microtime()*1000000); 
$encrypt_key = md5(rand(0,32000)); 
$ctr=0; 
$tmp = ""; 
for ($i=0;$i<strlen($txt);$i++){ 
   if ($ctr==strlen($encrypt_key)) $ctr=0; 
   $tmp.= substr($encrypt_key,$ctr,1) . 
       (substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1)); 
   $ctr++; 
} 
return base64_encode($this->keyED($tmp)); 

}

The part that has me stumped is this, I have to write it for ruby 1.8.6 as that's the server that it will be on. And there's no XOR for strings... Not that I would understand it if there was.

Any help, pointers, ideas would be much much appreciated.

Thanks, J

Addendum:

I realize, I didn't put any code up, the only difficulty is actually the xor problem, but here is my code so far:

def xor(s1,s2)
        if s2.empty? then
            return s1
        else
            a1 = s1.unpack("c*")
            a2 = s2.unpack("c*")
            a2 *= 2 while a2.length < a1.length
            return a1.zip(a2).collect {|c1,c2| c1 ^ c2}.pack("c*")
        end
    end
    def keyED(str)
        encrypt_key = Digest::MD5.digest(@key)
        ctr = 0
        tmp = ''
        for i in 0...str.length do
             ctr = 0 if ctr == encrypt_key.length
             tmp << xor(str.slice(i,1), encrypt_key.slice(ctr,1)).to_s
            ctr = ctr + 1
        end
        return tmp
    end

    # === Ported Code
    # This code was ported from Ari's Typo 3 Session Encryption
    def encrypt(str)
        encrypt_key = Digest::MD5.digest(rand(32000).to_s)
        ctr = 0
        tmp = ''
        for i in 0...str.length do
            ctr=0 if ctr==encrypt_key.length 
            tmp << encrypt_key.slice(ctr,1) << xor(str.slice(i,1), encrypt_key.slice(ctr,1))
            ctr = ctr + 1
        end
        return Base64.encode64(keyED(tmp))
    end
    def decrypt(str)
        txt = keyED(str)
        tmp = ''
        for i in 0...txt.length do 
            md = txt.slice(i,1)
            i = i + 1
            tmp << xor(txt.slice(i,1),md)
        end
        puts "Decrypte string:#{Base64.decode64(tmp)}EOSTRING"
    end

Update:

Final working source, based on James helpful answer, major props! And to David Garamound.

def xor(s1,s2)
    raise ArgumentError, "Can't bitwise-XOR a String with a non-String" unless s2.kind_of? String
    raise ArgumentError, "Can't bitwise-XOR strings of different length" unless s1.length == s2.length
    (0..s1.length-1).collect { |i| s1[i] ^ s2[i] }.pack("C*")
end

def keyED(txt,key)
    ctr,tmp = 0,''
    key = Digest::MD5.hexdigest(key)
    for i in 0...txt.length do
        ctr = 0 if ctr == key.length
        str = xor(txt.slice(i,1),key.slice(ctr,1))
        tmp << str
        ctr = ctr + 1
    end

    return tmp
end
def encrypt(txt,key)
    ctr,tmp = 0,''
    ekey = Digest::MD5.hexdigest(rand(32000).to_s)
    for i in 0...txt.length do
        ctr = 0 if ctr == ekey.length
        str = xor(txt.slice(i,1), ekey.slice(ctr,1))
        tmp << ekey.slice(ctr,1) << str
        ctr = ctr + 1
    end
    return Base64.encode64(keyED(tmp,key))
end
+2  A: 

From Erik Veenstra:

class String
  def xor(other)
    if other.empty?
      self
    else
      a1        = self.unpack("c*")
      a2        = other.unpack("c*")
      a2 *= 2   while a2.length < a1.length
      a1.zip(a2).collect{|c1,c2| c1^c2}.pack("c*")
    end
  end
end

Then your code becomes

tmp << str.slice(i,1).xor(encrypt_key.slice(i,1))

An alternate implementation of String#xor, as suggested by jolierouge and David Garamond:

class String
  def xor(other)
    raise ArgumentError, "Can't bitwise-XOR a String with a non-String" unless other.kind_of? String
    raise ArgumentError, "Can't bitwise-XOR strings of different length" unless self.length == other.length
    (0..self.length-1).collect { |i| self[i] ^ other[i] }.pack("C*")
  end
end
James A. Rosen
I tried a variant of this, the issue is, I don't have the knowledge to explain why it doesn't work int the same way the the PHP xor on a string works.I need it to do exactly what it's doing in the PHP function, without variation. I have written a decrypt function, ported also from the php, I should be able to put a string in, and get that same string out.
Or something else could be wrong, I'll update the ruby source thus far.
I'll post the update and working source for others to peruse.