-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SD] Add "Segurança e Criptografia" page (#1018)
Co-authored-by: fmata97 <[email protected]>
- Loading branch information
1 parent
7deea09
commit 4d0a8a4
Showing
7 changed files
with
5,939 additions
and
2,760 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,307 @@ | ||
--- | ||
title: Segurança e Criptografia | ||
description: > | ||
Chaves simétricas e assimétricas. | ||
Assinaturas digitais e certificados. | ||
Canais seguros. | ||
path: /sd/seguranca-e-criptografia | ||
type: content | ||
--- | ||
|
||
# Segurança e Criptografia | ||
|
||
```toc | ||
``` | ||
|
||
## Mecanismos Básicos de Comunicação Segura | ||
|
||
Iremos abordar como é possível comunicar com outra entidade de forma segura, | ||
garantindo que não existem intrusos a intercetar a conversa, a modificar o | ||
conteúdo das mensagens ou a fazer-se passar por um de nós. | ||
|
||
**Algumas notas prévias**: | ||
|
||
- Iremos designar por $A$ e $B$ as entidades que pretendem estabelecer um canal seguro | ||
(tipicamente designadas por "Alice" e "Bob") | ||
- Uma entidade que queira escutar ou perturbar a comunicação será designada por $T$ | ||
(tipicamente designada por "Trudy", de "intruder") | ||
|
||
Vamos estudar de seguida mecanismos básicos utilizados para tornar um canal seguro. | ||
|
||
### Chaves simétricas | ||
|
||
No contexto de uso de uma chave simétrica existe: | ||
|
||
- uma chave secreta $K_S$ conhecida por $A$ e $B$ | ||
- um texto $m$ (_plaintext_) | ||
- uma função de cifra que produz um texto cifrado (_ciphertext_) a partir de $m$ | ||
e $K_S$, designada $K_S(m)$ | ||
- **NOTA**: a chave em si não cifra a mensagem, esta é utilizada por um algoritmo | ||
- uma função para decifrar o texto cifrado que usa a mesma chave: $m = K_S(K_S(m))$ | ||
|
||
![Uso de chave simétrica](./assets/0007-symmetrical-key.svg#dark=3) | ||
|
||
Podemos nestas condições criar um canal seguro entre $A$ e $B$ desde que estes | ||
conheçam previamente a chave, mas **como é que a distribuição da chave é feita**? | ||
Por exemplo, pode ser trocada fisicamente ou então derivada de uma palavra chave | ||
combinada previamente. | ||
|
||
Existem **dois grandes problemas** com o uso desta chave: | ||
|
||
- Como podemos ter a certeza de que estamos de facto a ler o que foi enviado pela | ||
outra entidade? | ||
- Um intruso $T$ pode simplesmente escutar o que $A$ envia para $B$ e reenviar | ||
exatamente a mesma mensagem a $B$ (fazendo com que $B$ execute uma transferência | ||
bancária duas vezes por exemplo) | ||
|
||
:::tip[Fatores que influenciam a segurança da chave] | ||
|
||
A segurança depende não só do algoritmo de cifra usado como também do tamanho | ||
da própria chave (nº _bits_) e do poder computacional do adversário. | ||
|
||
::: | ||
|
||
### Chaves assimétricas | ||
|
||
Agora em vez de termos apenas uma chave partilhada, cada entidade passa a possuir | ||
um par de chaves ($K_+$, $K_-$), designadas por chave pública e privada, | ||
respectivamente. Estas chaves apresentam as seguintes propriedades: | ||
|
||
- a chave pública $K_+$ é partilhada com as outras entidades | ||
- a chave privada $K_-$ é mantida secreta | ||
- $K_+(K_-(m)) = m$ | ||
- $K_-(K_+(m)) = m$ | ||
- não é possível obter uma chave a partir da outra | ||
|
||
A utilização destas chaves permite que, ao enviarmos uma mensagem para $B$, se a | ||
encriptarmos com a chave $K_{B+}$, somente $B$, com a sua chave privada $K_{B-}$, | ||
será capaz de decifrá-la. Para além disso, se $A$ desejar provar a $B$ que o texto | ||
$m$ foi produzido por si, basta encriptar $m$ com $K_{A-}$ e $B$ decifrar com | ||
$K_{A+}$. Se for bem sucedido, então $m$ foi garantidamente enviado por $A$, já | ||
que apenas este conhece $K_{A-}$. | ||
|
||
![Uso de chave assimétrica](./assets/0007-assymmetrical-key.svg#dark=3) | ||
|
||
O uso destas chaves tem no entanto uma grande desvantagem: **a criptografia assimétrica | ||
é muito mais lenta do que a simétrica** (100 a 1000 vezes). | ||
Por este motivo, é muito comum utilizar esta criptografia para negociar uma chave | ||
simétrica durante a criação do canal seguro (de forma a garantir que estamos a | ||
comunicar com a entidade certa), que será posteriormente utilizada na encriptação | ||
de toda a comunicação (**método conhecido como "chave mista"**). | ||
Exemplo: | ||
|
||
1. $A$ cria uma chave simétrica $K_S$ e cifra-a com $K_{B+}$ | ||
2. Apenas $B$ consegue obter $K_S$ já que é o único que possui $K_{B-}$ | ||
|
||
:::warning[Perigo de divulgar a chave pública] | ||
|
||
Apesar da divulgação da chave pública parecer fácil visto que pode ser partilhada, | ||
tem um perigo associado: | ||
![intruso em chave assimétrica](./assets/0007-assymetrical-key-intruder.svg#dark=3) | ||
|
||
Este ataque é conhecido como **_"Men-in-the-middle attack"_**, onde o atacante | ||
reencaminha (e possivelmente altera) secretamente as comunicações entre duas | ||
entidades que acreditam estar a comunicar diretamente uma com a outra. | ||
|
||
::: | ||
|
||
:::tip[Criptografia RSA] | ||
|
||
Já abordámos em EMD um algoritmo de criptografia assimétrica, o [**RSA**](/emd/rsa). | ||
|
||
::: | ||
|
||
### Funções de Hash Criptográficas | ||
|
||
De forma a conseguirmos ter a **garantia que estamos a ler o que foi enviado** pelo | ||
remetente, podemos utilizar uma **função de _hash_** e enviar o _hash_ do texto | ||
(também conhecido como _"digest"_) juntamente com o mesmo. | ||
|
||
Dado um texto $m$, uma função de hash criptográfica cria um _hash_ de tamanho fixo | ||
$H(m)$. | ||
Estas funções apresentam uma propriedade fundamental que é ser computacionalmente | ||
inviável encontrar em tempo útil outro texto $m'$ tal que $H(m') = H(m)$, ou seja, | ||
**em termos práticos é impossível modificar o texto de forma a que tenha o mesmo | ||
_hash_ que o original**. | ||
|
||
Para aumentar ainda mais a segurança do _hash_, podemos calculá-lo a partir do texto | ||
e da chave privada (simétrica), em vez de apenas do texto. | ||
|
||
### _Nonce_ | ||
|
||
Um _nonce_ **é uma palavra chave única** que é utilizada para identificar uma troca | ||
de mensagens e que **não é usada novamente** em comunicações posteriores. | ||
|
||
![Utilização de _nonce_](./assets/0007-nonce.svg#dark=3) | ||
|
||
O objetivo da utilização do _nonce_ é eliminar uma vulnerabilidade mencionada | ||
anteriormente: um atacante pode simplesmente repetir as mensagens cifradas | ||
(problema conhecido como _"replay attack"_). | ||
Ao utilizar uma palavra chave única por comunicação, caso um atacante repita | ||
as mensagens posteriormente, estas serão ignoradas pelo destinatário, pois este | ||
sabe que se tratam de mensagens repetidas. | ||
|
||
O cálculo de um _nonce_ é tipicamente baseado numa das seguintes técnicas (ou | ||
na sua combinação): | ||
|
||
- **_timestamps_** (segurança dependente da segurança do relógio da máquina) | ||
- **números de sequência monotonicamente crescentes** (segurança dependente da capacidade | ||
das máquinas memorizarem o último número usado) | ||
|
||
Os _nonces_ também podem ser utilizados para garantir que estamos a falar com a | ||
entidade certa: | ||
|
||
- encriptamos o _nonce_ com uma chave | ||
- esperamos que a outra entidade devolva uma versão modificada (determinista) do | ||
_nonce_ enviado (por exemplo, _nonce_ + 1) | ||
- como apenas a outra entidade detém a chave de forma a decifrar o que enviamos, | ||
apenas esta consegue ler o _nonce_ enviado e modificá-lo como combinado | ||
|
||
![Confirmação de identidade com _nonce_](./assets/0007-nonce-identity-confirmation.svg#dark=3) | ||
|
||
## Mecanismos Avançados de Comunicação Segura | ||
|
||
Combinando todos os mecanismos abordados até agora, podemos obter outros mais | ||
sofisticados, como por exemplo: | ||
|
||
- Assinaturas digitais | ||
- Infra-estruturas de chaves públicas e certificados | ||
- Troca segura de e-mails | ||
- Canais seguros | ||
- Sistemas de autenticação com _"single sign-on"_ | ||
|
||
### Assinaturas Digitais com Chave Simétrica | ||
|
||
$A$ e $B$ partilham uma chave $K_S$ e $A$ quer assinar um texto $m$ de forma a que | ||
$B$ possa confirmar que $m$ foi gerado por $A$ (autenticidade) e que não foi alterado | ||
durante o percurso (integridade). | ||
Para tal: | ||
|
||
- $A$ cria uma versão estendida do texto ($m|K_S$), concatenando ao texto $m$ o | ||
segredo $K_S$ | ||
- $A$ usa uma função de _hash_ criptográfica para gerar o _digest_ da versão estendida | ||
$S_m = H(m|K_S)$ | ||
- $A$ envia ambos a $B$ | ||
- $B$ verifica se $S_m = H(m|K_S)$ | ||
|
||
Este tipo de assinatura apenas pode ser usado entre duas entidades que partilham | ||
um segredo e é tipicamente designado por **Message Authentication Code (MAC)** | ||
|
||
### Assinaturas Digitais com Chave Assimétrica | ||
|
||
$A$ tem $K_{A-}$ e $B$ conhece $K_{A+}$ (veremos como é que isto é possível com | ||
certificados). $A$ quer assinar um texto $m$ de forma a que $B$ possa confirmar | ||
a sua autenticidade e integridade, e que também **consiga provar a outro que a | ||
mensagem $m$ foi de facto enviada por $A$ (não repúdio)**. | ||
Para tal: | ||
|
||
- $A$ usa uma função de _hash_ criptográfica para gerar o _digest_ da mensagem $H(m)$ | ||
- $A$ usa a sua chave privada $K_{A-}$ para cifrar $H(m)$ | ||
- $K_{A-}(H(m))$ serve como assinatura de $m$ | ||
- qualquer entidade pode usar a chave pública $K_{A+}$ para validar a assinatura | ||
|
||
### Infra-estruturas de chaves públicas e certificados | ||
|
||
Quando falámos de chaves assimétricas, vimos que [corriamos um | ||
perigo](#chaves-assimétricas:~:text=PERIGO%20DE%20DIVULGAR%20A%20CHAVE%20P%C3%9ABLICA) | ||
ao tentar descobrir a chave pública da outra entidade: estávamos suscetíveis ao | ||
**_Men-in-the-middle attack_**. | ||
Esta vulnerabilidade existe já que não conseguimos ter a certeza de que estamos | ||
de facto a falar com a entidade pretendida, nem de verificar a autenticidade | ||
da chave pública que nos é devolvida. | ||
|
||
Há duas formas de evitar este problema: | ||
|
||
- se soubermos de antemão a chave pública de $B$, enviamos-lhe a chave simétrica | ||
cifrada com a sua chave pública (assim um intruso não consegue obter a | ||
nossa chave e fazer-se passar por $B$) | ||
- se existir um certificado digital que confirma que a chave que recebemos é de | ||
facto a de $B$, $B$ pode enviar-nos o certificado | ||
|
||
As autoridades que emitem estes certificados denominam-se **_Certification Authorities_ | ||
(CA)**. | ||
Assume-se que a chave pública das CA's é previamente conhecida (normalmente os | ||
_browsers_ trazem os certificados associados às CA's pré-instalados). | ||
|
||
Um certificado digital consiste num documento que associa uma entidade a uma chave | ||
pública e que está assinado pela CA. | ||
|
||
Após verificarmos que o certificado recebido está assinado pela CA, ainda precisamos | ||
de garantir que a entidade com a qual estamos a contactar é de facto legítima antes de | ||
negociarmos uma chave simétrica para usar durante a sessão, e para tal podemos usar | ||
a [estratégia mencionada previamente que utiliza um _nonce_](#nonce): | ||
|
||
- ciframos um _nonce_ com a chave pública retirada do certificado | ||
- esperamos receber uma versão modificada do mesmo | ||
|
||
Por vezes, existe a necessidade de invalidar um certo certificado. Seria caro, se | ||
não impossível, rastrear e apagar todas as cópias locais do certificado. A solução | ||
mais comum para este problema é incluir uma data de validade no próprio certificado, | ||
sendo que a receção de certificados expirados deve ser rejeitada (o titular do | ||
certificado deve solicitar a renovação do mesmo). | ||
|
||
### Troca segura de e-mails | ||
|
||
Um exemplo de sistema seguro de troca de e-mails é o **PGP**, _Pretty Good Privacy_: | ||
|
||
- cada utilizador possui um par de chaves simétricas | ||
- divulga a sua chave pública de forma a que outros possam ter acesso e confiança | ||
de que lhe pertence | ||
- **para garantir que um e-mail não é alterado**: | ||
- gera uma assinatura criptográfica do conteúdo e assina-a com a sua chave privada | ||
- o destinatário pode obter o _digest_ original e confrontá-lo com o que é gerado | ||
pelo conteúdo que recebeu | ||
- **para garantir que apenas o destinatário lê o e-mail**: | ||
- cria uma chave simétrica para cifrar o conteúdo do e-mail | ||
- cifra essa chave com a chave pública do destinatário | ||
- envia o conteúdo cifrado juntamente com a chave simétrica cifrada | ||
- como o destinatário é o único que possui a sua chave privada, apenas este | ||
consegue obter a chave simétrica para decifrar o conteúdo do e-mail | ||
|
||
### Canais seguros (SSL/TLS) | ||
|
||
Seja $A$ um _browser_ e $B$ um servidor _WWW_ que pretendem criar um canal seguro. | ||
Para tal, podem usar TLS (_Transport Layer Security_) ou SSL (_Secure Sockets Layer_, | ||
substituído pelo TLS). | ||
|
||
Iremos apresentar de uma forma simplificada o funcionamento do protocolo SSL: | ||
|
||
- $A$ envia um pedido a $B$ solicitando a sua chave pública, juntamente com um | ||
_nonce_, que será usado para tornar a ligação única | ||
- $B$ devolve um certificado com a sua chave pública, juntamente com outro _nonce_ | ||
- $A$ verifica a validade do certificado, abortando a ligação caso falhe | ||
- $A$ utiliza o _nonce_ recebido para gerar um segredo (designado por _"master | ||
secret"_) que irá partilhar com $B$ | ||
- $A$ cifra o segredo com a chave pública de $B$ e envia-lho | ||
- $B$ fica a conhecer também o _"master secret"_ | ||
- $A$ e $B$ criam de forma determinista um conjunto de chaves simétricas que usam | ||
para concretizar o canal seguro | ||
- estas chaves são geradas a partir do _"master secret"_ e dos _nonces_ trocados | ||
- são criadas 4 chaves: | ||
- $K_C =$ chave usada para cifrar os dados enviados do cliente para o servidor | ||
- $M_C =$ chave para assinar, com um _MAC_, os dados enviados do cliente para | ||
o servidor | ||
- $K_S =$ chave usada para cifrar os dados enviados do servidor para o cliente | ||
- $M_S =$ chave para assinar, com um _MAC_, os dados enviados do servidor para | ||
o cliente | ||
- os dados trocados no canal são agrupados em blocos designados por _"records"_ | ||
- cada _record_ é marcado com um campo que designa o seu tipo | ||
- existe um tipo de _record_ específico para fechar a ligação | ||
- cada _record_ é assinado pelo emissor com um _MAC_ | ||
- para gerar o _MAC_ de um _record_, o emissor usa a sua chave $M$, o tipo do | ||
_block_ e um número de sequência | ||
- _MAC_ $= \text{Hash(record||M||type||sequence\_number)}$ | ||
- impedindo assim que um _record_ seja alterado ou reordenado dentro de uma | ||
sequência sem que isso seja detetado | ||
- os _records_ são ainda cifrados antes de serem enviados, para assegurar a | ||
confidencialidade | ||
|
||
## Referências | ||
|
||
- Coulouris et al - Distributed Systems: Concepts and Design (5th Edition) | ||
- Secções 11.1 e 11.2 | ||
- Departamento de Engenharia Informática - Slides de Sistemas Distribuídos (2023/2024) | ||
- SlidesTagus-Aula11 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.