18

In our application we have to encrypt/decrypt the Json property values (not the property name) for each request and response. Example,
{"userName":"encrypted value", "email":"encrypted value"}

We use Sprint boot 1.3 and we are using @RequestBody and @ResponseBody annotations to bind the request json with the object and serialise the response object as JSON.

We don't want to call encrypt/decrypt method in our each controller method. Is there any way we can instruct sprint to decrypt the json values before binding with the request object? Similarly, to encrypt the response object field values before converting them to json? Or customising Jackson may help us?

Thanks!

4
  • Build a custom client and do it there before call the service .. Commented Nov 22, 2016 at 11:52
  • What about implementing decrypt in getter and encrypt in setter? Commented Nov 22, 2016 at 11:57
  • 1
    Wouldn't breaking into, or extending AbstractJackson2HttpMessageConverter be more specific as this is where the json response is built, and the last point before it is flushed? Commented Jan 13, 2017 at 15:33
  • 1
    Have you found the solution? I am facing the same problem and @eparvan 's answer is irrelevant! Commented Nov 26, 2019 at 13:01

2 Answers 2

18

You can write your own http message converter. Since you are using spring boot it would be quite easy: just extend your custom converter from AbstractHttpMessageConverter and mark the class with @Component annotation.

From spring docs:

You can contribute additional converters by simply adding beans of that type in a Spring Boot context. If a bean you add is of a type that would have been included by default anyway (like MappingJackson2HttpMessageConverter for JSON conversions) then it will replace the default value.

And here is a simple example:

@Component
public class Converter extends AbstractHttpMessageConverter<Object> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    @Inject
    private ObjectMapper objectMapper;

    public Converter(){
        super(MediaType.APPLICATION_JSON_UTF8,
            new MediaType("application", "*+json", DEFAULT_CHARSET));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz,
                                  HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return objectMapper.readValue(decrypt(inputMessage.getBody()), clazz);
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        outputMessage.getBody().write(encrypt(objectMapper.writeValueAsBytes(o)));
    }

    private InputStream decrypt(InputStream inputStream){
        // do your decryption here 
        return inputStream;
    }

    private byte[] encrypt(byte[] bytesToEncrypt){
        // do your encryption here 
        return bytesToEncrypt;
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

This will work if the whole json is encrypted. But in our case, only the json property values will be encrypted.
It's up to you to decide how to implement encrypt and decrypt methods. I think you may use JsonNode to iterate through all json object values in order to encrypt/decrypt them.
@eparvan what if the client is sending me a key in the request (in header), and I need to encrypt the response with it? How can I propagate the header value from readInternal to writeInternal method?
@Jezor use inputMessage.getHeaders() in readInternal and also same in writeInternal.
what about if few fields are encrypted and few fields are not ? How can we differentiate ?
|
1

Okay, so I used @eparvan 's answer and made few modifications.

  1. Create a component that encrypts the JSON response and decrypt the request params from frontend.

I am fetching request params in encrypted format in "data" object something like this and also sending the encrypted response in the same way data object.

reference response: {"data":"requestOrResponseInEncryptedUsingPrivateKey"}

    @Component
    public class Converter extends AbstractHttpMessageConverter<Object> {

        private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

        @Autowired
        private ObjectMapper objectMapper;

        public Converter() {
            super(MediaType.APPLICATION_JSON,
                    new MediaType("application", "*+json", DEFAULT_CHARSET));
        }

        @Override
        protected boolean supports(Class<?> clazz) {
            return true;
        }

        @Override
        protected Object readInternal(Class<? extends Object> clazz,
                                      HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            return objectMapper.readValue(decrypt(inputMessage.getBody()), clazz);
        }

        @Override
        protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            outputMessage.getBody().write(encrypt(objectMapper.writeValueAsBytes(o)));
        }

        /**
         * requests params of any API
         *
         * @param inputStream inputStream
         * @return inputStream
         */
        private InputStream decrypt(InputStream inputStream) {
            //this is API request params
            StringBuilder requestParamString = new StringBuilder();
            try (Reader reader = new BufferedReader(new InputStreamReader
                    (inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
                int c;
                while ((c = reader.read()) != -1) {
                    requestParamString.append((char) c);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                //replacing /n if available in request param json string

                //reference request: {"data":"thisisencryptedstringwithexpirytime"}

                JSONObject requestJsonObject = new
                        JSONObject(requestParamString.toString().replace("\n", ""));

                String decryptRequestString = EncryptDecrypt.decrypt(requestJsonObject.getString("data"));
                System.out.println("decryptRequestString: " + decryptRequestString);

                if (decryptRequestString != null) {
return new ByteArrayInputStream(decryptRequestString.getBytes(StandardCharsets.UTF_8));
                } else {
                    return inputStream;
                }
            } catch (JSONException err) {
                Log.d("Error", err.toString());
                return inputStream;
            }
        }

        /**
         * response of API
         *
         * @param bytesToEncrypt byte array of response
         * @return byte array of response
         */
        private byte[] encrypt(byte[] bytesToEncrypt) {
            // do your encryption here
            String apiJsonResponse = new String(bytesToEncrypt);

            String encryptedString = EncryptDecrypt.encrypt(apiJsonResponse);
            if (encryptedString != null) {
                //sending encoded json response in data object as follows

                //reference response: {"data":"thisisencryptedstringresponse"}

                Map<String, String> hashMap = new HashMap<>();
                hashMap.put("data", encryptedString);
                JSONObject jsob = new JSONObject(hashMap);
                return jsob.toString().getBytes();
            } else
                return bytesToEncrypt;
        }
    }
  1. Here is my EncryptDecrypt class where encryption and decryption is going on

    class EncryptDecrypt {
    
        static String encrypt(String value) {
            try {
                IvParameterSpec iv = new IvParameterSpec(Constants.Encryption.INIT_VECTOR.getBytes(StandardCharsets.UTF_8));
                SecretKeySpec skeySpec = new
                        SecretKeySpec("PRIVATE_KEY_FOR_ENCRYPTION_OR_DECRYPTION"
                        .getBytes(StandardCharsets.UTF_8), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
    
                byte[] encrypted = cipher.doFinal(value.getBytes());
                byte[] original = Base64.getEncoder().encode(encrypted);
                return new String(original);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }
    
        static String decrypt(String encrypted) {
            try {
                IvParameterSpec iv = new IvParameterSpec(Constants.Encryption.INIT_VECTOR
                        .getBytes(StandardCharsets.UTF_8));
                SecretKeySpec skeySpec = new SecretKeySpec("PRIVATE_KEY_FOR_ENCRYPTION_OR_DECRYPTION".
                        getBytes(StandardCharsets.UTF_8), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
                byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
                return new String(original);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
    
            return null;
        }
    

    }

And you're done!

4 Comments

Whenever you share some code please do share import statements also.
Is there a way I can prevent this conversion for responses coming from the request to certain path? e.g: ('/authenticate')
i have useed readInternal method issue with the encrypted request the error show -[AbstractHandlerExceptionResolver.java:207]-Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported] have you faced this issue ? have any solution for this ?
what do I import for Constants.Encryption.INIT_VECTOR ??

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.