Signal Desktop
Signal Desktop — SQLCipher Database
Overview
Signal Desktop stores all message data in a SQLCipher-encrypted SQLite database at ~/Library/Application Support/Signal/sql/db.sqlite. The database is fully accessible once the decryption key is extracted from the adjacent config.json file. After decryption, the database is standard SQLite with a rich relational schema spanning messages, conversations, contacts, cryptographic session state, and application configuration.
SQLCipher 4 Encryption Parameters
Signal Desktop uses SQLCipher 4 with the following parameters:
| Parameter | Value | Notes |
|---|---|---|
| Cipher | AES-256-CBC | Industry-standard symmetric encryption |
| KDF algorithm | PBKDF2-HMAC-SHA512 | Key derivation from the hex key |
| KDF iterations | 256,000 | High iteration count for brute-force resistance |
| Page size | 4,096 bytes | Every page encrypted independently |
| HMAC algorithm | SHA-512 | Page integrity verification |
| SQLCipher version | 4 | Default parameters differ from SQLCipher 3 |
Key Extraction from config.json
The decryption key is stored in ~/Library/Application Support/Signal/config.json as a 64-character hexadecimal string in the key field:
{
"key": "a3f8c2d14e5b9f07d823a1c4b6e9f012345678901234567890abcdef12345678"
}
The key may optionally appear with a 0x prefix. When passing the key to SQLCipher, strip the prefix if present and format it as x'<64_hex_chars>'.
Key length validation: The raw key must be exactly 64 hexadecimal characters (representing 32 bytes / 256 bits). A key of any other length indicates a corrupted or non-standard configuration.
Opening the Database
With the sqlcipher CLI
-- Launch sqlcipher against the database file
-- (copy the file first to avoid modifying the original)
sqlcipher db.sqlite
-- Apply the decryption key
PRAGMA key = "x'a3f8c2d14e5b9f07d823a1c4b6e9f012345678901234567890abcdef12345678'";
PRAGMA cipher_page_size = 4096;
-- Verify decryption succeeded (should return a non-empty result)
SELECT count(*) FROM sqlite_master WHERE type = 'table';
With DB Browser for SQLite
- Open DB Browser for SQLite
- Select "Open Database" and choose the
db.sqlitefile - In the password dialog, select "Raw key" mode and enter
0xfollowed by the 64 hex chars fromconfig.json
WAL Mode Consideration
Signal operates with SQLite WAL (Write-Ahead Logging) enabled. When copying the database for analysis, always copy all three files together:
db.sqlite— main database filedb.sqlite-wal— write-ahead log (may contain uncommitted or recently committed data)db.sqlite-shm— shared memory file (required for WAL recovery)
Failure to include the WAL file may result in missing recently written messages.
Database Tables
| Table | Purpose |
|---|---|
conversations | Contact and group conversation metadata |
messages | All sent and received messages |
message_reactions | Emoji reactions to messages (legacy; modern versions use the messages json column) |
sessions | Signal Protocol session state (per-contact ratchet sessions) |
identityKeys | Known Curve25519 identity public keys for contacts |
preKeys | One-time pre-key bundle components |
signedPreKeys | Signed pre-key bundle components |
senderKeys | Group sender key records |
items | Application configuration key-value store |
attachments | Attachment metadata (path references to files on disk) |
sticker_packs | Installed sticker pack metadata |
badges | Signal subscription badge data |
emojis | Recently used emoji tracking |
jobs | Background job queue (pending sends, retries) |
conversations
The central registry for all 1:1 and group conversations. See Conversations for the full column reference and analysis guidance.
messages
The primary message table. Contains all sent, received, and system messages. Key columns include id, conversationId, body, type, sent_at, received_at, sourceUuid, and isErased. See Messages for the full column reference.
sessions
| Column | Type | Description |
|---|---|---|
id | TEXT | Session identifier (UUID format) |
conversationId | TEXT | Associated conversation UUID |
json | TEXT | Full session state JSON blob (contains ratchet state — not extracted) |
The session table tracks active Signal Protocol sessions. Each session represents a Double Ratchet state with a specific contact or group. macfor collects session metadata (id, conversationId, timestamp) but does not extract the private ratchet state blob.
identityKeys
| Column | Type | Description |
|---|---|---|
id | TEXT | Contact identifier (UUID or phone number) |
publicKey | BLOB | Curve25519 identity public key (32 bytes) |
firstUse | INTEGER | Unix ms timestamp of first key exchange |
timestamp | INTEGER | Unix ms timestamp of last key update |
verified | INTEGER | Verification status (0=default, 1=verified, 2=unverified) |
The identity key table is critical for safety number forensics. Each row represents the known public key for a contact. Key changes — recorded in the messages table as type = 'keychange' — indicate when a contact's identity changed, which can signal a new device installation, Signal reinstall, or potential account compromise.
preKeys
| Column | Type | Description |
|---|---|---|
id | TEXT | Pre-key identifier |
publicKey | TEXT | Base64-encoded Curve25519 public key |
json | TEXT | Full key object JSON (private key excluded by macfor) |
signedPreKeys
| Column | Type | Description |
|---|---|---|
id | TEXT | Signed pre-key identifier |
publicKey | TEXT | Base64-encoded Curve25519 public key |
json | TEXT | Full key object JSON (private key excluded by macfor) |
senderKeys
| Column | Type | Description |
|---|---|---|
id | TEXT | Sender key identifier |
senderId | TEXT | Sender UUID or group+sender identifier |
json | TEXT | Full sender key state JSON |
Sender keys are used for group messaging. Each member of a group distributes a sender key, enabling efficient group message decryption.
items
The items table is a key-value configuration store. Forensically relevant keys include registration state, linked device information, and application version.
| Column | Type | Description |
|---|---|---|
id | TEXT | Key name |
json | TEXT | Value as a JSON object |
Notable items keys:
| Key | Description |
|---|---|
number_id | Registered phone number (E.164 format) |
uuid_id | Signal UUID of the account owner |
device_name | Display name of this linked device |
version | Signal Desktop application version |
registrationIdMap | Registration IDs for linked devices |
Key SQL Queries
List All Tables
SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;
Count Messages by Conversation
SELECT
c.name,
c.e164,
c.profileName,
count(m.id) AS message_count
FROM conversations c
LEFT JOIN messages m ON m.conversationId = c.id
GROUP BY c.id
ORDER BY message_count DESC;
Full Message Timeline
SELECT
datetime(m.sent_at / 1000, 'unixepoch') AS sent_utc,
m.type,
m.body,
c.name AS conversation,
m.sourceUuid AS sender_uuid
FROM messages m
JOIN conversations c ON c.id = m.conversationId
ORDER BY m.sent_at;
Identity Key Change History
SELECT
datetime(sent_at / 1000, 'unixepoch') AS event_time,
conversationId,
sourceUuid,
source
FROM messages
WHERE type = 'keychange'
ORDER BY sent_at;
Registration Information
SELECT id, json FROM items
WHERE id IN ('number_id', 'uuid_id', 'device_name', 'version')
ORDER BY id;
Contacts with Verification Status
SELECT
id,
publicKey,
datetime(firstUse / 1000, 'unixepoch') AS first_use_utc,
datetime(timestamp / 1000, 'unixepoch') AS last_updated_utc,
CASE verified
WHEN 0 THEN 'default'
WHEN 1 THEN 'verified'
WHEN 2 THEN 'unverified'
ELSE 'unknown'
END AS verification_status
FROM identityKeys
ORDER BY timestamp DESC;
Schema Stability
Signal Desktop is an Electron application with no guaranteed schema stability between releases. Signal's engineering team may add, rename, or remove columns in any version update. The macfor collector performs runtime column discovery using PRAGMA table_info() before querying each table, enabling it to handle both older and newer schema versions gracefully.
When running manual SQL queries, use PRAGMA table_info(<table_name>) to verify column availability before constructing queries that depend on specific columns.
Forensic Significance of Session and Identity Data
The session and identity key tables provide evidence that is not available from the message content alone:
- Session existence: An active session in the
sessionstable proves that the device successfully established an encrypted session with a contact, even if messages were deleted. - Key change events:
keychangerecords in themessagestable are system-generated entries that cannot be deleted by the user through the normal Signal interface. They provide a timestamped record of when a contact's identity key changed. - Verification history: The
verifiedcolumn inidentityKeysrecords whether the user manually verified a contact's safety number — a deliberate act indicating deliberate trust establishment. - Pre-key exhaustion: An empty
preKeystable may indicate the account was re-registered or the Signal Desktop data directory was partially cleared.