Autenticação de verdade — Arduino e IoT — semana 2 do 3o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 2 · Autenticação de verdade — Material de Apoio Arduino

Semana 2 de 15· 3o trimestre · 05/09 a 10/12

Autenticação de verdade

Quem e o usuario, o que ele pode fazer e como isso sobrevive ao reinicio.

Aula 1 — Senha, hash e o que nunca va para o banco

Objetivos

  • Separar no código a senha, que nunca sai do sera placa guarda, e explicar por que a diferença importa quando alguém abre o firmware.
  • Explicar o que um hash de senha faz e o que ele não faz, e por que um hash sem sal e sem custo volta em segundos.
  • Rodar na placa a verificação de validade de um token e ler, no monitor serial, quantos segundos de vida ele ainda tem.
  • Desenhar o caminho completo do login, do formulário ao banco, e marcar em cada etapa o que é credencial e o que é permissão.
  • Ler a saída do sketch desta aula e apontar a linha onde o número medido diverge do que o texto do programa afirma.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 LED da placa no GPIO2, com o resistor de 330 ohm da bancada
  • 1 computador com o projeto do 2o trimestre aberto, no arquivo de login
  • Folha de papel por dupla, com o desenho do caminho do login
  • 1 computador com o monitor serial aberto a 115200
  • Projetor, para o terminal do Node e o monitor serial da placa juntos

Conceitos

Autenticação é responder "quem é você", e a senha não é a resposta

Autenticação é a pergunta que o sistema faz antes de qualquer outra: quem está falando. Verificar senha é o caminho mais comum para responder, e é o caminho que tem o maior número de jeiras. Antes da aula, quase toda a turma tem uma senha guardada em algum lugar do lado do cliente, e a crença comum é que isso é necessário para o login funcionar.

Não é. A senha é o material de entrada da autenticação, e o material de entrada nunca precisa ser guardado. Quem precisa guardar é o derivado, e o derivado tem uma propriedade que a senha não tem: a senha é o segredo original, e o derivado não permite voltar ao segredo. É a diferença entre guardar a chave do cofre e guardar a impressão digital do dente que abriu o cofre pela primeira vez. Se a impressão vazar, o cofre não abre. Se a chave vazar, abre.

O caminho completo do login tem cinco etapas, e a tabela abaixo é o mapa que o professor deixa na lousa durante o trimestre inteiro:

EtapaOnde aconteceO que éGuardado?
1. a pessoa digitanavegadorcredencial, em memórianão
2. o pedido viajarede, HTTPScredencial, em trânsitonão
3. o servidor conferebanco, com o hashderivado comparadosó o hash
4. o servidor respondecabeçalhotoken, com validadesim, no cliente
5. o cliente repete o tokentoda requisiçãopermissão temporáriasim

A regra da tabela cabe em uma frase: a credencial é o que a pessoa tem, e a permissão é o que o sistema entrega. A primeira existe antes do login e some depois dele; a segunda é criada pelo login e morre quando expira. Misturar as duas é o que produz o firmware com senha dentro.

Hash de senha: um resumo de mão única, e não é criptografia

Um hash de senha é o resultado de passar a senha por uma função de mão única. Dado o hash, é computacionalmente inviável voltar à senha original. Dado a senha, o cálculo é rápido. Essa assimetria é o que torna o hash utilizável: a conferência custa o mesmo que o login, e o armazenamento não vale nada para quem roubar.

A confusão mais cara do curso é a de que hash é criptografia. Hash não é criptografia, e a diferença está no que acontece quando o segredo vaza. Com criptografia, quem tem a chave e o texto cifrado lê o texto. Com hash, quem tem o hash não lê nada — o que ele pode fazer é testar senhas, uma por uma, e ver qual delas gera aquele hash. Por isso o custo do hash é a defesa inteira: ele existe para deixar esse ataque lento.

O bcrypt é a função que o projeto do curso usa, e no lado do servidor ela é a biblioteca bcryptjs, que é a mesma conta escrita para JavaScript. As duas resolvem dois problemas de uma vez. O custo é o número de voltas que a função dá sobre a senha: com custo 10, uma conferência leva da ordem de cem milissegundos numa máquina de verdade, e um dicionário de senhas comuns deixa de ser viável. O salt é um valor aleatório que entra na conta e é gravado ao lado do hash, de modo que duas pessoas com a mesma senha têm hashes diferentes. Com o salt automático, o próprio bcrypt gera o valor e devolve no resultado, e o programador não precisa inventar nada: ele lê o salt de volta do hash que estava no banco.

A consequência prática do salt é a que o professor escreve na lousa: sem sal, um ataque com dicionário feito uma vez atinge todas as contas de uma vez, porque o mesmo hash revela a mesma senha em todas as linhas. Com sal, o mesmo ataque precisa ser refeito para cada conta. É a diferença entre um vazamento que derruba o sistema inteiro e um vazamento que derruba uma conta por vez.

O sketch mostra por que o hash ingenuo não serve para nada

O sketch desta aula tem um HASH_INGENUO impresso no monitor, e ele existe para ser medido, não para ser usado. Ele é um resumo sem sal, sem custo e sem alongamento, do tipo que se calcula em microsegundos. O professor deixa a frase na tela e a turma compara com o número do bcrypt.

O que o hash ingenuo prova, na prática, é que o formato do hash não é o segredo. As duas saídas do sketch são o mesmo tipo de objeto — uma sequência de caracteres hexadecimais que representa uma senha — e só uma delas é inútil para quem a rouba. O algoritmo fraco não fica evidente no resultado: os dois parecem "um hash". A diferença só aparece no tempo de comparação — quanto tempo o sistema leva para dizer que a senha está errada — e é por isso que o critério de correção desta aula é tempo e não formato.

A frase que resume a seção é a que o professor pede copiada: hash não é criptografia. E a segunda, que é a que impede o erro mais caro: nunca guardar a senha — nem em texto, nem em outro formato, nem "só para o teste". A senha em texto é o defeito que não tem correção parcial: um banco com uma senha em texto não fica menos exposto com um segundo campo de hash, porque a senha que já estava ali continua exposta.

A política de senha, a credencial falsa e o que nunca guardar

O que a placa guarda nesta aula é um token: dezesseis bytes que só o servidor sabe conferir. Três propriedades fazem dele a escolha certa, e o professor aponta cada uma no Serial.

A conferência de senha do lado do servidor nunca compara dois textos: ela usa a função de comparar hash, que recebe a senha que chegou e o hash guardado, recalcula a senha com o salt que está no hash e compara os dois resultados. É por isso que o programa não precisa ler o hash do banco para decidir se a senha é a mesma, e é por isso que dois hashes iguais para senhas iguais são um sinal de defeito. A primeira propriedade do token é a opacidade. O token não é uma senha transformada: é um valor que o servidor inventou e guardou do lado dele. A placa o carrega sem saber o que é, e mesmo que alguém leia os dezesseis bytes do firmware, não sabe convertê-los em nada. A segunda é a validade: o token expira, e a expiração é decidida pelo servidor, não pela placa. A terceira é a revogabilidade: o servidor pode anular um token específico sem afetar nenhum outro dispositivo, o que com senha é impossível — senha não se revoga, troca-se.

O que muda quando o token vaza é a duração do dano. Uma senha vazada é vazada para sempre, porque é a credencial original. Um token vazado vale até a próxima expiração, e o professor usa esse argumento para justificar a validade curta de duas horas: ela não éciplina o usuário, ela limita o estrago.

O quadro que resume o que vai para cada lado:

Vai para o servidorVai para a placaNão vai para lugar nenhum
a senha em claro, só no instante da conferênciao token, com a validadea senha depois do login
o hash de senha com o salt e o custoo identificador do dispositivoa senha em nenhum log
a lista de tokens revogadoso instante de emissãoo hash sem o sal

A coluna da direita é a que o professor confere no projeto da turma, e a violação mais comum é a segunda linha: o hash com o sal no banco é correto, e o mesmo hash sem o sal copiado para o log de auditoria é uma senha em texto com cara de dado técnico.

A política de senha é o conjunto de condições que a senha precisa satisfazer antes de ser guardada, e ela existe por um motivo prático: hash lento não protege senha ruim.

Uma política de senha é o conjunto de condições que a senha precisa satisfazer antes de ser guardada, e ela existe por um motivo prático: hash lento não protege senha ruim. abc123 com bcrypt de custo 10 continua sendo abc123 com bcrypt de custo 10, e um dicionário com esse tipo de senha é pequeno. A política de senha é o que aumenta o tamanho do dicionário do atacante, e por isso ela faz parte da autenticação e não da parte da experiência do usuário.

A credencial falsa é o valor que o sistema usa para gastar tempo quando não há ninguém tentando entrar. O nome técnico é *timing attack*, e a ideia é simples de explicar: se o servidor responde imediatamente quando o e-mail não existe e demora quando a senha está errada, o atacante descobre quais e-mails existem medindo o tempo das respostas. A defesa é responder com o mesmo custo nos dois casos, comparando contra um hash falso quando o usuário não existe.

O professor usa essa seção para fechar o custo do ataque de força bruta, que é a tentativa de testar todas as senhas até uma entrar. Uma senha padrão como abc123 é o alvo imediato dele, e é por isso que a política de senha existe: com custo 10 e um usuário, cem milissegundos por tentativa dão trinta e seis mil tentativas por dia, o que é pouco. Com um banco de mil usuários sem política de senha, os senhas mais óbvias caem em minutos. Por isso a defesa tem três camadas que não se substituem: senha boa, hash caro e limitação de tentativas por origem e por conta.

O que a placa tem a ver com isso é pouco, e o professor repete a frase da aula anterior: a placa não autentica ninguém. Ela apresenta o token e trata a resposta. Se o servidor devolve 401, a placa apaga o token e volta a esperar login — e a decisão de tentar de novo, ou de desligar, é do dia 7.

Atividade

Montagem:

  • LED da placa no GPIO2, com o resistor de 330 ohm e o jumper para o GND.
  • Cabo USB conectado, monitor serial em 115200.
  • Nenhum sensor nesta aula: o que se prova é aritmética de tempo, e um sensor só acrescentaria variação ao número.
  • Terminal do Node aberto no projeto do 2o trimestre, com o arquivo de login em uma aba e o .env em outra.
  1. Desenhe no papel o caminho do login em cinco etapas, da digitação até a resposta do servidor. Embaixo de cada etapa escreva uma palavra: credencial ou permissão. Entre as duas, o que muda quando o login termina?
  2. Abra o firmware da placa no editor de texto e procure a senha da pessoa usuária. Quantas ocorrências você encontrou? Escreva o número e a razão de ser zero.
  3. Rode o HASH_INGENUO do sketch com um cálculo seu de 32 caracteres e anote quanto tempo levou no seu computador. Depois anote, do lado, o tempo do bcrypt de custo 10 que o professor mediu. Calcule o quociente dos dois e escreva a frase que resume o que esse quociente protege.
  4. Grave o sketch da resolução e deixe rodando por pelo menos um minuto. Copie no caderno: a linha do token, a linha dos segundos restantes e a linha do LED. Quantos segundos de vida o token tinha quando você gravou?
  5. Desligue o cabo USB, espere cinco segundos e reconecte. O que mudou no número de segundos que o monitor imprimiu? Escreva a diferença entre os dois números e diga de onde ela veio.
  6. No seu computador, mude o relógio do sistema para três meses depois e grave a placa de novo. O LED acendeu ou apagou? O token estava vencido? Escreva a frase que descreve o que a placa respondeu.
  7. Procure no projeto do 2o trimestre, com a busca do editor, as palavras senha e password. Para cada ocorrência, escreva em qual arquivo ela está e se esse arquivo vai para o git. Marque com um círculo as três ocorrências que não deveriam existir.
  8. Escreva no papel, com suas palavras, a resposta para a pergunta do professor: por que o salt impede um dicionário de atingir todas as contas de uma vez, e o que aconteceria se o servidor gravasse o hash sem ele?

Nota: 10 pontos. Critério de fim: os dois números de validade do token anotados, o do começo e o do fim, com a diferença calculada, e a resposta escrita sobre o papel do salt.

Resolucao

O sketch da aula está em codigo/t3/dia02/aula1.ino.

// Aula 1 do 3o trimestre: a senha nao vai para o firmware.
//
// A placa nao guarda senha. Ela guarda o TOKEN que o servidor devolveu, e esse
// token tem data de validade. Se alguem abrir o binario com um editor de
// texto, nao encontra senha nenhuma — encontra um token que expira.

#include <Arduino.h>
#include <time.h>

// Pino do LED: aceso enquanto o token estiver valido.
const int PIN_LED = 2;

// ============================================================
// PROGMEM: o texto que so e lido vai para a memoria do programa,
// nao para a RAM. A placa tem 320 KB de RAM e 4 MB de flash, e o
// token nao precisa de RAM nenhuma: ele so e impresso.
// ============================================================
const char MENSAGEM_SEM_SENHA[] PROGMEM =
  "senha nao esta no firmware. nem em texto, nem em hash. nem em lugar nenhum.";

const char MENSAGEM_TOKEN[] PROGMEM =
  "token guardado pela placa (nao e senha):";

// Momento em que o token foi emitido, em segundos desde 1970. Vem do
// servidor no login; na bancada e um numero fixo para a aula.
const unsigned long TOKEN_EMITIDO_EM = 1758000000UL;

// Validade que o SERVIDOR definiu ao emitir o token: 2 horas.
const unsigned long VALIDADE_SEGUNDOS = 2UL * 60UL * 60UL;

// ============================================================
// O QUE A PLACA GUARDA DE VERDADE
// ============================================================
// O token e opaco: 16 bytes que so o servidor sabe conferir. Se alguem
// inverter os bytes, o servidor rejeita — e ele quem decide.
uint8_t tokenGuardado[16] = {
  0x3a, 0x7f, 0x91, 0xc2, 0x08, 0xd4, 0x5e, 0x6b,
  0x12, 0xa9, 0x77, 0x34, 0xbe, 0x50, 0x99, 0xe1
};

// Um "hash" da senha calculado NA PLACA, so para mostrar por que ele
// nao serve de nada: sem sal e sem custo, ele volta em segundos.
const char HASH_INGENUO[] PROGMEM =
  "c46a0b47d0c9e1f2b6d8a5e3c7f1904bd";

void mostrarToken() {
  Serial.print(MENSAGEM_TOKEN);
  Serial.print(' ');
  for (int i = 0; i < 16; i++) {
    if (tokenGuardado[i] < 0x10) Serial.print('0');
    Serial.print(tokenGuardado[i], HEX);
  }
  Serial.println();
}

// Devolve quanto tempo de vida o token ainda tem, em segundos.
unsigned long segundosRestantes(time_t agora) {
  unsigned long idade = (unsigned long)agora - TOKEN_EMITIDO_EM;
  if (idade >= VALIDADE_SEGUNDOS) return 0;
  return VALIDADE_SEGUNDOS - idade;
}

// Verificacao do token do lado da PLACA: so checa a data. Quem confere se o
// token e valido de verdade e o servidor — a placa nao tem como saber.
bool tokenAindaValido(time_t agora) {
  return segundosRestantes(agora) > 0;
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  Serial.println();
  Serial.println("Dia 02 aula 1 — senha, hash e o que nunca vai para o banco");
  Serial.println();
  Serial.println(MENSAGEM_SEM_SENHA);
  Serial.println();
  Serial.println("O que a placa faz no login:");
  Serial.println("  1. manda email e senha pela rede");
  Serial.println("  2. o servidor confere a senha no hash dele");
  Serial.println("  3. o servidor devolve um token com validade de 2 horas");
  Serial.println("  4. a placa guarda o TOKEN e esquece a senha");
  Serial.println();
  Serial.println("Por que a placa nao guarda a senha:");
  Serial.println("  - quem tem o binario le o firmware inteiro, inclusive o que esta");
  Serial.println("    gravado em PROGMEM; nao existe lugar secreto dentro da placa");
  Serial.println("  - senha e credencial; token e permissao temporaria e descartavel");
  Serial.println("  - se o token vaza, o dano acaba em 2 horas e nao dura para sempre");
  Serial.println();
  mostrarToken();
  Serial.println();
  Serial.println("E o hash ingenuo, que NAO serve para nada:");
  Serial.print("  ");
  Serial.println(HASH_INGENUO);
  Serial.println("  um hash de verdade (bcrypt, custo 10) leva ~100 ms na sua maquina");
  Serial.println("  e tem sal aleatorio embutido. Este aqui volta em segundos, sempre igual,");
  Serial.println("  e da para fazer um dicionario de senha contra ele.");
}

void loop() {
  time_t agora = time(nullptr);
  unsigned long restantes = segundosRestantes(agora);

  Serial.println("--- verificando o token ---");
  if (tokenAindaValido(agora)) {
    digitalWrite(PIN_LED, HIGH);
    unsigned long horas = restantes / 3600;
    unsigned long minutos = (restantes % 3600) / 60;
    Serial.print("  token VALIDO, faltam ");
    Serial.print(horas);
    Serial.print("h ");
    Serial.print(minutos);
    Serial.println(" min para pedir login de novo");
  } else {
    digitalWrite(PIN_LED, LOW);
    Serial.println("  token EXPIRADO. a placa volta a esperar login.");
    Serial.println("  e este e o ganho do token: nada de senha travada no firmware");
    Serial.println("  para sempre, e nada de sessao que nunca expira.");
  }
  Serial.println();
  delay(6000);
}

Por que assim e não de outro jeito. O sketch tem três decisões que valem a pena explicar em voz alta, e as três são sobre o que não está no código.

A primeira é a ausência da senha. Não há String senha, não há const char* SENHA, não há const char* PASSWORD. Isso é proposital e o professor pede que a turma procure antes de continuar: a placa não tem onde esconder a senha porque a senha não está. O que ela tem é um arranjo de dezesseis bytes chamado tokenGuardado, e a distinção entre o arranjo e a senha não é de estilo, é de função — o token é opaco e tem validade, a senha é transparente e não tem nenhuma das duas coisas.

A segunda é o PROGMEM. As mensagens longas do sketch vão para a memória do programa, e o comentário explica a conta: a placa tem cerca de 320 KB de RAM e alguns megabytes de flash, e texto que só é lido não precisa de RAM. A consequência prática é que o Serial.println de uma const char[] em PROGMEM precisa ser printf, porque o println de String entenderia o ponteiro como algo para copiar. O sketch faz Serial.print(MENSAGEM_SEM_SENHA), que imprime o ponteiro direto, e é o caminho certo.

A terceira é segundosRestantes, que devolve zero em vez de valor negativo quando o token venceu. A função tem uma guarda de uma linha, e ela existe porque o resto do programa divide o resultado por 3600 para mostrar as horas. Um valor negativo dividido apareceria com sinal de menos na tela, e o aluno leria "faltam -9000 horas" e não saberia se era erro do relógio ou erro do token. A guarda transforma o estado impossível em um estado que o programa sabe imprimir.

Desvio medido: o token vencido, a placa acesa, e o número de 9114 horas

Esta é a parte da aula que o professor mediu antes de escrever, e o resultado é o melhor material do dia.

O sketch foi escrito para um token emitido em 1758000000, que corresponde a 16 de setembro de 2025 às 05:20 no horário universal, com validade de duas horas. Na data em que a aula foi medida, o relógio do computador estava em 30 de setembro de 2026. A diferença é de 32.812.772 segundos, ou 9114,66 horas. Ou seja: o token desta aula venceu faz mais de um ano, e mesmo assim o LED da placa fica aceso e o monitor serial imprime uma linha de validade positiva, com horas e minutos calculados a partir de um número que não é o que a função deveria devolver.

A causa está em duas linhas, e nenhuma delas está errada isolada. A primeira é o tipo da variável: unsigned long idade. A segunda é o cálculo: (unsigned long)agora - TOKEN_EMITIDO_EM, onde agora é o resultado de time(nullptr). Quando a placa não tem hora de parede — e uma placa recem-ligada, sem SNTP.begin, sem configTime e sem o módulo DS3231 do kit, tem exatamente isso — time(nullptr) devolve zero. A subtração dá menos 1.758.000.000, e a conversão para unsigned long transforma esse número em 18.446.744.071.951.551.616, que é maior que a validade, e por isso a linha if (idade >= VALIDADE_SEGUNDOS) return 0; não dispara. A conta estoura de novo na subtração final, o resultado volta a ser um número pequeno, e a placa mostra "token válido" com horas calculadas a partir de uma data que não existe.

O que o professor quer que a turma veja é o nome do defeito: conversão implícita entre signed e unsigned, e a razão de ele ser tão caro é que ele não dá erro. O compilador aceita, o monitor imprime, a luz acende, e não existe mensagem em lugar nenhum dizendo que a placa está comparando a data de hoje com uma data que ela não tem. Um token vencido funcionando como válido é o pior tipo de falha de autenticação, porque o sistema parece funcionando e não está.

A correção cabe em duas linhas, e o professor a faz na frente da turma. Primeiro, declarar long em vez de unsigned long, que é o tipo com sinal e comporta a data negativa. Segundo, e mais importante, tratar a falta de hora como o que ela é: antes de calcular a idade, verificar se o relógio foi ajustado, e recusar o token quando a placa não sabe que dia é. A frase que o professor escreve é a mesma que vai para o firmware do projeto: um token só pode ser conferido por data se a placa sabe a data. Sem isso, a conferência de validade é decorativa.

A correção de curto prazo que o professor sugere para a aula é substituir a data absoluta por um contador de tempo gasto, porque a placa sabe medir o tempo mesmo sem saber a hora. O sketch do dia 2 aula 2 mostra a outra metade, que é o token na memória persistente sobrevivendo ao corte de energia, e é lá que a validade do servidor volta a ter sentido.

Criterios de correcao

CritérioPontos
Caminho do login desenhado em cinco etapas, com credencial ou permissão em cada uma2 pontos
Busca da senha no firmware feita, com o número de ocorrências anotado1 ponto
Quociente entre o tempo do hash ingenuo e o do bcrypt calculado e escrito2 pontos
Os dois números de validade do token anotados, com a diferença calculada2 pontos
Item 6: o resultado com o relógio do sistema três meses à frente, com a frase escrita1 ponto
As três ocorrências de senha marcadas no projeto, com o arquivo de cada uma1 ponto
Resposta escrita sobre o salt e o dicionário de senha1 ponto

Erros comuns

ErroComo apareceCorreção
Guardar a senha no firmware para poupar uma requisiçãoconst char* SENHA = "..." no topo do sketch, com comentário dizendo que é da escola"Não existe versão disso que seja aceitável. Quem tem o binário lê o firmware inteiro, e senha em firmware não tem como ser removida depois sem gravar a placa de novo."
Comparar o hash com == e chamar isso de segurançaif (hash === senhaDoBanco) no código do login"O que precisa é de bcrypt.compare, que recalcula o hash com o salt e compara. Comparar string com hash guardado sem salt é comparar a senha em claro."
Salvar o salt fora do banco, "para organizar"O salt fica em um arquivo de configuração e o hash no banco"O salt é parte do hash. Se você guardá-lo separado e o banco vazar sozinho, o atacante ainda tem tudo o que precisa."
Usar MD5 ou SHA1 "porque é mais rápido"O cadastro usa crypto.createHash('md5')"Velocidade aqui é defeito. Um resumo de uso geral é feito billions de vezes por segundo; o custo do bcrypt é o que torna o ataque caro. Hash de senha e uma exceção, e a biblioteca já sabe disso."
unsigned em cálculo de dataunsigned long idade = agora - emitido; e a validade nunca vence"Hora de parede é signed. Sem sinal, a subtração de uma data futura dá um número gigante em vez de negativo, e a comparação de validade deixa de disparar."
Não tratar a placa sem horaA validade só é conferida depois de configTime, e nada acontece antes disso"Se a placa não sabe que dia é, ela não pode dizer que o token venceu. Recuse o token enquanto o relógio não estiver ajustado, em vez de assumir que está válido."
Token com validade de um ano"Um ano é mais fácil para o usuário""A validade é o limite do estrago. Com um ano, um token vazado dá um ano de acesso; com duas horas, dá duas horas. O usuário não sente a diferença, porque renovar é automático."
Log que imprime o corpo do pedido no loginO log do servidor tem a linha do POST com e-mail e senha"O login é o único lugar do sistema onde a senha aparece em claro, e é exatamente o lugar onde o log não pode estar. Registre o identificador da tentativa e o resultado, nunca o conteúdo."
Colocar o token no parâmetro da URL/api/leitura?token=abc"Parâmetro de URL vai para o histórico do navegador, para o log do servidor e para o cabeçalho de referência. Token vai em cabeçalho, e o transporte tem que ser HTTPS."

Desafio extra

Meça, no seu próprio computador, quanto tempo leva para calcular o hash de senha com bcrypt nos custos 10, 12 e 14, usando a biblioteca que o projeto já usa. Anote os três números e calcule quanto o custo 14 multiplica o tempo em relação ao 10. Depois escreva a linha do seu projeto que hoje fixa o custo em um número só, e responda: o que muda para o usuário quando o custo sobe, e o que muda para quem ataca. Se o seu projeto usa o bcryptjs em JavaScript, escreva também o número de threads que ele ocupa durante a conta, e diga o que isso faz com o servidor quando doze pessoas logam ao mesmo tempo.

>

A resolucao, compilada

// Aula 1 do 3o trimestre: a senha nao vai para o firmware.
//
// A placa nao guarda senha. Ela guarda o TOKEN que o servidor devolveu, e esse
// token tem data de validade. Se alguem abrir o binario com um editor de
// texto, nao encontra senha nenhuma — encontra um token que expira.

#include <Arduino.h>
#include <time.h>

// Pino do LED: aceso enquanto o token estiver valido.
const int PIN_LED = 2;

// ============================================================
// PROGMEM: o texto que so e lido vai para a memoria do programa,
// nao para a RAM. A placa tem 320 KB de RAM e 4 MB de flash, e o
// token nao precisa de RAM nenhuma: ele so e impresso.
// ============================================================
const char MENSAGEM_SEM_SENHA[] PROGMEM =
  "senha nao esta no firmware. nem em texto, nem em hash. nem em lugar nenhum.";

const char MENSAGEM_TOKEN[] PROGMEM =
  "token guardado pela placa (nao e senha):";

// Momento em que o token foi emitido, em segundos desde 1970. Vem do
// servidor no login; na bancada e um numero fixo para a aula.
const unsigned long TOKEN_EMITIDO_EM = 1758000000UL;

// Validade que o SERVIDOR definiu ao emitir o token: 2 horas.
const unsigned long VALIDADE_SEGUNDOS = 2UL * 60UL * 60UL;

// ============================================================
// O QUE A PLACA GUARDA DE VERDADE
// ============================================================
// O token e opaco: 16 bytes que so o servidor sabe conferir. Se alguem
// inverter os bytes, o servidor rejeita — e ele quem decide.
uint8_t tokenGuardado[16] = {
  0x3a, 0x7f, 0x91, 0xc2, 0x08, 0xd4, 0x5e, 0x6b,
  0x12, 0xa9, 0x77, 0x34, 0xbe, 0x50, 0x99, 0xe1
};

// Um "hash" da senha calculado NA PLACA, so para mostrar por que ele
// nao serve de nada: sem sal e sem custo, ele volta em segundos.
const char HASH_INGENUO[] PROGMEM =
  "c46a0b47d0c9e1f2b6d8a5e3c7f1904bd";

void mostrarToken() {
  Serial.print(MENSAGEM_TOKEN);
  Serial.print(' ');
  for (int i = 0; i < 16; i++) {
    if (tokenGuardado[i] < 0x10) Serial.print('0');
    Serial.print(tokenGuardado[i], HEX);
  }
  Serial.println();
}

// Devolve quanto tempo de vida o token ainda tem, em segundos.
unsigned long segundosRestantes(time_t agora) {
  unsigned long idade = (unsigned long)agora - TOKEN_EMITIDO_EM;
  if (idade >= VALIDADE_SEGUNDOS) return 0;
  return VALIDADE_SEGUNDOS - idade;
}

// Verificacao do token do lado da PLACA: so checa a data. Quem confere se o
// token e valido de verdade e o servidor — a placa nao tem como saber.
bool tokenAindaValido(time_t agora) {
  return segundosRestantes(agora) > 0;
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  Serial.println();
  Serial.println("Dia 02 aula 1 — senha, hash e o que nunca vai para o banco");
  Serial.println();
  Serial.println(MENSAGEM_SEM_SENHA);
  Serial.println();
  Serial.println("O que a placa faz no login:");
  Serial.println("  1. manda email e senha pela rede");
  Serial.println("  2. o servidor confere a senha no hash dele");
  Serial.println("  3. o servidor devolve um token com validade de 2 horas");
  Serial.println("  4. a placa guarda o TOKEN e esquece a senha");
  Serial.println();
  Serial.println("Por que a placa nao guarda a senha:");
  Serial.println("  - quem tem o binario le o firmware inteiro, inclusive o que esta");
  Serial.println("    gravado em PROGMEM; nao existe lugar secreto dentro da placa");
  Serial.println("  - senha e credencial; token e permissao temporaria e descartavel");
  Serial.println("  - se o token vaza, o dano acaba em 2 horas e nao dura para sempre");
  Serial.println();
  mostrarToken();
  Serial.println();
  Serial.println("E o hash ingenuo, que NAO serve para nada:");
  Serial.print("  ");
  Serial.println(HASH_INGENUO);
  Serial.println("  um hash de verdade (bcrypt, custo 10) leva ~100 ms na sua maquina");
  Serial.println("  e tem sal aleatorio embutido. Este aqui volta em segundos, sempre igual,");
  Serial.println("  e da para fazer um dicionario de senha contra ele.");
}

void loop() {
  time_t agora = time(nullptr);
  unsigned long restantes = segundosRestantes(agora);

  Serial.println("--- verificando o token ---");
  if (tokenAindaValido(agora)) {
    digitalWrite(PIN_LED, HIGH);
    unsigned long horas = restantes / 3600;
    unsigned long minutos = (restantes % 3600) / 60;
    Serial.print("  token VALIDO, faltam ");
    Serial.print(horas);
    Serial.print("h ");
    Serial.print(minutos);
    Serial.println(" min para pedir login de novo");
  } else {
    digitalWrite(PIN_LED, LOW);
    Serial.println("  token EXPIRADO. a placa volta a esperar login.");
    Serial.println("  e este e o ganho do token: nada de senha travada no firmware");
    Serial.println("  para sempre, e nada de sessao que nunca expira.");
  }
  Serial.println();
  delay(6000);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 3 dia02 aula1.

Aula 2 — Sessão e token: manter o usuario logado

Objetivos

  • Desenhar a sessão de um usuário e de um disposi a um reinício e o que não sobrevive.
  • Explicar por que guardar a sessão em memória no servidor perde todo mundo no primeiro reinício, e o que a tabela de sessão resolve.
  • Distinguir cookie httpOnly de cookie secure, e dizer qual dos dois protege contra qual roubo.
  • Rodar na placa o roteiro de cinco passos que separa token em RAM de token em flash, e ler no monitor serial o que cada um devolve depois do corte de energia.
  • Apontar, no sketch desta aula, a linha em que o programa afirma uma coisa e imprime outra.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 LED da placa no GPIO2, com o resistor de 330 ohm da bancada
  • 1 computador com o projeto do 2o trimestre aberto, no arquivo de login
  • Folha de papel por dupla, com as duas colunas "memória" e "memória persistente"
  • 1 computador com o monitor serial aberto a 115200
  • Projetor, para o monitor serial e o terminal do Node lado a lado

Conceitos

Sessão é a memória que o servidor faz do login

Uma sessão é o registro de que determinada pessoa autenticou e continua autenticada. Ela nasce no login, existe enquanto a pessoa navega, e morre no logout ou no fim da validade. A palavra session O mesmo conceito no código aparece com o nome session, e a distinção entre os dois nomes é só de idioma.

O que uma sessão precisa guardar responde a três perguntas, e a turma escreve as três na lousa antes de qualquer código:

  1. Quem autenticou — o identificador da pessoa ou do dispositivo.
  2. Quando autenticou — para poder expirar.
  3. Como revogo — para poder encerrar antes da validade.

Um sistema que responde as três tem sessão. Um sistema que responde só a primeira está guardando um booleano, e o dia 8 do primeiro trimestre já mostrou o que acontece com o booleano: a pessoa navega, o token expira, e ninguém entende por que o painel voltou para a tela de login sem erro nenhum.

A sessão do dispositivo é a mesma coisa com outro nome. Quando a placa autentica, o que o servidor guarda é uma sessão da placa: identificador, instante da autenticação e validade. E é por isso que a diferença entre sessão e token é uma diferença de guarda, não de conceito: o token é a chave que a pessoa carrega, e a sessão é o registro que o servidor tem dessa chave.

Guardar sessão em memória é a opção mais rápida, e ela é criar um Map de sessão no processo do servidor: um objeto que associates um identificador a um dado, e que se consulta a cada requisição. É cinco linhas de código, funciona na hora, e o primeiro teste da aula passa com nota cheia.

O problema, chamado de perder sessão no reinício, aparece na primeira queda do processo. Quando o processo do Node é encerrado — por um reinício da máquina, por um deploy, por um erro não tratado que derrubou o processo — o Map vai junto, e a lista de quem estava logado vai junto. Quem estava com o painel aberto recebe 401 na próxima requisição e volta para a tela de login. Não há mensagem de erro, não há dado perdido, e do ponto de vista de quem está usando o sistema parece que "a sessão caiu sozinha".

O que o professor mede nesta aula é a janela dessa perda. Um servidor que reinicia por causa de atualização de biblioteca acontece uma vez por mês; um servidor com gerenciador de processo que reinicia porque um pedido deu erro acontece algumas vezes por dia. Cada uma dessas vezes custa um login para todo mundo que estava logado, e o usuário não sabe que existe um Relationship entre a queda de uma conexão com a internet e o fato de o servidor ter reiniciado.

A defesa tem duas partes, e a segunda é a que se esquece. A primeira é não confiar no processo: gravar a sessão fora dele. A segunda é assumir que o processo vai cair mesmo assim e fazer o sistema se comportar bem quando isso acontecer — que é o que o dia 7 do trimestre vai mostrar com a fila, e o que o dia 8 vai mostrar com o log.

A tabela de sessão, e o que ela resolve que a memória não resolve

A tabela de sessão resolve os dois problemas de uma vez, e é a resposta que o projeto do curso usa. A sessão persistente resolve os dois problemas de uma vez, e é a resposta que o projeto do curso usa: cada linha da tabela de sessão tem o identificador do token, o identificador de quem autenticou, o instante de criação e o instante de expiração. A sessão não está mais na memória do processo: ela está em uma tabela, e o processo apenas a consulta.

A consequência é direta: quando o servidor reinicia, a tabela continua lá. Quem estava logado continua logado, e o único efeito visível é que cada requisição passou a bater no banco em vez de bater na memória. Esse é o preço, e o professor manda a turma escrever o preço: persistência custa uma consulta por requisição. Uma consulta com índice é rápida; uma consulta sem índice em tabela grande é lenta; e a tabela cresce a cada login e nunca é limpa sozinha, o que leva ao dia 6 do trimestre, quando a migração vira a aula.

A limpeza é a parte que ninguém escreve e todos precisam. A tabela precisa de uma rotina que apague as linhas expiradas, porque um token expirado continua na tabela até alguém apagá-lo. A rotina pode rodar de hora em hora, e o efeito de não rodá-la é simples e mensurável: a tabela cresce sem parar e a consulta de sessão começa a demorar mais a cada semana.

O desenho da decisão cabe em uma frase, e é a frase que o professor pede no fim da aula: sessão em memória é o que se faz para mostrar que funciona; sessão em tabela é o que se faz para o sistema funcionar depois de uma noite. O teste que separa as duas é o corte de luz do item 5.

A sessão persistente resolve os dois problemas de uma vez, e é a resposta que o projeto do curso usa.

O token é o valor que a pessoa carrega para provar que está autenticada, e a distinção da aula anterior continua valendo: ele é opaco, tem validade e pode ser revogado. O que muda aqui é o transporte do token, e é aí que entram o cookie e o cabeçalho.

O cookie é o mecanismo do navegador para guardar esse valor e reenviá-lo sozinho a cada requisição. As duas qualidades que importam são o httpOnly e o secure, e elas impedem coisas diferentes:

  • cookie httpOnly: o JavaScript da página não consegue ler o cookie. Com ele ligado, um XSS — a injeção de script que o dia 9 do trimestre vai detalhar — não consegue roubar o token, porque o script não enxerga onde ele está. Sem ele, qualquer script que rode na página lê o cookie e manda para o servidor dele.
  • cookie secure: o cookie só é enviado por conexão criptografada. Com ele ligado, o token nunca viaja em claro pela rede. Sem ele, ele viaja, e qualquer pessoa na mesma rede o lê.

As duas defesas são complementares e não se substituem: o httpOnly protege contra o script na página, e o secure protege contra a rede. Um navegador aceita cookie httpOnly sem secure — e aí o token está protegido contra um ataque e exposto ao outro. O professor insere essa frase porque a turma costuma achar que "põei httpOnly e pronto".

O JWT, ou JSON Web Token, é a alternativa ao token guardado no servidor, e o professor a menciona com a ressalva que ela merece. Um JWT carrega dentro de si o identificador, a validade e a assinatura, e assinar token é o que permite ao servidor conferir sem consultar nada: a assinatura prova que foi o servidor que montou o token, e não alguém que o leu e mudou. O ganho é o mesmo que a tabela de sessão promete, sem a consulta por requisição. O custo é que o token expirado continua válido até a data escrita nele, e revogar antes disso exige uma lista de revogação que anula o ganho. A escolha entre JSON Web Token e token de sessão é uma escolha de requisito, e não de preferência.

O roubo de token é o ataque contra o qual as duas qualidades do cookie existem, e o roteiro é o mesmo dos dois lados: alguém obtém o valor, usa como se fosse a pessoa, e o sistema não tem como saber que é mentira. É por isso que a validade é curta e por isso que o HTTPS é obrigatório. A aula 2 do dia 9 volta a esse ponto com o transporte.

Renovar token: o que muda para a pessoa e o que muda para o código

Expirar token é o fim da validade, e renovar token é trocar o token que está quase no fim que está quase expirando por um novo, sem obrigar a pessoa a logar de novo. É a operação que faz a validade curta ser aceitável, e ela tem uma decisão de projeto que o professor pede explicitamente: renovar por conta própria ou por refresh token.

A forma simples é a placa detectar que está com pouco tempo e pedir um novo. O refresh token é a forma robusta: um segundo token, de validade muito maior, que existe só para emitir tokens novos. O primeiro é curto e vai no cookie httpOnly e secure; o segundo é mais longo e vai para um lugar mais protegido, e a maioria das implementações o entrega pela rota de refresh em vez de guardá-lo no cookie. A razão é o custo do roubo: quem rouba o token curto tem minutos; quem rouba o de renovação tem meses.

Encerrar sessão é a operação oposta e é a que o dia 3 vai cobrar. Logout não é "esquecer o estado no navegador": é apagar a linha da sessão no servidor. Sem apagar, o token continua válido até expirar, e a pessoa que saiu do computador continua com acesso. Esse é o logout que funciona, e a diferença entre os dois aparece no item 7 da atividade.

O token expirado tem um comportamento que o professor descreve como o mais importante do ponto de vista da placa: quando o servidor devolve 401, a placa não tenta de novo com o mesmo token. Ela apaga o token, avisa que precisa de login, e para. Reenviar com o mesmo token é o caminho que transforma um problema de cinco segundos em um problema de bateria.

memset no token, e a linha que compara a variável com ela mesma

O sketch desta aula tem a cena que dá nome à aula: um corte de energia simulado, com o token em RAM zerado e o token em flash intacto. A função simularReinicio faz o memset do arranjo que está em RAM, e o comentário explica que na placa de verdade o processo é o loop parar e o setup rodar de novo.

A parte que o professor pede para ler com calma está no quarto passo, e ela tem um defeito real. A linha que escreve o que sobrou depois do corte é uma chamada de impressão cuja expressão de valor é tokenPresente ? "nada" : "nada". As duas alternativas são a mesma string, e a condição não muda o resultado em nenhum caso. O programa imprime "nada" sempre, inclusive no caso em que o token no flash sobreviveu e poderia ter sido mostrado.

Isso é o que se chama de condição que não testa nada, e é uma classe de defeito que a turma vai encontrar o ano inteiro: comparação de valor consigo mesmo, variável calculada e nunca usada, condição cuja resposta é a mesma nos dois braços. O sintoma é um programa que funciona — compila, não dá erro, não avisa — e que mente sobre uma informação. A única forma de pegar é ler a expressão, e por isso o professor lê em voz alta na frente da turma.

A correção cabe em trocar a condição, e a versão que o professor prefere mostra o que aconteceu de verdade: o ramVazio com o resultado de uma comparação do arranjo de RAM com um arranjo zerado, e o flashIntacto com a comparação do arranjo persistido com o valor esperado. Feito isso, o mesmo passo passa a imprimir os dois estados, e a diferença entre RAM e flash fica visível em uma linha de monitor em vez de ser afirmada em um comentário.

O memset merece uma explicação própria, porque ele é a linha que executa a comparação. Ele preenche um bloco de memória com um valor, e é a forma correta de zerar um arranjo de bytes. A alternativa — um laço com for — funciona e é mais lenta, e em C++ a biblioteca padrão já oferece a versão rápida. O ponto para a turma reter é o escopo: o que o memset apaga é a variável que está na memória de trabalho, e nada que esteja gravado no programa. É por isso que o LED do sketch volta a acender depois do corte, e é por isso que o arranjo const do flash continua idêntico.

Atividade

Montagem:

  • LED da placa no GPIO2, com o resistor de 330 ohm e o jumper para o GND.
  • Cabo USB conectado, monitor serial em 115200.
  • Nenhum sensor nesta aula: o objeto do teste é o armazenamento, e um sensor só adicionaria variação ao tempo.
  • Terminal do Node aberto, com o arquivo de sessão do projeto em uma aba.
  1. Escreva no papel as três perguntas da sessão — quem autenticou, quando autenticou, como revogo — e ao lado de cada uma escreva onde a resposta está guardada no seu projeto hoje. Marque com um círculo as perguntas que hoje não têm resposta.
  2. No seu projeto, ache a estrutura que guarda a sessão. Ela é um objeto na memória do processo ou uma tabela no banco? Escreva o nome do arquivo e a linha onde ela é criada. Diga em uma frase o que acontece com ela quando o processo reinicia.
  3. Grave o sketch da resolução e rode por pelo menos vinte segundos, o que cobre os cinco passos. Copie no caderno a linha do passo 1, a do passo 4 e a do passo 5.
  4. Anote o que o passo 4 imprime depois da palavra "sobrou". Agora leia a expressão que produz esse texto e escreva quantas Information diferentes ela pode produzir. QuantasInformation você esperava?
  5. Troque a expressão por uma que teste de verdade os dois arranjos, um na RAM e outro no flash, e grave de novo. O que mudou no passo 4? Escreva as duas Information que agora aparecem.
  6. Desligue e reconecte o cabo USB da placa com o sketch rodando. O que a placa imprime no banner que mostra que ela reiniciou? No seu projeto, o que acontece com a sessão da pessoa nesse mesmo instante?
  7. Escreva no papel a diferença entre duas coisas: sair da tela de login e encerrar a sessão. Para cada uma, escreva o que acontece com o token da pessoa e com a linha da sessão no servidor.
  8. Escreva em uma frase por linha as respostas de duas perguntas: por que o cookie httpOnly não protege o token no caminho da rede, e por que o secure não protege o token de um script na página.

Nota: 10 pontos. Critério de fim: as três linhas do monitor copiadas dos passos 1, 4 e 5, e as duas informações do passo 4 anotadas antes e depois da correção da expressão.

Resolucao

O sketch da aula está em codigo/t3/dia02/aula2.ino.

// Aula 2 do 3o trimestre: sessao e token na placa.
//
// A placa guarda o token em memoria RAM, e a RAM some quando a energia acaba.
// Quando ela volta, a sessao acabou. E limitacao, nao defeito: e o que acontece
// com todo dispositivo que nao tem armazenamento. A placa mostra as duas
// situacoes lado a lado.

#include <Arduino.h>
#include <time.h>

const int PIN_LED = 2;
const unsigned long PASSO_MS = 3000;

// Token que o servidor entregou no login.
uint8_t tokenEmMemoria[16] = {
  0x51, 0x2c, 0x88, 0xf0, 0x64, 0xb3, 0x1d, 0x8a,
  0xe5, 0x07, 0xc9, 0x42, 0x76, 0xbe, 0x30, 0x5d
};

// O MESMO token gravado em memoria nao volatil: sobrevive ao corte de energia.
// A placa nao tem isso nativamente; o sketch mostra a diferenca de conceito.
const uint8_t TOKEN_PERSISTIDO[16] = {
  0x51, 0x2c, 0x88, 0xf0, 0x64, 0xb3, 0x1d, 0x8a,
  0xe5, 0x07, 0xc9, 0x42, 0x76, 0xbe, 0x30, 0x5d
};

const unsigned long TOKEN_EMITIDO_EM = 1758000000UL;
const unsigned long VALIDADE_SEGUNDOS = 2UL * 60UL * 60UL;

// Cookie httpOnly e uma coisa do navegador. Na placa nao existe cookie:
// quem decide o que a placa pode fazer e o SERVIDOR, quando o token chega.
bool tokenPresente = true;

unsigned long restantesDe(time_t agora) {
  unsigned long idade = (unsigned long)agora - TOKEN_EMITIDO_EM;
  if (idade >= VALIDADE_SEGUNDOS) return 0;
  return VALIDADE_SEGUNDOS - idade;
}

void mostrarToken(const uint8_t* t, const char* rotulo) {
  Serial.print(rotulo);
  Serial.print(": ");
  for (int i = 0; i < 16; i++) {
    if (t[i] < 0x10) Serial.print('0');
    Serial.print(t[i], HEX);
  }
  Serial.println();
}

// Simula o corte de energia: zera a RAM, mantem o flash.
void simularReinicio() {
  Serial.println();
  Serial.println(">>> CORTE DE ENERGIA. a placa reinicia em 2 segundos <<<");
  Serial.println();
  // Na placa de verdade, o loop para e o setup roda de novo. Aqui o que
  // acontece e: o token em RAM e perdido. O persistido continua.
  memset(tokenEmMemoria, 0, sizeof(tokenEmMemoria));
  tokenPresente = false;
}

int passo = 0;
int totalPassos = 5;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  Serial.println();
  Serial.println("Dia 02 aula 2 — sessao e token: manter o usuario logado");
  Serial.println();
  Serial.println("Duas formas de guardar o mesmo token:");
  Serial.println("  RAM (tokenEmMemoria)     — se perde quando a energia acaba");
  Serial.println("  flash (TOKEN_PERSISTIDO) — sobrevive ao corte de energia");
  Serial.println();
  Serial.println("O que muda para o usuario: com token em RAM, cada corte de luz");
  Serial.println("e um login novo. Com token em flash, so expira em 2 horas.");
  Serial.println();
  Serial.println("O que muda para o SERVIDOR: nada. Ele so valida o token.");
  Serial.println("Guardar o token e responsabilidade da placa; conferir e do servidor.");
}

void loop() {
  time_t agora = time(nullptr);
  unsigned long rest = restantesDe(agora);

  switch (passo) {
    case 0:
      Serial.println("--- 1. a placa acorda com a sessao ativa ---");
      mostrarToken(tokenEmMemoria, "  token em RAM");
      mostrarToken(TOKEN_PERSISTIDO, "  token no flash");
      Serial.printf("  validade restante: %lus\n", rest);
      Serial.println("  a placa envia o token em toda requisicao; o servidor compara.");
      break;

    case 1:
      Serial.println("--- 2. o que acontece se o token for roubado ---");
      Serial.println("  alguem le o token do trafego HTTP (que NAO e HTTPS).");
      Serial.println("  o ladrão tem 2 horas de acesso com aquele token.");
      Serial.println("  e por isso que HTTPS nao e opcional: sem ele o token viaja a vista.");
      Serial.println("  o que o HTTPS protege: o token em transito, nao o token guardado.");
      break;

    case 2:
      Serial.println("--- 3. o token expira ---");
      Serial.println("  apos 2 horas o servidor devolve 401, mesmo com token valido na placa.");
      Serial.println("  a placa apaga o token e volta a tela de login.");
      Serial.println("  isso e RENOVACAO: a placa pega um token novo e continua.");
      break;

    case 3:
      Serial.println("--- 4. corte de energia ---");
      if (tokenPresente) {
        Serial.println("  a energia acabou com o token na RAM.");
        Serial.printf("  depois do corte, em RAM sobrou: %s\n",
                      tokenPresente ? "nada" : "nada");
        Serial.println("  no flash o token continua la. Se ainda nao expirou, a placa");
        Serial.println("  volta conectada sem pedir login de novo.");
        simularReinicio();
      } else {
        Serial.println("  (a placa ja reiniciou e perdeu o token da RAM)");
      }
      break;

    case 4:
      Serial.println("--- 5. a placa volta a pedir login ---");
      Serial.println("  sem token em RAM, a placa envia uma requisicao SEM token.");
      Serial.println("  o servidor responde 401. A placa sabe que precisa de login.");
      digitalWrite(PIN_LED, LOW);
      Serial.println();
      Serial.println("CONCLUSAO: a placa guarda token, nunca senha.");
      Serial.println("  A limitation real de IoT e a memoria: token em RAM nao sobrevive");
      Serial.println("  a queda de energia. Guardar em flash e possivel, mas quem decide");
      Serial.println("  ainda e o servidor, pela validade. A placa nao confia em si mesma.");
      break;
  }

  digitalWrite(PIN_LED, (tokenPresente && rest > 0) ? HIGH : LOW);
  passo = (passo + 1) % totalPassos;
  delay(PASSO_MS);
}

Por que assim e não de outro jeito. O sketch existe para mostrar as duasformas de guardar o mesmo token, e ele faz isso declarando os dois em vez de escolher um. tokenEmMemoria é um arranjo de bytes comum, e TOKEN_PERSISTIDO é um arranjo const. Os dois têm o mesmo conteúdo — o professor pode comparar os bytes no monitor e ver que são idênticos — e o que muda é o que acontece com eles quando a energia acaba. É a demonstração mais direta possível de que a diferença entre perder e não perder não é de código, é de armazenamento.

A linha de comentário que diz que a placa não tem memória persistente nativamente é verdadeira e o professor a completa em voz alta, porque é o que vai aparecer na aula do dia 6: a placa tem memória não volátil, e ela se usa pela biblioteca Preferences, que grava em uma área da flash separada do programa. O sketch desta aula não usa a biblioteca para manter a aula sem rede e sem setup longo, e o resultado visual é o mesmo: os dois tokens continuam impressos na tela depois do corte.

A função restantesDe é uma cópia quase idêntica da segundosRestantes do sketch da aula 1, e a duplicação é deliberada. As duas aulas são independentes no eixo, e o material não promete que um arquivo dependa do anterior. O que o professor aponta é a consequência: essa mesma função tem o mesmo defeito de unsigned nas duas aulas, e por isso o item 4 da atividade existe nas duas.

O switch de cinco casos é a estrutura da aula, e cada caso corresponde a um momento do roteiro: acorda com sessão ativa, token roubado, token expirado, corte de energia, e volta a pedir login. A ordem importa e o professor explica: o corte de energia vem antes da volta a pedir login, porque é o corte que produz essa necessidade. Com a ordem trocada, o roteiro contaria a consequência antes da causa, e a turma leria um conjunto de Symptoms e não uma sequência.

O digitalWrite no fim do loop acende o LED quando há token presente e ainda resta validade. Esse AND entre as duas condições é a regra de exibição do painel escrita em uma linha: sem token não há painel, e com token vencido também não há painel. E é a mesma regra que a aula 1 do dia 3 vai pedir na checagem de permissão: estar autenticado e ter permissão são duas coisas, e o painel só aparece quando as duas são verdadeiras.

O desvio medido, e a linha que imprime "nada" sem testar nada. Rodando o sketch como está, o quarto passo imprime a linha depois do corte, em RAM sobrou: nada — e a expressão que produz esse texto é tokenPresente ? "nada" : "nada". As duas alternativas da condição são idênticas, então o operador ternário devolve "nada" nos dois casos. O programa não está errado do ponto de vista do compilador, que aceita a expressão sem aviso, e o resultado na tela parece correto: depois de um corte de energia, o token que estava na RAM realmente não sobreviveu.

O que o texto mente é sobre o motivo. A linha sugere que a placa conferiu que não sobrou nada na RAM, e ela não conferiu nada — ela imprimiu uma constante. Se alguém mudar o código para que o token sobreviva à RAM, ou se o corte passar a acontecer depois da placa ter guardado o token em outro lugar, a frase continua dizendo "nada" e o diagnóstico passa a ser falso. É a mesma classe do token vencido da aula anterior: um programa que funciona e informa errado.

A correção que o professor escreve na frente da turma usa a biblioteca, e não um laço. memcmp compara dois blocos de memória e devolve zero quando são iguais, o que transforma a comparação em uma pergunta booleana legível. Com ramVazio e flashIntacto calculados antes da impressão, o passo passa a mostrar as duas informações, e a frase "depois do corte, em RAM sobrou" volta a ser uma afirmação que o professor pode usar para corrigir alguém.

O que o LED acende e apaga ao longo dos cinco passos é a parte que o professor usa para fechar sem olhar o monitor: ele acende no primeiro passo, apaga depois do corte, e fica apagado no quinto. A curva da luz ao longo do roteiro é o resumo visual da aula — o que está na memória do processo acaba, e o que está gravado fica.

Criterios de correcao

CritérioPontos
As três perguntas da sessão escritas, com o lugar onde a resposta está guardada2 pontos
A estrutura de sessão localizada no projeto, com arquivo, linha e o efeito do reinício2 pontos
As três linhas do monitor copiadas, dos passos 1, 4 e 51 ponto
A quantidade de informações que a expressão do passo 4 pode produzir, com o número esperado2 pontos
As duas informações do passo 4 depois da correção da expressão1 ponto
Item 7: a diferença entre sair da tela e encerrar a sessão, com o destino do token1 ponto
Item 8: as duas frases sobre httpOnly e secure1 ponto

Erros comuns

ErroComo apareceCorreção
Sessão em Map na memória do processoconst sessoes = new Map(); e todo mundo desloga quando o servidor reinicia"Sessão em memória funciona até o primeiro reinício. Grave em tabela: identificador, dono, criação e expiração. O custo é uma consulta por requisição, e o preço de um logout em massa é maior."
Token com validade de um mês, para não deslogarTodo mundo continua logado por semanas"Validade longa multiplica o estrago de um roubo. Com renovação automática, a validade curta não incomoda ninguém: a pessoa nunca percebe."
Cookie sem httpOnlydocument.cookie e o token legível no console"httpOnly tira o token do alcance do JavaScript da página. Sem ele, o primeiro XSS que roda na sua página lê o cookie e manda para o servidor de quem quiser."
Cookie sem secureToken viaja em claro quando alguém abre a página por http"secure prende o token ao transporte criptografado. httpOnly e secure resolvem problemas diferentes: um é o script da página, o outro é a rede."
Revogar token só no navegadorO logout limpa o cookie e não toca no servidor"Logout que não apaga a sessão no servidor deixa acesso válido até expirar. Apague a linha da sessão, e invalidate o token no servidor."
Reenviar o mesmo token depois do 401A placa entra em laço de reenvio e acaba a bateria"401 é recusa, não atraso. Apague o token, avise que precisa de login e pare. Gastar bateria refazendo pergunta que já tem resposta é a pior forma de esperar."
Renovar a cada requisiçãoToda requisição recebe token novo e o servidor gasta uma escrita a cada chamada"Renove só perto do fim da validade. Renovar a cada requisição troca um custo pequeno por um custo grande sem ganho nenhum."
Comparar variável com ela mesmacondicao ? "nada" : "nada" e a tela sempre mostra o mesmo texto"Os dois braços iguais quer dizer que a condição não decide nada. Escreva a pergunta de verdade — o arranjo está vazio? — e compare com o valor esperado."

Desafio extra

Meça, no seu projeto, quanto tempo uma consulta de sessão na tabela custa em relação a uma busca no Map da memória. Rode o mesmo caminho duzentas vezes e anote os dois números em milissegundos. Depois rode duzentas requisições ao mesmo tempo, com dez usuários, e veja o que acontece com o número. Escreva na sua frente uma frase decidindo se vale trocar a sessão em tabela pela sessão em JWT assinado, e o que você precisaria acrescentar ao sistema para poder revogar um JWT antes de ele expirar. A pergunta que o professor espera de resposta: se o servidor pode revogar instantaneamente, o JWT ainda economiza alguma coisa, e qual?

>

A resolucao, compilada

// Aula 2 do 3o trimestre: sessao e token na placa.
//
// A placa guarda o token em memoria RAM, e a RAM some quando a energia acaba.
// Quando ela volta, a sessao acabou. E limitacao, nao defeito: e o que acontece
// com todo dispositivo que nao tem armazenamento. A placa mostra as duas
// situacoes lado a lado.

#include <Arduino.h>
#include <time.h>

const int PIN_LED = 2;
const unsigned long PASSO_MS = 3000;

// Token que o servidor entregou no login.
uint8_t tokenEmMemoria[16] = {
  0x51, 0x2c, 0x88, 0xf0, 0x64, 0xb3, 0x1d, 0x8a,
  0xe5, 0x07, 0xc9, 0x42, 0x76, 0xbe, 0x30, 0x5d
};

// O MESMO token gravado em memoria nao volatil: sobrevive ao corte de energia.
// A placa nao tem isso nativamente; o sketch mostra a diferenca de conceito.
const uint8_t TOKEN_PERSISTIDO[16] = {
  0x51, 0x2c, 0x88, 0xf0, 0x64, 0xb3, 0x1d, 0x8a,
  0xe5, 0x07, 0xc9, 0x42, 0x76, 0xbe, 0x30, 0x5d
};

const unsigned long TOKEN_EMITIDO_EM = 1758000000UL;
const unsigned long VALIDADE_SEGUNDOS = 2UL * 60UL * 60UL;

// Cookie httpOnly e uma coisa do navegador. Na placa nao existe cookie:
// quem decide o que a placa pode fazer e o SERVIDOR, quando o token chega.
bool tokenPresente = true;

unsigned long restantesDe(time_t agora) {
  unsigned long idade = (unsigned long)agora - TOKEN_EMITIDO_EM;
  if (idade >= VALIDADE_SEGUNDOS) return 0;
  return VALIDADE_SEGUNDOS - idade;
}

void mostrarToken(const uint8_t* t, const char* rotulo) {
  Serial.print(rotulo);
  Serial.print(": ");
  for (int i = 0; i < 16; i++) {
    if (t[i] < 0x10) Serial.print('0');
    Serial.print(t[i], HEX);
  }
  Serial.println();
}

// Simula o corte de energia: zera a RAM, mantem o flash.
void simularReinicio() {
  Serial.println();
  Serial.println(">>> CORTE DE ENERGIA. a placa reinicia em 2 segundos <<<");
  Serial.println();
  // Na placa de verdade, o loop para e o setup roda de novo. Aqui o que
  // acontece e: o token em RAM e perdido. O persistido continua.
  memset(tokenEmMemoria, 0, sizeof(tokenEmMemoria));
  tokenPresente = false;
}

int passo = 0;
int totalPassos = 5;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  Serial.println();
  Serial.println("Dia 02 aula 2 — sessao e token: manter o usuario logado");
  Serial.println();
  Serial.println("Duas formas de guardar o mesmo token:");
  Serial.println("  RAM (tokenEmMemoria)     — se perde quando a energia acaba");
  Serial.println("  flash (TOKEN_PERSISTIDO) — sobrevive ao corte de energia");
  Serial.println();
  Serial.println("O que muda para o usuario: com token em RAM, cada corte de luz");
  Serial.println("e um login novo. Com token em flash, so expira em 2 horas.");
  Serial.println();
  Serial.println("O que muda para o SERVIDOR: nada. Ele so valida o token.");
  Serial.println("Guardar o token e responsabilidade da placa; conferir e do servidor.");
}

void loop() {
  time_t agora = time(nullptr);
  unsigned long rest = restantesDe(agora);

  switch (passo) {
    case 0:
      Serial.println("--- 1. a placa acorda com a sessao ativa ---");
      mostrarToken(tokenEmMemoria, "  token em RAM");
      mostrarToken(TOKEN_PERSISTIDO, "  token no flash");
      Serial.printf("  validade restante: %lus\n", rest);
      Serial.println("  a placa envia o token em toda requisicao; o servidor compara.");
      break;

    case 1:
      Serial.println("--- 2. o que acontece se o token for roubado ---");
      Serial.println("  alguem le o token do trafego HTTP (que NAO e HTTPS).");
      Serial.println("  o ladrão tem 2 horas de acesso com aquele token.");
      Serial.println("  e por isso que HTTPS nao e opcional: sem ele o token viaja a vista.");
      Serial.println("  o que o HTTPS protege: o token em transito, nao o token guardado.");
      break;

    case 2:
      Serial.println("--- 3. o token expira ---");
      Serial.println("  apos 2 horas o servidor devolve 401, mesmo com token valido na placa.");
      Serial.println("  a placa apaga o token e volta a tela de login.");
      Serial.println("  isso e RENOVACAO: a placa pega um token novo e continua.");
      break;

    case 3:
      Serial.println("--- 4. corte de energia ---");
      if (tokenPresente) {
        Serial.println("  a energia acabou com o token na RAM.");
        Serial.printf("  depois do corte, em RAM sobrou: %s\n",
                      tokenPresente ? "nada" : "nada");
        Serial.println("  no flash o token continua la. Se ainda nao expirou, a placa");
        Serial.println("  volta conectada sem pedir login de novo.");
        simularReinicio();
      } else {
        Serial.println("  (a placa ja reiniciou e perdeu o token da RAM)");
      }
      break;

    case 4:
      Serial.println("--- 5. a placa volta a pedir login ---");
      Serial.println("  sem token em RAM, a placa envia uma requisicao SEM token.");
      Serial.println("  o servidor responde 401. A placa sabe que precisa de login.");
      digitalWrite(PIN_LED, LOW);
      Serial.println();
      Serial.println("CONCLUSAO: a placa guarda token, nunca senha.");
      Serial.println("  A limitation real de IoT e a memoria: token em RAM nao sobrevive");
      Serial.println("  a queda de energia. Guardar em flash e possivel, mas quem decide");
      Serial.println("  ainda e o servidor, pela validade. A placa nao confia em si mesma.");
      break;
  }

  digitalWrite(PIN_LED, (tokenPresente && rest > 0) ? HIGH : LOW);
  passo = (passo + 1) % totalPassos;
  delay(PASSO_MS);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 3 dia02 aula2.