From 87a31c5c9b903517ec0317d2a331be36f2ea5051 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 14 Jan 2021 14:49:39 +0100 Subject: [PATCH] Password -> Password.Pbkdf2 --- lib/pleroma/password/pbkdf2.ex | 55 +++++++++++++++++++++++++++ test/pleroma/password/pbkdf2_test.exs | 35 +++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 lib/pleroma/password/pbkdf2.ex create mode 100644 test/pleroma/password/pbkdf2_test.exs diff --git a/lib/pleroma/password/pbkdf2.ex b/lib/pleroma/password/pbkdf2.ex new file mode 100644 index 000000000..747bc1d1d --- /dev/null +++ b/lib/pleroma/password/pbkdf2.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Password.Pbkdf2 do + @moduledoc """ + This module implements Pleroma.Password.Pbkdf2 passwords in terms of Plug.Crypto. + """ + + alias Plug.Crypto.KeyGenerator + + def decode64(str) do + str + |> String.replace(".", "+") + |> Base.decode64!(padding: false) + end + + def encode64(bin) do + bin + |> Base.encode64(padding: false) + |> String.replace("+", ".") + end + + def verify_pass(password, hash) do + ["pbkdf2-" <> digest, iterations, salt, hash] = String.split(hash, "$", trim: true) + + salt = decode64(salt) + + iterations = String.to_integer(iterations) + + digest = String.to_atom(digest) + + binary_hash = + KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64) + + encode64(binary_hash) == hash + end + + def hash_pwd_salt(password, opts \\ []) do + salt = + Keyword.get_lazy(opts, :salt, fn -> + :crypto.strong_rand_bytes(16) + end) + + digest = Keyword.get(opts, :digest, :sha512) + + iterations = + Keyword.get(opts, :iterations, Pleroma.Config.get([:password, :iterations], 160_000)) + + binary_hash = + KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64) + + "$pbkdf2-#{digest}$#{iterations}$#{encode64(salt)}$#{encode64(binary_hash)}" + end +end diff --git a/test/pleroma/password/pbkdf2_test.exs b/test/pleroma/password/pbkdf2_test.exs new file mode 100644 index 000000000..4acbda939 --- /dev/null +++ b/test/pleroma/password/pbkdf2_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Password.Pbkdf2Test do + use Pleroma.DataCase, async: true + + alias Pleroma.Password.Pbkdf2 + + test "it generates the same hash as pbkd2_elixir" do + # hash = Pleroma.Password.Pbkdf2.hash_pwd_salt("password") + hash = + "$pbkdf2-sha512$1$QJpEYw8iBKcnY.4Rm0eCVw$UBPeWQ91RxSv3snxsb/ZzMeG/2aa03c541bbo8vQudREGNta5t8jBQrd00fyJp8RjaqfvgdZxy2rhSwljyu21g" + + # Use the same randomly generated salt + salt = Password.decode64("QJpEYw8iBKcnY.4Rm0eCVw") + + assert hash == Password.hash_pwd_salt("password", salt: salt) + end + + @tag skip: "Works when Pbkd2 is present. Source: trust me bro" + test "Pleroma.Password.Pbkdf2 can verify passwords generated with it" do + hash = Password.hash_pwd_salt("password") + + assert Pleroma.Password.Pbkdf2.verify_pass("password", hash) + end + + test "it verifies pbkdf2_elixir hashes" do + # hash = Pleroma.Password.Pbkdf2.hash_pwd_salt("password") + hash = + "$pbkdf2-sha512$1$QJpEYw8iBKcnY.4Rm0eCVw$UBPeWQ91RxSv3snxsb/ZzMeG/2aa03c541bbo8vQudREGNta5t8jBQrd00fyJp8RjaqfvgdZxy2rhSwljyu21g" + + assert Password.verify_pass("password", hash) + end +end