5G SUPI, SUCI and ECIES
The 3GPP has defined a nice subscriber’s identity protection scheme for 5G networks. This is a way for 5G handsets and terminals (or UE, in the 3GPP terminology) to encrypt the subscriber’s identity before sending it over-the-air. In this way, 5G radio sniffers, 5G IMSI-catchers, and more widely 5G networks without roaming agreement with the subscriber’s home-network, are not able to retrieve the entire subscriber’s permanent identity (i.e its IMSI).
In 5G, a new format has been defined for the subscriber identity: the SUPI (SUbscriber Permanent Identity), which is a container for an IMSI, or any other kind of network access identifier. We can illustrate such a format by using pycrate within an IPython interpreter:
In [1]: from pycrate_mobile.TS24501_IE import *
In [2]: imsi = '21210' + '0987654321' \
...: # let's illustrate this with a random IMSI from Monaco Telecom :)
In [3]: supi = FGSIDSUPI(val={ \
...: 'Fmt': FGSIDFMT_IMSI, \
...: 'Value': {'PLMN': imsi[:5], 'Output': imsi[5:]}}) \
...: # the MSIN part of the IMSI is set as the Output part of the SUPI
In [4]: show(supi)
### 5GSIDSUPI ###
<spare : 0>
<Fmt : 0 (IMSI)>
<spare : 0>
<Type : 1 (SUCI)>
### Value : 0 -> SUPI_IMSI ###
<PLMN : 21210 (Monaco.Monaco Telecom)>
<RoutingInd : 0000>
<spare : 0x0>
<ProtSchemeID : 0 (Null scheme)>
<HNPKID : 0>
### Output : 0 ###
<MSIN : 0987654321>
In [5]: supi.to_bytes() # this is the encoded SUPI
Out[5]: b'\x01\x12\xf2\x01\x00\x00\x00\x00\x90xV4\x12'
When the UE needs to send its SUPI over-the-air, in an encrypted form, an Elliptic-Curve public key of the subscriber’s home-network must be set in its SIM card. This public key is used together with an ECIES algorithm to encrypt the MSIN part of the subscriber’s IMSI (i.e. the Output part of the SUPI, as shown above), in order to form a SUCI (pronounced “sushi”, not kidding :). In order to exhibit the SUPI encryption and decryption steps, we need to use the CryptoMobile library. This is one of the open-source Python libraries maintained by P1 Security ; it implements Python wrappers for all the cellular encryption, integrity protection and authentication algorithms, allowing these to be readily available for pedagogic and prototyping purposes.
Back to our SUPI encryption, the first step is to create what will be considered here as the home-network public and private key-pair:
In [6]: from CryptoMobile.ECIES import *
In [7]: ec = X25519() \
...: # using Curve25519 elliptic curve, i.e. 3GPP ECIES profile A, see TS 33.501, annex C
In [8]: ec.generate_keypair()
In [9]: hn_pubkey = ec.get_pubkey() \
...: # this is the home-network pubkey, to be set in subscriber's SIM card
In [10]: hn_pubkey
Out[10]: b'.\\5\xabs\x8d0#\x9e}J\xf5\x8d.\xbb\x91\xcb\x82\xbcZ\xa6\xd6%\xf7H\xf6\xcd;|5e/'
In [11]: hn_privkey = ec.get_privkey() \
...: # this is the home-network private key, to be stored and processed securely in the UDM
In [12]: hn_privkey \
...: # in the real world, no one wants to disclose this key ;)
Out[12]: b'(pQ\xda[Ls\xbd_\x12n\xad<\x97{\xe59\xa2\xe0\x82\xfe\x9a\x80\xd52\x1aE\xac\xb9\xa0\xf9K'
The second step is to use the home-network public key, together with an ephemeral Elliptic-Curve public and private key-pair to be generated by the UE, in order to produce an ephemeral shared key. This shared key is then used to encrypt the SUPI into the SUCI form. The UE finally needs to send over-the-air its ephemeral public key, together with its encrypted MSIN and an associated MAC (Message Authentication Code):
In [13]: ue_ecies = ECIES_UE(profile='A')
In [14]: ue_ecies.generate_sharedkey(hn_pubkey)
In [15]: ue_pubkey, ue_encmsin, ue_mac = \
...: ue_ecies.protect(supi['Value']['Output'].to_bytes())
In [16]: suci = FGSIDSUPI(val={ \
...: 'Fmt': FGSIDFMT_IMSI, \
...: 'Value': { \
...: 'PLMN': '21210', \
...: 'ProtSchemeID': 1, \
...: 'Output': { \
...: 'ECCEphemPK': ue_pubkey, \
...: 'CipherText': ue_encmsin, \
...: 'MAC': ue_mac}}})
In [17]: show(suci)
### 5GSIDSUPI ###
<spare : 0>
<Fmt : 0 (IMSI)>
<spare : 0>
<Type : 1 (SUCI)>
### Value : 0 -> SUPI_IMSI ###
<PLMN : 21210 (Monaco.Monaco Telecom)>
<RoutingInd : 0000>
<spare : 0x0>
<ProtSchemeID : 1 (ECIES scheme profile A)>
<HNPKID : 0>
### Output : 1 -> SUCI_ECIESProfA ###
<ECCEphemPK : 0x0537e293a273304cf8f1e8841edb92eab47df8d25e04c74a594dde2a442eae60>
<CipherText : 0x1b37f726b9>
<MAC : 0x168b0aa539b1f39d>
In [18]: suci.to_bytes() \
...: # this is the actual 5G subscriber encrypted identity that would be send over-the-air by the UE
Out[18]: b'\x01\x12\xf2\x01\x00\x00\x01\x00\x057\xe2\x93\xa2s0L\xf8\xf1\xe8\x84\x1e\xdb\x92\xea\xb4}\xf8\xd2^\x04\xc7JYM\xde*D.\xae`\x1b7\xf7&\xb9\x16\x8b\n\xa59\xb1\xf3\x9d'
The third step happens in the home-network side. When received by any 5G serving-network, the PLMN identifier in the SUCI is used to route the signaling request to the correct subscriber’s home-network. There, the UDM (Unified Data Management, similar to an HSS in 4G networks) will have to decrypt the MSIN part, thanks to both the subscriber’s ephemeral EC public key, and the home-network EC private key:
In [19]: hn_ecies = ECIES_HN(hn_privkey, profile='A')
In [20]: rx_suci = FGSIDSUPI()
In [21]: rx_suci.from_bytes(b'\x01\x12\xf2\x01\x00\x00\x01\x00\x057\xe2\x93\xa2s0L\xf8\xf1\xe8\x84\x1e\xdb\x92\xea\xb4}\xf8\xd2^\x04\xc7JYM\xde*D.\xae`x1b7\xf7&\xb9\x16\x8b\n\xa59\xb1\xf3\x9d') \
...: # the home-network decodes the received SUCI buffer
In [22]: show(rx_suci)
### 5GSIDSUPI ###
<spare : 0>
<Fmt : 0 (IMSI)>
<spare : 0>
<Type : 1 (SUCI)>
### Value : 0 -> SUPI_IMSI ###
<PLMN : 21210 (Monaco.Monaco Telecom)>
<RoutingInd : 0000>
<spare : 0x0>
<ProtSchemeID : 1 (ECIES scheme profile A)>
<HNPKID : 0>
### Output : 1 -> SUCI_ECIESProfA ###
<ECCEphemPK : 0x0537e293a273304cf8f1e8841edb92eab47df8d25e04c74a594dde2a442eae60>
<CipherText : 0x1b37f726b9>
<MAC : 0x168b0aa539b1f39d>
In [23]: dec_msin = hn_ecies.unprotect( \
...: rx_suci['Value']['Output']['ECCEphemPK'].get_val(), \
...: rx_suci['Value']['Output']['CipherText'].get_val(), \
...: rx_suci['Value']['Output']['MAC'].get_val()) \
...: # and then decrypts the MSIN part of the SUCI
In [24]: from pycrate_mobile.TS24008_IE import decode_bcd
In [25]: dec_imsi = suci['Value']['PLMN'].decode() + decode_bcd(dec_msin) \
...: # the original IMSI is retrieved from the PLMN ID and decrypted MSIN
In [26]: dec_imsi # Victory !!!
Out[26]: '212100987654321'
After decrypting the encrypted part of the SUCI back into a clear-text MSIN, the home-network retrieves actually the complete original IMSI of the subscriber !