This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

7
unikernel/duniverse/kdf/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
_build/
_tests/
coverage/
*.install
**/*.merlin
*.byte
*.native

View file

@ -0,0 +1,98 @@
# v1.0.0 (2024-08-28)
* Migrate scrypt from Cstruct.t to string
* Merge ocaml-pbkdf (from https://github.com/abeaumont/ocaml-pbkdf),
hkdf (from https://github.com/hannesm/ocaml-hkdf), and scrypt (from
https://github.com/abeaumont/ocaml-scrypt-kdf) into a single repository
and opam package (with three subpackages, kdf.pbkdf. kdf.hkdf, and
kdf.scrypt).
* Disable a failing testcase for architectures with integers no longer than 31
bits (thanks to @kit-ty-kate)
# pbkdf 2.0.0 (2024-06-29)
* Update to mirage-crypto 1.0.0 (#13 @dinosaure)
# hkdf v2.0.0 (2024-06-29)
* use digestif instead of mirage-crypto (@dinosaure @hannesm)
# scrypt-kdf 1.2.0 (2021-08-03)
* Upgrade to Cstruct 6.0.0
# pbkdf 1.2.0 (2020-08-03)
* Upgrade to Cstruct 6.0.0
# pbkdf 1.1.0 (2020-03-31)
* Port to mirage-crypto (thanks to @hannesm)
# scrypt-kdf 1.1.0 (2020-03-31)
* Port to mirage-crypto (thanks to @hannesm)
# hkdf v1.0.4 (2020-03-11)
* use mirage-crypto instead of nocrypto
# scrypt-kdf 1.0.0 (2019-04-12)
* Move to dune
* Upgrade to opam 2.0
# pbkdf 1.0.0 (2019-04-12)
* Move to dune
* Upgrade to opam 2.0
* Reimplement `cdiv`, no longer available in `nocrypto`.
# hkdf 1.0.3 (2019-02-15)
* move to dune
# pbkdf 0.3.0 (2018-02-16)
* Build: switch to jbuilder
# scrypt-kdf 0.4.0 (2017-03-09)
* Removed Makefile, unneeded with topkg
* Made pkg.ml executable
* Added salsa20-core as a dependency and remove related code
# scrypt-kdf 0.3.0 (2017-02-21)
* Replaced underscores by dashes in library names
* Exported Salsa20_core module
# pbkdf 0.2.0 (2016-10-31)
* Added topkg dependency
# scrypt-kdf 0.2.0 (2016-10-31)
* Added topkg dependency
* Optimized inner loop in salsa_core to improve performance
* Replaced custom clone function by Nocrypto's implementation
# hkdf 1.0.2 (2016-07-18)
* move to topkg
# scrypt-kdf 0.1.0 (2016-03-18)
* Initial release
# pbkdf 0.1.0 (2016-03-14)
* Initial release
# hkdf 1.0.1 (2015-12-20)
* move from oasis to topkg
# hkdf 1.0.0 (2015-11-30)
* initial release

View file

@ -0,0 +1,25 @@
Copyright (c) 2014, Hannes Mehnert
Copyright (c) 2016, Alfredo Beaumont, Sonia Meruelo
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,17 @@
# kdf - Key Derivation Functions
This repository provides multiple already specified key derivation functions in
and for OCaml:
- [scrypt](https://tools.ietf.org/html/rfc7914),
- [PBKDF 1 and 2 as defined by PKCS#5](https://tools.ietf.org/html/rfc2898),
- and [HKDF](https://tools.ietf.org/html/rfc5869).
## Documentation
[API Documentation](https://robur-coop.github.io/kdf/doc)
## Installation
`opam install kdf` will install the latest released version.

View file

@ -0,0 +1,3 @@
(lang dune 1.8)
(name kdf)
(version v1.0.0)

View file

@ -0,0 +1,5 @@
(library
(name hkdf)
(public_name kdf.hkdf)
(modules hkdf)
(libraries digestif))

View file

@ -0,0 +1,42 @@
module type S = sig
val extract : ?salt:string -> string -> string
val expand : prk:string -> ?info:string -> int -> string
end
module Make (H : Digestif.S) : S = struct
let extract ?salt ikm =
let key = match salt with
| None -> String.make H.digest_size '\x00'
| Some x -> x
in
H.(to_raw_string (hmac_string ~key ikm))
let expand ~prk ?info len =
let info = match info with
| None -> ""
| Some x -> x
in
let t n last =
let nc = String.make 1 (Char.unsafe_chr n) in
H.(to_raw_string (hmac_string ~key:prk (String.concat "" [last ; info ; nc])))
in
let n = succ (len / H.digest_size) in
let rec compute acc count = match count, acc with
| c, xs when c > n -> String.concat "" (List.rev xs)
| c, x::_ -> compute (t c x :: acc) (succ c)
| _, [] -> invalid_arg "can not happen"
in
let buf = compute [""] 1 in
String.sub buf 0 len
end
let extract ~hash ?salt ikm =
let module H = (val (Digestif.module_of_hash' hash)) in
let module HKDF = Make (H) in
HKDF.extract ?salt ikm
let expand ~hash ~prk ?info len =
let module H = (val (Digestif.module_of_hash' hash)) in
let module HKDF = Make (H) in
HKDF.expand ~prk ?info len

View file

@ -0,0 +1,27 @@
(** {{:https://tools.ietf.org/html/rfc5869}RFC 5869} specifies a HMAC-based
Extract-and-Expand Key Derivation Function (HKDF), which is abstracted over
a specific hash function. *)
module type S = sig
(** [extract salt ikm] is [prk], the pseudorandom key of hash length octets.
The [salt] is an optional non-secret random value, [ikm] the input key
material. *)
val extract : ?salt:string -> string -> string
(** [extract prk info length] is [okm], the output keying material. Given the
pseudorandom key of hash length (usually output of [!extract] step), and an
optional context and application specific information [info], the [okm] is
generated. *)
val expand : prk:string -> ?info:string -> int -> string
end
(** Given a Hash function, get the HKDF *)
module Make (H : Digestif.S) : S
(** convenience [extract hash salt ikm] where the [hash] has to be provided explicitly *)
val extract : hash:Digestif.hash' -> ?salt:string -> string -> string
(** convenience [expand hash prk info len] where the [hash] has to be provided explicitly *)
val expand : hash:Digestif.hash' -> prk:string -> ?info:string -> int -> string

View file

@ -0,0 +1,4 @@
(test
(name rfctests)
(modules rfctests)
(libraries alcotest kdf.hkdf ohex))

View file

@ -0,0 +1,149 @@
let test ~hash ~ikm ?salt ?info ~l ~prk ~okm () =
let ikm = Ohex.decode ikm
and salt = match salt with None -> None | Some x -> Some (Ohex.decode x)
and info = match info with None -> None | Some x -> Some (Ohex.decode x)
and prk = Ohex.decode prk
and okm = Ohex.decode okm
in
(fun () ->
let cprk = Hkdf.extract ~hash ?salt ikm in
Alcotest.check Alcotest.string "PRK matches" prk cprk ;
let cokm = Hkdf.expand ~hash ~prk:cprk ?info l in
Alcotest.check Alcotest.string "OKM matches" okm cokm)
let test1 =
test
~hash:`SHA256
~ikm:"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
~salt:"000102030405060708090a0b0c"
~info:"f0f1f2f3f4f5f6f7f8f9"
~l:42
~prk:"077709362c2e32df0ddc3f0dc47bba63 \
90b6c73bb50f9c3122ec844ad7c2b3e5"
~okm:"3cb25f25faacd57a90434f64d0362f2a \
2d2d0a90cf1a5a4c5db02d56ecc4c5bf \
34007208d5b887185865"
()
and test2 =
test
~hash:`SHA256
~ikm:"000102030405060708090a0b0c0d0e0f \
101112131415161718191a1b1c1d1e1f \
202122232425262728292a2b2c2d2e2f \
303132333435363738393a3b3c3d3e3f \
404142434445464748494a4b4c4d4e4f"
~salt:"606162636465666768696a6b6c6d6e6f \
707172737475767778797a7b7c7d7e7f \
808182838485868788898a8b8c8d8e8f \
909192939495969798999a9b9c9d9e9f \
a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
~info:"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf \
c0c1c2c3c4c5c6c7c8c9cacbcccdcecf \
d0d1d2d3d4d5d6d7d8d9dadbdcdddedf \
e0e1e2e3e4e5e6e7e8e9eaebecedeeef \
f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
~l:82
~prk:"06a6b88c5853361a06104c9ceb35b45c \
ef760014904671014a193f40c15fc244"
~okm:"b11e398dc80327a1c8e7f78c596a4934 \
4f012eda2d4efad8a050cc4c19afa97c \
59045a99cac7827271cb41c65e590e09 \
da3275600c2f09b8367793a9aca3db71 \
cc30c58179ec3e87c14c01d5c1f3434f \
1d87"
()
and test3 =
test
~hash:`SHA256
~ikm:"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
~salt:""
(* info = (0 octets) *)
~l:42
~prk:"19ef24a32c717b167f33a91d6f648bdf \
96596776afdb6377ac434c1c293ccb04"
~okm:"8da4e775a563c18f715f802a063c5a31 \
b8a11f5c5ee1879ec3454e5f3c738d2d \
9d201395faa4b61a96c8"
()
and test4 =
test
~hash:`SHA1
~ikm:"0b0b0b0b0b0b0b0b0b0b0b"
~salt:"000102030405060708090a0b0c"
~info:"f0f1f2f3f4f5f6f7f8f9"
~l:42
~prk:"9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243"
~okm:"085a01ea1b10f36933068b56efa5ad81 \
a4f14b822f5b091568a9cdd4f155fda2 \
c22e422478d305f3f896"
()
and test5 =
test
~hash:`SHA1
~ikm:"000102030405060708090a0b0c0d0e0f \
101112131415161718191a1b1c1d1e1f \
202122232425262728292a2b2c2d2e2f \
303132333435363738393a3b3c3d3e3f \
404142434445464748494a4b4c4d4e4f"
~salt:"606162636465666768696a6b6c6d6e6f \
707172737475767778797a7b7c7d7e7f \
808182838485868788898a8b8c8d8e8f \
909192939495969798999a9b9c9d9e9f \
a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
~info:"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf \
c0c1c2c3c4c5c6c7c8c9cacbcccdcecf \
d0d1d2d3d4d5d6d7d8d9dadbdcdddedf \
e0e1e2e3e4e5e6e7e8e9eaebecedeeef \
f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
~l:82
~prk:"8adae09a2a307059478d309b26c4115a224cfaf6"
~okm:"0bd770a74d1160f7c9f12cd5912a06eb \
ff6adcae899d92191fe4305673ba2ffe \
8fa3f1a4e5ad79f3f334b3b202b2173c \
486ea37ce3d397ed034c7f9dfeb15c5e \
927336d0441f4c4300e2cff0d0900b52 \
d3b4"
()
and test6 =
test
~hash:`SHA1
~ikm:"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
~salt:""
(* info = (0 octets) *)
~l:42
~prk:"da8c8a73c7fa77288ec6f5e7c297786aa0d32d01"
~okm:"0ac1af7002b3d761d1e55298da9d0506 \
b9ae52057220a306e07b6b87e8df21d0 \
ea00033de03984d34918"
()
and test7 =
test
~hash:`SHA1
~ikm:"0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"
(* salt = not provided (defaults to HashLen zero octets) *)
(* info = (0 octets) *)
~l:42
~prk:"2adccada18779e7c2077ad2eb19d3f3e731385dd"
~okm:"2c91117204d745f3500d636a62f64f0a \
b3bae548aa53d423b0d1f27ebba6f5e5 \
673a081d70cce7acfc48"
()
let tests = [
"RFC 5869 Test Case 1", `Quick, test1 ;
"RFC 5869 Test Case 2", `Quick, test2 ;
"RFC 5869 Test Case 3", `Quick, test3 ;
"RFC 5869 Test Case 4", `Quick, test4 ;
"RFC 5869 Test Case 5", `Quick, test5 ;
"RFC 5869 Test Case 6", `Quick, test6 ;
"RFC 5869 Test Case 7", `Quick, test7 ;
]
let () = Alcotest.run "HKDF Tests" [ "RFC 5869", tests ]

View file

@ -0,0 +1,29 @@
version: "1.0.0"
opam-version: "2.0"
name: "kdf"
maintainer: ["Alfredo Beaumont <alfredo.beaumont@gmail.com>" "Hannes Mehnert <hannes@mehnert.org>"]
authors: ["Alfredo Beaumont <alfredo.beaumont@gmail.com>" "Sonia Meruelo <smeruelo@gmail.com>" "Hannes Mehnert <hannes@mehnert.org>"]
license: "BSD-2-Clause"
homepage: "https://github.com/robur-coop/kdf"
doc: "https://robur-coop.github.io/kdf/doc"
bug-reports: "https://github.com/robur-coop/kdf/issues"
depends: [
"ocaml" {>= "4.13.0"}
"dune" {>= "1.8.0"}
"digestif" {>= "1.2.0"}
"mirage-crypto" {>= "1.0.0"}
"alcotest" {with-test & >= "0.8.1"}
"ohex" {with-test & >= "0.2.0"}
]
build: [
["dune" "subst"] {dev}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
dev-repo: "git+https://github.com/robur-coop/kdf.git"
synopsis: "Key Derivation Functions: HKDF RFC 5869, PBKDF RFC 2898, SCRYPT RFC 7914"
description: """
A pure OCaml implementation of [scrypt](https://tools.ietf.org/html/rfc7914),
[PBKDF 1 and 2 as defined by PKCS#5](https://tools.ietf.org/html/rfc2898),
and [HKDF](https://tools.ietf.org/html/rfc5869).
"""

View file

@ -0,0 +1,4 @@
(library
(name pbkdf)
(public_name kdf.pbkdf)
(libraries digestif mirage-crypto))

View file

@ -0,0 +1,63 @@
module type S = sig
val pbkdf1 : password:string -> salt:string -> count:int -> dk_len:int -> string
val pbkdf2 : password:string -> salt:string -> count:int -> dk_len:int32 -> string
end
let cdiv x y =
(* This is lifted from Nocrypto.Uncommon.(//)
(formerly known as [cdiv]). It is part of the documented, publically
exposed _internal_ utility library not for public consumption, hence
the API break that prompted this copy-pasted function. *)
if y < 1 then raise Division_by_zero else
if x > 0 then 1 + ((x - 1) / y) else 0 [@@inline]
module Make (H: Digestif.S) : S = struct
let pbkdf1 ~password ~salt ~count ~dk_len =
if String.length salt <> 8 then invalid_arg "salt should be 8 bytes"
else if count <= 0 then invalid_arg "count must be a positive integer"
else if dk_len <= 0 then invalid_arg "derived key length must be a positive integer"
else if dk_len > H.digest_size then invalid_arg "derived key too long"
else
let rec loop t = function
0 -> t
| i -> loop H.(to_raw_string (digest_string t)) (i - 1)
in
String.sub (loop (password ^ salt) count) 0 dk_len
let pbkdf2 ~password ~salt ~count ~dk_len =
if count <= 0 then invalid_arg "count must be a positive integer"
else if dk_len <= 0l then invalid_arg "derived key length must be a positive integer"
else
let h_len = H.digest_size
and dk_len = Int32.to_int dk_len in
let l = cdiv dk_len h_len in
let r = dk_len - (l - 1) * h_len in
let block i =
let rec f u xor = function
| 0 -> xor
| j ->
let u = H.(to_raw_string (hmac_string ~key:password u)) in
f u (Mirage_crypto.Uncommon.xor xor u) (j - 1)
in
let int_i = Bytes.create 4 in
Bytes.set_int32_be int_i 0 (Int32.of_int i);
let u_1 = H.hmac_string ~key:password (salt ^ Bytes.unsafe_to_string int_i) in
let u_1 = H.to_raw_string u_1 in
f u_1 u_1 (count - 1)
in
let rec loop blocks = function
| 0 -> blocks
| i -> loop (block i :: blocks) (i - 1)
in
String.concat "" (loop [String.sub (block l) 0 r] (l - 1))
end
let pbkdf1 ~hash ~password ~salt ~count ~dk_len =
let module H = (val (Digestif.module_of_hash' hash)) in
let module PBKDF = Make (H) in
PBKDF.pbkdf1 ~password ~salt ~count ~dk_len
let pbkdf2 ~prf ~password ~salt ~count ~dk_len =
let module H = (val (Digestif.module_of_hash' prf)) in
let module PBKDF = Make (H) in
PBKDF.pbkdf2 ~password ~salt ~count ~dk_len

View file

@ -0,0 +1,23 @@
(** {{:https://tools.ietf.org/html/rfc2898}RFC 2898} specifies two password-based
key derivation functions (PBKDF1 and PBKDF2), which are abstracted over
a specific hash/pseudorandom function. *)
module type S = sig
(** [pbkdf1 password salt count dk_len] is [dk], the derived key of [dk_len] octets.
The [salt] must be eight octets, [count] the iteration count.
@raise Invalid_argument when either [salt] is not eight octets long or either
[count] or [dk_len] are not valid. *)
val pbkdf1 : password:string -> salt:string -> count:int -> dk_len:int -> string
(** [pbkdf2 password salt count dk_len] is [dk], the derived key of [dk_len] octets.
@raise Invalid_argument when either [count] or [dk_len] are not valid *)
val pbkdf2 : password:string -> salt:string -> count:int -> dk_len:int32 -> string
end
(** Given a Hash/pseudorandom function, get the PBKDF *)
module Make (H: Digestif.S) : S
(** convenience [pbkdf1 hash password salt count dk_len] where the [hash] has to be provided explicitly *)
val pbkdf1 : hash:Digestif.hash' -> password:string -> salt:string -> count:int -> dk_len:int -> string
(** convenience [pbkdf2 prf password salt count dk_len] where the [prf] has to be provided explicitly *)
val pbkdf2 : prf:Digestif.hash' -> password:string -> salt:string -> count:int -> dk_len:int32 -> string

View file

@ -0,0 +1,3 @@
(test
(name pbkdf_tests)
(libraries ohex kdf.pbkdf alcotest))

View file

@ -0,0 +1,287 @@
let () = Printexc.record_backtrace true
(* PBKDF1 *)
let test_pbkdf1 ~hash ~password ~salt ~count ~dk_len ~dk =
let salt = Ohex.decode salt
and dk = Ohex.decode dk in
(fun () ->
let edk = Pbkdf.pbkdf1 ~hash ~password ~salt ~count ~dk_len in
Alcotest.check Alcotest.string "PBKDF1 test" edk dk)
let test_pbkdf1_invalid_arg ~hash ~password ~salt ~count ~dk_len ~msg =
let salt = Ohex.decode salt in
(fun () ->
Alcotest.check_raises
msg
(Invalid_argument msg)
(fun () -> ignore (Pbkdf.pbkdf1 ~hash ~password ~salt ~count ~dk_len)))
(* Taken from http://www.di-mgt.com.au/cryptoKDFs.html *)
let pbkdf1_test1 =
test_pbkdf1
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb06"
~count:1000
~dk_len:16
~dk:"dc19847e05c64d2faf10ebfb4a3d2a20"
let pbkdf1_test2 =
test_pbkdf1_invalid_arg
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb"
~count:1000
~dk_len:16
~msg:"salt should be 8 bytes"
let pbkdf1_test3 =
test_pbkdf1_invalid_arg
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb0600"
~count:1000
~dk_len:16
~msg:"salt should be 8 bytes"
let pbkdf1_test4 =
test_pbkdf1_invalid_arg
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb06"
~count:(-1)
~dk_len:16
~msg:"count must be a positive integer"
let pbkdf1_test5 =
test_pbkdf1_invalid_arg
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb06"
~count:0
~dk_len:16
~msg:"count must be a positive integer"
let pbkdf1_test6 =
test_pbkdf1_invalid_arg
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb06"
~count:1000
~dk_len:24
~msg:"derived key too long"
let pbkdf1_test7 =
test_pbkdf1_invalid_arg
~hash:`SHA1
~password:"password"
~salt:"78578e5a5d63cb06"
~count:1000
~dk_len:0
~msg:"derived key length must be a positive integer"
let pbkdf1_tests = [
"Test Case 1", `Quick, pbkdf1_test1;
"Test Case 2", `Quick, pbkdf1_test2;
"Test Case 3", `Quick, pbkdf1_test3;
"Test Case 4", `Quick, pbkdf1_test4;
"Test Case 5", `Quick, pbkdf1_test5;
"Test Case 6", `Quick, pbkdf1_test6;
"Test Case 7", `Quick, pbkdf1_test7;
]
(* PBKDF2 *)
let test_pbkdf2 ~prf ~password ~salt ~count ~dk_len ~dk =
let salt = Ohex.decode salt
and dk = Ohex.decode dk in
(fun () ->
let edk = Pbkdf.pbkdf2 ~prf ~password ~salt ~count ~dk_len in
Alcotest.check Alcotest.string "PBKDF2 test" edk dk)
let test_pbkdf2_invalid_arg ~prf ~password ~salt ~count ~dk_len ~msg () =
let salt = Ohex.decode salt in
Alcotest.check_raises
msg
(Invalid_argument msg)
(fun () -> ignore (Pbkdf.pbkdf2 ~prf ~password ~salt ~count ~dk_len))
(* Taken from https://github.com/randombit/botan/blob/master/src/tests/data/pbkdf/pbkdf2.vec *)
let pbkdf2_test1 =
test_pbkdf2
~prf:`SHA1
~password:""
~salt:"0001020304050607"
~count:10000
~dk_len:32l
~dk:"59b2b1143b4cb1059ec58d9722fb1c72471e0d85c6f7543ba5228526375b0127"
let pbkdf2_test2 =
test_pbkdf2
~prf:`SHA1
~password:"jyueqgxrscgglpxdykcf"
~salt:"9b56e55328a4c97a250738f8dba1b992e8a1b508"
~count:10000
~dk_len:14l
~dk:"df6d9d72872404bf73e708cf3b7d"
let pbkdf2_test3 =
test_pbkdf2
~prf:`SHA1
~password:"aqrqsznzvvzgtksammgo"
~salt:"57487813cdd2220dfc485d932a2979ee8769ea8b"
~count:101
~dk_len:40l
~dk:"fa13f40af1ade2a30f2fffd66fc8a659ef95e6388c1682fc0fe4d15a70109517a32942e39c371440"
let pbkdf2_test4 =
test_pbkdf2
~prf:`SHA1
~password:"ltexmfeyylmlbrsyikaw"
~salt:"ed1f39a0a7f3889aaf7e60743b3bc1cc2c738e60"
~count:1000
~dk_len:10l
~dk:"027afadd48f4be8dcc4f"
let pbkdf2_test5 =
test_pbkdf2
~prf:`SHA1
~password:"cxgnyrcgrvllylolsjpo"
~salt:"94ac88200743fb0f6ac51be62166cbef08d94c15"
~count:1
~dk_len:32l
~dk:"7c0d009fc91b48cb6d19bafbfccff3e2ccabfe725eaa234e56bde1d551c132f2"
let pbkdf2_test6 =
test_pbkdf2
~prf:`SHA1
~password:"xqyfhrxehiedlhewnvbj"
~salt:"24a1a50b17d63ee8394b69fc70887f4f94883d68"
~count:5
~dk_len:32l
~dk:"4661301d3517ca4443a6a607b32b2a63f69996299df75db75f1e0b98dd0eb7d8"
let pbkdf2_test7 =
test_pbkdf2
~prf:`SHA1
~password:"andaqkpjwabvcfnpnjkl"
~salt:"9316c80801623cc2734af74bec42cf4dbaa3f6d5"
~count:100
~dk_len:30l
~dk:"82fb44a521448d5aac94b5158ead1e4dcd7363081a747b9f7626752bda2d"
let pbkdf2_test8 =
test_pbkdf2
~prf:`SHA1
~password:"hsavvyvocloyuztlsniu"
~salt:"612cc61df3cf2bdb36e10c4d8c9d73192bddee05"
~count:100
~dk_len:30l
~dk:"f8ec2b0ac817896ac8189d787c6424ed24a6d881436687a4629802c0ecce"
let pbkdf2_test9 =
test_pbkdf2
~prf:`SHA1
~password:"eaimrbzpcopbusaqtkmw"
~salt:"45248f9d0cebcb86a18243e76c972a1f3b36772a"
~count:100
~dk_len:34l
~dk:"c9a0b2622f13916036e29e7462e206e8ba5b50ce9212752eb8ea2a4aa7b40a4cc1bf"
let pbkdf2_test10 =
test_pbkdf2
~prf:`SHA1
~password:"gwrxpqxumsdsmbmhfhmfdcvlcvngzkig"
~salt:"a39b76c6eec8374a11493ad08c246a3e40dfae5064f4ee3489c273646178"
~count:1000
~dk_len:64l
~dk:"4c9db7ba24955225d5b845f65ef24ef1b0c6e86f2e39c8ddaa4b8abd26082d1f350381fadeaeb560dc447afc68a6b47e6ea1e7412f6cf7b2d82342fccd11d3b4"
let pbkdf2_test11 =
test_pbkdf2
~prf:`SHA256
~password:"xyz"
~salt:"0001020304050607"
~count: 10000
~dk_len:48l
~dk:"defd2987fa26a4672f4d16d98398432ad95e896bf619f6a6b8d4ed1faf98e8b531b39ffb66966d0e115a6cd8e70b72d0"
let pbkdf2_test12 =
test_pbkdf2
~prf:`SHA384
~password:"xyz"
~salt:"0001020304050607"
~count:10000
~dk_len:48l
~dk:"47a3ae920b24edaa2bb53155808554b13fab58df62b81f043d9812e9f2881164df20bbffa54e5ee2489fa183b6718a74"
let pbkdf2_test13 =
test_pbkdf2
~prf:`SHA512
~password:"xyz"
~salt:"0001020304050607"
~count:10000
~dk_len:48l
~dk:"daf8a734327745eb63d19054dbd4018a682cef11086a1bfb63fdbc16158c2f8b0742802f36aef1b1df92accbea5d31a5"
let pbkdf2_test14 =
test_pbkdf2_invalid_arg
~prf:`SHA1
~password:"password"
~salt:"0001020304050607"
~count:(-1)
~dk_len:48l
~msg:"count must be a positive integer"
let pbkdf2_test15 =
test_pbkdf2_invalid_arg
~prf:`SHA1
~password:"password"
~salt:"0001020304050607"
~count:0
~dk_len:48l
~msg:"count must be a positive integer"
let pbkdf2_test16 =
test_pbkdf2_invalid_arg
~prf:`SHA1
~password:"password"
~salt:"0001020304050607"
~count:1000
~dk_len:(-1l)
~msg:"derived key length must be a positive integer"
let pbkdf2_test17 =
test_pbkdf2_invalid_arg
~prf:`SHA1
~password:"password"
~salt:"0001020304050607"
~count:1000
~dk_len:0l
~msg:"derived key length must be a positive integer"
let pbkdf2_tests = [
"Test Case 1", `Quick, pbkdf2_test1;
"Test Case 2", `Quick, pbkdf2_test2;
"Test Case 3", `Quick, pbkdf2_test3;
"Test Case 4", `Quick, pbkdf2_test4;
"Test Case 5", `Quick, pbkdf2_test5;
"Test Case 6", `Quick, pbkdf2_test6;
"Test Case 7", `Quick, pbkdf2_test7;
"Test Case 8", `Quick, pbkdf2_test8;
"Test Case 9", `Quick, pbkdf2_test9;
"Test Case 10", `Quick, pbkdf2_test10;
"Test Case 11", `Quick, pbkdf2_test11;
"Test Case 12", `Quick, pbkdf2_test12;
"Test Case 13", `Quick, pbkdf2_test13;
"Test Case 14", `Quick, pbkdf2_test14;
"Test Case 15", `Quick, pbkdf2_test15;
"Test Case 16", `Quick, pbkdf2_test16;
"Test Case 17", `Quick, pbkdf2_test17;
]
let () =
Alcotest.run "PBKDF Tests" [
"PBKDF1 tests", pbkdf1_tests;
"PBKDF2 tests", pbkdf2_tests;
]

View file

@ -0,0 +1,7 @@
(library
(name scrypt)
(public_name kdf.scrypt)
(modules scrypt)
(libraries mirage-crypto kdf.pbkdf)
(c_names salsa-core)
(c_flags (:standard --std=c99 -Wall -Wextra -O3)))

View file

@ -0,0 +1,65 @@
#include <stdint.h>
#define CAML_NAME_SPACE
#include <caml/mlvalues.h>
#include <caml/bigarray.h>
static inline uint32_t r(uint32_t a, int b) {
int rs = 32 - b;
return (a << b) | (a >> rs);
}
static inline uint32_t combine(uint32_t y0, uint32_t y1, uint32_t y2, int shift) {
return r(y1 + y2, shift) ^ y0;
}
static inline void quarterround(uint32_t *x, int y0, int y1, int y2, int y3) {
x[y1] = combine(x[y1], x[y0], x[y3], 7);
x[y2] = combine(x[y2], x[y1], x[y0], 9);
x[y3] = combine(x[y3], x[y2], x[y1], 13);
x[y0] = combine(x[y0], x[y3], x[y2], 18);
}
static inline uint32_t get_u32_le(const uint8_t *input, int offset) {
return input[offset]
| (input[offset + 1] << 8)
| (input[offset + 2] << 16)
| (input[offset + 3] << 24);
}
static inline void set_u32_le(uint8_t *input, int offset, uint32_t value) {
input[offset] = (uint8_t) value;
input[offset + 1] = (uint8_t) (value >> 8);
input[offset + 2] = (uint8_t) (value >> 16);
input[offset + 3] = (uint8_t) (value >> 24);
}
static void salsa_core(int count, const uint8_t *src, uint8_t *dst) {
uint32_t x[16];
for (int i = 0; i < 16; i++) {
x[i] = get_u32_le(src, i * 4);
}
for (int i = 0; i < count; i++) {
quarterround(x, 0, 4, 8, 12);
quarterround(x, 5, 9, 13, 1);
quarterround(x, 10, 14, 2, 6);
quarterround(x, 15, 3, 7, 11);
quarterround(x, 0, 1, 2, 3);
quarterround(x, 5, 6, 7, 4);
quarterround(x, 10, 11, 8, 9);
quarterround(x, 15, 12, 13, 14);
}
for (int i = 0; i < 16; i++) {
uint32_t xi = x[i];
uint32_t hj = get_u32_le(src, i * 4);
set_u32_le(dst, i * 4, xi + hj);
}
}
CAMLprim value
caml_salsa_core(value count, value src, value dst)
{
salsa_core(Int_val(count), (const uint8_t*)(String_val(src)), Bytes_val(dst));
return Val_unit;
}

View file

@ -0,0 +1,66 @@
external salsa_core : int -> string -> bytes -> unit = "caml_salsa_core" [@@noalloc]
let salsa20_core count i =
let l = 64 in
if String.length i <> l then invalid_arg "input must be 16 blocks of 32 bits"
else
let o = Bytes.create l in
salsa_core count i o;
Bytes.unsafe_to_string o
let salsa20_8_core i =
salsa20_core 4 i
let scrypt_block_mix b r =
let b' = Bytes.create (String.length b) in
let x = Bytes.create 64 in
Bytes.unsafe_blit_string b ((2 * r - 1) * 64) x 0 64;
for i = 0 to 2 * r - 1 do
let b_i = Bytes.unsafe_of_string (String.sub b (i * 64) 64) in
Mirage_crypto.Uncommon.unsafe_xor_into (Bytes.unsafe_to_string x) ~src_off:0 b_i ~dst_off:0 64;
Bytes.unsafe_blit_string (salsa20_8_core (Bytes.unsafe_to_string b_i)) 0 x 0 64;
let offset = (i mod 2) lsl (max 0 (r / 2 - 1)) + i / 2 in
Bytes.blit x 0 b' (offset * 64) 64
done;
b'
let scrypt_ro_mix b ~r ~n =
let blen = r * 128 in
let x = ref (Bytes.copy b) in
let v = Bytes.create (blen * n) in
for i = 0 to n - 1 do
Bytes.unsafe_blit !x 0 v (blen * i) blen;
x := scrypt_block_mix (Bytes.unsafe_to_string !x) r
done;
for _ = 0 to n - 1 do
let integerify x =
let k = Bytes.get_int32_le x (128 * r - 64) in
let n' = n - 1 in
Int32.(to_int (logand k (of_int n')))
in
let j = integerify !x in
Mirage_crypto.Uncommon.unsafe_xor_into (Bytes.unsafe_to_string v) ~src_off:(blen * j) !x ~dst_off:0 blen;
x := scrypt_block_mix (Bytes.unsafe_to_string !x) r;
done;
!x
let scrypt ~password ~salt ~n ~r ~p ~dk_len =
let is_power_of_2 x = (x land (x - 1)) = 0 in
if n <= 1 then invalid_arg "n must be larger than 1"
else if not (is_power_of_2 n) then invalid_arg "n must be a power of 2"
else if p <= 0 then invalid_arg "p must be a positive integer"
else if p > (Int64.to_int (Int64.div 0xffffffffL 4L) / r) then invalid_arg "p too big"
else if dk_len <= 0l then invalid_arg "derived key length must be a positive integer";
let rec partition b blocks = function
| 0 -> blocks
| i ->
let off = (i - 1) * r * 128 in
let block = Bytes.unsafe_of_string (String.sub b off (r * 128)) in
partition b (block :: blocks) (i - 1)
in
let blen = Int32.of_int (128 * r * p) in
let dk = Pbkdf.pbkdf2 ~prf:`SHA256 ~password ~salt ~count:1 ~dk_len:blen in
let b = partition dk [] p in
let b' = List.map (scrypt_ro_mix ~r ~n) b in
let salt = String.concat "" (List.map Bytes.unsafe_to_string b') in
Pbkdf.pbkdf2 ~prf:`SHA256 ~password ~salt ~count:1 ~dk_len

View file

@ -0,0 +1,15 @@
(** {{:https://tools.ietf.org/html/rfc7914}
The scrypt Password-Based Key Derivation Function}
specifies the password-based key derivation function scrypt. The
function derives one or more secret keys from a secret string.
It is based on memory-hard functions which offer added protection
against attacks using custom hardware. *)
(** [scrypt_kdf password salt n r p dk_len] is [dk], the derived key
of [dk_len] octets.
[n], the cost parameter, must be larger than 1 and a power of 2.
[p], the parallelization parameter, must be a possitive integer
and less than or equal to 2^32 - 1 / (4 * r)
@raise Invalid_argument when either [n], [p] or [dk_len] are not
valid *)
val scrypt : password:string -> salt:string -> n:int -> r:int -> p:int -> dk_len:int32 -> string

View file

@ -0,0 +1,4 @@
(test
(name scrypt_kdf_tests)
(modules scrypt_kdf_tests)
(libraries kdf.scrypt ohex alcotest))

View file

@ -0,0 +1,64 @@
let test_scrypt_kdf ~password ~salt ~n ~r ~p ~dk_len ~dk =
let dk = Ohex.decode dk in
(fun () ->
let edk = Scrypt.scrypt ~password ~salt ~n ~r ~p ~dk_len in
Alcotest.check Alcotest.string "Scrypt test" edk dk)
let scrypt_kdf_test1 =
test_scrypt_kdf
~password:""
~salt:""
~n:16
~r:1
~p:1
~dk_len:64l
~dk:"77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906"
let scrypt_kdf_test2 =
test_scrypt_kdf
~password:"password"
~salt:"NaCl"
~n:1024
~r:8
~p:16
~dk_len:64l
~dk:"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640"
let scrypt_kdf_test3 =
test_scrypt_kdf
~password:"pleaseletmein"
~salt:"SodiumChloride"
~n:16384
~r:8
~p:1
~dk_len:64l
~dk:"7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887"
let scrypt_kdf_test4 =
test_scrypt_kdf
~password:"pleaseletmein"
~salt:"SodiumChloride"
~n:1048576
~r:8
~p:1
~dk_len:64l
~dk:"2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4"
let scrypt_kdf_tests () =
let tests = [
"Test Case 1", `Quick, scrypt_kdf_test1;
"Test Case 2", `Quick, scrypt_kdf_test2;
] in
(* Skip test case 3 and 4 for architectures with 31 bit sizes or less, as it requires a buffer larger than Int.max_size in those cases *)
if Sys.int_size <= 31 then
tests
else
tests @ [
"Test Case 3", `Quick, scrypt_kdf_test3;
"Test Case 4", `Slow, scrypt_kdf_test4;
]
let () =
Alcotest.run "Scrypt kdf Tests" [
"Scrypt kdf tests", scrypt_kdf_tests ();
]