scram.ex (2731B)
1 defmodule Postgrex.SCRAM do 2 @moduledoc false 3 4 @hash_length 32 5 @nonce_length 24 6 @nonce_rand_bytes div(@nonce_length * 6, 8) 7 @nonce_prefix "n,,n=,r=" 8 @nonce_encoded_size <<byte_size(@nonce_prefix) + @nonce_length::signed-size(32)>> 9 10 def challenge do 11 nonce = @nonce_rand_bytes |> :crypto.strong_rand_bytes() |> Base.encode64() 12 ["SCRAM-SHA-256", 0, @nonce_encoded_size, @nonce_prefix, nonce] 13 end 14 15 def verify(data, opts) do 16 server = 17 for kv <- :binary.split(data, ",", [:global]), into: %{} do 18 <<k, "=", v::binary>> = kv 19 {k, v} 20 end 21 22 {:ok, server_s} = Base.decode64(server[?s]) 23 server_i = String.to_integer(server[?i]) 24 25 pass = Keyword.fetch!(opts, :password) 26 salted_pass = hash_password(pass, server_s, server_i) 27 28 client_key = hmac(:sha256, salted_pass, "Client Key") 29 client_nonce = binary_part(server[?r], 0, @nonce_length) 30 31 message = ["n=,r=", client_nonce, ",r=", server[?r], ",s=", server[?s], ",i=", server[?i], ?,] 32 message_without_proof = ["c=biws,r=", server[?r]] 33 34 auth_message = IO.iodata_to_binary([message | message_without_proof]) 35 client_sig = hmac(:sha256, :crypto.hash(:sha256, client_key), auth_message) 36 proof = Base.encode64(:crypto.exor(client_key, client_sig)) 37 [message_without_proof, ",p=", proof] 38 end 39 40 defp hash_password(secret, salt, iterations) do 41 hash_password(secret, salt, iterations, 1, [], 0) 42 end 43 44 defp hash_password(_secret, _salt, _iterations, _block_index, acc, length) 45 when length >= @hash_length do 46 acc 47 |> IO.iodata_to_binary() 48 |> binary_part(0, @hash_length) 49 end 50 51 defp hash_password(secret, salt, iterations, block_index, acc, length) do 52 initial = hmac(:sha256, secret, <<salt::binary, block_index::integer-size(32)>>) 53 block = iterate(secret, iterations - 1, initial, initial) 54 length = byte_size(block) + length 55 hash_password(secret, salt, iterations, block_index + 1, [acc | block], length) 56 end 57 58 defp iterate(_secret, 0, _prev, acc), do: acc 59 60 defp iterate(secret, iteration, prev, acc) do 61 next = hmac(:sha256, secret, prev) 62 iterate(secret, iteration - 1, next, :crypto.exor(next, acc)) 63 end 64 65 # :crypto.mac/4 was added in OTP-22.1, and :crypto.hmac/3 removed in OTP-24. 66 # Check which function to use at compile time to avoid doing a round-trip 67 # to the code server on every call. The downside is this module won't work 68 # if it's compiled on OTP-22.0 or older then executed on OTP-24 or newer. 69 if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do 70 defp hmac(type, key, data), do: :crypto.mac(:hmac, type, key, data) 71 else 72 defp hmac(type, key, data), do: :crypto.hmac(type, key, data) 73 end 74 end