/// <summary> /// Return a decrypted entity. This method is used for decrypting entity properties. /// </summary> internal Dictionary <string, EntityProperty> DecryptEntity(IDictionary <string, EntityProperty> properties, HashSet <string> encryptedPropertyDetailsSet, string partitionKey, string rowKey, byte[] contentEncryptionKey, EncryptionData encryptionData) { try { Dictionary <string, EntityProperty> decryptedProperties = new Dictionary <string, EntityProperty>(); switch (encryptionData.EncryptionAgent.EncryptionAlgorithm) { case EncryptionAlgorithm.AES_CBC_256: #if WINDOWS_DESKTOP && !WINDOWS_PHONE using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider()) { using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider()) #else using (AesManaged myAes = new AesManaged()) { using (SHA256Managed sha256 = new SHA256Managed()) #endif { myAes.Key = contentEncryptionKey; foreach (KeyValuePair <string, EntityProperty> kvp in properties) { if (kvp.Key == Constants.EncryptionConstants.TableEncryptionKeyDetails || kvp.Key == Constants.EncryptionConstants.TableEncryptionPropertyDetails) { // Do nothing. Do not add to the result properties. } else if (encryptedPropertyDetailsSet.Contains(kvp.Key)) { byte[] columnIV = sha256.ComputeHash(CommonUtility.BinaryAppend(encryptionData.ContentEncryptionIV, Encoding.UTF8.GetBytes(string.Join(partitionKey, rowKey, kvp.Key)))); Array.Resize <byte>(ref columnIV, 16); myAes.IV = columnIV; byte[] src = kvp.Value.BinaryValue; using (ICryptoTransform transform = myAes.CreateDecryptor()) { byte[] dest = transform.TransformFinalBlock(src, 0, src.Length); string destString = Encoding.UTF8.GetString(dest, 0, dest.Length); decryptedProperties.Add(kvp.Key, new EntityProperty(destString)); } } else { decryptedProperties.Add(kvp.Key, kvp.Value); } } } } return(decryptedProperties); default: throw new StorageException(SR.InvalidEncryptionAlgorithm, null) { IsRetryable = false }; } } catch (JsonException ex) { throw new StorageException(SR.EncryptionMetadataError, ex) { IsRetryable = false }; } catch (StorageException) { throw; } catch (Exception ex) { throw new StorageException(SR.DecryptionLogicError, ex) { IsRetryable = false }; } }
/// <summary> /// Return an encrypted entity. This method is used for encrypting entity properties. /// </summary> internal Dictionary <string, EntityProperty> EncryptEntity(IDictionary <string, EntityProperty> properties, string partitionKey, string rowKey, Func <string, string, string, bool> encryptionResolver) { CommonUtility.AssertNotNull("properties", properties); // The Key should be set on the policy for encryption. Otherwise, throw an error. if (this.Key == null) { throw new InvalidOperationException(SR.KeyMissingError, null); } EncryptionData encryptionData = new EncryptionData(); encryptionData.EncryptionAgent = new EncryptionAgent(Constants.EncryptionConstants.EncryptionProtocolV1, EncryptionAlgorithm.AES_CBC_256); encryptionData.KeyWrappingMetadata = new Dictionary <string, string>(); Dictionary <string, EntityProperty> encryptedProperties = new Dictionary <string, EntityProperty>(); HashSet <string> encryptionPropertyDetailsSet = new HashSet <string>(); #if WINDOWS_DESKTOP && !WINDOWS_PHONE using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider()) { using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider()) #else using (AesManaged myAes = new AesManaged()) { using (SHA256Managed sha256 = new SHA256Managed()) #endif { encryptionData.ContentEncryptionIV = myAes.IV; // Wrap always happens locally, irrespective of local or cloud key. So it is ok to call it synchronously. Tuple <byte[], string> wrappedKey = this.Key.WrapKeyAsync(myAes.Key, null /* algorithm */, CancellationToken.None).Result; encryptionData.WrappedContentKey = new WrappedKey(this.Key.Kid, wrappedKey.Item1, wrappedKey.Item2); foreach (KeyValuePair <string, EntityProperty> kvp in properties) { if (encryptionResolver != null && encryptionResolver(partitionKey, rowKey, kvp.Key)) { // Throw if users try to encrypt null properties. This could happen in the DynamicTableEntity case // where a user adds a new property as follows - ent.Properties.Add("foo2", null); if (kvp.Value == null) { throw new InvalidOperationException(SR.EncryptingNullPropertiesNotAllowed); } kvp.Value.IsEncrypted = true; } // IsEncrypted is set to true when either the EncryptPropertyAttribute is set on a property or when it is // specified in the encryption resolver or both. if (kvp.Value != null && kvp.Value.IsEncrypted) { // Throw if users try to encrypt non-string properties. if (kvp.Value.PropertyType != EdmType.String) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.UnsupportedPropertyTypeForEncryption, kvp.Value.PropertyType)); } byte[] columnIV = sha256.ComputeHash(CommonUtility.BinaryAppend(encryptionData.ContentEncryptionIV, Encoding.UTF8.GetBytes(string.Join(partitionKey, rowKey, kvp.Key)))); Array.Resize <byte>(ref columnIV, 16); myAes.IV = columnIV; using (ICryptoTransform transform = myAes.CreateEncryptor()) { // Throw if users try to encrypt null properties. This could happen in the DynamicTableEntity or POCO // case when the property value is null. if (kvp.Value.IsNull) { throw new InvalidOperationException(SR.EncryptingNullPropertiesNotAllowed); } byte[] src = Encoding.UTF8.GetBytes(kvp.Value.StringValue); byte[] dest = transform.TransformFinalBlock(src, 0, src.Length); // Store the encrypted properties as binary values on the service instead of base 64 encoded strings because strings are stored as a sequence of // WCHARs thereby further reducing the allowed size by half. During retrieve, it is handled by the response parsers correctly // even when the service does not return the type for JSON no-metadata. encryptedProperties.Add(kvp.Key, new EntityProperty(dest)); encryptionPropertyDetailsSet.Add(kvp.Key); } } else { encryptedProperties.Add(kvp.Key, kvp.Value); } } // Encrypt the property details set and add it to entity properties. byte[] metadataIV = sha256.ComputeHash(CommonUtility.BinaryAppend(encryptionData.ContentEncryptionIV, Encoding.UTF8.GetBytes(string.Join(partitionKey, rowKey, Constants.EncryptionConstants.TableEncryptionPropertyDetails)))); Array.Resize <byte>(ref metadataIV, 16); myAes.IV = metadataIV; using (ICryptoTransform transform = myAes.CreateEncryptor()) { byte[] src = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(encryptionPropertyDetailsSet)); byte[] dest = transform.TransformFinalBlock(src, 0, src.Length); encryptedProperties.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails, new EntityProperty(dest)); } } } // Add the key details to entity properties. encryptedProperties.Add(Constants.EncryptionConstants.TableEncryptionKeyDetails, new EntityProperty(JsonConvert.SerializeObject(encryptionData))); return(encryptedProperties); }
internal byte[] DecryptMetadataAndReturnCEK(string partitionKey, string rowKey, EntityProperty encryptionKeyProperty, EntityProperty propertyDetailsProperty, out EncryptionData encryptionData) { // Throw if neither the key nor the resolver are set. if (this.Key == null && this.KeyResolver == null) { throw new StorageException(SR.KeyAndResolverMissingError, null) { IsRetryable = false }; } try { encryptionData = JsonConvert.DeserializeObject <EncryptionData>(encryptionKeyProperty.StringValue); CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV); CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey); // Throw if the encryption protocol on the entity doesn't match the version that this client library understands // and is able to decrypt. if (encryptionData.EncryptionAgent.Protocol != Constants.EncryptionConstants.EncryptionProtocolV1) { throw new StorageException(SR.EncryptionProtocolVersionInvalid, null) { IsRetryable = false }; } byte[] contentEncryptionKey = null; // 1. Invoke the key resolver if specified to get the key. If the resolver is specified but does not have a // mapping for the key id, an error should be thrown. This is important for key rotation scenario. // 2. If resolver is not specified but a key is specified, match the key id on the key and and use it. // Calling UnwrapKeyAsync synchronously is fine because for the storage client scenario, unwrap happens // locally. No service call is made. if (this.KeyResolver != null) { IKey keyEncryptionKey = this.KeyResolver.ResolveKeyAsync(encryptionData.WrappedContentKey.KeyId, CancellationToken.None).Result; CommonUtility.AssertNotNull("keyEncryptionKey", keyEncryptionKey); contentEncryptionKey = keyEncryptionKey.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; } else { if (this.Key.Kid == encryptionData.WrappedContentKey.KeyId) { contentEncryptionKey = this.Key.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; } else { throw new StorageException(SR.KeyMismatch, null) { IsRetryable = false }; } } // Decrypt the property details set and add it to entity properties. #if WINDOWS_DESKTOP && !WINDOWS_PHONE using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider()) { using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider()) #else using (AesManaged myAes = new AesManaged()) { using (SHA256Managed sha256 = new SHA256Managed()) #endif { byte[] metadataIV = sha256.ComputeHash(CommonUtility.BinaryAppend(encryptionData.ContentEncryptionIV, Encoding.UTF8.GetBytes(string.Join(partitionKey, rowKey, Constants.EncryptionConstants.TableEncryptionPropertyDetails)))); Array.Resize <byte>(ref metadataIV, 16); myAes.IV = metadataIV; myAes.Key = contentEncryptionKey; using (ICryptoTransform transform = myAes.CreateDecryptor()) { byte[] src = propertyDetailsProperty.BinaryValue; propertyDetailsProperty.BinaryValue = transform.TransformFinalBlock(src, 0, src.Length); } } } return(contentEncryptionKey); } catch (JsonException ex) { throw new StorageException(SR.EncryptionMetadataError, ex) { IsRetryable = false }; } catch (StorageException) { throw; } catch (Exception ex) { throw new StorageException(SR.DecryptionLogicError, ex) { IsRetryable = false }; } }