Client-side cryptography and canonicalization for Google Cloud Fraud Defense's private password leak check.
Google Cloud Fraud Defense uses canonicalized usernames.
- Everything after the first
@is trimmed. .'s are removed.- Every ascii character is downcased.
For example: J.Doe@gmail.com and jdoe@hotmail.com both equate to jdoe for leak checks.
Example Usage
project = "my_super_cool_gcp_project"
token = "my_super_secret_bearer_token"
# https://www.youtube.com/watch?v=gYs9nS8LlZ8
{:ok, verification} =
RecaptchaPasswordCheck.create("GabeN@valvesoftware.com", "MoolyFTW")
# Bring your own http/proto3 client
body = %{
"privatePasswordLeakVerification" => %{
"lookupHashPrefix" => Base.encode64(verification.lookup_hash_prefix),
"encryptedUserCredentialsHash" =>
Base.encode64(verification.encrypted_user_credentials_hash)
},
# "otherReCaptchaAssessmentFields" => "..."
}
response =
Req.post!("https://recaptchaenterprise.googleapis.com/v1/projects/#{project}/assessments",
json: body,
headers: [{"authorization", "Bearer " <> token}],
receive_timeout: 5_000
)
# Short of adopting `:protobuf` to restore proto3's presence semantics,
# the response shapes have to be spelled out by hand.
case response do
%{
status: 200,
body: %{
"privatePasswordLeakVerification" => %{
"reencryptedUserCredentialsHash" => reencrypted_user_credentials_hash,
"encryptedLeakMatchPrefixes" => encrypted_leak_match_prefixes
}
}
} ->
RecaptchaPasswordCheck.leaked?(
verification,
Base.decode64!(reencrypted_user_credentials_hash),
Enum.map(encrypted_leak_match_prefixes, &Base.decode64!/1)
)
%{status: 200, body: %{"privatePasswordLeakVerification" => _}} ->
false
%{status: status, body: body} ->
{:error, {status, body}}
endGet the token from :goth in an application or from gcloud auth print-access-token when trying this out by hand.
Summary
Functions
Defines the RecaptchaPasswordCheck struct.
Builds a RecaptchaPasswordCheck struct for a username and password.
Unblinds the response fields using the verification, returning true if the credentials were
found in a leak.
Functions
Defines the RecaptchaPasswordCheck struct.
Its fields are:
:username- the username as it was supplied.:canonical_username- that username after canonicalization, which is what every hash here derives from.:lookup_hash_prefix- four bytes holding the leading 26 bits of the canonical username's hash, zero-filled. It names the bucket of leaks the service searches, and is the only thing the service learns about the username.:encrypted_user_credentials_hash- the scrypt hash of the canonical username and password, blinded by multiplication on P-256 and encoded as a 33-byte compressed point. The service re-encrypts it under its own key without being able to read it.:private_key- the ephemeral scalar used for that blinding, and the only means of removing it from the reply. Never reused, never transmitted, and redacted when the struct is inspected.
Builds a RecaptchaPasswordCheck struct for a username and password.
Runs scrypt and an elliptic curve multiplication, so expect low tens of milliseconds.
Returns {:ok, verification}, or {:error, :empty_canonical_username} when canonicalization
consumes the username entirely — "@example.com" reduces to nothing, and a query on an empty
username would match on the password alone.
Raises when either credential is empty or not a binary, which is a caller error rather than a property of the input.
Unblinds the response fields using the verification, returning true if the credentials were
found in a leak.