My webapp takes in email addresses as user names at registration, verifies ownership by sending emails with confirmation links, etc.

I'm now looking for a secure design of a user deletion feature, such that despite the account being deleted, the system knows when somebody registers again under the same email address. An example of why I may need to know this is preventing giving a free trial period twice.

The immediate idea for that is obviously storing a hash of the user email in a table where entries don't get deleted. Let's call the table hashed_past_users.

However, as I thought about it more, problems emerge. In particular, it seems I can't use salt when calculating the hashes: when someone registers, I don't want to compute the hash with every possible salt already stored in hashed_past_users, but want to compute the hash once and compare with all entries in the table.

This however makes brute force attacks against the table much easier. Assume the attacker has the table offline because it leaked and can try to crack it on her/his fast hardware. Even if the hash function is run with a work factor making it compute for over the recommended 0.25 s per entry, the attacker can use some leaked list of real emails, and probably eventually get at least a few matching emails.

It obviously varies from system to system how critical it is that an attacker can't get any past emails, but in general I'd feel quite uncomfortable telling a user that all his data was deleted, and yet admitting that if a data leak happens, there's a real chance an attacker will be able to tell in the future that this or that email was a user at some point.

I thought about peppering the hashing process with an env variable, such that a data leak without knowing the variable is impossible to crack. But in the case of a complete system compromise this is still insufficient.

Any other ideas? Can this be at all done sufficiently well?

  • 1
    It seems you worry about your user will find out you didn't deleted his email as promised. Why not just tell him that you keep the hash of the his email for security needs ?
    – elsadek
    Mar 24 at 17:01

2 Answers 2


You can use one salt for the whole table.

You can encrypt a table of email addresses. The difficulty is to safely protect the encryption key. The best way to do it depends on your system architecture and your threat model.

You could combine the two approaches above and use a secret salt for the whole table, and secure this secret as you would for an encryption key.

In any case, you can reduce the risks by building this system behind a remote (REST) API, accessible only from your main application, that could only do two actions: add an e-mail address into its database, and answer yes/no to a request for the presence of an e-mail address in its database. How this system is implemented and its database is secured becomes less critical, because its only access would be through this API, and leaking its database would be far harder. This assume this subsystem is correctly isolated from the rest of the architecture.


If storing previously used email addresses was a critical business requirement, I'd implement a dedicated service to fulfil that requirement. The service would run on its own dedicated server hardware and operate in a similar way to a hardware security module (HSM). The idea is to have the service as a separate component with a bare minimum attack surface, so that even if an attacker compromises the web server they can't just recover the full "previous users" list.

The service would expose three externally-facing API calls: add, check, and remove. The add API would take the hash of an email address and add it to a database table along with the current timestamp. The check API would take the hash of an email address and return true if the hash is already in the database, otherwise false. The remove API would take a hash as an input and remove it from the database if it is found; this function would allow support staff to clear an erroneous entry. Finally, the service would have an internal process that removes entries older than a specific age. After this period the email could be used again, so the exact length of time is ultimately a question of what the business will accept, but it also minimises the impact of a breach.

As for the hashing itself, I'd use Argon2id with a static salt. The static salt prevents generic precomputation (i.e. a non-targeted database of hashes of email addresses), and Argon2id is designed to be slow to brute-force even with GPU acceleration. If an attacker gets access to the database that stores the hashed email addresses, and the salt, they will be able to check whether specific email addresses are in that list, and they will eventually be able to check a whole dictionary of known email addresses, but the process will be far slower than with a regular hash like SHA256.

The service could be implemented with a REST API over HTTPS, using client certificates for authentication. Rate limiting and alerting should be used to prevent an attacker who compromises the web server (or whatever other server consumes the service) from spamming it with thousands of email addresses to see if they are present.

The system itself should be locked down to present a bare minimum attack surface. Full-disk encryption should be used (this should be standard operating practice anyway), the host firewall should use a default block policy and only allow traffic from systems that require it, network firewall rules should replicate those rules for defence in depth, unnecessary services should be disabled, management interfaces (e.g. SSH) should be kept on their own isolated management network and be configured securely (latest protocol version only, no password logon, no remote logon for root, etc.), monitoring & alerting should be deployed, patches promptly installed, and all the other usual security hardening steps.

The question of whether you go to all this effort depends on how sensitive you (and your customers) consider the past registration status to be. That's ultimately a business decision.

The rest of the problem is one of communication and law. Make it clear in your terms of service / privacy policy that your systems log the minimum information necessary to prevent abuse, in exactly the same way as you'd keep web server logs with IP addresses in them for that purpose. Be clear about the maximum retention period of the information you store. Users will still be upset if their information is breached, but in my experience they're far less understanding if you surprise them with unexpected data retention that wasn't publicly documented. You may also want to consult a legal professional to determine the specifics of your statutory obligations in terms of privacy laws (e.g. GDPR, DPA, etc.) in your operating jurisdiction, particularly in relation to "right to be forgotten" laws and how they affect your intended goal here.

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.