module Cryptodbm:sig
..end
This library provides an encrypted layer on top of the Dbm and Cryptokit packages. The improvements over Dbm are:
As a quick example, the following uncrypted bindings (key => data):
"john-doe" => "age 36" "some secret" => "The cake is a lie." "Motto" => "For relaxing times, make it Suntory time"
are stored as follows in the encrypted file (with variations depending on the password, and other parameters):
[S~j....O.Q..tk^.2] => [...F...).Hsl..tB] [...y;....~.:.6V.2] => [....I...JR..w.E9..G..q=...K....b] [..'.C...F.x.3K.y2] => [1.)9q..M...et.b.] [S.....5 Y....8..2] => [.D........2..u...q.......}Z.b..z.zo.}.l3l.....>.] [...xD;@.8..wV..P1....e}....u..`.2] => [hb..2.._B....Y?0....|.....tM....] [K.#i.7j..H.ZZ.^.2] => [..z....,........]
Including several subtables in the same database file avoids having to deal with multiple files to store related information, and also prevents information leak through the number and sizes of a set of database files.
This library was primarily designed to store encrypted exam files on a university server. A common layout consists in several subtables encrypted with a global password, as well as an uncrypted subtable containing (public) meta-information.
Install and compile
Install using opam: opam install cryptodbm
Compile with ocamlbuild by adding the following to your _tags file:
<**/*> : package(cryptodbm)
Performance
I have not benchmarked this library. Keep in mind that every access (reading or writing a binding) requires to encrypt the key, and encrypt the data (when writing) or decrypt the data (when reading). Don't be pessimistic, though: it seems all right for non critical applications.
Also, there is only a global key-index for the whole table, no key-index for individual subtables. As a consequence, subtable iterators actually iterate over the whole table index (selecting only the expected subtable indexes).
A few technical details
When a database file is encrypted,
See also the project homepage.
Contact: D. Le Botlan (github.lebotlan@dfgh.met where you replace .met by .net.)
let table = open_append ~file:"/path/to/myfile" ~passwd:"my-secret-passwd" in
let subtable = append_subtable table ~name:"here the subtable name" () in
add subtable ~key:"key1" ~data:"data1" () ;
add subtable ~key:"key2" ~data:"data2" () ;
close table ;
()
module Error:sig
..end
Error(DB_Error)
when accessing the underlying database.type 'a
table
type
read
type
full
type 'a
subtable
Note that operations are not thread-safe at the library level: do not share a table or subtable handler between threads.
However, multiple processes might access the same database, whenever the low-level dbm permits it
(which depends on the low-level dbm library actually used).
gdbm allows many readers in parallel, or only one writer and no reader.
val open_read : ?iterations:int ->
file:string ->
passwd:string -> signwd:string -> unit -> read table
Error(File_not_found)
the file does not exist or is not readable.Error(Bad_format)
the database file uses an incompatible format.Error(Bad_password)
the non-empty given password is incorrect.Error(Bad_signature
the database signature does not match the expected signature, using the given non-empty signword.Error(No_signature)
the database is not signed but a signword is provided.Error(Corrupted)
the database file does not have the expected structure.iterations
: Number of iterations used to map the password/signword. A higher value means a longer time to open the database.
A default value is used if missing (around 12000).
The same value must be used when reading/writing the database - otherwise the password is not recognized (Bad_password).
The same value must be used when signing/checking the signature - oterwise the signature is not recognized (Bad_signature).
The number of iterations is NOT saved in the dbfile.file
: The full path to the database file, but without the .pag or .dir extension (when applicable).passwd
: Use the given password to decrypt. Use the empty string "" if the file is not encrypted.
In order to access only uncrypted bindings of an encrypted file, consider Cryptodbm.open_only_uncrypted
instead.signwd
: Use the given signword to check the signature. If the signword is the empty string, do not check the signature.val open_append : ?iterations:int ->
file:string ->
passwd:string ->
signwd:string ->
check_signature:bool -> unit -> full table
Error(File_not_appendable)
the file does not exists or is not readable, or is not writeable.Error(Bad_format)
the database file uses an incompatible format.Error(Bad_password)
the non-empty given password is incorrect.Error(Bad_signature)
the database signature does not match the expected signature, and check_signature is true.Error(No_signature)
the database is not signed, and check_signature is true.Error(Corrupted)
the database file does not have the expected structure.Failure(some
message) check_signature is true but the signwd is empty.iterations
: Number of iterations used to map the password/signword. (See open_read)file
: The full path to the database file, without the .pag or .dir extensionpasswd
: Use the given password to decrypt.signwd
: Use the given signword to sign the database.check_signature
: Whether to check the existing signature before appending new data. If the table was not signed, raises No_signature.val open_create : file:string ->
?overwrite:bool ->
?iterations:int ->
passwd:string ->
signwd:string ->
?max_extra_key:int ->
?max_extra_data:int ->
?max_extra_bindings:int -> perm:int -> unit -> full table
Error(File_overwrite)
the file already exists and overwriting is not explicitly allowed.Error(File_not_writeable)
the given permission does not allow writing.file
: The full path to the database file, without the .pag or .dir extensionoverwrite
: Indicates if overwriting an existing file is allowed.iterations
: Number of iterations used to map the password/signword. (See open_read)passwd
: If a password is provided, use it to encrypt. An empty password means no encryption.signwd
: If a signword is provided, use it to sign. An empty signword means no signature.max_extra_key
: Max length of random padding added to keys, default 0.max_extra_data
: Max length of random padding added to data, default 0.max_extra_bindings
: (default 0): because the number of bindings of the table can be guessed without knowing the password,
random extra bindings can be added to obfuscate the table.perm
: Unix permission to be used to create the file. Must allow the user to write on this file. Beware that in OCaml, 644 is not equal to 0o644.val open_only_uncrypted : ?iterations:int ->
file:string -> signwd:string -> unit -> read table
Error(File_not_found)
the file does not exist or is not readable.Error(Bad_format)
the database file uses an incompatible format.Error(Bad_signature)
the database signature does not match the expected signature, using the given non-empty signword.Error(No_signature)
the database is not signed but a signword is provided.Error(Corrupted)
the database file does not have the expected structure.iterations
: Used to check the signature (useless if the signwd is empty).file
: The full path to the database file, without the .pag or .dir extensionsignwd
: Use the given signword to check the signature. If the signword is the empty string, do not check the signature.val close : 'a table -> unit
Error(Is_Closed)
the database is already closed.val flush : ?backup:bool -> ?backup_name:string -> full table -> unit
Error(Is_Closed)
the database is already closed.Error(Backup_failure)
something went wrong when doing the backup.val get_rootfile : 'a table -> string
val create_subtable : full table ->
name:string ->
?iterations:int ->
passwd:string ->
signwd:string ->
?max_extra_key:int ->
?max_extra_data:int -> unit -> full subtable
Error(Is_Closed)
the database is closed.Error(Subtable_exists)
a standard subtable with this name already exists.name
: the subtable name. It can be any string.iterations
: Number of iterations used to map the password/signword. (See open_read). If unspecified, take a default value (not necessarily the value used to open the table itself).passwd
: If the passwd is empty, use the global table password. If the global table password is also empty, this subtable will be uncrypted.
If the passwd is not empty, use it to encrypt this subtable.
Consider Cryptodbm.create_uncrypted_subtable
to create an uncrypted subtable in an encrypted table.signwd
: If a signwd is provided, use it to sign this subtable.max_extra_key
: Use this max_extra_key (instead of the global table max_extra_key parameter).max_extra_data
: Use this max_extra_data (instead of the global table max_extra_data parameter).val create_uncrypted_subtable : ?iterations:int ->
full table ->
name:string -> signwd:string -> unit -> full subtable
Error(Is_Closed)
the database is closed.Error(Subtable_exists)
a subtable with this name already exists.iterations
: Used to check the signature (useless if the signwd is empty).name
: the subtable name. It can be virtually any string.signwd
: If a signwd is provided, use it to sign this subtable.val open_subtable : 'a table ->
name:string ->
?iterations:int ->
passwd:string -> signwd:string -> unit -> read subtable
Error(Is_Closed)
the database is closed.Error(Bad_password)
the given password does not match this subtable's password.Error(Bad_signature)
the subtable signature does not match the expected signature, using the given non-empty signword.Error(No_signature)
the subtable is not signed, but a signwd was provided.Error(Is_Already_Open)
this subtable (identified by its name) is already open.Error(No_subtable)
no subtable with this name exists (if the subtable is explicitly uncrypted, use Cryptodbm.open_uncrypted_subtable
instead).name
: the subtable name.iterations
: Number of iterations used to map the password/signword. (See open_read)passwd
: If a non-empty password is provided, use it to decrypt.signwd
: If a non-empty signword is provided, use it to check the signature of this subtable.val open_uncrypted_subtable : ?iterations:int ->
'a table ->
name:string -> signwd:string -> unit -> read subtable
Error(Is_Closed)
the database is closed.Error(Bad_signature)
the subtable signature does not match the expected signature, using the given non-empty signword.Error(No_signature)
the subtable is not signed, but a signwd was provided.Error(Is_Already_Open)
this subtable (identified by its name) is already open.Error(No_subtable)
no uncrypted subtable with this name exists.iterations
: Used to check the signature (useless if the signwd is empty).name
: the subtable name.signwd
: If a non-empty signword is provided, use it to check the signature of this subtable.val append_subtable : full table ->
name:string ->
?iterations:int ->
passwd:string ->
signwd:string ->
check_signature:bool -> unit -> full subtable
Error(Is_Closed)
the database is closed.Error(Bad_password)
the given password does not match this subtable's password.Error(Bad_signature)
the subtable signature does not match the expected signature, using the given non-empty signword.Error(No_signature)
the subtable is not signed, but a signwd was provided.Error(Is_Already_Open)
this subtable (identified by its name) is already open.Error(No_subtable)
no standard subtable with this name exists (if the subtable is explicitly uncrypted, use Cryptodbm.append_uncrypted_subtable
instead).name
: the subtable name.iterations
: Number of iterations used to map the password/signword. (See open_read)passwd
: If a non-empty password is provided, use it to decrypt and encrypt.signwd
: Use the given signword to sign this subtable.check_signature
: Whether to check the existing signature before appending new data. If the subtable was not signed, this parameter has no effect.val append_uncrypted_subtable : ?iterations:int ->
full table ->
name:string ->
signwd:string ->
check_signature:bool -> unit -> full subtable
Error(Is_Closed)
the database is closed.Error(Bad_signature)
the subtable signature does not match the expected signature, using the given non-empty signword.Error(No_signature)
the subtable is not signed, but a signwd was provided.Error(Is_Already_Open)
this subtable (identified by its name) is already open.Error(No_subtable)
no uncrypted subtable with this name exists.iterations
: Used to check the signature (useless if the signwd is empty).name
: the subtable name.signwd
: Use the given signword to sign this subtable.check_signature
: Whether to check the existing signature before appending new data. If the subtable was not signed, this parameter has no effect.val close_subtable : 'a subtable -> unit
Error(Is_Closed)
the subtable is already closed.val get_number : 'a subtable -> int
val get_name : 'a subtable -> string
val iter_subtables : 'a table -> (string -> int -> unit) -> unit
Error(Is_Closed)
the table is already closed.val iter_uncrypted_subtables : 'a table -> (string -> int -> unit) -> unit
Error(Is_Closed)
the table is already closed.val add : ?may_overwrite:bool ->
full subtable -> key:string -> data:string -> unit
add subt key data
binds key
to data
in subtable subt
.
By default, overwriting an existing binding is forbidden.Error(Overwrite)
the key is already bound and may_overwrite
is false.Error(Is_Closed)
the subtable is already closed.may_overwrite
: if true, replacing an old binding by a new binding is permitted.val find : 'a subtable -> string -> string
find subt key
returns the data associated to key
in subtable subt
.Error(Unbound)
the key is not bound in this subtable.Error(Is_Closed)
the subtable is already closed.val delete : full subtable -> string -> unit
delete subt key
removes the binding associated to key
in subtable subt
.Error(Unbound)
the key is not bound in this subtable.Error(Is_Closed)
the subtable is already closed.val clear : full subtable -> unit
clear subt
removes all the bindings in subtable subt
.Error(Is_Closed)
the subtable is already closed.val iter : 'a subtable -> (string -> string -> unit) -> unit
Error(Is_Closed)
the subtable is already closed.val iterkey : 'a subtable -> (string -> unit) -> unit
iter
since data is not decrypted.Error(Is_Closed)
the subtable is already closed.val fold : 'a subtable -> 'b -> (string -> string -> 'b -> 'b) -> 'b
val iter_extra_bindings : 'a table -> (string -> string -> unit) -> unit
val export : read table -> binfile:string -> unit
val import : binfile:string -> dbfile:string -> unit
dbfile
is the output dbfile, without .pag or .dir extension