0
\$\begingroup\$

I was curious on how annotations are used during runtime, so after some googling I gave it a try, but kinda have the feeling that is not the proper way as I still have to call the annotation helper on the object. Maybe I'm confusing annotations with Typescript decorators where you can have all the code and logic inside the decorator and you'll just have to decorate a class / field etc.

Anyway here's the code and thanks for having a look, any constructive feedback and/or more resources to read more than welcomed.

Edit. Forgot what I wanted to do

Basically was thinking a class that will map to anything outside the program can encrypt a value and when retrieved its encrypted value in another class that value could be decrypted.

package org.example;

import org.example.annotations.implementation.EncryptDecrypt;
import org.example.models.Person;
import org.example.models.PersonDTO;

public class App 
{
    public static void main( String[] args )
    {

        PersonDTO pdto = new PersonDTO("[email protected]", "DTO");

        EncryptDecrypt.encrypt(pdto);

        System.out.println("pdto email: " + pdto.getEmail());
        Person p = new Person(pdto.getEmail(), pdto.getName());

        EncryptDecrypt.decrypt(p);
        System.out.println("person email: " + p.getEmail());


    }
}
package org.example.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Decrypt {
}
package org.example.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {
}
package org.example.annotations.implementation;

import org.example.annotations.Decrypt;
import org.example.annotations.Encrypt;
import org.example.utils.UselessProtection;

import java.lang.reflect.Field;
import java.util.Arrays;

public class EncryptDecrypt {
    public static void encrypt(Object obj) {
        if (obj == null) return;
        Class<?> cl = obj.getClass();

        for (Field field : cl.getDeclaredFields()) {
            var annotatedField = field.getAnnotation(Encrypt.class);
            if (annotatedField != null) {
                field.setAccessible(true);
                try {
                    var methods = obj.getClass().getMethods();
                    var methodOptional = Arrays.stream(methods).filter(m -> m.getName().equals("getEmail")).findFirst();
                    var toEncrypt = (String) methodOptional.get().invoke(obj);

                    field.set(obj, UselessProtection.encrypt(toEncrypt));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void decrypt(Object obj) {
        if (obj == null) return;
        Class<?> cl = obj.getClass();

        for (Field field : cl.getDeclaredFields()) {
            var annotatedField = field.getAnnotation(Decrypt.class);
            if (annotatedField != null) {
                field.setAccessible(true);
                try {
                    var methods = obj.getClass().getMethods();
                    var methodOptional = Arrays.stream(methods).filter(m -> m.getName().equals("getEmail")).findFirst();
                    var toDecrypt = (String) methodOptional.get().invoke(obj);

                    field.set(obj, UselessProtection.decrypt(toDecrypt));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
package org.example.utils;

public class UselessProtection {

    public static String encrypt(String input) {
        return new StringBuilder(input).reverse().toString();
    }

    public static String decrypt(String input) {
        return encrypt(input);
    }
}

Person and PersonDTO are the same, just different classes

package org.example.models;

import org.example.annotations.Encrypt;

public class PersonDTO {

    @Encrypt
    private final String email;
    private final String name;

    public PersonDTO(String email, String name) {
        this.email = email;
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }
}
class Person {
    // same as PersonDTO apart from
    @Decrypt
    private final String email;
}
\$\endgroup\$
1
  • \$\begingroup\$ A proxy, either a Java proxy of an interface, or one generated by bytecode manipulation, might be a better way of doing this. \$\endgroup\$
    – tgdavies
    Commented Jan 5, 2023 at 23:44

1 Answer 1

1
\$\begingroup\$

I personally dislike annotations when thay are used to control program behaviour. They add an "invisible" layer that is fairly hard to unit test and maintain. And in this case, if someone breaks the annotation processing configuration, suddenly you're no longer encrypting stuff you're supposed to encrypt and you have no idea that it's happening. That might lead to a pretty big oof (and I mean "punitive damages" type of oof). I think they are fairly useful for handling auxiliary functionality like caching and audit logging, but for altering actual data that is passed within the program I would use an explicit code approach.

String is the wrong data type for holding sensitive decrypted information. Therefore just decrypting the data on the fly and maintaining the String data type is an incorrect approach. Decrypted String data should be stored in a char array, and cleared explicitely using Arrays.fill(...) immediately after use. If it is stored in a String, the decrypted secret will be held in memory for extended period of time until it gets garbage collected, which increases the risk that it gets accidentally leaked.

You might want to consider a specific EncryptedString data type for encrypted strings to hold the encrypted data, encryption algorithm, etc. and then provide a service to decrypt it into a char array when needed.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.