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:
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:
pwsh -ep bypass .\unprotect-signalconfig.ps1 which will show you your database key. Copy it by double-clicking the big string of letters and numbers then right-clicking.wsl --install Ubuntu and hit enter then wsl -d Ubuntu and hit enter to install and start an Ubuntu Linux distributionsudo apt install sqlcipher jq -y and hit enter to install the tools to query the database fileread KEY hit enter, then paste in your key from above and hit enter againfor 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).
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."
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.
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
This entry was posted on January 29, 2026, 5:52 am and is filed under Uncategorized. You can follow any responses to this entry through RSS 2.0. You can skip to the end and leave a response. Pinging is currently not allowed.