Secrets was designed to prioritize security from the very beginning. This entails the use of standard and robust encryption algorithms, as well as the implementation of every feature with end-to-end encryption in mind. This document provides an in-depth explanation of how Secrets handles user data and the reasoning behind its approach. For information regarding the security of previous versions of Secrets, you can click here.
The target audience for this document includes anyone with an interest in understanding how Secrets ensures the security of user data. While a basic understanding of encryption is recommended, it is not necessary. When discussing standard encryption algorithms or protocols, explanations of their provided properties will be provided as well.
If you're just seeking a brief overview of the encryption methods employed, here it is:
Secrets utilizes Libsodium for the majority of its cryptographic functions. Specifically, your data is encrypted with a 256-bit key that is itself encrypted using an Elliptic Curve key pair stored and safeguarded by Apple's keychain on your device. This key in the keychain can be secured with either biometric or password authentication, or both. Items are encrypted with XSalsa20+Poly1305, while file attachments employ XChaCha20+Poly1305.
If you're interested in a more more comprehensive explanation of how your information is protected, keep reading.
Secrets utilizes a proprietary persistence layer that has been designed based on the lessons learned from earlier versions and inspired by storage solutions like Git and Datomic.
The fundamental unit of storage in Secrets is called a Fact
, which consists of an entity, attribute, and value1. Here are some examples of facts:
Entity | Attribute | Value |
---|---|---|
Login/1C46B925… | username | Paulo |
Service/799FD6B0… | address | https://secrets.app |
Attachment/F7E463B4… | item | Login/1C46B925… |
This structure is similar to a conventional key-value storage system, with the addition of the entity value. In Secrets, the entity is composed of the item's type and a UUID.
When saving data, Secrets collects the current changes into a list of facts and appends them to the storage. This collection of facts is referred to as a Commit
.
The storage system is designed to be append-only, and facts are immutable. This does not mean that you cannot change a Login's username; rather, when you do, a new fact is created and written to disk.
The persistence layer extensively employs hashes for data indexing. Each commit is a Merkle tree, with each node of the tree transformed into a blob and stored on disk, with its hash value serving as the key2. Secrets uses BLAKE2b for its hashing requirements.
Disk storage will also coalesce these blobs in files named by the hex value of the first hash value byte. For instance, a blob with the hash 339de1115336b4…
would be stored in the file 33
. Similarly, files are organized into directories named after the first byte of their hash. You can check this by opening the Secrets.sct4 file package3.
Storage is divided into user-managed groups called Vaults
. A Vault can be categorized as:
Synced and Shared vaults are store on iCloud, CloudKit to be more precise, under separate zones. Each of these vaults has a one to one mapping to a CloudKit zone. Blobs are stored as regular CloudKit records and files as CloudKit assets.
As mentioned in the Overview, prior to writing, the persistence layer only deals with a blob and its hash, or a file and its hash. Blobs are encrypted using XSalsa20+Poly1305, while files are encrypted using XChaCha20+Poly1305. Both encryption algorithms are provided by Libsodium.
Hash values are also encrypted. They are encrypted using AES-128 in CBC mode. A new 256-bit key, specifically for encrypting these hashes, is derived from the storage key. The first 128 bits are utilized as an initialization vector, and the remaining bits serve as the AES key. Since the hash length is a multiple of the AES block size, no padding is necessary. This approach effectively conceals the hash value without requiring additional space. Hiding the hashes prevents the possibility of determining if a given value (e.g., a known file) is present in a Secrets database.
For iCloud-synced vaults, the encryption of blobs, files, and hashes mirrors the disk storage encryption. Further details regarding the sharing of vault keys among devices and members are provided in the following sections.
The 256-bit storage key, which protects each Secrets document, is encrypted using the device keys and can optionally be encrypted with one or more recovery keys.
Each device running Secrets generates a long-term key that uniquely identifies the device and provides encryption and authentication. Specifically, two keys are created: an identity Ed25519 key for signatures and an X25519 key for encryption.
These keys are stored in the device's operating system Keychain. Depending on the device, the keychain entry can be protected by various authentication options provided by the Keychain, including Face ID, Touch ID, passcode, passphrase, and Watch. You can learn more about the different authentication options in the "Securing your secrets" support page.
Unlike previous versions of Secrets, users can now utilize Secrets without ever setting up a passphrase. The keychain entry is also created with the protection set to kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, meaning the keys never leave the device, even during backups.
This means there are various scenarios in which a user could be locked out of Secrets, such as a malfunctioning Touch ID or Face ID sensor, unreadable fingerprint, loss of the device despite having backups, and so on.
To address this issue, Secrets allows the creation of a recovery key pair (X25519) that also encrypts the vault's 256-bit symmetric key. During creation, the private part of this key pair is printed on paper. The printed key can later be used to decrypt the document from a backup in case of sensor malfunction or other similar situations.
Recovery keys can be created for a specific document on each device. Alternatively, a Paper Device can serve as a recovery key and is automatically used by all of the user's devices.
In order to facilitate trust among devices owned by a user, Secrets implements a concept called the "Trust Chain." The Trust Chain serves as a tamper-proof ledger of operations1 and consists of a series of links.
struct Link {
var sequenceNo: Int
var operation: Operation
var previousLinkHash: Data
var linkHash: Data
var signature: Signature
}
By utilizing the Trust Chain, Secrets establishes a secure and verifiable history of operations, allowing devices to trust each other and enabling actions on behalf of the user.
The Device Trust Chain serves the purpose of establishing the identity of a user's devices. In this context, "identity" refers to the device's keys. The devices that are part of the Device Trust Chain are considered the user's "trusted devices."
The chain begins when a user sets up their first device. This device initiates a new Device Trust Chain by creating a Link with a sequence number of 0. The Link contains an AddDevice
operation, which includes the device's identity (the public key portion of the device keys), its APNS token (used for Apple Push Notification service), as well as its name and model. The device then signs this link and stores it in the private default zone of CloudKit.
It's important to note that this first link can only be created by a non-trusted device, as it is the device that starts the Device Trust Chain.
For any subsequent device that the user wishes to set up, authorization must be obtained from a currently trusted device in the Device Trust Chain. This ensures that only trusted devices can add new devices to the chain and maintain the integrity of the trust relationship.
The process of requesting authorization from a trusted device involves several steps:
Noise_NX_25519_ChaChaPoly_SHA256
variant of the protocol is employed.Any trusted device can easily revoke another trusted device by adding a new link with the RevokeDevice
operation.
The Identity Trust Chain is established alongside the Device Trust Chain and serves the purpose of managing the user's identity keys. Just as each device has its own set of keys, the user also possesses a set of signing and encrypting keys. The Identity Trust Chain keeps track of the user's current identity keys.
In addition to the identity keys, there is another set of transient keys used for encryption. These transient keys are automatically rotated every 30 days. The public part of the transient key-pair is not included in the trust chain but is stored in the user's public record on iCloud. It is signed with the user's current identity keys.
Both the identity keys and the transient keys are utilized when encrypting data intended for a specific user. This encryption process is further explained in the section on Vault Keys.
The identity chain, which consists of the user's identity keys, is stored in the public database of iCloud. Other users can access this chain to determine the current identity keys for a given user.
To facilitate the sharing of the private part of the user's identity keys and transient key-pair among trusted devices, the Libsodium library's crypto_box
function is employed. When a device rolls its transient key-pair or identity keys, it generates an encrypted version of the private keys for each of the other trusted devices, ensuring secure sharing and synchronization of these keys.
The Member Trust Chain serves the purpose of managing the current members of a specific vault. It operates in a similar manner to the Device Trust Chain, but instead of adding and revoking devices, it handles the addition and removal of users to the vault.
Just as the set of trusted devices shares the private part of the user's keys, the current members of a vault share the vault's symmetric key. This symmetric key is used for encrypting and decrypting the vault's contents.
The Member Trust Chain is stored in the same shared iCloud zone that is utilized for sharing the data of the vault. This ensures that the trust chain and the vault's data are stored and synchronized together for secure access and management by the authorized members of the vault.
Think blockchain but without the environmental impact. ↩
Currently Secrets uses iCloud for storing both Synced and Shared vaults. Each vault gets its own zone in the private database of the user that creates it. When sharing a vault, invited users will have access to it via their shared database on iCloud.
All operations related to sharing, adding and removing users are done exclusively through CloudKit APIs. Outer Corner cannot know, for example, who is a member of each vault, how many vaults are shared, etc.
The vault key in Secrets is a 256-bit symmetric key that is responsible for encrypting the vault. When encrypting the vault key for each member, Secrets employs a process similar to Signal's X3DH protocol, but without the use of one-time pre-keys.
Here's how the encryption of the vault key works in Secrets:
It's important to note that Secrets does not utilize one-time pre-keys from X3DH. These pre-keys are designed for use in chat protocols like Signal, where each message has its own unique one-time pre-key. However, in Secrets, the goal is to encrypt the vault key, not individual messages. Therefore, incorporating the complexity of one-time pre-keys would not provide significant benefits. It would be similar to including the entire history of exchanged messages within each message in Signal.
Existing data in the database remains protected with the old key, while new data saved to the database uses the new key. The database keeps track of all historic keys.
On the Mac, the Secrets Helper is a small utility app that Secrets starts automatically on launch. It's purpose is to facilitate a few different features on the Mac:
The Secrets Helper never accesses your data directly, nor does it ever ask you or handle your passphrase. It communicates with the main application via a unix socket. Communications between the browser extensions and the Secrets app are encrypted with the Noise Protocol using our open source wrapper for Noise-C.
More precisely, the NoisePSK_NN_25519_ChaChaPoly_SHA256
handshake format is used with a pre-shared key being generated by the main app and stored in the system keychain.
Similarly to the Secrets Helper, the browser extensions also never access your data directly. They serve two main purposes:
The extensions use Native Messaging to communicate with the Secrets Helper. More precisely, the extension code issues and receives commands to the browser launched executable included in Secrets. This communication is done simply via standard input and output. The executable itself then communicates with the Secrets Helper using another unix socket. Just like the communication between the main application and the helper, the browser extensions also use the Noise Protocol for encryption.
On Windows and other platforms the browser extensions can also remotely request secrets from Secrets for iOS. See Remote Access.
To allow for a better integration with operating system, iOS allows apps to include some extensions that itself will launch on behalf of the user. By design these extensions are not the same as your main app. Secrets includes extensions for Siri, Password AutoFill, Share and potentially others as iOS evolves.
Unlike the Mac, there's no communication between the extensions and the main app. So they all must read/write to a shared container where your data is stored. Although technically these are different apps, they share the same code with the main app. And for the most part they'll look and feel like the same app to you.
Starting with version 3.4.0, Secrets for iOS can also be used as a remote keychain. Similarly to how the browser extensions request secrets from a locally running instance of Secrets for Mac, remote requests also use the Noise protocol (with Noise_KK_25519_ChaChaPoly_SHA256
) to establish a secure end-to-end encrypted connection with Secrets for iOS. A WebSocket server in our control is used to bridge the two devices. Note however this server is stateless, it doesn't know anything about each of the connecting parties.
Here's a brief overview on how a request is sent:
Secrets 3.6.0 introduced Ad Hoc requests with the goal of making it easier to do one-time requests on shared computers. These type of requests exchange the pairing phase with showing a QR code for every request.