You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2019/11/13 14:26:30 UTC

[syncope] branch 2_1_X updated: [SYNCOPE-1510] Secret key can now also be referenced as Spring property + option to store encrypted and read cleartext

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/2_1_X by this push:
     new 8fcf318  [SYNCOPE-1510] Secret key can now also be referenced as Spring property + option to store encrypted and read cleartext
8fcf318 is described below

commit 8fcf318829ece1f077424815c4c3687a38a01ab5
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Nov 13 15:26:15 2019 +0100

    [SYNCOPE-1510] Secret key can now also be referenced as Spring property + option to store encrypted and read cleartext
---
 .../panels/ParametersCreateWizardAttrStep.java     | 33 ++++----
 .../console/panels/ParametersDetailsPanel.java     | 33 ++++----
 .../client/console/panels/PlainSchemaDetails.java  | 44 ++++++++---
 .../wizards/any/AbstractAttrsWizardStep.java       | 43 ++++++-----
 .../client/console/panels/PlainSchemaDetails.html  |  3 +
 .../console/panels/PlainSchemaDetails.properties   |  1 +
 .../panels/PlainSchemaDetails_it.properties        |  1 +
 .../panels/PlainSchemaDetails_ja.properties        |  1 +
 .../panels/PlainSchemaDetails_pt_BR.properties     |  1 +
 .../panels/PlainSchemaDetails_ru.properties        |  1 +
 .../app/js/directives/dynamicPlainAttribute.js     |  5 ++
 .../resources/app/views/dynamicPlainAttribute.html |  7 +-
 .../syncope/common/lib/SyncopeConstants.java       |  2 +
 .../jpa/entity/JPAJSONEntityListener.java          |  4 +-
 .../entity/anyobject/JPAJSONAnyObjectListener.java |  2 +-
 .../jpa/entity/conf/JPAJSONConfListener.java       |  2 +-
 .../jpa/entity/group/JPAJSONGroupListener.java     |  2 +-
 .../entity/user/JPAJSONLinkedAccountListener.java  |  2 +-
 .../jpa/entity/user/JPAJSONUserListener.java       |  2 +-
 .../entity/JPAJSONAttributableValidator.java       |  4 +-
 .../jpa/entity/AbstractPlainAttrValue.java         | 88 ++++++++++++++--------
 .../core/persistence/jpa/inner/PlainAttrTest.java  | 62 +++++++++++++++
 .../java/data/AnyObjectDataBinderImpl.java         | 26 ++++---
 fit/core-reference/pom.xml                         | 11 ++-
 .../org/apache/syncope/fit/core/GroupITCase.java   | 54 +++++++++++++
 .../reference-guide/concepts/typemanagement.adoc   |  4 +-
 26 files changed, 315 insertions(+), 123 deletions(-)

diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java
index 7fa1774..580e82c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java
@@ -62,7 +62,7 @@ public class ParametersCreateWizardAttrStep extends WizardStep {
         schema.setRequired(true);
         content.add(schema);
 
-        final LoadableDetachableModel<List<PlainSchemaTO>> loadableDetachableModel =
+        LoadableDetachableModel<List<PlainSchemaTO>> loadableDetachableModel =
                 new LoadableDetachableModel<List<PlainSchemaTO>>() {
 
             private static final long serialVersionUID = 7172461137064525667L;
@@ -74,14 +74,13 @@ public class ParametersCreateWizardAttrStep extends WizardStep {
 
         };
 
-        final ListView<PlainSchemaTO> listView = new ListView<PlainSchemaTO>("attrs", loadableDetachableModel) {
+        ListView<PlainSchemaTO> listView = new ListView<PlainSchemaTO>("attrs", loadableDetachableModel) {
 
             private static final long serialVersionUID = 9101744072914090143L;
 
             @Override
             protected void populateItem(final ListItem<PlainSchemaTO> item) {
-                final Panel panel = getFieldPanel("panel", modelObject.getAttrTO(), item.getModelObject());
-                item.add(panel);
+                item.add(getFieldPanel("panel", modelObject.getAttrTO(), item.getModelObject()));
             }
         };
 
@@ -89,16 +88,15 @@ public class ParametersCreateWizardAttrStep extends WizardStep {
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    private Panel getFieldPanel(final String id, final AttrTO attrTO, final PlainSchemaTO plainSchemaTO) {
+    private Panel getFieldPanel(final String id, final AttrTO attrTO, final PlainSchemaTO plainSchema) {
         final String valueHeaderName = getString("values");
 
         final FieldPanel panel;
-        switch (plainSchemaTO.getType()) {
+        switch (plainSchema.getType()) {
             case Date:
-                final String dataPattern = plainSchemaTO.getConversionPattern() == null
+                String dataPattern = plainSchema.getConversionPattern() == null
                         ? SyncopeConstants.DEFAULT_DATE_PATTERN
-                        : plainSchemaTO.getConversionPattern();
-
+                        : plainSchema.getConversionPattern();
                 if (dataPattern.contains("H")) {
                     panel = new AjaxDateTimeFieldPanel(
                             id, valueHeaderName, new Model<>(), dataPattern);
@@ -139,7 +137,7 @@ public class ParametersCreateWizardAttrStep extends WizardStep {
 
             case Enum:
                 panel = new AjaxDropDownChoicePanel<>(id, valueHeaderName, new Model<>(), false);
-                ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchemaTO));
+                ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema));
 
                 if (!attrTO.getValues().isEmpty()) {
                     ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer<String>() {
@@ -164,7 +162,7 @@ public class ParametersCreateWizardAttrStep extends WizardStep {
                     });
                 }
                 ((AjaxDropDownChoicePanel<String>) panel).setNullValid(
-                        "true".equalsIgnoreCase(plainSchemaTO.getMandatoryCondition()));
+                        "true".equalsIgnoreCase(plainSchema.getMandatoryCondition()));
                 break;
 
             case Long:
@@ -178,25 +176,26 @@ public class ParametersCreateWizardAttrStep extends WizardStep {
                 break;
 
             case Binary:
-                panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), plainSchemaTO.getMimeType(),
+                panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), plainSchema.getMimeType(),
                         schema.getModelObject());
                 break;
 
             case Encrypted:
-                panel = new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true);
+                panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern())
+                        ? new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false)
+                        : new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true);
                 break;
 
             default:
                 panel = new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false);
         }
-        if (plainSchemaTO.isMultivalue()) {
+        if (plainSchema.isMultivalue()) {
             return new MultiFieldPanel.Builder<>(
                     new PropertyModel<>(attrTO, "values")).build(id, valueHeaderName, panel);
-        } else {
-            panel.setNewModel(attrTO.getValues());
         }
 
-        panel.setRequired("true".equalsIgnoreCase(plainSchemaTO.getMandatoryCondition()));
+        panel.setNewModel(attrTO.getValues());
+        panel.setRequired("true".equalsIgnoreCase(plainSchema.getMandatoryCondition()));
         return panel;
     }
 }
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java
index c7e01fb..427ed8d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java
@@ -78,21 +78,21 @@ public class ParametersDetailsPanel extends Panel {
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private Panel getFieldPanel(final String id, final AttrTO attrTO) {
-        final String valueHeaderName = getString("values");
+        String valueHeaderName = getString("values");
 
-        final PlainSchemaTO schemaTO = schemaRestClient.read(SchemaType.PLAIN, attrTO.getSchema());
+        PlainSchemaTO plainSchema = schemaRestClient.read(SchemaType.PLAIN, attrTO.getSchema());
 
-        final FieldPanel panel;
-        switch (schemaTO.getType()) {
+        FieldPanel panel;
+        switch (plainSchema.getType()) {
             case Date:
-                final String datePattern = schemaTO.getConversionPattern() == null
+                final String datePattern = plainSchema.getConversionPattern() == null
                         ? SyncopeConstants.DEFAULT_DATE_PATTERN
-                        : schemaTO.getConversionPattern();
+                        : plainSchema.getConversionPattern();
 
                 if (StringUtils.containsIgnoreCase(datePattern, "H")) {
-                    panel = new AjaxDateTimeFieldPanel("panel", schemaTO.getKey(), new Model<>(), datePattern);
+                    panel = new AjaxDateTimeFieldPanel("panel", plainSchema.getKey(), new Model<>(), datePattern);
                 } else {
-                    panel = new AjaxDateFieldPanel("panel", schemaTO.getKey(), new Model<>(), datePattern);
+                    panel = new AjaxDateFieldPanel("panel", plainSchema.getKey(), new Model<>(), datePattern);
                 }
                 break;
             case Boolean:
@@ -125,7 +125,7 @@ public class ParametersDetailsPanel extends Panel {
                 break;
             case Enum:
                 panel = new AjaxDropDownChoicePanel<>(id, valueHeaderName, new Model<>(), false);
-                ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(schemaTO));
+                ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema));
 
                 if (!attrTO.getValues().isEmpty()) {
                     ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer<String>() {
@@ -150,7 +150,7 @@ public class ParametersDetailsPanel extends Panel {
                     });
                 }
                 ((AjaxDropDownChoicePanel<String>) panel).setNullValid(
-                        "false".equalsIgnoreCase(schemaTO.getMandatoryCondition()));
+                        "false".equalsIgnoreCase(plainSchema.getMandatoryCondition()));
                 break;
 
             case Long:
@@ -164,25 +164,26 @@ public class ParametersDetailsPanel extends Panel {
                 break;
 
             case Binary:
-                panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), schemaTO.getMimeType(),
+                panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), plainSchema.getMimeType(),
                         schema.getModelObject());
                 break;
 
             case Encrypted:
-                panel = new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true);
+                panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern())
+                        ? new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false)
+                        : new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true);
                 break;
 
             default:
                 panel = new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false);
         }
-        if (schemaTO.isMultivalue()) {
+        if (plainSchema.isMultivalue()) {
             return new MultiFieldPanel.Builder<>(
                     new PropertyModel<List<String>>(attrTO, "values")).build(id, valueHeaderName, panel);
-        } else {
-            panel.setNewModel(attrTO.getValues());
         }
 
-        panel.setRequired("true".equalsIgnoreCase(schemaTO.getMandatoryCondition()));
+        panel.setNewModel(attrTO.getValues());
+        panel.setRequired("true".equalsIgnoreCase(plainSchema.getMandatoryCondition()));
         return panel;
     }
 }
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java
index c6e3c88..c873d40 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java
@@ -25,6 +25,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleApplication;
 import org.apache.syncope.client.console.commons.Constants;
@@ -37,6 +38,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPan
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
@@ -82,20 +84,20 @@ public class PlainSchemaDetails extends AbstractSchemaDetailsPanel {
         add(type);
 
         // long, double, date
-        final AjaxTextFieldPanel conversionPattern = new AjaxTextFieldPanel("conversionPattern",
+        AjaxTextFieldPanel conversionPattern = new AjaxTextFieldPanel("conversionPattern",
                 getString("conversionPattern"), new PropertyModel<>(schemaTO, "conversionPattern"));
         add(conversionPattern);
 
-        final WebMarkupContainer conversionParams = new WebMarkupContainer("conversionParams");
+        WebMarkupContainer conversionParams = new WebMarkupContainer("conversionParams");
         conversionParams.setOutputMarkupPlaceholderTag(true);
         conversionParams.add(conversionPattern);
         add(conversionParams);
 
-        final WebMarkupContainer typeParams = new WebMarkupContainer("typeParams");
+        WebMarkupContainer typeParams = new WebMarkupContainer("typeParams");
         typeParams.setOutputMarkupPlaceholderTag(true);
 
         // enum
-        final AjaxTextFieldPanel enumerationValuesPanel = new AjaxTextFieldPanel("panel",
+        AjaxTextFieldPanel enumerationValuesPanel = new AjaxTextFieldPanel("panel",
                 "enumerationValues", new Model<>(null));
 
         enumerationValues = new MultiFieldPanel.Builder<String>(
@@ -176,34 +178,52 @@ public class PlainSchemaDetails extends AbstractSchemaDetailsPanel {
                 "enumerationKeys",
                 new AjaxTextFieldPanel("panel", "enumerationKeys", new Model<String>()));
 
-        final WebMarkupContainer enumParams = new WebMarkupContainer("enumParams");
+        WebMarkupContainer enumParams = new WebMarkupContainer("enumParams");
         enumParams.setOutputMarkupPlaceholderTag(true);
         enumParams.add(enumerationValues);
         enumParams.add(enumerationKeys);
         typeParams.add(enumParams);
 
         // encrypted
-        final AjaxTextFieldPanel secretKey = new AjaxTextFieldPanel("secretKey",
+        AjaxTextFieldPanel secretKey = new AjaxTextFieldPanel("secretKey",
                 getString("secretKey"), new PropertyModel<>(schemaTO, "secretKey"));
 
-        final AjaxDropDownChoicePanel<CipherAlgorithm> cipherAlgorithm = new AjaxDropDownChoicePanel<>(
+        AjaxDropDownChoicePanel<CipherAlgorithm> cipherAlgorithm = new AjaxDropDownChoicePanel<>(
                 "cipherAlgorithm", getString("cipherAlgorithm"),
                 new PropertyModel<>(schemaTO, "cipherAlgorithm"));
-
         cipherAlgorithm.setChoices(Arrays.asList(CipherAlgorithm.values()));
 
-        final WebMarkupContainer encryptedParams = new WebMarkupContainer("encryptedParams");
+        AjaxCheckBoxPanel transparentEncryption = new AjaxCheckBoxPanel(
+                "transparentEncryption", "transparentEncryption", new Model<Boolean>() {
+
+            private static final long serialVersionUID = 5636572627689425575L;
+
+            @Override
+            public Boolean getObject() {
+                return SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(schemaTO.getConversionPattern());
+            }
+
+            @Override
+            public void setObject(final Boolean object) {
+                schemaTO.setConversionPattern(BooleanUtils.isTrue(object)
+                        ? SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN
+                        : null);
+            }
+        }, true);
+
+        WebMarkupContainer encryptedParams = new WebMarkupContainer("encryptedParams");
         encryptedParams.setOutputMarkupPlaceholderTag(true);
         encryptedParams.add(secretKey);
         encryptedParams.add(cipherAlgorithm);
+        encryptedParams.add(transparentEncryption);
 
         typeParams.add(encryptedParams);
 
         // binary
-        final AjaxTextFieldPanel mimeType = new AjaxTextFieldPanel("mimeType",
+        AjaxTextFieldPanel mimeType = new AjaxTextFieldPanel("mimeType",
                 getString("mimeType"), new PropertyModel<>(schemaTO, "mimeType"));
 
-        final WebMarkupContainer binaryParams = new WebMarkupContainer("binaryParams");
+        WebMarkupContainer binaryParams = new WebMarkupContainer("binaryParams");
         binaryParams.setOutputMarkupPlaceholderTag(true);
         binaryParams.add(mimeType);
         typeParams.add(binaryParams);
@@ -301,7 +321,7 @@ public class PlainSchemaDetails extends AbstractSchemaDetailsPanel {
             final AjaxTextFieldPanel secretKey, final AjaxDropDownChoicePanel<CipherAlgorithm> cipherAlgorithm,
             final WebMarkupContainer binaryParams, final AjaxTextFieldPanel mimeType) {
 
-        final int typeOrdinal = Integer.parseInt(type.getField().getValue());
+        int typeOrdinal = Integer.parseInt(type.getField().getValue());
         if (AttrSchemaType.Long.ordinal() == typeOrdinal
                 || AttrSchemaType.Double.ordinal() == typeOrdinal
                 || AttrSchemaType.Date.ordinal() == typeOrdinal) {
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
index 5e894b5..c20df70 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
@@ -177,7 +177,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    protected FieldPanel getFieldPanel(final PlainSchemaTO schemaTO) {
+    protected FieldPanel getFieldPanel(final PlainSchemaTO plainSchema) {
         final boolean required;
         final boolean readOnly;
         final AttrSchemaType type;
@@ -189,9 +189,9 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
             type = AttrSchemaType.String;
             jexlHelp = true;
         } else {
-            required = schemaTO.getMandatoryCondition().equalsIgnoreCase("true");
-            readOnly = schemaTO.isReadonly();
-            type = schemaTO.getType();
+            required = plainSchema.getMandatoryCondition().equalsIgnoreCase("true");
+            readOnly = plainSchema.isReadonly();
+            type = plainSchema.getType();
             jexlHelp = false;
         }
 
@@ -200,27 +200,27 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
             case Boolean:
                 panel = new AjaxCheckBoxPanel(
                         "panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()),
+                        plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
                         new Model<>(),
                         true);
                 panel.setRequired(required);
                 break;
 
             case Date:
-                String datePattern = schemaTO.getConversionPattern() == null
+                String datePattern = plainSchema.getConversionPattern() == null
                         ? SyncopeConstants.DEFAULT_DATE_PATTERN
-                        : schemaTO.getConversionPattern();
+                        : plainSchema.getConversionPattern();
 
                 if (datePattern.contains("H")) {
                     panel = new AjaxDateTimeFieldPanel(
                             "panel",
-                            schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()),
+                            plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
                             new Model<>(),
                             datePattern);
                 } else {
                     panel = new AjaxDateFieldPanel(
                             "panel",
-                            schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()),
+                            plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
                             new Model<>(),
                             datePattern);
                 }
@@ -233,15 +233,15 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
 
             case Enum:
                 panel = new AjaxDropDownChoicePanel<>("panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
-                ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(schemaTO));
+                        plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
+                ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema));
 
-                if (StringUtils.isNotBlank(schemaTO.getEnumerationKeys())) {
+                if (StringUtils.isNotBlank(plainSchema.getEnumerationKeys())) {
                     ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer<String>() {
 
                         private static final long serialVersionUID = -3724971416312135885L;
 
-                        private final Map<String, String> valueMap = SchemaUtils.getEnumeratedKeyValues(schemaTO);
+                        private final Map<String, String> valueMap = SchemaUtils.getEnumeratedKeyValues(plainSchema);
 
                         @Override
                         public String getDisplayValue(final String value) {
@@ -269,7 +269,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
             case Long:
                 panel = new AjaxSpinnerFieldPanel.Builder<Long>().enableOnChange().build(
                         "panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()),
+                        plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
                         Long.class,
                         new Model<>());
 
@@ -281,7 +281,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
             case Double:
                 panel = new AjaxSpinnerFieldPanel.Builder<Double>().enableOnChange().step(0.1).build(
                         "panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()),
+                        plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
                         Double.class,
                         new Model<>());
 
@@ -294,9 +294,9 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
                 final PageReference pageRef = getPageReference();
                 panel = new BinaryFieldPanel(
                         "panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()),
+                        plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
                         new Model<>(),
-                        schemaTO.getMimeType(),
+                        plainSchema.getMimeType(),
                         fileKey) {
 
                     private static final long serialVersionUID = -3268213909514986831L;
@@ -313,8 +313,11 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
                 break;
 
             case Encrypted:
-                panel = new EncryptedFieldPanel("panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
+                panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern())
+                        ? new AjaxTextFieldPanel("panel",
+                                plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true)
+                        : new EncryptedFieldPanel("panel",
+                                plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
 
                 if (required) {
                     panel.addRequiredLabel();
@@ -323,7 +326,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
 
             default:
                 panel = new AjaxTextFieldPanel("panel",
-                        schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
+                        plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true);
 
                 if (jexlHelp) {
                     AjaxTextFieldPanel.class.cast(panel).enableJexlHelp();
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.html
index ac334de..6859c9d 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.html
@@ -45,6 +45,9 @@ under the License.
         <div class="form-group">
           <span wicket:id="cipherAlgorithm">[cipherAlgorithm]</span>
         </div>
+        <div class="form-group">
+          <span wicket:id="transparentEncryption">[transparentEncryption]</span>
+        </div>
       </div>
       <div wicket:id="binaryParams">
         <div class="form-group">
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.properties
index b3f99bb..9367a07 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails.properties
@@ -31,3 +31,4 @@ readonly=Read-only
 secretKey=Secret key
 cipherAlgorithm=Cipher algorithm
 mimeType=MIME Type
+transparentEncryption=Transparent encryption?
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_it.properties
index bd4766b..e9f4449 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_it.properties
@@ -31,3 +31,4 @@ multivalueAndUniqueConstr.validation=Le opzioni 'Multivalore' e 'Vincolo unique'
 secretKey=Chiave segreta
 cipherAlgorithm=Algoritmo di cifratura
 mimeType=MIME Type
+transparentEncryption=Cifratura trasparente?
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ja.properties
index eb9c986..6316527 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ja.properties
@@ -31,3 +31,4 @@ readonly=\u8aad\u307f\u53d6\u308a\u5c02\u7528
 secretKey=\u79d8\u5bc6\u9375
 cipherAlgorithm=\u6697\u53f7\u5316\u30a2\u30eb\u30b4\u30ea\u30ba\u30e0
 mimeType=MIME \u30bf\u30a4\u30d7
+transparentEncryption=Transparent encryption?
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_pt_BR.properties
index f607f99..88e664c 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_pt_BR.properties
@@ -31,3 +31,4 @@ readonly=Apenas leitura
 secretKey=Chave secreta
 cipherAlgorithm=Algoritmo de criptografia
 mimeType=MIME Type
+transparentEncryption=Transparent encryption?
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ru.properties
index b190c79..106c483 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/PlainSchemaDetails_ru.properties
@@ -49,3 +49,4 @@ secretKey=\u041a\u043b\u044e\u0447 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0
 cipherAlgorithm=\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f
 # mimeType=\u00d0\u00a2\u00d0\u00b8\u00d0\u00bf MIME
 mimeType=\u0422\u0438\u043f MIME
+transparentEncryption=Transparent encryption?
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
index 7ed07e1..3495bf6 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
@@ -29,6 +29,11 @@ angular.module('self')
               user: "="
             },
             controller: function ($scope, $rootScope, $element, $window) {
+              $scope.schemaType = function (schema) {
+                return schema.type == "Encrypted" && schema.conversionPattern == 'ENCRYPTED_DECODE_CONVERSION_PATTERN'
+                        ? "String"
+                        : schema.type;
+              };
               $scope.initAttribute = function (schema, index) {
                 switch (schema.type) {
                   case "Long":
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
index c6b5042..7dcd782 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
@@ -16,7 +16,7 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<div ng-switch="schema.type" class="schema-type">
+<div ng-switch="schemaType(schema)" class="schema-type">
   <input ng-switch-when="String" class="form-control" type="text"
          ng-model="user.plainAttrs[schema.key].values[index]"
          ng-required="{{schema.mandatoryCondition}}" validate="true"
@@ -143,8 +143,3 @@ under the License.
          ng-disabled="schema.readonly || customReadonly(schema.key)"
          ng-init="initAttribute(schema, index)"/>
 </div>
-
-
-
-
-
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java b/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
index b8a7ee4..fea103a 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
@@ -63,6 +63,8 @@ public final class SyncopeConstants {
 
     public static final Pattern UUID_PATTERN = Pattern.compile(UUID_REGEX);
 
+    public static final String ENCRYPTED_DECODE_CONVERSION_PATTERN = "ENCRYPTED_DECODE_CONVERSION_PATTERN";
+
     public static final String DOUBLE_DASH = "--";
 
     public static final String CRLF = "\r\n";
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAJSONEntityListener.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAJSONEntityListener.java
index 10b8e77..f89f6d6 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAJSONEntityListener.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAJSONEntityListener.java
@@ -28,7 +28,7 @@ import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 
 public abstract class JPAJSONEntityListener<A extends Any<?>> {
 
-    protected abstract List<? extends JSONPlainAttr<A>> getValues(String plainAttrsJSON);
+    protected abstract List<? extends JSONPlainAttr<A>> getAttrs(String plainAttrsJSON);
 
     @SuppressWarnings("unchecked")
     protected void json2list(final JSONAttributable<A> entity, final boolean clearFirst) {
@@ -36,7 +36,7 @@ public abstract class JPAJSONEntityListener<A extends Any<?>> {
             entity.getPlainAttrList().clear();
         }
         if (entity.getPlainAttrsJSON() != null) {
-            getValues(entity.getPlainAttrsJSON()).stream().filter(attr -> attr.getSchema() != null).map(attr -> {
+            getAttrs(entity.getPlainAttrsJSON()).stream().filter(attr -> attr.getSchema() != null).map(attr -> {
                 if (entity instanceof Any) {
                     attr.setOwner((A) entity);
                 } else if (entity instanceof LinkedAccount) {
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAJSONAnyObjectListener.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAJSONAnyObjectListener.java
index ad03fc5..f554332 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAJSONAnyObjectListener.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAJSONAnyObjectListener.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
 public class JPAJSONAnyObjectListener extends JPAJSONEntityListener<AnyObject> {
 
     @Override
-    protected List<? extends JSONPlainAttr<AnyObject>> getValues(final String plainAttrsJSON) {
+    protected List<? extends JSONPlainAttr<AnyObject>> getAttrs(final String plainAttrsJSON) {
         return POJOHelper.deserialize(plainAttrsJSON, new TypeReference<List<JPAJSONAPlainAttr>>() {
         });
     }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/conf/JPAJSONConfListener.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/conf/JPAJSONConfListener.java
index 7923c47..b10562d 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/conf/JPAJSONConfListener.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/conf/JPAJSONConfListener.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
 public class JPAJSONConfListener extends JPAJSONEntityListener<Conf> {
 
     @Override
-    protected List<? extends JSONPlainAttr<Conf>> getValues(final String plainAttrsJSON) {
+    protected List<? extends JSONPlainAttr<Conf>> getAttrs(final String plainAttrsJSON) {
         return POJOHelper.deserialize(plainAttrsJSON, new TypeReference<List<JPAJSONCPlainAttr>>() {
         });
     }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAJSONGroupListener.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAJSONGroupListener.java
index a259238..0574898 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAJSONGroupListener.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAJSONGroupListener.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
 public class JPAJSONGroupListener extends JPAJSONEntityListener<Group> {
 
     @Override
-    protected List<? extends JSONPlainAttr<Group>> getValues(final String plainAttrsJSON) {
+    protected List<? extends JSONPlainAttr<Group>> getAttrs(final String plainAttrsJSON) {
         return POJOHelper.deserialize(plainAttrsJSON, new TypeReference<List<JPAJSONGPlainAttr>>() {
         });
     }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONLinkedAccountListener.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONLinkedAccountListener.java
index 3a71a95..26436a4 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONLinkedAccountListener.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONLinkedAccountListener.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 public class JPAJSONLinkedAccountListener extends JPAJSONEntityListener<User> {
 
     @Override
-    protected List<? extends JSONLAPlainAttr> getValues(final String plainAttrsJSON) {
+    protected List<? extends JSONLAPlainAttr> getAttrs(final String plainAttrsJSON) {
         return POJOHelper.deserialize(plainAttrsJSON, new TypeReference<List<JPAJSONLAPlainAttr>>() {
         });
     }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUserListener.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUserListener.java
index 66f8b08..1b924cc 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUserListener.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAJSONUserListener.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
 public class JPAJSONUserListener extends JPAJSONEntityListener<User> {
 
     @Override
-    protected List<? extends JSONPlainAttr<User>> getValues(final String plainAttrsJSON) {
+    protected List<? extends JSONPlainAttr<User>> getAttrs(final String plainAttrsJSON) {
         return POJOHelper.deserialize(plainAttrsJSON, new TypeReference<List<JPAJSONUPlainAttr>>() {
         });
     }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
index 94fb108..b8d1a08 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
@@ -36,8 +36,8 @@ public class JPAJSONAttributableValidator extends AbstractValidator<JPAJSONAttri
         entity.getPlainAttrList().forEach(attr -> {
             PlainAttr<?> plainAttr = (PlainAttr<?>) attr;
             isValid.getAndSet(isValid.get() && attrValidator.isValid(plainAttr, context));
-            plainAttr.getValues().forEach(value
-                    -> isValid.getAndSet(isValid.get() && attrValueValidator.isValid(value, context)));
+            plainAttr.getValues().forEach(
+                    value -> isValid.getAndSet(isValid.get() && attrValueValidator.isValid(value, context)));
         });
 
         return isValid.get();
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttrValue.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttrValue.java
index 2ed3468..dcb9e54 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttrValue.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttrValue.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.persistence.jpa.entity;
 
 import java.util.Base64;
 import java.util.Date;
+import java.util.regex.Pattern;
 import javax.persistence.Lob;
 import javax.persistence.MappedSuperclass;
 import javax.persistence.Temporal;
@@ -27,12 +28,14 @@ import javax.persistence.TemporalType;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.jpa.validation.entity.PlainAttrValueCheck;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.spring.security.Encryptor;
 
 @MappedSuperclass
@@ -41,6 +44,8 @@ public abstract class AbstractPlainAttrValue extends AbstractGeneratedKeyEntity
 
     private static final long serialVersionUID = -9141923816611244785L;
 
+    private static final Pattern SPRING_ENV_PROPERTY = Pattern.compile("^\\$\\{.*\\}$");
+
     private String stringValue;
 
     @Temporal(TemporalType.TIMESTAMP)
@@ -127,6 +132,13 @@ public abstract class AbstractPlainAttrValue extends AbstractGeneratedKeyEntity
         this.binaryValue = ArrayUtils.clone(binaryValue);
     }
 
+    protected String getSecretKey(final PlainSchema schema) {
+        return SPRING_ENV_PROPERTY.matcher(schema.getSecretKey()).matches()
+                ? ApplicationContextProvider.getApplicationContext().getEnvironment().
+                        getProperty(StringUtils.substringBetween(schema.getSecretKey(), "${", "}"))
+                : schema.getSecretKey();
+    }
+
     @Override
     public void parseValue(final PlainSchema schema, final String value) {
         Exception exception = null;
@@ -139,42 +151,42 @@ public abstract class AbstractPlainAttrValue extends AbstractGeneratedKeyEntity
 
             case Long:
                 try {
-                    this.setLongValue(schema.getConversionPattern() == null
-                            ? Long.valueOf(value)
-                            : FormatUtils.parseNumber(value, schema.getConversionPattern()).longValue());
-                } catch (Exception pe) {
-                    exception = pe;
-                }
-                break;
+                this.setLongValue(schema.getConversionPattern() == null
+                        ? Long.valueOf(value)
+                        : FormatUtils.parseNumber(value, schema.getConversionPattern()).longValue());
+            } catch (Exception pe) {
+                exception = pe;
+            }
+            break;
 
             case Double:
                 try {
-                    this.setDoubleValue(schema.getConversionPattern() == null
-                            ? Double.valueOf(value)
-                            : FormatUtils.parseNumber(value, schema.getConversionPattern()).doubleValue());
-                } catch (Exception pe) {
-                    exception = pe;
-                }
-                break;
+                this.setDoubleValue(schema.getConversionPattern() == null
+                        ? Double.valueOf(value)
+                        : FormatUtils.parseNumber(value, schema.getConversionPattern()).doubleValue());
+            } catch (Exception pe) {
+                exception = pe;
+            }
+            break;
 
             case Date:
                 try {
-                    this.setDateValue(schema.getConversionPattern() == null
-                            ? FormatUtils.parseDate(value)
-                            : new Date(FormatUtils.parseDate(value, schema.getConversionPattern()).getTime()));
-                } catch (Exception pe) {
-                    exception = pe;
-                }
-                break;
+                this.setDateValue(schema.getConversionPattern() == null
+                        ? FormatUtils.parseDate(value)
+                        : new Date(FormatUtils.parseDate(value, schema.getConversionPattern()).getTime()));
+            } catch (Exception pe) {
+                exception = pe;
+            }
+            break;
 
             case Encrypted:
                 try {
-                    this.setStringValue(Encryptor.getInstance(schema.getSecretKey()).
-                            encode(value, schema.getCipherAlgorithm()));
-                } catch (Exception pe) {
-                    exception = pe;
-                }
-                break;
+                this.setStringValue(Encryptor.getInstance(getSecretKey(schema)).
+                        encode(value, schema.getCipherAlgorithm()));
+            } catch (Exception pe) {
+                exception = pe;
+            }
+            break;
 
             case Binary:
                 this.setBinaryValue(Base64.getDecoder().decode(value));
@@ -187,8 +199,8 @@ public abstract class AbstractPlainAttrValue extends AbstractGeneratedKeyEntity
         }
 
         if (exception != null) {
-            throw new ParsingValidationException("While trying to parse '" + value + "' as " + schema.getKey(),
-                    exception);
+            throw new ParsingValidationException(
+                    "While trying to parse '" + value + "' as " + schema.getKey(), exception);
         }
     }
 
@@ -282,9 +294,25 @@ public abstract class AbstractPlainAttrValue extends AbstractGeneratedKeyEntity
                 result = Base64.getEncoder().encodeToString(getBinaryValue());
                 break;
 
+            case Encrypted:
+                if (schema == null
+                        || !SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(schema.getConversionPattern())
+                        || !schema.getCipherAlgorithm().isInvertible()) {
+
+                    result = getStringValue();
+                } else {
+                    try {
+                        result = Encryptor.getInstance(getSecretKey(schema)).
+                                decode(getStringValue(), schema.getCipherAlgorithm());
+                    } catch (Exception e) {
+                        LOG.error("Could not decode encrypted value {} for schema {}", getStringValue(), schema, e);
+                        result = getStringValue();
+                    }
+                }
+                break;
+
             case String:
             case Enum:
-            case Encrypted:
             default:
                 result = getStringValue();
         }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
index 856e570..54c773f 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
@@ -19,19 +19,27 @@
 package org.apache.syncope.core.persistence.jpa.inner;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Random;
 import javax.validation.ValidationException;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -41,6 +49,7 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.jpa.AbstractTest;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -58,6 +67,9 @@ public class PlainAttrTest extends AbstractTest {
     @Autowired
     private PlainSchemaDAO plainSchemaDAO;
 
+    @Autowired
+    private AnyTypeClassDAO anyTypeClassDAO;
+
     @Tag("plainAttrTable")
     @Test
     public void findByKey() {
@@ -229,6 +241,56 @@ public class PlainAttrTest extends AbstractTest {
     }
 
     @Test
+    public void encryptedWithKeyAsSysProp() throws Exception {
+        PlainSchema obscureSchema = plainSchemaDAO.find("obscure");
+        assertNotNull(obscureSchema);
+
+        PlainSchema obscureWithKeyAsSysprop = entityFactory.newEntity(PlainSchema.class);
+        obscureWithKeyAsSysprop.setKey("obscureWithKeyAsSysprop");
+        obscureWithKeyAsSysprop.setAnyTypeClass(obscureSchema.getAnyTypeClass());
+        obscureWithKeyAsSysprop.setType(AttrSchemaType.Encrypted);
+        obscureWithKeyAsSysprop.setCipherAlgorithm(obscureSchema.getCipherAlgorithm());
+        obscureWithKeyAsSysprop.setSecretKey("${obscureSecretKey}");
+
+        obscureWithKeyAsSysprop = plainSchemaDAO.save(obscureWithKeyAsSysprop);
+
+        System.setProperty("obscureSecretKey", obscureSchema.getSecretKey());
+
+        UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
+        attr.setSchema(obscureWithKeyAsSysprop);
+        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+
+        assertEquals(Encryptor.getInstance(obscureSchema.getSecretKey()).
+                encode("testvalue", obscureSchema.getCipherAlgorithm()), attr.getValues().get(0).getStringValue());
+    }
+
+    @Test
+    public void encryptedWithDecodeConversionPattern() throws Exception {
+        PlainSchema obscureWithDecodeConversionPattern = entityFactory.newEntity(PlainSchema.class);
+        obscureWithDecodeConversionPattern.setKey("obscureWithDecodeConversionPattern");
+        obscureWithDecodeConversionPattern.setAnyTypeClass(anyTypeClassDAO.find("other"));
+        obscureWithDecodeConversionPattern.setType(AttrSchemaType.Encrypted);
+        obscureWithDecodeConversionPattern.setCipherAlgorithm(CipherAlgorithm.AES);
+        obscureWithDecodeConversionPattern.setSecretKey(SecureRandomUtils.generateRandomUUID().toString());
+
+        obscureWithDecodeConversionPattern = plainSchemaDAO.save(obscureWithDecodeConversionPattern);
+
+        UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
+        attr.setSchema(obscureWithDecodeConversionPattern);
+        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        
+        assertEquals(Encryptor.getInstance(obscureWithDecodeConversionPattern.getSecretKey()).
+                encode("testvalue", obscureWithDecodeConversionPattern.getCipherAlgorithm()),
+                attr.getValues().get(0).getStringValue());
+
+        obscureWithDecodeConversionPattern.setConversionPattern(SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN);
+        plainSchemaDAO.save(obscureWithDecodeConversionPattern);
+
+        assertNotEquals("testvalue", attr.getValues().get(0).getStringValue());
+        assertEquals("testvalue", attr.getValuesAsStrings().get(0));
+    }
+
+    @Test
     public void saveWithBinary() throws UnsupportedEncodingException {
         User user = userDAO.find("1417acbe-cbf6-4277-9372-e75e04f97000");
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index abb122b..ba46f9b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -337,9 +337,8 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
         SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
 
         // memberships
-        anyObjectPatch.getMemberships().stream().
-                filter((membPatch) -> (membPatch.getGroup() != null)).forEachOrdered(membPatch -> {
-            anyObject.getMembership(membPatch.getGroup()).ifPresent(membership -> {
+        anyObjectPatch.getMemberships().stream().filter(patch -> patch.getGroup() != null).forEach(patch -> {
+            anyObject.getMembership(patch.getGroup()).ifPresent(membership -> {
                 anyObject.remove(membership);
                 membership.setLeftEnd(null);
                 anyObject.getPlainAttrs(membership).forEach(attr -> {
@@ -349,7 +348,7 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
                     plainAttrValueDAO.deleteAll(attr, anyUtils);
                 });
 
-                if (membPatch.getOperation() == PatchOperation.DELETE) {
+                if (patch.getOperation() == PatchOperation.DELETE) {
                     groupDAO.findAllResourceKeys(membership.getRightEnd().getKey()).stream().
                             filter(resource -> reasons.containsKey(resource)).
                             forEach(resource -> {
@@ -358,10 +357,10 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
                             });
                 }
             });
-            if (membPatch.getOperation() == PatchOperation.ADD_REPLACE) {
-                Group group = groupDAO.find(membPatch.getGroup());
+            if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
+                Group group = groupDAO.find(patch.getGroup());
                 if (group == null) {
-                    LOG.debug("Ignoring invalid group {}", membPatch.getGroup());
+                    LOG.debug("Ignoring invalid group {}", patch.getGroup());
                 } else if (anyObject.getRealm().getFullPath().startsWith(group.getRealm().getFullPath())) {
                     AMembership newMembership = entityFactory.newEntity(AMembership.class);
                     newMembership.setRightEnd(group);
@@ -369,7 +368,7 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
 
                     anyObject.add(newMembership);
 
-                    membPatch.getPlainAttrs().forEach(attrTO -> {
+                    patch.getPlainAttrs().forEach(attrTO -> {
                         PlainSchema schema = getPlainSchema(attrTO.getSchema());
                         if (schema == null) {
                             LOG.debug("Invalid " + PlainSchema.class.getSimpleName()
@@ -387,10 +386,15 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
                                 newAttr.setSchema(schema);
                                 anyObject.add(newAttr);
 
-                                AttrPatch patch = new AttrPatch.Builder().attrTO(attrTO).build();
                                 processAttrPatch(
-                                        anyObject, patch, schema, newAttr, anyUtils,
-                                        resources, propByRes, invalidValues);
+                                        anyObject,
+                                        new AttrPatch.Builder().attrTO(attrTO).build(),
+                                        schema,
+                                        newAttr,
+                                        anyUtils,
+                                        resources,
+                                        propByRes,
+                                        invalidValues);
                             }
                         }
                     });
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 429c38d..517a63d 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -36,7 +36,9 @@ under the License.
   <properties>
     <jdbcdriver.groupId>com.h2database</jdbcdriver.groupId>
     <jdbcdriver.artifactId>h2</jdbcdriver.artifactId>
-    
+
+    <obscureSecretKey>obscureSecretKeyValue</obscureSecretKey>
+
     <rootpom.basedir>${basedir}/../..</rootpom.basedir>
     <cli-test.dir>${project.build.directory}/cli-test</cli-test.dir>
   </properties>
@@ -294,6 +296,12 @@ under the License.
         <inherited>true</inherited>
         <configuration>
           <container>
+            <systemProperties>
+              <java.security.egd>file:/dev/./urandom</java.security.egd>
+              <java.util.secureRandomSeed>true</java.util.secureRandomSeed>
+              
+              <obscureSecretKey>${obscureSecretKey}</obscureSecretKey>
+            </systemProperties>
             <dependencies>
               <dependency>
                 <groupId>com.h2database</groupId>
@@ -451,6 +459,7 @@ under the License.
             <configuration>
               <systemPropertyVariables>
                 <jaxrsContentType>${jaxrs.content.type}</jaxrsContentType>
+                <obscureSecretKey>${obscureSecretKey}</obscureSecretKey>
               </systemPropertyVariables>
               <includes>
                 <include>**/*ITCase.java</include>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
index d8a9894..3a20570 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
@@ -20,6 +20,7 @@ package org.apache.syncope.fit.core;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -76,6 +77,7 @@ import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ConnectorCapability;
 import org.apache.syncope.common.lib.types.MappingPurpose;
@@ -90,6 +92,7 @@ import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
+import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Test;
 
@@ -595,6 +598,57 @@ public class GroupITCase extends AbstractITCase {
     }
 
     @Test
+    public void encrypted() throws Exception {
+        // 1. create encrypted schema with secret key as system property
+        PlainSchemaTO encrypted = new PlainSchemaTO();
+        encrypted.setKey("encrypted" + getUUIDString());
+        encrypted.setType(AttrSchemaType.Encrypted);
+        encrypted.setCipherAlgorithm(CipherAlgorithm.SHA512);
+        encrypted.setSecretKey("${obscureSecretKey}");
+        schemaService.create(SchemaType.PLAIN, encrypted);
+
+        // 2. add the new schema to the default group type
+        AnyTypeTO type = anyTypeService.read(AnyTypeKind.GROUP.name());
+        String typeClassName = type.getClasses().get(0);
+        AnyTypeClassTO typeClass = anyTypeClassService.read(typeClassName);
+        typeClass.getPlainSchemas().add(encrypted.getKey());
+        anyTypeClassService.update(typeClass);
+        typeClass = anyTypeClassService.read(typeClassName);
+        assertTrue(typeClass.getPlainSchemas().contains(encrypted.getKey()));
+
+        // 3. create group, verify that the correct encrypted value is returned
+        GroupTO group = getSampleTO("encrypted");
+        group.getPlainAttrs().add(new AttrTO.Builder().schema(encrypted.getKey()).value("testvalue").build());
+        group = createGroup(group).getEntity();
+
+        assertEquals(Encryptor.getInstance(System.getProperty("obscureSecretKey")).
+                encode("testvalue", encrypted.getCipherAlgorithm()),
+                group.getPlainAttr(encrypted.getKey()).get().getValues().get(0));
+
+        // 4. update schema to return cleartext values
+        encrypted.setAnyTypeClass(typeClassName);
+        encrypted.setCipherAlgorithm(CipherAlgorithm.AES);
+        encrypted.setConversionPattern(SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN);
+        schemaService.update(SchemaType.PLAIN, encrypted);
+
+        // 5. update group, verify that the cleartext value is returned
+        GroupPatch patch = new GroupPatch();
+        patch.setKey(group.getKey());
+        patch.getPlainAttrs().add(new AttrPatch.Builder().
+                attrTO(new AttrTO.Builder().schema(encrypted.getKey()).value("testvalue").build()).build());
+        group = updateGroup(patch).getEntity();
+
+        assertEquals("testvalue", group.getPlainAttr(encrypted.getKey()).get().getValues().get(0));
+
+        // 6. update schema again to disallow cleartext values
+        encrypted.setConversionPattern(null);
+        schemaService.update(SchemaType.PLAIN, encrypted);
+
+        group = groupService.read(group.getKey());
+        assertNotEquals("testvalue", group.getPlainAttr(encrypted.getKey()).get().getValues().get(0));
+    }
+
+    @Test
     public void anonymous() {
         GroupService unauthenticated = clientFactory.create().getService(GroupService.class);
         try {
diff --git a/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc b/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc
index 0e77562..07e1c7f 100644
--- a/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/typemanagement.adoc
@@ -50,8 +50,10 @@ http://docs.oracle.com/javase/8/docs/api/java/text/DateFormat.html[DateFormat^]
 *** enumeration values (mandatory)
 *** enumeration labels (optional, values will be used alternatively)
 ** `Encrypted`
-*** secret key
+*** secret key (stored or referenced as https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-property-source-abstraction[Spring property^])
 *** cipher algorithm
+*** whether transparent encryption is to be enabled, e.g. attribute values are stored as encrypted but available as
+cleartext on-demand (requires AES ciphering)
 ** `Binary` - it is required to provide the declared mime type
 * Validator class - (optional) Java class validating the value(s) provided for attributes, see 
 ifeval::["{snapshotOrRelease}" == "release"]