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;
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.
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.
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.