Encoders

The Util.Encoders package defines the Encoder and Decoder types which provide a mechanism to transform a stream from one format into another format. The basic encoder and decoder support base16, base32, base64, base64url and sha1. The following code extract will encode in base64:

C : constant Encoder := Util.Encoders.Create ("base64");
S : constant String := C.Encode ("Ada is great!");

and the next code extract will decode the base64:

D : constant Decoder := Util.Encoders.Create ("base64");
S : constant String := D.Decode ("QWRhIGlzIGdyZWF0IQ==");

To use the packages described here, use the following GNAT project:

with "utilada_sys";

URI Encoder and Decoder

The Util.Encoders.URI package provides operations to encode and decode using the URI percent encoding and decoding scheme. A string encoded using percent encoding as described in RFC 3986 is simply decoded as follows:

Decoded : constant String := Util.Encoders.URI.Decode ("%20%2F%3A");

To encode a string, one must choose the character set that must be encoded and then call the Encode function. The character set indicates those characters that must be percent encoded. Two character sets are provided,

  • HREF_STRICT defines a strict character set to encode all reserved characters as defined by RFC 3986. This is the default.
  • HREF_LOOSE defines a character set that does not encode the reserved characters such as -_.+!*'(),%#@?=;:/&$.
Encoded : constant String := Util.Encoders.URI.Encode (" /:");

Secret keys

The Util.Encoders package defines the Secret_Key limited type which is intended to be used to hold secret keys. The secret itself can only be accessed from the Util.Encoders packages and its children. When the object representing the key is destroyed, the memory that hold the key is cleared. The Secret_Key is basically used for AES encryption but could be used for other purposes. The secret key can be created from a string as follows:

Key : constant Util.Encoders.Secret_Key := Create ("password");

Decoding

A secret key is a binary content that sometimes must be retrieved from another format. For example, it could be represented as a Base64 string. To help and reduce key leaks the Decode_Key function can be used. The first step is to obtain a Decoder object as described at begining:

D : constant Decoder := Util.Encoders.Create ("base64");
K : constant Secret_Key := Util.Encoders.Decode_Key (D, "cGFzc3dvcmQ=");

Decrypting keys

The secret key can also be obtained by using the Decrypt_Secret procedure provided by the AES package. The procedure only accepts a binary content and to decrypt that key it is also necessary to know a first encryption key.

Encrypted_Key : Ada.Streams.Stream_Element_Array := ...;
Decipher : Util.Encoders.AES.Decoder;
...
  Decipher.Set_Key (...);
  Decipher.Decrypt_Secret (Encrypted_Key, Key);

Password-based key derivation function 2

The Util.Encoders.KDF.PBKDF2 generic procedure can be used to generate a secure key from a password. It implements the key derivative function Password-Based Key Derivation Function 2, RFC 8018. The generic procedure is instantiated with a Hash function which is typically a HMAC-SHA256 function. An instantiation with such function is provided by the Util.Encoders.KDF.PBKDF2_HMAC_SHA256 procedure. After instantiation, the procedure gets the password, a salt and an iteration counter and it produces the derived key.

Pkey : constant Util.Encoders.Secret_Key
   := Util.Encoders.Create (Password);
Salt : constant Util.Encoders.Secret_Key
   := Util.Encoders.Create ("fakesalt");
Key  : Util.Encoders.Secret_Key (Length => AES.AES_256_Length
 ...
  Util.Encoders.KDF.PBKDF2_HMAC_SHA256
     (Password => Pkey,
      Salt     => Salt,
      Counter  => 2000000,
      Result   => Key);

AES Encryption and decryption

The Util.Encoders.AES package implements the basis for AES encryption and decryption used by the Util.Streams.AES package for AES streams. Several encryption modes are available and described by the AES_Mode type and the padding is controlled by the AES_Padding type. The encryption is provided by methods on the Encoder type. The Set_Key procedure configures the encryption key and AES encryption mode. The key must be 16, 24 or 32 bytes as specified by the pre-condition. Then, the Transform procedure is called as needed to encrypt the content in a destination buffer. It returns the position of the last encoded byte as well as the position of the last encrypted byte. It can be called several times if the data to encrypt does not fit in the destination buffer. At the end, it is important to call the Finish procedure so that it handles correctly the last AES block which may contain the last encrypted bytes as well as optional padding. An encryption function is shown below:

function Encrypt (Key   : in Secret_Key;
                  Src   : in Stream_Element_Array) return Stream_Element_Array is
   Size    : constant Stream_Element_Offset := AES.Align (Src'Length);
   Cipher  : Util.Encoders.AES.Encoder;
   Encoded, Last : Stream_Element_Offset;
   Result  : Stream_Element_Array (1 .. Size);
begin
   Cipher.Set_Key (Key, AES.ECB);
   Cipher.Transform (Src, Result (Result'First .. Result'Last - 16),
                     Last, Encoded);
   Cipher.Finish (Result (Last + 1 .. Result'Last), Last);
   return Result;
end Encrypt;

With AES, the encryption result is always a multiple of AES block size (16 bytes). The Align function allows to easily compute the expected encrypted size.

Decrypting a content follows the same principles but with the Decoder type. The source array must be a multiple of AES block and the last AES block may contain some padding. The Finish procedure will then indicate the last valid byte in the decrypted buffer.

function Decrypt (Key   : in Secret_Key;
                  Src   : in Stream_Element_Array) return Stream_Element_Array is
   Size     : constant Stream_Element_Offset := Src'Length;
   Decipher : Util.Encoders.AES.Decoder;
   Result   : Stream_Element_Array (1 .. Size);
   Encoded, Last : Stream_Element_Offset;
begin
   Decipher.Set_Key (Key, AES.ECB);
   Decipher.Set_Padding (AES.PKCS7_PADDING);
   Decipher.Transform (Src, Result, Last, Encoded);
   Decipher.Finish (Result (Last + 1 .. Result'Last), Last);
   return Result (Result'First .. Last);
end Decrypt;

Error Correction Code

The Util.Encoders.ECC package provides operations to support error correction codes. The error correction works on blocks of 256 or 512 bytes and can detect 2-bit errors and correct 1-bit error. The ECC uses only three additional bytes. The ECC algorithm implemented by this package is implemented by several NAND Flash memory. It can be used to increase the robustness of data to bit-tempering when the data is restored from an external storage (note that if the external storage has its own ECC correction, adding another software ECC correction will probably not help).

The ECC code is generated by using the Make procedure that gets a block of 256 or 512 bytes and produces the 3 bytes ECC code. The ECC code must be saved together with the data block.

Code : Util.Encoders.ECC.ECC_Code;
...
Util.Encoders.ECC.Make (Data, Code);

When reading the data block, you can verify and correct it by running again the Make procedure on the data block and then compare the current ECC code with the expected ECC code produced by the first call. The Correct function is then called with the data block, the expected ECC code that was saved with the data block and the computed ECC code.

New_Code : Util.Encoders.ECC.ECC_Code;
...
Util.Encoders.ECC.Make (Data, New_Code);
case Util.Encoders.ECC.Correct (Data, Expect_Code, New_Code) is
   when NO_ERROR | CORRECTABLE_ERROR => ...
   when others => ...
end case;