Packages
postgrex
0.16.1
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.0
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.13
0.15.12
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.13.0-rc.0
0.12.2
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
PostgreSQL driver for Elixir
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/postgrex/scram.ex
defmodule Postgrex.SCRAM do
@moduledoc false
@hash_length 32
@nonce_length 24
@nonce_rand_bytes div(@nonce_length * 6, 8)
@nonce_prefix "n,,n=,r="
@nonce_encoded_size <<byte_size(@nonce_prefix) + @nonce_length::signed-size(32)>>
def challenge do
nonce = @nonce_rand_bytes |> :crypto.strong_rand_bytes() |> Base.encode64()
["SCRAM-SHA-256", 0, @nonce_encoded_size, @nonce_prefix, nonce]
end
def verify(data, opts) do
server =
for kv <- :binary.split(data, ",", [:global]), into: %{} do
<<k, "=", v::binary>> = kv
{k, v}
end
{:ok, server_s} = Base.decode64(server[?s])
server_i = String.to_integer(server[?i])
pass = Keyword.fetch!(opts, :password)
salted_pass = hash_password(pass, server_s, server_i)
client_key = hmac(:sha256, salted_pass, "Client Key")
client_nonce = binary_part(server[?r], 0, @nonce_length)
message = ["n=,r=", client_nonce, ",r=", server[?r], ",s=", server[?s], ",i=", server[?i], ?,]
message_without_proof = ["c=biws,r=", server[?r]]
auth_message = IO.iodata_to_binary([message | message_without_proof])
client_sig = hmac(:sha256, :crypto.hash(:sha256, client_key), auth_message)
proof = Base.encode64(:crypto.exor(client_key, client_sig))
[message_without_proof, ",p=", proof]
end
defp hash_password(secret, salt, iterations) do
hash_password(secret, salt, iterations, 1, [], 0)
end
defp hash_password(_secret, _salt, _iterations, _block_index, acc, length)
when length >= @hash_length do
acc
|> IO.iodata_to_binary()
|> binary_part(0, @hash_length)
end
defp hash_password(secret, salt, iterations, block_index, acc, length) do
initial = hmac(:sha256, secret, <<salt::binary, block_index::integer-size(32)>>)
block = iterate(secret, iterations - 1, initial, initial)
length = byte_size(block) + length
hash_password(secret, salt, iterations, block_index + 1, [acc | block], length)
end
defp iterate(_secret, 0, _prev, acc), do: acc
defp iterate(secret, iteration, prev, acc) do
next = hmac(:sha256, secret, prev)
iterate(secret, iteration - 1, next, :crypto.exor(next, acc))
end
# :crypto.mac/4 was added in OTP-22.1, and :crypto.hmac/3 removed in OTP-24.
# Check which function to use at compile time to avoid doing a round-trip
# to the code server on every call. The downside is this module won't work
# if it's compiled on OTP-22.0 or older then executed on OTP-24 or newer.
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do
defp hmac(type, key, data), do: :crypto.mac(:hmac, type, key, data)
else
defp hmac(type, key, data), do: :crypto.hmac(type, key, data)
end
end