Runtime: Primitivo geral de baixo nível para cifras (sendo o AES-GCM o primeiro)

Criado em 29 ago. 2017  ·  143Comentários  ·  Fonte: dotnet/runtime

Justificativa

Há uma necessidade geral de um número de cifras para criptografia. A mistura de interfaces e classes de hoje tornou-se um pouco desarticulada. Também não há suporte para cifras de estilo AEAD, pois elas precisam da capacidade de fornecer informações extras de autenticação. Os designs atuais também são propensos à alocação e são difíceis de evitar devido aos arrays que retornam.

API proposta

Uma classe base abstrata de propósito geral que será implementada por classes concretas. Isso permitirá a expansão e também por ter uma classe em vez de métodos estáticos, temos a capacidade de criar métodos de extensão, bem como manter o estado entre as chamadas. A API deve permitir a reciclagem da classe para permitir alocações mais baixas (não precisando de uma nova instância a cada vez e para capturar, digamos, chaves não gerenciadas). Devido à natureza muitas vezes não gerenciada dos recursos que são rastreados, a classe deve implementar IDisposable

public abstract class Cipher : IDisposable
{
    public virtual int TagSize { get; }
    public virtual int IVSize { get; }
    public virtual int BlockSize { get; }
    public virtual bool SupportsAssociatedData { get; }

    public abstract void Init(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
    public abstract void Init(ReadOnlySpan<byte> iv);
    public abstract int Update(ReadOnlySpan<byte> input, Span<byte> output);
    public abstract int Finish(ReadOnlySpan<byte> input, Span<byte> output);
    public abstract void AddAssociatedData(ReadOnlySpan<byte> associatedData);
    public abstract int GetTag(Span<byte> span);
    public abstract void SetTag(ReadOnlySpan<byte> tagSpan);
}

Exemplo de uso

(a fonte de entrada/saída é um fluxo mítico baseado em span como fonte de E/S)

using (var cipher = new AesGcmCipher(bitsize: 256))
{
    cipher.Init(myKey, nonce);
    while (!inputSource.EOF)
    {
        var inputSpan = inputSource.ReadSpan(cipher.BlockSize);
        cipher.Update(inputSpan);
        outputSource.Write(inputSpan);
    }
    cipher.AddAssociatedData(extraInformation);
    cipher.Finish(finalBlockData);
    cipher.GetTag(tagData);
}

Comportamento da API

  1. Se a tag get for chamada antes de terminar, um [tipo de exceção?] deve ser lançado e o estado interno deve ser definido como inválido
  2. Se a tag for inválida no final da descriptografia, deve ser uma exceção lançada
  3. Uma vez que terminou é chamado, uma chamada para qualquer coisa diferente de um dos métodos Init lançará
  4. Uma vez que o Init é chamado, uma segunda chamada sem "terminar" lançará
  5. Se o tipo espera uma chave fornecida (uma instância "new'd" direta) se a chamada "Init" inicial tiver apenas um IV, ela lançará
  6. Se o tipo foi gerado, digamos, de uma chave baseada em armazenamento e você tenta alterar a chave via Init e não apenas o IV, ele lançará
  7. Se a tag get não for chamada antes do descarte ou do Init, uma exceção deve ser lançada? Para impedir que o usuário não colete a tag por acidente?

Referência dotnet/corefx#7023

Atualizações

  1. Alterado nonce para IV.
  2. Seção de comportamento adicionada
  3. Removidos os casos de intervalo de entrada/saída únicos de acabamento e atualização, eles podem ser apenas métodos de extensão
  4. Alterado vários períodos para readonlyspan, conforme sugerido por @bartonjs
  5. Removido Reset, Init com IV deve ser usado em seu lugar
api-suggestion area-System.Security

Comentários muito úteis

@bartonjs você está literalmente ignorando todas as análises técnicas e lógicas e, em vez disso, usando as datas dos projetos Go e libsodium como um proxy fraco para a análise real?

Não, estou usando o conselho de criptógrafos profissionais que dizem que é extraordinariamente perigoso e que devemos evitar o streaming AEAD. Então, estou usando informações da equipe do CNG de "muitas pessoas dizem que querem na teoria, mas na prática quase ninguém faz isso" (não sei quanto disso é telemetria versus anedótico de solicitações de assistência em campo). O fato de que outras bibliotecas seguiram o caminho único simplesmente _reforça_ a decisão.

Por que a demanda demonstrada até agora no GitHub é insuficiente?

Alguns cenários foram mencionados. O processamento de buffers fragmentados provavelmente poderia ser resolvido com a aceitação de ReadOnlySequence , se parecer que há um cenário suficiente para justificar a complicação da API em vez de fazer com que o chamador faça a remontagem dos dados.

Arquivos grandes são um problema, mas arquivos grandes já são um problema, pois o GCM tem um limite de apenas 64 GB, o que "não é tão grande" (ok, é muito grande, mas não é o "uau, isso é grande" que costumava ser). Os arquivos mapeados na memória permitiriam que Spans (de até 2^31-1) fossem utilizados sem exigir 2 GB de RAM. Então, reduzimos alguns pedaços do máximo... isso provavelmente aconteceria com o tempo de qualquer maneira.

Você percebe que decidir sobre uma interface sem streaming para o AEAD impede todas essas implementações no futuro, certo?

Estou cada vez mais convencido de que @GrabYourPitchforks estava certo (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891) de que provavelmente não há uma interface unificadora sensata. GCM _exigindo_ um nonce/IV e SIV _proibindo_ significa que a inicialização de um modo/algoritmo AEAD já requer conhecimento sobre o que vai acontecer... não há realmente uma noção "abstrata" para AEAD. SIV dita onde "a etiqueta" vai. GCM/CCM não. SIV é tag-first, por especificação.

O SIV não pode começar a criptografar até que tenha todos os dados. Portanto, sua criptografia de streaming será lançada (o que significa que você precisa saber para não chamá-lo) ou buffer (o que pode resultar em n ^ 2 tempo de operação). O CCM não pode iniciar até que o comprimento seja conhecido; mas o CNG não permite uma dica de pré-criptografia no comprimento, então está no mesmo barco.

Não devemos projetar um novo componente onde seja mais fácil fazer a coisa errada do que a certa por padrão. A descriptografia de streaming torna muito fácil e tentador conectar uma classe Stream (como sua proposta para fazer isso com CryptoStream), o que torna muito fácil obter um bug de validação de dados antes que a tag seja verificada, o que anula quase inteiramente o benefício do AE . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "espere, isso não é XML legal..." => oráculo de texto cifrado adaptável) .

Está chegando ao ponto... a demanda do cliente.

Como, infelizmente, já ouvi muitas vezes na vida: desculpe, mas você não é o cliente que temos em mente. Admito que talvez você saiba como fazer o GCM com segurança. Você sabe apenas transmitir para um arquivo/buffer/etc volátil até depois da verificação da tag. Você sabe o que significa gestão nonce e conhece os riscos de errar. Você sabe prestar atenção aos tamanhos de fluxo e cortar para um novo segmento GCM após 2^36-64 bytes. Você sabe que depois de tudo dito e feito, o problema é seu se você errar essas coisas.

O cliente que tenho em mente, por outro lado, é alguém que sabe "tenho que criptografar isso" porque seu chefe mandou. E eles sabem que ao pesquisar como fazer criptografia, algum tutorial disse "sempre use AE" e menciona GCM. Então eles encontram um tutorial de "criptografia em .NET" que usa CryptoStream. Eles então conectam o pipeline, sem ter a menor ideia de que fizeram a mesma coisa que escolher SSLv2... marcaram uma caixa na teoria, mas não na prática. E quando eles fazem isso _esse_ bug pertence a todos que sabiam melhor, mas deixe a coisa errada ser muito fácil de fazer.

Todos 143 comentários

Feedback rápido para avaliar a API proposta (tentando ser útil):

  • Span<T> não está no NetStandard2 .
  • "Nonce" é muito específico de implementação - ou seja. cheiro de GCM. No entanto, mesmo os documentos do GCM (ex. NIST SP800-38D) se referem a ele como "IV", que - no caso do GCM - é um nonce. No entanto, no caso de outras implementações de AEAD, o IV não precisa ser um nonce (ex. repetir o IV sob CBC+HMAC não é catastrófico).
  • O streaming AEAD deve funcionar perfeitamente com o CryptoStream ou fornecer seu próprio "AEADCryptoStream", que é tão fácil de transmitir como o CryptoStream.
  • As implementações da API AEAD devem ter permissão para fazer derivação de chave interna com base em AAD (dados associados). Usar o AAD apenas para cálculo/verificação de tags é muito restritivo e impede um modelo AEAD mais forte.
  • Os métodos "Get*" devem retornar algo (GetTag). Se eles forem nulos, eles devem estar definindo algo/alterando o estado.
  • Tentar obter uma tag antes de "terminar" é provavelmente uma má ideia, então "IsFinished" pode ser útil.
  • As pessoas que projetaram o ICryptoTransform pensaram em reutilização, suporte a vários blocos e tamanhos de bloco de entrada/saída de tamanhos diferentes. Essas preocupações não são capturadas.

Como prova da sanidade da API AEAD, a primeira implementação dessa API proposta não deve ser AES-GCM, mas o AES-CBC clássico/padrão com uma tag HMAC. A razão simples para isso é que qualquer um pode construir a implementação AES-CBC+HMAC AEAD hoje , com classes .NET simples, existentes e bem conhecidas. Vamos fazer com que um velho e chato [AES-CBC+HMAC] trabalhe primeiro nas novas APIs AEAD, já que é fácil para todos MVP e test-drive.

O problema de nomeação nonce/IV era algo sobre o qual eu estava indeciso, feliz com uma mudança para IV, então mudará.

Quanto aos métodos Get retornando algo, isso evita qualquer alocação. Pode haver uma sobrecarga Get() que retorna algo. Talvez isso exija uma mudança de nomenclatura, mas estou bem casado com a ideia de que toda a API precisa ser basicamente livre de alocação.

Quanto aos fluxos, etc, não estou muito incomodado com isso, pois são APIs de nível superior que podem ser facilmente construídas a partir das primitivas de nível inferior.

A obtenção de uma Tag antes de terminar não deve ser permitida, mas você deve saber quando chamou terminar, então não tenho certeza se deve estar na API, no entanto, deve ser um comportamento definido, então atualizei o design da API para incluir uma seção de comportamento para que possamos capturar quaisquer outras que sejam consideradas.

Quanto a qual cifra, não acho que nenhuma cifra específica deva ser o único alvo, para provar uma nova API de propósito geral, ela precisa se encaixar em um número. AES GCM e CBC devem ser cobertos.

(todo o feedback do tópico bom ou ruim é sempre útil!)

  • Classe ou interface?
  • Como, se é que as classes SymmetricAlgorithm atuais interagem com isso?
  • Como isso seria usado para chaves persistentes, como TripleDESCng e AesCng podem fazer?
  • Muitos desses Spans parecem ser ReadOnlySpan.

@Drawaes obrigado por dar o pontapé inicial nesta API. Alguns pensamentos:

  1. A geração e verificação de tags é uma parte muito importante dessa API, pois o uso indevido de tags pode anular todo o propósito. Se possível, gostaria de ver as tags embutidas nas operações de inicialização e término para garantir que elas não possam ser ignoradas acidentalmente. Isso provavelmente implica que criptografar e descriptografar não devem usar os mesmos métodos de inicialização e finalização.
  2. Eu tenho sentimentos mistos sobre a saída de blocos durante a descriptografia antes de chegar ao final, pois os dados não são confiáveis ​​até que a tag seja verificada (o que não pode ser feito até que todos os dados tenham sido processados). Precisaremos avaliar essa compensação com muito cuidado.
  3. O reset é necessário? Deve terminar apenas redefinir? Fazemos isso em hashes incrementais (mas eles não precisam de novos IVs)

@bartonjs

  1. Class, como muitas vezes foi visto na BCL com uma interface que você não pode expandi-la depois sem quebrar tudo. Uma interface é como um filhote de cachorro para a vida... a menos que métodos de interface padrão possam ser considerados como uma solução para esse problema.
    Além disso, uma classe selada de um tipo abstrato é realmente mais rápida (a partir de agora) porque o jitt pode desvirtualizar os métodos agora ... Então é basicamente gratuito. despacho de interface não é tão bom (ainda bom, mas não tão bom)
  2. Eu não sei como você gostaria que isso funcionasse? Eu tenho pouco interesse nas coisas atuais, pois é tão confuso que eu apenas corrigiria todos os algoritmos modernos razoáveis ​​​​direto (deixe 3DES nas outras classes :) Mas eu não tenho todas as respostas, então você tem mais alguma opinião sobre isso?
  3. Chaves persistentes devem ser fáceis. Crie um método de extensão no método de chave ou armazenamento de persistência.
MyKeyStore.GetCipher();

Não é inicializado. É descartável, portanto, qualquer referência pode ser descartada pelo padrão descartável normal. Se eles tentarem definir a chave, lançará uma exceção de operação inválida.

Sim para os intervalos somente leitura, ajustarei quando não estiver no tubo do meu telefone.

@morganbr sem problemas... eu só quero ver isso acontecer mais do que tudo ;)

  1. Você pode dar um trecho de código sobre como você vê isso funcionando? Não tenho certeza, mas o código sempre traz clareza
  2. É lamentável, mas você realmente tem que cuspir os blocos mais cedo. Com hmac e hash você não tem, mas você não tem dados provisórios apenas o estado. Portanto, neste caso, você teria que armazenar em buffer uma quantidade desconhecida de dados. Vamos dar uma olhada no exemplo de pipelines e TLS. Podemos escrever 16k de texto simples, mas os buffers de pipeline são hoje do tamanho de uma página de 4k. Então, na melhor das hipóteses, queremos criptografar/descriptografar 4*4k. De você não me dar a resposta até o final você precisa alocar memória interna para armazenar tudo isso e então eu presumo jogar fora quando eu conseguir o resultado? Ou você vai limpá-lo. E se eu descriptografar 10mb e você reter essa memória depois que eu tiver que me preocupar com o uso da memória latente.
  3. Não 100% na coisa de init/reset (não suas ideias, minha forma atual de API) não se encaixa bem comigo, então estou aberto a uma nova sugestão!

Eu tenho pouco interesse nas coisas atuais, pois é tão confuso que eu apenas corrigiria todos os algoritmos modernos razoáveis ​​​​direto (deixe 3DES nas outras classes :)

O problema seria que formatos de contêiner como EnvelopedCms (ou EncryptedXml) podem precisar trabalhar com 3DES-CBC, AES-CBC, etc. Alguém querendo descriptografar algo criptografado com ECIES/AES-256-CBC-PKCS7/HMAC-SHA-2 -256 provavelmente não sentiriam que estão fazendo coisas velhas e grosseiras.

Se deve ser apenas para AE, isso deve ser refletido em algum lugar no nome. Agora é "cifra" genérica (que eu estava/estou, em algum momento, vou sentar com um dicionário/glossário e descobrir se existe uma palavra para "um algoritmo de criptografia em um modo de operação", já que acho que "cifra" == "algoritmo", portanto "Aes").

:) Eu estava apenas apontando que não é minha área de assunto ou de muito interesse para mim, então estou disposto a adiar a você e à comunidade sobre este tópico que não pensei nas implicações disso.


Depois de escanear rapidamente por eles, uma opção é permitir que eles usem uma instância do "Cipher" ou o que quer que seja chamado de classe. Isso pode não ser feito na primeira onda, mas pode ser feito rapidamente. Se a API for super eficiente, não vejo motivo para que eles façam suas próprias coisas internamente e é exatamente o caso de uso dessa API.

Como uma barra lateral na nomeação ... devo admitir que é difícil, no entanto
Openssl = cifra
Ruby = cifra
Go = pacote cifrado com interfaces ducktyped para AEAD etc
Java = cifra

Agora sou a favor de ser diferente, mas... há uma tendência. Se algo melhor é possível que é legal.

Possivelmente "BlockModeCipher" ... ?

Fiz algumas alterações, mudarei o nome se um nome melhor for decidido.

Quando comecei a tentar responder a perguntas, percebi que a API já não tem diferenciação de criptografia/descriptografia, portanto, no seu exemplo, ela não sabe se deve criptografar ou descriptografar os dados. Colocar isso pode adicionar alguma clareza.

Eu poderia imaginar algumas maneiras pelas quais a API poderia impor o uso adequado de tags (com base na suposição de que esta é uma API AEAD, não apenas criptografia simétrica, pois já temos SymmetricAlgorithm/ICryptoTransform/CryptoStream). Não tome isso como prescritivo, apenas como um exemplo de aplicação da marcação.
Por método:

class Cipher
{
   void InitializeEncryption(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
   // Ensures decryptors get a tag
   void InitializeDecryption(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> tag);
   // Ensure encryptors produce a tag
    void FinishEncryption(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> tag);
   // Throws if tag didn't verify
   void FinishDecryption(ReadOnlySpan<byte> input, Span<byte> output);
   // Update and properties are unchanged, but GetTag and SetTag are gone
}

Por classe:

class Cipher
{
    // Has properties and update, but Initialize and Finish aren't present
}
class Encryptor : Cipher
{
    void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
    void Finish(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> tag);
}
class Decryptor : Cipher
{
   void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> tag);
   // Throws if tag didn't verify
   void Finish(ReadOnlySpan<byte> input, Span<byte> output);
}
class AesGCMEncryptor : Encryptor {}
class AesGCMDecryptor : Decryptor {}
}

Dito isto, se ele não armazenar em buffer na descriptografia, é prático garantir que a descriptografia seja realmente concluída e que a tag seja verificada? O Update pode de alguma forma descobrir que é hora de verificar? Isso é algo que o Dispose deveria fazer? (Descartar é um pouco perigoso, pois você já pode ter confiado nos dados no momento em que descarta o objeto)

Quanto à nomenclatura, nosso precedente é SymmetricAlgorithm e AsymmetricAlgorithm. Se isso for destinado ao AEAD, algumas ideias podem ser AuthenticatedSymmetricAlgorithm ou AuthenticatedEncryptionAlgorithm.

Alguns pensamentos e ideias de API:

public interface IAEADConfig
{
    // size of the input block (plaintext)
    int BlockSize { get; }

    // size of the output per input-block;
    // typically a multiple of BlockSize or equal to BlockSize.
    int FeedbackSize { get; }

    // IV size; CAESAR completition uses a fixed-length IV
    int IVSize { get; }

    // CAESAR competition uses a fixed-length key
    int KeySize { get; }

    // CAESAR competition states that typical AEAD ciphers have a constant gap between plaintext length
    // and ciphertext length, but the requirement is to have a constant *limit* on the gap.
    int MaxTagSize { get; }

    // allows for AE-only algorithms
    bool IsAdditionalDataSupported { get; }
}

public interface ICryptoAEADTransform : ICryptoTransform
{
    // new AEAD-specific ICryptoTransform interface will allow CryptoStream implementation
    // to distinguish AEAD transforms.
    // AEAD decryptor transforms should throw on auth failure, but current CryptoStream
    // logic swallows exceptions.
    // Alternatively, we can create a new AEAD_Auth_Failed exception class, and
    // CryptoTransform is modified to catch that specific exception.
}

public interface IAEADAlgorithm : IDisposable, IAEADConfig
{
    // separates object creation from initialization/keying; allows for unkeyed factories
    void Initialize(ArraySegment<byte> key);

    void Encrypt(
        ArraySegment<byte> iv, // readonly; covered by authentication
        ArraySegment<byte> plaintext, // readonly; covered by authentication
        ref ArraySegment<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
        ArraySegment<byte> additionalData = default(ArraySegment<byte>) // readonly; optional; covered by authentication
        ); // no failures expected under normal operation - abnormal failures will throw

    bool Decrypt(
        ArraySegment<byte> iv, // readonly
        ArraySegment<byte> ciphertext, // readonly
        ref ArraySegment<byte> plaintext, // must be of at least [ciphertext_length - MaxTagSize] length.
        ArraySegment<byte> additionalData = default(ArraySegment<byte>), // readonly; optional
        bool isAuthenticateOnly = false // supports Authentication-only mode
        );// auth failures expected under normal operation - return false on auth failure; throw on abnormal failure; true on success

    /*  Notes:
        * Array.LongLength should be used instead of Array.Length to accomodate byte arrays longer than 2^32.
        * Ciphertext/Plaintext produced by Encrypt()/Decrypt() must be determined *only* by method inputs (combined with a key).
          - (ie. if randomness or other hidden inputs are needed, they must be a part of iv)
        * Encrypt()/Decrypt() are allowed to write to ciphertext/plaintext segments under all conditions (failure/success/abnormal)
          - some implementations might be more stringent than others, and ex. not leak decrypted plaintext on auth failures
    */

    ICryptoAEADTransform CreateEncryptor(
        ArraySegment<byte> key,
        ArraySegment<byte> iv,
        ArraySegment<byte> additionalData = default(ArraySegment<byte>)
        );

    ICryptoAEADTransform CreateDecryptor(
        ArraySegment<byte> key,
        ArraySegment<byte> iv,
        ArraySegment<byte> additionalData = default(ArraySegment<byte>)
        );

    // Streaming AEAD can be done with good-old CryptoStream
    // (possibly modified to be AEAD-aware).
}

@sdrapkin , obrigado por contribuir com pensamentos. Onde as tags devem ir em sua API? Eles são implicitamente parte do texto cifrado? Se sim, isso implica em um protocolo que alguns algoritmos não possuem. Eu gostaria de entender quais protocolos as pessoas podem se importar para ver se o posicionamento implícito de tags é aceitável ou se precisa ser realizado separadamente.

@morganbr Também notei o problema de criptografar/descriptografar, mas não tive tempo hoje à noite para corrigir tão feliz pelo seu design. Eu prefiro os métodos sobre a classe, pois permite uma reciclagem mais agressiva (buffers para chaves e IVs podem ser adicionados).

Quanto a um cheque antes do descarte. Não é possível dizer o fim de uma operação, infelizmente.

As interfaces @sdrapkin , eu diria que são inválidas devido ao problema de versão mencionado anteriormente. A menos que contemos com a implantação padrão no futuro. Além disso, o envio de interface é mais lento. Os segmentos de matriz também são nossos, pois span é a primitiva inferior mais versátil. No entanto, métodos de extensão podem ser adicionados para o segmento de matriz ser estendido se houver demanda posteriormente.

Algumas de suas propriedades são de interesse, portanto, serão atualizadas quando eu estiver no computador e não no telefone.

Bom feedback todo!

@morganbr Tags fazem parte do texto cifrado. Isso é modelado após a API CAESAR (que inclui AES-GCM).

@Drawaes Eu usei interfaces apenas para ilustrar pensamentos - estou perfeitamente bem com métodos/classes estáticos. Períodonão existe. Eu não me importo com o que pode ou não estar chegando - não está no NetStandard2, e não está no .NET normal que projetos sérios realmente usam (sim, sim, eu sei que está no Core, mas o Core é um brinquedo por agora). Eu ficaria feliz em considerar pessoalmente o Spanquando eu vejo - até então ArraySegmenté a API NetStandard mais próxima que realmente é fornecida.

Vou dar uma olhada mais profunda na API CAESAR que é útil.

Quanto ao Span, ele está sendo enviado em torno do período de tempo 2.1, acredito que seja o mesmo tempo em que a primeira implementação dessa API seria enviada (ou pelo menos o mais cedo possível).

Se você der uma olhada no pacote nuget de pré-lançamento atual, ele suporta até o padrão .net 1.0 e não há planos de mudar isso no lançamento.

Talvez @stephentoub possa confirmar isso, pois ele está trabalhando para adicionar APIs baseadas em Span em toda a estrutura enquanto falamos.

(Nuget for Span)[https://www.nuget.org/packages/System.Memory/4.4.0-preview2-25405-01]

Então eu diria que é a única escolha real para uma nova API. Em seguida, métodos de extensão etc podem ser adicionados para obter um ArraySegment, se você assim o escolher e, se for útil o suficiente, poderá ser adicionado à estrutura, mas é trivial transformar um ArraySegment em um Span, mas a outra maneira requer copiar dados.

O problema que vejo com essa API acima é que será um desastre para o desempenho em qualquer dado "em partes". Digamos, por exemplo, o tráfego de rede, se um único bloco autenticado for dividido em várias leituras de um fluxo existente, preciso armazenar tudo em um único [inserir estrutura de dados] e criptografar/descriptografar tudo de uma vez. O derrota todas as tentativas de fazer qualquer tipo de cópia zero nesses dados.

Estruturas de rede, como as que o Pipelines fornece, conseguem evitar quase todas as cópias, mas, se atingirem qualquer tipo de criptografia nesta API, tudo isso desaparece.

O objeto de configuração separado (ou bolsa) na verdade esteve envolvido em uma discussão recente sobre outra API que tenho tido. Não me oponho a isso em princípio, pois se crescer no futuro, pode se tornar uma bagunça ter um grande número de propriedades no objeto principal.

Algumas coisas me ocorreram.

  • A proposta atual chama TagSize de um valor de saída (bem, uma propriedade somente de obtenção). Mas para GCM e CCM é uma entrada para criptografia (e derivável da descriptografia, pois você forneceu a tag real).
  • A proposta assume que entrada e saída podem acontecer ao mesmo tempo e aos poucos.

    • O IIRC CCM não pode fazer criptografia de streaming (o comprimento do texto simples é uma entrada na primeira etapa do algoritmo).

    • Os modos acolchoados atrasam a descriptografia em (pelo menos um) bloco, pois até que o Final seja chamado, eles não sabem se mais dados estão chegando / se o bloco atual precisa que o preenchimento seja removido

  • Alguns algoritmos podem considerar que o elemento AD é necessário no início da operação, tornando-o mais como um parâmetro Init/ctor do que uma associação de ligação tardia.

Não sei se os formatos de contêiner (EnvelopedCms, EncryptedXml) precisariam extrair a chave, ou se caberia simplesmente a eles gerá-la e lembrá-la (pelo tempo que precisarem anotá-la).

(Aparentemente eu não apertei o botão "comentário" sobre isso ontem, então não reconhecerá nada depois de "eu fiz algumas alterações" em 1910Z)

O tamanho real da tag precisaria ser variável. Acordado.

Se olharmos apenas para a criptografia por enquanto, para simplificar o caso de uso. Você está certo de que algumas cifras não retornarão nada, menos ou mais. Há uma pergunta geral sobre o que acontece se você não fornecer um buffer grande o suficiente.

Nas novas interfaces TextEncoding usando span, havia uma sugestão de ter o tipo de retorno um enum para definir se havia espaço suficiente para a saída ou não, e o tamanho realmente escrito em um parâmetro "out". Esta é uma possibilidade.

No caso do CCM, eu diria apenas que ele não retorna nada e terá que armazenar em buffer internamente até que você chame terminar, ponto em que ele deseja despejar todo o lote. Nada impede que você apenas chame finish como sua primeira chamada se você tiver todos os dados em um único bloco (nesse caso, pode haver um nome melhor). Ou é possível lançar se você tentar uma atualização dessas cifras. O CNG retorna um erro de tamanho inválido se você tentar fazer uma continuação no CCM, por exemplo.

Quanto a quando a tag é definida na descriptografia, você geralmente não sabe até ler o pacote inteiro, se tomarmos o TLS como exemplo, podemos ter que ler 8 * 2k pacotes de rede para chegar à tag no final de um bloco de 16k. Portanto, agora temos que armazenar em buffer os 16k inteiros antes de iniciar a descriptografia e não há chance de sobreposição (não estou dizendo que isso seria usado para TLS apenas que um processo vinculado a IO é comum para esses tipos de cifras, seja disco ou rede).

@Drawaes re. fluxos em partes e limites de buffer:
Você tem que escolher suas batalhas. Você não será capaz de criar uma API unificadora alinhada com todos os objetivos únicos no mundo AE - e existem muitos desses objetivos. Ex. há um streaming AEAD em pedaços decente no Inferno , mas não é um padrão de forma alguma, e esse padrão não existe. Em um nível mais alto, o objetivo é "canais seguros" (veja this , this e this ).

No entanto, precisamos pensar menor por enquanto. Chunking/buffer-limiting não está nem no radar para esforços de padronização (seção " AEADs with large plaintexts ").

As operações de criptografia/descriptografia são fundamentalmente sobre transformações. Essas transformações requerem buffers e não estão no local (os buffers de saída devem ser maiores que os buffers de entrada - pelo menos para a transformação Encrypt).

A RFC 5116 também pode ser de interesse.

@Drawaes , interessante você trazer o TLS. Eu diria que o SSLStream (se usar essa API) não deve retornar nenhum resultado não autenticado para um aplicativo, pois o aplicativo não terá como se defender.

Claro, mas isso é problema do SSLStreams. Eu prototipei exatamente isso (gerenciei TLS no nível do protocolo chamando CNG e OpenSSL para os bits de criptografia) em cima de pipelines. A lógica era bem simples, os dados criptografados entram, descriptografam o buffer no lugar, anexam à saída e repetem até chegar à tag. Na tag chamada finalizada...

Se ele lança fechar o pipeline. Se ele não for lançado, libere, permitindo que o próximo estágio funcione no mesmo thread ou via despacho.

Minha prova de conceito não estava pronta para o horário nobre, mas usando isso e evitando muitas cópias etc, mostrou um aumento de desempenho muito decente ;)

O problema com qualquer rede é onde as coisas no pipeline começam a alocar seus próprios buffers e não usar o máximo possível os que já estão se movendo pelo sistema.

A cripta do OpenSsl e o CNG têm o mesmo método Update, Update, Finish. Finish pode gerar a tag conforme discutido. As atualizações devem estar em tamanho de bloco (para CNG) e para OpenSsl ele faz um buffer mínimo para obter um tamanho de bloco.

Como eles são primitivos, não tenho certeza se esperaríamos uma funcionalidade de nível superior deles. Se estivéssemos projetando uma API de nível "usuário" em vez de primitivas para construí-las, eu argumentaria que a geração de chaves, construção de IV e partes autenticadas inteiras deveriam ser implementadas, então acho que depende de qual é o nível de destino dessa API.

Botão errado

@blowdart , que teve algumas ideias interessantes sobre gerenciamento de nonce.

Portanto, o gerenciamento de nonce é basicamente um problema do usuário e é específico para sua configuração.

Então... faça disso um requisito. Você deve conectar o gerenciamento nonce... e não fornecer uma implementação padrão ou nenhuma implementação. Isso, ao invés de um simples

cipher.Init(myKey, nonce);

força os usuários a fazer um gesto específico para que entendam os riscos.

A ideia do @blowdart pode ajudar com problemas de gerenciamento de nonce e diferenças entre algoritmos. Concordo que provavelmente é importante não ter uma implementação integrada para garantir que os usuários entendam que o gerenciamento de nonce é um problema que eles precisam resolver. Como é que algo assim se parece?

interface INonceProvider
{
    public void GetNextNonce(Span<byte> writeNonceHere);
}

class AesGcmCipher : Cipher
{
    public AesGcmCipher(ReadOnlySpan<byte> key, INonceProvider nonceProvider);
}

// Enables platform-specific hardware keys
class AesGcmCng : Cipher
{
    public AesGcmCng(CngKey key, INonceProvider nonceProvider);
}

// Example of AEAD that might not need a nonce
class AesCBCHmac : Cipher
{
    public AesCBC(ReadOnlySpan<byte> key)
}

class Cipher
{
    // As above, but doesn't take keys, IVs, or nonces
}

Mas qual é o objetivo do INonceProvider? É apenas uma interface/tipo extra, se o Init receber um none e precisar ser chamado antes de iniciar qualquer bloco, não é a mesma coisa sem uma interface/extra?

Também não sou especialista em criptografia, mas o AES não exige um IV (que não é um nonce, mas precisa ser fornecido pelo usuário?)

É apenas uma interface/tipo extra

Esse é o ponto. Essencialmente, diz que _nonce management_ é um problema, não apenas passando em uma matriz de bytes zerada ou mesmo aleatória. Também pode ajudar a evitar a reutilização inadvertida se as pessoas interpretarem GetNextNonce "devolva algo diferente do que você fez da última vez".

Também é útil não precisar dele para algoritmos que não têm problemas de gerenciamento de nonce (como AES SIV ou talvez AES + CBC + HMAC).

Os requisitos exatos de IV/nonce variam de acordo com o modo. Por exemplo:

  • AES BCE não exige um nonce ou IV
  • O AES GCM requer um nonce de 96 bits que nunca deve ser reutilizado ou a segurança da chave quebra. Baixa entropia é bom, desde que o nonce não seja reutilizado.
  • O AES CBC requer um IV de 128 bits que deve ser aleatório. Se o IV se repetir, apenas revela se a mesma mensagem já foi enviada antes.
  • O AES SIV não precisa de um IV explícito, pois o deriva de outras entradas.

AES CBC precisa de IV certo? Então você vai ter um InitializationVectorProvider? Não é um nonce, mas
não gostar e reutilizar o último bloco levou a um ataque tls porque o iv pode ser previsto. Você explicitamente não pode usar digamos um nonce sequencial para CBC.

Sim, mas um IV não é um nonce, então você não pode ter o termo provedor nomce

Eu não quis dizer que o AES CBC não precisa de um IV - ele precisa. Eu só quis especular sobre alguns esquemas que derivam o IV de outros dados.

Claro, acho que meu ponto é que geralmente gosto ... posso agrupar o provedor;) mas chame-o de provedor iv ou tenha 2 interfaces para esclarecer a intenção

@morganbr INonceProvider fábricas passadas para construtores de cifras são um design ruim. Ele ignora completamente o fato de que _nonce_ não existe por si só: a restrição "_...used once_" tem um _context_. Nos casos dos modos CTR e GCM (que usa CTR), o _context_ do _nonce_ é a _key_. Ou seja. _nonce provider_ deve retornar um nonce que é usado apenas uma vez dentro de um contexto de uma _key_ específica.

Como o INonceProvider em sua API proposta não reconhece a chave, ele não pode gerar nonces corretos (exceto por aleatoriedade, que não é o que um nonce é, mesmo que o espaço de bits seja grande o suficiente para que a aleatoriedade estatística funcione com segurança).

Não tenho certeza do que esse tópico de discussão pretende alcançar. Várias idéias de design de criptografia autenticada são discutidas... ok. E as interfaces de Criptografia Autenticada já incorporadas ao .NET Core -- especificamente em sua API ASP.NET? Existe IAuthenticatedEncryptor , etc. Todos esses recursos já estão implementados, extensíveis e enviados como parte do .NET Core hoje. Não estou dizendo que a criptografia DataProtection é perfeita, mas o plano é ignorá-la? Mude-os? Assimilar ou refatorar?

A criptografia DataProtection foi criada por @GrabYourPitchforks (Levi Broderick). Ele conhece o assunto, e sua opinião/input/feedback seria muito valioso para esta comunidade. Eu gosto de entretenimento com tema de criptografia tanto quanto qualquer um, mas se alguém quiser levar a sério o design da API de criptografia, os especialistas reais que já estão na equipe da MS devem ser contratados.

@sdrapkin , os provedores nonce que precisam estar cientes da chave é um bom ponto. Gostaria de saber se há uma maneira razoável de modificar essas APIs para impor isso.

DataProtection é uma boa API, mas é uma construção de nível superior. Ele encapsula geração e gerenciamento de chaves, IVs e protocolo de saída. Se alguém precisar usar (digamos) o GCM em uma implementação de um protocolo diferente, o DataProtection não torna isso possível.

A equipe de criptografia .NET inclui @bartonjs , @blowdart e eu. Claro, se @GrabYourPitchforks quiser entrar na conversa, ele é mais que bem-vindo.

Concordo com @morganbr em que isso deveria ser um primitivo de baixo nível (na verdade, diz isso no título). Embora a proteção de dados etc. seja projetada para ser usada diretamente no código do usuário e reduza a capacidade de dar um tiro no pé, a maneira como vejo essa primitiva é permitir que a estrutura e as bibliotecas construam construções de nível superior em uma base comum.

Com esse pensamento em mente, o provedor está bem se tiver que ser fornecido sempre que uma chave for fornecida. Isso torna um pouco confuso, deixe-me explicar o uso de TLS (é um uso bem conhecido dos modos de bloco AES para tráfego de rede).

Eu recebo um "frame" (talvez mais de 2 + TU's com o MTU ~1500 da internet). Ele contém o nonce (ou parte do nonce com 4 bytes deixados "escondidos"), então tenho que definir esse valor em um "provedor" de shell e, em seguida, chamar decrypt e passar pelo meu ciclo de descriptografar os buffers para obter um único texto simples .

Se você está feliz com isso, eu posso viver com isso. Estou ansioso para fazer isso avançar, tão ansioso para atualizar o design acima para algo com o qual possamos concordar.

Obrigado por bifurcar a discussão, tendo algum tempo livre para pular para isso. @Drawaes , você pode confirmar/atualizar a postagem principal como padrão ouro/alvo desta conversa em evolução? Se não, você pode atualizá-lo?

Vejo que a proposta atual tem um problema fatal e depois outros problemas por ser muito tagarela.

// current proposed usage
using (var cipher = new AesGcmCipher(bitsize: 256))
{
    cipher.Init(myKey, nonce);
    while (!inputSource.EOF)
    {
        var inputSpan = inputSource.ReadSpan(cipher.BlockSize);
        cipher.Update(inputSpan);
        outputSource.Write(inputSpan);
    }
    cipher.AddAssociatedData(extraInformation); // <= fatal, one can't just do this
    cipher.Finish(finalBlockData);
    cipher.GetTag(tagData);
}

Se você olhar para uma primitiva AEAD verdadeira, os dados de privacidade e os dados autenticados são misturados em etapas de bloqueio. Veja isto para Auth Data 1 e CipherText1. Isso, é claro, continua por vários blocos, não apenas 1.highlighted

Já que o mundo todo é um meme, não resisto, desculpe :)
Can't resist

Além disso, a API parece tagarela com new, init, update etc. Eu proporia o modelo deste programador

// proposed, see detailed comments below
using (var cipher = new AesGcmCipher(myKey, iv, aad)) // 1
{
    // 2
    while (!inputSource.EOF) 
    {
        var inputSpan = inputSource.ReadSpan(16411); // 3
        var outSpan = cipher.Encrypt(inputSpan); // 4
        outputSource.Write(outSpan); 
    }    
    var tag = cipher.Finish(finalBlockData); // 5
}
  1. Normalmente AAD << texto simples, então eu vi cipher.Init(mykey, nonce, aad); onde todo o AAD é passado como um buffer e, em seguida, a cifra processa o restante do fluxo potencialmente gigabyte +. (por exemplo, o parâmetro CipherModeInfo do BCryptEncrypts ). Além disso, o tamanho da myKey já estabelece AES128, 192, 256, sem necessidade de outro parâmetro.
  2. Init se torna uma API opcional caso o chamador deseje reutilizar a classe existente, as constantes AES existentes e pular a geração de subchave AES se a chave AES for a mesma
  3. a API do cipher deve proteger o chamador dos componentes internos do gerenciamento de tamanho de bloco, como a maioria das outras APIs de criptografia ou até mesmo as APIs .NET existentes. Chamador preocupado com o tamanho do buffer otimizado para seu caso de uso, por exemplo, IO de rede via buffers de 16K+). Demonstração com um número primo > 16K para testar suposições de implementação
  4. inputSpan é somente leitura. E entrada. Então precisa de um outSpan
  5. Update() criptografa ou descriptografa? Basta ter interfaces Encrypt e Decrypt para corresponder ao modelo mental de um desenvolvedor. tag também é o dado desejado mais importante neste instante, retorne isso.

Na verdade, indo um passo adiante, por que não apenas

using (var cipher = new AesGcmCipher(myKey, iv, aad))
{
    var tag = cipher.EncryptFinal(inputSpan, outputSpan);
}

Além disso, evite INonceProvider e os tipos. Os primitivos de criptografia não precisam disso, apenas fique com byte[] iv (meu favorito para dados pequenos) ou Span (o suposto novo IMHO legal, mas com muita abstração). O provedor Nonce opera em uma camada acima e o resultado disso pode ser apenas o iv visto aqui.

O problema de tornar os primitivos tão primitivos é que as pessoas simplesmente os usarão incorretamente. Com um provedor, podemos pelo menos forçar algum pensamento em seu uso.

Estamos falando do AEAD em geral, do qual o GCM é específico. Então, primeiro, o caso generalizado ( iv ) deve conduzir o design, não o caso específico ( nonce ).

Em segundo lugar, como simplesmente mudar de byte[] iv para GetNextNonce(Span<byte> writeNonceHere) realmente resolve o problema do nonce? Você apenas alterou o nome/rótulo do problema ao mesmo tempo em que o torna mais complexo do que deveria ser.

Terceiro, já que estamos entrando nas políticas de proteção iv , devemos também entrar nas principais políticas de proteção? E as políticas de distribuição de chaves? Essas são obviamente preocupações de nível superior.

Finalmente, o nonce é extremamente situacional no uso em camadas superiores. Você não quer ter uma arquitetura frágil onde as preocupações de camadas cruzadas estejam sendo acopladas.

Francamente, se pudéssemos esconder primitivos, a menos que alguém fizesse um gesto para dizer que eu sei o que estou fazendo, eu pressionaria por isso. Mas não podemos. Existem muitas implementações ruins de criptografia por aí porque as pessoas pensam "Oh, isso está disponível, eu vou usá-lo". Heck olhe para o próprio AES, vou usar isso sem HMAC.

Eu quero ver as APIs seguras por padrão, e se isso significa um pouco mais de dor, então, francamente, eu sou totalmente a favor. 99% dos desenvolvedores não sabem o que estão fazendo quando se trata de criptografia e facilitar para o 1% que sabe deveria ser uma prioridade menor.

O intervalo não existe. Eu não me importo com o que pode ou não estar chegando - não está no NetStandard2

@sdrapkin como @Drawaes aponta Span<T> é .NET Standard 1.0 , então pode ser usado em qualquer framework. Também é mais seguro que ArraySegment<T> , pois só permite acessar a janela real referenciada; em vez de toda a matriz.

Além disso ReadOnlySpan<T> impede a modificação dessa janela; novamente, ao contrário do segmento de matriz, onde qualquer coisa passada pode modificar e/ou reter uma referência à matriz passada.

Span deve ser a opção geral para apis de sincronização (o fato de uma api usando Span também pode lidar com memória nativa empilhada, bem como matrizes; é o gelo)

ou seja
Com ArraySegment o readonly é sugerido via docs; e nenhuma leitura/modificação fora dos limites é impedida

void Encrypt(
    ArraySegment<byte> iv, // readonly; covered by authentication
    ArraySegment<byte> plaintext, // readonly; covered by authentication
    ref ArraySegment<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
    ArraySegment<byte> additionalData = default(ArraySegment<byte>) // readonly; optional; covered by authentication
    );

No entanto, com Span, o readonly é aplicado pela API; bem como leituras fora dos limites das matrizes sendo impedidas

void Encrypt(
    ReadOnlySpan<byte> iv, // covered by authentication
    ReadOnlySpan<byte> plaintext, // covered by authentication
    Span<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
    ReadOnlySpan<byte> additionalData = ReadOnlySpan<byte>.Empty) // optional; covered by authentication
    );

Ele transmite a intenção com os parâmetros muito melhor; e é menos propenso a erros em relação a leituras/gravações fora dos limites.

@benaadams @Drawaes nunca disse que Span<T> estava no NetStandard ( qualquer NetStandard enviado ). O que ele disse é (1) concordar que Span<T> não está em nenhum NetStandard enviado; (2) que Span<T> será _"enviado no prazo 2.1"_.

Para este problema específico do Github, no entanto, a discussão (somente leitura) Span<T> está em alta agora - não há clareza sobre o escopo ou a finalidade da API a ser projetada.

Ou vamos com a API AEAD primitiva bruta de baixo nível (ex. semelhante ao CAESAR):

  • Prós: bom ajuste para AES-GCM/CCM, vetores de teste existentes de boas fontes (NIST, RFC). @sidshetye ficará feliz. @blowdart meditará sobre _"tornar primitivos tão primitivos"_, mas eventualmente verá o Yin e o Yang porque os primitivos são primitivos e não há como protegê-los para crianças.
  • Contras: Usuários experientes (o proverbial 1%) usarão as APIs de baixo nível com responsabilidade, enquanto os outros usuários não especialistas (99%) usarão mal para escrever software .NET quebrado que será responsável pela grande maioria do .NET CVEs, o que contribuirá muito para a percepção de que .NET é uma plataforma insegura.

Ou vamos com a API AEAD de alto nível, impossível de uso indevido ou resistente:

  • Prós: 99% dos usuários não especialistas continuarão cometendo erros, mas pelo menos não no código AEAD. O _"Quero ver as APIs seguras por padrão"_ de @blowdart ressoa profundamente no ecossistema, e segurança, prosperidade e bom karma acontecem a todos. Muitos bons designs e implementações de API já estão disponíveis.
  • Contras: Sem padrões. Nenhum vetor de teste. Não há consenso sobre se o AEAD é mesmo o objetivo certo para atingir a API de streaming online de alto nível (spoiler: não é - veja o artigo de Rogaway ).

Ou fazemos os dois. Ou entramos na paralisia da análise e podemos encerrar essa questão agora mesmo.

Eu sinto fortemente que, sendo parte da linguagem principal, a criptografia precisa ter uma API de base sólida e de baixo nível. Uma vez que você tenha isso, a criação de APIs de alto nível ou APIs de "roda de treinamento" pode ser conectada rapidamente pelo núcleo ou pela comunidade. Mas eu desafio qualquer um a fazer o inverso com elegância. Além disso, o tópico é " Primitiva geral de baixo nível para cifras "!

@Drawaes existe um cronograma para convergir e resolver isso? Algum plano de envolver pessoas que não são da Microsoft além desses alertas do GitHub? Como uma chamada de conferência de 30 minutos? Estou tentando ficar fora de uma toca de coelho, mas estamos apostando que a criptografia .NET core estará em um certo nível de maturidade e estabilidade.

Ainda estamos prestando atenção e trabalhando nisso. Reunimo-nos com o Microsoft Cryptography Board (o conjunto de pesquisadores e outros especialistas que aconselham o uso de criptografia pela Microsoft) e @bartonjs terá mais informações para compartilhar em breve.

Com base em um pequeno rabisco de fluxo de dados e nos conselhos do Crypto Board, chegamos ao seguinte. Nosso modelo era GCM, CCM, SIV e CBC+HMAC (observe que não estamos falando em fazer SIV ou CBC+HMAC agora, apenas queríamos provar a forma).

```C#
interface pública INonceProvider
{
ReadOnlySpanGetNextNonce(int nonceSize);
}

classe abstrata pública AuthenticatedEncryptor : IDisposable
{
public int NonceOrIVSizeInBits { get; }
public int TagSizeInBits { get; }
public bool SupportsAssociatedData { get; }
público ReadOnlySpanLastNonceOrIV { get; }
público ReadOnlySpanÚltimaTag { obter; }

protected AuthenticatedEncryptor(
    int tagSizeInBits,
    bool supportsAssociatedData,
    int nonceOrIVSizeInBits) => throw null;

protected abstract bool TryEncrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData,
    Span<byte> encryptedData,
    out int bytesWritten,
    Span<byte> tag,
    Span<byte> nonceOrIVUsed);

public abstract void GetEncryptedSizeRange(
    int dataLength,
    out int minEncryptedLength,
    out int maxEncryptedLength);

public bool TryEncrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData,
    Span<byte> encryptedData,
    out int bytesWritten) => throw null;

public byte[] Encrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData) => throw null;

// some variant of the Dispose pattern here.

}

classe selada pública AesGcmEncryptor : AuthenticatedEncryptor
{
public AesGcmEncryptor(ReadOnlySpankeySize, INonceProvider nonceProvider)
: base(128, verdadeiro, 96)
{
}
}

classe selada pública AesCcmEncryptor: AuthenticatedEncryptor
{
public AesCcmEncryptor(
ReadOnlySpanchave,
int nonceSizeInBits,
INonceProvider nonceProvider,
int tagSizeInBits)
: base(tagSizeInBits, true, nonceSizeInBits)
{
valide nonceSize e tagSize em relação à especificação do algoritmo;
}
}

classe abstrata pública AuthenticatedDecryptor : IDisposable
{
public abstract bool TryDecrypt(
ReadOnlySpanmarcação,
ReadOnlySpannonceOrIV,
ReadOnlySpanDados criptografados,
ReadOnlySpandados associados,
Períododados,
out int bytesWritten);

public abstract void GetEncryptedSizeRange(
    int encryptedDataLength,
    out int minDecryptedLength,
    out int maxDecryptedLength);

public byte[] Decrypt(
    ReadOnlySpan<byte> tag,
    ReadOnlySpan<byte> nonceOrIV,
    ReadOnlySpan<byte> encryptedData,
    ReadOnlySpan<byte> associatedData) => throw null;

// some variant of the Dispose pattern here.

}

classe selada pública AesGcmDecryptor : AuthenticatedDecryptor
{
public AesGcmDecryptor(ReadOnlySpanchave) => jogue null;
}

classe selada pública AesCcmDecryptor: AuthenticatedDecryptor
{
public AesCcmDecryptor(ReadOnlySpanchave) => jogue null;
}
```

Esta proposta elimina o streaming de dados. Nós realmente não temos muita flexibilidade nesse ponto. A necessidade do mundo real (baixa) combinada com os riscos associados (extremamente altos para GCM) ou impossibilidade (CCM) significa que acabou.

Esta proposta usa uma fonte externalizada de nonce para criptografia. Não teremos implementações públicas desta interface. Cada aplicativo/protocolo deve fazer sua própria vinculação da chave ao contexto para que possa alimentar as coisas adequadamente. Embora cada chamada para TryEncrypt faça apenas uma chamada para GetNextNonce, não há garantia de que esse TryEncrypt específico será bem-sucedido, portanto, ainda cabe ao aplicativo entender se isso significa que ele deve tentar novamente o nonce. Para CBC+HMAC, criaríamos uma nova interface, IIVProvider, para evitar confundir a terminologia. Para SIV o IV é construído, então não há parâmetro aceitável; e com base na especificação, o nonce (quando usado) parece ser considerado apenas como parte do associadoData. Portanto, SIV, pelo menos, sugere que ter nonceOrIV como parâmetro para TryEncrypt não é geralmente aplicável.

TryDecrypt definitivamente lança uma tag inválida. Ele só retorna false se o destino for muito pequeno (de acordo com as regras dos métodos Try)

Coisas que estão definitivamente abertas para feedback:

  • Os tamanhos devem estar em bits (como partes significativas das especificações) ou bytes (já que apenas %8 valores são válidos de qualquer maneira, e sempre vamos dividir, e algumas das partes das especificações falam sobre coisas como o tamanho nonce em bytes )?
  • Nomes e ordenação dos parâmetros.
  • As propriedades LastTag/LastNonceOrIV. (Tornar eles (graváveis) Spans em um TryEncrypt público significa apenas que existem três buffers que podem ser muito pequenos, fazendo com que eles fiquem do lado do "Try" é mais claro; a classe base pode fazer a promessa de que será nunca ofereça um buffer muito curto.).
  • Oferecendo um algoritmo AE para o qual isso não funciona.
  • O associatedData deve ser movido para o final com um padrão de ReadOnlySpan<byte>.Empty ?

    • Ou sobrecargas feitas que o omitem?

  • Alguém quer declarar amor ou ódio pelos métodos de retorno byte[] ? (Baixa alocação pode ser alcançada usando o método Span, isso é apenas por conveniência)
  • Os métodos de intervalo de tamanho foram meio que parafusados ​​no final.

    • O propósito deles é que

    • Se o intervalo de destino for menor que min, retorne false imediatamente.

    • Os métodos byte[] -returning irão alocar um buffer de max, e então Array.Resize ele conforme necessário.

    • Sim, para GCM e CCM min=max=input.Length , mas isso não é verdade para CBC+HMAC ou SIV

    • Existe um algoritmo que precisaria levar em consideração o comprimento de AssociatedData?

Definitivamente bytes - não bits.
O provedor Nonce que não reconhece a chave é um grande erro.

O provedor Nonce que não reconhece a chave é um grande erro.

Você pode escrever seu provedor nonce como quiser. Não estamos fornecendo nenhum.

E quanto à limpeza determinística/ IDisposable ?

E quanto à limpeza determinística/IDisposable ?

Boa decisão. Adicionado a AuthenticatedEncryptor/AuthenticatedDecryptor. Eu não acho que eles devem investigar a possibilidade de descarte no provedor nonce, o chamador pode simplesmente empilhar as instruções using.

INonceProvider conceito/propósito não faz sentido para mim (ecoando outros). Deixe os primitivos serem primitivos - passe o nonce da mesma forma que você passa a chave (ou seja, como bytes - seja como for declarado). Nenhuma especificação AE/AEAD força um algoritmo para como os nonces são gerados/derivados - esta é uma responsabilidade de camada superior (pelo menos no modelo let-primitives-be-primitive).

Sem streaming? Sério? Qual é a justificativa para remover forçosamente o streaming de uma cifra de fluxo como AES-GCM em um nível básico fundamental?

Por exemplo, o que sua placa de criptografia recomenda nesses dois cenários recentes que analisamos?

  1. O cliente tem grandes arquivos de saúde entre 10 e 30 GB. O núcleo vê apenas um fluxo de dados entre duas máquinas, portanto, é um fluxo de passagem. Obviamente, uma nova chave é emitida para cada arquivo de 10 GB, mas você acabou de tornar cada fluxo de trabalho inútil. Agora você quer que nós a) armazenemos esses dados em buffer (memória, sem pipeline) b) executemos criptografia (todas as máquinas no pipeline agora estão ociosas!) c) grave os dados (o primeiro byte escrito após a e b são 100% feito) ? Por favor, me diga que você está brincando. Vocês estão conscientemente colocando "criptografia é um fardo" de volta ao jogo.

  2. A unidade de segurança física possui vários fluxos 4K que também são criptografados para cenários em repouso. A emissão de chave nova acontece no limite de 15 GB. Você propõe armazenar em buffer o clipe inteiro?

Não vejo nenhuma entrada da comunidade, de pessoas realmente construindo software do mundo real, pedindo para remover o suporte de streaming. Mas então a equipe desaparece do diálogo da comunidade, se reúne internamente e depois volta com algo que ninguém pediu, algo que mata aplicativos reais e reforça que "a criptografia é lenta e cara, ignorá-la?"

Você pode fornecer Encrypt e EncryptFinal que dariam suporte a ambas as opções em vez de impor sua decisão para todo o ecossistema.

O design elegante elimina a complexidade, não o controle.

Qual é a justificativa para remover forçosamente o streaming de uma cifra de fluxo como AES-GCM em um nível básico fundamental?

acho que foi algo como

Esta proposta elimina o streaming de dados. Nós realmente não temos muita flexibilidade nesse ponto. A necessidade do mundo real (baixa) combinada com os riscos associados (extremamente altos para GCM) ou impossibilidade (CCM) significa que acabou.

O GCM tem muitos momentos de oops em que permite a recuperação de chaves. Se um invasor puder fazer um texto cifrado escolhido e observar a saída de streaming antes da verificação da tag, ele poderá recuperar a chave. (Ou assim me diz um dos criptoanalistas). Efetivamente, se algum dado processado pelo GCM for observável em qualquer ponto antes da verificação da etiqueta, a chave estará comprometida.

Tenho certeza de que o Crypto Board recomendaria NÃO usar o GCM para o primeiro cenário, mas sim o CBC + HMAC.

Se o seu segundo cenário for um enquadramento de 4k e você estiver criptografando cada quadro de 4k, isso funcionará com este modelo. Cada quadro de 4k + nonce + tag é descriptografado e verificado antes de você recuperar os bytes, para que você nunca vaze o fluxo de chaves / chave.

Para comparação: atualmente estou desenvolvendo essa API de criptografia "deixe os primitivos serem primitivos". Aqui está minha classe para criptografia autenticada.

Para mim, acabou sendo útil poder falar sobre uma primitiva de criptografia independentemente de uma chave. Por exemplo, muitas vezes eu quero conectar um primitivo específico em um método que funciona com qualquer algoritmo AEAD e deixar a geração de chaves etc. para esse método. Portanto, há uma classe AeadAlgorithm e uma classe Key separada.

Outra coisa muito útil que já preveniu vários bugs é usar tipos distintos para representar dados de formas diferentes, por exemplo, uma Key e um Nonce , ao invés de usar um simples byte[] ou Span<byte> para tudo.


API AeadAlgorithm (clique para expandir)

public abstract class AeadAlgorithm : Algorithm
{
    public int KeySize { get; }

    public int NonceSize { get; }

    public int TagSize { get; }

    public byte[] Decrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext)

    public void Decrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        Span<byte> plaintext)

    public byte[] Encrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> plaintext)

    public void Encrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> plaintext,
        Span<byte> ciphertext)

    public bool TryDecrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        out byte[] plaintext)

    public bool TryDecrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        Span<byte> plaintext)
}

@bartonjs ele / ela está correto, você precisa confiar no programa não produzindo até a autenticação. Então, por exemplo, se você não está autenticando (ou ainda não) você pode controlar a entrada de um bloco e, portanto, conhecer a saída e trabalhar de trás para frente a partir daí ...

Por exemplo, um ataque man in the middle pode injetar blocos conhecidos em um fluxo cbc e executar um ataque clássico de inversão de bits.

Não tenho certeza de como resolver o problema de grandes pedaços de dados, graças a fragmentá-los com nonces seriais ou semelhantes ... ala TLS.

Bem, deixe-me reformular o que faço, mas apenas no caso de tamanhos pequenos de rede, o que não é suficiente para uma lib de uso geral.

No espírito de abertura, é possível revelar quem está no Microsoft Cryptography Review Board (e idealmente os comentários/opiniões de membros específicos que revisaram este tópico)? Brian LaMacchia e quem mais?

_usando psicologia reversa:_

Estou feliz que o streaming AEAD foi lançado. Isso significa que o Inferno continua sendo o único AEAD de streaming baseado em CryptoStream prático para o Joe médio. Obrigado MS Crypto Review Board!

Com base no comentário de @ektrah , a abordagem dele (dela?) é orientada pela RFC 5116 , que mencionei anteriormente. Há muitas citações notáveis ​​na RFC 5116:

3.1. Requisitos de geração Nonce
É essencial para a segurança que os nonces sejam construídos de uma maneira que respeite o requisito de que cada valor de nonce seja distinto para cada invocação da operação de criptografia autenticada, para qualquer valor fixo da chave.
...

  1. Requisitos de Especificações do Algoritmo AEAD
    Cada algoritmo AEAD deve aceitar qualquer nonce com um comprimento entre N_MIN e N_MAX octetos, inclusive, onde os valores de N_MIN e N_MAX são específicos para esse algoritmo. Os valores de N_MAX e N_MIN PODEM ser iguais. Cada algoritmo DEVE aceitar um nonce com um comprimento de doze (12) octetos. Algoritmos aleatórios ou com estado, que são descritos abaixo, PODEM ter um valor N_MAX de zero.
    ...
    Um algoritmo de criptografia autenticada pode incorporar ou fazer uso de uma fonte aleatória, por exemplo, para a geração de um vetor de inicialização interno que é incorporado à saída de texto cifrado. Um algoritmo AEAD desse tipo é chamado de randomizado; embora observe que apenas a criptografia é aleatória e a descriptografia é sempre determinística. Um algoritmo aleatório PODE ter um valor de N_MAX igual a zero.

Um algoritmo de criptografia autenticada pode incorporar informações de estado interno que são mantidas entre as invocações da operação de criptografia, por exemplo, para permitir a construção de valores distintos que são usados ​​como nonces internos pelo algoritmo. Um algoritmo AEAD desse tipo é chamado de stateful. Esse método pode ser usado por um algoritmo para fornecer boa segurança mesmo quando o aplicativo insere nonces de comprimento zero. Um algoritmo stateful pode ter um valor de N_MAX que é igual a zero.

Uma ideia que vale a pena explorar é a passagem do Nonce de comprimento zero/nulo, que pode até ser o padrão. A passagem de tal valor Nonce "especial" randomizará o valor Nonce real, que estará disponível como saída do Encrypt.

Se INonceProvider ficar por causa de "motivos", outra ideia é adicionar uma chamada Reset() , que será acionada toda vez que o AuthenticatedEncryptor for rechaveado. Se, por outro lado, o plano for nunca redigitar instâncias AuthenticatedEncryptor , isso destruirá o GC se quisermos criar uma API de criptografia de fragmentos de streaming (por exemplo, fragmento = pacote de rede), e cada fragmento deve ser criptografado com uma chave diferente (ex. protocolo MSL Netflix, Inferno, outros). Especialmente para operações enc/dec paralelas em que gostaríamos de manter um pool de mecanismos AEAD e emprestar instâncias desse pool para fazer enc/dec. Vamos dar um pouco de amor ao GC :)

Do meu ponto de vista, o único propósito das primitivas de criptografia é implementar protocolos de segurança de alto nível bem projetados. Cada protocolo insiste em gerar nonces à sua maneira. Por exemplo:

  • O TLS 1.2 segue as recomendações da RFC 5116 e concatena um IV de 4 bytes com um contador de 8 bytes,
  • TLS 1.3 xor é um contador de 8 bytes preenchido com 12 bytes com um IV de 12 bytes,
  • O ruído usa um contador de 8 bytes preenchido para 12 bytes em ordem de bytes big-endian para AES-GCM e um contador de 8 bytes preenchido para 12 bytes em ordem de bytes little-endian para ChaCha/Poly.

O GCM é muito frágil para nonces aleatórios em tamanhos de nonce típicos (96 bits). E não conheço nenhum protocolo de segurança que realmente suporte nonces aleatórios.

Não há muita demanda por mais APIs que forneçam primitivas de criptografia. 99,9% dos desenvolvedores precisam de receitas de alto nível para cenários relacionados à segurança: armazenar uma senha em um banco de dados, criptografar um arquivo em repouso, transferir com segurança uma atualização de software etc.

No entanto, APIs para essas receitas de alto nível são raras. As únicas APIs disponíveis geralmente são apenas HTTPS e as primitivas de criptografia, o que força os desenvolvedores a implementar seus próprios protocolos de segurança. IMO a solução é não se esforçar muito no design de APIs para trabalhar com primitivos. São APIs para receitas de alto nível.

Obrigado pelo feedback, a todos! Algumas perguntas:

  1. Embora a descriptografia de streaming possa falhar catastroficamente, a criptografia de streaming pode ser factível. A criptografia de streaming (juntamente com uma opção sem streaming), mas apenas a descriptografia sem streaming, parece mais útil? Se sim, existem alguns problemas a serem resolvidos:
    uma. Alguns algoritmos (CCM, SIV) na verdade não suportam streaming. Devemos colocar criptografia de streaming na classe base e entradas de streaming em buffer ou lançar das classes derivadas?
    b. O streaming de AAD provavelmente não é possível devido a restrições de implementação, mas algoritmos diferentes precisam dele em momentos diferentes (alguns precisam no início, outros não até o final). Devemos exigi-lo antecipadamente ou ter um método para adicioná-lo que funcione quando os algoritmos individuais permitirem?
  1. Estamos abertos a melhorias no INonceProvider, desde que o ponto seja que os usuários precisem escrever código gerando um novo nonce. Alguém tem outra forma proposta para ele?

1 . a = Acho que pode ser um problema não avisar o usuário com antecedência. Imagine o cenário de alguém acima, um arquivo de 10gb. Eles acham que estão recebendo streaming, então, algum tempo depois, outro desenvolvedor altera a cifra e, em seguida, o código está armazenando 10 GB (ou tentando) antes de retornar um valor.

  1. b = Novamente com a ideia de "streaming" ou rede, por exemplo AES GCM etc, você não obtém as informações do AAD até o final da descriptografia. Quanto à criptografia, ainda estou para ver um caso em que você não tenha os dados antecipadamente. Então, eu diria que pelo menos para criptografia você deve exigir no início, a descriptografia é mais complexa.
  1. Eu acho que não é realmente um problema, fornecer os "bytes" para o nonce por meio de uma interface ou apenas diretamente não está aqui nem ali. Você pode conseguir a mesma coisa nos dois sentidos, só acho mais feio para um primitivo, mas não me oponho veementemente se isso faz as pessoas dormirem melhor à noite. Eu apenas eliminaria isso como um acordo feito e seguiria em frente com as outras questões.

Sobre o processo de deliberação

@bartonjs : Poderíamos discutir o dia todo se decisões a portas fechadas sem envolvimento da comunidade são uma justificativa eficaz, mas vamos fugir do assunto, então vou deixar isso pra lá. Além disso, sem comunicações cara a cara ou em tempo real mais ricas, não quero incomodar ninguém lá.

Em relação ao streaming

1. o argumento 'streaming não implica segurança AES-GCM'

Especificamente, steaming => retornar dados descriptografados ao chamador antes da verificação da tag => sem segurança. Isso não é som. @bartonjs afirma 'texto cifrado escolhido => assistir saída => recuperar chave' enquanto @drawaes afirma 'controlar entrada para um bloco => portanto saber saída => "trabalhar a partir daí" '

Bem, no AES-GCM, a única coisa que a etiqueta faz é a verificação de integridade (proteção contra adulteração). Tem 0 impacto na privacidade. Na verdade, se você remover o processamento de tags GCM/GHASH do AES-GCM, você simplesmente obterá o modo AES-CTR. É essa construção que lida com o aspecto de privacidade. E o CTR é maleável para bit flips, mas não é "quebrado" em nenhuma das maneiras que vocês dois estão afirmando (recuperando chave ou texto simples), porque isso significaria que o primitivo AES fundamental está comprometido. Se o seu criptoanalista (quem é?) sabe algo que o resto de nós não sabe, ele/ela deveria publicá-lo. A única coisa possível é que um atacado pode inverter o bit N e saber que o bit N do texto simples foi invertido - mas eles nunca sabem qual é o texto simples real.

assim

1) a privacidade de texto simples é sempre aplicada
2) a verificação de integridade é simplesmente adiada (até o final do fluxo) e
3) nenhuma chave é comprometida.

Para produtos e sistemas em que o streaming é fundamental, agora você pode pelo menos projetar uma troca em que se desce momentaneamente do AEAD para a criptografia AES regular - depois volta para o AEAD após a verificação da tag. Isso desbloqueia vários conceitos inovadores para abraçar a segurança em vez de "Você quer armazenar tudo isso em buffer - você está louco? Não podemos fazer criptografia!".

Tudo porque você deseja implementar apenas EncryptFinal em vez de Encrypt e EncryptFinal (ou equivalentes).

2. Não específico para GCM!

Agora, o AES-GCM não é uma fera mágica para ter "momentos oops" em abundância. É simplesmente AES-CTR + GHASH (uma espécie de hash, se me permite). Nonce as considerações relacionadas à privacidade são herdadas do modo CTR e as considerações de tag relacionadas à integridade vêm dos tamanhos de tag variáveis ​​permitidos na especificação. Ainda assim, AES-CTR + GHASH é muito semelhante a algo como AES-CBC + HMAC-SHA256, pois o primeiro algoritmo lida com privacidade e o segundo com integridade. Em AES-CBC + HMAC-SHA256, as inversões de bits no texto cifrado corromperão o bloco correspondente no texto descriptografado (ao contrário do CTR) E também inverterá deterministicamente os bits no seguinte bloco de texto simples descriptografado (como o CTR). Novamente, um invasor não saberá qual será o texto simples resultante - apenas que os bits foram invertidos (como CTR). Finalmente, a verificação de integridade (HMAC-SHA256) irá detectá-lo. Mas apenas processando o último byte (como GHASH).

Portanto, se o seu argumento de reter TODOS os dados descriptografados até que a integridade esteja OK for realmente bom - ele deve ser aplicado de forma consistente. Portanto, TODOS os dados que saem do caminho AES-CBC também devem ser armazenados em buffer (internamente pela biblioteca) até que o HMAC-SHA256 passe. Isso basicamente significa que no .NET, nenhum dado de streaming pode se beneficiar dos avanços do AEAD. O .NET força o streaming de dados a fazer downgrade. Para escolher entre nenhuma criptografia ou criptografia regular. Não AEAD. Onde o armazenamento em buffer é tecnicamente impraticável, os arquitetos devem pelo menos ter a opção de avisar os usuários finais de que "as imagens do drone podem estar corrompidas" em vez de "sem olhos para você".

3. É o melhor que temos

Os dados estão ficando maiores e a segurança precisa ser mais forte. O streaming também é uma realidade que os designers precisam adotar. Até que o mundo crie um algoritmo AEAD verdadeiramente integrado que possa detectar nativamente a adulteração de corrupção no meio do fluxo, estamos presos à criptografia + autenticação como parceiros. A verdadeira primitiva AEAD está sendo pesquisada, mas temos apenas criptografia + autenticação por enquanto.

Eu me importo menos com "AES-GCM" tanto quanto me importo com um algoritmo AEAD rápido e popular que pode suportar cargas de trabalho de streaming - super predominante em um mundo hiperconectado e rico em dados.

4. Use AES-CBC-HMAC, Use (insira a solução alternativa)

o Crypto Board recomendaria NÃO usar o GCM para o primeiro cenário, mas sim o CBC + HMAC.

Deixando de lado tudo o que foi mencionado acima ou mesmo as especificidades do cenário - sugerindo que o AES-CBC-HMAC não é gratuito. É ~3x mais lento que o AES-GCM, pois a criptografia AES-CBC não é paralelizável e o GHASH pode ser acelerado por meio da instrução PCLMULQDQ. Portanto, se você estiver em 1 GB/s com AES-GCM, agora atingirá ~ 300 MB/s com AES-CBC-HMAC. Isso novamente perpetua a mentalidade "Crypto retarda você, pule-a" - uma que o pessoal de segurança se esforça para combater.

criptografando cada quadro de 4k

codecs de vídeo devem de repente fazer criptografia? Ou a camada de criptografia deve agora entender os codecs de vídeo? É apenas um fluxo de bits na camada de segurança de dados. O fato de ser um vídeo/dados genômicos/imagens/formatos proprietários etc não deve ser uma preocupação da camada de segurança. Uma solução geral não deve misturar responsabilidades principais.

Nonce

O NIST permite IVs aleatórios para comprimento superior a 96 bits. Consulte a seção 8.2.2 em NIST 800-38D . Nada de novo aqui, uma vez que os requisitos vêm do modo CTR. O que também é bastante padrão na maioria das cifras de fluxo . Eu não entendo o medo repentino em relação aos nonces - sempre foi number used once . Ainda assim, enquanto o debate INonce torna a interface desajeitada, pelo menos não elimina inovações como a imposição de não-stream-for-you. Eu cederei a INonce qualquer dia se conseguirmos as inovações de segurança AEAD + carga de trabalho de streaming. Eu odeio chamar algo básico como streaming de uma inovação - mas é aí que temo que regrediríamos.

Eu adoraria ser provado errado

Eu sou apenas um cara que depois de um longo dia de trabalho, desistiu da noite de cinema com meus filhos para digitar isso. Estou cansado e posso estar errado. Mas pelo menos tenha um diálogo comunitário baseado em fatos, em vez de anedotas ou "razões do comitê" ou algum outro vodu. Meu negócio é promover inovações seguras em .NET e Azure. Acho que temos objetivos alinhados.

Falando em diálogo comunitário...

Podemos, por favor, fazer uma chamada do Skype para a comunidade? Expressar um tópico complexo como esse explode em uma parede gigante de texto. Bonito, por favor?

Por favor, não faça uma chamada pelo Skype - essa é a própria definição de "reunião à porta fechada", sem registros disponíveis para a comunidade. As questões do Github são o veículo certo para que todas as partes tenham um discurso civil documentado (ignorando os precedentes de remoção de comentários do MS).

O MS Crypto Review Board provavelmente também fez uma chamada pelo Skype. Não é culpa do pessoal da MS que participa deste tópico - eles provavelmente têm acesso muito limitado e poder de persuasão sobre as torres de marfim do MS Crypto Review Board (seja o que for / quem quer que seja).

Em relação ao streaming AEAD:

A _encriptação_ de streaming de tamanho de byte é possível para modos MAC-last como GCM, CTR+HMAC, mas não é possível para modos MAC-first como CCM. O streaming de tamanho de byte _decryption_ é fundamentalmente vazando e, portanto, não é considerado por ninguém. A _encriptação_ de streaming de tamanho de bloco também é possível para CBC+HMAC, mas isso não muda nada. Ou seja. As abordagens de tamanho de byte ou tamanho de bloco para streaming de AEAD são falhas.

_encryption_ e _decryption_ de streaming de tamanho de bloco funcionam muito bem, mas têm 2 restrições:

  • eles requerem buffering (além do tamanho do bloco). Isso pode ser feito pela biblioteca/API se o buffer for controlado/limitado (ex. Inferno), ou deixado para a camada superior (camada de chamada) para lidar com isso. De qualquer maneira funciona.

  • A AEAD de streaming em pedaços não é padronizada. Ex. nacl-stream , Inferno, DataProtection próprio do MS, make-your-own.

Este é apenas um resumo do que todos nesta discussão até agora já sabem.

@sdrapkin , para ter certeza de que entendi corretamente, você concorda com esta API fornecendo criptografia de streaming, mas sem descriptografia de streaming?

@sdrapkin bem, o brainstorming humano em tempo real é certamente benéfico, as preocupações com a manutenção de registros podem ser resolvidas com atas de reunião. Voltando ao lado técnico, embora o agrupamento funcione para descriptografia de streaming, isso não é uma primitiva de segurança de baixo nível. É um protocolo personalizado. E um fora do padrão como você observou.

@morganbr

_você concorda com esta API fornecendo criptografia de streaming, mas sem descriptografia de streaming?_

Não, eu não sou. Se tal API estivesse disponível, seria fácil criar um texto cifrado criptografado por fluxo de um tamanho que nenhuma descriptografia de buffer seria capaz de descriptografar (sem memória).

^^^^ Isso, não houve muita concordância até agora, mas acho que todos podemos concordar de qualquer maneira que uma API assimétrica seria um desastre. Tanto de um "ei eram os métodos de descriptografia de fluxo que eu pensei que confiaria porque havia métodos de criptografia" e por causa dos comentários do @sdrapkin acima.

@Drawaes Concordo. API enc/dec assimétrica seria horrível.

Alguma atualização pessoal?

Aparentemente eu confundi alguns ataques.

Fraquezas inerentes nas cifras de fluxo (que são AES-CTR e AES-GCM) permitem que ataques de texto cifrado escolhidos permitam a recuperação arbitrária de texto simples. A defesa contra ataques de texto cifrado escolhidos é a autenticação; então o AES-GCM é imune... a menos que você esteja fazendo descriptografia de streaming e possa identificar a partir de observações de canal lateral qual teria sido o texto simples. Por exemplo, se os dados descriptografados estiverem sendo processados ​​como XML, ele falhará muito rapidamente se outros caracteres além de espaço em branco ou < estiverem no início dos dados descriptografados. Então, isso é "a descriptografia de streaming reintroduz preocupações com o design de cifra de fluxo" (que, você deve ter notado, o .NET não tem nenhuma delas).

Enquanto procurava de onde vinha a recuperação da chave, existem documentos como Autenticaçãopontos fracos no GCM (Ferguson/Microsoft) , mas esse está recuperando a chave de autenticação com base em tamanhos de tags curtos (o que é parte do motivo pelo qual a implementação do Windows permite apenas tags de 96 bits). Provavelmente fui avisado sobre outros vetores de recuperação de chave de autenticação sobre por que o streaming do GCM é perigoso.

Em um comentário anterior , @sdrapkin observou que "a descriptografia de streaming de tamanho de byte está vazando fundamentalmente e, portanto, não é considerada por ninguém. ... As abordagens de tamanho de byte ou tamanho de bloco para streaming de AEAD são falhas". Isso, combinado com o CCM (e o SIV) não sendo capaz de fazer criptografia de streaming e o comentário de que seria estranho ter um streaming e não o outro, sugere que estamos de volta à proposta de ter apenas uma criptografia e descriptografar.

Então, parece que estamos de volta à minha última proposta de API (https://github.com/dotnet/corefx/issues/23629#issuecomment-329202845). A menos que haja outras questões pendentes que consegui esquecer enquanto estava de folga.

Bem vindo de volta @bartonjs

Vou dormir em breve, mas brevemente:

  1. Já combinamos o design do protocolo com o design primitivo antes neste tópico. Direi apenas que os ataques de texto cifrado escolhidos são uma preocupação de projeto de protocolo, não uma preocupação primitiva.

  2. A descriptografia de streaming AEAD pelo menos permite que você tenha privacidade e, em seguida, atualiza imediatamente para privacidade + autenticidade no último byte. Sem suporte de streaming no AEAD (ou seja, apenas privacidade tradicional), você está restringindo permanentemente as pessoas a uma garantia menor, apenas de privacidade.

Se os méritos técnicos forem insuficientes ou você estiver (com razão) cético quanto à autoridade de meus argumentos, tentarei a via da autoridade externa. Você deve saber que sua implementação subjacente real suporta AEAD (incluindo AES GCM) no modo de streaming. O sistema operacional Windows principal ( bcrypt ) permite o streaming do GCM por meio das funções BCryptEncrypt ou BCryptDecrypt . Veja dwFlags lá. Ou um exemplo de código de usuário . Ou um wrapper CLR de autoria da Microsoft. Ou que a implementação foi certificada pelo NIST FIP-140-2 tão recentemente quanto no início deste ano. Ou que tanto a Microsoft quanto o NIST gastaram recursos significativos em torno da implementação do AES e a certificaram aqui e aqui . E apesar de tudo isso, ninguém culpou os primitivos. Não faz nenhum sentido para o .NET Core aparecer de repente e impor sua própria tese de criptografia para diluir a poderosa implementação subjacente. Especialmente quando AMBOS o streaming e o one-shot podem ser suportados simultaneamente, muito trivialmente.

Mais? Bem, o acima é verdade para o OpenSSL, mesmo com suas APIs evp 'mais recentes'.

E é verdade para BouncyCastle.

E é verdade com a Arquitetura de Criptografia Java.

Felicidades!
Sid

@sidshetye ++10 se o cryptoboard está tão preocupado, por que eles permitem que o Windows CNG faça isso?

Se você verificar a validação NIST FIPS-140-2 AES da Microsoft (ex. # 4064 ), notará o seguinte:

AES-GCM:

  • Comprimentos de texto simples: 0, 8, 1016, 1024
  • Comprimentos AAD: 0, 8, 1016, 1024

AES-CCM:

  • Comprimento do texto simples: 0-32
  • Comprimento AAD: 0-65536

Não há validação para streaming. Eu nem tenho certeza se o NIST verifica esse ex. A implementação do AES-GCM não deve ter permissão para criptografar mais de 64 Gb de texto simples (outra limitação ridícula do GCM).

Não estou massivamente ligado ao streaming, pois meu uso não deve ultrapassar 16k, no entanto, buffers fragmentados seriam bons e não deveriam representar nenhum risco (na verdade, suspeito que o cng fez sua interface do jeito que é exatamente para esse propósito) ... por exemplo, eu quero ser capaz de passar em vários intervalos ou similar (lista vinculada, por exemplo) e descriptografar de uma só vez. Se ele descriptografar para um buffer contíguo, tudo bem.

Então, acho que mover a placa de criptografia sombria na API de "estilo de streaming" não é uma opção por enquanto, então vamos seguir em frente, fazer uma API de um tiro. Sempre há espaço para expandir uma API SE pessoas suficientes mostrarem uma necessidade mais tarde

@sdrapkin o ponto é que é a API de streaming que passou por extensa revisão pelo NIST Labs e MSFT. Cada compilação que está sendo validada custa entre US$ 80.000 - US$ 50.000 e a MSFT (e OpenSSL e Oracle e outros pesos pesados ​​de criptografia) investiram PESADO na validação dessas APIs e implementações por mais de 10 anos. Não vamos nos distrair com os tamanhos de texto simples específicos do plano de teste porque estou confiante de que o .NET suportará tamanhos diferentes de 0, 8, 1016, 1024, independentemente de streaming ou one-shot. O ponto é que todas essas APIs testadas em batalha (literalmente; em sistemas de suporte de armas), em todas essas plataformas suportam streaming AEAD no nível de API cripto-primitiva. Infelizmente, todos os argumentos até agora contra isso têm sido uma preocupação de nível de aplicativo ou protocolo citada como uma pseudo-preocupação no nível primitivo de criptografia.

Eu sou a favor de 'deixe a melhor ideia vencer', mas a menos que a equipe de criptografia .net core (MSFT ou comunidade) tenha alguma descoberta inovadora, eu simplesmente não vejo como todos que fazem criptografia até agora, de todas as organizações diferentes, estão errados e eles estão certos.

PS: Eu sei que estamos em desacordo aqui, mas todos nós queremos o que é melhor para a plataforma e seus clientes.

@Drawaes , a menos que a interface AEAD (não necessariamente implementação) que está sendo definida hoje suporte uma superfície de API de streaming, não vejo como as pessoas podem estendê-la sem ter duas interfaces ou interfaces personalizadas. Isso seria um desastre. Espero que esta discussão leve a uma interface que seja à prova de futuro (ou pelo menos, espelhe outras interfaces AEAD que existem há muitos anos!).

Eu costumo concordar. Mas esse problema não está indo a lugar nenhum rápido e quando isso acontecer, provavelmente atingiremos um ponto crítico ou não chegará ao 2.1 ou terá que ser resolvido sem tempo para resolver os problemas. Serei honesto, voltei aos meus invólucros antigos e estou apenas reformulando-os para 2.0;)

Temos algumas APIs de referência para Java , OpenSSL ou C# Bouncy Castle ou CLR Security . Francamente, qualquer um deles servirá e, a longo prazo, desejo que o C# tenha algo como a 'Java Cryptography Architecture' do Java, onde todas as implementações de criptografia são contra uma interface bem estabelecida, permitindo a troca de bibliotecas de criptografia sem afetar o código do usuário.

De volta aqui, acho melhor estendermos a interface ICryptoTransform do .NET Core como

public interface IAuthenticatedCryptoTransform : ICryptoTransform 
{
    bool CanChainBlocks { get; }
    byte[] GetTag();
    void SetExpectedTag(byte[] tag);
}

Se estamos Span ificando todos os byte[] s, isso deve permear toda a API no namespace System.Security.Cryptography para consistência geral.

Editar: links JCA corrigidos

Se estivermos Spanifying todos os byte[]s, isso deve permear toda a API no namespace System.Security.Cryptography para consistência geral.

Já fizemos isso. Tudo exceto ICryptoTransform, porque não podemos mudar as interfaces.

Acho melhor estendermos o ICryptoTransform do .NET Core ...

O problema com isso é que o padrão de chamada é muito estranho para obter a tag no final (especialmente se o CryptoStream estiver envolvido). Eu escrevi isso originalmente, e era feio. Há também o problema de como obter um desses, já que os parâmetros do GCM são diferentes dos parâmetros do CBC/ECB.

Então, aqui estão meus pensamentos.

  • A descriptografia de streaming é perigosa para o AE.
  • Em geral, sou fã de "me dê o primitivo e deixe-me gerenciar meu risco"
  • Também sou fã de ".NET não deve (facilmente) permitir coisas completamente inseguras, porque essa é parte de sua proposta de valor"
  • Se, como entendi mal originalmente, os riscos de fazer a descriptografia do GCM mal fossem a recuperação da chave de entrada, ainda estaria em "isso é muito inseguro". (A diferença entre .NET e todo o resto seria "tendo demorado mais para fazer isso, o mundo aprendeu mais")
  • Mas, como não é, se você realmente quer que as rodinhas saiam, então acho que vou considerar essa ideia.

Meus pensamentos bastante crus para esse fim (adicionando às sugestões existentes, então o one-shot permanece, embora eu ache como um impl padrão virtual em vez de um abstrato):

```C#
classe parcial AuthenticatedEncryptor
{
// lança se uma operação já estiver em andamento
public abstract void Initialize(ReadOnlySpandados associados);
// true em caso de sucesso, false em “destino muito pequeno”, exceção em qualquer outra coisa.
public abstract bool TryEncrypt(ReadOnlySpandados, intervalocryptoData, out int bytesRead, out int bytesWritten);
// false se restanteEncryptedData for muito pequeno, lança se outras entradas forem muito pequenas, consulte as propriedades NonceOrIVSizeInBits e TagSizeInBits.
// NonceOrIvUsed pode ser movido para Initialize, mas pode ser interpretado como uma entrada.
public abstract bool TryFinish(ReadOnlySpanrestanteData, SpanrestanteEncryptedData, out int bytesWritten, Spanmarca, intervalononceOrIvUsed);
}

classe parcial AuthenticatedDecryptor
{
// lança se uma operação já estiver em andamento
public abstract void Initialize(ReadOnlySpantag, ReadOnlySpannonceOrIv, ReadOnlySpandados associados);
// true em caso de sucesso, false em “destino muito pequeno”, exceção em qualquer outra coisa.
public abstract bool TryDecrypt(ReadOnlySpandados, intervalodecryptedData, out int bytesRead, out int bytesWritten);
// lança uma tag inválida, mas pode vazar os dados de qualquer maneira.
// (remainingDecryptedData é necessário para CBC+HMAC e, portanto, também pode adicionar leftData, eu acho?)
public abstract bool TryFinish(ReadOnlySpanrestanteData, SpanrestantesDecryptedData, out int bytesWritten);
}
```

AssociatedData vem em Initialize, porque os algoritmos que precisam dele por último podem segurá-lo, e os algoritmos que precisam dele primeiro não podem tê-lo de outra maneira.

Assim que uma forma for decidida para a aparência do streaming (e se as pessoas acham que o CCM deve armazenar em buffer internamente, ou deve lançar, quando estiver no modo de criptografia de streaming), voltarei ao quadro.

@bartonjs Eu sei o que você quer dizer sobre arrancar e programar a tag do final do fluxo para simetria entre criptografar/descriptografar. É complicado, mas pior se for deixado para cada usuário resolver. Eu tenho uma implementação que posso compartilhar no MIT; precisará olhar internamente com minha equipe (não na minha mesa/móvel)

Um meio termo pode ser como o OpenSSL ou o bcrypt do NT, onde você precisa conectar a tag logo antes da chamada de descriptografia final, pois é quando as comparações de tags acontecem. ou seja, um SetExpectedTag (antes da descriptografia final) e GetTag (após a criptografia final) funcionaria, mas descarrega o gerenciamento de tags para o usuário. A maioria simplesmente anexará a tag ao cipherstream, pois é a ordem temporal natural.

Eu acho que esperar a tag em Initialize em si (na descriptografia) quebra a simetria no espaço (fluxo de bytes) e no tempo (verificação da tag no final, não no início), o que limita sua utilidade. Mas as APIs de tags acima resolvem isso.

Também para criptografar, Initialize precisa do IV antes de qualquer transformação de criptografia.

Por fim, para criptografar e descriptografar, Initialize precisa das chaves de criptografia AES antes de qualquer transformação. (Estou perdendo algo óbvio ou você esqueceu de digitar essa parte?)

Eu acho que esperar a tag em Inicializar em si (na descriptografia) quebra a simetria

No CBC+HMAC, a recomendação usual é verificar o HMAC antes de iniciar qualquer descriptografia, portanto, é um algoritmo de descriptografia tag-first. Da mesma forma, pode haver um algoritmo "AE puro" que faz operações destrutivas na etiqueta durante os cálculos e apenas verifica se a resposta final foi 0. Assim, como o valor de dados associado, como pode haver algoritmos que precisam primeiro, ele tem em primeiro lugar em uma API totalmente generalizada.

Flutuá-los para SetAssociatedData e SetTag tem o problema de que, embora a classe base fosse independente do algoritmo, o uso se torna dependente do algoritmo. Alterar AesGcm para AesCbcHmacSha256 ou SomeTagDesctructiveAlgorithm agora resultaria no lançamento do TryDecrypt porque a tag ainda não foi fornecida. Para mim, isso é pior do que não ser polimórfico, portanto, permitir a flexibilidade sugere separar o modelo para ser totalmente isolado por algoritmo. (Sim, pode ser controlado por mais propriedades características de identificação de algoritmos como NeedsTagFirst , mas isso realmente torna mais difícil de usar)

Também para criptografar, Initialize precisa do IV antes de qualquer transformação de criptografia.

Por fim, para criptografar e descriptografar, o Initialize precisa das chaves de criptografia AES antes de qualquer transformação.

A chave era um parâmetro ctor de classe. O IV/nonce vem do provedor IV/nonce no parâmetro ctor.

O modelo de provedor resolve SIV, onde nenhum IV é fornecido durante a criptografia, um é gerado em nome dos dados. Caso contrário, SIV tem o parâmetro e requer que um valor vazio seja fornecido.

ou você esqueceu de digitar esse bit?

Os métodos de streaming estavam sendo adicionados à minha proposta existente, que já tinha a chave e o provedor IV/nonce como parâmetros ctor.

@bartonjs : Bom ponto que alguns algoritmos podem querer marcar primeiro enquanto outros no final e obrigado pelo lembrete de que é uma adição à especificação original. Descobri que considerar um caso de uso facilita, então aqui está um exemplo de nuvem em primeiro lugar:

Vamos realizar análises em um ou mais arquivos criptografados AES-GCM de 10 GB (ou seja, tags após texto cifrado) mantidos em armazenamento. Um funcionário de análise descriptografa simultaneamente vários fluxos de entrada em máquinas/clusters separados e, após as verificações do último byte + tag, inicia cada carga de trabalho de análise. Todas as VMs de armazenamento, trabalhador e análise estão no Azure US-West.

Aqui, não há como buscar a tag no final de cada stream e fornecê-la ao método Initialize do AuthenticatedDecryptor. Portanto, mesmo que um usuário se voluntarie para modificar o código para uso do GCM, ele não pode nem começar a usar a API.

Pensando bem, a única maneira de termos uma API que acomode vários AEADs E não tenha alterações no código do usuário, é se os provedores de criptografia para diferentes algoritmos AEAD manipularem automaticamente as tags. Java faz isso colocando as tags no final do texto cifrado para o GCM e as retira durante a descriptografia sem intervenção do usuário. Fora isso, sempre que alguém alterar significativamente o algoritmo (por exemplo, CBC-HMAC => GCM), terá que modificar seu código devido à natureza mutuamente exclusiva do processamento tag-first e tag-last.

IMHO, devemos primeiro decidir se

Opção 1) Os provedores de algoritmo lidam internamente com o gerenciamento de tags (como Java)

ou

Opção 2) Expor o suficiente na API para que os usuários façam isso sozinhos (como WinNT bcrypt ou openssl)

A opção 1 realmente simplificaria a experiência geral para os consumidores de bibliotecas porque o gerenciamento de buffer pode se tornar complexo. Resolva bem na biblioteca e cada usuário não terá que resolvê-lo sempre agora. Além disso, todos os AEADs obtêm a mesma interface (tag-first, tag-last, tag-less) e a troca de algoritmos também é mais simples.

Meu voto seria para a opção 1.

Por fim, conseguimos desenterrar nossa implementação, permitindo que as operações de streaming ICryptoTransform pelo GCM extraíssem automaticamente a fonte in-stream da tag . Esta foi uma atualização significativa para o próprio wrapper do CLR Security e, apesar das cópias de buffer adicionais, ainda é muito rápido (~4 GB/s em nosso macbook pro de teste no bootcamp do Windows 10). Basicamente, envolvemos o CLR Security para criar a opção 1 para nós mesmos, para que não precisemos fazer isso em nenhum outro lugar. Esse visual realmente ajuda a explicar o que está acontecendo na interface TransformBlock e TransformFinalBlock da interface ICryptoTransform .

@sidshetye Não sei por que seu exemplo de nuvem está bloqueado. Se você estiver lendo a partir do armazenamento, poderá fazer o download dos últimos bytes de tag primeiro e fornecê-los ao decodificador. Se estiver usando as APIs de armazenamento do Azure, isso será feito por meio de CloudBlockBlob.DownloadRangeXxx .

@GrabYourPitchforks Para não se desviar muito desse exemplo, mas esse é um recurso específico do Armazenamento de Blobs do Azure. Em geral, as cargas de trabalho de armazenamento baseado em VM (IaaS) ou de armazenamento não Azure normalmente obtêm um fluxo de rede que não é pesquisável.

Eu, pessoalmente, estou muito animado para ver @GrabYourPitchforks - yay!

Vamos realizar análises em um ou mais arquivos criptografados AES-GCM de 10 GB (ou seja, tags após texto cifrado) mantidos em armazenamento. Um funcionário de análise descriptografa simultaneamente vários fluxos de entrada em máquinas/clusters separados e, após as verificações do último byte + tag, inicia cada carga de trabalho de análise. Todas as VMs de armazenamento, trabalhador e análise estão no Azure US-West.

@sidshetye , você foi tão inflexível em manter os primitivos burros e perigosos e os protocolos inteligentes e abraçados separados! Tive um sonho - e acreditei. E então você joga isso em nós. Este é um protocolo - um projeto de sistema. Quem desenhou esse protocolo que você descreveu - estragou tudo. Não adianta chorar sobre a incapacidade de encaixar um pino quadrado em um buraco redondo agora.

Os arquivos de 10 Gb criptografados com GCM não apenas vivem perigosamente perto da borda primitiva (o GCM não é bom depois de 64 Gb), mas também houve uma afirmação implícita de que todo o texto cifrado precisará ser armazenado em buffer.

Quem criptografa arquivos de 10 Gb com GCM está cometendo um erro de protocolo com probabilidade esmagadora. A solução: criptografia em partes. O TLS tem fragmentação limitada a 16k de comprimento variável e há outros sabores mais simples e livres de PKI. O apelo sexual "primeiro na nuvem" desse exemplo hipotético não diminui os erros de design.

(Tenho muito o que fazer neste tópico.)

@sdrapkin levantou uma questão sobre a reutilização da interface IAuthenticatedEncryptor da camada de proteção de dados. Para ser honesto, não acho que essa seja a abstração correta para um primitivo, já que a camada de proteção de dados é bastante opinativa sobre como executa a criptografia. Por exemplo, ele proíbe a auto-seleção de um IV ou nonce, exige que uma implementação em conformidade entenda o conceito de AAD e produz um resultado que é um pouco proprietário. No caso do AES-GCM, o valor de retorno de IAuthenticatedEncryptor.Encrypt é a concatenação de uma coisa estranha usada para derivação de subchave, o texto cifrado resultante da execução do AES-GCM sobre o texto simples fornecido (mas não o AAD!) e a tag AES-GCM. Portanto, embora cada etapa envolvida na geração da carga útil protegida seja segura, a carga útil em si não segue nenhum tipo de convenção aceita e você não encontrará ninguém além da biblioteca de proteção de dados que possa descriptografar com êxito o texto cifrado resultante. Isso o torna um bom candidato para uma biblioteca voltada para o desenvolvedor de aplicativos, mas um candidato horrível para uma interface a ser implementada por primitivos.

Também devo dizer que não vejo valor considerável em ter uma One True Interface(tm) IAuthenticatedEncryptionAlgorithm que todos os algoritmos de criptografia autenticados devem implementar. Essas primitivas são "complexas", ao contrário das primitivas de cifra de bloco simples ou primitivas de hash. Existem simplesmente muitas variáveis ​​nessas primitivas complexas. O primitivo AE é apenas ou é AEAD? O algoritmo aceita um IV / nonce? (Já vi alguns que não.) Há alguma preocupação com a forma como a entrada IV / nonce ou os dados devem ser estruturados? IMO, as primitivas complexas deveriam ser simplesmente APIs independentes, e as bibliotecas de nível superior seriam preparadas em suporte para as primitivas complexas específicas com as quais elas se importam. Em seguida, a biblioteca de nível superior expõe qualquer API uniforme que ela acredite ser apropriada para seus cenários.

@sdrapkin Estamos saindo do assunto novamente. Direi apenas que um sistema é construído usando primitivas. Os primitivos de criptografia aqui são simples e poderosos. Enquanto a camada do sistema/protocolo manipulava o buffer; isso também em um nível de cluster, certamente não na memória principal do sistema que as primitivas one-shot forçariam. O limite de 'chunking' é X (X = 10 GB aqui) porque < 64 GB, porque a capacidade de armazenamento em buffer do cluster era quase ilimitada e nada começaria/poderia iniciar até que o último byte fosse carregado no cluster. Esta é exatamente a separação de preocupações, otimizando cada camada para seus pontos fortes que eu tenho falado. E isso só pode acontecer se os primitivos subjacentes não prejudicarem designs/limitações de camadas mais altas (observe que mais aplicativos do mundo real vêm com suas próprias desvantagens herdadas).

NIST 800-38d sec9.1 afirma:

A fim de inibir uma parte não autorizada de controlar ou influenciar a geração de IVs,
O GCM deve ser implementado apenas dentro de um módulo criptográfico que atenda aos requisitos de
Pub FIPS. 140-2. Em particular, o limite criptográfico do módulo deve conter um
“unidade de geração” que produz IVs de acordo com uma das construções do Sec. 8.2 acima.
A documentação do módulo para sua validação de acordo com os requisitos do FIPS 140-2 deve
descrever como o módulo atende ao requisito de exclusividade em IVs.

Isso implica para mim que os IVs do GCM devem ser gerados automaticamente internamente (e não transmitidos externamente).

@sdrapkin Bom ponto, mas se você ler ainda mais de perto, verá que para comprimentos de IV de 96 bits e acima, a seção 8.2.2 permite gerar um IV com um gerador de bits aleatórios (RBG) onde pelo menos 96 bits são aleatórios (você poderia apenas 0 outros bits). Eu mencionei isso no mês passado neste tópico em si ( aqui sob nonce).

LT;DR: INonce é uma armadilha que leva à não conformidade com as diretrizes do NIST e FIPS.

A seção 9.1 simplesmente diz que, para o FIPS 140-2, a unidade de geração IV (totalmente aleatória, ou seja, sec 8.2.2 ou implementação determinística, ou seja, sec 8.2.1) deve estar dentro do limite do módulo submetido à validação do FIPS. Desde a ...

  1. RBGs já são validados pelo FIPS
  2. Lente IV >= 96 é recomendada
  3. projetar uma unidade de geração IV que persista em reinicializações, perda indefinida de energia em uma camada primitiva de criptografia é difícil
  4. obter 3 acima implementado na biblioteca de criptografia E obtê-lo certificado é difícil e caro (US $ 50 mil para qualquer coisa que resulte em uma imagem de compilação não exata em bits)
  5. Nenhum código de usuário implementará o 3 e o obterá certificado por causa do 4 acima. (vamos deixar de lado algumas instalações exóticas militares/governamentais).

... a maioria das bibliotecas criptográficas (veja Java da Oracle, bcryptprimitives do WinNT, OpenSSL, etc.) submetidas à certificação FIPS usam a rota RBG para IV e simplesmente pegam uma matriz de bytes para entrada. Observe que ter a interface INonce é na verdade uma armadilha da perspectiva do NIST e do FIPS porque sugere implicitamente que um usuário deve passar uma implementação dessa interface para a função de criptografia. Mas é quase garantido que qualquer implementação de usuário de INonce NÃO tenha passado pelo processo de certificação NIST de 9 meses + e $ 50K +. No entanto, se eles tivessem acabado de enviar uma matriz de bytes usando a construção RGB (já na biblioteca de criptografia), eles estariam totalmente em conformidade com as diretrizes.

Eu já disse antes - essas bibliotecas de criptografia existentes evoluíram sua superfície de API e foram testadas em vários cenários. Mais do que abordamos neste longo tópico. Meu voto novamente é aproveitar esse conhecimento e experiência em todas essas bibliotecas, todas essas validações e todas essas instalações, em vez de tentar reinventar a roda. Não reinvente a roda. Use-o para inventar o foguete :)

Oi pessoal,

Alguma atualização sobre isso? Não vi nenhuma atualização no encadeamento do roteiro de criptografia de @karelz ou no encadeamento do AES GCM .

Obrigado
Sid

Portanto, a última proposta concreta é de https://github.com/dotnet/corefx/issues/23629#issuecomment -334328439:

partial class AuthenticatedEncryptor
{
    // throws if an operation is already in progress
    public abstract void Initialize(ReadOnlySpan<byte> associatedData);
    // true on success, false on “destination too small”, exception on anything else.
    public abstract bool TryEncrypt(ReadOnlySpan<byte> data, Span<byte> encryptedData, out int bytesRead, out int bytesWritten);
    // false if remainingEncryptedData is too small, throws if other inputs are too small, see NonceOrIVSizeInBits and TagSizeInBits properties.
    // NonceOrIvUsed could move to Initialize, but then it might be interpreted as an input.
    public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, Span<byte> remainingEncryptedData, out int bytesWritten, Span<byte> tag, Span<byte> nonceOrIvUsed);
}

partial class AuthenticatedDecryptor 
{
    // throws if an operation is already in progress
    public abstract void Initialize(ReadOnlySpan<byte> tag, ReadOnlySpan<byte> nonceOrIv, ReadOnlySpan<byte> associatedData);
    // true on success, false on “destination too small”, exception on anything else.
    public abstract bool TryDecrypt(ReadOnlySpan<byte> data, Span<byte> decryptedData, out int bytesRead, out int bytesWritten);
    // throws on bad tag, but might leak the data anyways.
    // (remainingDecryptedData is required for CBC+HMAC, and so may as well add remainingData, I guess?)
    public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, Span<byte> remainingDecryptedData, out int bytesWritten);
}

Apenas algumas questões potenciais foram levantadas desde:

  • A tag é necessária antecipadamente, o que dificulta determinados cenários. Ou a API deve se tornar significativamente mais complexa para permitir maior flexibilidade, ou esse problema deve ser considerado um problema de protocolo (ou seja, de alto nível).
  • INonceProvider pode ser desnecessariamente complexo e/ou levar à não conformidade com as diretrizes do NIST e FIPS.
  • A abstração pretendida de primitivos de criptografia autenticados pode ser um sonho, pois as diferenças podem ser muito grandes. Não houve mais discussão sobre esta sugestão.

Eu gostaria de sugerir o seguinte:

  1. A complexidade adicional de não exigir a tag antecipadamente parece grave, o cenário de problema correspondente parece incomum e o problema realmente parece muito uma questão de protocolo. Um bom design pode acomodar muito, mas não tudo. Pessoalmente, me sinto confortável deixando isso para o protocolo. (Fortes contra-exemplos são bem-vindos.)
  2. A discussão evoluiu consistentemente para uma implementação flexível e de baixo nível que não protege contra o uso indevido, com exceção da geração IV . Sejamos consistentes. O consenso geral parece ser que uma API de alto nível é um próximo passo importante, vital para o uso adequado pela maioria dos desenvolvedores - é assim que nos livramos de não proteger contra o uso indevido na API de baixo nível . Mas parece que uma dose extra de medo sustentou a ideia de prevenção de uso indevido _na área de geração IV_. No contexto de uma API de baixo nível, e para ser consistente, eu me inclinaria para um equivalente a byte[] . Mas a troca de implementação é mais simples com o INonceProvider injetado. O comentário de @sidshetye é irrefutável ou uma simples implementação de INonceProvider que apenas chama o RNG ainda pode ser considerada compatível?
  3. As abstrações parecem úteis, e tanto esforço foi feito para projetá-las que agora estou convencido de que farão mais bem do que mal. Além disso, APIs de alto nível ainda podem optar por implementar APIs de baixo nível que não estejam em conformidade com as abstrações de baixo nível.
  4. IV é o termo geral, e um nonce é um tipo específico de IV, correto? Isso implora por renomeações de INonceProvider para IIVProvider e de nonceOrIv* para iv* . Afinal, estamos sempre lidando com um IV, mas não necessariamente com um nonce.

A tag inicial não é inicial para o meu cenário, então provavelmente manterei minha própria implementação. O que é bom, não tenho certeza se é a xícara de chá de todos escrever código de alto desempenho nesta área.

O problema é que isso causará latência desnecessária. Você tem que pré-buffar uma mensagem inteira para obter a tag no final para começar a decodificar o quadro. Isso significa que você basicamente não pode sobrepor IO e descriptografia.

Não sei por que é tão difícil permitir isso no final. Mas eu não vou fazer um bloqueio para esta API, ela simplesmente não será de nenhum interesse no meu cenário.

IV é o termo geral, e um nonce é um tipo específico de IV, correto?

Não. Um nonce é um número usado uma vez . Um algoritmo que especifica um nonce indica que a reutilização viola as garantias do algoritmo. No caso do GCM, usar o mesmo nonce com a mesma chave e uma mensagem diferente pode resultar no comprometimento da chave GHASH, reduzindo o GCM a CTR.

De http://nvlpubs.nist.gov/nistpubs/ir/2013/NIST.IR.7298r2.pdf :

Nonce: Um valor usado em protocolos de segurança que nunca é repetido com a mesma chave. Por exemplo, os nonces usados ​​como desafios em protocolos de autenticação desafio-resposta geralmente não devem ser repetidos até que as chaves de autenticação sejam alteradas. Caso contrário, existe a possibilidade de um ataque de repetição. Usar um nonce como um desafio é um requisito diferente de um desafio aleatório, porque um nonce não é necessariamente imprevisível.

Um "IV" não tem os mesmos requisitos rigorosos. Por exemplo, repetir um IV com CBC apenas vaza se a mensagem criptografada é igual ou diferente de uma anterior com o mesmo IV. Não enfraquece o algoritmo.

Um nonce é um número usado uma vez.
Um "IV" não tem os mesmos requisitos rigorosos.

@bartonjs Sim. Eu argumentaria que, como um nonce é usado para inicializar a primitiva criptográfica, é seu vetor de inicialização. Adere perfeitamente a qualquer definição de IV que eu possa encontrar. Tem requisitos mais rigorosos, sim, assim como ser uma vaca tem requisitos mais rigorosos do que ser um animal. A redação atual parece pedir um parâmetro "cowOrAnimal". O fato de que modos diferentes têm requisitos variados do IV não altera o fato de que todos eles estão solicitando alguma forma de IV. Se estiver faltando algo, mantenha a redação atual, mas até onde posso dizer, apenas "iv" ou "IIVProvider" são simples e corretos.

Para se entregar ao nonceOrIv bikeshedding:

O GCM IV de 96 bits às vezes é definido como salt nonce 8 bytes (ex. RFC 5288). A RFC 4106 define o GCM nonce como salt de 4 bytes e $# iv salt de 8 bytes. RFC 5084 (GCM em CMS) diz que CCM leva nonce , GCM leva iv , mas _"... para ter um conjunto comum de termos para AES-CCM e AES-GCM , o AES-GCM IV é referido como um nonce no restante deste documento."_ RFC 5647 (GCM for SSH) diz _"nota: em [RFC5116], o IV é chamado de nonce."_ RFC 4543 ( GCM em IPSec) diz _"nós nos referimos à entrada AES-GMAC IV como um nonce, a fim de distingui-la dos campos IV nos pacotes."_ RFC 7714 (GCM para SRTP) fala sobre um IV de 12 bytes

Dada a completa falta de consistência na maioria das especificações do GCM, nonceOrIv meio que faz sentido. $ 0,02

A tag inicial não é inicial

Assim como outros clientes que se expressam aqui, exigir a tag com antecedência também não é um bom começo para nós. Não há como o .NET processar fluxos simultâneos com essa limitação introduzida artificialmente. Mata totalmente a escalabilidade.

Você pode apoiar a afirmação de que isso adiciona complexidade? Porque na verdade deveria ser trivial. Além disso, nenhuma das implementações de criptografia específicas da plataforma (que você envolverá) tem essa limitação. Especificamente, a razão é que a tag de entrada precisa ser meramente de tempo constante comparada com a tag computada. E a tag computada está disponível somente após o bloco final ter sido descriptografado durante TryFinish . Então, essencialmente, quando você iniciar sua implementação, você descobrirá que está apenas armazenando o tag dentro de sua instância até o TryFinish . Você poderia muito bem tê-lo como uma entrada opcional

public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, 
                             Span<byte> remainingDecryptedData, 
                             out int bytesWritten, 
                             ReadOnlySpan<byte> tag = null); // <==

Também acho que estamos tentando muito normalizar para uma única interface que cobrirá todos os cenários de criptografia. Eu também prefiro interfaces generalizadas, mas nunca à custa de funcionalidade ou escalabilidade - especialmente em uma camada tão fundamental como a biblioteca cryto padrão da própria linguagem. IMHO, se alguém se encontra fazendo isso, geralmente significa que a abstração está com defeito.

Se for necessária uma interface simples e consistente, prefiro a abordagem Java - também levantada anteriormente aqui como opção 1 . Ele também evita a questão acima de marcar primeiro/marcar por último, mantendo-os dentro das implementações do algoritmo (IMHO, como acho que deveria). Minha equipe não está implementando isso, então não é nossa decisão, MAS se tivéssemos que tomar uma decisão e começar a implementar - seguiríamos esse caminho com certeza.

Por favor, evite a interface INonce um simples byte[] ou span<> deve ser suficiente para uma interface de baixo nível compatível.

IV vs Nonce - Caso generalizado é de fato IV. Para GCM, o IV deve ser um Nonce (por exemplo, Car vs RedOrCar). E como estou copiando e colando isso, notei que o @timovzl usou um exemplo muito semelhante :)

@sidshetye Você pode fazer uma proposta precisa de que (1) suporte algoritmos que precisam da tag antecipadamente e (2) só precisa da tag até TryFinish em todas as outras situações?

Suponho que você esteja pensando algo na seguinte direção?

  • A tag em Initialize pode ser nula. Apenas os algoritmos que precisam dele antecipadamente lançarão nulo.
  • A tag em TryFinish é obrigatória ou (alternativamente) pode ser nula para os algoritmos que já a exigiram antecipadamente.

Suponho que o acima apenas adiciona complexidade na forma de documentação e know-how. Para uma API de baixo nível, isso pode ser considerado um pequeno sacrifício, já que documentação e know-how adequados são necessários de qualquer maneira.

Estou começando a me convencer de que isso deve ser possível, para compatibilidade com outras implementações e streaming.

@timovzl Claro, espero reservar algum tempo amanhã para isso.

@Timovzl , acabei tendo tempo hoje e isso acabou sendo a toca do coelho! Isso é longo, mas acho que captura a maioria dos casos de uso, captura os pontos fortes da criptografia .NET ( ICryptoTransform ) enquanto adota a direção .NET Core/Standard ( Span<> ). Eu re-li, mas espero que não haja erros de digitação abaixo. Eu também acho que algumas comunicações em tempo real (chat, conf call etc) são vitais para um brainstorming rápido; Espero que você possa considerar isso.

Modelo de programação

Primeiro falarei sobre o modelo de programação resultante para usuários da API.

Criptografia de streaming

var aesGcm = new AesGcm();
using (var encryptor = aesGcm.CreateAuthenticatedEncryptor(Key, IV, AAD))
{
  using (var cryptoOutStream = new CryptoStream(cipherOutStream, encryptor, CryptoStreamMode.Write))
  {
    clearInStream.CopyTo(cryptoOutStream);
  }
}

Descriptografar streaming

var aesGcm = new AesGcm();
using (var decryptor = aesGcm.CreateAuthenticatedDecryptor(Key, IV, AAD))
{
  using (var decryptStream = new CryptoStream(cipherInStream, encryptor, CryptoStreamMode.Write))
  {
    decryptStream.CopyTo(clearOutStream);
  }
}

Sem streaming

Como o não streaming é um caso especial de streaming, podemos agrupar o código do usuário acima em métodos auxiliares em AuthenticatedSymmetricAlgorithm (definido abaixo) para expor uma API mais simples. ou seja

public class AesGcm : AuthenticatedSymmetricAlgorithm
{
  ...
  // These return only after consuming entire input buffer

  // Code like Streaming Encrypt from above within here
​  public abstract bool TryEncrypt(ReadOnlySpan<byte> clearData, Span<byte> encryptedData);

  // Code like Streaming Decrypt from above within here
  public abstract bool TryDecrypt(ReadOnlySpan<byte> encryptedData, Span<byte> clearData); 
  ...
}

Isso pode dobrar como apresentar uma API mais simples como

Criptografia sem streaming

var aesGcm = new AesGcm(Key, IV, AAD);
aesGcm.TryEncrypt(clearData, encryptedData);
var tag = aesGcm.Tag;

Descriptografia sem streaming

var aesGcm = new AesGcm(Key, IV, AAD);
aesGcm.Tag = tag;
aesGcm.TryDecrypt(encryptedData, clearData);

Sob o capô

Olhando para a fonte do corefx, Span<> está em toda parte. Isso inclui System.Security.Cryptography.* - exceto para cifras simétricas, então vamos corrigir isso primeiro e camada de criptografia autenticada no topo.

1. Crie ICipherTransform para Span I/O

Isso é como uma versão com reconhecimento de Span de ICryptoTransform . Eu apenas mudaria a própria interface como parte da atualização da estrutura, mas como as pessoas podem ficar sensíveis a isso, estou chamando de ICipherTransform .

public partial interface ICipherTransform : System.IDisposable
{
  bool CanReuseTransform { get; }
  bool CanTransformMultipleBlocks { get; } // multiple blocks in single call?
  bool CanChainBlocks { get; }             // multiple blocks across Transform/TransformFinal
  int InputBlockSize { get; }
  int OutputBlockSize { get; }
  int TransformBlock(ReadOnlySpan<byte> inputBuffer, int inputOffset, int inputCount, Span<byte> outputBuffer, int outputOffset);
  Span<byte> TransformFinalBlock(ReadOnlySpan<byte> inputBuffer, int inputOffset, int inputCount);
}

Marque também ICryptoTransform como [Obsolete]

Ser educado com pessoas com conhecimento prévio de criptografia .NET

[Obsolete("See ICipherTransform")]
public partial interface ICryptoTransform : System.IDisposable { ... }

2. Estender a classe SymmetricAlgorithm existente para Span I/O

public abstract class SymmetricAlgorithm : IDisposable
{
  ...
  public abstract ICipherTransform CreateDecryptor(ReadOnlySpan<byte> Key, ReadOnlySpan<byte> IV);
  public abstract ICipherTransform CreateEncryptor(ReadOnlySpan<byte> Key, ReadOnlySpan<byte> IV);
  public virtual ReadOnlySpan<byte> KeySpan {...}
  public virtual ReadOnlySpan<byte> IVSpan {...}
  public virtual ReadOnlySpan<byte> KeySpan {...}
  ...
}

3. Estender CryptoStream existente para Span I/O

Isso é como Stream em System.Runtime. Além disso, adicionaremos um c'tor para o nosso caso AEAD a seguir.

CRÍTICO: CryptoStream precisará de uma atualização obrigatória em FlushFinalBlock para adicionar a tag ao final do fluxo durante a criptografia e extrair automaticamente a tag (bytes TagSize) durante a descriptografia . Isso é semelhante a outras APIs testadas em batalha, como Java's Cryptographic Architecture ou C# BouncyCastle. Isso é inevitável, mas o melhor lugar para fazer isso, pois no streaming a tag é produzida no final, mas não é necessária até que o bloco final seja transformado durante a descriptografia. A vantagem é que simplifica muito o modelo de programação.

Nota: 1) Com o CBC-HMAC, você pode optar por verificar a tag primeiro. É a opção mais segura, mas se for, na verdade o torna um algoritmo de duas passagens. A 1ª passagem calcula a tag HMAC e a 2ª passagem realmente faz a descriptografia. Portanto, o fluxo de memória ou fluxo de rede sempre terá que ser armazenado em buffer na memória, reduzindo-o ao modelo one-shot; não streaming. Algoritmos verdadeiros AEAD como GCM ou CCM podem transmitir com eficiência.

public class CryptoStream : Stream, IDisposable
{
  ...
  public CryptoStream(Stream stream, IAuthenticatedCipherTransform transform, CryptoStreamMode mode);
  public override int Read(Span<byte> buffer, int offset, int count);
  public override Task<int> ReadAsync(Span<byte> buffer, int offset, int count, CancellationToken cancellationToken);
  public override void Write(ReadOnlySpan<byte> buffer, int offset, int count);
  public override Task WriteAsync(ReadOnlySpan<byte> buffer, int offset, int count, CancellationToken cancellationToken);

  public void FlushFinalBlock()
  {
    ...
    // If IAuthenticatedCipherTransform
    //    If encrypting, `TransformFinalBlock` -> `GetTag` -> append to out stream
    //    If decryption, extract last `TagSize` bytes -> `SetExpectedTag` -> `TransformFinalBlock`
    ...
  }

  ...
}

Camada na criptografia autenticada

Com o acima, podemos adicionar os bits ausentes para permitir a Criptografia Autenticada com Dados Associados (AEAD)

Estender o novo ICipherTransform para AEAD

Isso permite que CryptoStream possa fazer seu trabalho corretamente. Também podemos usar a interface IAuthenticatedCipherTransform para implementar nossa própria classe/uso de streaming personalizado, mas trabalhar com CryptoStream cria uma API de criptografia .net super coesa e consistente.

  public interface IAuthenticatedCipherTransform : ICipherTransform
  {
    Span<byte> GetTag();
    void SetExpectedTag(Span<byte> tag);
  }

Classe base de criptografia autenticada

Simplesmente expande SymmetricAlgorithm

public abstract class AuthenticatedSymmetricAlgorithm : SymmetricAlgorithm
{
  ...
  // Program Key/IV/AAD via class properties OR CreateAuthenticatedEn/Decryptor() params
  public abstract IAuthenticatedCipherTransform CreateAuthenticatedDecryptor(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default);
  public abstract IAuthenticatedCipherTransform CreateAuthenticatedEncryptor(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default);
  public virtual Span<byte> AuthenticatedData {...}
  public virtual Span<byte> Tag {...}
  public virtual int TagSize {...}
  ...
}

Classe AES GCM

public class AesGcm : AuthenticatedSymmetricAlgorithm
{
  public AesGcm(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default)

  /* other stuff like valid key sizes etc similar to `System.Security.Cryptography.Aes` */
}

@sidshetye Eu aplaudo o esforço.

Streaming-Encrypt sobre GCM é factível. Streaming-Decrypt sobre GCM é

  • não permitido no NIST 800-38d. A seção 5.2.2 "Função de descriptografia autenticada" é clara que o retorno do texto simples descriptografado P deve implicar a autenticação correta via tag T.
  • não é seguro. Há uma noção de segurança de algoritmos sendo seguros na configuração "Release of Unverified Plaintext" (RUP). A segurança do RUP é formalizada em documento de 2014 por Andreeva-et-al. O GCM não é seguro na configuração do RUP. A competição CAESAR em que cada entrada é comparada com o GCM lista a segurança do RUP como uma propriedade desejável. Texto simples não verificado liberado do GCM é trivialmente propenso a ataques de inversão de bits.

Anteriormente neste tópico, a possibilidade de APIs de criptografia/descriptografia assimétricas foi levantada (conceitualmente), e acho que o consenso foi de que seria uma ideia muito ruim.

Em resumo, você não pode ter uma API de streaming granular de byte de alto nível para descriptografia do GCM. Eu disse isso muitas vezes antes, e estou dizendo de novo. A única maneira de ter uma API de streaming é a criptografia em partes. Vou poupar a todos do carrossel da criptografia em partes.

O que quer que a MS decida fazer para a API do GCM, o RUP não pode ser permitido.

@sdrapkin RUP foi discutido aqui em detalhes e já cruzamos essa ponte. Resumidamente, o RUP implica que os dados descriptografados não precisam ser usados ​​até que a tag seja verificada, mas como Java JCE, WinNT bcrypt, OpenSSL etc, não precisa ser aplicado no limite do método. Como com a maioria dos primitivos de criptografia, especialmente os de baixo nível, use com cautela.

^^^ tanto. Eu concordo com as APIs de fluxo de nível mais alto, etc., então aplico bem. Mas quando quero usar uma primitiva de baixo nível, preciso poder usar coisas como buffers divididos, etc., e cabe a mim garantir que os dados não sejam usados. Lance uma exceção no ponto de cálculo/verificação da tag, mas não limite as primitivas de baixo nível.

cabe a mim garantir que os dados não sejam usados

Errado. Não depende de você. AES-GCM tem uma definição muito _específica_, e essa definição garante que não depende de você. O que você quer é uma primitiva AES-CTR separada e uma primitiva GHASH separada, que você pode combinar e aplicar como achar melhor. Mas não estamos discutindo primitivos AES-CTR e GHASH separados, estamos? Estamos discutindo o AES-GCM. E o AES-GCM requer que o RUP não seja permitido.

Também sugiro revisar a resposta de Ilmari Karonen de crypto.stackexchange .

@sdrapkin Você faz um bom ponto. Seria desejável, no entanto, eventualmente ter um algoritmo que fosse seguro no RUP e que esse algoritmo se encaixasse na API decidida aqui. Então temos que escolher:

  1. A API não suporta streaming. Simples, mas carente de uma API de baixo nível. Podemos nos arrepender disso um dia.
  2. A implementação do AES-GCM impede o streaming, aderindo à especificação sem limitar a API.

Podemos detectar o cenário de streaming e lançar uma exceção que explica por que esse uso está incorreto? Ou precisamos recorrer a fazer com que sua implementação de streaming consuma todo o buffer? O último seria lamentável: você pode pensar que está transmitindo, mas não está.

Poderíamos adicionar um método SupportsStreaming(out string whyNot) que é verificado pela implementação de streaming.

Temos um argumento sólido contra streaming/tag-at-the-end em geral ? Caso contrário, acredito que devemos tentar não impedir isso com a API.

@sdrapkin : Vamos ter uma visão mais ampla do RUP, já que esta é uma biblioteca, não um aplicativo. Portanto, isso é mais um problema de buffer e design de camada do que liberação/uso real de dados não verificados. Olhando para a publicação especial do NIST 800-38D para AES GCM , vemos que

  1. A especificação GCM define o comprimento máximo do texto simples como 2^39-256 bits ~ 64 GB. O buffer em qualquer lugar próximo a isso na memória do sistema não é razoável.

  2. A especificação GCM definiu a saída como FAIL se a tag falhar. Mas não é prescritivo sobre qual camada em uma implementação deve armazenar em buffer até a verificação da tag. Vejamos uma pilha de chamadas como:

A => AESGCM_Decrypt_App(key, iv, ciphertext, aad, tag)
B =>  +- AESGCM_Decrypt_DotNet(key, iv, ciphertext, aad, tag)
C =>    +- AESGCM_Decrypt_OpenSSL(key, iv, ciphertext, aad, tag)

Onde
A é AES GCM na camada de aplicação
B é AES-GCM na camada de linguagem
C é AES-GCM na camada de plataforma

O texto simples é liberado em (A) se a tag for verificada, mas FALHA será retornado caso contrário. No entanto, absolutamente nenhum lugar na especificação sugere que a memória principal é o único lugar para armazenar em buffer o texto simples em andamento, nem que o buffer deve acontecer em (B) ou (C) ou em outro lugar. Na verdade, OpenSSL, Windows NT Bcrypt no exemplo de (C) onde o streaming permite o armazenamento em buffer na camada superior. E Java JCA, CLR Security da Microsoft e minha proposta acima são exemplos de (B) onde o streaming permite buffering na camada de aplicação. É presunçoso supor que os designers de A não têm melhores recursos de buffer antes de liberar o texto simples. Esse buffer na teoria e na prática pode ser memória ou SSDs ou um cluster de armazenamento na rede. Ou cartões perfurados ;) !

Mesmo deixando de lado o buffering, o artigo discute outras preocupações práticas (consulte a Seção 9.1, Considerações de design e 9.2, Considerações operacionais ) como atualização de chaves ou não repetição de IV em falhas indefinidas de energia. Obviamente, não vamos assar isso na camada B, ou seja, aqui.

@timovzl a proposta recente acima aborda os dois cenários - one-shot (o arquiteto não se importa com buffering), bem como streaming (o arquiteto tem melhores recursos de buffer). Contanto que a documentação da API de streaming de baixo nível esteja clara de que o consumidor agora é responsável por armazená-la em buffer, há redução zero da prova de segurança e não há desvio da especificação.

EDIT: gramática, erros de digitação e tentando fazer o markdown funcionar

Bingo .. Novamente é decisão dos projetistas da camada onde acontece a verificação da tag. Não estou defendendo a liberação de dados não verificados para a camada de aplicação.

A discussão volta a ser essas APIs de nível "consumidor" ou elas são verdadeiras Primitivas. Se eles são verdadeiros primitivos, eles devem expor a funcionalidade para que APIs "mais seguras" de nível superior possam ser construídas no topo. Já foi decidido acima com a discussão Nonce que estes devem ser verdadeiros primitivos, o que significa que você pode dar um tiro no pé, acho que o mesmo se aplica à decodificação de texto cifrado de streaming/parcial.

Ao dizer isso, será imperativo fornecer APIs de nível "mais alto" e mais seguras rapidamente para impedir que as pessoas "roleem" suas próprias em cima delas.

Meu interesse vem de redes/pipelines e se você não pode fazer buffers parciais e tem que fazer "one shot", então não haveria nenhum benefício para a desvantagem dessas APIs, então eu continuaria indo diretamente para BCrypt/OpenSsl etc.

Meu interesse vem de redes/pipelines e se você não pode fazer buffers parciais e tem que fazer "one shot", então não haveria nenhum benefício para a desvantagem dessas APIs, então eu continuaria indo diretamente para BCrypt/OpenSsl etc.

Exatamente. A necessidade não desaparecerá, então as pessoas usarão outras implementações ou criarão suas próprias. Isso não é necessariamente um resultado mais seguro do que permitir o streaming com uma boa documentação de aviso.

@Timovzl , acho que obtivemos muitos comentários técnicos e requisitos de design em torno da última proposta . Pensamentos sobre implementação e lançamento?

@Sidshetye fez uma proposta detalhada que, acredito, atende a todos os requisitos. A única crítica, em relação ao RUP, foi abordada sem mais oposição. (Especificamente, o RUP pode ser evitado em uma das várias camadas, e a API de baixo nível não deve ditar qual; e espera-se que oferecer _no_ streaming tenha efeitos piores.)

No interesse do progresso, gostaria de convidar qualquer pessoa com mais preocupações com a última proposta a falar - e, é claro, oferecer alternativas.

Estou entusiasmado com essa proposta e com uma API tomando forma.

@Sidshetye , tenho algumas perguntas e sugestões:

  1. É desejável herdar do SymmetricAlgorithm existente? Existem componentes existentes com os quais desejamos integrar? A menos que eu esteja perdendo alguma vantagem nessa abordagem, prefiro ver AuthenticatedEncryptionAlgorithm sem classe base. Se nada mais, ele evita expor métodos indesejáveis CreateEncryptor / CreateDecryptor (não autenticados!).
  2. Nenhum dos componentes envolvidos é utilizável com criptografia assimétrica, sim? Quase todos os componentes omitem "Symmetric" de seus nomes, algo com o qual concordo. A menos que continuemos herdando SymmetricAlgorithm , AuthenticatedSymmetricAlgorithm poderia ser renomeado para AuthenticatedEncryptionAlgorithm , aderindo ao termo convencional Criptografia Autenticada.
  3. Altere TryEncrypt / TryDecrypt para gravar/receber a tag, em vez de ter uma propriedade Tag configurável no algoritmo.
  4. Qual é o propósito de definir key , iv e authenticatedAdditionalData por meio de setters públicos? Eu evitaria múltiplas abordagens válidas e propriedades mutáveis ​​tanto quanto possível. Você poderia criar uma proposta atualizada sem eles?
  5. Queremos algum estado em AesGcm ? Meu instinto é definitivamente manter de fora iv e authenticatedAdditionalData , pois são por mensagem. O key pode valer a pena ter como estado, pois geralmente queremos fazer várias operações com uma única chave. Ainda assim, é possível receber a chave por chamada também. As mesmas perguntas valem CreateAuthenticatorEncryptor . De qualquer forma, devemos optar por _one way_ para passar os parâmetros. Estou ansioso para discutir prós e contras. Estou inclinado para o estado chave em AesGcm e o resto em CreateAuthenticatedEncryptor ou TryEncrypt respectivamente. Se já estivermos de acordo, mostre-nos uma proposta atualizada. :-)
  6. ICipherTransform provavelmente deve ser uma classe abstrata, CipherTransform , para que os métodos possam ser adicionados sem quebrar as implementações existentes.
  7. Todos os parâmetros de função devem usar camelCase, ou seja, começar em minúsculas. Além disso, devemos dizer authenticatedData ou authenticatedAdditionalData ? Além disso, acho que devemos escolher os nomes dos parâmetros plaintext e ciphertext .
  8. Onde quer que o IV seja passado, gostaria de vê-lo como um parâmetro opcional, tornando mais fácil obter um IV gerado corretamente (criptorandom) do que fornecer o nosso próprio. Torna o uso indevido da API de baixo nível mais difícil, pelo menos, e conseguimos isso de graça.
  9. Ainda estou tentando descobrir como o código do cliente TryEncrypt pode saber o comprimento do intervalo necessário para fornecer ciphertext ! O mesmo para TryDecrypt e o comprimento de plaintext . Certamente não devemos experimentá-los em um loop até o sucesso, dobrando o comprimento após cada iteração com falha?

Por fim, pensando no futuro, como seria uma API de alto nível criada com base nisso? Olhando puramente para o uso da API, parece haver pouco espaço para melhorias, já que as APIs de streaming e não streaming já são tão diretas! As principais diferenças que imagino são um IV automático, tamanhos de saída automáticos e possivelmente um limite na quantidade de dados criptografados.

O Windows permite streaming. O OpenSSL também. Ambos os classificaram principalmente em conceitos existentes (embora ambos tenham jogado uma chave com "e há essa coisa do lado que você tem que lidar ou eu vou errar").

Go não, e libsodium não.

Parece que a primeira onda permitiu, e as posteriores não. Como estamos indiscutivelmente em uma onda posterior, acho que vamos continuar não permitindo isso. Se houver um aumento na demanda por streaming após a introdução de um modelo único (para criptografia / descriptografia, a chave pode ser mantida em chamadas), podemos reavaliar. Portanto, uma proposta de API que siga esse padrão parece benéfica. Embora nem o SIV nem o CCM suportem criptografia de streaming, portanto, a API de streaming para eles é potencialmente muito buffering. Manter as coisas claras parece melhor.

As propostas também não devem incorporar a tag na carga útil (GCM e CCM a chamam como um dado separado), a menos que o próprio algoritmo (SIV) a incorpore na saída da criptografia. ( E(...) => (c, t) vs E(...) => c || t ou E(...) => t || c ). Os usuários da API certamente podem usá-la como concat (basta abrir os Spans adequadamente).

A especificação GCM não permite a liberação de nada além de FAIL em caso de incompatibilidade de tags. O NIST é bastante claro sobre isso. O documento GCM original de McGrew & Viega também diz:

A operação de descriptografia retornaria FAIL em vez do texto simples, e a decapsulação seria interrompida e o texto simples seria descartado em vez de encaminhado ou processado posteriormente.

Nenhum dos comentários anteriores abordou o RUP - eles apenas o descartaram ("a camada superior cuidará disso" - sim, certo).

É simples: a criptografia GCM pode transmitir. A descriptografia do GCM não pode transmitir. Qualquer outra coisa não é mais GCM.

Parece que a primeira onda permitiu, e as posteriores não. Como estamos indiscutivelmente em uma onda posterior, acho que vamos continuar não permitindo isso.

@bartonjs você está literalmente ignorando todas as análises técnicas e lógicas e, em vez disso, usando as datas dos projetos Go e libsodium como um proxy fraco para a análise real? Imagine se eu fizer um argumento semelhante com base nos nomes dos projetos. Além disso, estamos decidindo sobre a interface E as implementações. Você percebe que decidir sobre uma interface sem streaming para o AEAD impede todas essas implementações no futuro, certo?

Se houver um aumento na demanda por streaming após a introdução de um modelo único (para criptografia / descriptografia, a chave pode ser mantida em chamadas), podemos reavaliar.

Por que a demanda demonstrada até agora no GitHub é insuficiente? Está chegando ao ponto em que parece totalmente caprichoso suportar ter que fazer menos trabalho do que qualquer mérito técnico ou de demanda do cliente.

@bartonjs você está literalmente ignorando todas as análises técnicas e lógicas e, em vez disso, usando as datas dos projetos Go e libsodium como um proxy fraco para a análise real?

Não, estou usando o conselho de criptógrafos profissionais que dizem que é extraordinariamente perigoso e que devemos evitar o streaming AEAD. Então, estou usando informações da equipe do CNG de "muitas pessoas dizem que querem na teoria, mas na prática quase ninguém faz isso" (não sei quanto disso é telemetria versus anedótico de solicitações de assistência em campo). O fato de que outras bibliotecas seguiram o caminho único simplesmente _reforça_ a decisão.

Por que a demanda demonstrada até agora no GitHub é insuficiente?

Alguns cenários foram mencionados. O processamento de buffers fragmentados provavelmente poderia ser resolvido com a aceitação de ReadOnlySequence , se parecer que há um cenário suficiente para justificar a complicação da API em vez de fazer com que o chamador faça a remontagem dos dados.

Arquivos grandes são um problema, mas arquivos grandes já são um problema, pois o GCM tem um limite de apenas 64 GB, o que "não é tão grande" (ok, é muito grande, mas não é o "uau, isso é grande" que costumava ser). Os arquivos mapeados na memória permitiriam que Spans (de até 2^31-1) fossem utilizados sem exigir 2 GB de RAM. Então, reduzimos alguns pedaços do máximo... isso provavelmente aconteceria com o tempo de qualquer maneira.

Você percebe que decidir sobre uma interface sem streaming para o AEAD impede todas essas implementações no futuro, certo?

Estou cada vez mais convencido de que @GrabYourPitchforks estava certo (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891) de que provavelmente não há uma interface unificadora sensata. GCM _exigindo_ um nonce/IV e SIV _proibindo_ significa que a inicialização de um modo/algoritmo AEAD já requer conhecimento sobre o que vai acontecer... não há realmente uma noção "abstrata" para AEAD. SIV dita onde "a etiqueta" vai. GCM/CCM não. SIV é tag-first, por especificação.

O SIV não pode começar a criptografar até que tenha todos os dados. Portanto, sua criptografia de streaming será lançada (o que significa que você precisa saber para não chamá-lo) ou buffer (o que pode resultar em n ^ 2 tempo de operação). O CCM não pode iniciar até que o comprimento seja conhecido; mas o CNG não permite uma dica de pré-criptografia no comprimento, então está no mesmo barco.

Não devemos projetar um novo componente onde seja mais fácil fazer a coisa errada do que a certa por padrão. A descriptografia de streaming torna muito fácil e tentador conectar uma classe Stream (como sua proposta para fazer isso com CryptoStream), o que torna muito fácil obter um bug de validação de dados antes que a tag seja verificada, o que anula quase inteiramente o benefício do AE . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "espere, isso não é XML legal..." => oráculo de texto cifrado adaptável) .

Está chegando ao ponto... a demanda do cliente.

Como, infelizmente, já ouvi muitas vezes na vida: desculpe, mas você não é o cliente que temos em mente. Admito que talvez você saiba como fazer o GCM com segurança. Você sabe apenas transmitir para um arquivo/buffer/etc volátil até depois da verificação da tag. Você sabe o que significa gestão nonce e conhece os riscos de errar. Você sabe prestar atenção aos tamanhos de fluxo e cortar para um novo segmento GCM após 2^36-64 bytes. Você sabe que depois de tudo dito e feito, o problema é seu se você errar essas coisas.

O cliente que tenho em mente, por outro lado, é alguém que sabe "tenho que criptografar isso" porque seu chefe mandou. E eles sabem que ao pesquisar como fazer criptografia, algum tutorial disse "sempre use AE" e menciona GCM. Então eles encontram um tutorial de "criptografia em .NET" que usa CryptoStream. Eles então conectam o pipeline, sem ter a menor ideia de que fizeram a mesma coisa que escolher SSLv2... marcaram uma caixa na teoria, mas não na prática. E quando eles fazem isso _esse_ bug pertence a todos que sabiam melhor, mas deixe a coisa errada ser muito fácil de fazer.

você não é o cliente que temos em mente [...] O cliente que tenho em mente, por outro lado, é alguém que sabe "tenho que criptografar isso" porque seu chefe lhe disse para [...]

@bartonjs meses atrás, já tínhamos decidido que o objetivo era atingir dois perfis de clientes por meio de uma API de baixo nível (poderosa, mas insegura sob certas condições) e uma API de alto nível (infalível). Está até no título. É certamente um país livre, mas é insincero agora mudar a meta alegando o contrário.

O cliente que tenho em mente, por outro lado, é alguém que sabe "tenho que criptografar isso" porque seu chefe mandou. E eles sabem que ao pesquisar como fazer criptografia, algum tutorial disse "sempre use AE" e menciona GCM. Então eles encontram um tutorial de "criptografia em .NET" que usa CryptoStream. Eles então conectam o pipeline, sem ter a menor ideia de que fizeram a mesma coisa que escolher SSLv2... marcaram uma caixa na teoria, mas não na prática. E quando eles fazem isso, esse bug pertence a todos que sabiam melhor, mas deixe a coisa errada ser muito fácil de fazer.

@bartonjs Espere, o que aconteceu com um primitivo de baixo nível? Eu pensei que o objetivo desta edição em particular era a flexibilidade em relação à babá. Definitivamente, deixe-nos saber se o plano mudou, para que todos falemos da mesma coisa.

Além disso, os métodos por bloco ainda estão sendo considerados ou apenas métodos one-shot?

Estou cada vez mais convencido de que @GrabYourPitchforks estava certo (#23629 (comentário)) de que provavelmente não há uma interface unificadora sensata.

Vendo todos os exemplos, isso começa a parecer cada vez mais fútil - especialmente para uma API de baixo nível onde as implementações têm restrições tão diferentes. Talvez devêssemos apenas dar um exemplo sólido com o AES-GCM, em vez de uma interface unificadora. Como nota lateral, o último ainda pode ser interessante para uma futura API de alto nível. Sua propriedade de ser mais restritiva provavelmente tornará uma interface unificadora muito mais fácil de alcançar lá.

os métodos por bloco ainda estão sendo considerados, ou apenas métodos one-shot?

Conforme mencionado em https://github.com/dotnet/corefx/issues/23629#issuecomment -378605071, sentimos que o risco versus recompensa versus casos de uso expressos dizem que devemos permitir apenas versões únicas do AE.

Eu não li toda a discussão, apenas partes aleatórias. Não sei em que direção você está indo. Desculpe se o que escrevo não faz sentido neste contexto. Meus 2¢:

  • Os fluxos são importantes. Se você não puder suportá-los diretamente porque isso significaria vulnerabilidade de segurança, então, se possível, forneça um wrapper de nível superior construído em cima de sua API de baixo nível que exponha fluxos (de maneira ineficiente, mas segura).
  • Se o AES-GCM não puder usar fluxos em nenhum cenário, forneça uma implementação legítima do AES-CBC-HMAC baseada em fluxos. Ou algum outro algoritmo AE.
  • Quanto maior o nível melhor. Quanto menos áreas para cometer um erro pelo usuário, melhor. Significado — expor a API que esconderia o máximo de coisas possível (por exemplo, esta tag de autenticação). É claro que também pode (deveria) haver sobrecargas mais específicas.
  • O IMHO não se preocupa em unificar interfaces com outros serviços de criptografia se eles simplesmente não se encaixam. Isso é o que o openssl fez com sua CLI e o resultado é ruim (por exemplo, não há possibilidade de fornecer a tag de autenticação).

Nós (a equipe de segurança .NET) conferimos entre nós e com a equipe de criptografia mais ampla da Microsoft. Discutimos muitos dos problemas e preocupações mencionados neste tópico. Em última análise, essas preocupações não foram persuasivas o suficiente para justificar a introdução de uma API de streaming do GCM como um bloco de construção principal dentro da estrutura.

Esta decisão pode ser revista no futuro se for necessário. E, enquanto isso, não tornamos as coisas piores do que são hoje: os desenvolvedores que estão atualmente usando uma biblioteca de criptografia de terceiros para streaming de suporte ao GCM podem continuar a fazê-lo e não serão prejudicados pela introdução pretendida de um API GCM sem streaming.

Como lidar com a criptografia de dados que não cabem na memória?

@pgolebiowski Você usa bibliotecas de criptografia .NET de alto nível projetadas especificamente para oferecer criptografia de streaming segura.

@sdrapkin isso é mais fácil dizer do que fazer. "seguro" é pedir muito. o que está lá que é comprovado e pode realmente ser confiável? você mesmo diz:

Biblioteca Bouncy Castle c# (uma recomendação típica do StackOverflow). Bouncy Castle c# é um enorme (145k LOC), catálogo de museu de criptografia de baixo desempenho (algumas delas antigas), com antigas implementações Java portadas para .NET igualmente antigo (2.0?).

ok, então quais são as opções? talvez sua própria biblioteca? não realmente . hmm... talvez libsodium-net? não realmente .

quando você realmente procura por uma biblioteca auditada que vem de uma fonte bastante confiável (como a Microsoft ou amplamente usada pela comunidade), eu não acho que tal biblioteca exista no mundo .NET Core.


  • cliente: criptografia autenticada de dados que não cabem na memória?
  • microsoft: Desculpe, mas você não é o cliente que temos em mente. use uma biblioteca que não seja auditada e sua segurança seja questionável, não é nosso problema se você estiver sob ataque de canal lateral.
  • cliente:

@pgolebiowski As opções são usar uma estrutura .NET estabelecida - ou seja. a mesma coisa que foi comprovada e pode ser confiável, como você deseja. Outras bibliotecas (incluindo a minha) esperam que a Microsoft adicione primitivas de criptografia ausentes, como ECDH, no NetStandard.

Você também pode olhar para os garfos Inferno. Existem pelo menos 2 forks onde algumas mudanças triviais alcançaram o NetStandard20.

Sua biblioteca foi auditada há 2 anos em 2 dias por um cara . Não confio nisso, desculpe. Na Microsoft, haveria uma equipe dedicada para isso -- de pessoas em quem confio mais do que as do Cure53.

Sério, podemos falar sobre suporte de terceiros para muitas coisas. Mas todo o material necessário relacionado à segurança deve ser fornecido pela biblioteca padrão.

@pgolebiowski Longe de mim tentar convencer alguém a confiar em qualquer coisa, mas sua declaração não é precisa. O Inferno foi auditado por 2 profissionais da organização "Cure53". A auditoria levou 2 dias e toda a biblioteca tinha aproximadamente 1.000 linhas de código. Isso é ~ 250 linhas de código por auditor/dia - bastante gerenciável.

De fato, a facilidade de auditoria está entre as principais características do Inferno, justamente para quem não quer confiar.

O objetivo deste segmento é adicionar suporte para AES-GCM. Sua biblioteca nem mesmo suporta AES-GCM. Se você quiser que as pessoas usem seu código, envie-o como uma proposta para o corefx. Em um tópico apropriado.

Outra coisa, mesmo que ele suporte esse algoritmo - ele não foi revisado pela placa de criptografia .net e não faz parte do corefx. Nem sequer é um candidato para tal revisão. Isso significa o fim dessa discussão e publicidade fúteis.

@pgolebiowski Não anunciei nada - apenas respondi sua pergunta, sugeri alternativas para o seu caso de uso e corrigiu reivindicações imprecisas. O AES-GCM não é adequado para criptografia de streaming, e isso foi revisado e acordado pela equipe de segurança do .NET, então você pode confiar nesse .

estou defendendo o streaming AES-GCM em qualquer lugar? não consigo lembrar. mas pode se lembrar de dizer:

  • Os fluxos são importantes. Se você não puder suportá-los diretamente porque isso significaria vulnerabilidade de segurança, então, se possível, forneça um wrapper de nível superior construído em cima de sua API de baixo nível que exponha fluxos (de maneira ineficiente, mas segura).
  • Se o AES-GCM não puder usar fluxos em nenhum cenário, forneça uma implementação legítima do AES-CBC-HMAC baseada em fluxos. Ou algum outro algoritmo AE.

ou declarando um problema aberto para o corefx:

Como lidar com a criptografia de dados que não cabem na memória?


bônus:

Você também pode olhar para os garfos Inferno. Existem pelo menos 2 forks onde algumas mudanças triviais alcançaram o NetStandard20. [...] Inferno foi auditado por 2 profissionais da organização "Cure53" [...] a facilidade de auditoria está entre as principais características do Inferno, justamente para quem não quer confiar.

não anunciei nada

Apenas como um aparte eu nunca quis "streaming" no sentido que a maioria está pensando. Eu só queria processamento de "bloqueio" por motivos de desempenho e uso de memória. Na verdade, não quero "transmitir" os resultados. Isso é o que o suporte openssl e cng parece uma pena perder isso e basicamente torna os "primativos" inúteis em qualquer cenário que eu possa pensar.

@Drawaes Pensando bem, a operação de bloco pode ser muito mais segura do que usar fluxos. Um leigo pode tocar em streams, mas ele prefere usar a API one-shot do que a operação de bloco. Além disso, a operação de bloco não pode _straightforwardly_ ser combinada com, digamos, XmlReader . Então, na verdade, muitos dos perigos discutidos se aplicam a objetos de fluxo, mas não à operação de bloqueio.

Trabalhar com operação de bloco quando uma API one-shot também está disponível, sugere que sabemos o que estamos fazendo e que exigimos especificamente ajustes de baixo nível. Poderíamos proteger o leigo e ter flexibilidade.

Quanto a evitar o RUP, ainda estou pensando em quanto uma operação de bloco de vantagem realmente é para o GCM. A criptografia colhe todos os benefícios, enquanto a descriptografia beneficia apenas um pouco. Podemos evitar armazenar o texto cifrado completo, mas ainda devemos armazenar em buffer o texto simples completo. Um decodificador _poderia_ escolher armazenar o texto simples intermediário no disco. Mas, em troca, introduzimos mais espaço para erros. Temos um argumento convincente para não resolver esse problema em um nível mais alto (por exemplo, um pedaço ou usar um algoritmo verdadeiramente de streaming)?

TLS e pipelines. Atualmente (e para o futuro previsível) os pipelines usam blocos de 4k, mas uma mensagem tls pode ter 16k de texto cifrado. Com blocos, você pode ter, digamos, 4 ou 5 e talvez seja necessário armazenar em buffer até 16 bytes para garantir blocos concorrentes.

@Drawaes 16k ainda é constante e não enorme. Faz muita diferença neste contexto?

Sim, significa outra cópia no pipeline. Isso tem um efeito importante e mensurável no desempenho.

O que é necessário para que isso aconteça? Quais são os próximos passos? @Drawaes

Quanto ao AES-GCM, acho que sua entrega está prejudicada devido ao problema correspondente estar bloqueado: https://github.com/dotnet/corefx/issues/7023. @blowdart , você poderia desbloquear? É muito difícil ter progresso quando as pessoas não podem discutir. Ou, se isso não for uma opção, talvez propor uma solução alternativa que permita trazer esse recurso ao público.

Não, eu não estou desbloqueando isso. Uma decisão foi tomada, o tema está feito.

@blowdart Obrigado pela resposta. Eu entendo que talvez isso não tenha sido claro o suficiente:

Ou, se isso não for uma opção, talvez propor uma solução alternativa que permita trazer esse recurso ao público.

Eu aprecio que haja uma decisão para apoiar o AES-GCM. Isso é ótimo, eu definitivamente quero esse algoritmo. Assim, agora seria legal tê-lo realmente suportado. Você gostaria que a discussão sobre o projeto e implementação do AES-GCM fosse realizada aqui ou em uma nova edição?

Além disso, se esse tópico estiver concluído, por que não fechá-lo? E torná-lo mais explícito alterando o título dessa edição, pois agora sugere que a discussão sobre a implementação seria realizada aqui: https://github.com/dotnet/corefx/issues/7023. Talvez algo como Decida qual algoritmo AEAD suportar primeiro .

Em outras palavras: dou feedback de que, na situação atual, não está claro o que é necessário para impulsionar o AES-GCM.

@karelz

@pgolebiowski Já saiu um PR. Provavelmente estará disponível na master quarta-feira da próxima semana.

Esta página foi útil?
0 / 5 - 0 avaliações