Labs SD >

Criptografia

Objectivos

Resumos:

Exemplos:

 

Mini-Exercício

Neste exercício vamos acrescentar segurança a uma aplicação distribuída que usa gRPC. Nomeadamente, vamos garantir integridade da comunicação entre servidor e cliente.

  1. Fornecedor gRPC / Supplier

    O ponto de partida para o exercício é um programa cliente-servidor que usa gRPC.
    O servidor é um fornecedor de produtos para venda. O cliente contacta o servidor, chamando a operação remota listProducts, e o servidor responde com uma lista de produtos.
    1. Descarregue e descomprima o código fonte ZIP.
    2. Leia os README e analise o código, começando pelo contract, depois pelo server e finalmente o client.
    3. Para construir e executar:
      • Contrato
        • Compilar o Protobuf e gerar código Java
        • Instalar o módulo Maven no repositório local, para poder ser usado como dependência
        • mvn install
      • Servidor
        • Compilar e executar o servidor
        • Fica à espera de pedidos de clientes
        • mvn compile exec:java
      • Cliente
        • Abrir outro terminal
        • Compilar e executar o cliente
        • Prepara o pedido, imprime-o na consola, faz a chamada remota, e imprime o resultado na consola
        • mvn compile exec:java
    4. Perguntas
      1. Onde estão definidas as operações remotas e respetivas mensagens?
      2. Para que servem os objetos Builder usados no cliente e no servidor?
      3. Em que porto fica o servidor à escuta de pedidos? Onde está definido?

     

  2. Distribuição de chaves

    A lista devolvida pelo servidor ao cliente pode ser intercetada e modificada por um atacante.
    É necessário acrescentar uma assinatura para proteger a resposta do servidor.

    Vamos fazer uma assinatura baseada numa cifra simétrica, um MAC (Message Authentication Code).
    O servidor e o cliente vão partilhar uma chave para permitir assinar e verificar a mensagem.
    A chave foi gerada e guardada num ficheiro.
    1. Descarregue e descomprima as chaves de exemplo ZIP.
    2. Copie a chave secreta para o servidor (pasta /src/main/resources).
    3. Copie a mesma chave para o cliente (pasta /src/main/resources).
    4. Quando precisar da chave, no servidor ou no cliente, pode usar um código semelhante ao seguinte para ler o seu valor a partir do recurso da aplicação:
    5. ...
      
      import java.security.Key;
      
      import static javax.xml.bind.DatatypeConverter.printHexBinary;
      
      import java.io.InputStream;
      
      ...
      
      	public static Key readKey(String resourcePath) throws Exception {
      		System.out.println("Reading key from resource " + resourcePath + " ...");
      		
      		InputStream fis = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath);
      		byte[] encoded = new byte[fis.available()];
      		fis.read(encoded);
      		fis.close();
      		
      		System.out.println("Key:");
      		System.out.println(printHexBinary(encoded));
      		SecretKeySpec keySpec = new SecretKeySpec(encoded, "AES");
      
      		return keySpec;
      	}
      
      ...
      
    6. Perguntas
      1. Qual é o tamanho da chave?
      2. Porque é necessário copiar a mesma chave para o cliente e para o servidor?

     

  3. Acrescentar assinatura à definição da operação

    Vamos agora acrescentar uma assinatura à definição da operação RPC.
    1. Aceder à definição Protobuf no contract.
    2. Acrescentar a definição de uma nova estrutura de dados para a assinatura, composta por identificador do assinante e o valor a calcular.
    3. ...
      
      message Signature {
        string signerId = 1;
        bytes value = 2;
      }
      
      ...
      
    4. Acrescentar a definição de uma mensagem para a resposta original com uma assinatura:
    5. ...
      
      message SignedResponse {
        ProductsResponse response = 1;
        Signature signature = 2;
      }
      
      ...
      
    6. Modificar o tipo do resultado da operação RPC:
    7. ...
      
        rpc listProducts(ProductsRequest) returns (SignedResponse);
      
      ...
      
    8. Para reconstruir:
      • Compilar o Protobuf e gerar código Java
      • Reinstalar o módulo Maven no repositório local
      • mvn install
    9. Consulte o código Java gerado para verificar o que mudou.
    10.  

    11. Atualizar o código do servidor para refletir a modificação:
    12. ...
      
      import ...
      import example.grpc.SignedResponse;
      import example.grpc.Signature;
      
      ...
          @Override
          public void listProducts(ProductsRequest request, StreamObserver<SignedResponse> responseObserver) {
      ...
      
    13. Atualizar também a chamada do lado do cliente:
    14. ...
      
      import ...
      import example.grpc.SignedResponse;
      import example.grpc.Signature;
      
      ...
              SignedResponse response = stub.listProducts(request);
      ...
      

     

  4. Assinar a resposta a enviar

    Uma das implementações possíveis de um MAC é um resumo cifrado com a chave simétrica.
    Vamos então calcular o resumo da resposta e depois cifrar esse resumo com a chave secreta.
    1. Aceder à classe de implementação do serviço no server.
    2. Para calcular o resumo, criar um objecto MessageDigest com o algoritmo SHA-256
      (consultar os exemplos de criptografia para mais detalhes sobre este objeto).
    3. Para obter os dados a resumir, serializar o resultado com o seguinte método:
    4. ...
              byte[] responseBytes = response.toByteArray();
      ...
      
    5. Para cifrar o resumo, criar um objecto Cipher com o algoritmo AES/ECB/PKCS5Padding e inicializar em ENCRYPT_MODE com a chave
      (consultar os exemplos de criptografia para mais detalhes sobre este objeto).
    6. Preencher a assinatura e devolver a resposta que engloba a resposta anterior e a assinatura.
    7. Para testar:
      • Servidor
        • Compilar e executar o servidor
        • mvn compile exec:java
    8. Perguntas
      1. O que significa cada campo na expressão AES/ECB/PKCS5Padding?
      2. Concorda com a escolha?
        Consulte a documentação do JCE para fundamentar a sua resposta. Caso discorde, escolha uma alternativa mais segura.

     

  5. Verificar a assinatura da resposta recebida

    Para verificar a assinatura é necessário calcular o resumo da mensagem recebida e comparar com a decifra do resumo recebido na assinatura.
    1. Aceder à classe do client.
    2. Para decifrar o resumo cifrado recebido na assinatura, criar um objecto Cipher com o algoritmo AES/ECB/PKCS5Padding, e inicializar em DECRYPT_MODE com a chave.
    3. Para recalcular o resumo, criar um objecto MessageDigest com o algoritmo SHA-256. Calcular o resumo a partir dos dados recebidos.
    4. Comparar o resumo decifrado com o resumo calculado:
    5. ...
              if (Arrays.equals(digest, decipheredDigest))
                  System.out.println("Signature is valid! Message accepted! :)");
              else
                  System.out.println("Signature is invalid! Message rejected! :(");
      ...
      
    6. Para testar:
      • Servidor
        • mvn compile exec:java
      • Cliente
        • mvn compile exec:java
        • Consultar a consola para ver o que foi acrescentado à resposta.

     

  6. Verificar eficácia da assinatura

    Vamos modificar o conteúdo da mensagem de resposta depois de assinada, para confirmar que o cliente é capaz de detetar a alteração.
    1. No servidor, após a realização da assinatura, modificar um dos campos de um dos produtos.
      • Para criar um objeto modificado a partir de um objeto existente pode-se usar o método toBuilder().
    2. Para testar:
      • Servidor
        • mvn compile exec:java
      • Cliente
        • mvn compile exec:java
    3. Perguntas
      1. O cliente conseguiu detetar a alteração ao verificar a assinatura?
      2. Em caso afirmativo, a solução construída garante a frescura da assinatura?
        Ou seja, é resistente a um ataque de repetição?

     

  7. Alínea secreta
  8. O resto do enunciado será entregue na aula.
    O objectivo será estender a solução resultante do enunciado acima.

Entrega da solução

Fénix, Avaliação, Projetos, mini Exercício 3

A solução completa deverá ser submetida no Fénix antes do fim da sua aula de laboratório.
Trabalhos submetidos depois da hora de fim da aula não serão considerados.

Ter atenção ao seguinte:

 

  1. Alínea extra: criptografia assimétrica

    O exercício usou cifra simétrica para calcular um MAC (Message Authentication Code).
    Modifique a solução para usar cifra assimétrica RSA na assinatura digital:
    o servidor deve assinar com a sua chave privada;
    o cliente deve verificar com a chave pública do servidor.

 


© Docentes de Sistemas Distribuídos, Dep. Eng. Informática, Técnico Lisboa