unit cryptstreams;
{ A simple encryption/decryption stream based on a pseudo random
number generator, PRNG for short.
We use a Linear Congruential Generator, LCG for short.
This makes use of the deterministic nature of PRNG's, meaning that,
given a known seed, the sequence is always the same, while
approaching equi-distribution in its outcome. The possibility to
use cycles, strongly improves the crypt quality: i.e, five cycles is
mathematically almost unbreakable without knowledge of:
- Key, which is the random seed in the range of a Cardinal.
- Cycles, how many times the crypt is run over the same data.
- The algorithm used to crypt/decrypt.
Here I have used a very simple and fast PRNG, but the encryption
function can be overriden with your algorithm of choice without
affecting the functionality of the the class.
Encryption is limited to memory size, but you can also implement this
using a class derived from TFileStream instead without many changes.
The most important part is just the WriteByte function and that works
with any stream.
There is some more explanation in the sourcecode below.
Enjoy,
Thaddy de Koning.
NO license, use as you like.
(C)1998-2024 Thaddy de Koning
(I used the idea first in 1998, not as a class,
but as a function )}
{$mode objfpc}{$H+}
interface
uses
classes;
type
{$if not declared(Tbytes)}
TBytes = array of byte;
{$ifend}
TSimpleStreamCrypter = class(TMemoryStream)
strict private
FKey,
FCycles:Cardinal;
FInStream:TStream;
FLoaded:Boolean;
function GetBytes:TBytes;inline;
strict protected
{ Writes a byte, encrypts immediately on write.
You can override this with a different encryption algorithm }
procedure WriteByte(aByte:Byte);virtual;
procedure LoadFromStream(Stream:TStream);
public
constructor Create(InStream:TStream;Key:Cardinal;Cycles:cardinal = 5);virtual;overload;
property Bytes:TBytes read GetBytes;
end;
implementation
constructor TSimpleStreamCrypter.Create(InStream:TStream;Key:Cardinal;Cycles:cardinal);
begin
inherited create;
FLoaded := False;
FInStream := InStream;
FKey := Key;
FCycles := Cycles;
Size:=InStream.Size;
LoadFromStream(InStream);
end;
function TSimpleStreamCrypter.GetBytes:TBytes;
begin
Result := [];
SetLength(Result,Size);
move(PByte(memory)[0],Result[0],Size);
end;
{ If you distrust the LCG, you can override this with
another PRNG, but since we use 5 cycles as default,
the resulting encryption is very, very strong.
If you test with chi squared (.5) you will find almost
perfect equi-distribution over 5 cycles and 1000 probes
with different keys. That means it is pretty much unbreakable
without the knowledge of key, cycles and prng. }
procedure TSimpleStreamCrypter.WriteByte(aByte:Byte);
begin
{ This is an LCG, actually the same as Delphi's Random }
FKey := FKey * 134775813 { is prime, do not change } + 1;
inherited WriteByte(aByte xor (FKey * 256 shr 32));
end;
procedure TSimpleStreamCrypter.LoadFromStream(Stream:TStream);
var
i,j:integer;
b:Byte;
begin
{ freshen up first }
clear;
{ We only need to read the first cycle from the instream
Our stream is then already encrypted for one cycle }
if not Floaded then
begin
Stream.Seek(0,0);
for j:= 0 to Stream.Size -1 do
{ encryption is byte wise and in-place }
WriteByte(Stream.ReadByte);
{ flag the first encryption round }
FLoaded := True;
{ Keep track of the cycles }
Dec(FCycles);
end;
{ After that, we can cycle over our own data }
for i:= 0 to FCycles-1 do
begin
Seek(0,0);
for j:= 0 to Size -1 do
begin
b:= ReadByte;
{ Go back to where we were, because ReadByte advanced
the stream by one }
Seek(-1,soFromCurrent);
{ Overwrite with new value }
WriteByte(b);
end;
end;
end;
end.