···11+# v1.0.0 (2024-08-28)
22+33+* Migrate scrypt from Cstruct.t to string
44+* Merge ocaml-pbkdf (from https://github.com/abeaumont/ocaml-pbkdf),
55+ hkdf (from https://github.com/hannesm/ocaml-hkdf), and scrypt (from
66+ https://github.com/abeaumont/ocaml-scrypt-kdf) into a single repository
77+ and opam package (with three subpackages, kdf.pbkdf. kdf.hkdf, and
88+ kdf.scrypt).
99+* Disable a failing testcase for architectures with integers no longer than 31
1010+ bits (thanks to @kit-ty-kate)
1111+1212+# pbkdf 2.0.0 (2024-06-29)
1313+1414+* Update to mirage-crypto 1.0.0 (#13 @dinosaure)
1515+1616+# hkdf v2.0.0 (2024-06-29)
1717+1818+* use digestif instead of mirage-crypto (@dinosaure @hannesm)
1919+2020+# scrypt-kdf 1.2.0 (2021-08-03)
2121+2222+* Upgrade to Cstruct 6.0.0
2323+2424+# pbkdf 1.2.0 (2020-08-03)
2525+2626+* Upgrade to Cstruct 6.0.0
2727+2828+# pbkdf 1.1.0 (2020-03-31)
2929+3030+* Port to mirage-crypto (thanks to @hannesm)
3131+3232+# scrypt-kdf 1.1.0 (2020-03-31)
3333+3434+* Port to mirage-crypto (thanks to @hannesm)
3535+3636+# hkdf v1.0.4 (2020-03-11)
3737+3838+* use mirage-crypto instead of nocrypto
3939+4040+# scrypt-kdf 1.0.0 (2019-04-12)
4141+4242+* Move to dune
4343+* Upgrade to opam 2.0
4444+4545+# pbkdf 1.0.0 (2019-04-12)
4646+4747+* Move to dune
4848+* Upgrade to opam 2.0
4949+* Reimplement `cdiv`, no longer available in `nocrypto`.
5050+5151+# hkdf 1.0.3 (2019-02-15)
5252+5353+* move to dune
5454+5555+# pbkdf 0.3.0 (2018-02-16)
5656+5757+* Build: switch to jbuilder
5858+5959+# scrypt-kdf 0.4.0 (2017-03-09)
6060+6161+* Removed Makefile, unneeded with topkg
6262+* Made pkg.ml executable
6363+* Added salsa20-core as a dependency and remove related code
6464+6565+# scrypt-kdf 0.3.0 (2017-02-21)
6666+6767+* Replaced underscores by dashes in library names
6868+* Exported Salsa20_core module
6969+7070+# pbkdf 0.2.0 (2016-10-31)
7171+7272+* Added topkg dependency
7373+7474+# scrypt-kdf 0.2.0 (2016-10-31)
7575+7676+* Added topkg dependency
7777+* Optimized inner loop in salsa_core to improve performance
7878+* Replaced custom clone function by Nocrypto's implementation
7979+8080+# hkdf 1.0.2 (2016-07-18)
8181+8282+* move to topkg
8383+8484+# scrypt-kdf 0.1.0 (2016-03-18)
8585+8686+* Initial release
8787+8888+# pbkdf 0.1.0 (2016-03-14)
8989+9090+* Initial release
9191+9292+# hkdf 1.0.1 (2015-12-20)
9393+9494+* move from oasis to topkg
9595+9696+# hkdf 1.0.0 (2015-11-30)
9797+9898+* initial release
+25
LICENSE.md
···11+Copyright (c) 2014, Hannes Mehnert
22+Copyright (c) 2016, Alfredo Beaumont, Sonia Meruelo
33+All rights reserved.
44+55+Redistribution and use in source and binary forms, with or without
66+modification, are permitted provided that the following conditions are met:
77+88+* Redistributions of source code must retain the above copyright notice, this
99+ list of conditions and the following disclaimer.
1010+1111+* Redistributions in binary form must reproduce the above copyright notice,
1212+ this list of conditions and the following disclaimer in the documentation
1313+ and/or other materials provided with the distribution.
1414+1515+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1616+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1717+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1818+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
1919+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2020+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2121+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2222+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2323+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2424+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2525+
+17
README.md
···11+# kdf - Key Derivation Functions
22+33+This repository provides multiple already specified key derivation functions in
44+and for OCaml:
55+66+- [scrypt](https://tools.ietf.org/html/rfc7914),
77+- [PBKDF 1 and 2 as defined by PKCS#5](https://tools.ietf.org/html/rfc2898),
88+- and [HKDF](https://tools.ietf.org/html/rfc5869).
99+1010+## Documentation
1111+1212+[API Documentation](https://robur-coop.github.io/kdf/doc)
1313+1414+## Installation
1515+1616+`opam install kdf` will install the latest released version.
1717+
···11+22+module type S = sig
33+ val extract : ?salt:string -> string -> string
44+ val expand : prk:string -> ?info:string -> int -> string
55+end
66+77+module Make (H : Digestif.S) : S = struct
88+ let extract ?salt ikm =
99+ let key = match salt with
1010+ | None -> String.make H.digest_size '\x00'
1111+ | Some x -> x
1212+ in
1313+ H.(to_raw_string (hmac_string ~key ikm))
1414+1515+ let expand ~prk ?info len =
1616+ let info = match info with
1717+ | None -> ""
1818+ | Some x -> x
1919+ in
2020+ let t n last =
2121+ let nc = String.make 1 (Char.unsafe_chr n) in
2222+ H.(to_raw_string (hmac_string ~key:prk (String.concat "" [last ; info ; nc])))
2323+ in
2424+ let n = succ (len / H.digest_size) in
2525+ let rec compute acc count = match count, acc with
2626+ | c, xs when c > n -> String.concat "" (List.rev xs)
2727+ | c, x::_ -> compute (t c x :: acc) (succ c)
2828+ | _, [] -> invalid_arg "can not happen"
2929+ in
3030+ let buf = compute [""] 1 in
3131+ String.sub buf 0 len
3232+end
3333+3434+let extract ~hash ?salt ikm =
3535+ let module H = (val (Digestif.module_of_hash' hash)) in
3636+ let module HKDF = Make (H) in
3737+ HKDF.extract ?salt ikm
3838+3939+let expand ~hash ~prk ?info len =
4040+ let module H = (val (Digestif.module_of_hash' hash)) in
4141+ let module HKDF = Make (H) in
4242+ HKDF.expand ~prk ?info len
+27
hkdf/hkdf.mli
···11+22+(** {{:https://tools.ietf.org/html/rfc5869}RFC 5869} specifies a HMAC-based
33+ Extract-and-Expand Key Derivation Function (HKDF), which is abstracted over
44+ a specific hash function. *)
55+66+module type S = sig
77+88+ (** [extract salt ikm] is [prk], the pseudorandom key of hash length octets.
99+ The [salt] is an optional non-secret random value, [ikm] the input key
1010+ material. *)
1111+ val extract : ?salt:string -> string -> string
1212+1313+ (** [expand prk info length] is [okm], the output keying material. Given the
1414+ pseudorandom key of hash length (usually output of [!extract] step), and an
1515+ optional context and application specific information [info], the [okm] is
1616+ generated. *)
1717+ val expand : prk:string -> ?info:string -> int -> string
1818+end
1919+2020+(** Given a Hash function, get the HKDF *)
2121+module Make (H : Digestif.S) : S
2222+2323+(** convenience [extract hash salt ikm] where the [hash] has to be provided explicitly *)
2424+val extract : hash:Digestif.hash' -> ?salt:string -> string -> string
2525+2626+(** convenience [expand hash prk info len] where the [hash] has to be provided explicitly *)
2727+val expand : hash:Digestif.hash' -> prk:string -> ?info:string -> int -> string
···11+module type S = sig
22+ val pbkdf1 : password:string -> salt:string -> count:int -> dk_len:int -> string
33+ val pbkdf2 : password:string -> salt:string -> count:int -> dk_len:int32 -> string
44+end
55+66+let cdiv x y =
77+ (* This is lifted from Nocrypto.Uncommon.(//)
88+ (formerly known as [cdiv]). It is part of the documented, publically
99+ exposed _internal_ utility library not for public consumption, hence
1010+ the API break that prompted this copy-pasted function. *)
1111+ if y < 1 then raise Division_by_zero else
1212+ if x > 0 then 1 + ((x - 1) / y) else 0 [@@inline]
1313+1414+module Make (H: Digestif.S) : S = struct
1515+ let pbkdf1 ~password ~salt ~count ~dk_len =
1616+ if String.length salt <> 8 then invalid_arg "salt should be 8 bytes"
1717+ else if count <= 0 then invalid_arg "count must be a positive integer"
1818+ else if dk_len <= 0 then invalid_arg "derived key length must be a positive integer"
1919+ else if dk_len > H.digest_size then invalid_arg "derived key too long"
2020+ else
2121+ let rec loop t = function
2222+ 0 -> t
2323+ | i -> loop H.(to_raw_string (digest_string t)) (i - 1)
2424+ in
2525+ String.sub (loop (password ^ salt) count) 0 dk_len
2626+2727+ let pbkdf2 ~password ~salt ~count ~dk_len =
2828+ if count <= 0 then invalid_arg "count must be a positive integer"
2929+ else if dk_len <= 0l then invalid_arg "derived key length must be a positive integer"
3030+ else
3131+ let h_len = H.digest_size
3232+ and dk_len = Int32.to_int dk_len in
3333+ let l = cdiv dk_len h_len in
3434+ let r = dk_len - (l - 1) * h_len in
3535+ let block i =
3636+ let rec f u xor = function
3737+ | 0 -> xor
3838+ | j ->
3939+ let u = H.(to_raw_string (hmac_string ~key:password u)) in
4040+ f u (Crypto.Uncommon.xor xor u) (j - 1)
4141+ in
4242+ let int_i = Bytes.create 4 in
4343+ Bytes.set_int32_be int_i 0 (Int32.of_int i);
4444+ let u_1 = H.hmac_string ~key:password (salt ^ Bytes.unsafe_to_string int_i) in
4545+ let u_1 = H.to_raw_string u_1 in
4646+ f u_1 u_1 (count - 1)
4747+ in
4848+ let rec loop blocks = function
4949+ | 0 -> blocks
5050+ | i -> loop (block i :: blocks) (i - 1)
5151+ in
5252+ String.concat "" (loop [String.sub (block l) 0 r] (l - 1))
5353+end
5454+5555+let pbkdf1 ~hash ~password ~salt ~count ~dk_len =
5656+ let module H = (val (Digestif.module_of_hash' hash)) in
5757+ let module PBKDF = Make (H) in
5858+ PBKDF.pbkdf1 ~password ~salt ~count ~dk_len
5959+6060+let pbkdf2 ~prf ~password ~salt ~count ~dk_len =
6161+ let module H = (val (Digestif.module_of_hash' prf)) in
6262+ let module PBKDF = Make (H) in
6363+ PBKDF.pbkdf2 ~password ~salt ~count ~dk_len
+23
pbkdf/pbkdf.mli
···11+(** {{:https://tools.ietf.org/html/rfc2898}RFC 2898} specifies two password-based
22+ key derivation functions (PBKDF1 and PBKDF2), which are abstracted over
33+ a specific hash/pseudorandom function. *)
44+module type S = sig
55+ (** [pbkdf1 password salt count dk_len] is [dk], the derived key of [dk_len] octets.
66+ The [salt] must be eight octets, [count] the iteration count.
77+ @raise Invalid_argument when either [salt] is not eight octets long or either
88+ [count] or [dk_len] are not valid. *)
99+ val pbkdf1 : password:string -> salt:string -> count:int -> dk_len:int -> string
1010+1111+ (** [pbkdf2 password salt count dk_len] is [dk], the derived key of [dk_len] octets.
1212+ @raise Invalid_argument when either [count] or [dk_len] are not valid *)
1313+ val pbkdf2 : password:string -> salt:string -> count:int -> dk_len:int32 -> string
1414+end
1515+1616+(** Given a Hash/pseudorandom function, get the PBKDF *)
1717+module Make (H: Digestif.S) : S
1818+1919+(** convenience [pbkdf1 hash password salt count dk_len] where the [hash] has to be provided explicitly *)
2020+val pbkdf1 : hash:Digestif.hash' -> password:string -> salt:string -> count:int -> dk_len:int -> string
2121+2222+(** convenience [pbkdf2 prf password salt count dk_len] where the [prf] has to be provided explicitly *)
2323+val pbkdf2 : prf:Digestif.hash' -> password:string -> salt:string -> count:int -> dk_len:int32 -> string
···11+external salsa_core : int -> string -> bytes -> unit = "caml_salsa_core" [@@noalloc]
22+33+let salsa20_core count i =
44+ let l = 64 in
55+ if String.length i <> l then invalid_arg "input must be 16 blocks of 32 bits"
66+ else
77+ let o = Bytes.create l in
88+ salsa_core count i o;
99+ Bytes.unsafe_to_string o
1010+1111+let salsa20_8_core i =
1212+ salsa20_core 4 i
1313+1414+let scrypt_block_mix b r =
1515+ let b' = Bytes.create (String.length b) in
1616+ let x = Bytes.create 64 in
1717+ Bytes.unsafe_blit_string b ((2 * r - 1) * 64) x 0 64;
1818+ for i = 0 to 2 * r - 1 do
1919+ let b_i = Bytes.unsafe_of_string (String.sub b (i * 64) 64) in
2020+ Crypto.Uncommon.unsafe_xor_into (Bytes.unsafe_to_string x) ~src_off:0 b_i ~dst_off:0 64;
2121+ Bytes.unsafe_blit_string (salsa20_8_core (Bytes.unsafe_to_string b_i)) 0 x 0 64;
2222+ let offset = (i mod 2) lsl (max 0 (r / 2 - 1)) + i / 2 in
2323+ Bytes.blit x 0 b' (offset * 64) 64
2424+ done;
2525+ b'
2626+2727+let scrypt_ro_mix b ~r ~n =
2828+ let blen = r * 128 in
2929+ let x = ref (Bytes.copy b) in
3030+ let v = Bytes.create (blen * n) in
3131+ for i = 0 to n - 1 do
3232+ Bytes.unsafe_blit !x 0 v (blen * i) blen;
3333+ x := scrypt_block_mix (Bytes.unsafe_to_string !x) r
3434+ done;
3535+ for _ = 0 to n - 1 do
3636+ let integerify x =
3737+ let k = Bytes.get_int32_le x (128 * r - 64) in
3838+ let n' = n - 1 in
3939+ Int32.(to_int (logand k (of_int n')))
4040+ in
4141+ let j = integerify !x in
4242+ Crypto.Uncommon.unsafe_xor_into (Bytes.unsafe_to_string v) ~src_off:(blen * j) !x ~dst_off:0 blen;
4343+ x := scrypt_block_mix (Bytes.unsafe_to_string !x) r;
4444+ done;
4545+ !x
4646+4747+let scrypt ~password ~salt ~n ~r ~p ~dk_len =
4848+ let is_power_of_2 x = (x land (x - 1)) = 0 in
4949+ if n <= 1 then invalid_arg "n must be larger than 1"
5050+ else if not (is_power_of_2 n) then invalid_arg "n must be a power of 2"
5151+ else if p <= 0 then invalid_arg "p must be a positive integer"
5252+ else if p > (Int64.to_int (Int64.div 0xffffffffL 4L) / r) then invalid_arg "p too big"
5353+ else if dk_len <= 0l then invalid_arg "derived key length must be a positive integer";
5454+ let rec partition b blocks = function
5555+ | 0 -> blocks
5656+ | i ->
5757+ let off = (i - 1) * r * 128 in
5858+ let block = Bytes.unsafe_of_string (String.sub b off (r * 128)) in
5959+ partition b (block :: blocks) (i - 1)
6060+ in
6161+ let blen = Int32.of_int (128 * r * p) in
6262+ let dk = Pbkdf.pbkdf2 ~prf:`SHA256 ~password ~salt ~count:1 ~dk_len:blen in
6363+ let b = partition dk [] p in
6464+ let b' = List.map (scrypt_ro_mix ~r ~n) b in
6565+ let salt = String.concat "" (List.map Bytes.unsafe_to_string b') in
6666+ Pbkdf.pbkdf2 ~prf:`SHA256 ~password ~salt ~count:1 ~dk_len
+15
scrypt/scrypt.mli
···11+(** {{:https://tools.ietf.org/html/rfc7914}
22+ The scrypt Password-Based Key Derivation Function}
33+ specifies the password-based key derivation function scrypt. The
44+ function derives one or more secret keys from a secret string.
55+ It is based on memory-hard functions which offer added protection
66+ against attacks using custom hardware. *)
77+88+(** [scrypt_kdf password salt n r p dk_len] is [dk], the derived key
99+ of [dk_len] octets.
1010+ [n], the cost parameter, must be larger than 1 and a power of 2.
1111+ [p], the parallelization parameter, must be a possitive integer
1212+ and less than or equal to 2^32 - 1 / (4 * r)
1313+ @raise Invalid_argument when either [n], [p] or [dk_len] are not
1414+ valid *)
1515+val scrypt : password:string -> salt:string -> n:int -> r:int -> p:int -> dk_len:int32 -> string