8 A deeper look at keys
It doesn’t matter how strong an encryption algorithm is if the keys used to encrypt the data are available to an attacker or easily guessed. This section describes not only the details of the encryption algorithms and protocols used, it also covers how and where keys are derived, generated, and handled.
Here we provide details of how we achieve what is described in “How vault items are secured” and “How vaults are shared securely.” It’s one thing to assert (like we have) that our design means we never learn your secrets, but it’s another to show how this is the case. As a consequence, this section will be substantially more technical than others.
8.1 Key creation
All keys are generated by the client using Cryptographically Secure Pseudo-Random Number Generators (CSPRNGs). These keys are ultimately encrypted with keys derived from user secrets. Neither user secrets nor unencrypted keys are ever transmitted.
| Platform | Method | Library |
|---|---|---|
| iOS/macOS | SecRandomCopyBytes() |
iOS/OS X Security |
| Windows | CryptGenRandom() |
Cryptography API: NG |
| Browser | getRandomValues() |
WebCrypto |
| Android | SecureRandom() |
java.security |
| CLI | crypto/rand |
Go standard crypto library |
The public/private key pairs are generated using WebCrypto’s crypto.generateKey as an RSA-OAEP padded RSA key, with a modulus length of 2048 bits and a public exponent of 65537.
The secret part of the Secret Key is generated by the client as we would generate a random password. Our password generation scheme takes care to ensure each possible character is chosen independently and with probability equal to any other. Secret Key characters are drawn from the character set {2-9, A-H, J-N, P-T, V-Z}.
An Elliptic Curve Digital Signature Algorithm (ECDSA) key is also created at this time. It’s not used in the current version of 1Password, but its future use is anticipated. The key is generated on curve P-256.
8.2 Key derivation
For expository purposes, it’s easiest to work backwards. The first section will discuss what a typical login looks like from an already enrolled user using an already enrolled client, as this involves the simplest instance of the protocol.
8.2.1 Deriving two keys from two secrets
Figure 8.1: The AUK is derived from the account password, 𝑝; Secret Key, k𝐴, and several non- secrets including the account ID, 𝐼, and a salt, 𝑠.
As discussed in “Account password and Secret Key,” 1Password uses two-secret key derivation (2SKD) so data we store cannot be used in brute-force cracking attempts against a user’s account password. The two secrets held by the user are their account password and Secret Key.
From those two user secrets the client needs to derive two independent keys. One is the key needed to decrypt their data, the other is the key needed for authentication. We’ll call the key needed to decrypt the data encryption keys (and, in particular, the user’s private key) the Account Unlock Key (AUK), and the key that’s used as the secret for authentication SRP-\(x\).
The processes for deriving each of these are similar, but involve different salts in the key derivation function. A user will have a salt used for deriving the AUK and a different salt used for deriving SRP-\(x\).
In both cases, the secret inputs to the key derivation process are the user’s account password and Secret Key. Non-secret inputs include salts, algorithm information, and the user’s email address.
8.2.2 Preprocessing the account password
Before the user’s account password is handed off to the slow hashing mechanism, it undergoes a number of preparatory steps. The details and rationale for those are described here.
Account passwords are first stripped of any leading or trailing whitespace (Step 3, Figure 8.1). Whitespace is allowed within the account password, but because leading or trailing whitespace may not be visible to the user, we want to avoid creating an account password with spaces they’re unaware of. Then it’s normalized (Step 4) to a UTF-8 byte string using Unicode Normalization Form Compatibility Decomposition (NFKD). By normalizing these strings before any further processing, we allow for different byte sequences that may encode the same Unicode character to be treated identically. This is discussed in greater depth in Discussion 9.1.
People naturally want to use passwords that involve characters other than the 7-bit US-ASCII printable characters. Yet doing so poses difficulties that simply supporting Unicode doesn’t answer. Unicode normalization goes a long way toward addressing these difficulties.
The need for Unicode normalization can be exemplified by considering how the glyph “Å” may be encoded when it’s typed on some devices. It can be encoded in (at least) three different ways: It might be the byte sequence 0x212B, or 0x00C5, or 0x0041030A. Exactly how it’s encoded and passed to 1Password (or any software) depends on the often arbitrary details of the user’s keyboard, operating system, and settings. 1Password itself has no control over what particular sequence of bytes it will receive, but the user who uses “Å” in their password needs it to work reliably.
Normalization ensures that whichever particular UTF encoding of a string is passed to 1Password by the user’s operating system will be treated as identical. In the case of “Å”, the normalization we have chosen (NFKD), will convert any of those three representations to 0x0041030A.
Normalization does not correct for homoglyphs. For example, the letter “a” (the second letter in “password”) in the Latin alphabet will never be treated the same as the visually similar letter “а” (the second letter in “пароль”) in the Cyrillic alphabet. Thus, despite our use of normalization, users still have to exercise care in the construction of account passwords that go beyond the unambiguous 7-bit US-ASCII character set.
8.2.3 Preparing the salt
Next, the 16-byte salt is stretched using hash-based key derivation function (HKDF) and salted with the lowercase version of the email address (Step 5).12 The reason for binding the email address tightly with the cryptographic keys is discussed in “Restoring a user’s access to a vault.”
8.2.4 Slow hashing
The normalized account password is then processed with the slow hash PBKDF2-HMAC-SHA256 along with a salt.
The choice of PBKDF2-HMAC-SHA256 as our slow hash is largely a function of there being (reasonably) efficient implementations available for all our clients. While we could have used a more modern password hashing scheme, any advantage of doing so would have been lost by how slowly it would run within JavaScript in most web browsers.
Because key derivation is performed by the client (so the server never needs to see the password) we are constrained in our choices by our least efficient client. The Makwa password hashing scheme 13, however, is a possible road forward because it allows some of the computation to be passed to a server
In the current version, there are 650,000 iterations of PBKDF2.14 Extrapolating from a cracking challenge we ran,15 we estimate it costs an optimized attacker working at scale between 30 and ~40 US dollars to make \(2^{32}\) guesses against PBKDF2-SHA256 with 650,000 iterations.
8.2.5 Combining with the Secret Key
Figure 8.2: The AUK is represented as a JSON Web Key (JWK) object and given the distinguished key ID of mp.
The Secret Key, treated as a sequence of bytes, is used to generate an intermediate key of the same length as that derived from the account password. This is done using HKDF, using the raw Secret Key as its entropy source, the account ID as its salt, and the format version as additional data.
The resulting bytes from the use of HKDF are XORed with the result of the PBKDF2 operations. This is then set with the structure of a JWK object as illustrated in Figure 8.2.
8.2.6 Deriving the authentication key
The process of deriving the client-side authentication secret used for authenticating with the 1Password server is nearly identical to the procedure described above for deriving the AUK. The only difference is an entirely independent salt is used for the PBKDF2 rounds. This ensures the derived keys are independent of each other.
The 32-byte resulting key is converted into a BigNum for use with Secure Remote Password (SRP). We use the JSBN library in the browser, and the tools from OpenSSL for all other platforms.
The astute reader may have noticed the defender needs to perform 1,300,000 PBKDF2 rounds while an attacker (who has managed to obtain the Secret Key) only needs to perform 650,000 PBKDF2 rounds per guess, thus giving the attacker a 1-bit advantage over the defender in such an attack.
The sequence described above, however, in which the defender needs to derive both keys, rarely happens. In most instances, the SRP-\(x\) will be encrypted with the AUK (or by some other key that’s encrypted with the AUK) and stored locally. Thus the defender needs to derive the AUK only. The client needs to go through both derivations only at original sign-up or when enrolling a new client.
8.3 Initial sign-up
To focus on the initial creation of keys and establishment of authentication mechanisms, this section assumes the enrolling user has been invited to join a team by someone authorized to invite them.
When the invitation is created, the server generates an account ID and knows which team someone has been invited to join and the type of account that’s being created. The server is given the new user’s email address and possibly the new user’s real name. An invitation Universally Unique Identifier (UUID) is created to uniquely identify the invitation, and known to the team administrator. An invitation token is created by the server and not made available to the administrator. Other information about the status of the invitation is stored on the server.
The user is given (typically by email) the invitation UUID along with the invitation token, and uses them to request invitation details from the server. If the UUID is for a valid and active invitation and the provided token matches the invitation’s token, the server will send the invitation details, which include the account name, invited email address, and (if supplied by the inviter) real name of the user. If the server doesn’t find a valid and active invitation for that UUID, it returns an error.
The client will gather and compute a great deal of information, some of which is sent to the server.
| Symbol | Meaning |
|---|---|
| 𝜉 | Generated randomly |
| 🗝️ | Key-like thing |
| 🔒 | Encrypted |
| ↱ | Uploaded |
- Generate Secret Key 𝜉 🗝️
- Compute AUK
- Generate encryption key salt 𝜉 ↱
- Derive AUK from encryption salt, account password, and Secret Key as described in “Key derivation.” 🗝️
- Create encrypted account key set
- Generate private key 𝜉 🗝️
- Compute public key (from private key) 🗝️ ↱
- Encrypt private part with AUK 🔒 ↱
- Generate key set UUID 𝜉 ↱
- Include key set format ↱
- User information ↱
- Given name ↱
- Family name ↱
- Avatar image ↱
- Email address ↱
- Device information ↱
- Generate device UUID 𝜉 ↱
- Operating system (if available) ↱
- User agent (if applicable) ↱
- Hostname (if available) ↱
- Construct SRP verifier
- Generate authentication salt 𝜉 ↱
- Derive SRP-𝑥 from account password, Secret Key, an authentication salt 🗝️
- Computer SRP verifier from SRP-𝑥 🗝️ ↱
- Send to the server everything marked ↱
8.4 Enrolling a new client
When enrolling a new device, the user will provide the client with the add-device link (possibly in the form of a QR code) and their account password. The add-device link is generated at the user’s request from an already enrolled client and includes the domain name for the team, the user’s email address, and their Secret Key.
The link uses the custom schema onepassword: with a path of //team-account/add and a query string with fields email, server, and key. An example is shown in Figure 8.3.
Figure 8.3: An add link contains the email address, team domain, and Secret Key.
This new client doesn’t have its salts nor its key derivation parameters so requests them from the server. It’s able to generate its device information and create a device UUID.
Figure 8.4: Example response from server to auth request. The Secret Key is often referred to as “Account Key” internally.
The client initiates an auth request to the server, sending the email address and device UUID. A typical server response looks similar to what’s shown in Figure 8.4.
After the client has the salt used for deriving its authentication secret, it can compute its SRP-𝑥 from that salt, the account password, and the Secret Key. During authentication, neither the client nor server reveals any secrets to the other, and after authentication is complete, our own transport layer encryption is invoked on top of what is provided by TLS. In the discussion here, however, we’ll ignore those two layers of transport encryption and present the data as seen by the client and server after both transport encryption layers have been handled.
After successful authentication, the client requests its encrypted personal key set from the server. If the client has successfully authenticated, the server allows it to fetch the key sets associated with the account. The personal key set has the overall structure shown in Figure 8.5.
Figure 8.5: Overview of personal key set. The value of encryptedBy here indicates the encrypted symmetric key is encrypted with the Account Unlock Key.
This contains an encrypted private key, the associated public key, and an encrypted symmetric key that’s used to encrypt the private key. The encrypted symmetric key is encrypted with the AUK, using the parameters and salt that are included with the encrypted symmetric key as shown in Figure 8.6
Figure 8.6: The encrypted symmetric key is encrypted with the AUK, which in turn is derived using the salt in the p2s field, and using the methods indicated in the fields alg and p2c. The encrypted symmetric key itself is encrypted using AES256-GCM.
The details of the public and private keys are illustrated in Figure 8.7.
Figure 8.7: The public/private parts are specified using JWK.
8.5 Normal unlock and sign-in
When you unlock and and sign in to 1Password from a client that has previously signed in, the client may16 have everything it needs locally to compute its AUK and to compute or decrypt SRP-𝑥. The client may already have the salts, encryption parameters, and its encrypted personal key set.
After the user enters a correct account password and the client reads the Secret Key, it computes the AUK, decrypts the user’s private key, then decrypts any locally cached data. Depending on the completeness of the cached data, the client may be able to function offline.
HKDF places no security requirements on its salt, which may even be a constant or zero.↩︎
Accounts created prior to January 27, 2023 and have not changed their account password or Secret Key since this date, will use a lower iteration count. The iteration count can be updated to the current standard value by changing either the account password or Secret Key.↩︎
The use of the word “may” here reflects the fact that different 1Password clients take different approaches to what they store locally and what they recompute. The current version of the web client, for example, caches much less data locally than the mobile clients do.↩︎