Runtime: Primitivo general de bajo nivel para cifrados (AES-GCM es el primero)

Creado en 29 ago. 2017  ·  143Comentarios  ·  Fuente: dotnet/runtime

Razón fundamental

Hay una necesidad general de varios cifrados para el cifrado. La combinación actual de interfaces y clases se ha vuelto un poco inconexa. Además, no hay soporte para cifrados de estilo AEAD, ya que necesitan la capacidad de proporcionar información de autenticación adicional. Los diseños actuales también son propensos a la asignación y estos son difíciles de evitar debido a las matrices que regresan.

API propuesta

Una clase base abstracta de propósito general que será implementada por clases concretas. Esto permitirá la expansión y también al tener una clase en lugar de métodos estáticos, tenemos la capacidad de crear métodos de extensión y mantener el estado entre llamadas. La API debe permitir el reciclaje de la clase para permitir asignaciones más bajas (sin necesidad de una nueva instancia cada vez y para capturar, por ejemplo, claves no administradas). Debido a la naturaleza a menudo no administrada de los recursos que se rastrean, la clase debe 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);
}

Ejemplo de uso

(la fuente de entrada/salida es un flujo mítico basado en intervalos como la fuente IO)

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);
}

Comportamiento de la API

  1. Si se llama a get tag antes de finalizar, debe lanzarse un [¿tipo de excepción?] y el estado interno debe establecerse como no válido
  2. Si la etiqueta no es válida al finalizar para descifrar, debe ser una excepción lanzada
  3. Una vez que haya terminado, se lanzará una llamada a cualquier cosa que no sea uno de los métodos Init.
  4. Una vez que se llama a Init, se lanzará una segunda llamada sin "terminar".
  5. Si el tipo espera que se proporcione una clave (una instancia directa "nueva"), si la llamada inicial "Init" solo tiene un IV, arrojará
  6. Si el tipo se generó, por ejemplo, a partir de una clave basada en la tienda e intenta cambiar la clave a través de Init y no solo el IV, arrojará
  7. Si no se llama a get tag antes de dispose o Init, ¿debería lanzarse una excepción? ¿Para evitar que el usuario no recopile la etiqueta por accidente?

Referencia dotnet/corefx#7023

Actualizaciones

  1. Nonce cambiado a IV.
  2. Sección de comportamiento añadida
  3. Se eliminaron los casos de intervalo de entrada/salida única de finalización y actualización, solo pueden ser métodos de extensión
  4. Cambió una cantidad de tramos a readonlyspan como lo sugirió @bartonjs
  5. Se eliminó Restablecer, se debe usar Init con IV en su lugar
api-suggestion area-System.Security

Comentario más útil

@bartonjs , ¿está literalmente ignorando todo el análisis técnico y lógico y, en su lugar, está utilizando las fechas de los proyectos Go y libsodium como un proxy débil para el análisis real?

No, estoy siguiendo el consejo de criptógrafos profesionales que dicen que es extraordinariamente peligroso y que debemos evitar transmitir AEAD. Luego estoy usando información del equipo de CNG de "muchas personas dicen que lo quieren en teoría, pero en la práctica casi nadie lo hace" (no sé cuánto de eso es telemetría versus anecdótico de solicitudes de asistencia de campo). El hecho de que otras bibliotecas hayan tomado la ruta de una sola vez simplemente _refuerza_ la decisión.

¿Por qué la demanda demostrada hasta ahora en GitHub es insuficiente?

Se han mencionado algunos escenarios. El procesamiento de búferes fragmentados probablemente podría abordarse aceptando ReadOnlySequence , si parece que hay suficiente escenario para justificar la complicación de la API en lugar de que la persona que llama vuelva a ensamblar los datos.

Los archivos grandes son un problema, pero los archivos grandes ya son un problema ya que GCM tiene un límite de apenas 64 GB, que es "no tan grande" (está bien, es bastante grande, pero no es el "vaya, eso es grande" que solía ser). Los archivos asignados a la memoria permitirían utilizar Spans (de hasta 2^31-1) sin necesidad de 2 GB de RAM. Así que hemos recortado un par de bits del máximo... eso probablemente sucederá con el tiempo de todos modos.

Se da cuenta de que decidirse por una interfaz sin transmisión para AEAD excluye todas esas implementaciones en el futuro, ¿verdad?

Cada vez estoy más convencido de que @GrabYourPitchforks tenía razón (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891) de que probablemente no haya una interfaz unificadora sensata. GCM _requiriendo_ un nonce/IV y SIV _prohibiendo_ significa que la inicialización de un modo/algoritmo AEAD ya requiere conocimiento sobre lo que va a suceder... no hay realmente una noción "abstraída" para AEAD. SIV dicta dónde va "la etiqueta". GCM/CCM no. SIV es la etiqueta primero, por especificación.

SIV no puede comenzar a cifrar hasta que tenga todos los datos. Por lo tanto, su cifrado de transmisión se lanzará (lo que significa que debe saber que no debe llamarlo) o se almacenará en el búfer (lo que podría resultar en n ^ 2 de tiempo de operación). CCM no puede comenzar hasta que se conozca la duración; pero CNG no permite una pista preencriptada sobre la longitud, por lo que está en el mismo barco.

No deberíamos diseñar un nuevo componente en el que sea más fácil hacer lo incorrecto que lo correcto por defecto. El descifrado de transmisión hace que sea muy fácil y tentador conectar una clase Stream (al estilo de su propuesta para hacerlo con CryptoStream), lo que hace que sea muy fácil obtener un error de validación de datos antes de que se verifique la etiqueta, lo que anula casi por completo el beneficio de AE . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "espera, eso no es XML legal..." => oráculo de texto cifrado adaptable) .

Está llegando al punto... la demanda del cliente.

Como, lamentablemente, he escuchado demasiadas veces en mi vida: lo siento, pero usted no es el cliente que tenemos en mente. Reconozco que tal vez sepa cómo hacer GCM de forma segura. Sabe que solo debe transmitir a un archivo/búfer/etc. volátil hasta después de la verificación de la etiqueta. Sabe lo que significa la gestión de nonce y conoce los riesgos de equivocarse. Debe prestar atención a los tamaños de transmisión y pasar a un nuevo segmento de GCM después de 2^36-64 bytes. Sabes que después de que todo está dicho y hecho, es tu error si te equivocas en esas cosas.

El cliente que tengo en mente, por otro lado, es alguien que sabe "tengo que encriptar esto" porque su jefe se lo dijo. Y saben que al buscar cómo hacer el cifrado, algún tutorial decía "siempre use AE" y menciona GCM. Luego encuentran un tutorial de "cifrado en .NET" que usa CryptoStream. Luego conectan la canalización, sin tener idea de que acaban de hacer lo mismo que elegir SSLv2... marcaron una casilla en teoría, pero no realmente en la práctica. Y cuando lo hacen, ese error pertenece a todos los que sabían mejor, pero deja que lo incorrecto sea demasiado fácil de hacer.

Todos 143 comentarios

Comentarios rápidos para juzgar sobre la API propuesta (intentando ser útil):

  • Span<T> no está en NetStandard2 .
  • "Nonce" es muy específico de la implementación, es decir. huele a GCM. Sin embargo, incluso los documentos de GCM (p. ej., NIST SP800-38D) se refieren a él como "IV", que, en el caso de GCM, pasa a ser un nonce. Sin embargo, en el caso de otras implementaciones de AEAD, el IV no tiene que ser un nonce (por ejemplo, repetir IV bajo CBC+HMAC no es catastrófico).
  • La transmisión de AEAD debería funcionar a la perfección con CryptoStream o proporcionar su propio "AEADCryptoStream", que es tan fácil de transmitir como CryptoStream.
  • Se debe permitir que las implementaciones de API de AEAD realicen derivaciones de claves internas basadas en AAD (datos asociados). El uso de AAD únicamente para el cálculo/verificación de etiquetas es demasiado restrictivo e impide un modelo AEAD más sólido.
  • Los métodos "Get*" deberían devolver algo (GetTag). Si están vacíos, deben estar configurando algo/cambiando de estado.
  • Intentar obtener una etiqueta antes de "terminar" probablemente sea una mala idea, por lo que "IsFinished" podría ser útil.
  • Las personas que diseñaron ICryptoTransform pensaron en la reutilización, la compatibilidad con múltiples bloques y tamaños de bloques de entrada/salida de diferentes tamaños. Estas preocupaciones no se capturan.

Como prueba de la cordura de la API AEAD, la primera implementación de dicha API propuesta no debe ser AES-GCM, sino el AES-CBC clásico/predeterminado con una etiqueta HMAC. La razón simple de esto es que cualquiera puede construir la implementación AES-CBC+HMAC AEAD hoy , con clases .NET simples, existentes y bien conocidas. Hagamos que un viejo y aburrido [AES-CBC+HMAC] trabaje primero con las nuevas API de AEAD, ya que es fácil para todos el MVP y la prueba de manejo.

El problema de los nombres de nonce/IV era algo sobre lo que estaba indeciso, feliz con un cambio a IV, así que cambiará.

En cuanto a los métodos Get que devuelven algo, esto evita cualquier asignación. Podría haber una sobrecarga Get() que devuelva algo. Tal vez requiera un cambio de nombre, pero estoy bastante casado con la idea de que toda la API debe estar básicamente libre de asignación.

En cuanto a las transmisiones, etc., no me molesta demasiado, ya que son API de nivel superior que se pueden construir fácilmente a partir de las primitivas de nivel inferior.

No se debe permitir obtener una etiqueta antes de que haya terminado, sin embargo, debe saber cuándo ha llamado a finalizar, por lo que no estoy seguro de que deba estar en la API, sin embargo, debe ser un comportamiento definido, así que actualicé el diseño de la API para incluir una sección de comportamiento para que podamos capturar cualquier otro que se piense.

En cuanto a qué cifrado, no creo que ningún cifrado específico deba ser el único objetivo, para probar una nueva API de propósito general, debe ajustarse a un número. AES GCM y CBC deben estar cubiertos.

(¡todos los comentarios sobre el tema, buenos o malos, siempre son útiles!)

  • ¿Clase o interfaz?
  • ¿Cómo interactúan con esto las clases actuales de SymmetricAlgorithm, si es que lo hacen?
  • ¿Cómo se usaría esto para claves persistentes, como lo pueden hacer TripleDESCng y AesCng?
  • Muchos de estos intervalos parecen que podrían ser ReadOnlySpan.

@Drawaes gracias por poner en marcha esta API. Algunos pensamientos:

  1. La generación y verificación de etiquetas es una parte muy importante de esta API, ya que el mal uso de las etiquetas puede frustrar todo el propósito. Si es posible, me gustaría ver etiquetas integradas en las operaciones de inicialización y finalización para garantizar que no se puedan ignorar accidentalmente. Eso probablemente implica que cifrar y descifrar no deberían usar los mismos métodos de inicialización y finalización.
  2. Tengo sentimientos encontrados acerca de generar bloques durante el descifrado antes de llegar al final, ya que los datos no son confiables hasta que se haya verificado la etiqueta (lo que no se puede hacer hasta que se hayan procesado todos los datos). Tendremos que evaluar esa compensación con mucho cuidado.
  3. ¿Es necesario restablecer? ¿Debe terminar solo reiniciar? Hacemos eso en hashes incrementales (pero no necesitan nuevos IV)

@bartonjs

  1. Class, como se ha visto muchas veces en la BCL con una interfaz que no puedes expandir más tarde sin romperlo todo. Una interfaz es como un cachorro de por vida... a menos que los métodos de interfaz predeterminados puedan considerarse como una solución a ese problema.
    Además, una clase sellada de un tipo abstracto es en realidad más rápida (a partir de ahora) porque el jitt puede desvirtualizar los métodos ahora ... Así que es básicamente gratis. el envío de la interfaz no es tan bueno (sigue siendo bueno pero no tan bueno)
  2. No sé, ¿cómo te gustaría que funcionara? Tengo poco interés en las cosas actuales, ya que es tan confuso que simplemente parchearía todos los algoritmos modernos razonables directamente (dejaría 3DES en Las otras clases :) Pero no tengo todas las respuestas, ¿tiene alguna otra idea sobre esto?
  3. Las claves persistentes deberían ser fáciles. Cree un método de extensión en el método clave o almacén de persistencia.
MyKeyStore.GetCipher();

No está inicializado. Es desechable, por lo que cualquier referencia se puede dejar caer con un patrón desechable normal. Si intentan configurar la clave, arrojan una excepción de operación no válida.

Sí a los tramos de solo lectura que ajustaré cuando no esté en el tubo de mi teléfono.

@morganbr no hay problema... Solo quiero ver que suceda más que nada ;)

  1. ¿Puede dar un fragmento de código sobre cómo ve que funciona? No estoy seguro, pero el código siempre aporta claridad.
  2. Es desafortunado, pero realmente tienes que escupir los bloques temprano. Con hmac y hashing, no tiene, pero no tiene datos provisionales, solo el estado. Entonces, en este caso, tendría que almacenar en búfer una cantidad desconocida de datos. Echemos un vistazo al ejemplo de canalizaciones y TLS. Podemos escribir 16k de texto sin formato, pero los búferes de canalización tienen hoy el tamaño de una página de 4k. Entonces, en el mejor de los casos, querríamos cifrar/descifrar 4*4k. ¿Si no me das la respuesta hasta el final, necesitas asignar memoria interna para almacenar todo eso y luego supongo que la tiraré cuando obtenga el resultado? O lo borrarás. ¿Qué sucede si descifro 10 mb y conserva esa memoria después de que ahora tenga que preocuparme por el uso de la memoria latente?
  3. No estoy 100% en lo de init/reset (no son sus ideas, mi forma de API actual) no me sienta bien, ¡así que estoy abierto a una nueva sugerencia!

Tengo poco interés en las cosas actuales, ya que es tan confuso que simplemente parchearía todos los algoritmos modernos razonables directamente (deje 3DES en Las otras clases :)

El problema sería que los formatos de contenedor como EnvelopedCms (o EncryptedXml) pueden necesitar trabajar con 3DES-CBC, AES-CBC, etc. Alguien que quiera descifrar algo cifrado con ECIES/AES-256-CBC-PKCS7/HMAC-SHA-2 -256 probablemente no sentiría que están haciendo cosas viejas y toscas.

Si se supone que solo es para AE, eso debería reflejarse en algún lugar del nombre. En este momento es un "cifrado" genérico (que en algún momento iba a sentarme con un diccionario/glosario y averiguar si hay una palabra para "un algoritmo de cifrado en un modo de operación", ya que creo que "cifrado" == "algoritmo", por lo tanto "Aes").

:) Simplemente estaba señalando que no es mi área temática o que no es de mucho interés para mí, así que estoy dispuesto a ceder ante usted y la comunidad sobre este tema. No he pensado en las implicaciones de esto.


Después de escanear rápidamente a través de estos, una opción es permitirles tomar una instancia de la clase "Cifrado" o como se llame. Es posible que esto no se haga en la primera ola, pero podría seguirlo rápidamente. Si la API es súper eficiente, no veo ninguna razón por la que deban hacer lo suyo internamente y es exactamente el caso de uso de esta API.

Como una barra lateral en el nombramiento... Sin embargo, debo admitir que es difícil.
Openssl = cifrado
rubí = cifrado
Go = paquete de cifrado con interfaces tipo pato para AEAD, etc.
Java = cifrado

Ahora soy todo por ser diferente pero... hay una tendencia. Si algo mejor es posible, eso es genial.

¿Posiblemente "BlockModeCipher"...?

He hecho algunos cambios, cambiaré el nombre si se decide por un nombre mejor.

Cuando comencé a intentar responder preguntas, me di cuenta de que a la API ya le falta la diferenciación de cifrado/descifrado, por lo que, en su ejemplo, no sabe si cifrar o descifrar datos. Obtener eso podría agregar algo de claridad.

Podría imaginar un par de formas en que la API podría imponer el uso adecuado de etiquetas (basado en el supuesto de que se trata de una API AEAD, no solo de cifrado simétrico, ya que ya tenemos SymmetricAlgorithm/ICryptoTransform/CryptoStream). No tome esto como prescriptivo, solo como un ejemplo de cómo hacer cumplir el etiquetado.
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 clase:

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 {}
}

Dicho esto, si no se almacena en el búfer en el descifrado, ¿es práctico asegurarse de que el descifrado realmente finalice y se verifique la etiqueta? ¿Puede Update de alguna manera darse cuenta de que es hora de verificar? ¿Es eso algo que Dispose debería hacer? (Eliminar es un poco peligroso ya que es posible que ya haya confiado en los datos cuando elimine el objeto)

En cuanto a la denominación, nuestro precedente es SymmetricAlgorithm y AsymmetricAlgorithm. Si esto está destinado a AEAD, algunas ideas podrían ser AuthenticatedSymmetricAlgorithm o AuthenticatedEncryptionAlgorithm.

Algunos pensamientos e ideas 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 , gracias por aportar pensamientos. ¿Dónde deben ir las etiquetas en su API? ¿Son implícitamente parte del texto cifrado? Si es así, eso implica un protocolo que algunos algoritmos en realidad no tienen. Me gustaría entender qué protocolos podrían interesar a las personas para ver si la colocación implícita de etiquetas es aceptable o si debe llevarse por separado.

@morganbr También noté el problema de encriptar/desencriptar, pero no tuve tiempo esta noche para solucionarlo, así que me alegro por tu diseño. Prefiero los métodos sobre la clase, ya que permite un mejor reciclaje agresivo (los búferes para las claves y los IV pueden acumularse).

En cuanto a un control antes de la disposición. Desafortunadamente, no es posible saber el final de una operación.

Las interfaces @sdrapkin diría que no funcionan debido al problema de la versión mencionado anteriormente. A menos que confiemos en la implantación predeterminada en el futuro. Además, el envío de la interfaz es más lento. Los segmentos de la matriz también son nuestros, ya que el intervalo es la primitiva inferior más versátil. Sin embargo, se podrían agregar métodos de extensión para que se amplíe el segmento de matriz si hubiera demanda más adelante.

Algunas de sus propiedades son de interés, por lo que se actualizarán cuando esté en una computadora en lugar de en mi teléfono.

Buena retroalimentación en general!

@morganbr Las etiquetas son parte del texto cifrado. Esto se basa en la API de CAESAR (que incluye AES-GCM).

@Drawaes He usado interfaces solo para ilustrar pensamientos: estoy perfectamente de acuerdo con los métodos/clases estáticos. Lapsono existe. No me importa lo que pueda o no venir: no está en NetStandard2, y no está en el .NET normal que los proyectos serios realmente usan (sí, sí, sé que está en Core, pero Core es un juguete por ahora). Estaría feliz de considerar personalmente a Spancuando lo veo - hasta entonces ArraySegmentes la API de NetStandard más cercana que se distribuye actualmente.

Echaré un vistazo más profundo a la API de CAESAR que es útil.

En cuanto a Span, creo que se envía alrededor del período 2.1, que es de esperar que sea al mismo tiempo que se enviaría la primera implementación de esta API (o al menos lo antes posible).

Si echa un vistazo al paquete nuget preliminar actual, es compatible con .net estándar 1.0 y no hay planes para cambiar eso en el lanzamiento.

Tal vez @stephentoub pueda confirmarlo mientras está trabajando para agregar API basadas en Span en todo el marco mientras hablamos.

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

Así que diría que es la única opción real para una API nueva. Luego, se pueden agregar métodos de extensión, etc. para tomar un ArraySegment si así lo desea y si es lo suficientemente útil, entonces podría agregarse al marco, pero es trivial convertir un ArraySegment en un Span, pero la otra forma requiere copiar datos.

El problema que veo con esa API anterior es que será un desastre para el rendimiento de cualquier dato "fragmentado". Digamos, por ejemplo, el tráfico de red, si un solo bloque autenticado se divide en varias lecturas de un flujo existente, necesito almacenarlo todo en una sola [insertar estructura de datos] y cifrar/descifrar todo a la vez. Derrota todos los intentos de hacer cualquier tipo de copia cero de esos datos.

Los marcos de trabajo en red como los que proporciona Pipelines logran evitar casi todas las copias, pero luego, si encuentran algún tipo de criptografía en esta API, todo eso desaparece.

El objeto de configuración separado (o bolsa) en realidad ha estado involucrado en una discusión reciente sobre otra API que he estado teniendo. En principio, no me opongo, ya que si crece en el futuro, puede convertirse en un desastre tener una gran cantidad de propiedades en el objeto principal.

Se me han ocurrido un par de cosas.

  • La propuesta actual llama a TagSize un valor de salida (bueno, una propiedad de solo obtener). Pero tanto para GCM como para CCM es una entrada para el cifrado (y derivable del descifrado ya que proporcionó la etiqueta real).
  • La propuesta asume que la entrada y la salida pueden ocurrir al mismo tiempo y por partes.

    • IIRC CCM no puede realizar el cifrado de transmisión (la longitud del texto sin formato es una entrada en el primer paso del algoritmo).

    • Los modos acolchados retrasan el descifrado por (al menos un) bloque, ya que hasta que se llama a Final no saben si vendrán más datos / si el bloque actual necesita que se elimine el relleno

  • Algunos algoritmos pueden considerar que el elemento AD es necesario al comienzo de la operación, lo que lo hace más parecido a un parámetro Init/ctor que a una asociación de enlace en tiempo de ejecución.

No sé si los formatos de contenedor (EnvelopedCms, EncryptedXml) necesitarían extraer la clave, o si simplemente dependería de ellos generarla y recordarla (durante el tiempo que necesiten escribirla).

(Aparentemente no presioné el botón "comentario" ayer, por lo que no reconocerá nada después de "He realizado algunos cambios" en 1910Z)

El tamaño real de la etiqueta tendría que ser variable. Acordado.

Si solo miramos el cifrado por ahora, para simplificar el caso de uso. Tiene razón en que algunos cifrados no devolverán nada, menos o más. Existe una pregunta general sobre qué sucede si no proporciona un búfer lo suficientemente grande.

En las nuevas interfaces de TextEncoding que usan span, se sugirió que el tipo de devolución fuera una enumeración para definir si había suficiente espacio para generar o no, y el tamaño realmente escrito en un parámetro "out" en su lugar. Esta es una posibilidad.

En el caso de CCM, solo diría que no devuelve nada y tendrá que almacenar en búfer internamente hasta que llame a finalizar, momento en el que querría volcar todo el lote. Nada le impide simplemente llamar a finish como su primera llamada si tiene todos los datos en un solo bloque (en cuyo caso podría haber un nombre mejor). O es posible lanzar si intenta una actualización de esos cifrados. CNG devuelve un error de tamaño no válido si intenta hacer una continuación en CCM, por ejemplo.

En cuanto a cuándo la etiqueta está configurada en el descifrado, a menudo no lo sabe hasta que haya leído el paquete completo, si tomamos TLS como ejemplo, es posible que tengamos que leer 8 * 2k paquetes de red para llegar a la etiqueta al final. de un bloque de 16k. Entonces, ahora tenemos que almacenar en búfer los 16k completos antes de que podamos comenzar el descifrado y no hay posibilidad de superposición (no estoy diciendo que esto se usaría para TLS, solo que un proceso enlazado IO es común para este tipo de cifrados, ya sea disco o red).

@Drawaes re. flujos fragmentados y límites de almacenamiento en búfer:
Tienes que elegir tus batallas. No podrá crear una API unificadora alineada con todos los objetivos agradables de tener en el mundo AE, y hay muchos de esos objetivos. Ex. hay una transmisión AEAD fragmentada decente en Inferno , pero no es un estándar de ninguna manera, y tal estándar no existe. En un nivel superior, el objetivo es "canales seguros" (consulte this , this y this ).

Sin embargo, tenemos que pensar en algo más pequeño por ahora. La fragmentación/la limitación del búfer ni siquiera están en el radar para los esfuerzos de estandarización (sección " AEAD con textos sin formato grandes ").

Las operaciones de cifrado/descifrado se tratan fundamentalmente de transformaciones. Estas transformaciones requieren búferes y no están en su lugar (los búferes de salida deben ser más grandes que los búferes de entrada, al menos para la transformación Encrypt).

RFC 5116 también podría ser de interés.

@Drawaes , es interesante que menciones TLS. Yo diría que SSLStream (si usara esta API) no debe devolver ningún resultado no autenticado a una aplicación, ya que la aplicación no tendrá forma de defenderse.

Claro, pero ese es el problema de SSLStreams. Hice un prototipo de esto exactamente (gestioné TLS a nivel de protocolo llamando a CNG y OpenSSL para los bits criptográficos) en la parte superior de las tuberías. La lógica era bastante simple, ingresan datos cifrados, descifran el búfer en su lugar, los adjuntan al límite de salida y repiten hasta llegar a la etiqueta. En la llamada de la etiqueta terminada...

Si tira cerrar la tubería. Si no arroja, enjuague permitiendo que la siguiente etapa funcione en el mismo hilo o mediante el envío.

Mi prueba de concepto no estaba lista para el horario de máxima audiencia, pero al usar esto y evitar muchas copias, etc., mostró un aumento de rendimiento muy decente;)

El problema con cualquier red es cuando las cosas en la tubería comienzan a asignar sus propios búferes y no usan tanto como sea posible los que ya se están moviendo a través del sistema.

La cripta de OpenSsl y CNG tienen este mismo método Actualizar, Actualizar, Finalizar. Finalizar puede generar la etiqueta como se discutió. Las actualizaciones deben estar en tamaño de bloque (para CNG) y para OpenSsl hace un almacenamiento en búfer mínimo para llegar a un tamaño de bloque.

Como son primitivos, no estoy seguro de que esperemos una funcionalidad de mayor nivel de ellos. Si estuviéramos diseñando una API de nivel de "usuario" en lugar de primitivas para construirlas, entonces argumentaría que la generación de claves, la construcción de IV y los fragmentos autenticados completos deberían implementarse, así que supongo que depende de cuál sea realmente el nivel objetivo de esta API.

Botón equivocado

@blowdart , quien tuvo algunas ideas interesantes sobre la gestión de nonce.

Entonces, la administración de nonce es básicamente un problema del usuario y es específico de su configuración.

Así que... que sea un requisito. Debe conectar la administración de nonce... y no proporcionar una implementación predeterminada, ni ninguna implementación en absoluto. Esto, más que un simple

cipher.Init(myKey, nonce);

obliga a los usuarios a hacer un gesto específico para que comprendan los riesgos.

La idea de @blowdart podría ayudar tanto con los problemas de gestión de nonce como con las diferencias entre algoritmos. Estoy de acuerdo en que probablemente sea importante no tener una implementación integrada para garantizar que los usuarios entiendan que la administración de nonce es un problema que deben resolver. ¿Cómo se ve algo así?

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
}

Pero, ¿cuál es el punto de INonceProvider? Es solo una interfaz/tipo adicional, si el Init solo toma ninguno y es necesario llamarlo antes de iniciar cualquier bloque, ¿no es lo mismo sin una interfaz adicional?

Además, no soy un experto en criptografía, pero AES no requiere un IV (¿Qué no es un nonce pero debe ser proporcionado por el usuario?)

Es solo una interfaz/tipo adicional

Ese es el punto. Esencialmente dice que la _administración de nonce_ es un problema, no solo pasar una matriz de bytes cero o incluso aleatoria. También podría ayudar a evitar la reutilización inadvertida si las personas interpretan GetNextNonce como "devolver algo diferente a lo que hiciste la última vez".

También es útil no necesitarlo para algoritmos que no tienen problemas de administración (como AES SIV o quizás AES+CBC+HMAC).

Los requisitos exactos de IV/nonce varían según el modo. Por ejemplo:

  • AES ECB no requiere un nonce o IV
  • AES GCM requiere un nonce de 96 bits que nunca debe reutilizarse o se rompe la seguridad de la clave. La baja entropía está bien siempre que no se reutilice el nonce.
  • AES CBC requiere un IV de 128 bits que debe ser aleatorio. Si el IV se repite, solo revela si el mismo mensaje se ha enviado antes.
  • AES SIV no necesita un IV explícito ya que lo deriva de otras entradas.

AES CBC necesita IV ¿verdad? Entonces, ¿vas a tener un InitializationVectorProvider? No es un momento, pero
nonce me gusta y reutilizar el último bloque condujo a un ataque tls porque se puede predecir el iv. No puede usar explícitamente, digamos, un nonce secuencial para CBC.

Sí, pero un IV no es un nonce, por lo que no puede tener el término proveedor nomce

No quise dar a entender que AES CBC no necesita una vía intravenosa, sí. Solo quise especular sobre algunos esquemas que derivan el IV de otros datos.

Claro, supongo que mi punto es que me gusta en general ... Puedo agrupar el proveedor;) pero llamarlo proveedor iv o tener 2 interfaces para aclarar la intención

@morganbr INonceProvider fábricas pasadas a constructores de cifrado son un mal diseño. Pasa completamente por alto el hecho de que _nonce_ no existe por sí mismo: la restricción "_...used once_" tiene un _context_. En los casos de los modos CTR y GCM (que usa CTR), el _contexto_ del _nonce_ es la _clave_. Es decir. _nonce proveedor_ debe devolver un nonce que se usa solo una vez dentro de un contexto de una _clave_ específica.

Dado que INonceProvider en su API propuesta no reconoce claves, no puede generar nonces correctos (aparte de la aleatoriedad, que no es lo que es un nonce, incluso si el espacio de bits era lo suficientemente grande como para que funcione la aleatoriedad estadística). sin peligro).

No estoy del todo seguro de lo que este hilo de discusión pretende lograr. Se discuten varias ideas de diseño de cifrado autenticado... ok. ¿Qué pasa con las interfaces de cifrado autenticado ya integradas en .NET Core, específicamente en su API ASP.NET? Hay IAuthenticatedEncryptor , etc. Todas estas capacidades ya están implementadas, son extensibles y se envían como parte de .NET Core hoy. No digo que la criptografía de protección de datos sea perfecta, pero ¿el plan es ignorarlos? ¿Cámbialos? ¿Asimilar o refactorizar?

La criptografía de protección de datos fue creada por @GrabYourPitchforks (Levi Broderick). Él conoce el tema y su opinión/entrada/retroalimentación sería muy valiosa para esta comunidad. Disfruto del entretenimiento con temas criptográficos tanto como cualquiera, pero si alguien quiere tomarse en serio el diseño de la API criptográfica, entonces se deben contratar expertos reales que ya están en el equipo de MS.

@sdrapkin , los proveedores nonce que necesitan ser conscientes de las claves es un buen punto. Me pregunto si hay una forma razonable de modificar estas API para hacer cumplir eso.

DataProtection es una buena API, pero es una construcción de nivel superior. Encapsula la generación y gestión de claves, los IV y el protocolo de salida. Si alguien necesita usar (digamos) GCM en una implementación de un protocolo diferente, DataProtection no lo hace posible.

El equipo de criptografía de .NET incluye a @bartonjs , @blowdart y a mí. Por supuesto, si @GrabYourPitchforks quiere intervenir, es más que bienvenido.

Estoy de acuerdo con @morganbr en que se supone que esto es un primitivo de bajo nivel (de hecho, lo dice en el título). Si bien la protección de datos, etc., está diseñada para usarse directamente en el código de usuario y reduce la capacidad de dispararse en el pie, la forma en que veo esta primitiva es permitir que el marco y las bibliotecas construyan construcciones de nivel superior sobre una base común.

Con ese pensamiento en mente, el proveedor está bien si debe proporcionarse cada vez que se proporciona una clave. Lo hace un poco complicado, déjame explicarte el uso de TLS (es un uso bien conocido de los modos de bloque AES para el tráfico de red, eso es todo).

Obtengo un "marco" (tal vez más de 2 + TU con el MTU ~ 1500 de Internet). Contiene el nonce (o parte del nonce con 4 bytes "ocultos"). Luego tengo que establecer este valor en un "proveedor" de shell y luego llamar a decrypt y pasar por mi ciclo de descifrado de los búferes para obtener un solo texto sin formato .

Si estás contento con eso, puedo vivir con eso. Estoy ansioso por hacer que esto avance, así que estoy ansioso por actualizar el diseño anterior a algo en lo que podamos estar de acuerdo.

Gracias por bifurcar la discusión, tener algo de tiempo libre para saltar a esto. @Drawaes , ¿puede confirmar/actualizar la publicación principal como estándar de oro/objetivo de esta conversación en evolución? Si no, ¿puedes actualizarlo?

Veo que la propuesta actual tiene un problema fatal y luego otros problemas con ser demasiado habladora.

// 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);
}

Si observa una verdadera primitiva AEAD, los datos de privacidad y los datos autenticados se combinan en pasos de bloqueo. Vea esto para Auth Data 1 y CipherText1. Por supuesto, esto continúa para varios bloques, no solo para 1.highlighted

Como todo el mundo es un meme, no puedo resistirme, lo siento :)
Can't resist

Además, la API parece hablar con new, init, update, etc. Propondría el modelo de este 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. Por lo general, AAD << texto sin formato, por lo que he visto cipher.Init(mykey, nonce, aad); donde todo el AAD se pasa como un búfer y luego el cifrado procesa el resto de la secuencia potencialmente gigabyte+. (por ejemplo , el parámetro CipherModeInfo de BCryptEncrypts ). Además, el tamaño de myKey ya establece AES128, 192, 256, sin necesidad de otro parámetro.
  2. Init se convierte en una API opcional en caso de que la persona que llama desee reutilizar la clase existente, las constantes AES existentes y omitir la generación de subclaves AES si la clave AES es la misma
  3. La API de cipher debería proteger a la persona que llama de las funciones internas de administración del tamaño del bloque, como la mayoría de las otras API criptográficas o incluso las API .NET existentes. La persona que llama se preocupa por el tamaño del búfer optimizado para su caso de uso, por ejemplo, E/S de red a través de más de 16 000 búferes). Demostración con un número primo > 16K para probar los supuestos de implementación
  4. inputSpan es de solo lectura. Y entrada. Así que necesito un outSpan
  5. Update () ¿Cifra o descifra? Solo tenga interfaces de cifrado y descifrado para que coincidan con el modelo mental de un desarrollador. tag es también el dato deseado más importante en este instante, devuélvelo.

En realidad, yendo un paso más allá, ¿por qué no simplemente

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

Además, aléjate de INonceProvider y cosas por el estilo. Las primitivas criptográficas no necesitan esto, solo apégate a byte[] iv (mi favorito para datos pequeños) o Span (en mi humilde opinión, la supuesta nueva genialidad pero demasiada abstracción). El proveedor de Nonce opera en una capa superior y el resultado podría ser el iv que se ve aquí.

El problema de hacer primitivos tan primitivos es que la gente simplemente los usará incorrectamente. Con un proveedor, al menos podemos obligar a pensar en su uso.

Estamos hablando de AEAD en general de la cual GCM es específico. Primero, el caso generalizado ( iv ) debe impulsar el diseño, no el caso específico ( nonce ).

En segundo lugar, ¿cómo se soluciona el problema del nonce simplemente cambiando de byte[] iv a GetNextNonce(Span<byte> writeNonceHere) ? Solo ha cambiado el nombre/la etiqueta del problema y, al mismo tiempo, lo ha hecho más complejo de lo que debería ser.

En tercer lugar, dado que nos estamos metiendo en las políticas de protección de iv , ¿deberíamos entrar también en las políticas de protección clave? ¿Qué sucede con las políticas de distribución de claves? Esas son obviamente preocupaciones de alto nivel.

Finalmente, nonce es extremadamente situacional en el uso en capas superiores. No desea tener una arquitectura frágil donde las preocupaciones entre capas se unen.

Francamente, si pudiéramos ocultar los primitivos a menos que alguien haga un gesto para decir que sé lo que estoy haciendo, presionaría por eso. Pero no podemos. Hay demasiadas implementaciones criptográficas malas porque la gente pensó "Oh, esto está disponible, lo usaré". Diablos, mire AES en sí mismo, solo lo usaré sin HMAC.

Quiero ver que las API sean seguras de forma predeterminada, y si eso significa un poco más de dolor, entonces, francamente, estoy totalmente de acuerdo. El 99 % de los desarrolladores no saben lo que están haciendo cuando se trata de criptografía y facilitarle las cosas al 1 % que sí lo sabe debería ser una prioridad menor.

El intervalo no existe. No me importa lo que venga o no, no está en NetStandard2

@sdrapkin como @Drawaes señala que Span<T> es .NET Standard 1.0 , por lo que se puede usar en cualquier marco. También es más seguro que ArraySegment<T> ya que solo le permite acceder a la ventana real a la que se hace referencia; en lugar de toda la matriz.

También ReadOnlySpan<T> evita la modificación de esa ventana; nuevamente, a diferencia del segmento de matriz donde todo lo que se pasa puede modificar y/o retener una referencia a la matriz pasada.

Span debería ser la opción general para las API de sincronización (el hecho de que una API que use Span también pueda hacer frente a la memoria nativa apilada, así como a las matrices, es la guinda)

es decir
Con ArraySegment, el solo lectura se sugiere a través de documentos; y no se evitan lecturas/modificaciones fuera de los límites

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
    );

Sin embargo, con Span, la api aplica la función de solo lectura; así como lecturas fuera de los límites de las matrices que se impiden

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
    );

Transmite mucho mejor la intención con los parámetros; y es menos propenso a errores con respecto a las lecturas/escrituras fuera de los límites.

@benaadams @Drawaes nunca dijo que Span<T> estaba en NetStandard ( cualquier NetStandard enviado ). Lo que sí dijo es (1) estar de acuerdo en que Span<T> no está en ningún NetStandard enviado; (2) que Span<T> se _"enviará alrededor del marco de tiempo 2.1"_.

Para este problema particular de Github, sin embargo, (solo lectura) Span<T> la discusión se está desvaneciendo en este momento: no hay claridad sobre el alcance o el propósito de la API que se diseñará.

O vamos con la API AEAD primitiva de bajo nivel sin procesar (por ejemplo, similar a CAESAR):

  • Pros: buen ajuste para AES-GCM/CCM, vectores de prueba existentes de buenas fuentes (NIST, RFC). @sidshetye estará feliz. @blowdart meditará sobre _"hacer que los primitivos sean tan primitivos"_, pero finalmente verá el Yin y el Yang porque los primitivos son primitivos y no hay forma de protegerlos de los niños.
  • Contras: los usuarios expertos (el proverbial 1 %) usarán las API de bajo nivel de manera responsable, mientras que los otros usuarios no expertos (99 %) las usarán incorrectamente para escribir software .NET roto que será responsable de la gran mayoría de .NET. CVE, lo que contribuirá en gran medida a la percepción de que .NET es una plataforma insegura.

O vamos con la API AEAD de alto nivel de uso indebido imposible o resistente:

  • Ventajas: el 99 % de los usuarios no expertos seguirán cometiendo errores, pero al menos no en el código AEAD. El _"Quiero que las API sean seguras de forma predeterminada"_ de @blowdart resuena profundamente en el ecosistema, y ​​la seguridad, la prosperidad y el buen karma recaen sobre todos. Muchos buenos diseños e implementaciones de API ya están disponibles.
  • Desventajas: Sin estándares. Sin vectores de prueba. No hay consenso sobre si AEAD es el objetivo correcto para la API de transmisión en línea de alto nivel (spoiler: no lo es; consulte el artículo de Rogaway ).

O hacemos ambas cosas. O entramos en parálisis de análisis y también podríamos cerrar este problema ahora mismo.

Siento firmemente que, al ser parte del lenguaje central, la criptografía debe tener una API fundamental sólida y de bajo nivel. Una vez que tenga eso, la creación de API de alto nivel o API de "ruedas de entrenamiento" puede ser superada rápidamente por el núcleo o la comunidad. Pero desafío a cualquiera a que haga lo contrario con elegancia. ¡Además, el tema es " Primitiva general de bajo nivel para cifrados "!

@Drawaes , ¿hay una línea de tiempo para converger y resolver esto? ¿Algún plan para involucrar a personas que no sean de Microsoft más allá de las alertas de GitHub? ¿Como una conferencia telefónica de 30 minutos? Estoy tratando de mantenerme fuera de la madriguera del conejo, pero estamos apostando a que la criptografía de .NET core estará en un cierto nivel de madurez y estabilidad... por lo que puede clasificarse para tales discusiones.

Todavía estamos prestando atención y trabajando en esto. Nos reunimos con la Junta de Criptografía de Microsoft (el conjunto de investigadores y otros expertos que asesoran el uso de la criptografía de Microsoft) y @bartonjs tendrá más información para compartir pronto.

Basándonos en algunos garabatos de flujo de datos y el consejo de Crypto Board, se nos ocurrió lo siguiente. Nuestro modelo era GCM, CCM, SIV y CBC+HMAC (tenga en cuenta que no estamos hablando de hacer SIV o CBC+HMAC en este momento, solo que queríamos probar la forma).

```C#
interfaz pública INonceProvider
{
Intervalo de solo lecturaGetNextNonce(int nonceSize);
}

clase pública abstracta AuthenticatedEncryptor: IDisposable
{
public int NonceOrIVSizeInBits { get; }
public int TagSizeInBits { get; }
public bool SupportsAssociatedData { get; }
ReadOnlySpan públicoLastNonceOrIV { obtener; }
ReadOnlySpan públicoÚltima etiqueta { obtener; }

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.

}

clase pública sellada AesGcmEncryptor: AuthenticatedEncryptor
{
público AesGcmEncryptor(ReadOnlySpankeySize, INonceProvider nonceProvider)
: base (128, verdadero, 96)
{
}
}

clase pública sellada AesCcmEncryptor: AuthenticatedEncryptor
{
público AesCcmEncryptor(
Intervalo de solo lecturallave,
int nonceSizeInBits,
INonceProvider nonceProvider,
int tagSizeInBits)
: base(tagSizeInBits, true, nonceSizeInBits)
{
validar nonceSize y tagSize contra la especificación del algoritmo;
}
}

clase pública abstracta AuthenticatedDecryptor: IDisposable
{
público abstracto bool TryDecrypt(
Intervalo de solo lecturaetiqueta,
Intervalo de solo lecturanonceOrIV,
Intervalo de solo lecturadatos cifrados,
Intervalo de solo lecturadatos asociados,
Lapsodatos,
out int bytesEscritos);

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.

}

clase pública sellada AesGcmDecryptor: AuthenticatedDecryptor
{
público AesGcmDecryptor(ReadOnlySpanclave) => lanzar nulo;
}

clase pública sellada AesCcmDecryptor: AuthenticatedDecryptor
{
público AesCcmDecryptor(ReadOnlySpanclave) => lanzar nulo;
}
```

Esta propuesta elimina la transmisión de datos. Realmente no tenemos mucha flexibilidad en ese punto. La necesidad del mundo real (baja) combinada con los riesgos asociados (extremadamente alta para GCM) o la imposibilidad de la misma (CCM) significa que simplemente se ha ido.

Esta propuesta utiliza una fuente externalizada de nonce para el cifrado. No tendremos implementaciones públicas de esta interfaz. Cada aplicación/protocolo debe hacer su propia vinculación de la clave con el contexto para que pueda alimentar las cosas de manera adecuada. Si bien cada llamada a TryEncrypt solo realizará una llamada a GetNextNonce, no hay garantía de que ese TryEncrypt en particular tenga éxito, por lo que aún depende de la aplicación comprender si eso significa que debe volver a intentarlo. Para CBC+HMAC, crearíamos una nueva interfaz, IIVProvider, para evitar confundir la terminología. Para SIV se construye el IV, por lo que no hay un parámetro aceptable; y según la especificación, el nonce (cuando se usa) parece considerarse solo como parte de los datos asociados. Entonces, SIV, al menos, sugiere que tener nonceOrIV como parámetro para TryEncrypt no es generalmente aplicable.

TryDecrypt definitivamente arroja una etiqueta no válida. Solo devuelve falso si el destino es demasiado pequeño (según las reglas de los métodos Try-)

Cosas que definitivamente están abiertas para comentarios:

  • Los tamaños deben estar en bits (como partes significativas de las especificaciones) o bytes (dado que solo los valores de %8 son legales de todos modos, y siempre vamos a dividir, y algunas de las partes de las especificaciones hablan de cosas como el tamaño de nonce en bytes )?
  • Nombres de parámetros y ordenación.
  • Las propiedades LastTag/LastNonceOrIV. (Hacer que sean Spans (escribibles) en un TryEncrypt público solo significa que hay tres búferes que podrían ser demasiado pequeños, al hacer que se queden a un lado, el "Probar" es más claro; la clase base puede prometer que lo hará nunca ofrezca un búfer demasiado corto).
  • Ofreciendo un algoritmo AE para el que esto no funciona.
  • ¿Se debe mover associatedData al final con un valor predeterminado de ReadOnlySpan<byte>.Empty ?

    • ¿O sobrecargas hechas que lo omiten?

  • ¿Alguien quiere afirmar amor u odio por los métodos de devolución byte[] ? (Se puede lograr una asignación baja usando el método Span, esto es solo por conveniencia)
  • Los métodos de rangos de tamaño estaban más o menos pegados al final.

    • Su propósito es que

    • Si el intervalo de destino es inferior al mínimo, devuelve falso inmediatamente.

    • Los métodos de devolución byte[] asignarán un búfer de max, y luego Array. Cambie el tamaño según sea necesario.

    • Sí, para GCM y CCM min=max=input.Length , pero eso no es cierto para CBC+HMAC o SIV

    • ¿Hay algún algoritmo que deba tener en cuenta la longitud de los datos asociados?

Definitivamente bytes, no bits.
El proveedor de Nonce que no reconoce la clave es un gran error.

El proveedor de Nonce que no reconoce la clave es un gran error.

Puede escribir su proveedor nonce como quiera. No estamos proporcionando ninguno.

¿Qué pasa con la limpieza determinista/ IDisposable ?

¿Qué pasa con la limpieza determinista/IDisposable?

Buena llamada. Se agregó a AuthenticatedEncryptor/AuthenticatedDecryptor. No creo que deban probar la disponibilidad en el proveedor de nonce, la persona que llama puede simplemente apilar las declaraciones de uso.

INonceProvider concepto/propósito no tiene sentido para mí (haciéndose eco de otros). Deje que las primitivas sean primitivas: pase el nonce de la misma manera que pasa la clave (es decir, como bytes, sin importar cómo se declare). Ninguna especificación AE/AEAD fuerza un algoritmo sobre cómo se generan/derivan los nonces; esta es una responsabilidad de nivel superior (al menos en el modelo let-primitives-be-primitive).

¿Sin transmisión? ¿En serio? ¿Cuál es la justificación para eliminar por la fuerza la transmisión de un cifrado de flujo como AES-GCM en un nivel fundamental básico?

Por ejemplo, ¿qué recomienda su tablero criptográfico en estos dos escenarios recientes que revisamos?

  1. El cliente tiene grandes archivos de atención médica entre 10 y 30 GB. El núcleo solo ve un flujo de datos entre dos máquinas, por lo que es un flujo de un solo paso. Obviamente, se emite una nueva clave para cada archivo de 10 GB, pero acaba de inutilizar todos esos flujos de trabajo. Ahora quiere que a) guardemos esos datos en el búfer (memoria, sin canalización) b) realicemos el cifrado (¡todas las máquinas en la canalización ahora están inactivas!) c) escribamos los datos (el primer byte escrito después de a y b es 100% hecho) ? Por favor, dime que estás bromeando. Ustedes están volviendo a poner "el cifrado es una carga" en el juego a sabiendas.

  2. La unidad de seguridad física tiene múltiples flujos 4K que también están encriptados para escenarios en reposo. La emisión de claves nuevas ocurre en el límite de 15 GB. ¿Propone almacenar en búfer todo el clip?

No veo ningún aporte de la comunidad, de personas que realmente creen software del mundo real, que soliciten eliminar el soporte de transmisión. Pero luego el equipo desaparece del diálogo de la comunidad, se reúne internamente y luego regresa con algo que nadie pidió, algo que elimina las aplicaciones reales y refuerza que "el cifrado es lento y costoso, ¿omitirlo?"

Puede proporcionar Encrypt y EncryptFinal que respaldarían ambas opciones en lugar de imponer su decisión a todo el ecosistema.

El diseño elegante elimina la complejidad, no el control.

¿Cuál es la justificación para eliminar por la fuerza la transmisión de un cifrado de flujo como AES-GCM en un nivel fundamental básico?

Creo que fue algo como

Esta propuesta elimina la transmisión de datos. Realmente no tenemos mucha flexibilidad en ese punto. La necesidad del mundo real (baja) combinada con los riesgos asociados (extremadamente alta para GCM) o la imposibilidad de la misma (CCM) significa que simplemente se ha ido.

GCM tiene demasiados momentos en los que permite la recuperación de claves. Si un atacante puede hacer un texto cifrado elegido y ver la salida de transmisión antes de la verificación de la etiqueta, puede recuperar la clave. (O eso me dice uno de los criptoanalistas). Efectivamente, si cualquier dato procesado por GCM es observable en algún momento antes de la verificación de la etiqueta, entonces la clave está comprometida.

Estoy bastante seguro de que Crypto Board recomendaría NO usar GCM para el primer escenario, sino CBC + HMAC.

Si su segundo escenario es un marco de 4k y está encriptando cada marco de 4k, entonces eso funciona con este modelo. Cada marco de 4k + nonce + etiqueta se descifra y verifica antes de que recupere los bytes, por lo que nunca perderá el flujo de claves o la clave.

A modo de comparación: actualmente estoy desarrollando esta API criptográfica "dejar que los primitivos sean primitivos". Aquí está mi clase para el cifrado autenticado.

A mí me resultó útil poder hablar de una primitiva criptográfica independientemente de una clave. Por ejemplo, a menudo quiero conectar una primitiva específica a un método que funcione con cualquier algoritmo AEAD y dejar la generación de claves, etc. a ese método. Por lo tanto, hay una clase AeadAlgorithm y una clase Key separada.

Otra cosa muy útil que ya evitó varios errores es usar distintos tipos para representar datos de diferentes formas, por ejemplo, una Clave y un Nonce , en lugar de usar un simple byte[] o Span<byte> para todo.


API AeadAlgorithm (haga clic para ampliar)

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 tiene razón, debe confiar en que el programa no genera hasta la autenticación. Entonces, por ejemplo, si no se está autenticando (o simplemente no todavía), puede controlar la entrada de un bloque y, por lo tanto, conocer la salida y trabajar hacia atrás desde allí...

Por ejemplo, un hombre en el ataque medio puede inyectar bloques conocidos en un flujo cbc y realizar un ataque clásico de volteo de bits.

No estoy seguro de cómo resolver el problema de grandes cantidades de datos, en realidad, gracias a fragmentarlos con nonces en serie o similares ... ala TLS.

Bueno, permítanme reformular lo que hago, pero solo en el caso de tamaños pequeños de red, lo que no es suficiente para una biblioteca de propósito general.

En aras de la franqueza, ¿es posible revelar quién está en la Junta de Revisión de Criptografía de Microsoft (e idealmente los comentarios/opiniones de miembros específicos que revisaron este tema)? Brian LaMacchia y quien mas?

_usando psicología inversa:_

Estoy feliz de que la transmisión AEAD esté fuera. Esto significa que Inferno continúa siendo el único AEAD de transmisión basado en CryptoStream práctico para el Joe promedio. ¡Gracias Junta de revisión de MS Crypto!

Sobre la base del comentario de @ektrah , su (¿ella?) enfoque está impulsado por RFC 5116 , al que me he referido anteriormente. Hay muchas citas notables en RFC 5116:

3.1. Requisitos de la generación Nonce
Es esencial para la seguridad que los nonces se construyan de manera que respeten el requisito de que cada valor de nonce sea distinto para cada invocación de la operación de cifrado autenticada, para cualquier valor fijo de la clave.
...

  1. Requisitos de las especificaciones del algoritmo AEAD
    Cada algoritmo AEAD DEBE aceptar cualquier nonce con una longitud entre N_MIN y N_MAX octetos, inclusive, donde los valores de N_MIN y N_MAX son específicos de ese algoritmo. Los valores de N_MAX y N_MIN PUEDEN ser iguales. Cada algoritmo DEBERÍA aceptar un nonce con una longitud de doce (12) octetos. Los algoritmos aleatorios o con estado, que se describen a continuación, PUEDEN tener un valor N_MAX de cero.
    ...
    Un algoritmo de cifrado autenticado PUEDE incorporar o hacer uso de una fuente aleatoria, por ejemplo, para la generación de un vector de inicialización interno que se incorpora a la salida del texto cifrado. Un algoritmo AEAD de este tipo se llama aleatorio; aunque tenga en cuenta que solo el cifrado es aleatorio y el descifrado siempre es determinista. Un algoritmo aleatorio PUEDE tener un valor de N_MAX igual a cero.

Un algoritmo de cifrado autenticado PUEDE incorporar información de estado interno que se mantiene entre las invocaciones de la operación de cifrado, por ejemplo, para permitir la construcción de valores distintos que el algoritmo utiliza como nonces internos. Un algoritmo AEAD de este tipo se llama stateful. Este método podría ser utilizado por un algoritmo para proporcionar una buena seguridad incluso cuando la aplicación ingresa nonces de longitud cero. Un algoritmo con estado PUEDE tener un valor de N_MAX igual a cero.

Una idea que vale la pena explorar es el paso de Nonce de longitud cero/null, que incluso podría ser el valor predeterminado. El paso de dicho valor de Nonce "especial" aleatorizará el valor de Nonce real, que estará disponible como salida de Encrypt.

Si INonceProvider permanece por "razones", otra idea es agregar una llamada a Reset() , que se activará cada vez que se vuelva a ingresar AuthenticatedEncryptor . Si, por otro lado, el plan es nunca volver a codificar instancias AuthenticatedEncryptor , esto eliminará GC si queremos construir una API de cifrado de fragmentos de transmisión (por ejemplo, fragmento = paquete de red), y cada fragmento debe ser encriptado con una clave diferente (por ejemplo, protocolo MSL de Netflix, Inferno, otros). Especialmente para operaciones paralelas de enc/dec donde nos gustaría mantener un grupo de motores AEAD y tomar prestadas instancias de ese grupo para hacer enc/dec. Démosle un poco de amor a GC :)

Desde mi punto de vista, el único propósito de las primitivas criptográficas es implementar protocolos de seguridad de alto nivel bien diseñados. Cada uno de estos protocolos insiste en generar nonces a su manera. Por ejemplo:

  • TLS 1.2 sigue las recomendaciones de RFC 5116 y concatena un IV de 4 bytes con un contador de 8 bytes,
  • TLS 1.3 xor es un contador de 8 bytes rellenado a 12 bytes con un IV de 12 bytes,
  • Noise utiliza un contador de 8 bytes con relleno de 12 bytes en el orden de bytes big-endian para AES-GCM y un contador de 8 bytes con relleno de 12 bytes en el orden de bytes de little-endian para ChaCha/Poly.

GCM es demasiado frágil para nonces aleatorios en tamaños típicos de nonce (96 bits). Y no conozco ningún protocolo de seguridad que realmente admita nonces aleatorios.

No hay mucha demanda de más API que proporcionen primitivas criptográficas. El 99,9% de los desarrolladores necesitan recetas de alto nivel para escenarios relacionados con la seguridad: almacenar una contraseña en una base de datos, cifrar un archivo en reposo, transferir de forma segura una actualización de software, etc.

Sin embargo, las API para recetas de tan alto nivel son raras. Las únicas API disponibles a menudo son solo HTTPS y las primitivas criptográficas, lo que obliga a los desarrolladores a implementar sus propios protocolos de seguridad. En mi opinión, la solución no es esforzarse mucho en diseñar API para trabajar con primitivas. Son APIs para recetas de alto nivel.

¡Gracias a todos por la retroalimentación! Un par de preguntas:

  1. Si bien el descifrado de transmisión puede fallar catastróficamente, el cifrado de transmisión podría ser factible. ¿El cifrado de transmisión (junto con una opción sin transmisión) pero solo el descifrado sin transmisión suena más útil? En caso afirmativo, hay un par de problemas que resolver:
    una. Algunos algoritmos (CCM, SIV) en realidad no admiten la transmisión. ¿Deberíamos poner el cifrado de transmisión en la clase base y las entradas transmitidas en búfer o tirar de las clases derivadas?
    B. Es probable que la transmisión de AAD no sea posible debido a las restricciones de implementación, pero los diferentes algoritmos lo necesitan en diferentes momentos (algunos lo necesitan al principio, otros no lo necesitan hasta el final). ¿Deberíamos solicitarlo por adelantado o tener un método para agregarlo que funcione cuando los algoritmos individuales lo permitan?
  1. Estamos abiertos a mejoras en INonceProvider siempre que el punto sea que los usuarios necesitan escribir código generando un nuevo nonce. ¿Alguien tiene otra forma propuesta para ello?

1 . a = Creo que podría ser un problema no advertir al usuario con anticipación. Imagine el escenario de alguien de arriba, un archivo de 10 gb. Piensan que están recibiendo transmisión, luego, en algún momento, otro desarrollador cambia el cifrado y, a continuación, el código está almacenando en búfer 10 gb (o intentando) antes de devolver un valor.

  1. b = Nuevamente con la idea de "transmisión" o red, por ejemplo, AES GCM, etc., no obtiene la información de AAD hasta el final para el descifrado. En cuanto al cifrado, todavía tengo que ver un caso en el que no tenga los datos por adelantado. Por lo tanto, diría que al menos para el cifrado debe solicitarlo al principio, el descifrado es más complejo.
  1. Creo que realmente no es un problema, proporcionar los "bytes" por el momento a través de una interfaz o simplemente directamente no está ni aquí ni allá. Puede lograr lo mismo en ambos sentidos, simplemente lo encuentro más feo para un primitivo, pero no me opongo con vehemencia si hace que las personas duerman mejor por la noche. Simplemente tacharía esto como un trato hecho y continuaría con los otros problemas.

Sobre el proceso de deliberación

@bartonjs : Podríamos discutir todo el día si las decisiones a puertas cerradas sin participación de la comunidad son una justificación efectiva, pero nos desviaremos del tema, así que lo dejaré así. Además, sin comunicaciones más ricas cara a cara o en tiempo real, no quiero molestar a nadie allí.

Con respecto a la transmisión

1. el argumento 'la transmisión no implica seguridad AES-GCM'

Específicamente, enviar al vapor => devolver datos descifrados a la persona que llama antes de la verificación de la etiqueta => sin seguridad. Esto no es sonido. @bartonjs afirma 'texto cifrado elegido => ver salida => recuperar clave' mientras que @drawaes afirma 'controlar entrada para un bloque => por lo tanto, conocer salida => "trabajar desde allí"'

Bueno, en AES-GCM, lo único que hace la etiqueta es verificar la integridad (protección contra manipulaciones). Tiene 0 impacto en la privacidad. De hecho, si elimina el procesamiento de etiquetas GCM/GHASH de AES-GCM, simplemente obtiene el modo AES-CTR. Es esta construcción la que maneja el aspecto de la privacidad. Y CTR es maleable para cambios de bits, pero no está "roto" en ninguna de las formas en que ustedes dos están afirmando (recuperando clave o texto sin formato) porque eso significaría que la primitiva AES fundamental está comprometida. Si su criptoanalista (¿quién es?) sabe algo que el resto de nosotros no sabemos, debería publicarlo. Lo único posible es que un atacante puede voltear el bit N y saber que se volteó el bit N del texto sin formato, pero nunca saben cuál es el texto sin formato real.

Entonces

1) la privacidad de texto sin formato siempre se aplica
2) la verificación de integridad simplemente se pospone (hasta el final de la transmisión) y
3) nunca se compromete ninguna clave.

Para los productos y sistemas en los que la transmisión es fundamental, ahora puede al menos diseñar una compensación en la que uno pasa momentáneamente de AEAD a la encriptación AES normal y luego vuelve a AEAD tras la verificación de la etiqueta. Eso desbloquea varios conceptos innovadores para adoptar la seguridad en lugar de decir: "¿Quieres almacenar en búfer todo eso ? ¿Estás loco? ¡No podemos encriptar!".

Todo porque desea implementar solo EncryptFinal en lugar de Encrypt y EncryptFinal (o equivalentes).

2. ¡No es específico de GCM!

Ahora AES-GCM no es una bestia mágica para tener "momentos ups" en abundancia. Es simplemente AES-CTR + GHASH (una especie de hash si se me permite). Las consideraciones de nonce relacionadas con la privacidad se heredan del modo CTR y las consideraciones de etiquetas relacionadas con la integridad provienen de los tamaños de etiquetas variables permitidos en la especificación. Aún así, AES-CTR + GHASH es muy similar a algo como AES-CBC + HMAC-SHA256 en que el primer algoritmo maneja la privacidad y el segundo maneja la integridad. En AES-CBC + HMAC-SHA256, los cambios de bits en el texto cifrado dañarán el bloque correspondiente en el texto descifrado (a diferencia de CTR) Y también cambiarán de manera determinista los bits en el siguiente bloque de texto sin formato descifrado (como CTR). Nuevamente, un atacante no sabrá cuál será el texto sin formato resultante, solo que los bits se invirtieron (como CTR). Finalmente, la verificación de integridad (HMAC-SHA256) lo detectará. Pero solo procesando el último byte (como GHASH).

Entonces, si su argumento de retener TODOS los datos descifrados hasta que la integridad esté bien es realmente bueno, debe aplicarse de manera consistente. Por lo tanto, TODOS los datos que salen de la ruta AES-CBC también deben almacenarse en búfer (internamente por la biblioteca) hasta que pase HMAC-SHA256. Básicamente, eso significa que en .NET, ninguna transmisión de datos puede beneficiarse de los avances de AEAD. .NET fuerza la transmisión de datos a una versión anterior. Para elegir entre ningún cifrado o cifrado regular. Sin AEAD. Cuando el almacenamiento en búfer no sea técnicamente práctico, los arquitectos deberían al menos tener la opción de advertir a los usuarios finales que "las imágenes del dron pueden estar corruptas" en lugar de "no tener ojos para ti".

3. Es lo mejor que tenemos

Los datos son cada vez más grandes y la seguridad debe ser más fuerte. La transmisión también es una realidad que los diseñadores deben adoptar. Hasta que el mundo elabore un algoritmo AEAD verdaderamente integrado que pueda detectar de forma nativa la manipulación corrupta en medio de la transmisión, estamos atrapados con el cifrado + autenticación como amigos. Se está investigando el primitivo AEAD verdadero, pero por ahora solo tenemos cifrado + autenticación.

Me importa menos "AES-GCM" tanto como un algoritmo AEAD rápido y popular que puede admitir cargas de trabajo de transmisión, muy frecuente en un mundo rico en datos e hiperconectado.

4. Usar AES-CBC-HMAC, Usar (insertar solución alternativa)

Crypto Board recomendaría NO usar GCM para el primer escenario, sino CBC + HMAC.

Dejando de lado todo lo mencionado anteriormente o incluso los detalles del escenario, lo que sugiere que AES-CBC-HMAC no es gratuito. Es aproximadamente 3 veces más lento que AES-GCM, ya que el cifrado AES-CBC no es paralelizable y GHASH se puede acelerar a través de la instrucción PCLMULQDQ. Entonces, si está a 1 GB/s con AES-GCM, ahora llegará a ~300 MB/s con AES-CBC-HMAC. Esto nuevamente perpetra la mentalidad de "Crypto lo ralentiza, sáltelo", una mentalidad que la gente de seguridad se esfuerza por combatir.

cifrar cada fotograma de 4k

¿Los códecs de video deberían encriptar de repente? ¿O la capa de cifrado ahora debe comprender los códecs de video? Es solo un flujo de bits en la capa de seguridad de datos. El hecho de que sea un video/datos genómicos/imágenes/formatos propietarios, etc. no debería ser una preocupación de la capa de seguridad. Una solución general no debe mezclar las responsabilidades principales.

Mientras tanto

NIST permite IV aleatorios de longitud superior a 96 bits. Consulte la sección 8.2.2 en NIST 800-38D . No hay nada nuevo aquí, los requisitos de nonce provienen del modo CTR. Lo cual también es bastante estándar en la mayoría de los cifrados de flujo . No entiendo el miedo repentino hacia los tontos, siempre ha sido number used once . Aún así, mientras que el debate INonce crea una interfaz torpe, al menos no elimina la innovación como la imposición de no transmitir. Concederé INonce cualquier día si podemos obtener las innovaciones de la carga de trabajo de transmisión y seguridad de AEAD. Odio llamar a algo básico como la transmisión una innovación, pero ahí es donde me temo que retrocederemos.

Me encantaría que me demuestren que estoy equivocado

Solo soy un tipo que después de un largo día de trabajo, renunció a la noche de cine con mis hijos para escribir esto. Estoy cansado y podría estar equivocado. Pero al menos tenga un diálogo comunitario abierto basado en hechos en lugar de anécdotas o "razones del comité" o algún otro vudú. Estoy en el negocio de promover innovaciones seguras de .NET y Azure. Creo que tenemos objetivos alineados.

Hablando de diálogo comunitario...

¿Podemos tener una llamada comunitaria de Skype? Expresar un tema complejo como este se convierte en una pared gigante de texto. ¿Bastante por favor?

No haga una llamada de Skype: esa es la definición misma de "reunión a puerta cerrada", sin registros disponibles para la comunidad. Los problemas de Github son el vehículo adecuado para que todas las partes tengan un discurso civil documentado (ignorando los precedentes de eliminación de comentarios de MS).

MS Crypto Review Board probablemente también hizo una llamada de Skype. No es culpa de la gente de MS que participa en este hilo; es probable que tengan un acceso muy limitado y un poder de persuasión sobre las torres de marfil de MS Crypto Review Board (sea lo que sea).

Respecto al streaming de la AEAD:

El _encriptado_ de transmisión de tamaño de byte es posible para los últimos modos MAC como GCM, CTR+HMAC, pero no es posible para los primeros modos MAC como CCM. El _descifrado_ de transmisión de tamaño de byte se está filtrando fundamentalmente y, por lo tanto, nadie lo considera. El _cifrado_ de transmisión de tamaño de bloque también es posible para CBC+HMAC, pero eso no cambia nada. Es decir. Los enfoques de tamaño de byte o tamaño de bloque para la transmisión de AEAD son defectuosos.

El _cifrado_ y el _descifrado_ de transmisión de tamaño de fragmento funcionan muy bien, pero tienen 2 limitaciones:

  • requieren almacenamiento en búfer (más allá del tamaño del bloque). Esto puede hacerlo la biblioteca/API si el almacenamiento en búfer está controlado/limitado (p. ej., Inferno), o puede dejarse en manos de la capa superior (capa de llamada) para que se encargue. De cualquier manera funciona.

  • La transmisión fragmentada AEAD no está estandarizada. Ex. nacl-stream , Inferno, MS-own DataProtection, hazlo tú mismo.

Esto es solo un resumen de lo que todos en esta discusión ya saben hasta ahora.

@sdrapkin , para asegurarme de que entiendo correctamente, ¿está de acuerdo con esta API que proporciona cifrado de transmisión, pero no descifrado de transmisión?

@sdrapkin bueno, la lluvia de ideas de los humanos en tiempo real es ciertamente beneficiosa, las preocupaciones sobre el mantenimiento de registros se pueden resolver con las actas de las reuniones. Volviendo al aspecto técnico, aunque la fragmentación funciona para el descifrado de transmisión, no es una primitiva de seguridad de bajo nivel. Es un protocolo personalizado. Y uno no estándar como usted notó.

@morganbr

_¿Está de acuerdo con que esta API proporcione cifrado de transmisión, pero no descifrado de transmisión?_

No no soy. Si dicha API estuviera disponible, sería fácil crear un texto cifrado de flujo cifrado de un tamaño que ningún descifrado de búfer podría descifrar (sin memoria).

^^^^ En esto, no ha habido mucho acuerdo hasta ahora, pero creo que todos podemos estar de acuerdo, sea como sea, una API asimétrica sería un desastre. Tanto por un "hey, eran los métodos de descifrado de flujo en los que pensé que confiaría porque había métodos de cifrado", como por los comentarios de @sdrapkin anteriores.

@Drawaes De acuerdo. La API asimétrica de enc/dec sería horrible.

¿Alguna actualización amigos?

Aparentemente combiné algunos ataques.

Las debilidades inherentes en los cifrados de flujo (que son AES-CTR y AES-GCM) permiten ataques de texto cifrado elegidos para permitir la recuperación arbitraria de texto sin formato. La defensa contra los ataques de texto cifrado elegidos es la autenticación; por lo tanto, AES-GCM es inmune... a menos que esté realizando un descifrado de transmisión y pueda identificar a partir de las observaciones del canal lateral cuál habría sido el texto sin formato. Por ejemplo, si los datos descifrados se procesan como XML, fallarán muy rápidamente si al principio de los datos descifrados hay caracteres que no sean espacios en blanco o <. Entonces eso es "el descifrado de transmisión reintroduce preocupaciones con el diseño de cifrado de transmisión" (que, como habrás notado, .NET no tiene ninguno).

Mientras busca de dónde procedía la recuperación de la clave, hay documentos como Autenticacióndebilidades en GCM (Ferguson/Microsoft) , pero esa es la recuperación de la clave de autenticación basada en tamaños de etiquetas cortos (que es parte de por qué la implementación de Windows solo permite etiquetas de 96 bits). Probablemente me informaron sobre otros vectores de recuperación de claves de autenticación sobre por qué la transmisión de GCM es peligrosa.

En un comentario anterior , @sdrapkin señaló: "El descifrado de transmisión de tamaño de byte se está filtrando fundamentalmente y, por lo tanto, nadie lo considera ... Los enfoques de tamaño de byte o tamaño de bloque para la transmisión AEAD son defectuosos". Eso, combinado con CCM (y SIV) que no son capaces de realizar el cifrado de transmisión y el comentario de que sería extraño tener una transmisión y no la otra, sugiere que volvemos a la propuesta de solo tener un cifrado de una sola vez y descifrar

Entonces parece que volvimos a mi última propuesta de API (https://github.com/dotnet/corefx/issues/23629#issuecomment-329202845). A menos que haya otros asuntos pendientes que logré olvidar mientras me tomaba un tiempo libre.

Bienvenido de nuevo @bartonjs

Me voy a dormir breve pero brevemente:

  1. Hemos fusionado el diseño del protocolo con el diseño primitivo antes en este hilo. Solo diré que los ataques de texto cifrado elegidos son una preocupación de diseño de protocolo, no una preocupación primitiva.

  2. El descifrado de Streaming AEAD al menos le permite tener privacidad y luego se actualiza inmediatamente a privacidad + autenticidad en el último byte. Sin soporte de transmisión en AEAD (es decir, solo privacidad tradicional), está restringiendo permanentemente a las personas a una garantía más baja y solo de privacidad.

Si los méritos técnicos son insuficientes o eres (con razón) escéptico de la autoridad de mis argumentos, probaré la ruta de la autoridad externa. Debe saber que su implementación subyacente real es compatible con AEAD (incluido AES GCM) en modo de transmisión. El sistema operativo central de Windows ( bcrypt ) permite la transmisión de GCM a través de las funciones BCryptEncrypt o BCryptDecrypt . Ver dwFlags allí. O un ejemplo de código de usuario . O un contenedor CLR creado por Microsoft. O que la implementación haya sido certificada por NIST FIP-140-2 tan recientemente como a principios de este año. O que tanto Microsoft como NIST gastaron importantes recursos en torno a la implementación de AES y lo certificaron aquí y aquí . Y a pesar de todo esto, nadie ha criticado a los primitivos. No tiene ningún sentido que .NET Core aparezca repentinamente e imponga su propia criptotesis para diluir la poderosa implementación subyacente. Especialmente cuando AMBOS streaming y one-shot pueden admitirse simultáneamente, de manera muy trivial.

¿Más? Bueno, lo anterior es cierto para OpenSSL, incluso con sus API evp 'más nuevas'.

Y es cierto para BouncyCastle.

Y es cierto con la arquitectura criptográfica de Java.

¡salud!
sid

@sidshetye ++ 10 si el tablero criptográfico está tan preocupado, ¿por qué permiten que Windows CNG haga esto?

Si verifica la validación AES NIST FIPS-140-2 de Microsoft (por ejemplo, # 4064 ), notará lo siguiente:

AES-GCM:

  • Longitudes de texto sin formato: 0, 8, 1016, 1024
  • Longitudes de AAD: 0, 8, 1016, 1024

AES-CCM:

  • Longitud de texto sin formato: 0-32
  • Longitud de AAD: 0-65536

No hay validación para la transmisión. Ni siquiera estoy seguro de si NIST verifica ese ex. No se debe permitir que la implementación de AES-GCM cifre más de 64 Gb de texto sin formato (otra limitación ridícula de GCM).

No estoy muy comprometido con la transmisión, ya que mi uso no debería superar los 16k; sin embargo, los búferes fragmentados serían buenos y no deberían representar ningún riesgo (de hecho, sospecho que cng hizo que su interfaz fuera exactamente para ese propósito)... por ejemplo, quiero poder pasar una cantidad de tramos o similar (lista vinculada, por ejemplo) y hacer que se descifre de una sola vez. Si se descifra en un búfer contiguo, todo está bien.

Así que supongo que mover el tablero criptográfico sombrío en la API de "estilo de transmisión" no es posible por ahora, así que avancemos para hacer una API de una sola vez. Siempre hay margen para expandir una API SI suficientes personas muestran una necesidad más adelante

@sdrapkin , el punto es que es la API de transmisión la que ha pasado por una revisión exhaustiva por parte de NIST Labs y MSFT. Cada compilación que se valida cuesta entre $ 80,000 y $ 50,000 y MSFT (y OpenSSL y Oracle y otros pesos pesados ​​​​de criptografía) han invertido MUCHO para validar estas API e implementaciones durante más de 10 años. No nos distraigamos con los tamaños de texto sin formato específicos del plan de prueba porque estoy seguro de que .NET admitirá tamaños distintos de 0, 8, 1016, 1024, independientemente de la transmisión o de una sola toma. El punto es que todas esas API probadas en batalla (literalmente, en sistemas de soporte de armas), en todas estas plataformas admiten la transmisión de AEAD en el nivel de API cripto-primitivo. Desafortunadamente, todos los argumentos hasta ahora en su contra han sido una preocupación a nivel de aplicación o protocolo citada como una pseudo-preocupación en el nivel criptográfico primitivo.

Estoy a favor de 'dejar que gane la mejor idea', pero a menos que el equipo de criptografía de .net core (MSFT o comunidad) tenga algún descubrimiento innovador, simplemente no veo cómo todos los que están haciendo criptografía hasta ahora, de todas las organizaciones diferentes, están equivocados. y tienen razón.

PD: Sé que no estamos de acuerdo, pero todos queremos lo mejor para la plataforma y sus clientes.

@Drawaes a menos que la interfaz AEAD (no necesariamente la implementación) que se define hoy admita una superficie API de transmisión, no veo cómo la gente puede extenderla sin tener dos interfaces o interfaces personalizadas. Eso sería un desastre. Espero que esta discusión conduzca a una interfaz que esté preparada para el futuro (o, al menos, ¡refleje otras interfaces AEAD que han existido durante muchos años!).

Tiendo a estar de acuerdo. Pero este problema no va a ninguna parte rápidamente y cuando eso suceda, es probable que lleguemos a un punto crítico, ya sea que no lo haga para 2.1 o que tenga que ser resuelto sin tiempo para solucionar los problemas. Seré honesto, he vuelto a mis viejos envoltorios y los estoy renovando para 2.0;)

Tenemos algunas API de referencia para Java , OpenSSL , C# Bouncy Castle o CLR Security . Francamente, cualquiera de ellos funcionará y, a largo plazo, deseo que C # tenga algo como la 'Arquitectura criptográfica de Java' de Java, donde todas las implementaciones criptográficas están en contra de una interfaz bien establecida que permite intercambiar bibliotecas criptográficas sin afectar el código de usuario.

Aquí atrás, creo que es mejor que ampliemos la interfaz ICryptoTransform de .NET Core como

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

Si estamos Span ificando todos los byte[] , eso debería impregnar toda la API en el espacio de nombres System.Security.Cryptography para lograr una coherencia general.

Editar: enlaces JCA fijos

Si estamos expandiendo todos los bytes [], eso debería impregnar toda la API en el espacio de nombres System.Security.Cryptography para lograr una coherencia general.

Eso ya lo hicimos. Todo excepto ICryptoTransform, porque no podemos cambiar las interfaces.

Creo que es mejor que ampliemos ICryptoTransform de .NET Core...

El problema con esto es que el patrón de llamada es muy incómodo al sacar la etiqueta al final (particularmente si se trata de CryptoStream). Escribí esto originalmente, y era feo. También está el problema de cómo obtener uno de estos, ya que los parámetros GCM son diferentes a los parámetros CBC/ECB.

Entonces, aquí están mis pensamientos.

  • El descifrado de transmisión es peligroso para AE.
  • En general, soy fanático de "dame lo primitivo y déjame manejar mi riesgo".
  • También soy fanático de ".NET no debería (fácilmente) permitir cosas completamente inseguras, porque esa es parte de su propuesta de valor".
  • Si, como entendí mal originalmente, los riesgos de hacer mal el descifrado de GCM eran la recuperación de la clave de entrada, entonces todavía estaría en "esto es demasiado inseguro". (La diferencia entre .NET y todo lo demás sería "habiendo tardado más en hacer esto, el mundo ha aprendido más")
  • Pero, dado que no lo es, si realmente quieres que se quiten las ruedas de entrenamiento, entonces supongo que consideraré esa idea.

Mis pensamientos bastante crudos con ese fin (que se suman a las sugerencias existentes, por lo que el one-shot permanece, aunque supongo que como un impl virtual predeterminado en lugar de un resumen):

```C#
clase parcial AuthenticatedEncryptor
{
// lanza si una operación ya está en progreso
public abstract void Inicializar (ReadOnlySpandatos asociados);
// verdadero en caso de éxito, falso en "destino demasiado pequeño", excepción en cualquier otra cosa.
public abstract bool TryEncrypt(ReadOnlySpandatos, intervaloencryptedData, out int bytesRead, out int bytesWritten);
// falso si los datos restantes encriptados son demasiado pequeños, arroja si otras entradas son demasiado pequeñas, consulte las propiedades NonceOrIVSizeInBits y TagSizeInBits.
// NonceOrIvUsed podría moverse a Initialize, pero luego podría interpretarse como una entrada.
public abstract bool TryFinish(ReadOnlySpandatos restantes, intervaloRestingEncryptedData, out int bytesWritten, Spanetiqueta, intervalononceOrIvUsed);
}

clase parcial AuthenticatedDecryptor
{
// lanza si una operación ya está en progreso
public abstract void Inicializar (ReadOnlySpanetiqueta, ReadOnlySpannonceOrIv, ReadOnlySpandatos asociados);
// verdadero en caso de éxito, falso en "destino demasiado pequeño", excepción en cualquier otra cosa.
public abstract bool TryDecrypt(ReadOnlySpandatos, intervalodecryptedData, out int bytesRead, out int bytesWritten);
// arroja una etiqueta incorrecta, pero podría filtrar los datos de todos modos.
// (Se requieren datos descifrados restantes para CBC + HMAC, por lo que también puede agregar datos restantes, ¿supongo?)
public abstract bool TryFinish(ReadOnlySpandatos restantes, intervaloRestingDecryptedData, out int bytesWritten);
}
```

AssociatedData viene en Initialize, porque los algoritmos que lo necesitan al final pueden retenerlo, y los algoritmos que lo necesitan primero no pueden tenerlo de otra manera.

Una vez que se decida una forma de cómo se vería la transmisión (y si las personas piensan que CCM debería almacenar en búfer internamente, o debería lanzar, cuando está en modo de encriptación de transmisión), volveré a la pizarra.

@bartonjs Sé lo que quiere decir acerca de extraer y programar la etiqueta desde el final de la secuencia para simetría en el cifrado/descifrado. Es complicado pero peor si se deja que cada usuario lo resuelva. Tengo una implementación que puedo compartir bajo MIT; tendré que mirar internamente con mi equipo (no en mi escritorio/móvil)

Un término medio podría ser como OpenSSL o bcrypt de NT, donde debe conectar la etiqueta justo antes de la última llamada de descifrado, ya que es cuando ocurren las comparaciones de etiquetas. es decir, SetExpectedTag (antes del descifrado final) y GetTag (después del cifrado final) funcionarían pero descargan la administración de etiquetas al usuario. La mayoría simplemente agregará la etiqueta al flujo de cifrado, ya que es el orden temporal natural.

Creo que esperar la etiqueta en Initialize en sí misma (en el descifrado) rompe la simetría en el espacio (flujo de bytes) y el tiempo (comprobación de la etiqueta al final, no al inicio), lo que limita su utilidad. Pero las API de etiquetas anteriores resuelven eso.

También para cifrar, Initialize necesita el IV antes de que se transforme cualquier criptografía.

Por último, para cifrar y descifrar, Initialize necesita las claves de cifrado AES antes de cualquier transformación. (¿Me estoy perdiendo algo obvio o se olvidó de escribir ese bit?)

Creo que esperar que la etiqueta se inicie en sí misma (en descifrar) rompe la simetría

En CBC+HMAC, la recomendación habitual es verificar el HMAC antes de comenzar cualquier descifrado, por lo que es un algoritmo de descifrado de etiquetas primero. De manera similar, podría haber un algoritmo "AE puro" que realice operaciones destructivas en la etiqueta durante los cálculos y simplemente verifique que la respuesta final sea 0. Entonces, al igual que el valor de datos asociado, dado que podría haber algoritmos que lo necesiten primero, tiene llegar primero en una API completamente generalizada.

Hacerlos flotar en SetAssociatedData y SetTag tiene el problema de que, si bien la clase base era independiente del algoritmo, el uso se vuelve dependiente del algoritmo. Cambiar AesGcm a AesCbcHmacSha256 o SomeTagDesctructiveAlgorithm ahora resultaría en el lanzamiento de TryDecrypt porque aún no se proporcionó la etiqueta. Para mí, eso es peor que no ser polimórfico en absoluto, por lo que permitir la flexibilidad sugiere dividir el modelo para que esté completamente aislado por algoritmo. (Sí, podría ser controlado por más propiedades características de identificación de algoritmos como NeedsTagFirst , pero eso realmente hace que sea más difícil de usar)

También para cifrar, Initialize necesita el IV antes de cualquier transformación criptográfica.

Por último, para cifrar y descifrar, Initialize necesita las claves de cifrado AES antes de cualquier transformación.

La clave era un parámetro de clase ctor. El IV/nonce proviene del proveedor IV/nonce en el parámetro ctor.

El modelo de proveedor resuelve SIV, donde no se proporciona IV durante el cifrado, se genera uno en nombre de los datos. De lo contrario, SIV tiene el parámetro y requiere que se proporcione un valor vacío.

o se olvidó de escribir ese bit?

Los métodos de transmisión se agregaron a mi propuesta existente, que ya tenía la clave y el proveedor IV/nonce como parámetros ctor.

@bartonjs : Buen punto de que algunos algos podrían querer etiquetar primero mientras que otros al final y gracias por el recordatorio de que es una adición a la especificación original. Descubrí que considerar un caso de uso lo hace más fácil, así que aquí hay un ejemplo de nube primero:

Vamos a realizar análisis en uno o más archivos cifrados AES-GCM de 10 GB (es decir, etiquetas después del texto cifrado) almacenados. Un trabajador de análisis descifra simultáneamente múltiples flujos entrantes en máquinas/agrupaciones separadas y después de las verificaciones del último byte + etiqueta, comienza cada carga de trabajo de análisis. Todas las máquinas virtuales de análisis, trabajadores y almacenamiento están en Azure EE. UU. Oeste.

Aquí, no hay forma de obtener la etiqueta al final de cada transmisión y proporcionarla al método Initialize de AuthenticatedDecryptor. Entonces, incluso si un usuario se ofrece como voluntario para modificar el código para el uso de GCM, ni siquiera puede comenzar a usar la API.

Ahora que lo pienso, la única forma en que podríamos tener una API que se adapte a varios AEAD Y no tenga cambios en el código de usuario es si los proveedores de cifrado para diferentes algoritmos AEAD manejan automáticamente las etiquetas. Java hace esto colocando las etiquetas al final del texto cifrado para GCM y las extrae durante el descifrado sin la intervención del usuario. Aparte de eso, cada vez que alguien cambie el algoritmo significativamente (por ejemplo, CBC-HMAC => GCM) tendrá que modificar su código debido a la naturaleza mutuamente excluyente del procesamiento de etiqueta primero y último.

En mi humilde opinión, primero debemos decidir si

Opción 1) Los proveedores de algoritmos manejan internamente la gestión de etiquetas (como Java)

o

Opción 2) Exponga lo suficiente en la API para que los usuarios lo hagan ellos mismos (como WinNT bcrypt o openssl)

La opción 1 realmente simplificaría la experiencia general para los consumidores de bibliotecas porque la administración del búfer puede volverse compleja. Resuelva bien en la biblioteca y cada usuario no tendrá que resolverlo cada vez ahora. Además, todos los AEAD obtienen la misma interfaz (primero con etiqueta, último con etiqueta, sin etiqueta) y el intercambio de algoritmos también es más simple.

Mi voto sería por la opción 1.

Finalmente, pudimos desenterrar nuestra implementación que permite que las operaciones de transmisión ICryptoTransform a través de GCM eliminen automáticamente la fuente in-stream de la etiqueta. Esta fue una actualización importante del contenedor propio de CLR Security y, a pesar de las copias de búfer adicionales, sigue siendo muy rápido (~4 GB/seg en nuestro macbook pro de prueba en el bootcamp de Windows 10). Básicamente, nos adaptamos a CLR Security para crear la opción 1 para nosotros mismos, de modo que no necesitemos hacerlo en ningún otro lugar. Este visual realmente ayuda a explicar lo que sucede dentro de TransformBlock y TransformFinalBlock de la interfaz ICryptoTransform .

@sidshetye No estoy seguro de por qué su ejemplo de nube primero está bloqueado. Si está leyendo desde el almacenamiento, puede descargar primero los últimos bytes de la etiqueta y proporcionarlos al descifrador ctor. Si usa las API de Azure Storage, esto se logrará a través CloudBlockBlob.DownloadRangeXxx .

@GrabYourPitchforks No es para desviarse demasiado de ese ejemplo, pero esa es una capacidad específica de Azure Blob Storage. En general, las cargas de trabajo de almacenamiento basado en VM (IaaS) o que no son de Azure Storage suelen obtener un flujo de red que no se puede buscar.

Yo, personalmente, estoy muy emocionado de ver @GrabYourPitchforks - ¡sí!

Vamos a realizar análisis en uno o más archivos cifrados AES-GCM de 10 GB (es decir, etiquetas después del texto cifrado) almacenados. Un trabajador de análisis descifra simultáneamente múltiples flujos entrantes en máquinas/agrupaciones separadas y después de las verificaciones del último byte + etiqueta, comienza cada carga de trabajo de análisis. Todas las máquinas virtuales de análisis, trabajadores y almacenamiento están en Azure EE. UU. Oeste.

@sidshetye , ¡eres tan inflexible en mantener separados los primitivos tontos y peligrosos y los protocolos inteligentes y abrazables! Tuve un sueño, y lo creí. Y luego nos lanzas esto. Este es un protocolo, un diseño de sistema. Quienquiera que haya diseñado ese protocolo que describiste, lo arruinó. No tiene sentido llorar por la incapacidad de colocar una clavija cuadrada en un agujero redondo ahora.

Cualquiera que sea el archivo de 10 Gb cifrado con GCM no solo vive peligrosamente cerca del borde primitivo (GCM no es bueno después de 64 Gb), sino que también hubo una afirmación implícita de que todo el texto cifrado deberá almacenarse en búfer.

Quienquiera que cifre con GCM archivos de 10 Gb está cometiendo un error de protocolo con una probabilidad abrumadora. La solución: cifrado fragmentado. TLS tiene fragmentación limitada de 16k de longitud variable, y hay otras versiones más simples y libres de PKI. El atractivo sexual de "nube primero" de este ejemplo hipotético no disminuye los errores de diseño.

(Tengo mucho que ponerme al día en este hilo.)

@sdrapkin planteó un punto sobre la reutilización de la interfaz IAuthenticatedEncryptor de la capa de protección de datos. Para ser honesto, no creo que esa sea la abstracción correcta para un primitivo, ya que la capa de Protección de datos es bastante obstinada en la forma en que realiza la criptografía. Por ejemplo, prohíbe la autoselección de un IV o nonce, exige que una implementación conforme comprenda el concepto de AAD y produce un resultado que es algo patentado. En el caso de AES-GCM, el valor de retorno de IAuthenticatedEncryptor.Encrypt es la concatenación de algo extraño casi instantáneo utilizado para la derivación de subclaves, el texto cifrado resultante de ejecutar AES-GCM sobre el texto sin formato proporcionado (pero no el AAD!) y la etiqueta AES-GCM. Entonces, si bien cada paso involucrado en la generación de la carga útil protegida es seguro, la carga útil en sí no sigue ningún tipo de convención aceptada, y no encontrará a nadie aparte de la biblioteca de Protección de datos que pueda descifrar con éxito el texto cifrado resultante. Eso lo convierte en un buen candidato para una biblioteca orientada al desarrollador de aplicaciones, pero un candidato horrible para una interfaz implementada por primitivos.

También debo decir que no veo un valor considerable en tener One True Interface(tm) IAuthenticatedEncryptionAlgorithm que se supone que deben implementar todos los algoritmos de encriptación autenticados. Estas primitivas son "complejas", a diferencia de las primitivas de cifrado de bloques simples o las primitivas de hashing. Simplemente hay demasiadas variables en estas primitivas complejas. ¿Es solo el AE primitivo o es AEAD? ¿El algoritmo acepta un IV / nonce en absoluto? (He visto algunos que no lo hacen). ¿Hay alguna preocupación sobre cómo se debe estructurar la entrada IV / nonce o los datos? En mi opinión, las primitivas complejas deberían ser simplemente API independientes, y las bibliotecas de nivel superior serían compatibles con las primitivas complejas específicas que les interesan. Luego, la biblioteca de nivel superior expone cualquier API uniforme que crea que es apropiada para sus escenarios.

@sdrapkin Nos estamos desviando del tema otra vez. Solo diré que un sistema se construye usando primitivas. Las primitivas criptográficas aquí son simples y poderosas. Mientras que la capa de sistema/protocolo manejaba el almacenamiento en búfer; eso también a nivel de clúster, ciertamente no en la memoria principal del sistema que forzarían las primitivas de un disparo. El límite de 'fragmentación' es X (X = 10 GB aquí) porque < 64 GB, porque la capacidad de almacenamiento en búfer del clúster era casi ilimitada y nada comenzaría/podría comenzar hasta que se cargue el último byte en el clúster. Esta es exactamente la separación de preocupaciones, optimizando cada capa para sus fortalezas de las que he estado hablando. Y esto solo puede suceder si las primitivas subyacentes no perjudican los diseños/limitaciones de capas superiores (tenga en cuenta que más aplicaciones del mundo real vienen con sus propias desventajas heredadas).

NIST 800-38d sec9.1 establece:

Para impedir que una parte no autorizada controle o influya en la generación de IV,
GCM se implementará solo dentro de un módulo criptográfico que cumpla con los requisitos de
Publicación FIPS. 140-2. En particular, el límite criptográfico del módulo contendrá un
“unidad de generación” que produce IVs según una de las construcciones en la Sec. 8.2 anterior.
La documentación del módulo para su validación frente a los requisitos de FIPS 140-2 deberá
describir cómo el módulo cumple con el requisito de unicidad en los IV.

Eso implica para mí que los GCM IV deben generarse automáticamente internamente (y no pasarse externamente).

@sdrapkin Buen punto, pero si lee aún más de cerca, verá que para longitudes de IV de 96 bits y superiores, la sección 8.2.2 permite generar un IV con un generador de bits aleatorios (RBG) donde al menos 96 bits son aleatorios (usted podría simplemente 0 otros bits). Mencioné esto el mes pasado en este mismo hilo ( aquí debajo).

LT;DR: INonce es una trampa que conduce al incumplimiento de las directrices de NIST y FIPS.

La sección 9.1 simplemente dice, para FIPS 140-2, la unidad de generación IV (totalmente aleatoria, es decir, sec 8.2.2 o implementación determinista, es decir, sec 8.2.1) debe estar dentro del límite del módulo sometido a la validación de FIPS. Ya que ...

  1. Los RBG ya están validados por FIPS
  2. Se recomienda lente IV >= 96
  3. diseñar una unidad de IV generación que persista reinicios, la pérdida indefinida de energía en una capa primitiva criptográfica es difícil
  4. implementar 3 anteriores dentro de la biblioteca criptográfica Y obtener la certificación es difícil y costoso ($ 50K por cualquier cosa que resulte en una imagen de compilación no exacta)
  5. Ningún código de usuario implementará nunca 3 y lo certificará debido a 4 anterior. (Dejemos de lado algunas instalaciones militares/gubernamentales exóticas).

... la mayoría de las bibliotecas criptográficas (consulte Java de Oracle, bcryptprimitives de WinNT, OpenSSL, etc.) que se someten a la certificación FIPS utilizan la ruta RBG para IV y simplemente toman una matriz de bytes como entrada. Tenga en cuenta que tener la interfaz INonce es en realidad una trampa desde la perspectiva de NIST y FIPS porque implícitamente sugiere que un usuario debe pasar una implementación de esa interfaz a la función criptográfica. Pero casi se garantiza que cualquier implementación de usuario de INonce NO se ha sometido al proceso de certificación NIST de más de 9 meses y $50K+. Sin embargo, si hubieran enviado una matriz de bytes utilizando la construcción RGB (ya en la biblioteca criptográfica), cumplirían totalmente con las pautas.

Lo dije antes: estas bibliotecas criptográficas existentes han desarrollado su superficie API y han sido probadas en batalla en múltiples escenarios. Más de lo que hemos tocado en este largo hilo. Mi voto nuevamente es aprovechar ese conocimiento y experiencia en todas esas bibliotecas, todas esas validaciones y todas esas instalaciones en lugar de intentar reinventar la rueda. No reinventes la rueda. Úsalo para inventar el cohete :)

Hola amigos,

¿Alguna actualización sobre esto? No he visto ninguna actualización en el hilo de la hoja de ruta criptográfica de @karelz o en el hilo AES GCM .

Gracias
sid

Entonces, la última propuesta concreta es 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);
}

Solo se han planteado algunos problemas potenciales desde:

  • La etiqueta se requiere por adelantado, lo que dificulta ciertos escenarios. O la API debe volverse significativamente más compleja para permitir una mayor flexibilidad, o este problema debe considerarse un problema de protocolo (es decir, de alto nivel).
  • INonceProvider puede ser innecesariamente complejo y/o conducir al incumplimiento de las pautas de NIST y FIPS.
  • La abstracción prevista de las primitivas de cifrado autenticadas podría ser una quimera, ya que las diferencias podrían ser demasiado grandes. No ha habido más discusión sobre esta sugerencia.

Me gustaría sugerir lo siguiente:

  1. La complejidad adicional de no requerir la etiqueta por adelantado parece grave, el escenario del problema correspondiente parece poco común y el problema parece mucho más una cuestión de protocolo. Un buen diseño puede acomodar mucho, pero no todo. Personalmente me siento cómodo dejando esto al protocolo. (Los contraejemplos fuertes son bienvenidos).
  2. La discusión se ha movido consistentemente hacia una implementación flexible y de bajo nivel que no protege contra el uso indebido, con la excepción de la IV generación . Seamos consistentes. El consenso general parece ser que una API de alto nivel es un próximo paso importante, vital para el uso adecuado por parte de la mayoría de los desarrolladores; así es como nos salimos con la nuestra sin protegernos contra el uso indebido en la API de bajo nivel . Pero parece que una dosis extra de miedo ha sostenido la idea de la prevención del mal uso _en el ámbito de la IV generación_. En el contexto de una API de bajo nivel, y para ser coherente, me inclinaría por un byte[] . Pero el intercambio de implementación es más fluido con el INonceProvider inyectado. ¿Es irrefutable el comentario de @sidshetye , o podría considerarse compatible una simple implementación INonceProvider que simplemente llama al RNG?
  3. Las abstracciones parecen útiles, y se ha puesto tanto esfuerzo en diseñarlas, que ahora estoy convencido de que harán más bien que mal. Además, las API de alto nivel aún pueden optar por implementar API de bajo nivel que no se ajusten a las abstracciones de bajo nivel.
  4. IV es el término general, y un nonce es un tipo específico de IV, ¿correcto? Esto pide cambios de nombre de INonceProvider a IIVProvider , y de nonceOrIv* a iv* . Después de todo, siempre estamos tratando con un IV, pero no necesariamente con un nonce.

La etiqueta por adelantado no es un iniciador para mi escenario, por lo que probablemente solo mantendré mi propia implementación. Lo cual está bien. No estoy seguro de que sea una taza de té para todos escribir código de alto rendimiento en esta área.

El problema es que causará una latencia innecesaria. Tienes que almacenar previamente en el búfer un mensaje completo para obtener la etiqueta al final y comenzar a decodificar el marco. Esto significa que básicamente no puede superponer IO y descifrado.

No estoy seguro de por qué es tan difícil permitirlo al final. Pero no voy a descartar un obstáculo para esta API, simplemente no será de ningún interés en mi escenario.

IV es el término general, y un nonce es un tipo específico de IV, ¿correcto?

No. Un nonce es un número que se usa una vez . Un algoritmo que especifica un nonce indica que la reutilización viola las garantías del algoritmo. En el caso de GCM, usar el mismo nonce con la misma clave y un mensaje diferente puede resultar en el compromiso de la clave GHASH, reduciendo GCM a CTR.

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

Nonce: un valor utilizado en los protocolos de seguridad que nunca se repite con la misma clave. Por ejemplo, los nonces utilizados como desafíos en los protocolos de autenticación de desafío-respuesta generalmente no deben repetirse hasta que se cambien las claves de autenticación. De lo contrario, existe la posibilidad de un ataque de repetición. Usar un nonce como desafío es un requisito diferente al de un desafío aleatorio, porque un nonce no es necesariamente impredecible.

Un "IV" no tiene los mismos requisitos estrictos. Por ejemplo, repetir un IV con CBC solo filtra si el mensaje cifrado es igual o diferente a uno anterior con el mismo IV. No debilita el algoritmo.

Un nonce es un número que se usa una vez.
Un "IV" no tiene los mismos requisitos estrictos.

@bartonjs Sí. Razonaría que, dado que se usa un nonce para inicializar la primitiva criptográfica, es su vector de inicialización. Se adhiere perfectamente a cualquier definición de IV que pueda encontrar. Tiene requisitos más estrictos, sí, al igual que ser una vaca tiene requisitos más estrictos que ser un animal. La redacción actual parece solicitar un parámetro "cowOrAnimal". El hecho de que los diferentes modos tengan diferentes requisitos del IV no cambia el hecho de que todos solicitan algún tipo de IV. Si hay algo que me estoy perdiendo, por todos los medios mantenga la redacción actual, pero por lo que puedo decir, solo "iv" o "IIVProvider" son simples y correctos.

Para disfrutar del nonceOrIv bikeshedding:

El GCM IV de 96 bits a veces se define como salt de 4 bytes y salt nonce 8 bytes (por ejemplo, RFC 5288). RFC 4106 define GCM nonce como salt de 4 bytes y $#$5 iv salt de 8 bytes. RFC 5084 (GCM en CMS) dice que CCM toma nonce , GCM toma iv , pero _"... para tener un conjunto común de términos para AES-CCM y AES-GCM , el AES-GCM IV se denomina nonce en el resto de este documento."_ RFC 5647 (GCM para SSH) dice _"nota: en [RFC5116], el IV se denomina nonce"._ RFC 4543 ( GCM en IPSec) dice _"nos referimos a la entrada AES-GMAC IV como un nonce, para distinguirla de los campos IV en los paquetes"._ RFC 7714 (GCM para SRTP) habla de un IV de 12 bytes

Dada la total falta de consistencia en la mayoría de las especificaciones de GCM, nonceOrIv tiene sentido. $0.02

La etiqueta por adelantado no es un comienzo

Al igual que otros clientes que se expresan aquí, solicitar la etiqueta por adelantado tampoco es un buen comienzo para nosotros. No hay forma de que .NET pueda procesar flujos simultáneos con esta limitación introducida artificialmente. Mata totalmente la escalabilidad.

¿Puede respaldar la afirmación de que agrega complejidad? Porque en realidad debería ser trivial. Además, ninguna de las implementaciones criptográficas específicas de la plataforma (que envolverá) tiene esta limitación. Específicamente, la razón es que la etiqueta de entrada debe compararse simplemente en tiempo constante con la etiqueta calculada. Y la etiqueta calculada está disponible solo después de que el bloque final se haya descifrado durante TryFinish . Entonces, esencialmente, cuando comience su implementación, encontrará que simplemente está almacenando el tag dentro de su instancia hasta el TryFinish . Muy bien podría tenerlo como una entrada opcional

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

También creo que nos estamos esforzando demasiado para normalizar a una sola interfaz que cubra todos los escenarios criptográficos. Yo también prefiero las interfaces generalizadas, pero nunca a expensas de la funcionalidad o la escalabilidad, especialmente en una capa tan fundamental como la biblioteca criptográfica estándar del propio lenguaje. En mi humilde opinión, si uno se encuentra haciéndolo, generalmente significa que la abstracción es defectuosa.

Si se necesita una interfaz simple y consistente, prefiero el enfoque de Java , también planteado anteriormente aquí como opción 1 . También evita el problema anterior de etiquetar primero/etiquetar al final manteniéndolos dentro de las implementaciones del algoritmo (en mi humilde opinión, como creo que debería). Mi equipo no está implementando esto, por lo que no es nuestra decisión, PERO si tuviéramos que tomar una decisión y comenzar a implementar, seguramente tomaríamos esta ruta.

Evite la interfaz INonce , una simple byte[] o span<> debería ser suficiente para una interfaz compatible de bajo nivel.

IV vs Nonce: el caso generalizado es de hecho IV. Para GCM, se requiere que el IV sea un Nonce (por ejemplo, Car vs RedOrCar). Y mientras estoy copiando y pegando esto, noté que @timovzl usó un ejemplo muy similar :)

@sidshetye ¿Puede hacer una propuesta precisa de que (1) admite algoritmos que necesitan la etiqueta por adelantado y (2) solo necesita la etiqueta hasta TryFinish en todas las demás situaciones?

¿Supongo que estás pensando algo en la siguiente dirección?

  • Se permite que la etiqueta en Initialize sea ​​nula. Solo aquellos algoritmos que lo necesitan por adelantado arrojarán un valor nulo.
  • La etiqueta en TryFinish es obligatoria o (alternativamente) se permite que sea nula para aquellos algoritmos que ya la han requerido por adelantado.

Supongo que lo anterior solo agrega complejidad en forma de documentación y conocimientos. Para una API de bajo nivel, esto podría considerarse un pequeño sacrificio, ya que de todos modos se requiere la documentación y los conocimientos adecuados.

Estoy empezando a convencerme de que esto debería ser posible, por compatibilidad con otras implementaciones y transmisión.

@timovzl Claro, espero presupuestar algo de tiempo mañana para esto.

@Timovzl , terminé teniendo tiempo hoy y resultó ser una madriguera de conejo. Esto es largo, pero creo que captura la mayoría de los casos de uso, captura las fortalezas de .NET crypto ( ICryptoTransform ) mientras adopta la dirección .NET Core/Standard ( Span<> ). He vuelto a leer, pero espero que no haya errores tipográficos a continuación. También creo que algunas comunicaciones en tiempo real (chat, llamada de conferencia, etc.) son vitales para una lluvia de ideas rápida; Espero que puedas considerar eso.

modelo de programación

Primero hablaré sobre el modelo de programación resultante para los usuarios de la API.

Cifrado de transmisión

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

Transmisión de descifrado

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

sin transmisión

Dado que la no transmisión es un caso especial de transmisión, podemos envolver el código de usuario anterior en métodos auxiliares en AuthenticatedSymmetricAlgorithm (definidos a continuación) para exponer una API más simple. es decir

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); 
  ...
}

Esto puede funcionar como la presentación de una API más simple como

Cifrado sin transmisión

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

Descifrado sin transmisión

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

Bajo el capó

Mirando la fuente de corefx, Span<> está en todas partes. Esto incluye System.Security.Cryptography.* - a excepción de los cifrados simétricos, así que arreglemos ese primer cifrado autenticado en la capa superior.

1. Cree ICipherTransform para E/S de tramo

Esto es como una versión consciente de Span de ICryptoTransform . Simplemente cambiaría la interfaz en sí como parte de la actualización del marco, pero dado que las personas pueden ponerse delicadas al respecto, lo llamaré 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);
}

También marque ICryptoTransform como [Obsolete]

Ser cortés con las personas con conocimientos previos de .NET crypto

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

2. Ampliar la clase 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. Ampliar el CryptoStream existente para E/S de tramo

Esto es como Stream en System.Runtime. Además, agregaremos un c'tor para que siga nuestro caso AEAD.

CRÍTICO: CryptoStream necesitará una actualización obligatoria en FlushFinalBlock para agregar la etiqueta al final de la transmisión durante el cifrado y extraer automáticamente la etiqueta (bytes TagSize) durante el descifrado . Esto es similar a otras API probadas en batalla como la arquitectura criptográfica de Java o C# BouncyCastle. Este es inevitable, pero es el mejor lugar para hacerlo, ya que en la transmisión, la etiqueta se produce al final, pero no se necesita hasta que el bloque final se transforma durante el descifrado. La ventaja es que simplifica enormemente el modelo de programación.

Nota: 1) Con CBC-HMAC, puede optar por verificar la etiqueta primero. Es la opción más segura, pero si es así, en realidad lo convierte en un algoritmo de dos pasos. El primer paso calcula la etiqueta HMAC y luego el segundo paso hace el descifrado. Por lo tanto, el flujo de memoria o el flujo de red siempre tendrá que almacenarse en la memoria, reduciéndolo al modelo de una sola vez; sin transmisión. Los verdaderos algoritmos AEAD como GCM o CCM pueden transmitir de manera eficiente.

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`
    ...
  }

  ...
}

Capa en cifrado autenticado

Con lo anterior, podemos agregar los bits que faltan para permitir el Cifrado Autenticado con Datos Asociados (AEAD)

Ampliar el nuevo ICipherTransform para AEAD

Esto permite que CryptoStream pueda hacer su trabajo correctamente. También podemos usar la interfaz IAuthenticatedCipherTransform para implementar nuestra propia clase/uso de transmisión personalizado, pero trabajar con CryptoStream lo convierte en una API criptográfica .net súper cohesiva y consistente.

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

Clase base de cifrado autenticado

Simplemente 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 {...}
  ...
}

Clase 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 Aplaudo el esfuerzo.

Streaming-Encrypt sobre GCM es factible. Streaming-Decrypt sobre GCM es

  • no permitido en NIST 800-38d. La Sección 5.2.2 "Función de descifrado autenticado" es muy clara en cuanto a que la devolución del texto sin formato P descifrado debe implicar una autenticación correcta a través de la etiqueta T.
  • no es seguro. Existe una noción de seguridad de que los algoritmos son seguros en la configuración "Lanzamiento de texto sin formato no verificado" (RUP). La seguridad de RUP se formaliza en un documento de 2014 de Andreeva-et-al. GCM no es seguro en la configuración de RUP. La competencia de CAESAR donde cada entrada se compara con GCM enumera la seguridad de RUP como una propiedad deseable. El texto sin formato no verificado publicado por GCM es trivialmente propenso a ataques de volteo de bits.

Anteriormente en este hilo se planteó la posibilidad de API de cifrado/descifrado asimétricas (conceptualmente), y creo que el consenso fue que sería una muy mala idea.

En resumen, no puede tener una API de transmisión granular de bytes de alto nivel para el descifrado de GCM. Lo dije muchas veces antes, y lo estoy diciendo de nuevo. La única forma de tener una API de transmisión es el cifrado fragmentado. Les ahorraré a todos el tiovivo del cifrado fragmentado.

Independientemente de lo que MS decida hacer para la API de GCM, no se puede permitir RUP.

@sdrapkin RUP se discutió aquí en detalle y ya hemos cruzado ese puente. Brevemente, RUP implica que no es necesario utilizar los datos descifrados hasta que se verifique la etiqueta, pero al igual que Java JCE, WinNT bcrypt, OpenSSL, etc., no es necesario aplicarlos en el límite del método. Al igual que con la mayoría de las primitivas criptográficas, especialmente las de bajo nivel, utilícelas con precaución.

^^^ que tanto. Estoy de acuerdo con las API de transmisión de nivel superior, etc., entonces aplíquelo bien. Pero cuando quiero usar una primitiva de bajo nivel, necesito poder usar cosas como búferes divididos, etc., y depende de mí asegurarme de que los datos no se usen. Inicie una excepción en el cálculo de la etiqueta/punto de control, pero no limite las primitivas de bajo nivel.

Depende de mí asegurarme de que los datos no se utilicen.

Incorrecto. No depende de ti. AES-GCM tiene una definición muy _específica_, y esa definición asegura que no depende de usted. Lo que desea es una primitiva AES-CTR separada y una primitiva GHASH separada, que luego podría combinar y aplicar como mejor le parezca. Pero no estamos hablando de primitivas AES-CTR y GHASH separadas, ¿verdad? Estamos hablando de AES-GCM. Y AES-GCM requiere que RUP no esté permitido.

También sugiero revisar la respuesta de Ilmari Karonen de crypto.stackexchange .

@sdrapkin Tienes un buen punto. Sería deseable, sin embargo, tener eventualmente un algoritmo que sea seguro bajo RUP, y que ese algoritmo se ajuste a la API que se decide aquí. Así que tenemos que elegir:

  1. La API no admite la transmisión. Simple, pero sin una API de bajo nivel. Podríamos arrepentirnos de esto algún día.
  2. La implementación de AES-GCM evita la transmisión y se adhiere a la especificación sin limitar la API.

¿Podemos detectar el escenario de transmisión y lanzar una excepción que explique por qué este uso es incorrecto? ¿O debemos recurrir a hacer que su implementación de transmisión consuma todo el búfer? Esto último sería desafortunado: podría pensar que está transmitiendo, pero no es así.

Podríamos agregar un método SupportsStreaming(out string whyNot) que sea verificado por la implementación de transmisión.

¿Tenemos un argumento sólido en contra de la transmisión/etiquetado al final en general ? Si no es así, creo que deberíamos tratar de no impedirlo con la API.

@sdrapkin : Vamos a tener una visión más amplia de RUP, ya que se trata de una biblioteca, no de una aplicación. Así que esto es más un problema de almacenamiento en búfer y diseño de capas que la liberación/uso real de datos no verificados. Mirando la publicación especial NIST 800-38D para AES GCM , vemos que

  1. La especificación GCM define la longitud máxima del texto sin formato en 2^39-256 bit ~ 64 GB. El almacenamiento en búfer en cualquier lugar cercano a eso en la memoria del sistema no es razonable.

  2. La especificación GCM definió la salida como FAIL si la etiqueta falla. Pero no es prescriptivo sobre qué capa en una implementación debe almacenar en búfer hasta la verificación de la etiqueta. Veamos una pila de llamadas 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)

Donde
A es AES GCM en la capa de aplicación
B es AES-GCM en la capa de idioma
C es AES-GCM en la capa de plataforma

El texto sin formato se publica en (A) si la etiqueta se verifica, pero se devuelve FALLO en caso contrario. Sin embargo, en ninguna parte de la especificación sugiere que la memoria principal sea el único lugar para almacenar en búfer el texto sin formato en curso, ni que el almacenamiento en búfer deba ocurrir en (B) o (C) o en otro lugar. De hecho, OpenSSL, Windows NT Bcrypt en el ejemplo de (C) donde la transmisión permite el almacenamiento en búfer en la capa superior. Y Java JCA, CLR Security de Microsoft y mi propuesta anterior son ejemplos de (B) donde la transmisión permite el almacenamiento en búfer en la capa de aplicación. Es presuntuoso suponer que los diseñadores de A no tienen mejores capacidades de almacenamiento en búfer antes de lanzar texto sin formato. Ese búfer, en teoría y en la práctica, podría ser memoria o SSD o un clúster de almacenamiento en la red. O tarjetas perforadas ;) !

Incluso dejando de lado el almacenamiento en búfer, el documento analiza otras preocupaciones prácticas (consulte la Sección 9.1, Consideraciones de diseño y 9.2, Consideraciones operativas) como la frescura de las claves o la no repetición de IV en fallas indefinidas de energía. Obviamente no estaremos horneando esto en la capa B, es decir, aquí.

@timovzl, la propuesta reciente anterior aborda ambos escenarios: una sola vez (el arquitecto no se preocupa por el almacenamiento en búfer) y la transmisión (el arquitecto tiene mejores capacidades de almacenamiento en búfer). Siempre que la documentación de la API de transmisión de bajo nivel deje claro que el consumidor ahora es responsable de almacenarla en búfer, no hay reducción de la prueba de seguridad y no hay desviación de la especificación.

EDITAR: gramática, errores tipográficos y tratando de hacer funcionar el descuento

Bingo... Una vez más, es decisión de los diseñadores de capas dónde se realiza la verificación de la etiqueta. De ninguna manera estoy abogando por la liberación de datos no verificados a la capa de aplicación.

La discusión vuelve a ser estas API de nivel de "consumidor" o son verdaderas primitivas. Si son verdaderos primitivos, entonces deberían exponer la funcionalidad para que se puedan construir API de nivel superior "más seguras". Ya se decidió anteriormente con la discusión de Nonce que estos deberían ser verdaderos primitivos, lo que significa que podrías pegarte un tiro en el pie, creo que lo mismo se aplica a la transmisión/descodificación parcial del texto cifrado.

Al decir eso, será imperativo proporcionar el nivel "más alto" y las API más seguras rápidamente para evitar que las personas "hagan rodar" sus propias API sobre estas.

Mi interés proviene de las redes / canalizaciones y si no puede hacer búferes parciales y tiene que hacer "una sola vez", entonces no habría ningún beneficio para la desventaja de estas API, por lo que continuaría yendo a BCrypt / OpenSsl, etc. directamente.

Mi interés proviene de las redes / canalizaciones y si no puede hacer búferes parciales y tiene que hacer "una sola vez", entonces no habría ningún beneficio para la desventaja de estas API, por lo que continuaría yendo a BCrypt / OpenSsl, etc. directamente.

Exactamente. La necesidad no desaparecerá, por lo que la gente usará otras implementaciones o creará las suyas propias. Eso no es necesariamente un resultado más seguro que permitir la transmisión con una buena documentación de advertencia.

@Timovzl , creo que obtuvimos muchos comentarios técnicos y requisitos de diseño en torno a la última propuesta . ¿Pensamientos sobre la implementación y el lanzamiento?

@Sidshetye ha hecho una propuesta detallada que creo que aborda todos los requisitos. La única crítica, relativa a RUP, ha sido atendida sin más oposición. (Específicamente, RUP puede evitarse en una de varias capas, y la API de bajo nivel no debe dictar cuál; ​​y se espera que ofrecer _no_ streaming tenga peores efectos).

En aras del progreso, me gustaría invitar a cualquier persona que tenga más inquietudes con la última propuesta a que hable y, por supuesto, ofrezca alternativas.

Estoy entusiasmado con esta propuesta y con la forma de una API.

@Sidshetye , tengo algunas preguntas y sugerencias:

  1. ¿Es deseable heredar del SymmetricAlgorithm existente? ¿Hay algún componente existente con el que queramos integrarnos? A menos que me esté perdiendo alguna ventaja de ese enfoque, preferiría ver AuthenticatedEncryptionAlgorithm sin clase base. Por lo menos, evita exponer métodos no deseados CreateEncryptor / CreateDecryptor (¡no autenticados!).
  2. Ninguno de los componentes involucrados se puede usar con criptografía asimétrica, ¿sí? Casi todos los componentes omiten "Symmetric" de sus nombres, algo con lo que estoy de acuerdo. A menos que sigamos heredando SymmetricAlgorithm , AuthenticatedSymmetricAlgorithm podría cambiarse de nombre a AuthenticatedEncryptionAlgorithm , adhiriéndose al término convencional Cifrado autenticado.
  3. Cambie TryEncrypt / TryDecrypt para escribir o recibir la etiqueta, en lugar de tener una propiedad configurable Tag en el algoritmo.
  4. ¿Cuál es el propósito de establecer key , iv y authenticatedAdditionalData a través de setters públicos? Me mantendría alejado de múltiples enfoques válidos y de propiedades mutables tanto como sea posible. ¿Podría crear una propuesta actualizada sin ellos?
  5. ¿Queremos algún estado en AesGcm ? Mi instinto es definitivamente dejar fuera iv y authenticatedAdditionalData , ya que estos son por mensaje. Puede valer la pena tener key como estado, ya que generalmente queremos realizar múltiples operaciones con una sola tecla. Aún así, también es posible tomar la llave por llamada. Las mismas preguntas se aplican a CreateAuthenticatorEncryptor . En cualquier caso, deberíamos decidirnos por _one way_ para pasar los parámetros. Estoy ansioso por discutir los pros y los contras. Me inclino por el estado clave en AesGcm , y el resto en CreateAuthenticatedEncryptor o TryEncrypt respectivamente. Si ya estamos de acuerdo, por favor muéstranos una propuesta actualizada. :-)
  6. ICipherTransform probablemente debería ser una clase abstracta, CipherTransform , para que se puedan agregar métodos sin interrumpir las implementaciones existentes.
  7. Todos los parámetros de la función deben usar camelCase, es decir, comenzar en minúsculas. Además, ¿deberíamos decir authenticatedData o authenticatedAdditionalData ? Además, creo que deberíamos elegir nombres de parámetros plaintext y ciphertext .
  8. Dondequiera que se pase el IV, me gustaría verlo como un parámetro opcional, lo que hace que sea más fácil obtener un IV generado correctamente (criptoraleatorio) que proporcionar el nuestro. Hace que el uso indebido de la API de bajo nivel sea más difícil, al menos, y lo obtenemos de forma gratuita.
  9. ¡Todavía estoy tratando de averiguar cómo el código de cliente TryEncrypt puede saber la longitud de tramo requerida para proporcionar ciphertext ! Lo mismo para TryDecrypt y la longitud de plaintext . Seguramente no se supone que debemos probarlos en un bucle hasta el éxito, duplicando la longitud después de cada iteración fallida.

Finalmente, pensando en el futuro, ¿cómo sería una API de alto nivel construida sobre esto? Mirando puramente el uso de la API, parece que hay poco margen de mejora, ya que tanto la API de transmisión como la de no transmisión ya son muy sencillas. Las principales diferencias que imagino son un IV automático, tamaños de salida automáticos y posiblemente un límite en la cantidad de datos cifrados.

Windows permite la transmisión. OpenSSL también lo hace. Ambos en su mayoría lo encasillaron en conceptos existentes (aunque ambos lanzaron una llave inglesa con "y hay esta cosa en el lado con la que tienes que lidiar o me equivocaré").

Go no lo hace, y libsodium tampoco.

Parece que la primera ola lo permitió y las posteriores no. Dado que indiscutiblemente estamos en una ola posterior, creo que nos vamos a quedar con no permitirlo. Si hay una mayor demanda de transmisión después de introducir un modelo único (para el cifrado/descifrado, la clave se puede mantener entre llamadas), entonces podemos volver a evaluar. Entonces, una propuesta de API que se adhiere a ese patrón parece beneficiosa. Aunque ni SIV ni CCM admiten el cifrado de transmisión, por lo que la API de transmisión para ellos es potencialmente un gran almacenamiento en búfer. Mantener las cosas claras parece mejor.

Las propuestas tampoco deben incrustar la etiqueta en la carga útil (GCM y CCM lo mencionan como un dato separado), a menos que el propio algoritmo (SIV) lo incorpore en la salida del cifrado. ( E(...) => (c, t) frente a E(...) => c || t o E(...) => t || c ). Los usuarios de la API ciertamente pueden usarla como concatenación (simplemente abra los Spans apropiadamente).

La especificación GCM no permite la liberación de nada que no sea FAIL en caso de discrepancia de etiquetas. NIST es bastante claro al respecto. El artículo original de GCM de McGrew & Viega también dice:

La operación de descifrado devolvería FALLO en lugar del texto sin formato, y la desencapsulación se detendría y el texto sin formato se descartaría en lugar de reenviarse o procesarse más.

Ninguno de los comentarios anteriores abordó RUP; simplemente lo descartaron ("la capa superior se encargará de eso", sí, claro).

Es simple: el cifrado GCM puede transmitir. El descifrado de GCM no puede transmitir. Cualquier otra cosa ya no es GCM.

Parece que la primera ola lo permitió y las posteriores no. Dado que indiscutiblemente estamos en una ola posterior, creo que nos vamos a quedar con no permitirlo.

@bartonjs , ¿está literalmente ignorando todo el análisis técnico y lógico y, en su lugar, está utilizando las fechas de los proyectos Go y libsodium como un proxy débil para el análisis real? Imagínese si hago un argumento similar basado en los nombres de los proyectos. Además, estamos decidiendo la interfaz y las implementaciones. Se da cuenta de que decidirse por una interfaz sin transmisión para AEAD excluye todas esas implementaciones en el futuro, ¿verdad?

Si hay una mayor demanda de transmisión después de introducir un modelo único (para el cifrado/descifrado, la clave se puede mantener entre llamadas), entonces podemos volver a evaluar.

¿Por qué la demanda demostrada hasta ahora en GitHub es insuficiente? Se está llegando al punto en que parece completamente caprichoso apoyar el hecho de tener que hacer menos trabajo que cualquier mérito técnico o de demanda del cliente.

@bartonjs , ¿está literalmente ignorando todo el análisis técnico y lógico y, en su lugar, está utilizando las fechas de los proyectos Go y libsodium como un proxy débil para el análisis real?

No, estoy siguiendo el consejo de criptógrafos profesionales que dicen que es extraordinariamente peligroso y que debemos evitar transmitir AEAD. Luego estoy usando información del equipo de CNG de "muchas personas dicen que lo quieren en teoría, pero en la práctica casi nadie lo hace" (no sé cuánto de eso es telemetría versus anecdótico de solicitudes de asistencia de campo). El hecho de que otras bibliotecas hayan tomado la ruta de una sola vez simplemente _refuerza_ la decisión.

¿Por qué la demanda demostrada hasta ahora en GitHub es insuficiente?

Se han mencionado algunos escenarios. El procesamiento de búferes fragmentados probablemente podría abordarse aceptando ReadOnlySequence , si parece que hay suficiente escenario para justificar la complicación de la API en lugar de que la persona que llama vuelva a ensamblar los datos.

Los archivos grandes son un problema, pero los archivos grandes ya son un problema ya que GCM tiene un límite de apenas 64 GB, que es "no tan grande" (está bien, es bastante grande, pero no es el "vaya, eso es grande" que solía ser). Los archivos asignados a la memoria permitirían utilizar Spans (de hasta 2^31-1) sin necesidad de 2 GB de RAM. Así que hemos recortado un par de bits del máximo... eso probablemente sucederá con el tiempo de todos modos.

Se da cuenta de que decidirse por una interfaz sin transmisión para AEAD excluye todas esas implementaciones en el futuro, ¿verdad?

Cada vez estoy más convencido de que @GrabYourPitchforks tenía razón (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891) de que probablemente no haya una interfaz unificadora sensata. GCM _requiriendo_ un nonce/IV y SIV _prohibiendo_ significa que la inicialización de un modo/algoritmo AEAD ya requiere conocimiento sobre lo que va a suceder... no hay realmente una noción "abstraída" para AEAD. SIV dicta dónde va "la etiqueta". GCM/CCM no. SIV es la etiqueta primero, por especificación.

SIV no puede comenzar a cifrar hasta que tenga todos los datos. Por lo tanto, su cifrado de transmisión se lanzará (lo que significa que debe saber que no debe llamarlo) o se almacenará en el búfer (lo que podría resultar en n ^ 2 de tiempo de operación). CCM no puede comenzar hasta que se conozca la duración; pero CNG no permite una pista preencriptada sobre la longitud, por lo que está en el mismo barco.

No deberíamos diseñar un nuevo componente en el que sea más fácil hacer lo incorrecto que lo correcto por defecto. El descifrado de transmisión hace que sea muy fácil y tentador conectar una clase Stream (al estilo de su propuesta para hacerlo con CryptoStream), lo que hace que sea muy fácil obtener un error de validación de datos antes de que se verifique la etiqueta, lo que anula casi por completo el beneficio de AE . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "espera, eso no es XML legal..." => oráculo de texto cifrado adaptable) .

Está llegando al punto... la demanda del cliente.

Como, lamentablemente, he escuchado demasiadas veces en mi vida: lo siento, pero usted no es el cliente que tenemos en mente. Reconozco que tal vez sepa cómo hacer GCM de forma segura. Sabe que solo debe transmitir a un archivo/búfer/etc. volátil hasta después de la verificación de la etiqueta. Sabe lo que significa la gestión de nonce y conoce los riesgos de equivocarse. Debe prestar atención a los tamaños de transmisión y pasar a un nuevo segmento de GCM después de 2^36-64 bytes. Sabes que después de que todo está dicho y hecho, es tu error si te equivocas en esas cosas.

El cliente que tengo en mente, por otro lado, es alguien que sabe "tengo que encriptar esto" porque su jefe se lo dijo. Y saben que al buscar cómo hacer el cifrado, algún tutorial decía "siempre use AE" y menciona GCM. Luego encuentran un tutorial de "cifrado en .NET" que usa CryptoStream. Luego conectan la canalización, sin tener idea de que acaban de hacer lo mismo que elegir SSLv2... marcaron una casilla en teoría, pero no realmente en la práctica. Y cuando lo hacen, ese error pertenece a todos los que sabían mejor, pero deja que lo incorrecto sea demasiado fácil de hacer.

usted no es el cliente que tenemos en mente [...] El cliente que tengo en mente, en cambio, es alguien que sabe "tengo que encriptar esto" porque su jefe le dijo que [...]

@bartonjs meses atrás, ya habíamos decidido que el objetivo era apuntar a dos perfiles de clientes al tener una API de bajo nivel (poderosa pero insegura bajo ciertas condiciones) y una API de alto nivel (infalible). Incluso está en el título. Ciertamente es un país libre, pero es falso mover ahora el poste de la portería afirmando lo contrario.

El cliente que tengo en mente, por otro lado, es alguien que sabe "tengo que encriptar esto" porque su jefe se lo dijo. Y saben que al buscar cómo hacer el cifrado, algún tutorial decía "siempre use AE" y menciona GCM. Luego encuentran un tutorial de "cifrado en .NET" que usa CryptoStream. Luego conectan la canalización, sin tener idea de que acaban de hacer lo mismo que elegir SSLv2... marcaron una casilla en teoría, pero no realmente en la práctica. Y cuando lo hacen, ese error pertenece a todos los que sabían mejor, pero que lo incorrecto sea demasiado fácil de hacer.

@bartonjs Espera, ¿qué pasó con un primitivo de bajo nivel? Pensé que el objetivo de este número en particular era la flexibilidad sobre el cuidado de los niños. Definitivamente háganos saber si el plan ha cambiado, para que todos estemos hablando de lo mismo.

Además, ¿se siguen considerando los métodos por bloque o solo los métodos de una sola vez?

Cada vez estoy más convencido de que @GrabYourPitchforks tenía razón (#23629 (comentario)) de que probablemente no haya una interfaz unificadora sensata.

Al ver todos los ejemplos, esto comienza a parecer cada vez más inútil, especialmente para una API de bajo nivel donde las implementaciones tienen restricciones tan diferentes. Tal vez deberíamos simplemente establecer un ejemplo sólido con AES-GCM, en lugar de una interfaz unificadora. Como nota al margen, este último podría seguir siendo interesante para una futura API de alto nivel. Su propiedad de ser más restrictiva probablemente hará que una interfaz unificadora sea mucho más fácil de lograr allí.

¿Todavía se están considerando los métodos por bloque, o solo los métodos de una sola vez?

Como se menciona en https://github.com/dotnet/corefx/issues/23629#issuecomment -378605071, creemos que los casos de riesgo frente a recompensa frente a uso expreso dicen que solo debemos permitir versiones únicas de AE.

No he leído toda la discusión, solo partes aleatorias. No sé en qué dirección vas. Lo siento si lo que escribo no tiene sentido en este contexto. Mis 2¢:

  • Los arroyos son importantes. Si no puede admitirlos directamente porque eso significaría una vulnerabilidad de seguridad, entonces, si es posible, proporcione un contenedor de nivel superior construido sobre su API de bajo nivel que expondría las transmisiones (de una manera ineficiente pero segura).
  • Si AES-GCM absolutamente no puede usar flujos en ningún escenario, proporcione una implementación legítima de AES-CBC-HMAC que se base en flujos. O algún otro algoritmo AE.
  • Cuanto más alto sea el nivel, mejor. Cuantas menos áreas puedan cometer un error por parte del usuario, mejor. Significado: exponga la API que ocultaría la mayor cantidad de cosas posible (por ejemplo, esta etiqueta de autenticación). Por supuesto, también puede (debería) haber sobrecargas más específicas.
  • En mi humilde opinión, no se moleste en unificar las interfaces con otros servicios de encriptación si simplemente no encajan. Eso es lo que ha hecho openssl con su CLI y el resultado es pobre (por ejemplo, no hay posibilidad de proporcionar la etiqueta de autenticación).

Nosotros (el equipo de seguridad de .NET) consultamos entre nosotros y con el equipo criptográfico más amplio dentro de Microsoft. Discutimos muchos de los problemas y preocupaciones mencionados en este hilo. En última instancia, estas preocupaciones no fueron lo suficientemente persuasivas como para justificar la introducción de una API GCM de transmisión como un componente básico dentro del marco.

Esta decisión puede revisarse en el futuro si surge la necesidad. Y mientras tanto, no hemos empeorado las cosas de lo que son hoy: los desarrolladores que actualmente usan una biblioteca criptográfica de terceros para transmitir soporte de GCM pueden continuar haciéndolo, y no se verán afectados por nuestra introducción prevista de un API de GCM sin transmisión.

¿Cómo lidiar con el cifrado de datos que no caben en la memoria?

@pgolebiowski Utiliza bibliotecas criptográficas .NET de alto nivel diseñadas específicamente para ofrecer cifrado de transmisión seguro.

@sdrapkin esto es más fácil decirlo que hacerlo. "seguro" es mucho pedir. ¿Qué hay que esté probado y realmente se pueda confiar? tu mismo dices:

Biblioteca Bouncy Castle C# (una recomendación típica de StackOverflow). Bouncy Castle c# es un enorme (145k LOC) catálogo de museo de criptografía (algunas de ellas antiguas) de bajo rendimiento, con implementaciones antiguas de Java portadas a .NET igualmente antiguas (¿2.0?).

bien, ¿cuáles son las opciones? tal vez su propia biblioteca? no realmente hmm... tal vez libsodium-net? no realmente

cuando realmente busca una biblioteca auditada que proviene de una fuente bastante confiable (como Microsoft o ampliamente utilizada por la comunidad), no creo que exista tal biblioteca en el mundo de .NET Core.


  • cliente: cifrado autenticado de datos que no caben en la memoria?
  • microsoft: lo siento, pero usted no es el cliente que tenemos en mente. use una biblioteca que no está auditada y su seguridad es cuestionable, no es nuestro problema si está bajo un ataque de canal lateral.
  • cliente:

@pgolebiowski Las opciones son usar un marco .NET establecido, es decir. lo mismo que ha sido probado y en el que se puede confiar, como usted desea. Otras bibliotecas (incluida la mía) esperan a que Microsoft agregue primitivos criptográficos faltantes como ECDH en NetStandard.

También puedes mirar las horquillas Inferno. Hay al menos 2 bifurcaciones donde algunos cambios triviales lograron NetStandard20.

Su biblioteca fue auditada hace 2 años en 2 días por un tipo . No confío en eso, lo siento. En Microsoft, habría un equipo dedicado para eso, de personas en las que tengo más confianza que las de Cure53.

En serio, podemos hablar de soporte de terceros para muchas cosas. Pero todo el material necesario relacionado con la seguridad debe ser proporcionado por la biblioteca estándar.

@pgolebiowski Lejos de mí tratar de convencer a alguien de que confíe en algo, pero su declaración no es precisa. Inferno fue auditado por 2 profesionales de la organización "Cure53". La auditoría tomó 2 días y toda la biblioteca tenía ~1000 líneas de código. Eso es ~250 líneas de código por auditor/día, bastante manejable.

De hecho, la fácil auditabilidad es una de las características clave de Inferno, precisamente para aquellos que no quieren confiar.

El objetivo de este hilo es agregar soporte para AES-GCM. Su biblioteca ni siquiera es compatible con AES-GCM. Si desea que la gente use su código, envíelo como una propuesta para corefx. En un hilo apropiado.

Otra cosa, incluso si fuera compatible con este algoritmo, no ha sido revisado por la junta criptográfica de .net y no es parte de corefx. Ni siquiera es candidato para tal revisión. Esto significa el final de esta discusión y publicidad inútiles.

@pgolebiowski No anuncié nada, simplemente respondí su pregunta, sugerí alternativas para su caso de uso y corregí reclamos inexactos. AES-GCM no es adecuado para el cifrado de transmisión, y eso es algo que ha sido revisado y acordado por el equipo de seguridad de .NET, por lo que puede confiar en eso .

¿Estoy defendiendo la transmisión de AES-GCM en cualquier lugar? no puedo recordar pero recuerdo haber dicho:

  • Los arroyos son importantes. Si no puede admitirlos directamente porque eso significaría una vulnerabilidad de seguridad, entonces, si es posible, proporcione un contenedor de nivel superior construido sobre su API de bajo nivel que expondría las transmisiones (de una manera ineficiente pero segura).
  • Si AES-GCM absolutamente no puede usar flujos en ningún escenario, proporcione una implementación legítima de AES-CBC-HMAC que se base en flujos. O algún otro algoritmo AE.

o indicando un problema abierto para corefx:

¿Cómo lidiar con el cifrado de datos que no caben en la memoria?


prima:

También puedes mirar las horquillas Inferno. Hay al menos 2 bifurcaciones donde algunos cambios triviales lograron NetStandard20. [...] Inferno fue auditado por 2 profesionales de la organización "Cure53" [...] la fácil auditabilidad es una de las características clave de Inferno, precisamente para aquellos que no quieren confiar.

no anuncie nada

Solo como un aparte, nunca quise "transmitir" en el sentido en que la mayoría piensa. Solo quería el procesamiento de "bloques" por motivos de rendimiento y uso de memoria. En realidad, no quiero "transmitir" los resultados. Esto es lo que parece una pena perder el soporte de openssl y cng y básicamente hace que los "primativos" sean inútiles en cualquier escenario que se me ocurra.

@Drawaes Ahora que lo pienso, la operación de bloque podría ser mucho más segura que usar transmisiones. Un profano puede tocar las transmisiones, pero preferirá usar la API de un solo uso que la operación de bloqueo. Además, la operación de bloque no se puede combinar _sencillamente_ con, digamos, XmlReader . Entonces, en realidad, muchos de los peligros discutidos se aplican a los objetos de flujo, pero no a la operación de bloqueo.

Para trabajar con la operación de bloques cuando también está disponible una API única, sugiere que sabemos lo que estamos haciendo y que necesitamos específicamente ajustes de bajo nivel. Podríamos proteger al profano _y_ tener flexibilidad.

En cuanto a evitar RUP, todavía me estoy preguntando qué tan ventajosa es realmente la operación de bloques para GCM. El cifrado obtiene todos los beneficios, mientras que el descifrado se beneficia solo un poco. Podemos evitar almacenar el texto cifrado completo, pero aún debemos almacenar en búfer el texto sin formato completo. Un descifrador _podría_ optar por almacenar el texto sin formato intermedio en el disco. Pero a cambio, hemos introducido más margen de error. ¿Tenemos un argumento convincente para no resolver este problema en un nivel superior (por ejemplo, dividirlo allí o usar un algoritmo de transmisión real)?

TLS y canalizaciones. Actualmente (y en el futuro previsible), las canalizaciones utilizan bloques de 4k, pero un mensaje tls puede ser de 16k de texto cifrado. Con un solo disparo, deberá copiar los 16k en un solo búfer contingente antes de poder descifrar. Con los bloques, es posible que tenga 4 o 5 y es posible que necesite almacenar en búfer hasta 16 bytes para garantizar que los bloques compitan.

@Drawaes 16k sigue siendo constante y no enorme. ¿Hace mucha diferencia en este contexto?

Sí, significa otra copia en preparación. Esto tiene un efecto importante y medible en el rendimiento.

¿Qué se necesita para que esto suceda? ¿Cuáles son los siguientes pasos? @Drawaes

En cuanto a AES-GCM, creo que su entrega se ve afectada debido al bloqueo del problema correspondiente: https://github.com/dotnet/corefx/issues/7023. @blowdart , ¿podrías desbloquear? Es realmente difícil tener progreso cuando la gente no puede discutir. O, si esa no es una opción, tal vez proponga una solución alternativa que permita llevar esta función al público.

No, no voy a desbloquear eso. Se ha tomado una decisión, el tema está hecho.

@blowdart Gracias por la respuesta. Entiendo que tal vez esto no fue lo suficientemente claro:

O, si esa no es una opción, tal vez proponga una solución alternativa que permita llevar esta función al público.

Agradezco que haya una decisión de apoyo a AES-GCM. Esto es genial, definitivamente quiero ese algoritmo. Por lo tanto, ahora sería genial tenerlo compatible. ¿Le gustaría que la discusión sobre el diseño e implementación de AES-GCM se lleve a cabo aquí o en una nueva edición?

Además, si ese tema está hecho, ¿por qué no cerrarlo? Y hazlo más explícito cambiando el título de ese problema, ya que en este momento sugiere que la discusión sobre la implementación se llevaría a cabo aquí: https://github.com/dotnet/corefx/issues/7023. Tal vez algo como Decidir qué algoritmo AEAD admitir primero .

En otras palabras: proporciono comentarios de que en la situación actual no está claro qué se necesita para impulsar AES-GCM.

@karelz

@pgolebiowski Ya hay un PR out. Probablemente estará disponible en master el miércoles de la próxima semana.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

jamesqo picture jamesqo  ·  3Comentarios

Timovzl picture Timovzl  ·  3Comentarios

omariom picture omariom  ·  3Comentarios

GitAntoinee picture GitAntoinee  ·  3Comentarios

matty-hall picture matty-hall  ·  3Comentarios