Class: Chef::EncryptedAttribute::EncryptedMash::Version1
- Inherits:
-
Version0
- Object
- Mash
- Chef::EncryptedAttribute::EncryptedMash
- Version0
- Chef::EncryptedAttribute::EncryptedMash::Version1
- Defined in:
- lib/chef/encrypted_attribute/encrypted_mash/version1.rb
Overview
EncryptedMash Version1 format: using RSA with a shared secret and message authentication (HMAC).
This is the Chef::EncryptedAttribute::EncryptedMash version used by default. Uses public key cryptography (PKI) to encrypt a shared secret. Then this shared secret is used to encrypt the data.
- This implementation can be improved, is not optimized either for performance or for space.
- Every time the
EncryptedAttribute
is updated, all the shared secrets are regenerated.
EncryptedMash::Version1
Structure
If you try to read this encrypted attribute structure, you can see a
Mash
attribute with the following content:
EncryptedMash
├── chef_type: "encrypted_attribute" (string).
├── x_json_class: The used `EncryptedMash` version class name (string).
├── encrypted_data
│ ├── cipher: The used PKI algorithm, "aes-256-cbc" (string).
│ ├── data: PKI encrypted data (base64).
│ └── iv: Initialization vector (in base64).
├── encrypted_secret
│ ├── pub_key_hash1: The shared secrets encrypted for the public key 1
│ │ (base64).
│ ├── pub_key_hash2: The shared secrets encrypted for the public key 2
│ │ (base64).
│ └── ...
└── hmac
├── cipher: The used HMAC algorithm, currently ignored and always
│ "sha256" (string).
└── data: Hash-based message authentication code value (base64).
x_json_class
field is used, with thex_
prefix, to be easily integrated with Chef in the future.
EncryptedMash[encrypted_data][data]
The data inside encrypted_data
is symmetrically encrypted using the
secret shared key. The data is converted to JSON before the
encryption, then encrypted and finally encoded in base64. By default,
the 'aes-256-cbc'
algorithm is used for encryption.
After decryption, the JSON has the following structure:
└── encrypted_data
└── data (symmetrically encrypted JSON in base64)
└── content: attribute content as a Mash.
- In the future, this structure may contain some metadata like default configuration values.
EncryptedMash[encrypted_secret][pub_key_hash1]
The public_key_hash1
key value is the SHA1 of the public key used
for encryption.
Its content is the encrypted shared secrets in base64. The encryption is done using the RSA algorithm (PKI).
After decryption, you find the following structure in JSON:
└── encrypted_secret
└── pub_key_hash1 (PKI encrypted JSON in base64)
├── data: The shared secret used to encrypt the data (base64).
└── hmac: The shared secret used for the HMAC calculation
(base64).
EncryptedMash[hmac][data]
The HMAC data is in base64. The hashing algorithm used is 'sha256'
.
The following data is used in a alphabetically sorted JSON to calculate the HMAC:
Data to calculate the HMAC from
├── cipher: The algorithm used for `encrypted_data` encryption
│ ("aes-256-cbc").
├── data: The `encrypted_data` data content after the encryption
│ (encrypt-then-mac).
└── iv: The initialization vector used to encrypt the encrypted_data.
- All the data required for decryption is included in the HMAC (except
the secret key, of course):
cipher
,data
andiv
. - The data used to calculate the HMAC is the encrypted data, not the clear text data (Encrypt-then-MAC).
- The secret used to calculate the HMAC is not the same as the secret used to encrypt the data.
- The secret used to calculate the HMAC is shared inside
encrypted_secret
field with the data secret.
Direct Known Subclasses
Constant Summary collapse
- SYMM_ALGORITHM =
Symmetric algorithm to use by default.
'aes-256-cbc'
- HMAC_ALGORITHM =
Algorithm used for HMAC calculation.
'sha256'
Constants inherited from Chef::EncryptedAttribute::EncryptedMash
CHEF_TYPE, CHEF_TYPE_VALUE, JSON_CLASS, VERSION_PREFIX
Instance Method Summary collapse
-
#can_be_decrypted_by?(keys) ⇒ Boolean
Checks if the current Chef::EncryptedAttribute::EncryptedMash can be decrypted by all of the provided keys.
-
#decrypt(key) ⇒ Mixed
Decrypts the current Chef::EncryptedAttribute::EncryptedMash object.
-
#encrypt(value, public_keys) ⇒ EncryptedMash
Encrypts data inside the current Chef::EncryptedAttribute::EncryptedMash object.
-
#needs_update?(keys) ⇒ Boolean
Checks if the current Chef::EncryptedAttribute::EncryptedMash needs to be re-encrypted.
Methods inherited from Chef::EncryptedAttribute::EncryptedMash
create, exist?, exists?, #for_json, #initialize, json_create, string_to_klass, #to_json, #update_from!, version_klass
Constructor Details
This class inherits a constructor from Chef::EncryptedAttribute::EncryptedMash
Instance Method Details
#can_be_decrypted_by?(keys) ⇒ Boolean
Checks if the current Chef::EncryptedAttribute::EncryptedMash can be decrypted by all of the provided keys.
178 179 180 181 |
# File 'lib/chef/encrypted_attribute/encrypted_mash/version1.rb', line 178 def can_be_decrypted_by?(keys) return false unless encrypted? data_can_be_decrypted_by_keys?(self['encrypted_secret'], keys) end |
#decrypt(key) ⇒ Mixed
Decrypts the current Chef::EncryptedAttribute::EncryptedMash object.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/chef/encrypted_attribute/encrypted_mash/version1.rb', line 157 def decrypt(key) key = parse_decryption_key(key) enc_value = self['encrypted_data'].dup hmac = self['hmac'].dup # decrypt the shared secrets secrets = json_decode(rsa_decrypt_multi_key(self['encrypted_secret'], key)) enc_value['secret'] = secrets['data'] hmac['secret'] = secrets['hmac'] # check hmac (encrypt-then-mac -> mac-then-decrypt) unless hmac_matches?(hmac, json_encode(self['encrypted_data'].sort)) fail DecryptionFailure, 'Error decrypting encrypted attribute: invalid hmac. Most '\ 'likely the data is corrupted.' end # decrypt the data value_json = symmetric_decrypt_value(enc_value) json_decode(value_json) end |
#encrypt(value, public_keys) ⇒ EncryptedMash
Encrypts data inside the current Chef::EncryptedAttribute::EncryptedMash object.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/chef/encrypted_attribute/encrypted_mash/version1.rb', line 136 def encrypt(value, public_keys) secrets = {} value_json = json_encode(value) public_keys = parse_public_keys(public_keys) # encrypt the data encrypted_data = symmetric_encrypt_value(value_json) # should no include the secret in clear secrets['data'] = encrypted_data.delete('secret') self['encrypted_data'] = encrypted_data # generate hmac (encrypt-then-mac), excluding the secret hmac = generate_hmac(json_encode(self['encrypted_data'].sort)) secrets['hmac'] = hmac.delete('secret') self['hmac'] = hmac # encrypt the shared secrets self['encrypted_secret'] = rsa_encrypt_multi_key(json_encode(secrets), public_keys) self end |
#needs_update?(keys) ⇒ Boolean
Checks if the current Chef::EncryptedAttribute::EncryptedMash needs to be re-encrypted.
This usually happends when new keys are provided or some keys are removed from the previous encryption process.
In other words, this method checks all key can decrypt the data and only those keys.
184 185 186 187 188 |
# File 'lib/chef/encrypted_attribute/encrypted_mash/version1.rb', line 184 def needs_update?(keys) keys = parse_public_keys(keys) !can_be_decrypted_by?(keys) || self['encrypted_secret'].keys.count != keys.count end |