In the past few days Signal groups exploded in the news with revelations that Signal groups are the primary "ICE tracker" channels, may have dispatched Alex Pretti to his fatal encounter with DHS, and are under investigation by the FBI. As groups frequently hit the 1000-member capacity, concern about infiltration is rampant. Key facets of the groups include:
- Frequently rapidly changing display names and usernames and
- Members using aliases to avoid being identified by name.
Today we'll evaluate the security of those measures. To summarize, it doesn't look good for this threat model. Users can be tracked through changing names by other users and the FBI can get members' phones.
Anyone in or invited to a group can get ID's for all members that Signal or AWS can obtain phone numbers for and can be linked to Apple/Google ID's and that will remain constant through username or display name changes. Some of this has been theorized before, but to prove the concept, here's a step by step guide:
- Download Signal Desktop for Windows
- Install it and link to your phone app
- Quit signal (right-click on the tray icon and hit Quit. This is important!)
- Download and unzip a current PowerShell from https://github.com/PowerShell/PowerShell/releases/download/v7.5.4/PowerShell-7.5.4-win-x64.zip
- Right-click pwsh.exe and select run as administrator
- Save https://raw.githubusercontent.com/MatejKafka/PSSignalDecrypt/1c8aa1a4b5a29290f54dbd032c6228652aad8609/Unprotect-SignalConfig.ps1 in the same folder as pwsh.exe
- In your pwsh window, run
pwsh -ep bypass .\unprotect-signalconfig.ps1which will show you your database key. Copy it by double-clicking the big string of letters and numbers then right-clicking. - Type
wsl --install Ubuntuand hit enter thenwsl -d Ubuntuand hit enter to install and start an Ubuntu Linux distribution - Type
sudo apt install sqlcipher jq -yand hit enter to install the tools to query the database file - Type
read KEYhit enter, then paste in your key from above and hit enter again - Paste the following and hit enter. This will spit out a list of all groups you are part of and all their member ID's and also save it in "convomembers.txt" by enumerating the mapping between ID and display names, known phone numbers, and about information, then listing all group members matching them to profile information:
for USER in $(ls /mnt/c/Users/);do DB=/mnt/c/Users/$USER/AppData/Roaming/Signal/sql/db.sqlite ;if [ -f $DB ] ;then declare -A mappings;while IFS= read LINE ;do SID=$(echo $LINE|tr "," "\n"|grep serviceId|awk -F'"' '{print $4}');if [ ! -z "$SID" ] ;then mappings["$SID"]="$LINE";fi;done < <(echo "PRAGMA key = \"x'$KEY'\"; select json from conversations where type = 'private';"|sqlcipher $DB|tail -n +2|jq -cr '.|{serviceId, name, e164, profileName, profileFamilyName, about}');echo "PRAGMA key = \"x'$KEY'\"; select json from conversations where type <> 'private';" | sqlcipher $DB | tail -n +2 | jq -r '.name, .membersV2[].aci' 2>/dev/null| while IFS= read -r LINE ; do if [[ -v mappings["$LINE"] ]]; then echo "${mappings["$LINE"]}";else echo -e "\n$LINE";fi;done;fi;done| tee convomembers.txt
You'll see output like this:
Here, the three members of a group named "The Jedi Council" are listed. While the display name and about are the same as those visible in the signal UI, we can also see the "serviceID" which is the account ID for each.
Not shown here, but you can also identify which accounts are admins by grabbing the membersV2 array for a group conversation and looking for those serviceID's with a role of 2. (Above we simply grabbed all ID's). - Take the convo's and people you care about, and note their serviceID's.
Now what?
Grabbing the account data from Signal either directly or via the cloud provider they rely on (AWS) is trivial. The FBI can use National Security Letters, as explained by the EFF: "the FBI has issued hundreds of thousands of such letters seeking the private telecommunications and financial records of Americans without any prior approval from courts. In addition to this immense investigatory power, NSL statutes also permit the FBI to unilaterally gag recipients and prevent them from criticizing such actions publicly."
- Signal can do what the Signal Server does and do a simple DB query on their accounts table for the serviceId (a.k.a. aci or account id). This will return the account JSON for each. It will include a devices array which will include gcmId and/or apnId for each mobile device, and it will probably include the phone number as well (note how the number provided to tests is a plain phone number and that gets directly set to the number field).
- This information is also likely cached in redis (see source analysis below).
- It's a common task to grant access to an AWS hosted redis instances and probably trivial for Amazon to do so if they were served with an NSL, and not much more difficult to provide access to the underlying foundation DB as well.
- So despite being apparently legally required, it's unlikely that Signal's cooperation would even be technically required here. Even if Signal might want to devote expensive time and resources to fight an NSL, they may never get the chance. It's worth noting that AWS gets something like $10 billion from a single NSA contract alone.
- Google or Apple can then provide the phone number (and account owner name, email and likely location, birthday, payment details, etc.) for the gcmId or apnId respectively as well if the FBI wanted to pursue this avenue after obtaining them.
There are lots of other ways to get these ID's as well, including from mac or phones directly, but this is one convenient one.
Next, you can try changing your display name, about information, and username, or encouraging one of your contacts to do so. Then re-check any existing or new groups that are joined. You'll find the aci/serviceID remains the same. You can use this to track users even as they leave one chat, change username and display names, and join new chats.
If you are looking to maintain your own OPSEC, the most straightforward way to get a genuinely new identifier is to get a new phone with a new number and set up an entirely fresh Signal profile. But even this will not be secure against the FBI.
Source analysis
Sending a group message starts from the server side with /multi_recipient which for normal group messages (not stories) end up here at sendMultiRecipientMessage which does checkGroupSendToken to make sure the sender is part of the group and resolves recipients.
Resolving recipients is done by MessageUtil.java which grabs the ServiceIdentifier (which is just a UUID via ServiceIdentifier.java corresponding to the serviceId's above) and calls getByServiceIdentifierAsync which can check redis then getByAccountIdentifierAsync which does the DB query.
Then it calls the inner sendMultiRecipientMessage which calls messageSender's sendMultiRecipientMessage which gets service identifier and devices and which does pushNotificationManager.sendNewMessageNotification(account, deviceId, isUrgent) and sendNewMessageNotification wraps sendNotification.
The notifications are scheduled by scheduleBackgroundNotification called by sendNotification.
Those notifications get processed by another thread, in sendBackgroundNotification which is called by processScheduledBackgroundNotifications tokenType is APN or FCM final String pushToken = getPushToken(tokenType, device); ... sender.sendNotification(new PushNotification(pushToken, tokenType, PushNotification.NotificationType.NOTIFICATION, null, account, device, false)
getPushToken returns case FCM -> device.getGcmId(); case APN -> device.getApnId(); which are private strings
also, likewise with individual messages:
called by various methods including sendNewMessageNotification
called by MessageSender's sendMessages
called by sendIndividualMessage