Ada Cryptographic Objects

Peter C. Vermont Technical College Chapin

Vermont Technical College
Computer Information Systems Department

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the license is included in the section entitled "GNU Free Documentation License".

Revision History
Revision 0.02009-02-10pcc
This is a work in progress.

Table of Contents

Introduction
1. Building ACO
2. Reference
Package ACO
Package ACO.Access_Types
Package ACO.Crypto
Package ACO.Crypto.Algorithms
Package ACO.Crypto.Algorithms.Blowfish
Package ACO.Crypto.Exceptions
Package ACO.Crypto.Block_Cipher
Package ACO.Crypto.Block_Cipher.None
Package ACO.Crypto.Block_Cipher.Blowfish
Package ACO.Crypto.Stream_Cipher
Package ACO.Crypto.Stream_Cipher.None
Package ACO.Crypto.Hash
Package ACO.Crypto.Hash.None
Package ACO.Utility.Very_Longs

Introduction

The Ada Cryptographic Objects library (ACO) was originally developed to meet my need for a free (as in speech) library of cryptographic primitives in Ada. Although there are a number of other such libraries available, for various reasons none of them seemed to suit my needs. Other libraries were incomplete in that they left out operations that were critical to me (public key cryptography being the most common omission), or were too heavyweight in that they included many facilities that were irrelevant to me. In contrast, ACO is tightly focused on cryptographic operations. It is not intended to be a general purpose library nor a collection of arbitrary, unrelated packages.

ACO is written entirely in Ada with no calls to C or assembly language. In fact the core algorithms in ACO are written in a subset of Ada used for high integrity programming known as SPARK Ada. The algorithm implementations have been checked with the SPARK toolset to provide additional confidence of their correctness over and above that provided by testing alone[1]. This is a unique aspect of ACO that sets it apart from other free cryptographic libraries.

One consequence of ACO's use of SPARK Ada is that the performance of ACO is not as good as for other cryptogrphic libraries that are willing to slip into C or assembly language for their innermost loops. While performance is clearly a priority for a general purpose cryptographic library, we believe that correctness and security are even higher priorities for a library of this nature. It is unfortunately too common for security libraries to actually introduce security vulnerabilities into the programs that use them.

Although ACO is first and foremost a cryptographic library, we do include a few general purpose components. The purpose of these components is to support the cryptographic operations provided by the rest of the library. In some cases they are not general enough to be of interest in other applications. In some cases they are. In any event, we wanted to make ACO as self-contained as possible. Thus we are reluctant to make extensive use of existing third party component libraries.

This may seem contrary to the principles of good software engineering. However, this approach means that you don't have to download and install some other library to use ACO and you don't have to worry about inconsistent visions between different libraries (at least insofar as ACO is concerned).

ACO is covered by the Lesser GNU Public License. This basically means that you are free to use it in any way that you wish as well as modify it as you desire. See the file LGPL.txt for more details.

The algorithms implemented in this library are all freely usable. They are either not patented, their patents have expired, or their patent owners have specifically allowed the algorithms to be used in any application[2]. The code in ACO was independently developed without reference to any existing Ada implementation. In effect, this code was all written in an Ada clean room, and does not infringe any copyrights. Finally, this document is also free (as in speech) and may be distributed and modified under the terms of the GNU Free Documentation License. See the fileGFDL.txt for more details.



[1] This work is currently ongoing.

[2] If you believe this is not true for any of the algorithms we provide here, please let us know so we can investigate and correct the situation.

Chapter 1. Building ACO

This chapter describes how to build ACO and associated documentation. Currently the tools used to create this library are as listed below. The version numbers given are for the versions used by the ACO developers. Other versions of each tool may also work. In addition, tools from other vendors may work although some of the build scripts and configuration files would likely need to be updated first. This document does not describe how to install these tools; consult the corresponding documentation for each tool for that tool's installation instructions.

Build Tools

Ada Compiler

GNAT GPL 2008. Although other Ada compilers may work, you will definitely need an Ada 2005 compiler. Neither Ada83 nor Ada95 is supported by ACO.

The ACO distribution includes GNAT project files that help coordinate the build. If you are using a compiler other than GNAT you may have to create your own makefiles (or similar, depending on your compiler).

Subversion Client

The version control system used by the ACO developers is Subversion. The GNAT project files that come with the ACO source distribution specify this. Although it is not strictly necessary to have a Subversion client installed to build and use ACO, you may find it convenient because it will allow you to easily access the latest version of the system.

There is a version of the ACO distribution that comes with all the Subversion control folders in place. When you unpack that distribution you will have a valid Subversion working copy. This approach is recommended for users who always want the absolute latest version of the library available. More conservative users can download the (smaller) distribution without the Subversion control folders and thus don't need to worry about having a Subversion client installed.

GPS requires you to be using a command line Subversion client. On Unix systems this is the standard svn program. On Windows we recommend installing a Windows native svn command line client. The GNAT project files are configured with this in mind; if you intend to use the Cygwin command line client, you should change the VCS settings in the project files accordingly. Note that GPS will produce a warning message on start up if it can't find a suitable svn program. If you do not intend to use Subversion to manage the ACO library you can ignore this warning. If you find the warning bothersome you can change the VCS settings in the project files to 'None.'

AUnit

AUnit v2.01. This is a unit test framework. Although this is not strictly necessary for building the library, if you want to run the test program (and you probably do), you'll need to have AUnit installed and compiled on your system somewhere.

DocBook Tool Chain

The documentation is written in DocBook. This is an XML format and so is readable in an ordinary text editor. However, if you want to build nicely formatted documentation you will need a tool chain for processing DocBook documents. Several such tool chains exist. How to set up your DocBook environment is not discussed further here.

SPARK

If you want to check the implementation of the core algorithms in ACO to verify that they do indeed conform to the requirements of SPARK, you will need a copy of the SPARK toolset from Praxis. Contact Praxis for more information.

Note that you can build, test, and use ACO without the SPARK toolset. You can even extend ACO without the SPARK toolset, although your extensions may not be SPARK conformant.

There are three GNAT project files in ACO. In the src folder you will find aco.gpr. This project file builds the library. In tests you will find do_tests.gpr that builds the test program, and in benchmarks you will find do_benchmarks.gpr that builds the benchmark programs. These projects can all be run separately so, for example, if you only want to build the library for use in your applications, you only need to run the aco.gpr project. You can also include this project into your own project files. Note that the test and benchmark projects are linked to the library project; building those other projects will automatically cause the library to be built.

We recommend that you at least build and run the test program to ensure that the ACO library can be built correctly on your platform and with your compiler. The test project requires that you have AUnit already installed on your system. However, it is the only project with this requirement. You do not need AUnit if you are only going to build the library (or benchmark programs)

The ACO projects include configuration variables that specify the operating system and that specify the build type. ACO supports modern Windows operating systems and Unix systems (specifically Linux). The project files should automatically detect the correct operating system based on the settings (or lack of settings) of standard environment variables. The build type configuration defaults to 'Debug' but you can manually select the 'Release' type, for example in GPS. As is traditional, the Debug build is unoptimized and contains support for run time assertions and other similar things. The Release build is optimized and removes support for certain run time checks but leaves all Ada language mandated checks enabled. Although efficiency is an important concern in any cryptographic library, ACO is an Ada library and it not our intention to subvert the design of Ada for the sake of efficiency.

Each project deposits the results of the build in a subfolder of that project. The two projects that build programs put their results in a build folder, with separate subfolders beneath that for the Debug and Release configuration. The library project puts the final library in a lib folder, also with separate subfolders beneath it for the Debug and Release configurations. This approach allows both the Debug and Release builds to be created and maintained in parallel without overwriting each other.

Although we expect ACO users will be primarily interested in testing the Debug build (for example, during development) and benchmarking the Release build, there are good reasons for using the other configurations as well. Clearly it is important to check that enabling optimizations does not introduce any errors. However, users of ACO may also wish to deploy the Debug version of the library in their applications in order to take advantage of the greater checking (assertions) that are enabled in that configuration. Before doing so they may wish to experiment with the Debug version of the benchmark program to get an idea of how much of a performance degradation such a choice entails.

Chapter 2. Reference

In this chapter the application program interface (API) is described in detail. A separate section is given for each major package. In addition to discussing the types and subprograms provided by the ACO library, the motivation for certain low level design decisions is also given. This provides insight into how the library should be used.

Not all of the packages in ACO are written in the SPARK subset of Ada. SPARK imposes significant restrictions on the Ada language features that can be used. In particular object oriented techniques are not allowed in SPARK. However, ACO provides object oriented hierarchies to help make the library more flexible. This apparent contradiction is resolved by dividing the library into two parts: one part is written in SPARK and contains the raw algorithm implementations (and supporting packages). The other part is written in full Ada and includes types that wrap the raw implementations in a convenient object oriented manner.

Most users will want to use the object oriented wrappers. Specialized users who are intereted in high reliability or who don't want the overhead of an object oriented interface, can use the raw algorithm implementations directly. In fact, ACO is distributed in two ways: the full distribution contains all packages in the library, but there is also a minimal distribution that just contains the raw algorithm implementations written in SPARK Ada.

Note that package ACO is the root of ACO's entire package hierarchy; there are (direct and indirect) child packages of ACO that are in full Ada and (direct and indirect) child packages that are in SPARK Ada. SPARK rules require that all packages in the hierarchy above a SPARK Ada package also be written in SPARK Ada (thus package ACO itself is in SPARK Ada). However, that does not prevent sibling packages to be in full Ada if desired. This is the approach taken by ACO.

In the documentation that follows each package is noted as being in either full Ada or SPARK Ada. The packages that are in full Ada are not part of the ACO minimal distribution.

Package ACO

The package ACO (SPARK Ada) is the top level package of this library. It primarly serves as a container for various child packages, but it also holds certain utility types that are of interest to the entire library (and also clients of this library).

The child packages are as follows.

Package ACO.Crypto contains the core cryptographic algorithms.

Package ACO.Access_Types contains the definitions of various useful Access types. These type definitions are not in package ACO because package ACO must be written in SPARK Ada, and SPARK does not allow access types.

Package ACO.Utility contains child packages supporting various utiliity types and subprograms. These are facilities that might in principle be useful outside of ACO but are being provided with ACO as a convenience.

In addition there are private child packages for each raw data type. This is described in more detail below.

Additional child packages of ACO will be defined in the future to provide other cryptographic services (OpenPGP, SSL, etc). At this time no other child packages are defined, however.

The utility types are intended to represent raw, uninterpreted data in various sizes along with the corresponding array types.

    type Octet           is mod 2**8;
    type Double_Octet    is mod 2**16;
    type Quadruple_Octet is mod 2**32;
    type Octuple_Octet   is mod 2**64;
    
    -- Force the raw types to have certain sizes.
    for Octet'Size           use 8;
    for Double_Octet'Size    use 16;
    for Quadruple_Octet'Size use 32;
    for Octuple_Octet'Size   use 64
    
    type Octet_Array is array(Natural range <>) of Octet;
    -- And similarly for the other types.
  

The raw data types are modular types so that they will have intrinsic bitwise operations. Representation clauses are used to ensure that the raw types have the expected sizes. Note that ACO uses the type Natural uniformly for counting.

In addition to intrinsic bitwise operations, it is highly desirable for performance reasons to also have intrinsic bit shift and rotate operations defined for the raw data types. This can be accomplished with GNAT by using a pragma Import directive. However, GNAT requires that the functions specified as intrinsic conform to a particular profile and have particular names. Since SPARK does not allow names to be overloaded and since we wanted to provide instrinsic bit shifts and rotates for all of the raw data types defined in package ACO, it was necessary to create a separate child package for each type (written in SPARK Ada). These packages contain raw data operations of interest to the rest of the ACO library and have names such as ACO.Octet_Operations, etc.

The summary of each of these operations packages is as follows.

    function Shift_Left(Value : Octet; Count : Natural) return Octet
    -- And similarly for the other types in their separate packages.
    
    function Shift_Right(Value : Octet; Count : Natural) return Octet;
    -- And similarly for the other types in their separate packages.
    
    function Rotate_Left(Value : Octet; Count : Natural) return Octet;
    -- And similarly for the other types in their separate packages.
    
    function Rotate_Right(Value : Octet; Count : Natural) return Octet;
    -- And similarly for the other types in their separate packages.

    procedure Xor_Array
      (Accumulator  : in out Octet_Array;
       Incoming     : in     Octet_Array;
       Success_Flag : out    Boolean);
    -- And similarly for the other types in their separate packages.
  

The procedures such as Xor_Array extend the bitwise operations of the raw data types to entire arrays of those types. If the given arrays have different sizes, the value False is written into the Success_Flag parameter and no other operation takes place; otherwise the value True is returned. This approach for handling errors is due to SPARK's restriction against exceptions.

In the future additional raw data operations may be added to the operations packages (whole array shifts and rotates?) The precise operations that will be added will depend on the needs that arise as ACO develops.

Package ACO.Access_Types

The package ACO.Access_Types (full Ada) contains useful access type definitions that can't be put in package ACO because of SPARK restrictions.

    type Octet_Array_Access is access Octet_Array;
    procedure Free_Octet_Array is new
       Ada.Unchecked_Deallocation(Octet_Array, Octet_Array_Access);
    
    type Double_Octet_Array_Access is access Double_Octet_Array;
    procedure Free_Double_Octet_Array is new
       Ada.Unchecked_Deallocation(Double_Octet_Array, Double_Octet_Array_Access);
    
    type Quadruple_Octet_Array_Access is access Quadruple_Octet_Array;
    procedure Free_Quadruple_Octet_Array is new
       Ada.Unchecked_Deallocation(Quadruple_Octet_Array, Quadruple_Octet_Array_Access);
    
    type Octuple_Octet_Array_Access is access Octuple_Octet_Array;
    procedure Free_Octuple_Octet_Array is new
       Ada.Unchecked_Deallocation(Octuple_Octet_Array, Octuple_Octet_Array_Access);
  

Note that only access types for the raw array types are provided. Note also that a suitable deallocation procedure for each array type is also provided.

Package ACO.Crypto

The package ACO.Crypto (SPARK Ada) contains all the low level cryptographic algorithms in this libary. These algorithms are categorized by various child packages as described elsewhere in this document. The package ACO.Crypto itself contains a few general purpose helper types that are useful to several different child packages.

    type Cipher_Mode is (Encrypt_Mode, Decrypt_Mode, Neither_Mode);
  

The type Cipher_Mode is used by the various cipher objects to track if they are encrypting or decrypting their input data. Most cipher objects start in a neutral state (Neither_Mode) but are then committed to encryption or decyrption after they are initialized (or in some cases after they are first used). Once an object has been committed to a particular mode, it can not usually be switched to a different mode. See the detailed documentation for the child packages of ACO.Crypto for specific exceptions to this rule.

Package ACO.Crypto.Algorithms

The package ACO.Crypto.Algorithms (SPARK Ada) contains child packages implementing the various core algorithms offered by ACO. These algorithms are used by the object oriented wrappers, but they can also be used directly in programs that don't need the wrappers, or that want to limit themselves to SPARK Ada or Ada 95.

Package ACO.Crypto.Algorithms.Blowfish

      type Blowfish_Algorithm is private;
      
      procedure Initialize
        (B       : out Blowfish_Algorithm;
         Key     : in  ACO.Octet_Array;
         Success : out Boolean);
      
      procedure Encrypt
        (B       : in     Blowfish_Algorithm;
         Block   : in out ACO.Octet_Array;
         Success : in out Boolean);
      
      procedure Decrypt
        (B       : in     Blowfish_Algorithm;
         Block   : in out ACO.Octet_Array;
         Success : in out Boolean);
    

The package ACO.Crypto.Algorithms.Blowfish provides a private type Blowfish_Algorithm that implements the Blowfish encryption algorithm. This type provides an Initialize procedure that initializes Blowfish_Algorithm instances. The Initialize procedure takes the key to be used as an octet array. Note that Blowfish can not use just any key size. If an invalid key size is provided, Success is set to False; otherwise Success is set to True.

The Encrypt and Decrypt procedures perform the actual encryption and description on the given block. They logically 'and' the Success with False if they are given an uninitialized Blowfish_Algorithm instance or a block with an invalid size. This allows the success status of many invocations to be accumulated easily. One should pass a variable with a value True to the first invocation of these procedures, and then pass the same variable to each subsequent invocation (for example in a loop). At the end of the entire process, if the value of that variable is still True then all invocations were successful. It is safe to attempt to encrypt or decrypt even if a previous attempt failed. A failed attempt does not evolve the state of the Blowfish_Algorithm object.

Package ACO.Crypto.Exceptions

The package ACO.Crypto.Exceptions (full Ada) contains all the exceptions and related subprograms used by the object oriented cryptographic wrappers.

    Bad_Cipher_Mode : exception;
    Bad_Block_Size  : exception;
  

The exception Bad_Cipher_Mode is raised if one tries to use a cipher object inconsistently. For example attempting to decrypt data after the object has already been committed to encryption mode. Also Bad_Cipher_Mode is raised if the object is used uninitialized.

The exception Bad_Block_Size is raised if a block of the wrong size is given to a Block_Cipher for encryption or decryption.

Package ACO.Crypto.Block_Cipher

The package ACO.Crypto.Block_Cipher (full Ada) contains the definition of a root Block_Cipher type along with its associated primitive operations.

    type Block_Cipher is abstract new Ada.Finalization.Controlled with private;
    type Block_Cipher_Access is access all Block_Cipher'Class;
    
    function Block_Size(B : Block_Cipher) return Natural is abstract;
    
    -- In-place form.
    procedure Encrypt(B : in out Block_Cipher; Block : in out Octet_Array);
    procedure Decrypt(B : in out Block_Cipher; Block : in out Octet_Array);
    
    -- Copying form.
    procedure Encrypt
       (B            : in out Block_Cipher;
        Input_Block  : in     Octet_Array;
        Output_Block : out    Octet_Array);
    
    procedure Decrypt
       (B            : in out Block_Cipher;
        Input_Block  : in     Octet_Array;
        Output_Block : out    Octet_Array);
  

The main Block_Cipher type specifies the protocol that must be used with block cipher objects. This protocol is quite simple: to encrypt a block one of the Encrypt procedures must be used, and to decrypt a block one of the Decrypt procedures must be used. Each type derived from Block_Ciper provides its own initialization procedure, if necessary.

There are in-place and copying forms of the encryption and decryption procedures. The in-place form performs its operation on the block in place, overwriting the input block with the result. The copying form writes its result to a new block and treats its input as read-only. Both forms are provided for all Block_Cipher types so that whichever form is more convenient can be used.

In the Block_Cipher type the in-place forms are implemented in terms of the copying forms and visa-versa. Thus derived types need only override one of the forms and can inherit the other form as necessary. Since different ciphers may be more naturally implemented as in-place or as copying, this approach allows the author of a derived type the flexibility of implementing whichever form is most natural for that type. Note that using an "unnatural" form will most likely cause extra copying to be done. Thus for maximum efficiency the developer will want to know which approach is best for the particular ciphers being used. However, all ciphers will provide both forms (perhaps by way of inheritance) so that correct programs can be written either way.

Warning

When implementing a type derived from Block_Cipher at least one of the two forms must be implemented. If both forms are inherited any attempt to encrypt or decrypt a block will result in an infinite recursion as the inherited forms call each other. However, creating a Block_Cipher without implementing at least one form of encryption and decryption should be rare. This warning mostly applies to people writing their own null ciphers or who are creating stubs during program development[3].

In the copying forms if the output buffer is a different size than the input buffer, the Constraint_Error exception is raised.

Normally a given Block_Cipher instance can be in either encryption mode or decryption mode. The Block_Cipher interface does not specify how this mode is selected. It may be part of the instance's initialization or it may be automatic with the first operation applied to the instance defining its mode. In general Block_Cipher instances can not change mode during their lifetime, although some derived types may provide a way to reinitialize the instance into a different mode. However, some Block_Cipher types may be modeless and can be switched between encryption mode and decryption mode freely. In any case, if an attempt is made to use the wrong mode, for example by calling Decrypt on an instance in encryption mode, the ACO.Crypto.Bad_Cipher_Mode exception is raised.

Function Block_Size returns the size of the block in bits.

    type CBC_Mode is new Block_Cipher with private;
    
    not overriding
    procedure Make
       (B          : out CBC_Mode;
        Underlying : in  Block_Cipher_Access;
        IV         : in  Octet_Array);
  

The type CBC_Mode is a wrapper around Block_Cipher that implements the cyclic block chaining (CBC) encryption mode. It provides the protocol of a Block_Cipher and is initialized with procedure Make. The initialization takes an access value that references an underlying Block_Cipher instance and an initialization vector with a size equal to the underlying cipher's block size (else Bad_Block_Size is raised). Once initialized a CBC_Mode object can be used like any other Block_Cipher. However, be aware that CBC mode retains information about past encryptions so the order in which blocks are processed by a CBC_Mode instance is significant.

Note that when a CBC_Mode instance is finalized the underlying Block_Cipher instance is not modified. Only the additional resources required for the CBC mode implementation are released.

Package ACO.Crypto.Block_Cipher.None

      type Null_Cipher is new Block_Cipher with private;

      not overriding
      procedure Make(B : out Null_Cipher; Block_Size : in Natural);
    

The ACO.Crypto.Block_Cipher.None provides a concrete type Null_Cipher. Instances of this type obey the Block_Cipher interface but perform no computations. They can be used as a place holder when a block cipher is needed by the software and yet no encryption is desired.

The Make procedure allows a Null_Cipher to be initialized with any desired block size. This allows Null_Cipher instances to be used with software expecting various block sizes.

Package ACO.Crypto.Block_Cipher.Blowfish

      Bad_Key_Length : exception;
      
      type Blowfish_Cipher is new Block_Cipher with private;
      
      not overriding
      procedure Make(B : out Blowfish_Cipher; Key : in Octet_Array);
    

The ACO.Crypto.Block_Cipher.Blowfish provides a concrete type Blowfish_Cipher that implements the Blowfish encryption algorithm. This type obeys the Block_Cipher interface and also provides a Make procedure that initializes Blowfish_Cipher instances. The Make procedure takes the key to be used as an octet array. Note that Blowfish can not use just any key size. If an invalid key size is provided, the exception Bad_Key_Length is raised.

The Block_Cipher type is modeless. Unlike general Block_Cipher instances, Blowfish_Cipher instances can be used for both encryption and decryption without any explicit mode changes and without causing the Bad_Cipher_Mode exception to be raised.

Package ACO.Crypto.Stream_Cipher

The package ACO.Crypto.Stream_Cipher contains the definition of a root Stream_Cipher type along with its associated primitive operations. ACO.Crypo.Stream_Cipher also declares types supporting various stream cipher encryption modes.

    type Stream_Cipher is abstract new Ada.Finalization.Controlled with private;
    type Stream_Cipher_Access is access all Stream_Cipher'Class;
    
    -- In-place form.
    procedure Encrypt(S : in out Stream_Cipher; Data : in out Octet);
    procedure Decrypt(S : in out Stream_Cipher; Data : in out Octet);
    
    -- Copying form.
    procedure Encrypt(S : in out Stream_Cipher; Input_Data : in Octet; Output_Data : out Octet);
    procedure Decrypt(S : in out Stream_Cipher; Input_Data : in Octet; Output_Data : out Octet);
  

The main Stream_Cipher type specifies the protocol that must be used with stream cipher objects. This protocol is quite simple: to encrypt an octet one of the Encrypt procedures must be used, and to decrypt an octet one of the Decrypt procedures must be used. Each type derived from Stream_Ciper provides its own initialization procedure, if necessary.

There are in-place and copying forms of the encryption and decryption procedures. The in-place form performs its operation on the octet in place, overwriting the input octet with the result. The copying form writes its result to a new octet and treats its input as read-only. Both forms are provided for all Stream_Cipher types so that whichever form is more convenient can be used.

In the Stream_Cipher type the in-place forms are implemented in terms of the copying forms and visa-versa. Thus derived types need only override one of the forms and can inherit the other form as necessary. Since different ciphers may be more naturally implemented as in-place or as copying, this approach allows the author of a derived type the flexibility of implementing whichever form is most natural for that type. Note that using an "unnatural" form will most likely cause extra copying to be done. Thus for maximum efficiency the developer will want to know which approach is best for the particular ciphers being used. However, all ciphers will provide both forms (perhaps by way of inheritance) so that correct programs can be written either way.

Warning

When implementing a type derived from Stream_Cipher at least one of the two forms must be implemented. If both forms are inherited any attempt to encrypt or decrypt an octent will result in an infinite recursion as the inherited forms call each other. However, creating a Stream_Cipher without implementing at least one form of encryption and decryption should be rare. This warning mostly applies to people writing their own null ciphers or who are creating stubs during program development[4].

Normally a given Stream_Cipher instance can be in either encryption mode or decryption mode. The Stream_Cipher interface does not specify how this mode is selected. It may be part of the instance's initialization or it may be automatic with the first operation applied to the instance defining its mode. In general Stream_Cipher instances can not change mode during their lifetime, although some derived types may provide a way to reinitialize the instance into a different mode. However, some Stream_Cipher types may be modeless and can be switched between encryption mode and decryption mode freely. In any case, if an attempt is made to use the wrong mode, for example by calling Decrypt on an instance in encryption mode, the ACO.Crypto.Bad_Cipher_Mode exception is raised.

    type CFB_Mode is new Stream_Cipher with private;
    
    not overridding
    procedure Make
       (S          : out CFB_Mode;
        Underlying : in  Block_Cipher_Access;
        IV         : in  Octet_Array);
  

The type CFB_Mode is a wrapper around Stream_Cipher that implements the cipher feedback (CFB) encryption mode. It provides the protocol of a Stream_Cipher and is initialized with procedure Make. The initialization takes an access value that references an underlying Block_Cipher instance and an initialization vector with a size equal to the underlying cipher's block size (else ACO.Crypto.Block_Cipher.Bad_Block_Size is raised). Once initialized a CFB_Mode object can be used like any other Stream_Cipher.

Note that when a CFB_Mode instance is finalized the underlying Block_Cipher instance is not modified. Only the additional resources required for the CFB mode implementation are released.

Package ACO.Crypto.Stream_Cipher.None

      type Null_Cipher is new Stream_Cipher with private;
    

The ACO.Crypto.Stream_Cipher.None provides a concrete type Null_Cipher. Instances of this type obey the Stream_Cipher interface but perform no computations. They can be used as a place holder when a stream cipher is needed by the software and yet no encryption is desired.

Package ACO.Crypto.Hash

The package ACO.Crypto.Hash contains the definition of a root Hasher interface along with its associated primitive operations. ACO.Crypo.Hash also declares a private type to represent the hash values themselves in an abstract way.

    type Hash_Value(<>) is private;   
    function To_String(Value : Hash_Value) return String;
  

The type Hash_Value is an abstract representation of a hash value. It can represent hash values of any length. The function To_String converts hash values into a displayable form. The resulting string is in hex using upper case letters 'A' through 'F'. It also has an appropriate number of leading zeros, depending on the size of the hash value itself. The string does not contain a prefix of "16#" nor a "#" suffix. That is, the string is not in the form of an Ada hex literal. This allows the application to add (or not) any prefix and/or suffix the application deems appropriate.

    type Hasher is interface;
    
    procedure Start_Hashing(Hash_Object : in out Hasher) is null;
    procedure Compute_Hash
       (Hash_Object : in out Hasher; Data_Block  : in Octet_Array) is abstract;
    procedure Finish_Hashing(Hash_Object : in out Hasher) is null;
    function Retrieve_Hash(Hash_Object : Hasher) return Hash_Value is abstract;
  

The main Hasher interface specifies the protocol that must be used with hasher objects. Before any hashing is attempted the procedure Start_Hashing must be called. This procedure initializes the hasher object. It can also be used to re-initialize an object that has been previously initialized.

Procedure Compute_Hash updates the hash value using the raw octets provided in Data_Block. It is not necessary for the given data block to be any specific size. In particular it need not be the natural block size of the underlying hash function. The hasher object will buffer octets if necessary to handle partial blocks.

When all the data has been processed the procedure Finish_Hashing is called to finalize the hash value. After Finish_Hashing has been called, no further calls to Compute_Hash are allowed.

The actual hash value is obtained by calling function Retrieve_Hash. This function can only be called after Finish_Hashing has been called. However, Retrieve_Hash can be called multiple times. In effect, the hasher object is read-only once Finish_Hashing has been called on it.

    Read_Hash : exception;
    
    type Stream_Hasher(Engine : access Hasher'Class) is
       abstract new Ada.Streams.Root_Stream_Type with private;
    procedure Start_Hashing(Hash_Object : in out Stream_Hasher);
    procedure Finish_Hashing(Hash_Object : in out Stream_Hasher);
    function Retrieve_Hash(Hash_Object : Stream_Hasher) return Hash_Value;
    
    overriding procedure Read
       (Hash_Object : in out Stream_Hasher;
        Item        : out Ada.Streams.Stream_Element_Array;
        Last        : out Ada.Streams.Stream_Element_Offset);
    
    overriding procedure Write
       (Hash_Object : in out Stream_Hasher;
        Item        : in Ada.Streams.Stream_Element_Array);
  

In addition to the raw data hasher interface presented above, package ACO.Crypt.Hash defines a Stream_Hasher type. Instances of this type wrap an underlying hasher object and provide a streams interface to the hashing process. The subprograms Start_Hashing, Finish_Hashing, and Retrieve_Hash behave as described above. However, Stream_Hashers are derived from the Root_Stream_Type and thus can be used with 'Read and 'Write attributes in the usual way. To accomplish this Stream_Hasher provides overridings of the Read and Write procedures as apppropriate. The Read overriding simply raises Read_Hash because it does not make sense to read a hasher object as a stream. The Write overriding, however, simply gathers the Stream_Elements and passes them to the underlying hasher for handling.

The intent of Stream_Hasher is to allow one to compute the hash value of an in-memory data structure in a meaningful way. For example, if a complex structure such as a tree is endowed with suitable streaming capability, it can be streamed into a Stream_Hasher to compute its hash value. If the tree is then reconstructed elsewhere (perhaps inside a different program), the hash value can be checked after reconstruction without concerns about "trivial" differences in the external representation of the structure.

Package ACO.Crypto.Hash.None

      type Null_Hasher is new ACO.Crypto.Hash.Hasher with private;
    

The ACO.Crypto.Hash.None provides a concrete type Null_Hasher. Instances of this type obey the Hasher interface but perform no computations. They return a hash value consisting of a single octet with the value zero regardless of the input provided to them. A Null_Hasher instance can be used when no hash value is needed but the code (for example a third party library) expects some type of Hasher object. In addition Null_Hasher instances can be used in benchmark programs to measure the overhead associated with any hashing infrastructure without the distraction of also measuring the time required for executing an actual hash algorithm.

Package ACO.Utility.Very_Longs

type    Very_Long is private;
subtype Bit       is Natural range 0..1;
type    Bit_Index is new Natural;

Package Very_Longs contains a type Very_Long that represents arbitrary precision signed integers. Very_Long objects can be manipulated similarly to ordinary integers. The individual bits of the Very_Long object are also easily accessible. Values are stored in "signed-magnitude" form. Negative values are represented internally as their corresponding positive value with a flag set to indicate the negative. This is significant if you access the bits of a negative value.

Very_Long Integer Operations

function Make(Number : in Integer) return Very_Long;
function Make(Number : in String) return Very_Long;

Constructs a Very_Long integer from an ordinary integer or from a string of digits with an optional sign. The second form is useful for initializing Very_Long integers with extremely large numeric literals. It assumes the given string contains only digits with an optional leading sign and optional leading spaces. Specifically the string must match the following regular expression: \s*[+-]?\s*\d(_?\d+)*, where \s stands for a space and \d stands for a decimal digit. Note that underscores are allowed as optional spacers in the string of digits. The second form raises Invalid_Number if the given string has an incorrect form. Note that in a future version of this package, the string constructor will be able to accept numbers in multiple bases using an Ada-like syntax.

function   "+"(L, R : Very_Long) return Very_Long;
function   "-"(L, R : Very_Long) return Very_Long;
function   "*"(L, R : Very_Long) return Very_Long;
function   "/"(L, R : Very_Long) return Very_Long;
function "mod"(L, R : Very_Long) return Very_Long;
function   "-"(N    : Very_Long) return Very_Long;

The usual arithmetic operations. Overflow does not occur although the program may run out of memory if one attempts to compute an excessively large value. The running time of "+" and binary "-" is O(n) where n is the number of digits in the Very_Long objects being manipulated. The running time of "*", "/", and "mod" is O(n2).

function  "<"(L, R : Very_Long) return Boolean;
function "<="(L, R : Very_Long) return Boolean;
function  ">"(L, R : Very_Long) return Boolean;
function ">="(L, R : Very_Long) return Boolean;

The usual relational operators. The type Very_Long is private and so "=" and "/=" are already available.

function Number_Of_Bits(Number : in Very_Long) return Bit_Index;

Returns the number of bits in the given Very_Long object. The value 0 has zero bits. Leading zero bits are not counted since there are, in principle, an infinite number of them.

function Get_Bit(Number     : in Very_Long;
                 Bit_Number : in Bit_Index) return Bit;

Returns the specified bit in the given Very_Long integer. The least significant bit is bit zero. If the given Bit_Number is out of range, the value zero is returned.

procedure Put_Bit(Number     : in out Very_Long;
                  Bit_Number : in Bit_Index;
                  Bit_Value  : in Bit);

Replaces the specified bit with the given new value in the given Very_Long integer. The least significant bit is bit zero. If the given Bit_Number is out of range, the Very_Long integer is extended accordingly unless Bit_Value is zero.



[3] Design Note: It would be safer to declare the encryption and decryption procedures in Block_Cipher as abstract and force each downstream implementor to provide both forms, implementing one in terms of the other explicilty as necessary.

[4] Design Note: It would be safer to declare the encryption and decryption procedures in Stream_Cipher as abstract and force each downstream implementor to provide both forms, implementing one in terms of the other explicilty as necessary.