-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Pevensie/v1
Initial development
- Loading branch information
Showing
8 changed files
with
517 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: test | ||
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
- main | ||
pull_request: | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: erlef/setup-beam@v1 | ||
with: | ||
otp-version: "26.0.2" | ||
gleam-version: "1.3.2" | ||
rebar3-version: "3" | ||
# elixir-version: "1.15.4" | ||
- run: gleam deps download | ||
- run: gleam test | ||
- run: gleam format --check src test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.beam | ||
*.ez | ||
/build | ||
erl_crash.dump |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Argus | ||
|
||
Argon2 password hashing library for Gleam, based on the reference C implementation. | ||
|
||
[![Package Version](https://img.shields.io/hexpm/v/antigone)](https://hex.pm/packages/antigone) | ||
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/antigone/) | ||
|
||
This library uses another Pevensie project, [jargon](https://github.com/Pevensie/jargon), to provide the underlying NIF. | ||
|
||
It currently only supports Gleam's Erlang backend. | ||
|
||
## Example | ||
|
||
```bash | ||
gleam add argus | ||
``` | ||
|
||
```gleam | ||
import argus | ||
pub fn main() { | ||
// Hash a password using the recommended settings for Argon2id. | ||
let assert Ok(hashes) = | ||
argus.hasher() | ||
|> argus.hash("password", gen_salt()) | ||
// Hash a password with custom settings and a custom salt. | ||
let assert Ok(hashes) = | ||
argus.hasher() | ||
|> argus.algorithm(argus.Argon2id) | ||
|> argus.time_cost(3) | ||
|> argus.memory_cost(12228) // 12 mebibytes | ||
|> argus.parallelism(1) | ||
|> argus.hash_length(32) | ||
|> argus.hash("password", "custom_salt") | ||
// Verify a password. | ||
let assert Ok(True) = argus.verify(hashes.encoded_hash, "password") | ||
} | ||
``` | ||
|
||
More information can be found in the [documentation](https://hexdocs.pm/argus/). | ||
|
||
## Why 'Argus'? | ||
|
||
[Argus](https://en.wikipedia.org/wiki/Argus_(Argonaut)) was the builder of the | ||
[Argo](https://en.wikipedia.org/wiki/Argo) ship and was one of the | ||
[Argonauts](https://en.wikipedia.org/wiki/Argonauts). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
name = "argus" | ||
version = "1.0.0" | ||
description = "Argon2 password hashing library for Gleam, based on the reference C implementation." | ||
licences = ["MIT"] | ||
repository = { type = "github", user = "Pevensie", repo = "argus" } | ||
# links = [{ title = "Website", href = "" }] | ||
|
||
[dependencies] | ||
gleam_stdlib = ">= 0.34.0 and < 2.0.0" | ||
startest = ">= 0.4.0 and < 1.0.0" | ||
jargon = ">= 1.0.0 and < 2.0.0" | ||
|
||
[dev-dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# This file was generated by Gleam | ||
# You typically do not need to edit this file | ||
|
||
packages = [ | ||
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, | ||
{ name = "bigben", version = "1.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "bigben", source = "hex", outer_checksum = "8E5A98FA6E981EEEF016C40F1CDFADA095927CAF6CAAA0C7E295EED02FC95947" }, | ||
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" }, | ||
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, | ||
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, | ||
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" }, | ||
{ name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" }, | ||
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" }, | ||
{ name = "gleam_javascript", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "483631D3001FCE8EB12ADEAD5E1B808440038E96F93DA7A32D326C82F480C0B2" }, | ||
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, | ||
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" }, | ||
{ name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" }, | ||
{ name = "glint", version = "1.0.0-rc2", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "FD5C47CE237CA67121F3946ADE7C630750BB67F5E8A4717D2DF5B5EE758CCFDB" }, | ||
{ name = "jargon", version = "1.0.0", build_tools = ["rebar3"], requirements = [], otp_app = "jargon", source = "hex", outer_checksum = "60FBFACC920EAEBC96C76DA3D8ED814FABDDC2103CC0D04FE314A3C15F3174DF" }, | ||
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" }, | ||
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" }, | ||
{ name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" }, | ||
{ name = "startest", version = "0.4.0", build_tools = ["gleam"], requirements = ["argv", "bigben", "birl", "exception", "gleam_community_ansi", "gleam_erlang", "gleam_javascript", "gleam_stdlib", "glint", "simplifile", "tom"], otp_app = "startest", source = "hex", outer_checksum = "BA5B1D896F097040557C7DC311FA3FFACEBBD182CCBB02503D7218545D37F348" }, | ||
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, | ||
{ name = "tom", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0831C73E45405A2153091226BF98FB485ED16376988602CC01A5FD086B82D577" }, | ||
] | ||
|
||
[requirements] | ||
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } | ||
jargon = { version = ">= 1.0.0 and < 2.0.0"} | ||
startest = { version = ">= 0.4.0 and < 1.0.0" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
pub type Argon2Algorithm { | ||
Argon2d | ||
Argon2i | ||
Argon2id | ||
} | ||
|
||
pub opaque type ArgusHasher { | ||
ArgusHasher( | ||
algorithm: Argon2Algorithm, | ||
time_cost: Int, | ||
memory_cost: Int, | ||
parallelism: Int, | ||
hash_length: Int, | ||
) | ||
} | ||
|
||
pub type ArgusHash { | ||
ArgusHash(raw_hash: BitArray, encoded_hash: String) | ||
} | ||
|
||
pub type HashError { | ||
OutputPointerIsNull | ||
OutputTooShort | ||
OutputTooLong | ||
PasswordTooShort | ||
PasswordTooLong | ||
SaltTooShort | ||
SaltTooLong | ||
AssociatedDataTooShort | ||
AssociatedDataTooLong | ||
SecretTooShort | ||
SecretTooLong | ||
TimeCostTooSmall | ||
TimeCostTooLarge | ||
MemoryCostTooSmall | ||
MemoryCostTooLarge | ||
TooFewLanes | ||
TooManyLanes | ||
PasswordPointerMismatch | ||
SaltPointerMismatch | ||
SecretPointerMismatch | ||
AssociatedDataPointerMismatch | ||
MemoryAllocationError | ||
FreeMemoryCallbackNull | ||
AllocateMemoryCallbackNull | ||
IncorrectParameter | ||
IncorrectType | ||
InvalidAlgorithm | ||
OutputPointerMismatch | ||
TooFewThreads | ||
TooManyThreads | ||
NotEnoughMemory | ||
EncodingFailed | ||
DecodingFailed | ||
ThreadFailure | ||
DecodingLengthFailure | ||
VerificationFailure | ||
UnknownErrorCode | ||
} | ||
|
||
/// Create a new hasher with default settings based on the | ||
/// [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id). | ||
/// | ||
/// Note: if you change the algorithm to Argon2i, you will need to change the | ||
/// `memory_cost` to 12_228 (12 mebibytes) or less for performance reasons. | ||
/// | ||
/// The `hasher_argon2i` function is provided with the recommended settings for | ||
/// Argon2i. | ||
pub fn hasher() -> ArgusHasher { | ||
ArgusHasher( | ||
Argon2id, | ||
2, | ||
// 19 mebibytes | ||
19_456, | ||
1, | ||
32, | ||
) | ||
} | ||
|
||
/// Create a new hasher with default settings based on the | ||
/// [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id) for | ||
/// Argon2i. | ||
pub fn hasher_argon2i() -> ArgusHasher { | ||
ArgusHasher( | ||
Argon2i, | ||
3, | ||
// 12 mebibytes | ||
12_228, | ||
1, | ||
32, | ||
) | ||
} | ||
|
||
/// Set the algorithm to use for the hasher. | ||
pub fn algorithm(hasher: ArgusHasher, algorithm: Argon2Algorithm) -> ArgusHasher { | ||
ArgusHasher(..hasher, algorithm: algorithm) | ||
} | ||
|
||
/// Set the time cost to use for the hasher. | ||
pub fn time_cost(hasher: ArgusHasher, time_cost: Int) -> ArgusHasher { | ||
ArgusHasher(..hasher, time_cost: time_cost) | ||
} | ||
|
||
/// Set the memory cost to use for the hasher. | ||
pub fn memory_cost(hasher: ArgusHasher, memory_cost: Int) -> ArgusHasher { | ||
ArgusHasher(..hasher, memory_cost: memory_cost) | ||
} | ||
|
||
/// Set the parallelism to use for the hasher. | ||
pub fn parallelism(hasher: ArgusHasher, parallelism: Int) -> ArgusHasher { | ||
ArgusHasher(..hasher, parallelism: parallelism) | ||
} | ||
|
||
/// Set the hash length to use for the hasher. | ||
pub fn hash_length(hasher: ArgusHasher, hash_length: Int) -> ArgusHasher { | ||
ArgusHasher(..hasher, hash_length: hash_length) | ||
} | ||
|
||
/// Hash a password using the provided hasher. | ||
/// | ||
/// ## Examples | ||
/// | ||
/// ```gleam | ||
/// import argus | ||
/// | ||
/// let assert Ok(hashes) = | ||
/// argus.hasher() | ||
/// |> argus.algorithm(argus.Argon2id) | ||
/// |> argus.time_cost(3) | ||
/// |> argus.memory_cost(12228) | ||
/// |> argus.parallelism(1) | ||
/// |> argus.hash_length(32) | ||
/// |> argus.hash("password", gen_salt()) | ||
/// | ||
/// let assert Ok(True) = argus.verify(hashes.encoded_hash, "password") | ||
/// ``` | ||
pub fn hash( | ||
hasher: ArgusHasher, | ||
password: String, | ||
salt: String, | ||
) -> Result(ArgusHash, HashError) { | ||
let result = | ||
jargon_hash( | ||
password, | ||
salt, | ||
hasher.algorithm, | ||
hasher.time_cost, | ||
hasher.memory_cost, | ||
hasher.parallelism, | ||
hasher.hash_length, | ||
) | ||
case result { | ||
Ok(#(raw_hash, encoded_hash)) -> Ok(ArgusHash(raw_hash, encoded_hash)) | ||
Error(error) -> Error(error) | ||
} | ||
} | ||
|
||
/// Verify a password using the provided encoded hash. | ||
pub fn verify(encoded_hash: String, password: String) -> Result(Bool, HashError) { | ||
jargon_verify(encoded_hash, password) | ||
} | ||
|
||
/// Generate a random salt of at least 64 bytes. | ||
@external(erlang, "argus_nif", "gen_salt") | ||
pub fn gen_salt() -> String | ||
|
||
@external(erlang, "argus_nif", "hash") | ||
fn jargon_hash( | ||
password: String, | ||
salt: String, | ||
algorithm: Argon2Algorithm, | ||
time_cost: Int, | ||
memory_cost: Int, | ||
parallelism: Int, | ||
hash_length: Int, | ||
) -> Result(#(BitArray, String), HashError) | ||
|
||
@external(erlang, "jargon", "verify") | ||
fn jargon_verify( | ||
encoded_hash: String, | ||
password: String, | ||
) -> Result(Bool, HashError) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
-module(argus_nif). | ||
|
||
-export([hash/7, gen_salt/0]). | ||
|
||
hash(Password, Salt, Algorithm, TimeCost, MemoryCost, Parallelism, HashLength) -> | ||
case jargon:hash(Password, Salt, Algorithm, TimeCost, MemoryCost, Parallelism, HashLength) | ||
of | ||
{ok, RawHash, EncodedHash} -> | ||
{ok, {RawHash, EncodedHash}}; | ||
{error, Error} -> | ||
{error, Error} | ||
end. | ||
|
||
gen_random_int(Min, Max) -> | ||
crypto:strong_rand_bytes(4), | ||
<<Int:32/integer>> = crypto:strong_rand_bytes(4), | ||
Int rem (Max - Min) + Min. | ||
|
||
%% Use a min of 64 bytes rather than the default of 32 | ||
%% for additional security. | ||
gen_salt() -> | ||
Bytes = gen_random_int(64, 1024), | ||
base64:encode(crypto:strong_rand_bytes(Bytes)). | ||
|
||
|
Oops, something went wrong.