Encryption Algorithm from PHP to Ruby (Vignere variant)
-
20-09-2019 - |
Question
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
Solution
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
OTHER TIPS
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