Tuesday, January 28, 2020

Criptografia e Segurança com Java: parte 1


Criptografia é a prática e o estudo de técnicas que viabilizam uma comunicação segura entre duas partes, através de um canal inseguro (a internet, por exemplo) de modo que seja assegurada a total confidencialidade da mensagem trocada entre as partes, isto é, se algum terceiro interceptar a mensagem, ele não terá condições de entender seu conteúdo, porque a mesma estará criptografada. Caso ele consiga decifrar a mensagem, diz-se que a criptografia foi quebrada.


Em resumo, seguindo a ilustração acima, extraida do livro Understanding Cryptography de Christof Paar e Jan Pelzl, Oscar é um sujeito mau que tentará interceptar a mensagem trocada entre Alice e Bob. Quando se trata de criptografia, temos duas funções:

e(): criptografa (encrypt) o texto x usando a chave k e retorna y (y é x criptografado)

d(): descriptografa (decrypty usando a mesma chave k, de modo que o resultado seja o texto x original

Quando a chave que criptografa e descriptografa são iguais, diz-se que a cripografia é simétrica; quando são diferentes, a criptografia é asimetrica.

Existem dezenas de algoritimos que determinam as etapas para criptografar/descriptografar o texto usando uma determinada chave. Exemplos comuns:

DES: (Data Encryption Standard) durante quase 3 décadas foi o método mais popular de criptografia simétrica. Caiu em desuso no final dos anos 90, quando o barateamento do poder computacional viabilizou a quebra desse método.

RSA: (Rivest–Shamir–Adleman) este algoritmo usa chave asimétrcia, o que significa que a chave que criptografa é diferente da chave que descriptografa. Considerado seguro, é muito usado em autenticações SSH e SSL.

AES: (Advanced Encryption Standard) Largamente aceito hoje, substituiu o DES, tambem é simétrico. AES é o padrão pelo NIST. É usando principalmente em comunicações via HTTPS.

SHA: (Secure Hash Algorithm) Representa uma família de algoritmos (SHA-0, SHA-1, SHA-2, etc.). Transforma o texto usando uma função hash. A criptografia usando SHA só tem um-caminho, ou seja, uma vez criptografada a mensagem, é impossível recuperar a mensagem de volta a partir do hash. Uma aplicação comum do SHA é criptografia de senhas e assinaturas digitais.

MD5: (Message Digest 5) Assim como o SHA, o MD5 é uma função hash de mão única.

A relação completa dos algoritmos suportados pela plataforma Java está na documentação da arquitetura de criptografia do Java.

Exemplo de DES (Data Encryption Standard) em Java

Neste exemplo em java criptografamos uma mensagem utilzando o algoritmo DES, e depois descriptografamos de volta para a original. As classes e interfaces que provem operações de criptografia no java estão no pacote javax.cripto. O pacote java.security também dá suporte para essas operações (as declarações de import estão omitidas).

/**
 * DES (Data Encryption Standard)
 */
public class _01_DES_example {

    //javax.crypto.Cipher provê funcionalidade para Criptografar ou Descriptografar.
    private static Cipher eCipher;
    private static Cipher dCipher;

    // java.security.Key é a interface que representa todos tipos de chave em java
    private static Key secretKey;

    public static void main(String args[])
    {
        try
        {
            // gera uma chave secreta para o algoritmo especificado 'DES'
            secretKey = KeyGenerator.getInstance("DES").generateKey();

            // cria uma instansia de Cipher que irá implementar a transformação da mensagem de acordo com
            // algoritmo especificado, neste caso DES
            eCipher = Cipher.getInstance("DES");
            dCipher = Cipher.getInstance("DES");

            // inicializa este cipher, especificando que ele irá criptografar mensagens
            eCipher.init(Cipher.ENCRYPT_MODE, secretKey);

            // inicializa este cipher, especificando que ele irá DEScriptografar mensagens
            dCipher.init(Cipher.DECRYPT_MODE, secretKey);

            // como a chave que faz a criptografia e a descriptografia é a mesma, então a operação é simétrica

            // mensagem a ser criptografada
            String classifiedMessage = new String("Top secret - This is a classified message!");

            // metodo auxiliar
            String encryptedMsg = criptografar(classifiedMessage);
            System.out.println("\nmenssagem criptografada: "+encryptedMsg);

            // metodo auxiliar
            String decrypted = descriptografar(encryptedMsg);
            System.out.println("\nmensagem descriptografada: "+decrypted);
        }
        // exceções checadas durante o processo de des/criptografia
        catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e)
        {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
    }

    static String criptografar(String classifiedMessage) throws UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
        // converte a String em bytes
        byte[] utf8 = classifiedMessage.getBytes("UTF-8");

        // criptografa a mensagem
        byte[] mensagemCriptografada = eCipher.doFinal(utf8);

        // converte em base64 para transmissao na rede
        mensagemCriptografada = Base64.getEncoder().encode(mensagemCriptografada);

        return new String(mensagemCriptografada);
    }

    // faz o caminho inverso do metodo criptografar()
    static String descriptografar(String encryptedMessage) throws BadPaddingException, IllegalBlockSizeException {

        // volta para base decimal
        byte[] dec = Base64.getDecoder().decode(encryptedMessage);

        // descriptografa a mensagem
        byte[] utf8 = dCipher.doFinal(dec);

        // retorna a mensagem original
        return new String(utf8);
    }
}

A conversão do texto criptografado em Base64 é necessária para garantir que todos os caracteres da mensagem cheguem integralmente ao destino, uma vez que, sem esse tratamento, durante o tráfego nas diferentes camadas da rede, alguns protocolos ou equipamentos poderiam interpretar errôneamente alguns caracteres e corromper a mensagem.

Resultado da execução do exemplo:

menssagem criptografada: +dIABfsau+f6eq021nwy/dN3ohqDFNcW7Gy6Yfs2PRFV6Fm+vLUc78QTlW8W4m3S

mensagem descriptografada: Top secret - This is a classified message!


"Estejam sempre alegres, rezem sem cessar. Dêem graças em todas as circunstâncias, porque esta é a vontade de Deus a respeito de vocês em Jesus Cristo. Não extingam o Espírito, não desprezem as profecias; examinem tudo e fiquem com o que é bom. Fiquem longe de toda espécie de mal."

1 Ts 5:16-22

s        

Wednesday, January 1, 2020

AWS: API Gateway e Lambda: parte 1

AWS Lambda e API Gateway


API Gateway é um serviço da AWS para criar, publicar, manter e monitorar de maneira muito simples APIs REST ou Web Socket que servem como porta de entrada para serviços que operam no back-end como acesso ao banco de dados, processamento de regras de negócios e diversas outras funcionalidades.


APIs REST são baseadas no protocolo HTTP e implementam os métodos padrões deste protocolo tais como GET, PUT, POST, DELETE e PATCH.

Neste artigo fazemos uma introdução a API Gateway com REST endpoints chamando outro serviço AWS no back-end, neste caso AWS Lambda.

AWS Lambda


Lambda é outro serviço da AWS que permite executar um código baseado em algum evento. O diferencial do Lambda é que ele é sevrerless, ou seja, você apenas cria sua lógica e a AWS cuida de toda infraestrutura necessária como escalonamento, memória, processamento, etc. Lambdas são como funções que rodam na nuvem, cujo código pode ser disparado sob diversas formas.

API Gateway + Lambda

Um caso de uso muito comum é integrar os serviços API Gateway e Lambdas, de modo que a chamada ao primeiro dispare a execução da lógica no segundo. Como exemplo dessa integração:
  1. Criamos uma função Lambda que cria um JSON e o retorna (parte 1)
  2. Um REST endpoint via API Gateway que chama a função e retorna seu resultado (parte 2)
Para executar o exemplo é necessário que você tenha pelo menos uma conta gratuita na AWS e instalar a ferramenta AWS CLI, que permite gerenciar os serviços da AWS por linha de comando.

Criando o Lambda

Utilizando sua IDE de preferencia (neste exemplo uso o INteliJ), crie um projeto MAVEN. O arquivo pom.xml contém as dependencias para se trabalhar com o Lambda e manipulação de JSON com a API gson google:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.finalexception</groupId>
    <artifactId>LambdaApiGatewayP01</artifactId>
    <version>1.0</version>
    <name>lambda-api</name>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

O plugin maven-shade permite compactar todas as dependencias junto com o jar final. Isso será necessário ao subir o arquivo compactado do projeto para executá-lo na nuvem.
Crie um pacote chamado service.lambda e crie classe Function01 cujo código é o que segue:

package service.lambda;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.google.gson.JsonObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class Function01 implements RequestStreamHandler{

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException
    {
        //API para manipulacao de JSONs
        JsonObject responseJson = new JsonObject();
        JsonObject responseBody = new JsonObject();
        JsonObject headerJson = new JsonObject();

        //propriedade mensagem na resposta
        responseBody.addProperty("message", "it's everything OK here!");

        // um header aleatório na resposta para API Gateway
        headerJson.addProperty("x-custom-header", "my custom header value");

        // Http status code 200 OK
        responseJson.addProperty("statusCode", 200);
        responseJson.add("headers", headerJson);
        responseJson.addProperty("body", responseBody.toString());

        // serializa o JSON para um OutputStream
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
        writer.write(responseJson.toString());
        writer.close();
    }
}


A partir da raiz do seu projeto, rode o comando mvn clean package para empacotar o projeto, gerando o arquivo LambdaApiGatewayP01-1.0.jar

Fazendo o deploy do arquivo na AWS

Agora vamos enviar o arquivo do projeto para o serviço AWS Lambda. A partir da raiz do seu projeto, execute o seguinte comando:

aws lambda create-function --function-name funcao01 \
    --zip-file fileb://target/LambdaApiGatewayP01-1.0.jar \
    --handler service.lambda.Function01::handleRequest \
    --runtime java8 \
    --role arn:aws:iam::12121212121:role/developer

O valor do parâmetro --role deve ser qualquer role na sua conta AWS que tenha autorização para criar Lambdas. Quando você cria uma conta, no mínimo deve ter a role administrador, que possui todas as permissões.

Após criar a função, é retornado um JSON contendo várias propriedades da mesma. Observe e copie o valor da propriedade functionArn, representa o endereço dessa função específica no ambiente AWS. Ao invocar essa função, utilizaremos o valor desse functionArn.

Agora que você publicou a função, faça um teste invocando-a. Substitua o valor de functionArn perlo valor gerado na sua conta:

 aws lambda invoke --function-name arn:aws:lambda:us-east-1:205303771310:function:funcao01 \
      --invocation-type RequestResponse /tmp/outfile.txt

O parâmetro /tmp/outfile contém a resposta gerada pela função no formato JSON. Você pode checá-lo abrindo o arquivo:


Na segunda parte intergamos essa função com um endpoint REST via API Gateway. Dessa forma, requisições HTTP no endpoint farão a função disparar.

Referências:
https://aws.amazon.com/lambda/
https://aws.amazon.com/api-gateway/

"Quanto a vocês, não fiquem procurando o que vão de comer e o qque vão beber. Não fiquem inquietos. Porque são os pagãos deste mundo que procuram tudo isso. O Pai bem sabe que vocês têm necessidade dessas coisas. Portanto, busquem o Reino dele, e Deus dará a vocês essas coisas em acréscimo. Não tenha medo, pequeno rebanho, porque o Pai de vocês tem prazer em dar-lhes o Reino"

Lucas 12:29-31