One of the things that's come up several times in my Delphi experience is the need to Base 64 encode and decode data. Sadly, there's no built-in function to do this, but as Base-64 is a very straightforward encoding, it's a pretty trivial task to write your own.
Base-64 is a means of encoding binary data using the 62 characters comprising the lowercase alphabet, the uppercase alphabet and the numbers 0-9, as well as the characters + and / for a total of 64 different characters, plus = used for padding.
Why 64? Because the number 63 is the highest number that can be represented in 6 bits. 63 allows for 64 positions in a zero-indexed array, and as we'll see in a minute, the use of 6 bits is crucial to how Base-64 works.
The basic principle is this: take three 8-bit values and turn them into four 6-bit values. These values are used to index an array of the character set mentioned above, and the characters then form the output string.
For example, take the string "Cat". Assuming an ASCII encoding, this comprises the values 67, 97, and 116, the values for uppercase C, lowercase A and lowercase T, respectively. These are represented in binary thus:
01000011 01100001 01110100
Divided into groups of 6:
010000 110110 000101 110100
These become the numbers
16, 54, 5, and 52
And if we use these to index into the following array (zero-indexed):
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
We get the characters:
Q2F0
Decoding is just finding the index of the characters, converting those values to binary, grouping the bits in 8s and converting back to numerical values.
Earlier we said we were converting groups of three 8-bit bytes into groups of four 6-bit bytes, but what if the input data isn't divisible by four? Simple, we pad any partial groups with zeros and use the padding character = for any output characters that are missing entirely.
So, if our input was "Ca" instead of "Cat" we'd have input bytes of:
01000011 01100001
Grouped into sixes, we finish off the last group with zeros:
010000 110110 000100
Convert to the character sequence as before, and append the padding character:
Q2E=
Simple, right?
So why do we need the padding character? Even without it, we'd be able to unambiguously decode back to our original data. But what if we concatenated several base-64 values one after the other? If we couldn't guarantee that each one was a multiple of four we could find ourselves in the situation where the last bits of one string are concatenated to the first bits of another string and produce a string that is different to the input from that point on. The padding characters remove that possibility.
So how do we do this in Delphi?
Well, first it's important to remember that Base-64 can be used to encode **any** binary data, not just character strings, so it makes sense that the input for our encoding function is an array of bytes. If we do happend to want to encode a string, we can get the appropriately encoded byte values back from the string using the TEncoding class.
Then we loop through the string taking three characters at a time, keeping track of how many bits are left to read, and converting them to 6-bit bytes.
So far:
function Base64EncodeBytes(inputstring : TBytes) : string;
const base64set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// Our array index into the input string
// and the number of characters remaining
var i, rem: integer;
// The three characters of the input
// we're currently processing
i1, i2, i3 : byte;
// The four array indexes
// for our output characters
n1, n2, n3, n4 : byte;
begin
rem := Length(inputstring);
i := 0;
while rem > 0 do
begin
i1 := 0;
i2 := 0;
i3 := 0;
if rem > 0 then
begin
i1 := inputstring[i];
inc(i);
end;
if rem > 1 then
begin
i2 := inputstring[i];
inc(i);
end;
if rem > 2 then
begin
i3 := inputstring[i];
inc(i);
end;
So that's the 8-bit bytes gathered. Now we need to split these into groups of 6. The way I decided to do this was to make one long number by left shifting the byte values and then right shifting each group of 6 into the lowest 6-bit position to get the new byte values.
// Form a long number from which
// to take our groups of six
outputNo := (i1 shl 16) + (i2 shl 8) + i3;
// Shift the six left most bits
// over to the right by 18 places,
// so the occupy the six least-significant bits
n1 := outputNo shr 18;
n2 := (outputNo shr 12) and 63;
n3 := (outputNo shr 6) and 63;
n4 := (outputNo) and 63;
The fact our variables n1 - n4 are bytes means that the higher order bits we don't need are automatically truncated. All we do then is ensure that bits 7 and 8 are also both 0 by anding with 63 (binary 00111111) and we have our array indexes.
All that remains is to get the correct output characters, add any padding based on the number of remaining input characters and close off our loop.
if rem < 2 then
result := result + Base64Set[n1 + 1]
+ Base64Set[n2 + 1] + '=='
else if rem < 3 then
result := result + Base64Set[n1 + 1]
+ Base64Set[n2 + 1]
+ Base64Set[n3 + 1] + '='
else
result := result + Base64Set[n1 + 1]
+ Base64Set[n2 + 1]
+ Base64Set[n3 + 1]
+ Base64Set[n4 + 1];
rem := length(inputstring) - i;
end;
end;
Et voilá!
I recently needed to implement RSA cryptography in Delphi, sticking to a few requirements of my own choosing:
1. The library used should be free
2. The library license should be permissive enough that I can use the library in a commercial application
3. The library should have as few extra files to include as possible
I looked initially at LockBox, but it came with a multitude of files to either copy to my source folder or include in my library path. I don't want other developers in my team to have to add yet more third party libraries, so I gave this a miss. Additionally, the documentation wasn't great and finding example online was quite tricky.
I then tried PGPCrypt, but could only find a version of that for Lazarus, the free Delphi compiler.
Next was the TMS Cryptography pack, but this isn't free, so again a non-starter.
Then I thought about OpenSSL. We already use OpenSSL as a DLL for a different, but related, purpose so the file is already present in our source and program folder. What we don't have are function headers as we're calling the DLL functions by importing them directly (we only use three at present), but RSA is more complicated and uses a particular structure that would need replicating in Delphi. I thought this might mean OpenSSL was also a non-starter, but after much searching online I came across the Delphi-OpenSSL project at https://github.com/UniGe/delphiopenssl/ which consists really of only three .pas files, only one of which is necessary for what I'm trying to implement: RSA encryption and decryption. For this purpose I need use only the LibEAY32.pas file coupled with the LibEAY32.dll file. We're using version 1.0.2 of this, and the Delphi-OpenSSL project is compatible with that. Great!
I needed to make a few minor changes, as despite the programmers' best efforts it seemed confused about the difference between Char and AnsiChar in Delphi 2009, but the changes were minimal and consisted, almost entirely, of me replacing references to PChar or PCharacter with PAnsiChar.
The next challenge was to see if I could use an existing key to encrypt a file.
I added my modulus, public exponent and private exponent (not needed for encryption) as Base64 encoded string constants and used:
_rsa = RSA_New();
to create a new RSA structure to populate with the key information. This actually returns a pointer to an RSA object, so it needs both dereferencing and casting when used. The properties we're interested in are the modulus, n; the public exponent, e; and the private exponent, d (when decrypting, later).
These properties take the form of BigNum objects as they can be sometimes thousands of bits long. The BigNums need to be created before being used:
RSA(_rsa^).n := BN_New();
RSA(_rsa^).e := BN_New();
The Base64 values were then decoded and altered, one byte at a time, to hex using IntToHex (which returns a string) and then BN_HexToBN was used to set the RSA record's properties accordingly:
BN_HexToBN(RSA(_rsa^).n, ansistring(public_modulus));
BN_HexToBN(RSA(_rsa^).e, ansistring(public_exponent));
The input string can then be encrypted by calling:
enc_in := SysUtils.TEncoding.ASCII.GetBytes(string(input_text));
to get the string character values as a byte array, followed by:
RSA_Public_Encrypt(length(enc_in),PAnsiChar(enc_in), PAnsiChar(@textout),_rsa,RSA_PKCS1_PADDING);
to do the actual encryption. The output cypher text is held in textout. This will look like garbage but can be converted to a Base64 string for convenient storage.
And that's it.
Two caveats:
I had to add an extra property to the RSA record to match the version of OpenSSL we're using. This was:
engine: pointer;
below the meth property.
And I had to initialise the SSL library by calling:
SSLEay_version(_SSLEAY_VERSION);
before making any calls to encryption or decryption routines. There's probably a better way to do this, but this did the trick.
I put this information here in the hope that it helps someone else who struggled as much as I did to implement this!