Copyright © 2008 Peter C. Chapin
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.0 | 2009-02-10 | pcc |
| This is a work in progress. | ||
Table of Contents
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.
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
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).
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 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.
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.
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.
Table of Contents
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.