B4J Question [B4X] XOR string encoding/decoding

luc-dev

Member
Licensed User
Hi,

I am trying to write XOR encoding/decoding for B4X (I need to use it as a light weight and fast encoding/decoding cross plateforme solution with B4I, B4A, B4J and Lazarus/FPC). I know XOR is not the best/strongest encryption solution but it should be ok for my needs).

The following code works but need Base64 encoding first (I was not able to directly encode a string with XOR when the initial string has non-ascii chars ie UTF-8).

It would be great if someone knows a way to avoid the base64 encoding and help to increase the speed of this code :)

B4X:
Sub Button1_Click
    Dim str As StringUtils
    Dim aStr1 As String
    Dim aStr2 As String
    Dim b As ByteConverter
    Dim By() As Byte
    Dim DT As Long = DateTime.Now
       
    For i = 0 To 1000000
        aStr1 = str.EncodeBase64("ABCéè§ê:/%$€".GetBytes("UTF-8"))
        aStr1 = XorEncode("MyKey", aStr1)
        aStr2 = XorDecode("MyKey", aStr1)
        By = str.DecodeBase64(aStr2)
        aStr2 = B.StringFromBytes(By, "UTF-8")
    Next
   
    Log("Ellapsed Time: " & (DateTime.Now - DT))
End Sub

Sub XorEncode(Key As String, Source As String) As String
     Dim b As ByteConverter
     Dim IntVal As Int
     Dim IntVal As Int
     Dim aStrVal As String
     Dim aResult As StringBuilder
     aResult.Initialize
     Dim S1() As Byte = b.StringToBytes(Source, "UTF-8")
     Dim k1() As Byte = b.StringToBytes(Key, "UTF-8")

     For i = 0 To S1.Length-1
         IntVal = Bit.Xor(k1(i Mod k1.Length), S1(i))
         aStrVal = Bit.ToHexString(IntVal)
         If aStrVal.Length = 1 Then aStrVal = "0" & aStrVal
         aResult.Append(aStrVal)
     Next
     Return aResult.ToString
End Sub

Sub XorDecode(Key As String, Source As String) As String
      Dim b As ByteConverter
      Dim aResult As String = ""
      Dim S1() As Byte = b.HexToBytes(Source)
      Dim k1() As Byte = b.StringToBytes(Key, "UTF-8")
      Dim ByteVals(S1.Length) As Byte
     
      For i = 0 To S1.Length -1
          ByteVals(i) = Bit.Xor(k1(i Mod k1.Length), S1(i))
      Next
      aResult = BytesToString(ByteVals, 0, ByteVals.Length, "UTF-8")
      Return aResult
End Sub
Thanks!
 

MarkusR

Well-Known Member
Licensed User
if you encode a string=bytes with xor and other bytes (as key) the result is a byte array.
 

luc-dev

Member
Licensed User
Thanks Markus,

In fact, XorDecode works even without Base64 but XorEncode only works when the Source String doesn't contain non-ascii chars. When Source String has non-ascii chars (UTF-8), EncodeBase64 is the only solution found so far...
 

MarkusR

Well-Known Member
Licensed User
i meant if you modify a string with xor its not a valid utf8 string output.
it looks like this
string->bytes->xor->bytes
bytes->xor->bytes->string
 

luc-dev

Member
Licensed User
The main problem here (I think) is in the string->bytes conversion in XorEncode. The string is converted to Byte() with
Dim S1() As Byte = b.StringToBytes(Source, "UTF-8")
but the resulting Byte array contains negative values.
Here is the log output of S1(0) to S1(10) when calling XorEncode("MyKey", "ABCéè§ê:/%$€") (without EncodeBase64)

S1(0) = 65 (A)
S1(1) = 66 (B)
S1(2) = 67 (C)
S1(3) = -61
S1(4) = -87
S1(5) = -61
S1(6) = -88
S1(7) = -62
S1(8) = -89
S1(9) = -61
S1(10) = -86

"ABC" is correct but "éè§ê:/%$€" are not. Did you try the code?
 

MarkusR

Well-Known Member
Licensed User
this xor all with 88 as example, this 88 can be replaced with a key value in the iteration
B4X:
Sub Button1_Click
  
    Dim Bytes() As Byte
    Bytes = XorEncode("ABCéè§ê:/%$€")
    Log(XorDecode(Bytes))
 
End Sub

Sub XorEncode(Source As String) As Byte()

    Dim Bytes() As Byte
    Bytes = Source.GetBytes("UTF8")

    Dim i As Int
    For i = 0 To Bytes.Length-1
        Bytes(i) = Bit.Xor(Bytes(i),88)
    Next

    Return Bytes

End Sub

Sub XorDecode(Bytes() As Byte) As String
  
    Dim i As Int   
    For i = 0 To Bytes.Length -1
        Bytes(i) = Bit.Xor(Bytes(i),88)
    Next

    Return BytesToString(Bytes, 0, Bytes.Length, "UTF-8")

End Sub
Plan B if you need to store the encryped bytes as base 64 string
used lib b4xencryption
Password (=Key) is a global variable used inside this subs
and its insecure same xor
B4X:
Sub EncryptText(text As String) As String

    Dim c As B4XCipher

    Dim e() As Byte
    e = c.Encrypt(text.GetBytes("utf8"), Password)
       
    Dim su As StringUtils
       
    Return su.EncodeBase64(e)

End Sub

Sub DecryptText(textb64 As String) As String

    Try
        Dim c As B4XCipher
        Dim su As StringUtils
        Dim a() As Byte = su.DecodeBase64(textb64)
        If a.Length=0 Then Return ""
        Dim b() As Byte = c.Decrypt( a, Password)
           
        Return BytesToString(b, 0, b.Length, "UTF-8")
    Catch
            Log(LastException)
            Return ""
    End Try

End Sub
 
Last edited:

luc-dev

Member
Licensed User
Thank you Markus for spending your time trying to solve this.
I did further testing but I didn't get the expected results :(

Your XorEncode is fine but I need to store the result in a sqlite database, so I need to get a string from XorEncode, not Byte().

Plan B could be fine too but unfortunately I need to be able to read and write the encrypted data from Lazarus/FPC (I can't use B4XCipher)

Here is the code used in FPC, in case someone has a way to solve the XorEncode but I don't think this can be done with B4J (No offense here, it is just two different ways to work with strings):

B4X:
function XorEncode(const Key, Source: string): string;

var
  i: Integer;
  C: Byte;

begin
  Result:='';
  for i:=1 to Length(Source) do
    begin
    if Length(Key) > 0 then
      C:=Byte(Key[1 + ((i - 1) mod Length(Key))]) xor Byte(Source[i])
    else
      C:=Byte(Source[i]);
    Result:=Result+AnsiLowerCase(intToHex(C, 2));
    end;
end;
 

MarkusR

Well-Known Member
Licensed User
and this?

string->bytes->xor->bytes->hex string
hex string->bytes->xor->bytes->string

your FPC seems just output the bytes as hex string.
typically you would store bytes in sqlite in a blob (binary) type field.

B4X:
Sub Button1_Click
    
    Dim HexString As String
    HexString =  XorEncode("ABCéè§ê:/%$€")
    Log(HexString)
      
    Log(XorDecode(HexString))
 
End Sub

Sub XorEncode(Source As String) As String

    Dim b As ByteConverter

    Dim Bytes() As Byte
    Bytes = Source.GetBytes("UTF8")

    Dim i As Int
    For i = 0 To Bytes.Length-1
        Bytes(i) = Bit.Xor(Bytes(i),88)
    Next

    Return b.HexFromBytes(Bytes)

End Sub

Sub XorDecode(HexString As String) As String
 
    Dim b As ByteConverter
 
    Dim Bytes() As Byte
    Bytes = b.HexToBytes(HexString)
 
    Dim i As Int
    For i = 0 To Bytes.Length -1
        Bytes(i) = Bit.Xor(Bytes(i),88)
    Next

    Return BytesToString(Bytes, 0, Bytes.Length, "UTF-8")

End Sub
 

OliverA

Expert
Licensed User
Lazarus/FPC (I can't use B4XCipher)
As long as you have an encryption library that can do AES, you can re-implement B4XCipher in PFC/Lazarus. The source (Java) for B4XCipher can be found here https://github.com/AnywhereSoftware...nywheresoftware/b4x/object/B4XEncryption.java.
but the resulting Byte array contains negative values.
That should not matter. Java sees it as signed, but the internal representation (the bit format of the byte) is still what it should be. Just when you try to log the byte with Java, you'll get a signed output. For unsigned output just add 256 to negative values when logging (just add it in the log statement, not directly to the byte).
I need to get a string from XorEncode
If you need string, @MarkusR gave you two options: Base64 and Hex. Base64 will increase the size by about 33% and Hex will double the size of your string. Hex may be faster to produce (you will need to test this), but if you need to read/write data from/to disk or sent it across the network, then the amount of data (less) may be more important (disk and network are way slower than memory. Again test, test, test).
 

luc-dev

Member
Licensed User
Many thanks to @MarkusR and @OliverA for your help!

I finally managed to have it working (I need further testing but I didn't get any error so far).

Here is the resulting code in case it can help someone else:

for B4J:

B4X:
Sub Button1_Click
    Dim aStr1 As String
    Dim aStr2 As String
    aStr1 = XorEncode("MyKey", "ABCéè§ê:/%$€")
    Log(aStr1)
    aStr2 = XorDecode("MyKey", aStr1)
    Log(aStr2)
End Sub

Sub XorEncode(Key As String, Source As String) As String
    Dim b As ByteConverter
    Dim S1() As Byte = Source.GetBytes("UTF-8")
    Dim k1() As Byte = Key.GetBytes("UTF-8")
    Dim aResult As StringBuilder
    aResult.Initialize
    For i = 0 To S1.Length-1
        S1(i) = Bit.Xor(k1(i Mod k1.Length), S1(i))
    Next
    Return b.HexFromBytes(S1)
End Sub

Sub XorDecode(Key As String, Source As String) As String
    Dim b As ByteConverter
    Dim aResult As String = ""
    Dim S1() As Byte = b.HexToBytes(Source)
    Dim k1() As Byte = b.StringToBytes(Key, "UTF-8")
     
    For i = 0 To S1.Length -1
        S1(i) = Bit.Xor(k1(i Mod k1.Length), S1(i))
    Next
    aResult = BytesToString(S1, 0, S1.Length, "UTF-8")
    Return aResult
End Sub
For Lazarus or Delphi (to be check for Delphi and UTF-16 Encoding):
B4X:
function XorEncode2(const Key, Source: string): string;
var
  i: Integer;
  C: Byte;
begin
  Result:='';
  for i:=1 to Length(Source) do
    begin
      C:=Byte(Key[1 + ((i - 1) mod Length(Key))]) xor Byte(Source[i]);
      Result:=Result + (intToHex(C, 2));
    end;
end;

function XorDecode2(const Key, Source: string): string;
var
  i: Integer;
  C: Char;
begin
  Result:='';
  for i:=0 to Length(Source) div 2 - 1 do
    begin
    C:=Chr(StrTointDef('$' + Copy(Source, (i * 2) + 1, 2), Ord(' ')));
    C:=Chr(Byte(Key[1 + (i mod Length(Key))]) xor Byte(C));
    Result:=Result + C;
    end;
end;
The code for lazarus is merely a simplified copy of the original XorEncode and XorDecode functions from freepascal RTL.

B4J is very, very fast doing the encoding/decoding (I was quite impressed as it seems to be faster than freepascal)

Thanks again for this great forum and support!

Luc
 
Top