13
$\begingroup$

I want to SHA256 hash phone numbers in order to hide them. Is this a good idea? Are there any other ways I could make this safe?

$\endgroup$
  • 10
    $\begingroup$ What are you doing with the phone numbers that you need to hide them? $\endgroup$ – Aman Grewal Jun 30 at 19:28
  • 6
    $\begingroup$ Remark (not an answer): there's a special thing with phone numbers. Typically, adding a suffix leaves the number functional. Sometime adding a space or other character, or some prefix, will do the same. E.g. where I live, 0123456789 is the same as 01234567890, 01234567891, 01 23 45 67 89, +33123456789. That makes blacklists based on phone numbers (hashed, encrypted, whatever) easy to circumvent unless there is a strong normalization of the phone numbers, and that's not easy. $\endgroup$ – fgrieu Jul 1 at 7:21
  • 1
    $\begingroup$ What is your actual problem? When someone is trying to hide a phone number this indicates they need more to hide. The articles below the answer How can frequency analysis be applied to modern ciphers? might be helpful to your real cause. $\endgroup$ – kelalaka Jul 1 at 10:47
  • 4
    $\begingroup$ @David Z: Kerckhoffs's (second) principle or Shannon's «the enemy knows the system» makes it necessary to assume adversaries know what you hash. Plus, the popular use of phone numbers as passwords makes it plausible they are tested when attacking a password database. $\endgroup$ – fgrieu Jul 1 at 11:47
  • 7
    $\begingroup$ Related Secure hashing when the input comes from a small space $\endgroup$ – kelalaka Jul 1 at 12:48
30
$\begingroup$

No, it is not a good idea to hash phone numbers. There are only a limited number of phone numbers, so it is pretty easy for an adversary to try and hash all of them. Then you can simply compare the hash of each with the stored hash. Generally you don't have to deal with all telephone numbers, only a subsection of phone numbers anyway (for a specific country or other group that is logically distinct).

You could use a slow password hash with salt and work factor, but that's only going to mean that time time required is multiplied by a large, constant value. It won't change the order of operations. If the subset is small enough it may not deter an adversary to perform all necessary calculations.

In this case you will probably need to encrypt the phone numbers instead. Or use a keyed hash such as HMAC. For both options you need to perform key management on the secret key though; it's not as easy as just hashing the number.

| improve this answer | |
$\endgroup$
  • 7
    $\begingroup$ Limited is not good for quantify. $2^{256}$ is also limited. I would rather say there are at most $10^{10}$ phone numbers ($\approx 2^{34}$), and actually less, ABC-XYZ-XXYY where usually ABC less than 10^{3}. $\endgroup$ – kelalaka Jun 30 at 19:50
  • 2
    $\begingroup$ Yeah, only a few billion, you don't really need too much calculations for that. And it could be that you just want to check if a few phone numbers are in there, in that case you might be able to count them on your fingers. $\endgroup$ – Maarten Bodewes Jun 30 at 20:24
  • 1
    $\begingroup$ @kelalaka The (significant) number space for phone numbers is 15 digits long. You are just looking at some local part of that. So it's more like 2^50... - The rest of your comment is valid, of course. $\endgroup$ – I'm with Monica Jul 1 at 7:14
  • 4
    $\begingroup$ @I'mwithMonica I'm not sure you can even say that much; libphonenumber claims some German numbers exceed the ITU-T 15-digit standard and some systems allow you to automatically dial internal extensions, which could be any length. On the flip side, an attacker knowing that the target number is in a particular region might have as few as 5 digits to guess. I don't think it's really meaningful to talk about the possible range of numbers without knowing more context of the application. $\endgroup$ – IMSoP Jul 1 at 11:03
  • 2
    $\begingroup$ @I'mwithMonica: Keep in mind that even if the total space of all possible phone numbers is quite large, an attacker may still be able to accomplish quite a lot by just assuming "some local part of that" space (especially since they'll know enough about the application to be able to guess which local part). If they manage to crack only half the phone numbers in the set, that's still probably a huge problem. $\endgroup$ – ruakh Jul 1 at 21:44
12
$\begingroup$

It is always a bad idea to hash data that has a limited set of length or characters.

A phone number in Germany for example has normally no more than 12 digits. The first digit is always a 0 and the vast majority of numbers is longer as 3 digits, as those are normally reserved for emergency services.

This effectively leaves us with 10^11-10^3 possible combinations. The amount of time required for brute forcing this amount of combinations greatly depends on the used algorithm.

When using MD5 which is absolutely insecure to use nowadays, cracked by 8x Nvidia GTX 1080's and Hashcat, this is done in less than 10 minutes. Unfortunately, according to my experience, there are still thousands if not millions of services, hashing even passwords with insecure algorithms.

E.g. when using bcrypt, you could slow this down by a factor of more than 2000, however, this would still be incredibly insecure. And normally, the cost needs to be set according to the backend perfomance requirements as well. If an attacker could guess the location of the phone numbers to crack, it would be a matter of seconds.

You have the same problem when trying to hash IP addresses, it's also not a secure way to hide the plaintext.

| improve this answer | |
$\endgroup$
  • 3
    $\begingroup$ Phone numbers in Germany do not always start with 01. That's only the case for mobile phone numbers. The numbers 2 through 9 are all used by area codes for land lines. There's a lot of detail in en.wikipedia.org/wiki/List_of_dialling_codes_in_Germany $\endgroup$ – Nzall Jul 1 at 12:48
  • $\begingroup$ @Nzall You are obviously correct. I only thought of mobile numbers! Still, quite a small set :-) $\endgroup$ – dmuensterer Jul 1 at 13:26
  • $\begingroup$ Phone numbers in Germany can, actually, be 3 digits long. They are either emergency services, then, or only locally dialable, though. But there are local area nets with (usually legacy) three digit phone numbers. $\endgroup$ – I'm with Monica Jul 1 at 20:57
  • 2
    $\begingroup$ A tenfold increase is a serious underestimate. I can compute SHA-512 at 36 MHash/s on my CPU, taking about four minutes to brute-force all possible phone numbers. $\endgroup$ – Mark Jul 1 at 21:40
  • 1
    $\begingroup$ @dmuensterer That's how bcrypt works, you must give it a cost setting. See gist.github.com/epixoip/… and gist.github.com/epixoip/… in that thread. $\endgroup$ – Schwern Jul 2 at 17:23
10
$\begingroup$

In the general sense, The problem is known as the small input space on the hash functions, and in short simple hashing won't be secure.

If you hash data ( here a phone number) and an attacker tries to find an input value that matches the hash value is called the pre-image attack. In a secure Cryptographic hash functions pre-image attack requires $\mathcal{O}(2^n)$-time where the $n$ is the output size of the hash function and in SHA256 $n=256$

If the input space is small, this gives an attacker a huge boost, that is they can only brute force the small space. If 10 digit phone numbers are stored then the attackers need to search only $\approx 2^{34}$-space and if 15 that can make only $\approx 2^{50}$. Even the last space is highly achievable with a good GPU, see the hashcat performance. Therefore one needs either a way to slow the attacker or make it harder.

  • To make the attack slower, slow and memory-hard hash functions can be preferred like the Scrypt or Argon2id. This amount can be adjusted according to the target's capabilities. For example, using 100K iteration will slow the attacker time 100K or will reduce their search space capabilities within a limited time approx by $2^{16}$. As upper computing power, the collective power of the Bitcoin miners can reach $\approx 2^{92}$ double SHA256 in a year. If your enemy has this power slowing will not help much.

    Another choice is using salt per data as stated in the answers together with slow and memory-hard hash functions. This will only slow the attack time and prevent pre-computed tables like the rainbow tables. The attacker's execution time will increase by the number of the target hashes.

  • To make it harder, HMAC can be preferred, This is a keyed hash function and can be initialized with SHA256, too. The attacker without the key has no luck to attack the hash value. Another way is encryption. Although the phone numbers are should be unique, if one uses ECB mode that can be used to mount some attack to identify the number. The attackers can register and enter a target phone number as their phone number to identify the target position on the database. Therefore, an Ind-CPA secure mode should be preferred like CBC or CTR.

Both HMAC and Encryption have an additional problem to be solved. The storage of the keys. For this Hardware Secure Modules (HSM) can be preferred. The keys cannot be extracted from the modules and the HMAC and Encryption can be performed over these devices. If the attackers access the application server that uses the HSM the only hope is that they have limited access to use the HSM as a slave.

Conclusion: Use encryption or HMAC. If one fears of the loss or access of the keys use HSM to store and execute the Encryption/HMAC on HSMs.

| improve this answer | |
$\endgroup$
7
$\begingroup$

As an alternative, you can salt the phone numbers to avoid pre-calculation attacks.

A known salt will help against an adversary who has already done a hash of all possible phone numbers but just adds one order of magnitude of work (the adversary just has to recalculate all the hashs with the salted phone numbers).

If you can keep the salt private raises the bar on brute force attacks (essentially you are adding the salt's bits of entropy to the entropy inherent in the phone numbers).

| improve this answer | |
$\endgroup$
  • 5
    $\begingroup$ Yes, this will avoid pre-calculation attacks using a salt, but this will not change the order of operations for brute forcing a limited set. And if you are applying a salt then using a password hash / PBKDF with an additional work factor adds to the complexity. $\endgroup$ – Maarten Bodewes Jul 1 at 7:38
  • $\begingroup$ As @MaartenBodewes already correctly stated, this won't make it much more secure because a limited, short set is always very easy to crack. $\endgroup$ – dmuensterer Jul 1 at 10:41
  • 5
    $\begingroup$ If you can keep the salt private you can just keep the hash private. $\endgroup$ – user253751 Jul 1 at 10:46
  • 6
    $\begingroup$ A secret salt is called a "pepper". Of course, you might as well encrypt with a secret key instead of using a secret pepper if you can guess the telephone number by trying all options once you have the secret. But yeah, it can be used, and a pepper might be agood idea. $\endgroup$ – Maarten Bodewes Jul 1 at 11:39
  • $\begingroup$ @dmuensterer depending on a length of a salt. First, salt is random different string for each phone number hashed (if it is secret fixed one, it's called pepper, as Maarten notes). So with good enough RNG and big enough salt, you can make it secure against precomputed rainbow tables (but not against brute forcing). Still, if they need them, it's better than storing them in plaintext, especially if there is a lot of them... $\endgroup$ – Matija Nalis Jul 1 at 12:18
2
$\begingroup$

An alternative is to encrypt the phone number as proposed in the previous answers. For example, Mobile connect identity service encrypts the MSISDN (aka phone number) using a specific algorithm.

This GSMA specification gives information about decoding the payload :

Following are the example of encrypted MSISDN passed:

  • with URL encoding:

      login_hint=ENCR_MSISDN%3A0bb3020c7758f34e012da3f0bf13dc7674b3a9527
      6e804388d5aae4a034fe442a65e03027d0651da3b0646df6c11d3c5d6f46879480b
      623bd5024d9e0879727f46fbd1e8f5383a115678ea638a4ba5399a2dd37138246e
      db06718bb44be98f5331a1331902d6333993642e2f25197961ee0b0a14ddf66083
      4d49f7f385d82cad5a12003cd8aa235a92b71589110d76df382eab80b12a8dfa6d0
      5b4ca548538ac4b09a2868448957604eb52b1ceecc89dfe836e7113e51645c2a14f
      ff900228a8475983435647e88552a96eb692685b12abfc7ae0ad2bc23d30b3c8d82
      8ca101e186455b4d618a8c9022662ee1c5b8ffea40defdb92a20dce39bdbedcbf78
      5a2e
    
  • The serving operator recognises the input of the encrypted MSISDN and decodes the base64 encoded data.

  • The serving operator applies their private key to decode the RSA coded data.

  • The decrypted string looks like 441234567890|dasd23231139dskdeirirewr0234043ekewrwe4034c.

  • The serving operator then extracts the initial (numeric) portion of the decrypted data as the MSISDN separated by (|) pipe and uses this for any relevant purpose in API services/user sign-in.

In this case, it uses RSA encryption, and the private key is only known by the Mobile Network Operator

So an implementation would be :

base64(encrypt(44123456789|randomString))
| improve this answer | |
$\endgroup$

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.