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 cliente-servidor que usa gRPC GitHub.
    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. Faça Clone or Download do ponto de partida.
    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
        • mvn compile exec:java -Ddebug
        • Fica à espera de pedidos de clientes
        • A definição de debug ativa a impressão de mensagens detalhadas
      • Cliente
        • Abrir outro terminal
        • Compilar e executar o cliente (com mensagens
        • mvn compile exec:java -Ddebug
        • Prepara o pedido, imprime-o na consola, faz a chamada remota, e imprime o resultado na consola
    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:
      ...
      
      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;
      	}
      
      ...
      				
    5. 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.
      ...
      
      message Signature {
        string signerId = 1;
        bytes value = 2;
      }
      
      ...
      				
    3. Acrescentar a definição de uma mensagem para a resposta original com uma assinatura:
      ...
      
      message SignedResponse {
        ProductsResponse response = 1;
        Signature signature = 2;
      }
      
      ...
      				
    4. Modificar o tipo do resultado da operação RPC:
      ...
      
        rpc listProducts(ProductsRequest) returns (SignedResponse);
      
      ...
      				
    5. Para reconstruir:
      • Compilar o Protobuf e gerar código Java
      • Reinstalar o módulo Maven no repositório local
      • mvn install

    6. Consulte o código Java gerado para verificar o que mudou.

    7. Atualizar o código do servidor para refletir a modificação:
      ...
      
      import ...
      import pt.tecnico.supplier.grpc.SignedResponse;
      import pt.tecnico.supplier.grpc.Signature;
      
      ...
          @Override
          public void listProducts(ProductsRequest request, StreamObserver<SignedResponse> responseObserver) {
      ...
      				
    8. Atualizar também a chamada do lado do cliente:
      ...
      
      import ...
      import pt.tecnico.supplier.grpc.SignedResponse;
      import pt.tecnico.supplier.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 a documentação e o exemplo sobre este objeto).
    3. Para obter os dados a resumir, serializar o resultado com o seguinte método:
      ...
              byte[] responseBytes = response.toByteArray();
      ...
      				
    4. Para cifrar o resumo, criar um objecto Cipher com o algoritmo AES/ECB/PKCS5Padding e inicializar em ENCRYPT_MODE com a chave
      (consultar a documentação e o exemplo sobre este objeto).
    5. Preencher a assinatura e devolver a resposta que engloba a resposta anterior e a assinatura.
    6. Para executar:
      • Servidor
        • Compilar e executar o servidor
        • mvn compile exec:java -Ddebug
    7. Perguntas
      1. O que significa cada campo na expressão AES/ECB/PKCS5Padding?
      2. Concorda com a escolha?

     

  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:
      ...
      	if (Arrays.equals(digest, decipheredDigest))
      		System.out.println("Signature is valid! Message accepted! :)");
      	else
      		System.out.println("Signature is invalid! Message rejected! :(");
      ...
      				
    5. Para testar:
      • Servidor
        • mvn compile exec:java -Ddebug
      • Cliente
        • mvn compile exec:java -Ddebug
        • 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.
      • Os objetos construídos para os pedidos e respostas são imutáveis, ou seja, não podem ser mudados depois de construídos. 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 -Ddebug
      • Cliente
        • mvn compile exec:java -Ddebug
    3. Perguntas
      1. O cliente conseguiu detetar a alteração?
      2. O cliente consegue detetar se a mensagem é repetida?

     

  7. Alínea secreta

    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 uma 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