Executando verificação de segurança...
3

Comparar algoritmo criptográficos sem antes averiguar a geração de chaves é como comparar carros modernos sem motor. Primeiros, temos que saber as diferenças de hashing e encriptação. Os algoritmos mostrados na tabela são apenas de encriptação/decriptação, os quais ainda dependem de um algoritmo de geração de chaves para funcionar.

Nesse post do meu blog eu explico o quão seguro é uma criptografia xor, o que muitos falam que é muito inseguro devido sua simplicidade no algoritmo mas ignoram o principal em uma operação criptográfica: o quão segura é sua chave.

Muitos sistemas não sabem o conceito de segurança criptográfica e dedicam toda sua segurança em um código copiado-e-colado que pegaram da internet. Configurar um algoritmo AES para encriptar/decriptar informação não é simples, é complexo, pode ter várias falhas se não fizer direito e cada situação é uma situação.

Além disso, tentar decriptar algo sem saber algoritmo usado, fluxo de criptografia (a forma que você aplicou o salt, chaves privadas e conteúdo), algoritmo usado para gerar a chave, o tamanho da chave, o vetor de inicialização da chave, e tudo o que é necessário para encriptar o seu conteúdo misterioso, fica quase impossível obter a decriptografia do mesmo.

Resumo: não dedique toda a segurança de uma operação criptográfica à um algoritmo. Existem muitos outros passos antes de tomar esse, o que nem computação quântica ou magia negra conseguirão quebrar, mas se tiverem acesso ao seu código fonte, talvez 95% do trabalho de quebrar a criptografia já esteja adiantado.

Carregando publicação patrocinada...
1

Passou um bom tempo antes de eu precisar recorrer à essa conversa aqui novamente. Pesquisando a respeito do assunto, realmente faz sentido suas dicas, cyp. Daí pensei, o que posso fazer com o que tenho disponível:

  • a "aplicação XOR" escrita em C;
  • o openssl disponível no sistema.

Preferi o openssl nesse experimento para testar suas possibilidades. Assim, usando um arquivo TXT como exemplo:

msg_hex="57652063616EE2809974207475726E206261636B2074686520636C6F636B206275742077652063616E20676574206168656164206F662069742E2E2E2073746F7265206E6F772C2064656372797074206C61746572"

ou

cat message.dat | hexdump -Cv
00000000  57 65 20 63 61 6e e2 80  99 74 20 74 75 72 6e 20  |We can...t turn |
00000010  62 61 63 6b 20 74 68 65  20 63 6c 6f 63 6b 20 62  |back the clock b|
00000020  75 74 20 77 65 20 63 61  6e 20 67 65 74 20 61 68  |ut we can get ah|
00000030  65 61 64 20 6f 66 20 69  74 2e 2e 2e 20 73 74 6f  |ead of it... sto|
00000040  72 65 20 6e 6f 77 2c 20  64 65 63 72 79 70 74 20  |re now, decrypt |
00000050  6c 61 74 65 72                                    |later|
00000055

e encriptando a mensagem com aes-256-ctr (ao invés de aes-256-cbc), passando os 16 bytes do vetor de inicialização (IV) e os 32 bytes da chave derivada,

iv_hex="4DFF8852F1A0521514EB57A47D6C60E0"
key_hex="31AE978DB130B660C8922073BD7BACAC1F4D0CA4159FA2803DD3320CA5FB212D"

obtém-se os bytes encriptados (em vez de bloco encriptado)

cat message.dat | openssl enc -aes-256-ctr -iv ${iv_hex} -K ${key_hex} | hexdump -Cv
00000000  83 75 5d ac 28 82 01 5e  6e e1 c8 b1 b4 2b b9 74  |.u].(..^n....+.t|
00000010  da a2 a2 d5 72 2d 31 87  f6 ec 4d be 7f 95 49 40  |....r-1...M...I@|
00000020  5f 8c a1 80 47 6a 09 f3  ce e9 96 db 68 63 48 1b  |_...Gj......hcH.|
00000030  dc 9c 47 10 02 62 3e f8  5d da 5e 8c 2b d1 77 28  |..G..b>.].^.+.w(|
00000040  48 15 e2 e8 f1 0b f8 63  13 da 0b 8c d3 f3 14 18  |H......c........|
00000050  f4 83 2f a1 c0                                    |../..|

Para decriptar a stream de bytes,

cat message.dat | openssl enc -aes-256-ctr    -iv ${iv_hex} -K ${key_hex} | \
                  openssl enc -aes-256-ctr -d -iv ${iv_hex} -K ${key_hex} | hexdump -Cv

obtém-se a mensagem original, como esperado

00000000  57 65 20 63 61 6e e2 80  99 74 20 74 75 72 6e 20  |We can...t turn |
00000010  62 61 63 6b 20 74 68 65  20 63 6c 6f 63 6b 20 62  |back the clock b|
00000020  75 74 20 77 65 20 63 61  6e 20 67 65 74 20 61 68  |ut we can get ah|
00000030  65 61 64 20 6f 66 20 69  74 2e 2e 2e 20 73 74 6f  |ead of it... sto|
00000040  72 65 20 6e 6f 77 2c 20  64 65 63 72 79 70 74 20  |re now, decrypt |
00000050  6c 61 74 65 72                                    |later|
00000055

Neste exemplo, o modo counter (-aes-256-ctr) parece ser mais seguro que o modo block (-aes-256-cbc), pois durante a decriptação neste último modo, considerando que a chave derivada está corretamente informada mas o IV incorreto e contendo todos os bytes nulos, boa parte da mensagem original vai surgir, pois somente o primeiro bloco estará comprometido:

1Password uses AES128, in CBC mode to encrypt data – and it’s the design of CBC that enables the next optimization. If you have the wrong IV when you decrypt in CBC mode it corrupts the first block – but only the first block, the rest will be fine.
https://adamcaudill.com/2013/04/16/1password-pbkdf2-and-implementation-flaws

Ao informar a chave derivada como parâmetro para o openssl, o conteúdo resultante não irá conter os bytes mágicos e o salt logo nos primeiros 16 bytes, pois são desnecessários quando a chave derivada é fornecida em vez do password em texto plano e, opcionalmente, o salt. Logo, tanto a chave como o IV devem ser armazenados em outro lugar seguro e os bytes resultantes, aparentemente sem qualquer dica sobre o que são (sem header).

iv_hex="4DFF8852F1A0521514EB57A47D6C60E0"
key_hex="31AE978DB130B660C8922073BD7BACAC1F4D0CA4159FA2803DD3320CA5FB212D"

No modo aes-256-ctr, o conteúdo resultante da encriptação conterá os bytes aleatórios com comprimento igual ao da mensagem (stream) original (abaixo em hex), ou seja, algo sem sentido algum se os elementos de segurança forem perdidos.

msg_enc_hex="74733B62D4EEE423EBBAA78F68520F3EDA426F7A2C7B515F0797BDE35EEA59D115A2B3927382774A45A51EFDCF4BE819276907F5EBA9EC0C8A1D9DF389800ABF0D5B5A648D9F305DC25757BD69251F75BE337B35ED"

Para decriptação, exige-se o conhecimento desses dois elementos de segurança, ou seja, o IV e a chave. Em caso da chave e/ou IV fornecidos estarem incorretos, bytes aleatórios serão obtidos na saída, sem qualquer advertência como ocorre no caso do modo block (-aes-256-cbc).

000000000000000:error:00000000:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:../crypto/evp/evp_enc.c:999:

A meu ver, essa advertência é um tipo de ajuda no caso de um ataque de força bruta para a chave. [In]felizmente o mesmo não acontece caso um IV incorreto seja fornecido na decriptação.

1

Muito boas suas observações, cyp. Relatam detalhes que sequer lembrei-me da relevância. Valeu por destacá-los bem, de modo claro e objetivo. É verdade, existe o algoritmo criptográfico e o algoritmo gerador de chaves. A segurança por obscuridade, ocultando detalhes do algoritmo além da chave, dificulta um pouco mais o processo de reversão.

Vi o texto que publicou em seu blog a respeito da cifragem XOR. No código que lá apresenta, realça quão simples e seguro pode ser caso tal cifragem seja corretamente implementada.

Consideramos o pseudo-código abaixo:

byte[] inputBytes = ... // 1024 bytes
byte[] derivedBytes = Rfc2898DeriveBytes.Pbkdf2(
    key,
    salt,
    10000,
    HashAlgorithmName.SHA256,
    inputBytes.Length);
byte[] encryptedBytes = XOR(inputBytes, derivedBytes);

No código, a função Rfc2898DeriveBytes.Pbkdf2 é um Pseudo Random Noise Generator. Em vez de guardar toda chave aleatória, armazena-se apenas os parâmetros passados para a Rfc2898DeriveBytes.Pbkdf2. Lembro-me disto com certa vantagem quando usando OTP. Precisei armazenar toda chave aleatória gerada a partir de ruído atmosférico que, praticamente, não pode ser reproduzida, logo, a necessidade de armazená-la em outro local seguro. O armazenamento é custoso, pois não há algoritmo de compactação eficiente para os conteúdos aleatórios gerados (chave e cifra).