diff --git a/.travis.yml b/.travis.yml
index 1060e48..f36c61f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,10 +10,6 @@ env:
matrix:
- LUA="luajit 2.0"
- LUA="luajit 2.1"
- - LUA="luajit 2.1"
- ARGON2_VERSION=20160406
- - LUA="luajit 2.1"
- ARGON2_VERSION=20160821
before_install:
- bash .ci/setup_argon2.sh
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ded321e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,54 @@
+## [Unreleased][unrelease]
+
+## [3.0.0] - 2016/12/08
+
+**Note**: This module's version was bumped to `3.0.0` to reflect the
+interoperability of its API with the lua-argon2 implementation. In the future,
+lua-argon2 and lua-argon2-ffi will continue sharing the same version number
+for similar versions.
+
+### Changed
+
+- :warning: This version is only compatible with Argon2
+ [20160406](https://github.com/P-H-C/phc-winner-argon2/releases/tag/20160406)
+ and later.
+- :warning: Renamed the `encrypt()` function to `hash_encoded()`, in order to
+ carry a stronger meaning and to eventually implement a `hash_raw()` function
+ in the future.
+- New `variants` field with supported Argon2 encoding variants (as userdatum).
+ See documentation and the "Added" section of this Changelog.
+- Updated the default hashing options to match those of the Argon2 CLI:
+ `t_cost = 3`, `m_cost = 4096`, `parallelism = 1`, `hash_len = 32`.
+
+### Added
+
+- :stars: Support for Argon2id encoding variant.
+ [#24](https://github.com/thibaultcha/lua-argon2/pull/24)
+- We now automatically compute the length of the retrieved encoded hash from
+ `encrypt()`. [#21](https://github.com/thibaultcha/lua-argon2/pull/21)
+- New option: `hash_len`.
+ [#22](https://github.com/thibaultcha/lua-argon2/pull/22)
+- Return errors from `verify()`. A mismatch now returns `false, nil`, while an
+ error will return `nil, "err string"`.
+ [#23](https://github.com/thibaultcha/lua-argon2/pull/23)
+
+## [1.0.0] - 2016/04/10
+
+### Added
+
+- :stars: Support for Argon2
+ [20160406](https://github.com/P-H-C/phc-winner-argon2/releases/tag/20160406)
+ (and later). The major version of this module has been bumped because the
+ resulting hashes will not be backwards compatible.
+- Performance improvements.
+ [e9e6f9](https://github.com/thibaultcha/lua-argon2-ffi/commit/e9e6f9a609f40f9f26834364e05d701fbc0f9780)
+
+## [0.0.1] - 2016/02/21
+
+Initial release of this module for Argon2
+[20151206](https://github.com/P-H-C/phc-winner-argon2/releases/tag/20151206).
+
+[unreleased]: https://github.com/thibaultcha/lua-argon2-ffi/compare/3.0.0...master
+[3.0.0]: https://github.com/thibaultcha/lua-argon2-ffi/compare/1.0.0...3.0.0
+[1.0.0]: https://github.com/thibaultcha/lua-argon2-ffi/compare/0.0.1...1.0.0
+[0.0.1]: https://github.com/thibaultcha/lua-argon2-ffi/compare/a2f94a08ec34bdd570ff707f5e2bebf87a60ba62...0.0.1
diff --git a/README.md b/README.md
index dea3dd0..75d944a 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,24 @@
# lua-argon2-ffi
+
[![Module Version][badge-version-image]][luarocks-argon2-ffi]
[![Build Status][badge-travis-image]][badge-travis-url]
[![Coverage Status][badge-coveralls-image]][badge-coveralls-url]
-FFI binding of [Argon2] for LuaJIT.
+LuaJIT FFI binding for the [Argon2] password hashing function.
While [lua-argon2] provides a PUC Lua binding through the Lua C API, this
module is a binding for the LuaJIT FFI, especially fit for use in
[ngx_lua]/[OpenResty].
-### Prerequisites
+### Table of Contents
+
+- [Requirements](#requirements)
+- [Installation](#installation)
+- [Documentation](#documentation)
+- [Example](#example)
+- [License](#license)
+
+### Requirements
The [Argon2] shared library must be compiled and available in your system.
@@ -19,11 +28,16 @@ Compatibility:
- Version `1.x` of this module is compatible with Argon2
[`20160406`](https://github.com/P-H-C/phc-winner-argon2/releases/tag/20160406)
and later.
+- Version `3.x` of this module is compatible with Argon2
+ [`20161029`](https://github.com/P-H-C/phc-winner-argon2/releases/tag/20161029)
+ and later.
See the [CI builds][badge-coveralls-url] for the status of the currently
supported versions.
-### Install
+[Back to TOC](#table-of-contents)
+
+### Installation
This binding can be installed via [Luarocks](https://luarocks.org):
@@ -33,61 +47,85 @@ $ luarocks install argon2-ffi
Or simply by copying the `src/argon2.lua` file in your `LUA_PATH`.
-### Usage
+[Back to TOC](#table-of-contents)
+
+### Documentation
**Note**: lua-argon2-ffi uses the same API as [lua-argon2], to the exception of
the default settings capabilities of lua-argon2.
-Encrypt:
+This binding's documentation is available at
+.
+
+The Argon2 password hashing function documentation is available at
+.
+
+[Back to TOC](#table-of-contents)
+
+### Example
+
+Hash a password to an encoded string:
```lua
local argon2 = require "argon2"
--- Prototype
--- local hash, err = argon2.encrypt(pwd, salt, opts)
+-- local encoded, err = argon2.hash_encoded(pwd, salt, opts)
--- Argon2i
-local hash = assert(argon2.encrypt("password", "somesalt"))
--- hash is "$argon2i$m=12,t=2,p=1$c29tZXNhbHQ$ltrjNRFqTXmsHj++TFGZxg+zSg8hSrrSJiViCRns1HM"
+local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+-- encoded is "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$iWh06vD8Fy27wf9npn6FXWiCX4K6pW6Ue1Bnzz07Z8A"
--- Argon2d
-local hash = assert(argon2.encrypt("password", "somesalt", {argon2d = true}))
--- hash is "$argon2d$m=12,t=2,p=1$c29tZXNhbHQ$mfklun4fYCbv2Hw0UnZZ56xAqWbjD+XRMSN9h6SfLe4"
+local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_d
+}))
+-- encoded is "$argon2d$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$2+JCoQtY/2x5F0VB9pEVP3xBNguWP1T25Ui0PtZuk8o"
+
+--- Argon2id
+local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_id
+}))
+-- encoded is "$argon2id$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$qLml5cbqFAO6YxVHhrSBHP0UWdxrIxkNcM8aMX3blzU"
-- Hashing options
-local hash = assert(argon2.encrypt("password", "somesalt", {
+local encoded = assert(argon2.hash_encoded("password", "somesalt", {
t_cost = 4,
- m_cost = 24,
- parallelism = 2,
- hash_len = 64
+ m_cost = math.pow(2, 16), -- 65536 KiB
+ parallelism = 2
}))
--- hash is "$argon2i$v=19$m=24,t=4,p=2$c29tZXNhbHQ$NV3zeCzIhUhosd7jtTuifTEoPOb/aPAtO0oTYdZkWfNBXCglBgxVEiJy+tLG4j011vZRO3pnmG82Vc/C1B6Tzw"
+-- encoded is "$argon2i$v=19$m=65536,t=4,p=2$c29tZXNhbHQ$n6x5DKNWV8BOeKemQJRk7BU3hcaCVomtn9TCyEA0inM"
```
-Verify:
+Verify a password against an encoded string:
```lua
local argon2 = require "argon2"
--- Prototype
-- local ok, err = argon2.decrypt(hash, plain)
-local hash = assert(argon2.encrypt("password", "somesalt"))
--- hash is an argon2i hash
+local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+-- encoded: argon2i encoded hash
-assert(argon2.verify(hash, "password")) -- ok: true
-assert(argon2.verify(hash, "passworld")) -- error: The password did not match
-```
+local ok, err = argon2.verify(encoded, "password")
+if err then
+ error("could not verify: " .. err)
+end
-### Documentation
+if not ok then
+ error("The password does not match the supplied hash")
+end
+```
-This module's API being the same as [lua-argon2]'s, the detailed documentation
-is available at .
+[Back to TOC](#table-of-contents)
### License
Work licensed under the MIT License. Please check
-[P-H-C/phc-winner-argon2][Argon2] for license over Argon2 and the reference
+[P-H-C/phc-winner-argon2][Argon2] for the license over Argon2 and the reference
implementation.
+[Back to TOC](#table-of-contents)
+
[Argon2]: https://github.com/P-H-C/phc-winner-argon2
[lua-argon2]: https://github.com/thibaultCha/lua-argon2
[luarocks-argon2-ffi]: http://luarocks.org/modules/thibaultcha/argon2-ffi
@@ -97,6 +135,6 @@ implementation.
[badge-travis-url]: https://travis-ci.org/thibaultcha/lua-argon2-ffi
[badge-travis-image]: https://travis-ci.org/thibaultcha/lua-argon2-ffi.svg?branch=master
-[badge-version-image]: https://img.shields.io/badge/version-1.0.0-blue.svg?style=flat
+[badge-version-image]: https://img.shields.io/badge/version-3.0.0-blue.svg?style=flat
[badge-coveralls-url]: https://coveralls.io/github/thibaultcha/lua-argon2-ffi?branch=master
[badge-coveralls-image]: https://coveralls.io/repos/github/thibaultcha/lua-argon2-ffi/badge.svg?branch=master
diff --git a/argon2-ffi-1.0.0-0.rockspec b/argon2-ffi-3.0.0-1.rockspec
similarity index 57%
rename from argon2-ffi-1.0.0-0.rockspec
rename to argon2-ffi-3.0.0-1.rockspec
index a77571d..fab793f 100644
--- a/argon2-ffi-1.0.0-0.rockspec
+++ b/argon2-ffi-3.0.0-1.rockspec
@@ -1,12 +1,12 @@
package = "argon2-ffi"
-version = "1.0.0-0"
+version = "3.0.0-1"
source = {
- url = "git://github.com/thibaultCha/lua-argon2-ffi",
- tag = "1.0.0"
+ url = "git://github.com/thibaultcha/lua-argon2-ffi",
+ tag = "3.0.0"
}
description = {
- summary = "LuaJIT FFI binding for the Argon2 password hashing algorithm",
- homepage = "https://github.com/thibaultCha/lua-argon2-ffi",
+ summary = "LuaJIT FFI binding for the Argon2 password hashing function",
+ homepage = "https://github.com/thibaultcha/lua-argon2-ffi",
license = "MIT"
}
build = {
diff --git a/spec/argon2_spec.lua b/spec/argon2_spec.lua
index fec48cc..26e6d6a 100644
--- a/spec/argon2_spec.lua
+++ b/spec/argon2_spec.lua
@@ -1,147 +1,336 @@
local argon2 = require "argon2"
describe("argon2", function()
- it("_VERSION field", function()
- assert.equal("1.0.0", argon2._VERSION)
- end)
- it("_AUTHOR field", function()
- assert.equal("Thibault Charbonnier", argon2._AUTHOR)
- end)
- it("_LICENSE field", function()
- assert.equal("MIT", argon2._LICENSE)
- end)
- it("_URL field", function()
- assert.equal("https://github.com/thibaultCha/lua-argon2-ffi", argon2._URL)
- end)
+ it("_VERSION field", function()
+ assert.equal("3.0.0", argon2._VERSION)
+ end)
+
+ it("_AUTHOR field", function()
+ assert.equal("Thibault Charbonnier", argon2._AUTHOR)
+ end)
+
+ it("_LICENSE field", function()
+ assert.equal("MIT", argon2._LICENSE)
+ end)
+
+ it("_URL field", function()
+ assert.equal("https://github.com/thibaultcha/lua-argon2-ffi", argon2._URL)
+ end)
+
+ it("variants field", function()
+ assert.is_table(argon2.variants)
+ --assert.is_userdata(argon2.variants.argon2_i)
+ --assert.is_userdata(argon2.variants.argon2_d)
+ --assert.is_userdata(argon2.variants.argon2_id)
+ end)
end)
-describe("encrypt()", function()
- it("should throw error on invalid argument", function()
- assert.has_error(function()
- argon2.encrypt(nil)
- end, "bad argument #1 to 'encrypt' (string expected, got nil)")
-
- assert.has_error(function()
- argon2.encrypt("", nil)
- end, "bad argument #2 to 'encrypt' (string expected, got nil)")
-
- assert.has_error(function()
- argon2.encrypt("", "", "")
- end, "bad argument #3 to 'encrypt' (table expected, got string)")
-
- assert.has_error(function()
- argon2.encrypt("", "", {t_cost = ""})
- end, "expected t_cost to be a number")
-
- assert.has_error(function()
- argon2.encrypt("", "", {m_cost = ""})
- end, "expected m_cost to be a number")
-
- assert.has_error(function()
- argon2.encrypt("", "", {parallelism = ""})
- end, "expected parallelism to be a number")
-
- assert.has_error(function()
- argon2.encrypt("", "", {hash_len = ""})
- end, "expected hash_len to be a number")
- end)
- it("salt too short", function()
- local hash, err = argon2.encrypt("password", "")
- assert.falsy(hash)
- assert.equal("Salt is too short", err)
-
- hash, err = argon2.encrypt("password", "abcdefg")
- assert.falsy(hash)
- assert.equal("Salt is too short", err)
- end)
- it("hash_len too short", function()
- local hash, err = argon2.encrypt("password", "somesalt", {hash_len = 2})
- assert.falsy(hash)
- assert.equal("Output is too short", err)
- end)
- it("should return a hash", function()
- local hash = assert(argon2.encrypt("password", "somesalt"))
- assert.matches("$argon2i$v=19$m=12,t=2,p=1$", hash, nil, true)
- local hash_pattern = "%$" .. string.rep(".", 43) .. "$"
- assert.matches(hash_pattern, hash)
- end)
- it("should return a hash with longer encoded_len", function()
- local hash = assert(argon2.encrypt("password", string.rep("salt", 10)))
- assert.matches("$argon2i$v=19$m=12,t=2,p=1$", hash, nil, true)
- end)
- it("should hash with argon2d", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {argon2d = true}))
- assert.matches("argon2d", hash)
- end)
- it("should accept time cost", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {t_cost = 4}))
- assert.matches("t=4", hash)
- end)
- it("should accept memory cost", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {m_cost = 13}))
- assert.matches("m=13", hash)
- end)
- it("should accept parallelism", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {parallelism = 2, m_cost = 24}))
- assert.matches("p=2", hash)
- end)
- it("should accept hash_len", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {hash_len = 7}))
- local hash_pattern = "%$" .. string.rep(".", 10) .. "$"
- assert.matches(hash_pattern, hash)
- end)
- it("should accept all options", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {
- t_cost = 4,
- m_cost = 24,
- parallelism = 2,
- hash_len = 9
- }))
- assert.matches("m=24,t=4,p=2", hash)
- local hash_pattern = "%$" .. string.rep(".", 12) .. "$"
- assert.matches(hash_pattern, hash)
- end)
+describe("hash_encoded()", function()
+
+ describe("variants encoding", function()
+ it("argon2_i", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+ assert.matches("$argon2i$v=19$m=4096,t=3,p=1$", encoded, nil, true)
+ end)
+
+ it("argon2_d", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_d
+ }))
+ assert.matches("$argon2d$", encoded, nil, true)
+ end)
+
+ it("argon2_id", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_id
+ }))
+ assert.matches("$argon2id$", encoded, nil, true)
+ end)
+ end)
+
+ it("calculates the appropriate encoded_len (triggered by long salt)", function()
+ local encoded = assert(argon2.hash_encoded("password", string.rep("salt", 8)))
+ assert.matches("$argon2i$v=19$m=4096,t=3,p=1$", encoded, nil, true)
+ end)
+
+ describe("errors", function()
+ it("throws error on invalid argument", function()
+ assert.has_error(function()
+ argon2.hash_encoded(nil)
+ end, "bad argument #1 to 'hash_encoded' (string expected, got nil)")
+
+ assert.has_error(function()
+ argon2.hash_encoded("", nil)
+ end, "bad argument #2 to 'hash_encoded' (string expected, got nil)")
+
+ assert.has_error(function()
+ argon2.hash_encoded("", "", "")
+ end, "bad argument #3 to 'hash_encoded' (expected to be a table)")
+
+ assert.has_error(function()
+ argon2.hash_encoded("", "", {t_cost = ""})
+ end, "bad argument #3 to 'hash_encoded' (expected t_cost to be a number, got string)")
+
+ assert.has_error(function()
+ argon2.hash_encoded("", "", {m_cost = ""})
+ end, "bad argument #3 to 'hash_encoded' (expected m_cost to be a number, got string)")
+
+ assert.has_error(function()
+ argon2.hash_encoded("", "", {parallelism = ""})
+ end, "bad argument #3 to 'hash_encoded' (expected parallelism to be a number, got string)")
+
+ --[[
+ assert.has_error(function()
+ argon2.hash_encoded("", "", {}, "")
+ end, "expecting no more than 3 arguments, but got 4")
+ --]]
+
+ assert.has_error(function()
+ argon2.hash_encoded("", "", { variant = "" })
+ end, "bad argument #3 to 'hash_encoded' (expected variant to be an argon2_type, got string)")
+ end)
+
+ it("returns error on failure to hash_encoded", function()
+ local encoded, err = argon2.hash_encoded("password", "")
+ assert.falsy(encoded)
+ assert.equal("Salt is too short", err)
+
+ encoded, err = argon2.hash_encoded("password", "abcdefg")
+ assert.falsy(encoded)
+ assert.equal("Salt is too short", err)
+ end)
+ end)
+
+ describe("options", function()
+ it("accepts time cost", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ t_cost = 4
+ }))
+ assert.matches("t=4", encoded)
+ end)
+
+ it("accepts memory cost", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ m_cost = 13
+ }))
+ assert.matches("m=13", encoded)
+ end)
+
+ it("accepts parallelism", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ parallelism = 2,
+ m_cost = 24
+ }))
+ assert.matches("p=2", encoded)
+ end)
+
+ it("accepts hash_len", function()
+ local encoded_hash_default = assert(argon2.hash_encoded("password", "somesalt"))
+ assert.is_string(encoded_hash_default)
+
+ local encoded_hash_64 = assert(argon2.hash_encoded("password", "somesalt", {
+ hash_len = 64
+ }))
+ assert.is_string(encoded_hash_64)
+ assert.not_equal(encoded_hash_default, encoded_hash_64)
+ end)
+
+ it("accepts several options at once", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ t_cost = 4,
+ m_cost = 24,
+ parallelism = 2
+ }))
+ assert.matches("m=24,t=4,p=2", encoded)
+ end)
+ end)
end)
describe("verify()", function()
- it("should throw error on invalid argument", function()
- assert.has_error(function()
- argon2.verify(nil)
- end, "bad argument #1 to 'verify' (string expected, got nil)")
-
- assert.has_error(function()
- argon2.verify("", nil)
- end, "bad argument #2 to 'verify' (string expected, got nil)")
- end)
- it("should verify ok", function()
- local hash = assert(argon2.encrypt("password", "somesalt"))
- assert(argon2.verify(hash, "password"))
- end)
- it("should verify ok with hash_len option", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {hash_len = 10}))
- assert(argon2.verify(hash, "password"))
- end)
- it("should verify fail", function()
- local hash = assert(argon2.encrypt("password", "somesalt"))
- local ok, err = argon2.verify(hash, "passworld")
- assert.False(ok)
- assert.equal("The password did not match.", err)
- end)
- it("should verify argon2d ok", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {argon2d = true}))
- assert(argon2.verify(hash, "password"))
- end)
- it("should verify argon2d ok with hash_len option", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {
- argon2d = true,
- hash_len = 10
- }))
- assert(argon2.verify(hash, "password"))
- end)
- it("should verify argon2d fail", function()
- local hash = assert(argon2.encrypt("password", "somesalt", {argon2d = true}))
- local ok, err = argon2.verify(hash, "passworld")
- assert.False(ok)
- assert.equal("The password did not match.", err)
- end)
+
+ describe("variants", function()
+
+ describe("match returns true", function()
+ it("argon2_i", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+ assert(argon2.verify(encoded, "password"))
+ end)
+
+ it("argon2_d", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_d
+ }))
+ assert(argon2.verify(encoded, "password"))
+ end)
+
+ it("argon2_id", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_id
+ }))
+ assert(argon2.verify(encoded, "password"))
+ end)
+ end)
+
+ describe("mismatch returns false and no error", function()
+ it("argon2_i", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+ local ok, err = argon2.verify(encoded, "passworld")
+ assert.False(ok)
+ assert.is_nil(err)
+ end)
+
+ it("argon2_d", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_d
+ }))
+
+ local ok, err = argon2.verify(encoded, "passworld")
+ assert.False(ok)
+ assert.is_nil(err)
+ end)
+
+ it("argon2_id", function()
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_id
+ }))
+
+ local ok, err = argon2.verify(encoded, "passworld")
+ assert.False(ok)
+ assert.is_nil(err)
+ end)
+ end)
+ end)
+
+ describe("errors", function()
+ it("throws error on invalid argument", function()
+ --[[
+ assert.has_error(function()
+ argon2.verify(nil)
+ end, "expecting 2 arguments, but got 1")
+ --]]
+
+ assert.has_error(function()
+ argon2.verify(nil, nil)
+ end, "bad argument #1 to 'verify' (string expected, got nil)")
+
+ assert.has_error(function()
+ argon2.verify("", nil)
+ end, "bad argument #2 to 'verify' (string expected, got nil)")
+
+ --[[
+ assert.has_error(function()
+ argon2.verify("", "", "")
+ end, "expecting 2 arguments, but got 3")
+ --]]
+ end)
+
+ it("returns nil and an error message on failure to decode", function()
+ local encoded = "$argon2i$v=19$m=4096,t=3,p=1c29tZXNhbHQ$iWh06vD8Fy27wf9npn6FXWiCX4K6pW6Ue1Bnzz07Z8A"
+ local ok, err = argon2.verify(encoded, "")
+ assert.is_nil(ok)
+ assert.equal("Decoding failed", err)
+ end)
+ end)
+end)
+
+--[[
+pending("module settings", function()
+
+ it("throws an error on invalid argument", function()
+ assert.has_error(function()
+ argon2.t_cost(0, 0)
+ end, "expecting no more than 1 arguments, but got 2")
+
+ assert.has_error(function()
+ argon2.t_cost ""
+ end, "bad argument #1 to 't_cost' (expected t_cost to be a number, got string)")
+
+ assert.has_error(function()
+ argon2.variant(nil)
+ end, "bad argument #1 to 'variant' (userdata expected, got nil)")
+ end)
+
+ it("accepts t_cost module setting", function()
+ finally(function()
+ argon2.t_cost(3)
+ end)
+
+ assert.equal(4, argon2.t_cost(4))
+
+ local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+ assert.matches("$argon2i$v=19$m=4096,t=4,p=1$", encoded, nil, true)
+
+ encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ t_cost = 2
+ }))
+ assert.matches("$argon2i$v=19$m=4096,t=2,p=1$", encoded, nil, true)
+ end)
+
+ it("accepts m_cost module setting", function()
+ finally(function()
+ argon2.m_cost(4096)
+ end)
+
+ assert.equal(24, argon2.m_cost(24))
+
+ local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+ assert.matches("$argon2i$v=19$m=24,t=3,p=1$", encoded, nil, true)
+
+ encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ m_cost = 12
+ }))
+ assert.matches("$argon2i$v=19$m=12,t=3,p=1$", encoded, nil, true)
+ end)
+
+ it("accepts parallelism module setting", function()
+ finally(function()
+ argon2.parallelism(1)
+ end)
+
+ assert.equal(2, argon2.parallelism(2))
+
+ local encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ m_cost = 24
+ }))
+ assert.matches("$argon2i$v=19$m=24,t=3,p=2$", encoded, nil, true)
+
+ encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ parallelism = 1
+ }))
+ assert.matches("$argon2i$v=19$m=4096,t=3,p=1$", encoded, nil, true)
+ end)
+
+ it("accepts hash_len module setting", function()
+ finally(function()
+ argon2.hash_len(32)
+ end)
+
+ local encoded_default_hash_len = assert(argon2.hash_encoded("password", "somesalt"))
+
+ assert.equal(64, argon2.hash_len(64))
+
+ local encoded_hash_len_64 = assert(argon2.hash_encoded("password", "somesalt"))
+
+ assert.not_equal(encoded_default_hash_len, encoded_hash_len_64)
+ end)
+
+ it("accepts variant module setting", function()
+ finally(function()
+ argon2.variant(argon2.variants.argon2_i)
+ end)
+
+ local variant = argon2.variant(argon2.variants.argon2_d)
+ assert.equal(argon2.variants.argon2_d, variant)
+
+ local encoded = assert(argon2.hash_encoded("password", "somesalt"))
+ assert.matches("$argon2d$v=19$m=4096,t=3,p=1$", encoded, nil, true)
+
+ encoded = assert(argon2.hash_encoded("password", "somesalt", {
+ variant = argon2.variants.argon2_i
+ }))
+ assert.matches("$argon2i$v=19$m=4096,t=3,p=1$", encoded, nil, true)
+ end)
end)
+--]]
+
+-- vim:st=4 sts=4 sw=4 et:
diff --git a/src/argon2.lua b/src/argon2.lua
index 5390666..ce8ff62 100644
--- a/src/argon2.lua
+++ b/src/argon2.lua
@@ -2,6 +2,7 @@
local ffi = require "ffi"
+local ffi_new = ffi.new
local ffi_str = ffi.string
local find = string.find
local error = error
@@ -10,75 +11,109 @@ local type = type
ffi.cdef [[
- typedef enum Argon2_type { Argon2_d = 0, Argon2_i = 1 } argon2_type;
+ typedef enum Argon2_type {
+ Argon2_d = 0,
+ Argon2_i = 1,
+ Argon2_id = 2
+ } argon2_type;
+
+ const char *argon2_error_message(int error_code);
+
+ size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
+ uint32_t parallelism, uint32_t saltlen,
+ uint32_t hashlen, argon2_type type);
int argon2i_hash_encoded(const uint32_t t_cost,
- const uint32_t m_cost,
- const uint32_t parallelism,
- const void *pwd, const size_t pwdlen,
- const void *salt, const size_t saltlen,
- const size_t hashlen, char *encoded,
- const size_t encodedlen);
+ const uint32_t m_cost,
+ const uint32_t parallelism,
+ const void *pwd, const size_t pwdlen,
+ const void *salt, const size_t saltlen,
+ const size_t hashlen, char *encoded,
+ const size_t encodedlen);
int argon2d_hash_encoded(const uint32_t t_cost,
- const uint32_t m_cost,
- const uint32_t parallelism,
- const void *pwd, const size_t pwdlen,
- const void *salt, const size_t saltlen,
- const size_t hashlen, char *encoded,
- const size_t encodedlen);
+ const uint32_t m_cost,
+ const uint32_t parallelism,
+ const void *pwd, const size_t pwdlen,
+ const void *salt, const size_t saltlen,
+ const size_t hashlen, char *encoded,
+ const size_t encodedlen);
+
+ int argon2id_hash_encoded(const uint32_t t_cost,
+ const uint32_t m_cost,
+ const uint32_t parallelism,
+ const void *pwd, const size_t pwdlen,
+ const void *salt, const size_t saltlen,
+ const size_t hashlen, char *encoded,
+ const size_t encodedlen);
int argon2_verify(const char *encoded,
- const void *pwd,
- const size_t pwdlen,
- argon2_type type);
+ const void *pwd,
+ const size_t pwdlen,
+ argon2_type type);
+]]
- const char *argon2_error_message(int error_code);
- size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
- uint32_t parallelism, uint32_t saltlen,
- uint32_t hashlen, argon2_type type);
-]]
+local lib = ffi.load "argon2"
-local OPTIONS = {
- t_cost = 2,
- m_cost = 12,
- parallelism = 1,
- hash_len = 32,
- argon2d = false,
-}
+local ARGON2_OK = 0
+local ARGON2_VERIFY_MISMATCH = -35
-local argon2_t = ffi.typeof(ffi.new "argon2_type")
-local c_type_i = ffi.new(argon2_t, "Argon2_i")
-local c_type_d = ffi.new(argon2_t, "Argon2_d")
+local argon2_i, argon2_d, argon2_id
-local lib = ffi.load "argon2"
+do
+ local argon2_t = ffi.typeof(ffi.new "argon2_type")
+
+ argon2_i = ffi_new(argon2_t, "Argon2_i")
+ argon2_d = ffi_new(argon2_t, "Argon2_d")
+ argon2_id = ffi_new(argon2_t, "Argon2_id")
+end
+
+
+local OPTIONS = {
+ t_cost = 3,
+ m_cost = 4096,
+ parallelism = 1,
+ hash_len = 32,
+ variant = argon2_i,
+}
local _M = {
- _VERSION = "1.0.0",
+ _VERSION = "3.0.0",
_AUTHOR = "Thibault Charbonnier",
_LICENSE = "MIT",
- _URL = "https://github.com/thibaultCha/lua-argon2-ffi",
+ _URL = "https://github.com/thibaultcha/lua-argon2-ffi",
+ variants = {
+ argon2_i = argon2_i,
+ argon2_d = argon2_d,
+ argon2_id = argon2_id,
+ },
}
-function _M.encrypt(pwd, salt, opts)
+
+function _M.hash_encoded(pwd, salt, opts)
if type(pwd) ~= "string" then
- return error("bad argument #1 to 'encrypt' (string expected, got " .. type(pwd) .. ")", 2)
+ return error("bad argument #1 to 'hash_encoded' (string expected, got "
+ .. type(pwd) .. ")", 2)
+ end
+
+ if type(salt) ~= "string" then
+ return error("bad argument #2 to 'hash_encoded' (string expected, got "
+ .. type(salt) .. ")", 2)
+ end
- elseif type(salt) ~= "string" then
- return error("bad argument #2 to 'encrypt' (string expected, got " .. type(salt) .. ")", 2)
+ if opts and type(opts) ~= "table" then
+ return error("bad argument #3 to 'hash_encoded' (expected to be a "
+ .. "table)", 2)
end
if opts == nil then
opts = OPTIONS
- elseif type(opts) ~= "table" then
- return error("bad argument #3 to 'encrypt' (table expected, got " .. type(opts) .. ")", 2)
-
else
for k, v in pairs(OPTIONS) do
local o = opts[k]
@@ -86,27 +121,46 @@ function _M.encrypt(pwd, salt, opts)
if o == nil then
opts[k] = v
- elseif k ~= "argon2d" and type(o) ~= "number" then
- return error("expected " .. k .. " to be a number", 2)
+ elseif k == "variant" then
+ if type(o) ~= "cdata" then
+ return error("bad argument #3 to 'hash_encoded' (expected "
+ .. k .. " to be an argon2_type, got "
+ .. type(o) .. ")", 2)
+ end
+
+ elseif type(o) ~= "number" then
+ return error("bad argument #3 to 'hash_encoded' (expected "
+ .. k .. " to be a number, got "
+ .. type(o) .. ")", 2)
end
end
end
- local c_type = opts.argon2d and c_type_d or c_type_i
- local buf_len = lib.argon2_encodedlen(opts.t_cost, opts.m_cost, opts.parallelism,
- #salt, opts.hash_len, c_type)
- local buf = ffi.new("char[?]", buf_len)
- local res
- if opts.argon2d then
- res = lib.argon2d_hash_encoded(opts.t_cost, opts.m_cost, opts.parallelism,
- pwd, #pwd, salt, #salt, opts.hash_len, buf, buf_len)
+ local buf_len = lib.argon2_encodedlen(opts.t_cost, opts.m_cost,
+ opts.parallelism, #salt,
+ opts.hash_len, opts.variant)
+
+ local buf = ffi_new("char[?]", buf_len)
+ local ret_code
+
+ if opts.variant == argon2_d then
+ ret_code = lib.argon2d_hash_encoded(opts.t_cost, opts.m_cost,
+ opts.parallelism, pwd, #pwd, salt,
+ #salt, opts.hash_len, buf, buf_len)
+
+ elseif opts.variant == argon2_id then
+ ret_code = lib.argon2id_hash_encoded(opts.t_cost, opts.m_cost,
+ opts.parallelism, pwd, #pwd, salt,
+ #salt, opts.hash_len, buf, buf_len)
+
else
- res = lib.argon2i_hash_encoded(opts.t_cost, opts.m_cost, opts.parallelism,
- pwd, #pwd, salt, #salt, opts.hash_len, buf, buf_len)
+ ret_code = lib.argon2i_hash_encoded(opts.t_cost, opts.m_cost,
+ opts.parallelism, pwd, #pwd, salt,
+ #salt, opts.hash_len, buf, buf_len)
end
- if res ~= 0 then
- local c_msg = lib.argon2_error_message(res)
+ if ret_code ~= ARGON2_OK then
+ local c_msg = lib.argon2_error_message(ret_code)
return nil, ffi_str(c_msg)
end
@@ -114,21 +168,38 @@ function _M.encrypt(pwd, salt, opts)
end
-function _M.verify(hash, plain)
- if type(hash) ~= "string" then
- return error("bad argument #1 to 'verify' (string expected, got " .. type(hash) .. ")", 2)
+function _M.verify(encoded, plain)
+ if type(encoded) ~= "string" then
+ return error("bad argument #1 to 'verify' (string expected, got "
+ .. type(encoded) .. ")", 2)
+ end
- elseif type(plain) ~= "string" then
- return error("bad argument #2 to 'verify' (string expected, got " .. type(plain) .. ")", 2)
+ if type(plain) ~= "string" then
+ return error("bad argument #2 to 'verify' (string expected, got "
+ .. type(plain) .. ")", 2)
end
- local argon2d = find(hash, "argon2d", nil, true) ~= nil
- local c_type = argon2d and c_type_d or c_type_i
+ local variant
- local res = lib.argon2_verify(hash, plain, #plain, c_type)
+ if find(encoded, "argon2d", nil, true) then
+ variant = argon2_d
- if res ~= 0 then
- return false, "The password did not match."
+ elseif find(encoded, "argon2id", nil, true) then
+ variant = argon2_id
+
+ else
+ variant = argon2_i
+ end
+
+ local ret_code = lib.argon2_verify(encoded, plain, #plain, variant)
+
+ if ret_code == ARGON2_VERIFY_MISMATCH then
+ return false
+ end
+
+ if ret_code ~= ARGON2_OK then
+ local c_msg = lib.argon2_error_message(ret_code)
+ return nil, ffi_str(c_msg)
end
return true