Security Design Document

We don’t believe in security by obscurity. We put our trust in transparency instead. That’s why we document our security measures here so security experts from all over the world can review it.

Note that this document assumes a familiarity with basic cryptographic concepts and algorithms. It’s still an easy enough read though. If you have any questions or feedback, please send us an email at security@secrethub.io or contact us directly.

TL;DR

SecretHub’s most notable security features are:

  • Secrets are encrypted before they leave the client device.
  • The server never sees any plaintext secret content or key material used for encryption.
  • Only you have the keys to unlock your secrets.
  • Even the names of your secrets and directories are encrypted with keys you control.
  • SecretHub is built on top of modern, open source libraries and battle-tested algorithms.

1. Concepts

SecretHub helps DevOps teams encrypt secrets and distribute them between people and the machines they operate. Before diving deeper into the internal cryptography that ensures this is done securely, it is worth defining a few concepts first.

A repository or ‘repo’ is a central location in which secrets can be stored and managed. A repository contains all of a project’s secrets and their revision history, storing a unique version of the secret content (e.g. a password) for each revision. To help you organize your secrets, repositories can contain multiple directories and subdirectories, behaving a lot like a traditional file system.

Organizations are shared workspaces to help users collaborate on many repositories at the same time. Each person or organization can own unlimited repositories. Users can be invited to an organization’s repository, provided they are a member of the organization.

Every user has a unique account and each account comes with a unique account-key.

Service accounts are special non-human accounts that can be provisioned to allow a machine or service independent access to SecretHub. A service account also has a unique account and corresponding account-key but differs from a user account in that it is always attached to one repository.

For the purpose of clarity, referring to an account can mean both a user or a service and we’ll refer to users or services specifically when needed.

Finally, each account can have one or more credentials associated with it. A credential is a cryptographic key that is never sent to the server and always stored privately by the client. Credentials are used to authenticate and unlock an account.

2. Encryption

The server never sees any plaintext secret content or any key material used for encryption. Only you have the keys to unlock your secrets. This section explains the entire encryption scheme and how it is designed to be fast, flexible and secure.

2.1 Accounts

When creating a SecretHub account, two keys are generated on the client:

  • A unique asymmetric account key pair. This is a 4096-bit RSA key.
  • A unique asymmetric account credential key pair. This is currently a 4096-bit RSA key, support for more key types can be added in the future.

The public key part of the account key is stored in plaintext on the server. The private key part of the account key is encrypted with the account credential’s public key using a key wrapping procedure described below.

Account key wrapping

Figure 1: account key wrapping

To wrap the private account key with the account credential, a random 256-bit symmetric intermediate key is generated on the client. Using the AES algorithm in Galois/Counter Mode (AES-GCM) with a randomly generated nonce of 96 bits, the private account key is encrypted with this intermediate AES key. The intermediate AES key is then encrypted with the public part of the account credential using the RSA-OAEP algorithm with SHA256. This produces two ciphertexts that are sent to the server and stored there.

The account credential is used to authenticate the account to the server (more on that will be published in section 4) and to encrypt/decrypt the account key. The private part of this credential is never sent to the server and is stored locally on the account’s machine. To protect locally stored account credentials, the private account credential can be encrypted with a passphrase.

2.2 Protecting account credentials with a passphrase

The account credential private key is typically stored locally on an account’s device in a credential file. In addition to file system permissions, SecretHub allows users to add another layer of protection to the credential file by encrypting it with a passphrase.

Upon creation of the account credential, users are prompted to type in a passphrase. The passphrase is then used to derive a 256-bit AES key using a Password Based Key Derivation (PBKDF) algorithm called scrypt. The scrypt parameters are set to N=215, r=8, and p=1 and a 272-bit cryptographic salt is used. When using a salt for key derivation, all code verifies the salt’s purpose matches that of the intended use case of the derived key. For a more extensive discussion of the scrypt algorithm and its parameters, see section 3.4.

Using the 256-bit key derived from the passphrase, the client is then able to encrypt the private account credential using the AES-GCM algorithm with a randomly generated nonce of 96 bits. The scrypt parameters and salt are not sensitive and are therefore stored in plaintext next to the encrypted credential in the same file. When reading the credential file, the salt and scrypt parameters are used together with a user-provided passphrase to reconstruct the derived key and subsequently decrypt the credential private key.

Note that neither the passphrase, derived key, nor the credential private key are ever sent to the server and only the contents of the credential file are persisted on the client’s filesystem.

Credential encryption

Figure 2: local credential encryption
1 Stored in the local credential file.

2.3 Secrets

When writing secret content (e.g. a password) to SecretHub, a new version of the secret is always created. This makes secret versions ‘append-only’ and ensures no secret content can be overwritten. Versions can only be deleted if done so explicitly.

When creating a new secret, a secret key is generated on the client. This is a 256-bit AES key. The secret content is then encrypted with the secret key using the AES-GCM algorithm with a randomly generated nonce of 96 bits. The same secret key is used for subsequent versions of the same secret, as long as the key is not flagged (see section 2.6).

The secret key is then individually encrypted for each account-key that has access to the secret, using the public account-key with the RSA-OAEP algorithm using SHA256. When n accounts have access to a secret, this results in n uniquely encrypted copies of the secret key.

Secret encryption

Figure 3: Secret encryption

Note that whether or not an account has access to a secret is managed by user-defined access rules (see section 4).

Subsequently, the encrypted secret content and n encrypted copies of the secret key are sent to and stored on the server.

2.4 Metadata

Names of directories and the names of secrets they contain are considered sensitive. For instance, when users name their secret root_database_password we don’t want to know about it and we don’t want to expose that name to any potential adversary. So, in addition to the secret content itself, the client also encrypts secret metadata before sending it to the SecretHub server.

Names are individually encrypted for each account that has access to the directory or secret, using the public account-key with the RSA-OAEP algorithm using SHA256. When n accounts have access to a secret or directory, this results in a list of n encrypted copies of the name to be stored on the server. This way the plaintext name is never sent to the server.

Name encryption Figure 4: name encryption

User-defined access rules determine which accounts may have access to a directory or secret’s metadata. The following two rules apply here:

  • An account has access to the metadata of a secret when there exists an access rule that grants the account access on the secret’s directory of any of its parents.
  • An account has access to the metadata of a directory when there exists an access rule that grants the account access on the directory itself, one of its parents or any of the directory’s children.

2.5 Blind names

When the server never sees plaintext names, how can users query secrets or directories by their name? To solve that without exposing the plaintext name to the server, we introduce the concept of blind names.

A blind name is a value that uniquely identifies a resource by its name while the value itself does not leak any information about the actual name of the resource. This means anyone can use the blind name without ever seeing the actual name itself, hence the ‘blind’.

Names are converted to blind names using a repository index key. The repository index key is a 256-bit value that is randomly generated by the client when a repository is created. Upon creation, the repository index key is encrypted with the creator’s public account key using RSA-OAEP with SHA256. The encrypted repository index key is then sent to the server.

Whenever an account is invited to collaborate on a repository, the repository index key is re-encrypted for that account’s public key using the same algorithm. With n accounts that have access to a repository, this results in a list of n uniquely encrypted copies of the repository index key to be stored on the server.

With the 256-bit long repository index key, a client is then able to create a blind name using the Keyed-Hash Message Authentication Code (HMAC) algorithm and SHA256. The resulting blind name is a 256-bit value that is indistinguishable from random noise and cannot be used to reconstruct the original name.

Blind name generation

Figure 5: blind name generation

This mechanism allows SecretHub to index and query directories and secrets by their name without clients ever sending the actual name to the server. Only blind names are sent to and stored by the server.

2.6 Revocation and rotation

When an account’s access to a secret is revoked, all copies of secret keys encrypted for that account are immediately deleted from the server. Also, the server automatically flags all secret-keys that the account has ever read. To determine which secret-keys the revoked account has read, the server queries the audit log.

Flagged secret keys and the secret content they protect should be considered compromised from that point onward and should be rotated, replacing compromised secrets with newly generated ones.

When writing a new version of an existing secret, the client checks whether there already exists a valid (i.e. not flagged) secret key and uses that to encrypt the secret content using the same mechanism as described in section 2.3. When no valid secret key exists for a pre-existing secret, the client generates a new secret key and sends n encrypted copies of it to the server.

Once a secret has been flagged, accounts cannot ‘unflag’ it. When flagged, both server and client only accept writing new versions that are encrypted with a new secret key. This ensures forward secrecy of the secret keys and the data they protect, i.e. even the compromise of one secret key (when properly flagged) does not affect secret content protected by previous or future secret keys.

If an adversary has had access to the encrypted secret content and its corresponding secret key, it has also had access to the plaintext secret content itself. In this case, rotating the compromised secret key and re-encrypting the secret content with the new key does not provide a higher level of security. Therefore, the encryption of flagged secret content is not changed and rotating the secret content itself (i.e. writing a new version) is required.

Secret names are not re-encrypted when a user is revoked. The reason for this is that a user is assumed to have full knowledge of the names of all secrets it had access to. So re-encrypting does not increase the level of security. The names of future secrets won’t be encrypted for the revoked user anymore.

3. Algorithms

SecretHub is built on top of modern, open source libraries and battle-tested algorithms. Each algorithm, library and parameter choice is explained below.

3.1 RSA-OAEP with SHA256

For asymmetric encryption, RSA-OAEP with SHA256 hashing is used. RSA is a widely used and accepted algorithm for asymmetric encryption based on the integer factorization problem. For it to be used safely, a padding scheme is needed. Optimal asymmetric encryption padding (OAEP) with SHA256 hashing is a padding scheme approved by NIST (NIST, 2014), ENISA (ENISA, 2014), ANSSI (ANSSI, 2007) and BSI (BSI, 2018).

For the length of the key, all these authorities agree that a length of 4096 can be used for long term storage (2030 and beyond).

The SecretHub software uses Golang’s standard library implementation of RSA-OAEP. RSA-OAEP requires a random number generator, to prevent encrypting a plaintext twice to the same ciphertext. See section 3.5 for the used random number generator.

3.2 AES-GCM

For symmetric encryption, the Galois/Counter Mode of operation of AES is chosen. This mode of operation provides authenticated encryption at a high speed and allows for the embedding of extra (unencrypted) data in the authentication tag. Most importantly, it is a widely accepted standard. It is accepted by the NIST (NIST, 2007), as by BSI (BSI, 2018) and also by ENISA (ENISA, 2014). ANSSI does not mention the GCM mode in their document about encryption standards (ANSSI, 2007), but it must be noted that authenticated encryption algorithms are not discussed in the paper at all.

For data that should be kept secret beyond the year 2030 current research recommends a key length of 128 bits or higher (see keylength.com to compare different key sizes). To be future proof and because it is the strongest widely implemented key size available, the chosen key-length is 256 bits. A research by Arjen K. Lenstra even suggests 256-bit symmetric keys are safe until the year 2282 (Lenstra, 2004). Furthermore, the performance and storage cost as a result of choosing a bigger symmetric key are negligible for SecretHub’s use cases.

The AES-GCM algorithm takes a nonce as input, sometimes called an initial value (IV). For the nonce, a random 96-bit value is chosen as other lengths require additional computation. The nonce is always randomly generated and never reused. The 96-bit length is also the value recommended by the NIST:

For IVs, it is recommended that implementations restrict support to the length of 96 bits, to promote interoperability, efficiency, and simplicity of design.

(NIST, 2007)

The SecretHub software uses Golang’s standard library implementation of AES-GCM.

3.3 HMAC-SHA256

For creating blind names as described in section 2.5, the HMAC message authentication code is used. This HMAC-construct uses a hashing algorithm to produce a hash from a message and a key, for which the SHA-256 hashing algorithm has been chosen. Both the use of HMAC and SHA-256 are explicitly approved by NIST (NIST, 2012), BSI (BSI, 2018) and ENISA (ENISA, 2014).

The key used for the HMAC is a 256-bit long random value. The hash size of 256 bits is widely believed to be secure beyond 2030 (see keylength.com to compare different hash sizes).

The SecretHub software uses Golang’s standard library implementation of HMAC and the standard implementation of SHA-256.

3.4 scrypt

As a password-based key derivation function (PBKDF), the scrypt algorithm (pronounced “ess crypt” (Percival, 2016)) is chosen. This is a memory-hard PBKDF, which means that it has a non-negligible memory-cost, making it costly for an attacker to perform a brute-force attack (see table 1).

Table 1: estimated hardware cost to crack a password in 1 year (Percival, 2009)

Table 1

The older and widely applied PBKDF2 is not chosen, because of its weakness against brute force attacks using dedicated hardware (Percival, 2009). The bcrypt PBKDF is comparable to scrypt in that it is also a memory-hard function. But as table 1 shows, when keeping the computation duration for a client the same, the cost for cracking the password is way higher when using scrypt.

The scrypt algorithm takes a couple of parameters: N, r, p, and a cryptographic salt. Design decisions for each parameter are explained below.

The N parameter is the work factor of the scrypt key derivation function. Changing the work factor N linearly scales the memory and CPU usage of the key derivation function. N must be a power of two to allow for optimizations inside the algorithm using bit masking. The value for N has been set to 215, which is the biggest power of two that will run the scrypt key derivation function in approximately 100 milliseconds on our benchmark pc from 2015. The 100 milliseconds is the recommended time cost for interactive use cases that won’t bother the user, e.g. logging into a web application or using a command-line interface. In the future, we will add the option for the user to change the number of iterations to their preferred value.

The r parameter also affects the computation and memory cost of the function but because the impact can be dependent on the hardware that is used, the parameter is kept at its default of 8.

The p parameter is kept at the default value of 1. Increasing this value increases the number of CPU cycles but allows computation to be parallelized. However, this could be used by an attacker to optimize for either memory of CPU cycles. To disallow such optimizations, the parameter is set to 1.

The cryptographic salt used for the scrypt algorithm consists of 272 bits of data. The first 16 bits of the salt define the ‘salt purpose’ and the last 256-bit segment of the salt is a randomly generated value.

The 16-bit long ‘salt purpose’ is used to distinguish between different algorithms (e.g. AES-GCM), key lengths (e.g. 256 bits) and operation types (e.g. local credential encryption) for which the derived key is to be used. Whenever a salt or its derived value is used, the salt’s purpose is verified by the code using it, ensuring salts are never reused for different purposes. This approach is recommended by RFC 2898, Password-Based Cryptography (Kaliski, 2000, sec. 4.1) and prevents a type of attack where a user is tricked into using a derived key for something else than its intended purpose. Such an attack might leak information about the used key or the encrypted plaintext.

RFC 2898 (Kaliski, 2000, sec. 4.1) recommends a minimum length of 64 bits for the random salt segment. However, because salt length does not significantly impact performance of the key derivation function a longer random segment of 256 bits has been chosen. The 256-bit long random segment provides enough ‘address space’ to generate salts in to guarantee each generated salt is unique and unlikely to be ever reused. Collisions are only likely to occur after generating 2128 salts according to the Birthday Paradox.

Moreover, a sufficiently large random salt segment ensures it is prohibitively difficult for an opponent to precompute all the keys corresponding to a dictionary of passwords, or even the most likely keys. An attacker is limited to searching for passwords only after a password-based key derivation operation has been performed and the salt is known. This effectively limits the attack-surface to each individual key derived from a passphrase.

The SecretHub software uses Golang’s standard library implementation of scrypt.

3.5 Random number generator

Whenever this document refers to a randomly generated value (e.g. keys, salts, etc.), it refers to a value generated using a Cryptographically Strong Pseudo-Random Number Generator (CSPRNG).

The SecretHub software uses Golang’s standard library implementation of a CSPRNG. This implementation makes use of the OS source of randomness:

  • On Linux it uses the uses getrandom(2) if available or /dev/urandom otherwise.
  • On OpenBSD it uses getentropy(2).
  • On other Unix-like systems it reads from /dev/urandom.
  • On Windows systems it uses the CryptGenRandom API.

4. Authentication & authorization

This section describes how HTTP requests to the SecretHub API are authenticated and what logic determines whether or not an account is allowed to perform certain actions.

In summary, the client-server authentication scheme is based on well-known and battle-tested protocols. All connections to the API are secured using TLS.

Whether users have access to a secret is cryptographically enforced: if you have not been granted access, a secret is not encrypted for you and therefore you cannot read it. Whether an account has read, write or admin permissions is determined by user-defined access rules and enforced with server logic.

Note that this section is still under construction and more detailed documentation will be published soon.

5. Server security & availability

This sections describes the SecretHub infrastructure and how it has been purpose-built to guarantee security and availability of the SecretHub service.

SecretHub is hosted on Amazon Web Services (AWS) in multiple datacenters to ensure maximum availability and reliability of our services. Residing across different regions allows us to cover complete region/datacenter failover scenarios.

The SecretHub API only accepts connections over HTTPS, using TLS 1.2 and a small set of supported cipher suites. Incoming requests over plain HTTP are automatically redirected to a secure HTTPS connection.

Note that this section is still under construction and more detailed documentation will be published soon.

6. Threat model

This section describes the extensive threat modeling we perform to identify the objectives and potential vulnerabilities of the SecretHub system. This allows us to deploy effective countermeasures to prevent or mitigate effects of threats to the system.

A few foundational principals to SecretHub’s security design are listed below:

  • We recognize security through obscurity does not exist, so we assume any attacker has full access to client and server code and has a deep understanding of the system.
  • We don’t ever want to be able read your secrets, so everything is encrypted client-side with keys you control. Plaintext key material (that could be used to decrypt your secrets) is never sent to the server.
  • We follow the very privacy respecting Dutch law. SecretHub is incorporated in the Netherlands with a completely Dutch management. Only a legitimate Dutch court order can force us to cooperate with lawful requests for information. Client-side encryption guarantees that even in such extreme cases your secrets are safe.

Note that this section is still under construction and a more extensive threat model will be published soon.

7. Caveats & known issues

This section describes the conditions and limitations to use SecretHub securely.

The main thing to consider when using SecretHub is that YOU as a user are responsible for your own key security. That’s why we offer password based encryption for your account credential (see section 2.2 of the encryption documentation). Also, because data cannot be recovered if you lose the key, users should take care to keep a backup copy of their key in a safe location.

The chief attack vector users should consider is a direct attack on your workstation, e.g. the laptop of a DevOps engineer. That’s why we strongly recommend to install security updates, encrypt your disks and use a strong password to protect access to your workstation. Furthermore, we advise to always adhere to the principle of least privilege, meaning that users and services are only granted access to secrets they actually need for their legitimate purpose. This minimizes the potential damage a breach of one of your systems could cause.

References

  1. ANSSI. (2007). Cryptographic mechanisms: Rules and recommendations about the choice and parameters sizes of cryptographic mechanisms with standard robustness level. General Secretariat for National Defence, Central Directorate for Information Systems Security. Retrieved from www.ssi.gouv.fr
  2. BSI. (2018). BSI TR-02102-1: "Cryptographic Mechanisms: Recommendations and Key Lengths". German Federal Office for Information Security - Bundesamt für Sicherheit in der Informationstechnik (BSI). Retrieved from www.bsi.bund.de
  3. ENISA. (2014). Algorithms, key size and parameters report. European Union Agency for Network and Information Security. Retrieved from www.enisa.europa.eu
  4. Kaliski, B. (2000). RFC 2898 PKCS# 5: Password-based cryptography specification version 2.0. Retrieved from www.ietf.org
  5. Lenstra, A. K. (2004). Key Lengths, Contribution to The Handbook of Information Security.
  6. NIST. (2007). Special Publication 800-38D: Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC. National Institute of Standards and Technology (NIST), U.S. Department of Commerce. Retrieved from nvlpubs.nist.gov
  7. NIST. (2012). Special Publication 800-107 Revision 1: Recommendation for Applications Using Approved Hash Algorithms. National Institute of Standards and Technology (NIST), U.S. Department of Commerce. Retrieved from nvlpubs.nist.gov
  8. NIST. (2014). Special Publication 800-56B Revision 1: Recommendation for Pair-Wise Key-Establishment Schemes Using Integer Factorization Cryptography. National Institute of Standards and Technology (NIST), U.S. Department of Commerce. Retrieved from nvlpubs.nist.gov
  9. Percival, C. (2009). Stronger key derivation via sequential memory-hard functions. Self-Published, 1–16. Retrieved from www.tarsnap.com
  10. Percival, C. (2016). on Twitter. Retrieved from twitter.com