2

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.

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
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
J. Martin
  • 1,683
  • 2
  • 17
  • 33
  • One thing I did find, is that you can get a Fixnum by trying str[i] and encrypt_key[ctr], and perform an xor on those...That didn't work either, but I may have fudged it elsewhere in the code. – J. Martin Jan 13 '10 at 19:20
  • As an update, I manages to fix the problem by creating a tokenmaker.php file with the working php functions, and calling it using net/http. I hate doing it this way, but I am on a schedule. I would really really love to find a solution to this problem. – J. Martin Jan 13 '10 at 19:54

2 Answers2

2

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
Community
  • 1
  • 1
James A. Rosen
  • 64,193
  • 61
  • 179
  • 261
  • 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. – J. Martin Jan 13 '10 at 19:15
  • Or something else could be wrong, I'll update the ruby source thus far. – J. Martin Jan 13 '10 at 19:16
  • I'll post the update and working source for others to peruse. – J. Martin Jan 14 '10 at 14:42
0

Editor's Note: The final working code was moved from the question to its own answer.

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
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52