PDA

View Full Version : Difficulties translating XTea encryption to Pascal



Robert Kosek
11-03-2006, 12:01 AM
For a simple overview and pseudocode on XTea, you can wiki-it (http://en.wikipedia.org/wiki/XTEA).

XTea is supposed to be a simple encryption algorithm, and it is mathematically. But my implementation is going wrong somewhere in the encryption and possibly decryption phases. Very wrong. Like it could simply crash silently on me, or the encryption "halts" part way through. It seems to be something in relation to password length and content length. It might work with a 16 character password, and a short paragraph, but change either around and it'll go screwy in no time flat!

As you can guess it's got me fairly confused. :oops: I have to thank Lifepower a lot just for helping me this far along! Any further help from ya'll will really be appreciated. I'll be trying to juggle the discussion here and at Afterwarp, so just know that unless you check the thread there (http://www.afterwarp.net/forum/showthread.php?p=2694#post2694), you might not be up to date on the problem.

Here's the source. To use it just make a TXTea object, create it, Init the key, Encrypt/Decrypt then burn it.


(************************************************* *****************************)
(* Translated from C into Object Pascal by Robert Kosek with much help from *)
(* Yuriy Kotsarenko. Please leave this header in any subsequent distribution *)
(* and maybe mention us in the credits of whatever uses this. Especially *)
(* Yuriy, he did the majority of the work in the end. ;-) *)
(* *)
(* My only stipulation to this is that if you use it, adapt it or change it, *)
(* please let us know and post your changes here: *)
(* http://www.afterwarp.net/forum/thread460.html *)
(************************************************* *****************************)
(* Original code taken from: *)
(* http://en.wikipedia.org/wiki/XTEA *)
(* http://www.irnis.net/soft/xted/ *)
(* *)
(* Our Sites: *)
(* http://afterwarp.net/ *)
(* http://thewickedflea.com/ *)
(************************************************* *****************************)
unit xtea;

interface

uses SysUtils;

type
PKey128 = ^TKey128;
TKey128 = array [0..3] of longword;

PBlock64 = ^TBlock64;
TBlock64 = array [0..1] of longword;

TXTea = class(TObject)
private
FKey: TKey128;
FInitialized: Boolean;
public
property Initialized: boolean read FInitialized default false;
procedure Burn;
procedure InitKey(const Key: string);
function Encrypt(const Input: string): string;
function Decrypt(const Input: string): string;
destructor Destroy; override;
constructor Create;
end;

const
DELTA = $9e3779b9;
XTEA_BLANK: TBlock64 = (0,0);
XTEA_BURN: TKey128 =(0,0,0,0);

implementation

destructor TXTea.Destroy;
begin
Burn;
inherited Destroy;
end;

constructor TXTea.Create;
begin
inherited Create;
Burn;
end;

procedure TXTea.Burn;
begin
FKey := XTEA_BURN;
FInitialized := False;
end;

procedure TXTea.InitKey(const Key: string);
var L: integer;
begin
if Length&#40;Key&#41; <= SizeOf&#40;FKey&#41; then
L &#58;= Length&#40;Key&#41;
else
L &#58;= SizeOf&#40;FKey&#41;;

Move&#40;Key&#91;1&#93;,FKey&#91;0&#93;,L&#41;;

FInitialized &#58;= True;
end;

function TXTea.Encrypt&#40;const Input&#58; string&#41;&#58; string;
var i,l,n,b&#58; integer;
v&#58; TBlock64;

procedure CipherXTea&#40;v&#58; PBlock64; key&#58; PKey128&#41;;
var sum&#58; longword;
i&#58; integer;
begin
Sum&#58;= 0;

for i&#58;= 0 to 31 do
begin
Inc&#40;v&#91;0&#93;, &#40;&#40;v&#91;1&#93; shl 4 xor v&#91;1&#93; shr 5&#41; + v&#91;1&#93;&#41; xor &#40;Sum + Key&#91;Sum and 3&#93;&#41;&#41;;
Inc&#40;Sum, Delta&#41;;
Inc&#40;v&#91;1&#93;, &#40;&#40;v&#91;0&#93; shl 4 xor v&#91;0&#93; shr 5&#41; + v&#91;0&#93;&#41; xor &#40;Sum + Key&#91;Sum shr 11 and 3&#93;&#41;&#41;;
end;
end;
begin
if not Initialized then
raise Exception.Create&#40;'Error&#58; You must define a password.'&#41;;

b &#58;= SizeOf&#40;v&#41;;
l &#58;= Length&#40;Input&#41;;
SetLength&#40;Result,l&#41;;

i &#58;= 1;
while i < l do
try
v &#58;= XTEA_BLANK;
n &#58;= l - &#40;i-1&#41;;

if n > b then
Move&#40;Input&#91;i&#93;,v&#91;0&#93;,b&#41;
else
Move&#40;Input&#91;i&#93;,v&#91;0&#93;,n&#41;;

CipherXTea&#40;@v,@FKey&#41;;

Move&#40;v&#91;0&#93;,Result&#91;i&#93;,b&#41;;
inc&#40;i,8&#41;;
except
raise;
end;
end;

function TXTea.Decrypt&#40;const Input&#58; string&#41;&#58; string;
var i,l,n,b&#58; integer;
v&#58; TBlock64;

procedure DecipherXTea&#40;v&#58; PBlock64; Key&#58; PKey128&#41;;
var
i&#58; Integer;
Sum&#58; Longword;
begin
Sum&#58;= $C6EF3720;

for i&#58;= 0 to 31 do
begin
Dec&#40;v&#91;1&#93;, &#40;&#40;v&#91;0&#93; shl 4 xor v&#91;0&#93; shr 5&#41; + v&#91;0&#93;&#41; xor &#40;Sum + Key&#91;Sum shr 11 and 3&#93;&#41;&#41;;
Dec&#40;Sum, Delta&#41;;
Dec&#40;v&#91;0&#93;, &#40;&#40;v&#91;1&#93; shl 4 xor v&#91;1&#93; shr 5&#41; + v&#91;1&#93;&#41; xor &#40;Sum + Key&#91;Sum and 3&#93;&#41;&#41;;
end;
end;
begin
if not Initialized then
raise Exception.Create&#40;'Error&#58; You must define a password.'&#41;;

b &#58;= SizeOf&#40;v&#41;;
l &#58;= Length&#40;Input&#41;;
SetLength&#40;Result,l&#41;;

i &#58;= 1;
while i < l do
try
v &#58;= XTEA_BLANK;
n &#58;= l - &#40;i-1&#41;;

if n > b then
Move&#40;Input&#91;i&#93;,v&#91;0&#93;,b&#41;
else
Move&#40;Input&#91;i&#93;,v&#91;0&#93;,n&#41;;

DecipherXTea&#40;@v,@FKey&#41;;

Move&#40;v&#91;0&#93;,Result&#91;i&#93;,b&#41;;
inc&#40;i,8&#41;;
except
raise;
end;
end;

end.

Sly
12-03-2006, 11:22 PM
From a brief look over both your code and Lifepower's, I have these comments.

In your Encrypt and Decrypt methods, you are setting Result to the length of the input string, but inside the loop you overflow the buffer allocated for Result in the last call to Move by up to seven bytes (your last Move is always the size of TBlock64). What you should do is set the length of Result to be the next highest multiple of eight over the length of Input. This is easily achieved with the following
l &#58;= &#40;Length&#40;Input&#41; + 7&#41; and &#40;not 7&#41;;
SetLength&#40;Result, l&#41;;

Edit: Incidentally, the Pascal code linked from that Wiki page does the same thing (in a different way). I'm not sure why you didn't use that code in the first place.

Lifepower's latest version makes a few assumptions about the strings which is why it does not work with ansi or wide strings.

This line
// Step 3. Encrypt source string.
Move&#40;Source, Result, Length&#40;Source&#41; + 1&#41;;is not the correct way to copy a string. This is the primary reason why this routine will not work with ansi or wide strings. Using Result := Source will not work with ansi or wide strings either, because you want Result to be a unique copy of Source (because the Result copy gets modified). Hence you should use a function that generates a unique copy of the string. I believe there is a UniqueStr() function in the RTL or something similar.

In CipherDataXTEA, the loop will not execute at all for strings with less than 8 characters. It will also not encrypt any remaining bytes after the last eight-byte block. The same goes for DecipherDataXTEA.

Also, and this might affect both, I believe that the C >> operator is a logical shift (bits fall off the end) and the Delphi shr operator is an arithmetic shift (bits wrap around). I seem to remember we found this when porting Quake 2 to Delphi.

Robert Kosek
13-03-2006, 12:03 AM
Thanks very much, you've helped me quite a bit. I did not want to use the linked unit because it was really messy and confusing, on top of being non-standard for the encryption. It's not true XTea, but some kind of variant. You can compare the math to the main code on the Wiki page.

I have one question though, and it's really puzzling me. I take and encrypt a block of text bigger than 256 in length, such as your post, and just encrypt with "0123456789abcdef" and the text gets garbled something odd in the middle of the decryption. The encryption stops somewhere in the middle, then during decryption the very last 8 character block is not decrypted. Like so:

Start:
is not the correct way to copy a string. This is the primary reason why this routine will not work with ansi or wide strings. Using Result := Source will not work with ansi or wide strings either, because you want Result to be a unique copy of Source (because the Result copy gets modified). Hence you should use a function that generates a unique copy of the string. I believe there is a UniqueStr() function in the RTL or something similar.

In CipherDataXTEA, the loop will not execute at all for strings with less than 8 characters. It will also not encrypt any remaining bytes after the last eight-byte block. The same goes for DecipherDataXTEA.

Also, and this might affect both, I believe that the C >> operator is a logical shift (bits fall off the end) and the Delphi shr operator is an arithmetic shift (bits wrap around). I seem to remember we found this when porting Quake 2 to Delphi.

Encrypted:
Àú¬æ?ª{?î?? uqo- ?é+L‚Äú?ü?±?üBd
I?Ǭºu¬¨V‚ÄôS?éol&?õ¬æ?ïM?å¬?¬æ/b¬±¬êo¬¨‚Ä¢N.\ r07‚Äò1S¬§¬æ?ìa?•???Ç ?ø[?ß?µ?¢?Ç?æt!‚Ćb¬®‚Ä°Àú^M¬??í(¬±?î??LH¬??ìÀú6KGÀ ܬÅ7?Ä?£¬æ‚Äô?°RaN¬±‚Ä°T> h¬±?ä?º?£ ?ɂĢ?ë?û‚Ǩ?Ü?ü‚ÄìOKJ?ØR¬£‚Ä°Àú^M¬??í(¬±?î??LH¬ ??ìÀú6KGÀܬÅ7?Ä?£¬æ‚Äô?°Ra e0¬ê¬??º?ë?™$?ëy‚Äì2¬¨¬çg?ô?¨?™?ɂ݂Ģ?â¬?E¬£+ ‚ÄòE‚Ä?Pu?¥d‚Äî
s_EL¬®?ø??¬µ?æd¬øz?ç[¬µ¬Åd‚Ñ¢l¬?P
?ñ¬º¬¨+P¬?+,?µ?í‚Ǩa?í?á2&}1‚Ä??± ?㬵r¬?5c??¬?¬?m82¬??å?ûf?ä‚Äî#??W:?í'-¬±l=¬è?ô?î?ê‚Äù ?ô¬•c??li¬?9?ô(?Ü,a?®M¬?*¬??º!3‚Ä°?Ü<6Vui"?åG?•??¬µ?æd¬øz?ç[?úK6??~?£?®‚Äî?¨?嬮¬èf2nO?Ä??.YR9|??g>+?° ?•)?í?ç{?ø¬?¬?=nY?Ƭ±??xc?éCJ!N¬±?†(¬º&?}?â?¨?æ?±‚Äú?ÇEE #

Decrypted:
is not the correct way to copy a string. This is the primary reason why this routine will not work with ansi or wide strings. Using Result := Source will not work with ansi or wide strings either, because you want Result to be a unique copy of Source (because the Result copy gets modified). Hence you should use a function that generates a unique copy of the string. I believe there is a UniqueStr() function in the RTL or ‚Äù¬? ¬°?®??

What might be going on here? I really don't get how that thing could happen. Even with the length change you gave me, which slightly helped, the algorithm is not working right. Even odder is that with the same password and longer text, the result sometimes is a cryptic 2 characters. I don't even know what they were. An attempt at decryption results in a crash, as you'd expect.

In the loop should I use the n/b comparison to assign to a temporary value for the 2 Move calls and the ending increment? Would that help, perhaps?

Sly
14-03-2006, 10:32 PM
Sorry I haven't got back to you on this. I have had a quick look at it under the Delphi 2005 debugger (I every time I use the Delphi debugger reminds me just how brilliant the VS debugger is) and I haven't come up with anything yet.

User137
15-03-2006, 01:27 AM
lol, after lots of various testing here comes my conclusion... The text happens to have a block that encrypts into somekind of ending character that simply refuses to print any more characters after it :twisted: And with all certainty i found out that is chr(0). So.. if string is not allowed to contain #0 in between text you need an alternate type for storing the encrypted data.

Sly
15-03-2006, 01:35 AM
I had thought about that, and had wondered if XTEA had the trait of not producing any NUL bytes in the output. Apparently not.

Robert Kosek
15-03-2006, 02:12 AM
Hmm, I see how that is a problem. Is there a possible way to fix or work around this? I am curious about it. Perhaps an array of bytes would be better for encryption/decryption, seeing that a 0 byte is valid. :?

Sly
15-03-2006, 03:28 AM
You could input a string to encrypt and get a block of bytes back, then pass the block of bytes in to decrypt and get a string back. That should work.

Robert Kosek
15-03-2006, 03:32 AM
True.

It should work dandy as-is for stream/binary encryption though. Perhaps an XOR of byte 1 would alleviate the problem with strings?

Sly
15-03-2006, 03:37 AM
Then might turn a $01 into a $00. :)

The essence of it is that the encrypted data should never be passed around as a string.

technomage
15-03-2006, 07:03 AM
I've been following this thread with interest. I have a suggestion. If this algorithm will work fine on stream data then why not just make is encrpt and decrypt streams only? The Turbo Power LockBox (http://sourceforge.net/projects/tplockbox/) functions do this, they mainly work on streams and have a wrapper function to convert the string to a memory stream (using TStreamStream and a copy) to handle the actual encryption.

I have found in the past with compression that dealing with strings directly can always be a bit tricky.

That is an interesting point about the C >> operator, I have had problems converting code which uses >> before but I can never remember how I get round it each time :wink:

User137
15-03-2006, 10:38 AM
Here's another suggestion if using string is a must:
- First test if 1 of the 8 byte numbers in v block contains 0. This is easy when converting
array[0..1] of longword -> array[0..7] of byte
For example: b8:=TBlock8(v);

- Then split 8 characters in 4 character parts and add another 4 characters after/before both 2 parts that are easy to detect and delete when decrypting.

- A bit trickier here makes the selection of added characters. Obsiously that 4 char sequence could not then be allowed to exist in plain text, so use characters that are not used when writing text.

-Also it now requires that both blocks are tested for #0 again. Change the 4 before/after characters until there is no #0 in outcome :roll:
Example: default 4 chars + #1#1#1#1
if #0 found in any of 8 outcome, then #1#1#1#2 has high propability of not contain one. this #1#1#1 will still be detectable no matter how large you grow the latter char.

Too worky? :wink:

Sly
15-03-2006, 12:16 PM
Here is a working version using streams for the encrypted data. The only code that has changed is the Encrypt and Decrypt methods.

xtea.pas

unit xtea;

interface

uses
SysUtils, Classes;

type
PKey128 = ^TKey128;
TKey128 = array &#91;0..3&#93; of longword;

PBlock64 = ^TBlock64;
TBlock64 = array &#91;0..1&#93; of longword;

TXTea = class&#40;TObject&#41;
private
FKey&#58; TKey128;
FInitialized&#58; Boolean;
public
property Initialized&#58; boolean read FInitialized default false;
procedure Burn;
procedure InitKey&#40;const Key&#58; string&#41;;
procedure Encrypt&#40;const Input&#58; string; Stream&#58; TStream&#41;;
function Decrypt&#40;Stream&#58; TStream; Count&#58; Integer&#41;&#58; string;
destructor Destroy; override;
constructor Create;
end;

implementation

const
DELTA = $9e3779b9;
XTEA_BLANK&#58; TBlock64 = &#40;0,0&#41;;
XTEA_BURN&#58; TKey128 =&#40;0,0,0,0&#41;;

destructor TXTea.Destroy;
begin
Burn;
inherited;
end;

constructor TXTea.Create;
begin
inherited;
Burn;
end;

procedure TXTea.Burn;
begin
FKey &#58;= XTEA_BURN;
FInitialized &#58;= False;
end;

procedure TXTea.InitKey&#40;const Key&#58; string&#41;;
var L&#58; integer;
begin
if Length&#40;Key&#41; <= SizeOf&#40;FKey&#41; then
L &#58;= Length&#40;Key&#41;
else
L &#58;= SizeOf&#40;FKey&#41;;

Move&#40;Key&#91;1&#93;,FKey&#91;0&#93;,L&#41;;

FInitialized &#58;= True;
end;

procedure TXTea.Encrypt&#40;const Input&#58; string; Stream&#58; TStream&#41;;
var i,l&#58; integer;
v&#58; TBlock64;

procedure CipherXTea&#40;v&#58; PBlock64; key&#58; PKey128&#41;;
var sum&#58; longword;
i&#58; integer;
begin
Sum&#58;= 0;

for i&#58;= 0 to 31 do
begin
Inc&#40;v&#91;0&#93;, &#40;&#40;v&#91;1&#93; shl 4 xor v&#91;1&#93; shr 5&#41; + v&#91;1&#93;&#41; xor &#40;Sum + Key&#91;Sum and 3&#93;&#41;&#41;;
Inc&#40;Sum, Delta&#41;;
Inc&#40;v&#91;1&#93;, &#40;&#40;v&#91;0&#93; shl 4 xor v&#91;0&#93; shr 5&#41; + v&#91;0&#93;&#41; xor &#40;Sum + Key&#91;Sum shr 11 and 3&#93;&#41;&#41;;
end;
end;

begin
if not Initialized then
raise Exception.Create&#40;'Error&#58; You must define a password.'&#41;;

l &#58;= &#40;Length&#40;Input&#41; + 1 + &#40;SizeOf&#40;v&#41; - 1&#41;&#41; and &#40;not &#40;SizeOf&#40;v&#41; - 1&#41;&#41;;

i &#58;= 1;
while i < l do
try
v &#58;= XTEA_BLANK;
if l - &#40;i - 1&#41; < SizeOf&#40;v&#41; then
Move&#40;Input&#91;i&#93;, v, l - &#40;i - 1&#41;&#41;
else
Move&#40;Input&#91;i&#93;, v, SizeOf&#40;v&#41;&#41;;
CipherXTea&#40;@v, @FKey&#41;;
Stream.Write&#40;v, SizeOf&#40;v&#41;&#41;;
Inc&#40;i, SizeOf&#40;v&#41;&#41;;
except
raise;
end;
end;

function TXTea.Decrypt&#40;Stream&#58; TStream; Count&#58; Integer&#41;&#58; string;
var i&#58; integer;
v&#58; TBlock64;

procedure DecipherXTea&#40;v&#58; PBlock64; Key&#58; PKey128&#41;;
var
i&#58; Integer;
Sum&#58; Longword;
begin
Sum&#58;= $C6EF3720;

for i&#58;= 0 to 31 do
begin
Dec&#40;v&#91;1&#93;, &#40;&#40;v&#91;0&#93; shl 4 xor v&#91;0&#93; shr 5&#41; + v&#91;0&#93;&#41; xor &#40;Sum + Key&#91;Sum shr 11 and 3&#93;&#41;&#41;;
Dec&#40;Sum, Delta&#41;;
Dec&#40;v&#91;0&#93;, &#40;&#40;v&#91;1&#93; shl 4 xor v&#91;1&#93; shr 5&#41; + v&#91;1&#93;&#41; xor &#40;Sum + Key&#91;Sum and 3&#93;&#41;&#41;;
end;
end;
begin
if not Initialized then
raise Exception.Create&#40;'Error&#58; You must define a password.'&#41;;

SetLength&#40;Result, Count&#41;;

i &#58;= 1;
while i < Count do
try
v &#58;= XTEA_BLANK;
Stream.Read&#40;v, SizeOf&#40;v&#41;&#41;;
DecipherXTea&#40;@v, @FKey&#41;;
Move&#40;v, Result&#91;i&#93;, SizeOf&#40;v&#41;&#41;;
Inc&#40;i, SizeOf&#40;v&#41;&#41;;
except
raise;
end;
end;

end.

Sample app

unit Unit4;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, xtea;

type
TForm4 = class&#40;TForm&#41;
Memo1&#58; TMemo;
Memo2&#58; TMemo;
Button1&#58; TButton;
Button2&#58; TButton;
procedure Button2Click&#40;Sender&#58; TObject&#41;;
procedure FormDestroy&#40;Sender&#58; TObject&#41;;
procedure Button1Click&#40;Sender&#58; TObject&#41;;
procedure FormCreate&#40;Sender&#58; TObject&#41;;
private
FXTea&#58; TXTea;
FStream&#58; TMemoryStream;
public
&#123; Public declarations &#125;
end;

var
Form4&#58; TForm4;

implementation

&#123;$R *.dfm&#125;

procedure TForm4.FormCreate&#40;Sender&#58; TObject&#41;;
begin
FXTea &#58;= TXTea.Create;
FXTea.InitKey&#40;'0123456789abcdef'&#41;;
FStream &#58;= TMemoryStream.Create;
end;

procedure TForm4.FormDestroy&#40;Sender&#58; TObject&#41;;
begin
FStream.Free;
FXTea.Free;
end;

procedure TForm4.Button1Click&#40;Sender&#58; TObject&#41;;
var
Input&#58; String;
begin
Input &#58;= Memo1.Lines.Text;
&#40;* Set the size and position of the stream outside the method so that
we could append to an existing stream if we wanted to. *&#41;
&#40;* Add one to allow for the NUL terminator on the string *&#41;
FStream.SetSize&#40;Length&#40;Input&#41; + 1&#41;;
FStream.Position &#58;= 0;
FXTea.Encrypt&#40;Input, FStream&#41;;
end;

procedure TForm4.Button2Click&#40;Sender&#58; TObject&#41;;
begin
&#40;* This sample assumes that the stream contains only the encrypted data,
but it could be part of a much larger stream. *&#41;
FStream.Position &#58;= 0;
Memo2.Lines.Text &#58;= FXTea.Decrypt&#40;FStream, FStream.Size&#41;;
end;

end.

Press Button1 to encrypt the text in Memo1 into a TMemoryStream. Press Button2 to decrypt the stream into Memo2.

Robert Kosek
15-03-2006, 07:38 PM
Thanks, and I was actually going to write it as Stream to Stream encryption and decryption, with String overloads of each. And I'll probably recover the code by the MPL license, rather than my own. I'm not very good with legalese. ;)

So I'll upload a final unit shortly, after I rewrite the functions real quick.

So there is a way around Delphi's implementation of binary shifts? I hope so, since I'm certain that it's our culprit. Or I could write a C* DLL or use the compiled .OBJs created for all this...

jdarling
15-03-2006, 10:30 PM
Thanks, and I was actually going to write it as Stream to Stream encryption and decryption, with String overloads of each. And I'll probably recover the code by the MPL license, rather than my own. I'm not very good with legalese. ;)

So I'll upload a final unit shortly, after I rewrite the functions real quick.

So there is a way around Delphi's implementation of binary shifts? I hope so, since I'm certain that it's our culprit. Or I could write a C* DLL or use the compiled .OBJs created for all this...

Sure there is a way around Delphi's implementation of Shifts. Simply write your own methods to do it for you (fair bit of warning, I'm doing this from the cuff with no IDE, so I'll guarntee its wrong):

function RotateBitsRight(Param, Amount:Byte): Byte; // Note you could also do this as a var param
asm
ror Param, Amount // Rotate right
mov Amount, result
end;

function RotateBitsRight(Param, Amount:Byte): Byte; // Note you could also do this as a var param
asm
rol Param, Amount // Rotate left
mov Amount, result
end;

Anyways my point is, do it in assembly. Good reference guides can be found all over, but I use:
http://courses.ece.uiuc.edu/ece390/books/labmanual/inst-ref.html
http://www.intel.com/design/pentium4/manuals/index_new.htm
http://www.nondot.org/sabre/os/files/Processors/PentiumOptimization.html
http://www.website.masmforum.com/tutorials/fptute/index.html

Pretty much in that order. Looking at my code above, I know its wrong but it gets the idea across. You really would have to look into BASM and samples on the b.p.n.basm group for proper answers.

Sly
15-03-2006, 10:52 PM
Yes, but I believe that is what Delphi is already doing. You need the shr and shl opcodes.

LP
16-03-2006, 03:55 AM
Sorry for a bit late reply, I'm quite out of time lately... :?



In CipherDataXTEA, the loop will not execute at all for strings with less than 8 characters. It will also not encrypt any remaining bytes after the last eight-byte block. The same goes for DecipherDataXTEA.
Yes, I'm not using padding at all. The code assumes that data block is a multiple of 8 bytes.



Also, and this might affect both, I believe that the C >> operator is a logical shift (bits fall off the end) and the Delphi shr operator is an arithmetic shift (bits wrap around). I seem to remember we found this when porting Quake 2 to Delphi.

If you look at C source code posted on Wikipedia web page, you will notice that they use UNSIGNED LONG. In that case, ">>" operator is translated to unsigned binary shift, which equals to "shr" in Delphi. The Pascal code is correct.

Anywhere else: if you need to use SIGNED SHIFT in Delphi, you may try to use power-of-two division instead, which will be replaced by signed binary shift (with Optimizations ON). It's not exactly equivalent though, since Delphi adds bias before shift. E.g.:

var
Test, Test2&#58; Integer;
begin
Test &#58;= $12345678;
// Test2&#58;= Test sar 4; -> this is not possible
Test2&#58;= Test div 16;
end;

The above code gets translated into:

mov eax, $12345678
mov esi, eax
test esi, esi
jns @NoBias
add esi, $0F
@NoBias&#58;
sar esi, 4


See Art of Assembly: Chapter Six-3 (http://maven.smith.edu/~thiebaut/ArtOfAssembly/CH06/CH06-3.html) for more information about how SHR/SAR work and their differences.

Murmandamus
16-03-2006, 04:00 AM
Greetings,

As others have mentioned, under normal circumstances, encryption/decryption routines should work on binary data types as opposed to strings; in many cases, using untyped data cast as the needed data structures for the cipher. The issue with strings not liking nulls (#0) is a notable example of this problem.

Further, when using block ciphers, care must be taken to pad out the final block when the data doesn't fill it to a block boundary. In some ciphers, improper padding can introduce weaknesses in the encoded message that cryptanalysis can exploit to recover the plaintext easier.

Outside of those two things, it looks like your encryption object is close to being 100% functional. :)

Addendum - You also have to make sure that your encrypted output is the full blocksize in the last block. IE, if you use this 8-byte block cipher to encrypt a stream of plaintext bytes with length of 21, you have to keep all 24 bytes of the encrypted message for decryption; you can't just toss those extra 3 bytes at the end, because they will contain some portion of the last 5 plaintext bytes. Output data length of a block cipher encode message function is always a multiple of the block size.

Sly
16-03-2006, 11:27 AM
Murmandamus, that is exactly what my modifications do. It just took a little while to work out the rules. :)

Sly
16-03-2006, 01:01 PM
Here is an updated xtea.pas that uses streams by default with overloaded methods for strings. The formatting and class style has also been Delphi'ed. The sample code hasn't changed except the call to InitKey is now a write to the Key property.

Tested and all works fine. This has been an interesting challenge. :)

unit xtea;

interface

uses
Classes, SysUtils;

type
TKey128 = array [0..3] of LongWord;
TBlock64 = array [0..1] of LongWord;

TXTea = class(TObject)
private
FKey: TKey128;
procedure CipherXTea(var Block: TBlock64);
procedure DecipherXTea(var Block: TBlock64);
function GetKey: String;
procedure SetKey(const Value: String);
public
constructor Create;
destructor Destroy; override;
procedure Burn;
function Encrypt(Input, Output: TStream; InputSize: LongWord): LongWord; overload;
function Encrypt(const Input: String; Output: TStream): LongWord; overload;
procedure Decrypt(Input, Output: TStream; InputSize: LongWord); overload;
function Decrypt(Input: TStream; Count: LongWord): String; overload;
function PaddedSize(Size: LongWord): LongWord;
property Key: String read GetKey write SetKey;
end;

implementation

const
DELTA = $9e3779b9;
XTEA_BLANK: TBlock64 = (0, 0);
XTEA_BURN: TKey128 =(0, 0, 0, 0);

function Min(A, B: Integer): Integer;
begin
Result := A;
if B < A then
Result := B;
end;

constructor TXTea.Create;
begin
inherited;
Burn;
end;

destructor TXTea.Destroy;
begin
Burn;
inherited;
end;

procedure TXTea.Burn;
begin
FKey := XTEA_BURN;
end;

procedure TXTea.DecipherXTea(var Block: TBlock64);
var
Index: Integer;
Sum: Longword;
begin
Sum := $C6EF3720;

for Index := 0 to 31 do
begin
Dec(Block[1], ((Block[0] shl 4 xor Block[0] shr 5) + Block[0]) xor (Sum + FKey[Sum shr 11 and 3]));
Dec(Sum, DELTA);
Dec(Block[0], ((Block[1] shl 4 xor Block[1] shr 5) + Block[1]) xor (Sum + FKey[Sum and 3]));
end;
end;

procedure TXTea.Decrypt(Input, Output: TStream; InputSize: LongWord);
var
Index: LongWord;
Block: TBlock64;
begin
if Key = '' then
raise Exception.Create('Error: You must set a key');

Index := 0;
while Index < InputSize do
try
Block := XTEA_BLANK;
Input.Read(Block, SizeOf(Block));
DecipherXTea(Block);
Output.Write(Block, SizeOf(Block));
Inc(Index, SizeOf(Block));
except
raise;
end;
end;

function TXTea.Decrypt(Input: TStream; Count: LongWord): String;
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Decrypt(Input, Stream, Count);
Result := PChar(Stream.Memory);
finally
Stream.Free;
end;
end;

function TXTea.PaddedSize(Size: LongWord): LongWord;
begin
Result := (Size + (SizeOf(TBlock64) - 1)) and (not (SizeOf(TBlock64) - 1));
end;

procedure TXTea.CipherXTea(var Block: TBlock64);
var
Sum: LongWord;
Index: Integer;
begin
Sum := 0;

for Index := 0 to 31 do
begin
Inc(Block[0], ((Block[1] shl 4 xor Block[1] shr 5) + Block[1]) xor (Sum + FKey[Sum and 3]));
Inc(Sum, DELTA);
Inc(Block[1], ((Block[0] shl 4 xor Block[0] shr 5) + Block[0]) xor (Sum + FKey[Sum shr 11 and 3]));
end;
end;

function TXTea.Encrypt(Input, Output: TStream; InputSize: LongWord): LongWord;
var
Index: LongWord;
Block: TBlock64;
begin
if Key = '' then
raise Exception.Create('Error: You must set a key');

Result := PaddedSize(InputSize);

Index := 0;
while Index < InputSize do
try
Block := XTEA_BLANK;
Input.Read(Block, Min(SizeOf(Block), InputSize - Index));
CipherXTea(Block);
Output.Write(Block, SizeOf(Block));
Inc(Index, SizeOf(Block));
except
raise;
end;
end;

function TXTea.Encrypt(const Input: String; Output: TStream): LongWord;
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Stream.Write(Input[1], Length(Input) + 1);
Stream.Position := 0;
Result := Encrypt(Stream, Output, Stream.Size);
finally
Stream.Free;
end;
end;

function TXTea.GetKey: String;
begin
Result := PChar(@FKey);
end;

procedure TXTea.SetKey(const Value: String);
begin
Burn;
Move(Value[1], FKey, Min(Length(Value), SizeOf(FKey)));
end;

end.

Murmandamus
16-03-2006, 04:46 PM
Way to go! 8)