Skip to content

Commit 452a2ac

Browse files
committed
Add multiple hashing algorithms
1 parent 60fb93f commit 452a2ac

File tree

7 files changed

+117
-23
lines changed

7 files changed

+117
-23
lines changed

lib/password_example/hash.ex

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
defmodule PasswordExample.Hash do
2+
def partial_changeset password do
3+
algorithm = PasswordExample.HashingAlgorithmChoice.get()
4+
# todo: time
5+
{usecs_hashing_took, ret} = :timer.tc(&hash/2, [algorithm, password])
6+
Map.merge(
7+
%{password: nil, hashed_password: nil, salt: nil, hash_type: algorithm, useconds_hashing_took: usecs_hashing_took},
8+
ret
9+
)
10+
end
11+
defp hash :plaintext, password do
12+
%{password: password}
13+
end
14+
defp hash :sha1, password do
15+
%{hashed_password: get_sha1_hash(password)}
16+
end
17+
defp hash :sha1_with_salt, password do
18+
salt = make_salt()
19+
%{salt: salt, hashed_password: get_sha1_hash(password <> salt)}
20+
end
21+
defp hash :argon2id, password do
22+
%{hashed_password: Argon2.hash_pwd_salt(password)}
23+
end
24+
25+
def verify attempted_password, %{hash_type: :plaintext, password: password} do
26+
attempted_password == password
27+
end
28+
def verify attempted_password, %{hash_type: :sha1, hashed_password: hashed_password} do
29+
get_sha1_hash(attempted_password) == hashed_password
30+
end
31+
def verify attempted_password, %{hash_type: :sha1_with_salt, salt: salt, hashed_password: hashed_password} do
32+
get_sha1_hash(attempted_password <> salt) == hashed_password
33+
end
34+
def verify attempted_password, %{hash_type: :argon2id, hashed_password: hashed_password} do
35+
Argon2.verify_pass(attempted_password, hashed_password)
36+
end
37+
def verify _, %{hash_type: :argon2id} do
38+
Argon2.no_user_verify()
39+
end
40+
41+
defp get_sha1_hash password do
42+
:crypto.hash(:sha, password) |> Base.encode16()
43+
end
44+
defp make_salt(length \\ 12) do
45+
:crypto.strong_rand_bytes(length) |> Base.url_encode64 |> binary_part(0, length)
46+
end
47+
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
defmodule PasswordExample.HashingAlgorithmChoice do
2+
use Ecto.Schema
3+
# import Ecto.Changeset
4+
alias PasswordExample.{Repo, HashingAlgorithmChoice}
5+
6+
@choices [:plaintext, :sha1, :sha1_with_salt, :argon2id]
7+
def choices, do: @choices
8+
9+
schema "hashing_algorithm_choice" do
10+
field :choice, Ecto.Enum, values: @choices
11+
12+
timestamps()
13+
end
14+
15+
# @doc false
16+
# def changeset(hashing_algorithm_choice, attrs) do
17+
# hashing_algorithm_choice
18+
# |> cast(attrs, [:choice])
19+
# |> validate_required([:choice])
20+
# end
21+
22+
def get() do
23+
if existing = Repo.one(HashingAlgorithmChoice) do
24+
existing.choice
25+
else
26+
:plaintext
27+
end
28+
end
29+
def set(new_val) do
30+
if existing = Repo.one(HashingAlgorithmChoice) do
31+
Repo.update(Ecto.Changeset.change(existing, %{choice: new_val}))
32+
else
33+
Repo.insert(%HashingAlgorithmChoice{choice: new_val})
34+
end
35+
end
36+
end

lib/password_example/user.ex

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ defmodule PasswordExample.User do
44

55
schema "users" do
66
field :name, :string
7-
field :password, :string, virtual: true
7+
field :password, :string
88
field :hashed_password, :string
9-
field :seconds_hashing_took, :float
9+
field :salt, :string
10+
field :useconds_hashing_took, :integer
11+
field :hash_type, Ecto.Enum, values: PasswordExample.HashingAlgorithmChoice.choices
1012

1113
timestamps()
1214
end
@@ -25,7 +27,7 @@ defmodule PasswordExample.User do
2527

2628
def get_by_name_and_password(name, password) do
2729
user = Repo.get_by(User, name: name)
28-
if valid_password?(user, password), do: user
30+
if PasswordExample.Hash.verify(password, user), do: user
2931
end
3032

3133
@doc false
@@ -56,30 +58,18 @@ defmodule PasswordExample.User do
5658

5759
# We only need to hash the password if the password is being changed
5860
if password && changeset.valid? do
59-
{usecs_hashing_took, hashed_password} = :timer.tc(Argon2, :hash_pwd_salt, [password])
60-
61-
seconds_hashing_took = usecs_hashing_took / 1_000_000
62-
63-
IO.puts(seconds_hashing_took)
64-
61+
%{password: password, hashed_password: hashed_password, salt: salt, hash_type: hash_type, useconds_hashing_took: useconds_hashing_took} = PasswordExample.Hash.partial_changeset(password)
6562
changeset
63+
|> put_change(:password, password)
6664
|> put_change(:hashed_password, hashed_password)
67-
|> put_change(:seconds_hashing_took, seconds_hashing_took)
68-
|> delete_change(:password)
65+
|> put_change(:salt, salt)
66+
|> put_change(:hash_type, hash_type)
67+
|> put_change(:useconds_hashing_took, useconds_hashing_took)
6968
else
7069
changeset
7170
end
7271
end
7372

74-
def valid_password?(%User{hashed_password: hashed_password}, password) do
75-
Argon2.verify_pass(password, hashed_password)
76-
end
77-
78-
def valid_password?(_, _) do
79-
Argon2.no_user_verify()
80-
false
81-
end
82-
8373
## users PubSub
8474
@topic "users"
8575

lib/password_example_web/live/users_table_component.html.leex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
<thead>
44
<th>Name</th>
55
<th>Password</th>
6+
<th>Salt</th>
67
<th>Hashed Password</th>
78
</thead>
89
<tbody>
910
<%= Enum.map @users, fn user -> %>
1011
<tr>
1112
<td><%= user.name %></td>
1213
<td><%= user.password %></td>
14+
<td><%= user.salt %></td>
1315
<td><%= user.hashed_password %></td>
1416
</tr>
1517
<% end %>

lib/password_example_web/templates/login/show.html.eex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
<p>Your hashed password is "<b><%= @user.hashed_password %></b>"</p>
66
<% end %>
77

8+
<%= if @user.salt != nil do %>
9+
<p>Your salt is "<b><%= @user.salt %></b>"</p>
10+
<% end %>
11+
812
<%= if @user.password == nil do %>
913
<p>I don't know your password.</p>
1014
<% else %>
1115
<p>Your password is "<b><%= @user.password %></b>"</p>
1216
<% end %>
1317

14-
<%= if @user.seconds_hashing_took != nil do %>
15-
<p>Time to calculate password hash: <b><%= @user.seconds_hashing_took %> seconds.</b></p>
18+
<%= if @user.useconds_hashing_took != nil do %>
19+
<p>Time to hash password: <b><%= @user.useconds_hashing_took %> microseconds.</b></p>
1620
<% end %>

priv/repo/migrations/20210428013831_create_users.exs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ defmodule PasswordExample.Repo.Migrations.CreateUsers do
44
def change do
55
create table(:users) do
66
add :name, :string
7+
add :password, :string
78
add :hashed_password, :string
8-
add :seconds_hashing_took, :float
9+
add :salt, :string
10+
add :useconds_hashing_took, :integer
11+
add :hash_type, :string
912

1013
timestamps()
1114
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule PasswordExample.Repo.Migrations.CreateHashingAlgorithmChoice do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:hashing_algorithm_choice) do
6+
add :choice, :string
7+
8+
timestamps()
9+
end
10+
11+
end
12+
end

0 commit comments

Comments
 (0)