├── .github ├── dependabot.yml └── workflows │ ├── build-test.yml │ ├── deploy-doc.yml │ ├── lint.yml │ └── opam-dependency-submission.yml ├── .gitignore ├── .ocamlformat ├── CHANGES.md ├── LICENSE ├── Makefile ├── README.md ├── argon2.opam ├── dune-project ├── examples ├── dune └── examples.ml ├── src ├── argon2.ml ├── argon2.mli ├── config │ ├── discover.ml │ └── dune └── dune └── tests ├── argon2d.expected ├── argon2d.ml ├── argon2i.expected ├── argon2i.ml ├── argon2id.expected ├── argon2id.ml ├── dune ├── generic.expected └── generic.ml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build-and-test: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: 13 | - macos-latest 14 | - ubuntu-latest 15 | # - windows-latest 16 | ocaml-version: 17 | - 4.14 18 | - 5.1 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | 26 | - name: Use OCaml ${{ matrix.ocaml-version }} 27 | uses: ocaml/setup-ocaml@v2 28 | with: 29 | ocaml-compiler: ${{ matrix.ocaml-version }} 30 | dune-cache: true 31 | opam-depext-flags: "--with-test" 32 | allow-prerelease-opam: true 33 | 34 | - run: opam install . --deps-only --with-test 35 | 36 | - name: build project 37 | run: opam exec -- dune build 38 | 39 | - name: run test 40 | run: opam exec -- dune runtest 41 | -------------------------------------------------------------------------------- /.github/workflows/deploy-doc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy odoc to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: read-all 9 | 10 | concurrency: 11 | group: deploy-odoc 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | deploy-odoc: 16 | name: Deploy odoc to GitHub Pages 17 | 18 | environment: 19 | name: github-pages 20 | url: ${{ steps.deployment.outputs.page_url }} 21 | 22 | permissions: 23 | contents: read 24 | id-token: write 25 | pages: write 26 | 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout tree 31 | uses: actions/checkout@v4 32 | 33 | - name: Set-up OCaml 34 | uses: ocaml/setup-ocaml@v2 35 | with: 36 | ocaml-compiler: "5.1" 37 | dune-cache: true 38 | opam-depext-flags: "--with-doc" 39 | allow-prerelease-opam: true 40 | 41 | - name: Install dependencies 42 | run: opam install . --deps-only --with-doc 43 | 44 | - name: Build documentation 45 | run: opam exec -- dune build @doc 46 | 47 | - name: Set-up Pages 48 | uses: actions/configure-pages@v5 49 | 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: _build/default/_doc/_html 54 | 55 | - name: Deploy odoc to GitHub Pages 56 | id: deployment 57 | uses: actions/deploy-pages@v4 58 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | lint-fmt: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v4 13 | 14 | - name: Use OCaml 5.1 15 | uses: ocaml/setup-ocaml@v2 16 | with: 17 | ocaml-compiler: 5.1 18 | dune-cache: true 19 | opam-depext-flags: "--with-doc" 20 | allow-prerelease-opam: true 21 | 22 | - name: Lint fmt 23 | uses: ocaml/setup-ocaml/lint-fmt@v2 24 | 25 | lint-opam: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v4 30 | 31 | - name: Use OCaml 5.1 32 | uses: ocaml/setup-ocaml@v2 33 | with: 34 | ocaml-compiler: 5.1 35 | dune-cache: true 36 | opam-depext-flags: "--with-doc" 37 | allow-prerelease-opam: true 38 | 39 | - name: Lint opam 40 | uses: ocaml/setup-ocaml/lint-opam@v2 41 | 42 | lint-doc: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: Checkout code 46 | uses: actions/checkout@v4 47 | 48 | - name: Use OCaml 5.1 49 | uses: ocaml/setup-ocaml@v2 50 | with: 51 | ocaml-compiler: 5.1 52 | dune-cache: true 53 | opam-depext-flags: "--with-doc" 54 | allow-prerelease-opam: true 55 | 56 | - name: Lint doc 57 | uses: ocaml/setup-ocaml/lint-doc@v2 58 | -------------------------------------------------------------------------------- /.github/workflows/opam-dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Opam Dependency Submission 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | opam-dependency-submission: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout tree 12 | uses: actions/checkout@v4 13 | 14 | - name: Set-up OCaml 5.1 15 | uses: ocaml/setup-ocaml@v2 16 | with: 17 | ocaml-compiler: 5.1 18 | dune-cache: true 19 | allow-prerelease-opam: true 20 | 21 | - name: Opam Dependency Submission 22 | uses: ocaml/setup-ocaml/analysis@v2 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *~ 3 | .merlin 4 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | profile=conventional 2 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 (2020-04-15) 2 | 3 | - fix wrong length of hash encoded (#5) 4 | 5 | ## 1.0.0 (2020-04-12) 6 | 7 | - compatibility with libargon2 20171227 (#3) 8 | - change build system to dune (#3) 9 | - ocamlformat sources 10 | - support argon2id 11 | - add tests 12 | 13 | ## 0.2 (2016-04-12) 14 | 15 | - compatibility with libargon2 20160406 16 | 17 | ## 0.1 (2016-01-15) 18 | 19 | - first basic version 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Louis Roché 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile used for development 2 | 3 | DUNE=dune 4 | 5 | lib: 6 | $(DUNE) build @install 7 | 8 | .PHONY: examples 9 | examples: 10 | $(DUNE) exec -- examples/examples.exe 11 | 12 | doc: 13 | $(DUNE) build @doc 14 | 15 | upload_doc: doc 16 | git checkout gh-pages && rm -rf dev/* && cp -r _build/default/_doc/_html/argon2/* dev && \ 17 | git add --all dev 18 | 19 | fmt: 20 | $(DUNE) build @fmt --auto-promote 21 | 22 | clean: 23 | $(DUNE) clean 24 | 25 | dev: 26 | $(DUNE) build @all @fmt --auto-promote --watch 27 | 28 | test: 29 | $(DUNE) runtest 30 | 31 | test-promote: 32 | $(DUNE) runtest --auto-promote 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ocaml-argon2 2 | 3 | Bindings to [Argon2](https://github.com/P-H-C/phc-winner-argon2). 4 | 5 | Online documentation can be found 6 | [here](https://khady.github.io/ocaml-argon2/). 7 | 8 | ## Installation 9 | 10 | ``` 11 | opam install argon2 12 | ``` 13 | 14 | ## Examples 15 | 16 | See the `examples/` directory. 17 | -------------------------------------------------------------------------------- /argon2.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Louis Roché " 3 | authors: "Louis Roché " 4 | homepage: "https://github.com/Khady/ocaml-argon2" 5 | dev-repo: "git+https://github.com/Khady/ocaml-argon2.git" 6 | bug-reports: "https://github.com/Khady/ocaml-argon2/issues" 7 | doc: "https://khady.github.io/ocaml-argon2/" 8 | license: "MIT" 9 | depends: [ 10 | "ocaml" {>= "4.02.3"} 11 | "dune" {>= "2.0"} 12 | "dune-configurator" {>= "2.0"} 13 | "ctypes" {>= "0.4.1"} 14 | "ctypes-foreign" 15 | "result" 16 | "odoc" {with-doc} 17 | ] 18 | build: [ 19 | ["dune" "subst"] {dev} 20 | ["dune" "build" "-p" name "-j" jobs "@install" "@doc" {with-doc}] 21 | ] 22 | depexts: [ 23 | ["libargon2-dev"] {os-family = "debian"} 24 | ["libargon2-dev"] {os-family = "ubuntu"} 25 | ["libargon2-devel"] {os-distribution = "fedora"} 26 | ] 27 | synopsis: "OCaml bindings to Argon2" 28 | description: """ 29 | Based on argon2 library as described in https://github.com/P-H-C/phc-winner-argon2. 30 | 31 | libargon2 must be installed on your system for this library to work. 32 | """ 33 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name argon2) 3 | -------------------------------------------------------------------------------- /examples/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name examples) 3 | (libraries argon2)) 4 | 5 | (install 6 | (section doc) 7 | (files examples.ml)) 8 | -------------------------------------------------------------------------------- /examples/examples.ml: -------------------------------------------------------------------------------- 1 | open Argon2 2 | 3 | let () = 4 | let message fmt = Printf.ksprintf (Printf.printf "%s\n%!") fmt in 5 | let hash_len = 32 in 6 | let t_cost = 2 in 7 | let m_cost = 65536 in 8 | let parallelism = 1 in 9 | let salt = "0000000000000000" in 10 | let salt_len = String.length salt in 11 | let pwd = "password" in 12 | let encoded_len = 13 | encoded_len ~t_cost ~m_cost ~parallelism ~salt_len ~hash_len ~kind:D 14 | in 15 | ( match I.hash_raw ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len with 16 | | Result.Ok hash -> 17 | message "argon2i hash:"; 18 | (* ec6891c09fc1461720e508485da42b2087ca9a708185d9dc890539a33cd2af6f *) 19 | I.hash_to_string hash 20 | |> String.iter (fun c -> Printf.printf "%02x" (Char.code c)); 21 | message "" 22 | | Result.Error e -> 23 | message "Error while computing hash with argon2i_hash_raw: %s" 24 | (ErrorCodes.message e) ); 25 | ( match D.hash_raw ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len with 26 | | Result.Ok hash -> 27 | message "argon2d hash:"; 28 | (* 30f5e9f2584e962bba213cc256dcf9ccdce18d8f67a6cbf8e012e0619b6d52d2 *) 29 | D.hash_to_string hash 30 | |> String.iter (fun c -> Printf.printf "%02x" (Char.code c)); 31 | message "" 32 | | Result.Error e -> 33 | message "Error while computing hash with argon2d_hash_raw: %s" 34 | (ErrorCodes.message e) ); 35 | match 36 | hash ~t_cost ~m_cost ~parallelism ~pwd ~salt ~kind:D ~hash_len ~encoded_len 37 | ~version:VERSION_NUMBER 38 | with 39 | | Result.Ok (hash, encoded) -> ( 40 | message "hash argon2d:"; 41 | String.iter (fun c -> Printf.printf "%02x" (Char.code c)) hash; 42 | message "\nencoded argon2d:%s" encoded; 43 | match verify ~encoded ~pwd ~kind:D with 44 | | Result.Ok _ -> message "verify OK" 45 | | Result.Error e -> 46 | message "Error while computing verify: %s" (ErrorCodes.message e) ) 47 | | Result.Error e -> 48 | message "Error while computing hash: %s" (ErrorCodes.message e) 49 | -------------------------------------------------------------------------------- /src/argon2.ml: -------------------------------------------------------------------------------- 1 | open Ctypes 2 | open Foreign 3 | 4 | module Kind = struct 5 | type t = D | I | ID 6 | 7 | let read = function 8 | | 0 -> D 9 | | 1 -> I 10 | | 2 -> ID 11 | | _ as e -> invalid_arg (Printf.sprintf "%d is not a valid argon2_type" e) 12 | 13 | let write = function D -> 0 | I -> 1 | ID -> 2 14 | 15 | let t = view int ~read ~write 16 | 17 | let argon2_type2string = 18 | foreign "argon2_type2string" 19 | (t (* type *) @-> int (* uppercase *) @-> returning string) 20 | 21 | let show (case : [ `Upper | `Lower ]) t = 22 | let case = match case with `Upper -> 1 | `Lower -> 0 in 23 | argon2_type2string t case 24 | end 25 | 26 | type kind = Kind.t = D | I | ID 27 | 28 | let show_kind = Kind.show 29 | 30 | module Version = struct 31 | type t = VERSION_10 | VERSION_13 | VERSION_NUMBER 32 | 33 | let read = function 34 | | 0x10 -> VERSION_10 35 | | 0x13 -> VERSION_13 36 | | _ as e -> 37 | invalid_arg (Printf.sprintf "%d is not a valid argon2_version" e) 38 | 39 | let write = function 40 | | VERSION_10 -> 0x10 41 | | VERSION_13 -> 0x13 42 | | VERSION_NUMBER -> 0x13 43 | 44 | let t = view int ~read ~write 45 | end 46 | 47 | type version = Version.t = VERSION_10 | VERSION_13 | VERSION_NUMBER 48 | 49 | module ErrorCodes = struct 50 | type t = 51 | | OK 52 | | OUTPUT_PTR_NULL 53 | | OUTPUT_TOO_SHORT 54 | | OUTPUT_TOO_LONG 55 | | PWD_TOO_SHORT 56 | | PWD_TOO_LONG 57 | | SALT_TOO_SHORT 58 | | SALT_TOO_LONG 59 | | AD_TOO_SHORT 60 | | AD_TOO_LONG 61 | | SECRET_TOO_SHORT 62 | | SECRET_TOO_LONG 63 | | TIME_TOO_SMALL 64 | | TIME_TOO_LARGE 65 | | MEMORY_TOO_LITTLE 66 | | MEMORY_TOO_MUCH 67 | | LANES_TOO_FEW 68 | | LANES_TOO_MANY 69 | | PWD_PTR_MISMATCH 70 | | SALT_PTR_MISMATCH 71 | | SECRET_PTR_MISMATCH 72 | | AD_PTR_MISMATCH 73 | | MEMORY_ALLOCATION_ERROR 74 | | FREE_MEMORY_CBK_NULL 75 | | ALLOCATE_MEMORY_CBK_NULL 76 | | INCORRECT_PARAMETER 77 | | INCORRECT_TYPE 78 | | OUT_PTR_MISMATCH 79 | | THREADS_TOO_FEW 80 | | THREADS_TOO_MANY 81 | | MISSING_ARGS 82 | | ENCODING_FAIL 83 | | DECODING_FAIL 84 | | THREAD_FAIL 85 | | DECODING_LENGTH_FAIL 86 | | VERIFY_MISMATCH 87 | | Other of int 88 | 89 | let read = function 90 | | -0 -> OK 91 | | -1 -> OUTPUT_PTR_NULL 92 | | -2 -> OUTPUT_TOO_SHORT 93 | | -3 -> OUTPUT_TOO_LONG 94 | | -4 -> PWD_TOO_SHORT 95 | | -5 -> PWD_TOO_LONG 96 | | -6 -> SALT_TOO_SHORT 97 | | -7 -> SALT_TOO_LONG 98 | | -8 -> AD_TOO_SHORT 99 | | -9 -> AD_TOO_LONG 100 | | -10 -> SECRET_TOO_SHORT 101 | | -11 -> SECRET_TOO_LONG 102 | | -12 -> TIME_TOO_SMALL 103 | | -13 -> TIME_TOO_LARGE 104 | | -14 -> MEMORY_TOO_LITTLE 105 | | -15 -> MEMORY_TOO_MUCH 106 | | -16 -> LANES_TOO_FEW 107 | | -17 -> LANES_TOO_MANY 108 | | -18 -> PWD_PTR_MISMATCH 109 | | -19 -> SALT_PTR_MISMATCH 110 | | -20 -> SECRET_PTR_MISMATCH 111 | | -21 -> AD_PTR_MISMATCH 112 | | -22 -> MEMORY_ALLOCATION_ERROR 113 | | -23 -> FREE_MEMORY_CBK_NULL 114 | | -24 -> ALLOCATE_MEMORY_CBK_NULL 115 | | -25 -> INCORRECT_PARAMETER 116 | | -26 -> INCORRECT_TYPE 117 | | -27 -> OUT_PTR_MISMATCH 118 | | -28 -> THREADS_TOO_FEW 119 | | -29 -> THREADS_TOO_MANY 120 | | -30 -> MISSING_ARGS 121 | | -31 -> ENCODING_FAIL 122 | | -32 -> DECODING_FAIL 123 | | -33 -> THREAD_FAIL 124 | | -34 -> DECODING_LENGTH_FAIL 125 | | -35 -> VERIFY_MISMATCH 126 | | _ as other -> Other other 127 | 128 | let write = function 129 | | OK -> 0 130 | | OUTPUT_PTR_NULL -> -1 131 | | OUTPUT_TOO_SHORT -> -2 132 | | OUTPUT_TOO_LONG -> -3 133 | | PWD_TOO_SHORT -> -4 134 | | PWD_TOO_LONG -> -5 135 | | SALT_TOO_SHORT -> -6 136 | | SALT_TOO_LONG -> -7 137 | | AD_TOO_SHORT -> -8 138 | | AD_TOO_LONG -> -9 139 | | SECRET_TOO_SHORT -> -10 140 | | SECRET_TOO_LONG -> -11 141 | | TIME_TOO_SMALL -> -12 142 | | TIME_TOO_LARGE -> -13 143 | | MEMORY_TOO_LITTLE -> -14 144 | | MEMORY_TOO_MUCH -> -15 145 | | LANES_TOO_FEW -> -16 146 | | LANES_TOO_MANY -> -17 147 | | PWD_PTR_MISMATCH -> -18 148 | | SALT_PTR_MISMATCH -> -19 149 | | SECRET_PTR_MISMATCH -> -20 150 | | AD_PTR_MISMATCH -> -21 151 | | MEMORY_ALLOCATION_ERROR -> -22 152 | | FREE_MEMORY_CBK_NULL -> -23 153 | | ALLOCATE_MEMORY_CBK_NULL -> -24 154 | | INCORRECT_PARAMETER -> -25 155 | | INCORRECT_TYPE -> -26 156 | | OUT_PTR_MISMATCH -> -27 157 | | THREADS_TOO_FEW -> -28 158 | | THREADS_TOO_MANY -> -19 159 | | MISSING_ARGS -> -30 160 | | ENCODING_FAIL -> -31 161 | | DECODING_FAIL -> -32 162 | | THREAD_FAIL -> -33 163 | | DECODING_LENGTH_FAIL -> -34 164 | | VERIFY_MISMATCH -> -35 165 | | Other o -> o 166 | 167 | let t = view int ~read ~write 168 | 169 | let argon2_error_message = 170 | foreign "argon2_error_message" (t @-> returning string) 171 | 172 | let message error_code = argon2_error_message error_code 173 | end 174 | 175 | let hash_encoded fun_name = 176 | foreign fun_name 177 | (uint32_t (* t_cost *) @-> uint32_t (* m_cost *) 178 | @-> uint32_t (* parallelism *) @-> string 179 | (* pwd *) @-> size_t (* pwdlen *) 180 | @-> string (* salt *) @-> size_t 181 | (* saltlen *) @-> size_t (* hashlen *) 182 | @-> ptr char (* encoded *) @-> size_t (* encodedlen *) 183 | @-> returning ErrorCodes.t) 184 | 185 | let hash_raw fun_name = 186 | foreign fun_name 187 | (uint32_t (* t_cost *) @-> uint32_t (* m_cost *) 188 | @-> uint32_t (* parallelism *) @-> string 189 | (* pwd *) @-> size_t (* pwdlen *) 190 | @-> string (* salt *) @-> size_t 191 | (* saltlen *) @-> ptr void (* hash *) 192 | @-> size_t (* hashlen *) @-> returning ErrorCodes.t) 193 | 194 | let argon2i_hash_encoded = hash_encoded "argon2i_hash_encoded" 195 | 196 | let argon2i_hash_raw = hash_raw "argon2i_hash_raw" 197 | 198 | let argon2d_hash_encoded = hash_encoded "argon2d_hash_encoded" 199 | 200 | let argon2d_hash_raw = hash_raw "argon2d_hash_raw" 201 | 202 | let argon2id_hash_encoded = hash_encoded "argon2id_hash_encoded" 203 | 204 | let argon2id_hash_raw = hash_raw "argon2id_hash_raw" 205 | 206 | let argon2_hash = 207 | foreign "argon2_hash" 208 | (uint32_t (* t_cost *) @-> uint32_t (* m_cost *) 209 | @-> uint32_t (* parallelism *) @-> string 210 | (* pwd *) @-> size_t (* pwdlen *) 211 | @-> string (* salt *) @-> size_t 212 | (* saltlen *) @-> ptr void (* hash *) 213 | @-> size_t (* hashlen *) @-> ptr char (* encoded *) 214 | @-> size_t (* encodedlen *) @-> Kind.t (* type *) 215 | @-> Version.t (* version *) @-> returning ErrorCodes.t) 216 | 217 | let verify fun_name = 218 | foreign fun_name 219 | (string (* encoded *) @-> string 220 | (* pwd *) @-> size_t (* pwdlen *) 221 | @-> returning ErrorCodes.t) 222 | 223 | let argon2i_verify = verify "argon2i_verify" 224 | 225 | let argon2d_verify = verify "argon2d_verify" 226 | 227 | let argon2id_verify = verify "argon2id_verify" 228 | 229 | let argon2_verify = 230 | foreign "argon2_verify" 231 | (string (* encoded *) @-> string 232 | (* pwd *) @-> size_t (* pwdlen *) 233 | @-> Kind.t (* type *) @-> returning ErrorCodes.t) 234 | 235 | let argon2_encodedlen = 236 | foreign "argon2_encodedlen" 237 | (uint32_t (* t_cost *) @-> uint32_t (* m_cost *) 238 | @-> uint32_t (* parallelism *) @-> uint32_t (* saltlen *) 239 | @-> uint32_t (* hashlen *) @-> Kind.t 240 | (* type *) @-> returning size_t) 241 | 242 | let hash_encoded hash_fun ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len 243 | ~encoded_len = 244 | let u_t_cost = Unsigned.UInt32.of_int t_cost in 245 | let u_m_cost = Unsigned.UInt32.of_int m_cost in 246 | let u_parallelism = Unsigned.UInt32.of_int parallelism in 247 | 248 | let s_pwd_len = Unsigned.Size_t.of_int @@ String.length pwd in 249 | let s_salt_len = Unsigned.Size_t.of_int @@ String.length salt in 250 | 251 | let s_hash_len = Unsigned.Size_t.of_int hash_len in 252 | 253 | let encoded = allocate_n char ~count:encoded_len in 254 | let s_encoded_len = Unsigned.Size_t.of_int encoded_len in 255 | 256 | match 257 | hash_fun u_t_cost u_m_cost u_parallelism pwd s_pwd_len salt s_salt_len 258 | s_hash_len encoded s_encoded_len 259 | with 260 | | ErrorCodes.OK -> 261 | let encoded = string_from_ptr encoded ~length:(encoded_len - 1) in 262 | Result.Ok encoded 263 | | e -> Result.Error e 264 | 265 | let hash_raw hash_fun ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len = 266 | let u_t_cost = Unsigned.UInt32.of_int t_cost in 267 | let u_m_cost = Unsigned.UInt32.of_int m_cost in 268 | let u_parallelism = Unsigned.UInt32.of_int parallelism in 269 | 270 | let s_pwd_len = Unsigned.Size_t.of_int @@ String.length pwd in 271 | let s_salt_len = Unsigned.Size_t.of_int @@ String.length salt in 272 | 273 | let hash = allocate_n char ~count:hash_len |> to_voidp in 274 | let s_hash_len = Unsigned.Size_t.of_int hash_len in 275 | 276 | match 277 | hash_fun u_t_cost u_m_cost u_parallelism pwd s_pwd_len salt s_salt_len hash 278 | s_hash_len 279 | with 280 | | ErrorCodes.OK -> 281 | let hash = string_from_ptr (from_voidp char hash) ~length:hash_len in 282 | Result.Ok hash 283 | | e -> Result.Error e 284 | 285 | let verify verify_fun ~encoded ~pwd = 286 | let s_pwd_len = Unsigned.Size_t.of_int @@ String.length pwd in 287 | match verify_fun encoded pwd s_pwd_len with 288 | | ErrorCodes.OK -> Result.Ok true 289 | | e -> Result.Error e 290 | 291 | module type HashBindings = sig 292 | val hash_raw : 293 | Unsigned.uint32 -> 294 | Unsigned.uint32 -> 295 | Unsigned.uint32 -> 296 | string -> 297 | Unsigned.size_t -> 298 | string -> 299 | Unsigned.size_t -> 300 | unit Ctypes_static.ptr -> 301 | Unsigned.size_t -> 302 | ErrorCodes.t 303 | 304 | val hash_encoded : 305 | Unsigned.uint32 -> 306 | Unsigned.uint32 -> 307 | Unsigned.uint32 -> 308 | string -> 309 | Unsigned.size_t -> 310 | string -> 311 | Unsigned.size_t -> 312 | Unsigned.size_t -> 313 | char Ctypes_static.ptr -> 314 | Unsigned.size_t -> 315 | ErrorCodes.t 316 | 317 | val verify : string -> string -> Unsigned.size_t -> ErrorCodes.t 318 | end 319 | 320 | module type HashFunctions = sig 321 | type hash 322 | 323 | type encoded 324 | 325 | val hash_raw : 326 | t_cost:int -> 327 | m_cost:int -> 328 | parallelism:int -> 329 | pwd:string -> 330 | salt:string -> 331 | hash_len:int -> 332 | (hash, ErrorCodes.t) Result.result 333 | 334 | val hash_encoded : 335 | t_cost:int -> 336 | m_cost:int -> 337 | parallelism:int -> 338 | pwd:string -> 339 | salt:string -> 340 | hash_len:int -> 341 | encoded_len:int -> 342 | (encoded, ErrorCodes.t) Result.result 343 | 344 | val verify : 345 | encoded:encoded -> pwd:string -> (bool, ErrorCodes.t) Result.result 346 | 347 | val hash_to_string : hash -> string 348 | 349 | val encoded_to_string : encoded -> string 350 | end 351 | 352 | module MakeInternal (H : HashBindings) : HashFunctions = struct 353 | type hash = string 354 | 355 | type encoded = string 356 | 357 | let hash_to_string h = h 358 | 359 | let encoded_to_string e = e 360 | 361 | let hash_raw = hash_raw H.hash_raw 362 | 363 | let hash_encoded = hash_encoded H.hash_encoded 364 | 365 | let verify = verify H.verify 366 | end 367 | 368 | module I = MakeInternal (struct 369 | let hash_raw = argon2i_hash_raw 370 | 371 | let hash_encoded = argon2i_hash_encoded 372 | 373 | let verify = argon2i_verify 374 | end) 375 | 376 | module D = MakeInternal (struct 377 | let hash_raw = argon2d_hash_raw 378 | 379 | let hash_encoded = argon2d_hash_encoded 380 | 381 | let verify = argon2d_verify 382 | end) 383 | 384 | module ID = MakeInternal (struct 385 | let hash_raw = argon2id_hash_raw 386 | 387 | let hash_encoded = argon2id_hash_encoded 388 | 389 | let verify = argon2id_verify 390 | end) 391 | 392 | type hash = string 393 | 394 | type encoded = string 395 | 396 | let hash ~t_cost ~m_cost ~parallelism ~pwd ~salt ~kind ~hash_len ~encoded_len 397 | ~version = 398 | let u_t_cost = Unsigned.UInt32.of_int t_cost in 399 | let u_m_cost = Unsigned.UInt32.of_int m_cost in 400 | let u_parallelism = Unsigned.UInt32.of_int parallelism in 401 | 402 | let s_pwd_len = Unsigned.Size_t.of_int @@ String.length pwd in 403 | let s_salt_len = Unsigned.Size_t.of_int @@ String.length salt in 404 | 405 | let hash = allocate_n char ~count:hash_len |> to_voidp in 406 | let s_hash_len = Unsigned.Size_t.of_int hash_len in 407 | 408 | let encoded = allocate_n char ~count:encoded_len in 409 | let s_encoded_len = Unsigned.Size_t.of_int encoded_len in 410 | 411 | let res = 412 | argon2_hash u_t_cost u_m_cost u_parallelism pwd s_pwd_len salt s_salt_len 413 | hash s_hash_len encoded s_encoded_len kind version 414 | in 415 | match res with 416 | | ErrorCodes.OK -> 417 | let hash = string_from_ptr (from_voidp char hash) ~length:hash_len in 418 | let encoded = string_from_ptr encoded ~length:(encoded_len - 1) in 419 | Result.Ok (hash, encoded) 420 | | _ as e -> Result.Error e 421 | 422 | let verify ~encoded ~pwd ~kind = 423 | let s_pwd_len = Unsigned.Size_t.of_int @@ String.length pwd in 424 | match argon2_verify encoded pwd s_pwd_len kind with 425 | | ErrorCodes.OK -> Result.Ok true 426 | | e -> Result.Error e 427 | 428 | let encoded_len ~t_cost ~m_cost ~parallelism ~salt_len ~hash_len ~kind = 429 | let u_t_cost = Unsigned.UInt32.of_int t_cost in 430 | let u_m_cost = Unsigned.UInt32.of_int m_cost in 431 | let u_parallelism = Unsigned.UInt32.of_int parallelism in 432 | let u_salt_len = Unsigned.UInt32.of_int salt_len in 433 | let u_hash_len = Unsigned.UInt32.of_int hash_len in 434 | let len = 435 | argon2_encodedlen u_t_cost u_m_cost u_parallelism u_salt_len u_hash_len kind 436 | in 437 | Unsigned.Size_t.to_int len 438 | -------------------------------------------------------------------------------- /src/argon2.mli: -------------------------------------------------------------------------------- 1 | (** Ocaml bindings to Argon2. *) 2 | 3 | module ErrorCodes : sig 4 | type t = 5 | | OK 6 | | OUTPUT_PTR_NULL 7 | | OUTPUT_TOO_SHORT 8 | | OUTPUT_TOO_LONG 9 | | PWD_TOO_SHORT 10 | | PWD_TOO_LONG 11 | | SALT_TOO_SHORT 12 | | SALT_TOO_LONG 13 | | AD_TOO_SHORT 14 | | AD_TOO_LONG 15 | | SECRET_TOO_SHORT 16 | | SECRET_TOO_LONG 17 | | TIME_TOO_SMALL 18 | | TIME_TOO_LARGE 19 | | MEMORY_TOO_LITTLE 20 | | MEMORY_TOO_MUCH 21 | | LANES_TOO_FEW 22 | | LANES_TOO_MANY 23 | | PWD_PTR_MISMATCH 24 | | SALT_PTR_MISMATCH 25 | | SECRET_PTR_MISMATCH 26 | | AD_PTR_MISMATCH 27 | | MEMORY_ALLOCATION_ERROR 28 | | FREE_MEMORY_CBK_NULL 29 | | ALLOCATE_MEMORY_CBK_NULL 30 | | INCORRECT_PARAMETER 31 | | INCORRECT_TYPE 32 | | OUT_PTR_MISMATCH 33 | | THREADS_TOO_FEW 34 | | THREADS_TOO_MANY 35 | | MISSING_ARGS 36 | | ENCODING_FAIL 37 | | DECODING_FAIL 38 | | THREAD_FAIL 39 | | DECODING_LENGTH_FAIL 40 | | VERIFY_MISMATCH 41 | | Other of int 42 | 43 | val message : t -> string 44 | (** Get the associated error message for given error code. *) 45 | end 46 | 47 | module type HashFunctions = sig 48 | type hash 49 | 50 | type encoded 51 | 52 | val hash_raw : 53 | t_cost:int -> 54 | m_cost:int -> 55 | parallelism:int -> 56 | pwd:string -> 57 | salt:string -> 58 | hash_len:int -> 59 | (hash, ErrorCodes.t) Result.result 60 | (** Hashes a password, producing a raw hash. *) 61 | 62 | val hash_encoded : 63 | t_cost:int -> 64 | m_cost:int -> 65 | parallelism:int -> 66 | pwd:string -> 67 | salt:string -> 68 | hash_len:int -> 69 | encoded_len:int -> 70 | (encoded, ErrorCodes.t) Result.result 71 | (** Hashes a password, producing an encoded hash. *) 72 | 73 | val verify : 74 | encoded:encoded -> pwd:string -> (bool, ErrorCodes.t) Result.result 75 | (** Verifies a password against an encoded string. *) 76 | 77 | val hash_to_string : hash -> string 78 | (** Converts a raw hash value to a string. *) 79 | 80 | val encoded_to_string : encoded -> string 81 | (** Converts an encoded hash to a string. *) 82 | end 83 | 84 | (** Bindings to Argon2i. *) 85 | module I : HashFunctions 86 | 87 | (** Bindings to Argon2d. *) 88 | module D : HashFunctions 89 | 90 | (** Bindings to Argon2id. *) 91 | module ID : HashFunctions 92 | 93 | type hash = string 94 | 95 | type encoded = string 96 | 97 | type kind = D | I | ID 98 | 99 | val show_kind : [ `Upper | `Lower ] -> kind -> string 100 | 101 | type version = 102 | | VERSION_10 103 | | VERSION_13 104 | | VERSION_NUMBER (** Currently an alias for [VERSION_13] *) 105 | 106 | val hash : 107 | t_cost:int -> 108 | m_cost:int -> 109 | parallelism:int -> 110 | pwd:string -> 111 | salt:string -> 112 | kind:kind -> 113 | hash_len:int -> 114 | encoded_len:int -> 115 | version:version -> 116 | (hash * encoded, ErrorCodes.t) Result.result 117 | (** Generic function underlying the above ones. *) 118 | 119 | val verify : 120 | encoded:encoded -> 121 | pwd:string -> 122 | kind:kind -> 123 | (bool, ErrorCodes.t) Result.result 124 | (** Verifies a password against an encoded string. *) 125 | 126 | val encoded_len : 127 | t_cost:int -> 128 | m_cost:int -> 129 | parallelism:int -> 130 | salt_len:int -> 131 | hash_len:int -> 132 | kind:kind -> 133 | int 134 | (** Returns the encoded hash length for the given input parameters. *) 135 | -------------------------------------------------------------------------------- /src/config/discover.ml: -------------------------------------------------------------------------------- 1 | module C = Configurator.V1 2 | 3 | (* Backported from OCaml 4.10.0 *) 4 | let concat_map f l = 5 | let rec aux f acc = function 6 | | [] -> List.rev acc 7 | | x :: l -> 8 | let xs = f x in 9 | aux f (List.rev_append xs acc) l 10 | in 11 | aux f [] l 12 | 13 | let () = 14 | C.main ~name:"argon2" (fun c -> 15 | let default : C.Pkg_config.package_conf = 16 | { libs = [ "-largon2" ]; cflags = [] } 17 | in 18 | let conf = 19 | match C.Pkg_config.get c with 20 | | None -> default 21 | | Some pc -> ( 22 | match C.Pkg_config.query pc ~package:"libargon2" with 23 | | None -> default 24 | | Some deps -> deps) 25 | in 26 | 27 | ([ "-ccopt"; "-Wl,--no-as-needed" ]) 28 | @ concat_map (fun flag -> [ "-cclib"; flag ]) conf.libs 29 | |> C.Flags.write_sexp "flags.sexp") 30 | -------------------------------------------------------------------------------- /src/config/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name discover) 3 | (libraries dune-configurator)) 4 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name argon2) 3 | (public_name argon2) 4 | (libraries result ctypes.foreign ctypes) 5 | (flags 6 | :standard 7 | (:include flags.sexp)) 8 | (synopsis "OCaml bindings to argon2")) 9 | 10 | (rule 11 | (targets flags.sexp) 12 | (action 13 | (run ./config/discover.exe))) 14 | -------------------------------------------------------------------------------- /tests/argon2d.expected: -------------------------------------------------------------------------------- 1 | argon2d hash:30f5e9f2584e962bba213cc256dcf9ccdce18d8f67a6cbf8e012e0619b6d52d2 2 | argon2d encoded:$argon2d$v=19$m=65536,t=2,p=1$MDAwMDAwMDAwMDAwMDAwMA$MPXp8lhOliu6ITzCVtz5zNzhjY9npsv44BLgYZttUtI 3 | -------------------------------------------------------------------------------- /tests/argon2d.ml: -------------------------------------------------------------------------------- 1 | open Argon2 2 | open Printf 3 | 4 | let hash_len = 32 5 | 6 | let t_cost = 2 7 | 8 | let m_cost = 65536 9 | 10 | let parallelism = 1 11 | 12 | let salt = "0000000000000000" 13 | 14 | let salt_len = String.length salt 15 | 16 | let pwd = "password" 17 | 18 | let hash_raw () = 19 | match D.hash_raw ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len with 20 | | Result.Ok hash -> 21 | printf "argon2d hash:"; 22 | D.hash_to_string hash 23 | |> String.iter (fun c -> Printf.printf "%02x" (Char.code c)); 24 | printf "\n" 25 | | Result.Error e -> 26 | printf "Error while computing hash with argon2d_hash_raw: %s\n" 27 | (ErrorCodes.message e) 28 | 29 | let hash_encoded () = 30 | let encoded_len = 31 | encoded_len ~t_cost ~m_cost ~parallelism ~salt_len ~hash_len ~kind:D 32 | in 33 | match D.hash_encoded ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len ~encoded_len with 34 | | Result.Ok hash -> 35 | printf "argon2d encoded:%s\n" (D.encoded_to_string hash) 36 | | Result.Error e -> 37 | printf "Error while computing hash with argon2d_hash_encoded: %s\n" 38 | (ErrorCodes.message e) 39 | 40 | let () = 41 | hash_raw (); 42 | hash_encoded (); 43 | () 44 | -------------------------------------------------------------------------------- /tests/argon2i.expected: -------------------------------------------------------------------------------- 1 | argon2d hash:ec6891c09fc1461720e508485da42b2087ca9a708185d9dc890539a33cd2af6f 2 | -------------------------------------------------------------------------------- /tests/argon2i.ml: -------------------------------------------------------------------------------- 1 | open Argon2 2 | open Printf 3 | 4 | let hash_len = 32 5 | 6 | let t_cost = 2 7 | 8 | let m_cost = 65536 9 | 10 | let parallelism = 1 11 | 12 | let salt = "0000000000000000" 13 | 14 | let salt_len = String.length salt 15 | 16 | let pwd = "password" 17 | 18 | let hash_raw () = 19 | match I.hash_raw ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len with 20 | | Result.Ok hash -> 21 | printf "argon2d hash:"; 22 | I.hash_to_string hash 23 | |> String.iter (fun c -> Printf.printf "%02x" (Char.code c)); 24 | printf "\n" 25 | | Result.Error e -> 26 | printf "Error while computing hash with argon2i_hash_raw: %s\n" 27 | (ErrorCodes.message e) 28 | 29 | let () = 30 | hash_raw (); 31 | () 32 | -------------------------------------------------------------------------------- /tests/argon2id.expected: -------------------------------------------------------------------------------- 1 | argon2id hash:36477de377e451ad8a9e38cac8feaa58b5c7e6ee972a4f7ce5fb9df6f5356042 2 | -------------------------------------------------------------------------------- /tests/argon2id.ml: -------------------------------------------------------------------------------- 1 | open Argon2 2 | open Printf 3 | 4 | let hash_len = 32 5 | 6 | let t_cost = 2 7 | 8 | let m_cost = 65536 9 | 10 | let parallelism = 1 11 | 12 | let salt = "0000000000000000" 13 | 14 | let salt_len = String.length salt 15 | 16 | let pwd = "password" 17 | 18 | let hash_raw () = 19 | match ID.hash_raw ~t_cost ~m_cost ~parallelism ~pwd ~salt ~hash_len with 20 | | Result.Ok hash -> 21 | printf "argon2id hash:"; 22 | ID.hash_to_string hash 23 | |> String.iter (fun c -> Printf.printf "%02x" (Char.code c)); 24 | printf "\n" 25 | | Result.Error e -> 26 | printf "Error while computing hash with argon2id_hash_raw: %s\n" 27 | (ErrorCodes.message e) 28 | 29 | let () = 30 | hash_raw (); 31 | () 32 | -------------------------------------------------------------------------------- /tests/dune: -------------------------------------------------------------------------------- 1 | (tests 2 | (names argon2d argon2i argon2id generic) 3 | (libraries argon2)) 4 | -------------------------------------------------------------------------------- /tests/generic.expected: -------------------------------------------------------------------------------- 1 | ===== kind ===== 2 | show_kind upper: Argon2d 3 | show_kind upper: Argon2i 4 | show_kind upper: Argon2id 5 | show_kind lower: argon2d 6 | show_kind lower: argon2i 7 | show_kind lower: argon2id 8 | Argon2i: hash: 9 | ec6891c09fc1461720e508485da42b2087ca9a708185d9dc890539a33cd2af6f 10 | Argon2i: encoded: 11 | $argon2i$v=19$m=65536,t=2,p=1$MDAwMDAwMDAwMDAwMDAwMA$7GiRwJ/BRhcg5QhIXaQrIIfKmnCBhdnciQU5ozzSr28 12 | Argon2i: verify OK 13 | Argon2id: hash: 14 | 36477de377e451ad8a9e38cac8feaa58b5c7e6ee972a4f7ce5fb9df6f5356042 15 | Argon2id: encoded: 16 | $argon2id$v=19$m=65536,t=2,p=1$MDAwMDAwMDAwMDAwMDAwMA$Nkd943fkUa2KnjjKyP6qWLXH5u6XKk985fud9vU1YEI 17 | Argon2id: verify OK 18 | Argon2d: hash: 19 | 30f5e9f2584e962bba213cc256dcf9ccdce18d8f67a6cbf8e012e0619b6d52d2 20 | Argon2d: encoded: 21 | $argon2d$v=19$m=65536,t=2,p=1$MDAwMDAwMDAwMDAwMDAwMA$MPXp8lhOliu6ITzCVtz5zNzhjY9npsv44BLgYZttUtI 22 | Argon2d: verify OK 23 | -------------------------------------------------------------------------------- /tests/generic.ml: -------------------------------------------------------------------------------- 1 | open Argon2 2 | open Printf 3 | 4 | let kind () = 5 | printf "===== kind =====\n"; 6 | [ D; I; ID ] 7 | |> List.map (show_kind `Upper) 8 | |> List.iter (printf "show_kind upper: %s\n"); 9 | [ D; I; ID ] 10 | |> List.map (show_kind `Lower) 11 | |> List.iter (printf "show_kind lower: %s\n"); 12 | () 13 | 14 | let show_kind = show_kind `Upper 15 | 16 | let hash_len = 32 17 | 18 | let t_cost = 2 19 | 20 | let m_cost = 65536 21 | 22 | let parallelism = 1 23 | 24 | let salt = "0000000000000000" 25 | 26 | let salt_len = String.length salt 27 | 28 | let pwd = "password" 29 | 30 | let gen kind = 31 | let msg fmt = Printf.ksprintf (Printf.printf "%s: %s" (show_kind kind)) fmt in 32 | 33 | let encoded_len = 34 | encoded_len ~t_cost ~m_cost ~parallelism ~salt_len ~hash_len ~kind 35 | in 36 | match 37 | hash ~t_cost ~m_cost ~parallelism ~pwd ~salt ~kind ~hash_len ~encoded_len 38 | ~version:VERSION_NUMBER 39 | with 40 | | Result.Ok (hash, encoded) -> ( 41 | msg "hash:\n"; 42 | String.iter (fun c -> Printf.printf "%02x" (Char.code c)) hash; 43 | printf "\n"; 44 | msg "encoded:\n%s\n" encoded; 45 | match verify ~encoded ~pwd ~kind with 46 | | Result.Ok _ -> msg "verify OK\n" 47 | | Result.Error e -> 48 | msg "Error while computing verify: %s\n" (ErrorCodes.message e) ) 49 | | Result.Error e -> 50 | msg "Error while computing hash: %s\n" (ErrorCodes.message e) 51 | 52 | let () = 53 | kind (); 54 | gen I; 55 | gen ID; 56 | gen D; 57 | () 58 | --------------------------------------------------------------------------------