delphiencryptionvcllockbox-3

Unable to decrypt XXTEA encrypted string using LockBox3


I am currently testing Lockbox3, and have run into a problem trying to do a simple string encryption & decryption using the XXTEA cipher. I'm using Delphi 10.1 Berlin and installed the latest version of Lockbox3 using GetIt! My test app is targeted for Win32 (VCL).

The issue is that the encryption works fine, but when I attempt to decrypt the string, an exception is thrown : "No mapping for the Unicode character exists in the target multi-byte code page.". I'm not sure what the issue is?

procedure TForm1.btnEncryptClick(Sender: TObject);
var
    PlainText : string;
   CipherText : string;
    CryptoLib : TCryptographicLibrary;
        Codec : TCodec;
begin
    try
      CryptoLib := TCryptographicLibrary.Create(nil);
      Codec := TCodec.Create(nil);
      Codec.CryptoLibrary := CryptoLib;
      Codec.StreamCipherId := 'native.XXTEA.Large.Littleend';
      Codec.ChainMode := 'native.CBC';
      Codec.Password := 'password';
      PlainText := Edit1.Text;
      Codec.EncryptString( PlainText, CipherText, Tencoding.UTF8 );
      Codec.Burn;
    finally
      Codec.Free;
      CryptoLib.Free;
      Edit2.Text := CipherText;
    end;
end;

procedure TForm1.btnDecryptClick(Sender: TObject);
var
     PlainText : string;
    CipherText : string;
     CryptoLib : TCryptographicLibrary;
         Codec : TCodec;
begin
    try
      CryptoLib := TCryptographicLibrary.Create(nil);
      Codec := TCodec.Create(nil);
      Codec.CryptoLibrary := CryptoLib;
      Codec.StreamCipherId := 'native.XXTEA.Large.Littleend';
      Codec.ChainMode := 'native.CBC';
      Codec.Password := 'password';
      CipherText := Edit2.Text;
      Codec.DecryptString( PlainText, CipherText, Tencoding.UTF8 );
      Codec.Burn;
    finally
      Codec.Free;
      CryptoLib.Free;
      Edit1.Text := PlainText;
    end;
end;

Solution

  • There are two issues. The first is that there is a defect in the XXTEA decryptor. If you can't wait for the fix to be published, I have posted a code delta that you can apply and recompile for an immediate solution. I am a bit surprised that there are no unit tests for XXTEA. Everything else in this library has plenty of unit tests, so I will work on that too.

    The second issue is that the codec property names that you should set when doing it at runtime are slightly different from the design-time property names. (ChainModeId not ChainMode). The difference was made to make design-time property configuration easier.

    Any way, I did some testing on Lockbox-3 v3.7.0 . The home site can be found at http://lockbox.seanbdurkin.id.au/HomePage and the source code from https://github.com/SeanBDurkin/tplockbox . I tested with Delphi 10.2 Tokyo; Target=Win32.

    Listing one gives the test program (a one-file console program), and Listing two gives the fix. The fix is an update to method procedure TXXTEA_LargeBlock_LE_Decryptor.End_Decrypt() which can be found in unit TPLB3.XXTEA . The GetIt version will have slightly different unit names. That being said, in relation to XXTEA, it might be safer to wait until unit tests have been published.

    Also, if any one wants to help out by contributing Known-Answer-Tests (KATs), that would be good for all.

    Listing One: A Test program

    program TestLockBox;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils,
      TPLB3.CryptographicLibrary,
      TPLB3.Codec,
      TPLB3.Random,
      TPLB3.Constants;
    
    var
      sMessage: string;
      sCipher: string;
      sRecon: string;
    
    const
          CipherId    = XXTEA_Large_ProgId;  IsBlockMode = False;
      //  CipherId    = Twofish_ProgId;      IsBlockMode = True;
      ChainMode   = 'native.CBC';
      Password    = 'password';
      Seed        = 1;
    
    function TForm1_btnEncryptClick( const sPlainText: string): string;
    var
        PlainText : string;
       CipherText : string;
        CryptoLib : TCryptographicLibrary;
            Codec : TCodec;
    begin
        try
          CryptoLib := TCryptographicLibrary.Create(nil);
          Codec := TCodec.Create(nil);
          Codec.CryptoLibrary := CryptoLib;
          if IsBlockMode then
              begin
              Codec.StreamCipherId := BlockCipher_ProgId;
              Codec.BlockCipherId  := CipherId
              end
            else
              Codec.StreamCipherId := CipherId;
          Codec.ChainModeId    := ChainMode;
          Codec.Password := Password;
          PlainText := sPlainText;
          Codec.EncryptString( PlainText, CipherText, Tencoding.UTF8 );
          Codec.Burn;
        finally
          Codec.Free;
          CryptoLib.Free;
          result := CipherText;
        end;
    end;
    
    function TForm1_btnDecryptClick( const sCipherText: string): string;
    var
         PlainText : string;
        CipherText : string;
         CryptoLib : TCryptographicLibrary;
             Codec : TCodec;
    begin
        try
          CryptoLib := TCryptographicLibrary.Create(nil);
          Codec := TCodec.Create(nil);
          Codec.CryptoLibrary := CryptoLib;
          if IsBlockMode then
              begin
              Codec.StreamCipherId := BlockCipher_ProgId;
              Codec.BlockCipherId  := CipherId
              end
            else
              Codec.StreamCipherId := CipherId;
          Codec.ChainModeId    := ChainMode;
          Codec.Password := Password;
          CipherText := sCipherText;
          Codec.DecryptString( PlainText, CipherText, Tencoding.UTF8 );
          Codec.Burn;
        finally
          Codec.Free;
          CryptoLib.Free;
          result := PlainText;
        end;
    end;
    
    begin
      if Seed = -1 then
          TRandomStream.Instance.Randomize
        else
          TRandomStream.Instance.Seed := Seed;
      try
        sMessage := 'Your lips are smoother than vasoline.';
        WriteLn( 'Plaintext="' + sMessage + '"');
        sCipher  := TForm1_btnEncryptClick( sMessage);
        WriteLn( 'Base64 representation of ciphertext=' + sCipher);
        sRecon := TForm1_btnDecryptClick( sCipher);
        WriteLn( 'The reconstructed plaintext = "' + sRecon + '"');
        if sMessage = sRecon then
            WriteLn( 'Test result = PASS')
          else
            WriteLn( 'Test result = FAIL')
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      WriteLn('Press Enter to close the program.');
      ReadLn;
    end.
    

    Listing Two: The fix

    procedure TXXTEA_LargeBlock_LE_Decryptor.End_Decrypt;
    var
      RequiredSizeDecrease: integer;
      L: integer;
      PlaintextArray, CiphertextArray: TLongWordDynArray;
    begin
    if FisBuffering then
        begin
        if FBufLen = 0 then exit;
    //    c <= 211 ==>
    //      2.2.1 Decrypt the message as one XXTEA block.
    //      2.2.2 De-salt the decrypted plaintext. That is to say discard the last
    //        8 bytes at the head of the decrypted plaintext.
    //      2.2.3 De-pad the message out at the tail. The number of pad bytes to
    //        remove is the value of the last byte.
        L := FBufLen div 4;
        SetLength( CiphertextArray, L); // Setup longword array.
        SetLength( PlaintextArray , L);
          begin
          // XXTEA only valid if blocksize is at least 2 longwords.
          // With the padding, this should ALWAYS be the case.
          // Otherwise the ciphertext message is invalid.
          Move( FBuffer[0], CiphertextArray[0], FBufLen); // Convert padded message to longwords.
          XXTEA_Decrypt( FKey.FNativeKey, CiphertextArray, PlaintextArray); // One-block encryption.
          end;
        FBufLen := L * 4;
        if FBufLen >= 8 then
            Dec( FBufLen, 8) // de-salt
          else
            FBufLen := 0;
        if FBufLen > 0 then  // Calculate pad.
            begin
            if Length( FBuffer) < FBufLen then
              SetLength( FBuffer, FBufLen);
            Move( PlaintextArray[0], FBuffer[0], FBufLen);
            RequiredSizeDecrease := FBuffer[FBufLen-1]
            end
          else
            RequiredSizeDecrease := 0;
        if FBufLen >= RequiredSizeDecrease then
            Dec( FBufLen, RequiredSizeDecrease) // de-pad
          else
            FBufLen := 0;
        if FBufLen > 0 then
          FPlainText.Write( FBuffer[0], FBufLen)
        end
      else
        begin
        FFixedDec.End_Decrypt;
        FOutputBuffer.EndStreaming; // Discard last 12 bytes
        FreeAndNil( FOutputBuffer)
        end;
    FFixedDec := nil;
    FFixedCipher := nil
    end;
    

    The next version will be 3.8.0 and will include the XXTEA fix and XXTEA unit tests. I can't give a schedule for it.