Criptografia Assimétrica Java Usando JDK

Casos de uso

  • Criptografia assimétrica

Versão Java

  • JDK 11

Código de exemplo para criptografia assimétrica RSA 4096 usando Java

package com.cryptoexamples.java;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Exemplo para criptografia assimétrica e descriptografia de uma string em um método.
 * - Geração de par de chaves público e privado RSA 4096 bits
 * - Codificação BASE64 como representação para as matrizes de bytes
 * - Codificação UTF-8 de Strings
 * - Exception handling
 */
public class ExampleAsymmetricStringEncryption {
  private static final Logger LOGGER = Logger.getLogger(ExampleAsymmetricStringEncryption.class.getName());

  public static void main(String[] args) {
    String plainText = "Text that is going to be sent over an insecure channel and must be encrypted at all costs!";
    try {
      // GENERATE NEW KEYPAIR
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      /* @see https://www.keylength.com/ */
      keyPairGenerator.initialize(4096);
      KeyPair keyPair = keyPairGenerator.generateKeyPair();

      // ENCRYPTION
      Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
      cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());

      byte[] cipherTextBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));

      // CONVERSION of raw bytes to BASE64 representation
      String cipherText = Base64.getEncoder().encodeToString(cipherTextBytes);

      // DECRYPTION
      cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
      byte[] decryptedCipherTextBytes = cipher.doFinal(Base64.getDecoder().decode(cipherText));
      String decryptedCipherText = new String(decryptedCipherTextBytes,StandardCharsets.UTF_8);

      LOGGER.log(Level.INFO, () -> String.format("Decrypted and original plain text are the same: %b", decryptedCipherText.compareTo(plainText) == 0));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidParameterException  e) {
      LOGGER.log(Level.SEVERE, e.getLocalizedMessage());
    }
  }
}

References

Arquitetura Hexagonal com Java

Arquitetura hexagonal é um estilo que fala sobre camadas de seus objetos de uma forma que isola sua lógica central de elementos externos. A lógica principal é a parte específica do seu negócio, e os elementos externos são como pontos de integração, por exemplo, bancos de dados, APIs externas, interfaces de usuário e outros. Ele divide o software nas partes interna e externa. As partes internas contêm a lógica do Core Business e a camada Domain (explicada em Layered Architecture). A parte externa consiste em interface do usuário, banco de dados, mensagens e outras coisas. Partes internas e externas se comunicam entre si por meio de portas e adaptadores.

Benefícios

  • O software desenvolvido usando esta arquitetura é independente de canais e pode suportar múltiplos canais
  • Fácil de trocar os pontos de integração de entrada e saída
  • Testar o software torna-se fácil porque podemos simular pontos de integração facilmente

Implementação em Java

Como explicado acima, a arquitetura hexagonal é mais em torno de portas e adaptadores. Em Java, as interfaces implementam as portas e a classe de implementação funciona como os adaptadoresEntão, vamos dar uma olhada em um exemplo simples usando o Spring Boot e ver como esse estilo pode ser aplicado a este framework.

Nesta aplicação, temos a funcionalidade para criar / visualizar detalhes do empregado. A lógica do Core Business está no  EmployeeService e o domínio é umEmployeeEntão, essas serão consideradas partes internas 

@Service
public class EmployeeService {
    @Autowired
    private EmployeeRepositoryPort employeeRepository;
    public void create(String name, String role, long salary){
        employeeRepository.create(name, role, salary);
    }
    public Employee view(Integer userId){
        return employeeRepository.getEmployee(userId);
    }
}
@Entity
@Table(name = "employee")
public class Employee{
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Integer id;
    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "role", nullable = false)
    private String role;
    @Column(name = "salary", nullable = false)
    private long salary;
    // Setter and Getter methods
}

Portanto, agora, esse aplicativo pode expor essa funcionalidade via REST ou Messaging. Assim, criamos o  EmployeeControllerAdapter para expor endpoints REST, que implementa o EmployeeUIPort.

@RestController
@RequestMapping("/employees/")
public class EmployeeControllerAdapter implements EmployeeUIPort{
    @Autowired
    private EmployeeService employeeService;
   @Override
    public void create(@RequestBody Employee request) {
        employeeService.create(request.getName(), request.getRole(), request.getSalary());
    }
    @Override
    public Employee view(@PathVariable Integer id) {
        Employee employee = employeeService.view(id);
        return employee;
    }
}
 public interface EmployeeUIPort {
    @PostMapping("create")
    public void create(@RequestBody Employee request);
    @GetMapping("view/{id}")
    public Employee view(@PathVariable Integer userId);
 }

Como parte da lógica de negócios,EmployeeService também precisa chamar o banco de dados, que é, novamente, um ponto de integração (parte externa), portanto, criamos  EmployeeRepositoryPort, e EmployeeServiceAdapterimplementamos essa porta.

@Service
public class EmployeeServiceAdapter implements EmployeeRepositoryPort {
    @PersistenceContext
    private EntityManager entityManager;
    @Transactional
    @Override
    public void create(String name, String role, long salary) {
        Employee employee = new Employee();
        employee.setName(name);
        employee.setRole(role);
        employee.setSalary(salary);
        entityManager.persist(employee);
    }
    @Override
    public Employee getEmployee(Integer userId) {
        return entityManager.find(Employee.class, userId);
    }
}
public interface EmployeeRepositoryPort {
    void create(String name, String role, long salary);
    Employee getEmployee(Integer userId);
}

Então, vemos como  EmployeeService usou a EmployeeUIPortporta para expor seu serviço e   EmployeeRepositoryPort interagir com o banco de dados. Além disso, EmployeeControllerAdapter eEmployeeServiceAdapter ajuda a integrar com APIs REST e DB.

Conclusão

Para resumir, a arquitetura hexagonal é uma abordagem usada para dividir o aplicativo em  partes internas e externas . Eles estão conectados através de portas (expostas pelo lado de dentro) e adaptadores (implementados pelo lado de fora). Portanto, ao aplicar essa abordagem, o código do caso de uso principal permanece intacto e pode servir a vários canais, suportando diferentes protocolos. Também ajuda a tornar o aplicativo testado com facilidade. No entanto, sugiro não implementar essa arquitetura completamente para todo o aplicativo, mas usar interfaces e adaptadores seletivamente.

O código de todos os exemplos acima pode ser encontrado  no GitHub .

Classes imutáveis ​​em Java

Olá!

Agora vamos falar sobre classes imutáveis ​​em Java e mostrar como tornar a classe imutável.

A classe é chamada imutável se for impossível alterar seu estado e conteúdo após a inicialização (criação).

Os benefícios de tais classes são:

  • segurança: você, como desenvolvedor, pode ter certeza de que ninguém é capaz de mudar seu estado;
  • thread-safety: o mesmo mencionado acima também é real para o ambiente multithread;
  • armazenável em cache: as instâncias podem ser armazenadas em cache facilmente pelo cache da VM ou pela implementação customizada, pois temos 100% de certeza de que seus valores não serão alterados;
  • hashable: tais classes podem ser seguramente colocadas dentro das coleções de hash (como HashMapHashSetetc) – é claro, se os métodos equals()hashCode()são substituídos de maneira apropriada.

Então, vamos tentar implementar a classe imutável pegando o mutável e mudando sua implementação para o imutável passo a passo.

Considere, temos a seguinte classe POJO típica Hobbit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.List;
 
public class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Address getAddress() {
        return address;
    }
 
    public void setAddress(Address address) {
        this.address = address;
    }
 
    public List<String> getStuff() {
        return stuff;
    }
 
    public void setStuff(List<String> stuff) {
        this.stuff = stuff;
    }
}

Hobbitclasse tem um campo addressdo tipo Address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Address {
    private String country;
    private String city;
 
    public Address(String country, String city) {
        this.country = country;
        this.city = city;
    }
 
    public String getCountry() {
        return country;
    }
 
    public void setCountry(String country) {
        this.country = country;
    }
 
    public String getCity() {
        return city;
    }
 
    public void setCity(String city) {
        this.city = city;
    }
}

Address A classe não será alterada neste artigo, portanto, em caso de dúvidas, consulte o código incorporado acima.

A primeira questão a ser notada são setters, que permitem alterar o valor de cada campo de classe. Então, vamos remover esses métodos:

1. removendo setters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
 
    // getters:
    public String getName() {
        return name;
    }
 
    public Address getAddress() {
        return address;
    }
 
    public List<String> getStuff() {
        return stuff;
    }
}

Parece melhor, mas agora o usuário de nossa classe não tem chances de definir os valores para campos de classe. Então, vamos fornecer o construtor apropriado:

2. adicionando todos os construtores args

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
 
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = address;
        this.stuff = stuff;
    }
 
    // getters:
    public String getName() {
        return name;
    }
 
    public Address getAddress() {
        return address;
    }
 
    public List<String> getStuff() {
        return stuff;
    }
}

Nós terminamos agora? Ainda não, restam vários problemas. Primeiro de tudo, nossa imutabilidade poderia ser hackeada usando um dos princípios da OOP – herança. Deixe-me demonstrar isso com um exemplo simples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.List;
 
public class Gandalf extends Hobbit {
    private String hackedName;
 
    public Gandalf(String name, Address address, List<String> stuff) {
        super(name, address, stuff);
        hackedName = name;
    }
 
    public void hackTheImmutability(String newNameValue) {
        hackedName = newNameValue;
        System.out.println("Immutability has been hacked!");
    }
 
    @Override
    public String getName() {
        return hackedName;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Collections;
 
public class Hack {
    public static void main(String[] args) {
        Gandalf gandalf = new Gandalf("Frodo Baggins", new Address("Hobitton", "Shire"), Collections.emptyList());
        Hobbit hobbit = (Hobbit) gandalf;
 
        System.out.println(hobbit.getName());
 
        System.out.println();
        gandalf.hackTheImmutability("Mr. Underhill");
        System.out.println();
 
        System.out.println(hobbit.getName());
    }
}

Se executarmos a Hackturma, obteremos a seguinte saída:

Frodo Bolseiro
Imutabilidade foi hackeada!
Mr. Underhill

Para corrigir esse problema, precisamos:

3. classe de marcação como final para protegê-la de ser estendida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
 
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = address;
        this.stuff = stuff;
    }
 
    // getters:
    public String getName() {
        return name;
    }
 
    public Address getAddress() {
        return address;
    }
 
    public List<String> getStuff() {
        return stuff;
    }
}

Agora, se executarmos a Hackaula mais uma vez, a saída estará correta:

Frodo Bolseiro
Imutabilidade foi hackeada!
Frodo Bolseiro

Há mais uma questão interessante: como sabemos, em Java quando passamos o valor de um objeto, estamos passando a referência a ele. Assim, o estado dos objetos poderia ser alterado pelos efeitos colaterais. Vamos voltar ao código para ver isso:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.List;
import java.util.ArrayList;
 
public class Hack {
    public static void main(String[] args) {
        Address address = new Address("Hobitton", "Shire");
        List<String> stuff = new ArrayList<>();
        stuff.add("Sword");
        stuff.add("Ring of Power");
 
        Hobbit hobbit = new Hobbit("Frodo Baggins", address, stuff);
 
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
 
        address.setCountry("Isengard");
        address.setCity("Saruman tower");
        stuff.remove("Ring of Power");
        stuff.remove("Sword"); 
 
        System.out.println();
        System.out.println("Immutability has been hacked!");
        System.out.println();
 
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
    }
}

A saída deste código é:

Hobbit country: Hobitton 
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!  
Hobbit country: Isengard 
Cidade de Hobbit: Saruman tower
Hobbit stuff: []

Esse problema pode ser corrigido por:

4. inicializando todos os campos mutáveis ​​não-primitivos via construtor executando uma cópia profunda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.List;
import java.util.ArrayList;
 
public final class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
 
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = new Address(address.getCountry(), address.getCity());
        this.stuff = new ArrayList<>(stuff);
    }
 
    // getters:
    public String getName() {
        return name;
    }
 
    public Address getAddress() {
        return address;
    }
 
    public List<String> getStuff() {
        return stuff;
    }
}

Agora a saída da Hackclasse é mais previsível:

Hobbit country: Hobitton 
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!
Hobbit country: Hobitton 
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]

Mas o mesmo problema de efeito colateral poderia ser produzido através de métodos getter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.ArrayList;
import java.util.List;
 
public class Hack {
    public static void main(String[] args) {
        Address address = new Address("Hobitton", "Shire");
 
        List<String> stuff = new ArrayList<>();
        stuff.add("Sword");
        stuff.add("Ring of Power");
 
        Hobbit hobbit = new Hobbit("Frodo Baggins", address, stuff);
 
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
 
        Address hobbitAddress = hobbit.getAddress();
        hobbitAddress.setCountry("Isengard");
        hobbitAddress.setCity("Saruman tower");
 
        List<String> hobbitStuff = hobbit.getStuff();
        hobbitStuff.remove("Ring of Power");
        hobbitStuff.remove("Sword");
 
        System.out.println();
        System.out.println("Immutability has been hacked!");
        System.out.println();
 
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
    }
}

Saída:

Hobbit country: Hobitton 
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!  
Hobbit country: Isengard 
Cidade de Hobbit: Saruman tower
Hobbit stuff: []

Então, vai com outro passo a ser feito:

5. executando clonagem do objeto mutável não-primitivo retornado em métodos getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.ArrayList;
import java.util.List;
 
public final class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
 
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = new Address(address.getCountry(), address.getCity());
        this.stuff = new ArrayList<>(stuff);
    }
 
    // getters:
    public String getName() {
        return name;
    }
 
    public Address getAddress() {
        return new Address(address.getCountry(), address.getCity());
    }
 
    public List<String> getStuff() {
        return new ArrayList<>(stuff);
    }
}

Agora a Hacksaída é:

Hobbit country: Hobitton 
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!
Hobbit country: Hobitton 
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]

Há mais uma etapa opcional a ser executada, mas se todos os outros pré-requisitos forem atendidos, isso não é necessário. De qualquer forma, vamos mencionar isso:

6. marcando todos os campos de classe como final (opcional)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.ArrayList;
import java.util.List;
 
public final class Hobbit {
    private final String name;
    private final Address address;
    private final List<String> stuff;
 
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = new Address(address.getCountry(), address.getCity());
        this.stuff = new ArrayList<>(stuff);
    }
 
    // getters:
    public String getName() {
        return name;
    }
 
    public Address getAddress() {
        return new Address(address.getCountry(), address.getCity());
    }
 
    public List<String> getStuff() {
        return new ArrayList<>(stuff);
    }
}

E é isso, estamos prontos – nossa Hobbitclasse é imutável agora.

Vamos resumir os passos:

  1. remover setters;
  2. adicionando todo o construtor args;
  3. marcando a classe de modo finala protegê-la de ser estendida;
  4. inicializar todos os campos mutáveis ​​não primitivos via construtor executando uma cópia profunda;
  5. executando clonagem do objeto mutável não-primitivo retornado nos métodos getter;
  6. marcando todos os campos de classe como final(etapa opcional).

Claro, existem outras maneiras de tornar a classe imutável (por exemplo, usando o padrão Builder), mas está fora do escopo da história atual.