Sign In with Password: a protocol to authenticate with username and password on the IC

I designed a protocol for username & password login on the Internet Computer that keeps the password private and prevents offline brute-force attacks. A working demo is available at password-demo-fhw.caffeine.xyz.

Do you see a need for login with username and password, or is Internet Identity already sufficient for your use cases and users?

Authentication steps

1. Seed Generation (Browser)

Argon2id derives a high-cost 32-byte seed from the user’s password and a username-based salt. This makes offline brute-force attacks significantly harder if the sign-in identity becomes accessible, for example through a node provider or HTTP gateway.

2. Sign-In Key Derivation (Browser)

The derived seed is used to generate an Ed25519 key pair, forming the sign-in identity. This identity is only used for authenticating the user during sign-in and is not used for direct dApp interactions.

3. Delegation Creation (Sign-In Canister)

A delegation is issued to a separate, randomly generated session key (analogous to Internet Identity). The sign-in identity authenticates this delegation without exposing the password. The sign-in canister can also handle user management tasks, such as checking username availability or linking usernames to sign-in identities. This enables helpful error messages like “username is taken” or “invalid password.”

4. Delegation Usage (dApp)

The dApp operates with the session key, which is completely independent of the user’s password. This ensures that offline brute-force attacks on the password are impossible during normal dApp usage.

Password Safety Considerations

Passwords are never perfect, and weak or common choices could reduce security. However, this protocol addresses several traditional pitfalls of password-based login. By deriving a high-cost seed with Argon2id and separating sign-in keys from session keys, it makes brute-force attacks much more difficult. The frontend could still enforce basic checks, such as rejecting common or easily guessable passwords.

Feedback Welcome

I would appreciate any feedback, especially if you are interested in using this approach in an app or if you notice any potential security flaws.
You can share your thoughts here or through the feedback form at Feedback Gateway .

Hashing passwords is a good idea because it prevents storing them in plain text and reduces the immediate damage of a data leak. This only works well if every password has a unique salt, so attackers have to brute force each one individually.

In your system, users only need to remember their username and password. They aren’t asked to remember a random salt, which makes sense. The salt you’re using, "icpasswordlogin" + username, is better than a fixed salt because it prevents attackers from creating a single precomputed lookup table that works for all users.

Instead, they would need to generate a new table for each username, which is more work. However, since usernames are typically short and predictable, the total search space (username plus password) is still limited, which makes it feasible for an attacker to brute force offline if they know the username and public key.

The biggest issue isn’t lookup tables. It’s that if an attacker has both the username and the registered public key, which is likely public information in (and possibly outside) the canister, they can brute force the password offline. Depending on the strength of the password, this could take days or even less. The only real fix would be to increase password entropy, but longer passwords are harder for users to remember.

In the end, low entropy is low entropy. No matter how much hashing or key derivation you apply, you can’t turn a short, weak password into something truly strong. If you rely on passwords, the system is only as secure as the password users choose.

Thanks @sea-snake for taking a look at this and providing this feedback. In general I agree with these statements, but I want to highlight some details that make this different from normal server/client systems with password sign in.

For the IC, where do you hash the password? In most systems it is assumed that the SSL connection to the server is secure to send the password and hash it on the server. But this is not the case on the IC, where node provider and gateways can see the content of the messages, and therefore could extract the password before it is hashed. Alternatively, if we hash in the browser, the hashed password becomes and an attacker can sign in with the hash, even he doesn’t know the original password.

Therefore the proposed protocol uses a derived key pair for sign in.

This is the reason why I propose to not use the derived key directly in the identity to interact with dapps. Instead, the derived key is just used as a sign-in-key, so it won’t be used in the actual app, which would likely expose it.
The delegation identity is not connected to the sign in identity or the password in any way, it uses a separate random session key with a canister signature. I currently link this to the username, but it could also be any random identifier stored in the backend.
Of cause, node provider and gateways could still extract the sign-in-public-key from memory, but this at least limits the number of potential attackers. (TEEs could add an additional layer of protection in the future)

Exactly. The goal here is to make it as hard as possible for an attacker, and if the password has enough entropy, it should be secure.

One more defense layer I would like to add is some kind of rate limiting to prevent online password guessing. But I don’t know how this would be implemented on the IC, without also introducing a vector for DOS. E.g. if I limit password guesses per user, an attacker could prevent the legitimate user from signing in by just sending sign in requests for that username to continuously hit the rate limit.

Wouldn’t the authentication with the sign-in-key still rely on verifying a signature of this key with the related public key stored in the canister?

The risk here is that, once this public key is exposed (e.g. malicious subnet node or gateway), an attacker could do a brute force attack offline, bypassing any rate limiting.

The reason why passkeys and recovery phrases don’t have this same risk is due to the significantly higher entropy. Brute forcing becomes infeasible due to this high entropy, avoiding the need to rely on keeping the public key a secret.

Overall, the security parameters would probably be similar to storing e.g. API keys in a canister. It might be a somewhat acceptable security trade off if rotated frequently enough, but shouldn’t be relied upon for e.g. higher risk applications like defi.

Frequently rotating the salt would be a challenge though, since registering the new public key would rely on the user supplying the password again every once in a while.


Another sign-in method alternative to passkeys worth looking into would be DKIM email signatures. For example https://zk.email uses this to authenticate by email signature in combination with zero knowledge proofs.

The main challenges here are the complexity around DNS lookups from a canister and that not all email providers support DKIM. Also the UX would be different, users would need to respond to (or send) an email instead of following a magic link to authenticate.

Yes, the sign in key is stored in the canister, but not exposed in transactions, etc.
Providing a secure password with enough entropy is responsibility of the user. The frontend can encourage that, e.g. by requiring a minimum length. For a good password, brute force attacks are also impractical.

Storing API keys in a canister is significantly less secure than what I proposed here, because node provider can read that key directly. That would be equivalent of storing the plain text password.

Key rotation is a good point, that I forgot to mention. Password updates would also possible, because the delegation identity and sign-in-identity are separated. So we can just send the old and new signin-identity to the canister and update the mapping.

In the end it comes down to the question which tradeoff you want to make:

  • trust the user to choose a secure password, and risk low entropy
  • require hardware bound passkeys and pre-register each device you want to use, and ask users to learn how to use them
  • effectively handover your sign in keys to a third party (Google, Apple, MS, etc. with OAuth, or your email provider/DNS provider with DKIM), and hope that they don’t lock you out.

For Apps, where the user potentially get’s access to high value assets, passkeys is probably the best option. If I want users to sign into a puzzle game where users can win tiny prices, password sign in might be good enough and easier for the users.