I want to be able to securely (1) create and (2) validate a PIN, typed by a user primarily in a mobile app. There is no 'typical' password, although there is another 2nd factor.

The starting point would be something based on OWASP standards for passwords:

  1. get salt_1 (send to user device)
  2. hash PIN on device using salt_1 and bcrypt
  3. send over HTTPS to backend
  4. get salt_2
  5. hash the received hash using salt_2 and bcrypt
  6. save resulting hash or compare against previously saved hash

Now, this scenario obviously has a limited viability and robustness due to low PIN entropy (4 digits), so typical approach for passwords requires at least some alterations.

My ideas for additional security measures would be:

  • salts should be treated as secret and stored securely and separately from hashed PINs (as in this answer )? If yes, sending it, even briefly, to user device is compromising this security a lot? Can I do something more here?
  • limiting retrials of PIN validation appears to be more crucial than with passwords

Is there any other approach or enhancements to existing approach that I should consider?

There are, obviously, at least 3 perspectives: backend leak, MITM and device leak and the last one is the one I am least confident with, but maybe I should leave this to the user if he "chooses" to compromise his device security?

  • $\begingroup$ Do you really need to have a PIN (4-8 digits) or any authentication method (drawing, biometric) used by ? If it's a mobile app, it is possible to use native secure (key) store. $\endgroup$ – gusto2 Mar 5 at 13:17

PIN check is bound to rely on trusted device or security by obscurity.

If a 4-digit Personal Identification Number is stored and validated in an device (possibly composite, like combination of server and mobile device) of known construction, and an adversary acquires/extracts all the data stored in the device, including the (possibly encrypted or hashed) PIN and try counter, then that adversary is able to find the PIN with at most 10000 times the effort/energy it takes to try a wrong value of the PIN. Argument: the adversary simulates what the device does to test a candidate PIN, and when wrong just fires another simulation. The multiple PINs can be tried in parallel, thus the time it takes can be down to a single PIN try.

All ways around move the PIN and counter into a trusted device: security IC (Smart Card, Subscriber Identity Module, Trusted Platform Module...) for offline solution, or online server. The appropriate logic for a PIN check in a trusted device is:

  • if the error counter is below a threshold (typically 3)
    • increase the error counter in non-volatile memory, and if that succeeds
      • compare the proposed PIN to the stored PIN, and if it matches
        • reset the error counter to 0
        • authorize access until some event, such as a reset or a timer elapsing.

Historical section: Implementers sometime got that wrong. My deceased friend and Smart Card inventor Roland Moreno can be seen (audio in French) demoing a device that finds the PIN code of a bank card as was used in France in the early 1990s or slightly earlier. That particular one:

  • Used PROM (implemented as UV-EPROM with protection against UV erase) for the PIN error counter, and relied on a separate VPP power contact to program that, allowing an adversary to inhibit the writing by supplying too low a voltage.
  • Used an array of bits for the counter, with a scheme maximizing the number of PIN presentations considering the physical impossibility to erase a bit: the position of the programmed bit told if the count was reset or incremented, with the counter determined by the pattern on the frontier at the left of the right zone of unprogrammed bits (* = programmed, . not programmed, x = any)
    • xxxxxx**................ 0
    • xxxxxx**.*.............. 1
    • xxxxxx**.*.*............ 2
    • xxxxxx**.*.*.*.......... 3
  • Towards the goal of choosing which bit to program, performed the PIN comparison before the increment.
  • Leaked the result of that comparison thru a side channel. It happened that a byte characteristic of the PIN comparison was in a RAM buffer also used for communication, and readable after a warm reset that did not clear that byte, using a standard command.

I was told that a different model of card using comparable technology, and immune to this exact attack, still allowed to recover the result of PIN comparison before counter update, thru some timing attack.

Practical section:

Can I do something more ?

Two main cases (with the question about the second):

  1. The PIN needs to be checked locally on the mobile device: crypto mostly does not help!
    1. Offload the PIN check security functionality to the mobile device OS, which is in a better position than an application programmer to use the underlying hardware. If there's no such explicit service, it might still be possible to replace PIN authentication by the mere unlocking of the device, leaving it to the user to decide if that's with a PIN or otherwise.
    2. OR use security by obscurity in the mobile app.
    3. OR/AND, under the plausible assumption that (D)RAM is harder for an adversary to read than something more permanent, and if there is some form of more secure user login (even a password), and it is acceptable that the user makes use of that at each boot: hold the PIN hash encrypted/protected under that more secure user login credentials when on disk, and decipher it in RAM on boot. The makes the PIN as secure as the other login form when the user device is powered down to the point that (D)RAM content is lost.
  2. The PIN only needs to be checked in an online scenario: crypto helps but is not a silver bullet!
    1. Have the mobile device carefully validate the remote server in charge of PIN check and counter maintenance, e.g. thru a pinned TLS certificate, and send the PIN keyed-in by the user in a TLS tunnel with the server for checking. Additionally sending messages (e.g. PIN or a derivative) encrypted with another server public key devoted to PIN, along a server challenge preventing replay of the cryptogram, is theoretically redundant with TLS, but complicates attack. An alternative (with little if any benefit) is a PAKE such as SRP (tags: ) to send and store the PIN on the server.
    2. AND, use a long-term secret on the mobile side, if it is functionally possible that a user resetting the device or using a different one prevents login with PIN only. That can help protect from Denial of Service (where an attacker submits wrong PIN to prevent the rightful user to log-in), and make it impossible to find the actual PIN from server-side data (which is useful when a user reuses the same PIN on multiple servers).
      A possibility is that the user device draws and stores a fresh ECC private key and a random secret integer $S\in[0\ldots9999]$ each time the PIN is changed, with public key and Randomized Identification Number $\text{RIN}=(S+\text{PIN})\bmod10000$ stored on the server side. At each login the server sends a random challenge $C$, the device asks the PIN and gets $\text{PIN}'$, the device computes $\text{RIN}'=(S+\text{PIN}')\bmod10000$ and sends $\operatorname{Sign}(C\mathbin\|\text{RIN}')\mathbin\|\text{RIN}'$ encrypted to the server (see 2.1 for how). The server extracts the signature and $\text{RIN}'$, increments the transaction counter only if the signature matches that of $C\mathbin\|\text{RIN}'$, then in the affirmative tests if $\text{RIN}'=\text{RIN}$ to decide if the counter should be reset and access granted.
      Note: that provides functionality similar to that other answer with 35 bytes per user stored on the server side instead of several kilobytes.
    3. AND take computer security precautions so that the server does not leak its storage, which (combined with long-term secret on the mobile side if any) is bound to allow finding the PIN with little effort for a competent attacker (the mere ability to roll back a backup of the PIN state counters unavoidably makes such attack possible). Precautions must include trustworthy server administrators with robust access control to administration, encrypted offline backups (preferably with a public-key), and can include making the PIN/RIN check and counter update in or with the help of a security device such as an HSM or security IC.
    4. AND it can only help that the PIN or RIN of 2.1 is stored (on disk/NVM/(D)RAM/database..) on the server side as a password should be: hashed with a purposely-slow memory-hard entropy-stretching hash such as Argon2, together with random salt (stored along the hash) and semi-secret pepper, and ample security parameters (number of iterations, RAM). But again, that's not an even passable protection, for 10000 attempts is sure to allow to find the PIN (for who knows hash, salt and pepper, and long-term secret on the mobile side if any).
| improve this answer | |
  • $\begingroup$ Using security by obscurity... that was interesting. $\endgroup$ – Patriot Mar 4 at 15:51

Take advantage of the fact that you can store information on the client. If you're okay with requiring network access to perform authentication, then you can use a simple protocol to enforce incorrect-pin limits.

Store a random 128-bit key, $s$, on the client. On registration, client sends the server $\operatorname{Hash}(s, pin)$. Then sends 9999 hashes $\operatorname{Hash}(s, p)$ for each wrong pin, $p$. The server stores a hash of each individual hash.

Client sends $h = \operatorname{Hash}(s, x)$ after the user enters a pin, $x$.

  • If a failed login $\text{counter} \geq 4$, then reject the login attempt.
  • If $h$ is the correct hash, then approve the login attempt.
  • If $h$ is in the incorrect-hash list, then increment a failed-login counter.
  • If $h$ is not the correct hash and not in the incorrect-hash list, then reject the login without changing the failed-login counter.

This has the following security properties:

  • A malicious server cannot brute-force a user's pin because it should not know $s$.
  • A compromised client cannot brute-force a user's pin without a server compromise.
  • A third party cannot lock out a legitimate client by spamming the server incorrect pins because it does not know $s$.
  • Only if the server and the client is compromised, can an attacker freely crack a user's pin.
  • The server can invalidate individual (compromised, lost, or unusable) clients at anytime by deleting the hashes associated with a client.
  • A server can register additional new clients with a user account by allowing multiple hash lists per-user.
  • But safely registering new clients requires logging in with an old client (since the server cannot validate a pin on its own) and/or using a second authentication factor.

You should consider disallowing users from setting their own pins and always use a randomly generated one.

Because the pin is so short, you ought to invalidate a pin when the failed-login limit is reached and force a new pin to be used. The failed-login limit can be whatever you want.

You shouldn't rely on temporary lockouts or rate limiting. A patient attacker could exploit that if they gain access to $s$.

$\operatorname{Hash}$ needs only to be one way. It can be implemented as $\operatorname{Hash}(x, y) = \operatorname{SHA-512}(x\|y)$ assuming fixed-length $x$.

The server can truncate its hashes to 128-bits to save space. The server-side hash function can be truncated to 128-bits because only preimage-resistance is required of it.

Neither hash needs a salt or stretching because $s$ should be generated by a secure RNG.

| improve this answer | |
  • $\begingroup$ Nice anti-DNS technique. For best results, the connection to the server should be protected by TLS or similar, otherwise the correct login can be replayed. "approve the login attempt" should go with "reset failed login counter", $\endgroup$ – fgrieu Mar 4 at 20:35
  • $\begingroup$ I hope having reduced the storage requirements on the server side to 35 bytes while keeping all the advantages of your solution. See 2.2 together with 2.1 in my answer. $\endgroup$ – fgrieu Mar 5 at 7:24
  • $\begingroup$ @fgrieu Nice. After deciding I was over-complicating things (thinking about OPAQUE), I completely overlooked signatures. Problems don't immediately come to mind. $\endgroup$ – Future Security Mar 5 at 16:34

I think this post from Signal team also provides an approach to PIN security: https://signal.org/blog/improving-registration-lock/

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.