How to securely store and validate a 4-digit PIN used on a mobile device? 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:


get salt_1 (send to user device)
hash PIN on device using salt_1 and bcrypt
send over HTTPS to backend
get salt_2
hash the received hash using salt_2 and bcrypt
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?
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):


The PIN needs to be checked locally on the mobile device: crypto mostly does not help!


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.
OR use security by obscurity in the mobile app.
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.

The PIN only needs to be checked in an online scenario: crypto helps but is not a silver bullet!


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: pake srp) to send and store the PIN on the server.
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.
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.
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).