pregunta básica sobre OpenSSL y AES-GCM

2

Mientras investigaba cómo cifrar las claves privadas para las conexiones SSH de la forma más segura posible, me he encontrado con los siguientes problemas de comprensión muy básicos (Nota: he utilizado la versión 1.0.2h más nueva de OpenSSL para mi investigación):

  1. man enc indica que la utilidad enc no admite modos de cifrado autenticados como GCM. Por otro lado, estoy utilizando sistemas de cifrado TLS 1.2 como ECDHE-RSA-AES128-GCM-SHA256 en mi servidor web que utiliza OpenSSL para cifrar las conexiones, y he comprobado que esos sistemas de cifrado se negocian cuando se conecta mediante SSL / TLS al respectivo sitio web.

    Obviamente, OpenSSL puede cifrar datos en el modo AESxxx-GCM. Hubiera esperado que OpenSSL use el mismo código para cifrar los datos cuando se los invoca a través de openssl enc que el que usa para cifrar los datos al tiempo que proporciona la funcionalidad SSL / TLS a los servidores web.

    ¿Entendí mal algo? ¿Cuál es la razón por la que OpenSSL admite AESxxx-GCM mientras realiza TLS / SSL, pero no puede cifrar los datos directamente de esa manera cuando se los llama a través de la línea de comandos?

  2. Un método común para ver los nombres de cifrado disponibles (no las suites de cifrado) es llamar a openssl enc con un parámetro incorrecto, por ejemplo. %código%. Esto hace que openssl imprima una breve ayuda, incluidos los nombres de cifrado disponibles. En mi caso, la salida incluye múltiples cifrados AES-GCM. ¿Por qué openssl enc --help dice que es compatible con esos cifrados mientras que la página de manual respectiva afirma que no?

pregunta Binarus 01.07.2016 - 10:35
fuente

2 respuestas

5

OpenSSL implementa casi una docena de cifrados simétricos y varias docenas de combinaciones de modo de cifrado, pero proporciona una (casi) interfaz única a todas en el módulo EVP ( es decir, la función externa y los nombres de tipo que comienzan con EVP_ ) documentados aquí en línea o en el (reticulado) La página de manual para EVP_{Cipher,Encrypt,Decrypt}* y EVP_CIPHER_* y EVP_CIPHER_CTX_* en cualquier sistema Unixy con OpenSSL instalado. (Si lo construyó / instaló usted mismo en lugar de usar el administrador de paquetes o su equivalente para su sistema operativo o distro, es posible que necesite usar MANPATH u otras opciones man para encontrar las páginas man). De manera similar, numerosos algoritmos de resumen / hash y públicos se accede a los algoritmos de cifrado y firma clave (e híbridos) a través de interfaces genéricas; man evp . Pero los modos AEAD (GCM y CCM, más OCB planeado en 1.1.0) no se ajustan exactamente a la API genérica y requieren llamadas de control adicionales, consulte las secciones ' Modos GCM y OCB 'y' modo CCM 'en la página de manual anterior.

El módulo SSL y TLS (directorio de nivel superior ssl/ en la fuente) contiene código en t1_enc.c que hace diferentes EVP llamadas para suites AEAD (implementadas), de la misma manera que también maneja las variaciones para CBC y cifrados de flujo, cifrados obsoletos pero todavía codificados para 'exportar', manejo de TLS1.1 vs IV anterior y otras opciones y variaciones de protocolo.

Pero la línea de comando enc en apps/enc.c solo usa la interfaz genérica y no las especiales de AEAD, aunque hay una entrada no asignada en el rastreador de solicitudes (inicio de sesión invitado / invitado) para agregar esto. Las utilidades de la línea de comandos en general son envolturas mínimas alrededor de la funcionalidad en libssl y libcrypto, y si desea algo completo, pulido, conveniente, etc. la idea es que debe modificarlas o reemplazarlas. Para este caso, debe (debería) definir cómo se maneja la etiqueta, y posiblemente AAD, en el formato de archivo de texto cifrado, que actualmente es simple hasta el punto de ser trivial, y recuerde cualquier cambio que no funcione con el Millones de enc de archivos que los usuarios han almacenado en las últimas dos décadas no serán aceptados por nadie más que usted.

Recuerde también que enc con una contraseña (no clave real y IV usando -K mayúsculas y -iv ) usa un PBKDF muy pobre , una variante de PBKDF1 ver EVP_BytesToKey con solo una iteración .
Ver openssl: clave de recuperación y IV por frase de contraseña
y openssl enc usa md5 para hash the contraseña y la sal
y enlace (divulgación: mía). Preocuparse por usar sistemas de cifrado de mejores prácticas como AES256-GCM con este PBKDF es como el proverbial dorado de una vaca.

El mensaje de uso para openssl enc -invalid enumera todos los cifrados / modos simétricos en EVP, incluso aquellos que enc no son compatibles. Si te importa, podrías reportar esto como un error. openssl list-cipher-algorithms (espacio planificado en lugar del primer guión en 1.1.0) hace lo mismo, pero openssl list-cipher-commands (ídem) enumera solo aquellos que se pueden usar como comandos, que excluyen los de AEAD.

Finalmente, usted menciona pero en realidad no pregunta por SSH. Si quiere decir OpenSSH (que no es el único SSH), FYI OpenSSH antes del 6,5 ssh-keygen realmente usa OpenSSL libcrypto para escribir privatekeys en los formatos "heredados" de OpenSSL (tipos PEM RSA PRIVATE KEY , DSA PRIVATE KEY , EC PRIVATE KEY ) que también usan EVP_BytesToKey con una iteración. Pero las rutinas ssh-keygen y ssh y sshd usando OpenSSL read también pueden manejar el "nuevo" (¿aproximadamente 2000?) Formato PKCS # 8 de OpenSSL, tipo PEM ENCRYPTED PRIVATE KEY , que puede use PBKDF2 con 2048 iteraciones a través de 1.0.2 (válido alrededor de 2000, apenas adecuado ahora) y planificado configurable hasta INT_MAX en 1.1.0. OpenSSH a partir de 6.5 tiene una opción -o para su propio formato (no ASN.1 pero aún PEM) usando bcrypt, y fuerza esa opción para el tipo de clave ed25519 (que OpenSSL no admite, al menos no todavía).

    
respondido por el dave_thompson_085 04.07.2016 - 04:06
fuente
1

Debido a que hasta ahora, openssl enc no es compatible con AES-256-GCM, he escrito el siguiente código fuente de C para hacer lo que openssl enc haría:

(compile de esta manera gcc -Wall -lcrypto -o aes256gcm aes256gcm.c )

// AES-256-GCM with libcrypto
// gcc -Wall -lcrypto -o aes256gcm aes256gcm.c

// tag is 16 bytes long
// no AAD (Additional Associated Data)
// output format: tag is written just after cipher text (see RFC-5116, sections 5.1 and 5.2)

// KEY=a6a7ee7abe681c9c4cede8e3366a9ded96b92668ea5e26a31a4b0856341ed224
// IV=87b7225d16ea2ae1f41d0b13fdce9bba
// echo -n 'plain text' | ./aes256gcm $KEY $IV | od -t x1

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>

EVP_CIPHER_CTX *ctx = NULL;
unsigned char *iv = NULL;
unsigned char *buf_plain = NULL;
unsigned char *buf_cipher = NULL;

typedef enum { false, true } bool;

void freeCrypto() {
  if (ctx) {
    EVP_CIPHER_CTX_free(ctx);
    ctx = NULL;
  }
  CRYPTO_cleanup_all_ex_data();
  ERR_free_strings();

  if (iv) {
    free(iv);
    iv = NULL;
  }
  if (buf_plain) {
    free(buf_plain);
    buf_plain = NULL;
  }
  if (buf_cipher) {
    free(buf_cipher);
    buf_cipher = NULL;
  }
}

void handleCryptoError() {
  fprintf(stderr, "ERROR\n");
  ERR_print_errors_fp(stderr);
  freeCrypto();
  exit(1);
}

bool isValidHexChar(char c) {
  return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}

unsigned char hex2uchar(char *hex) {
  unsigned char ret;

  if (hex[0] >= 'a' && hex[0] <= 'f') ret = (hex[0] - 'a' + 10) * 16;
  else ret = (hex[0] - '0') * 16;
  if (hex[1] >= 'a' && hex[1] <= 'f') ret += hex[1] - 'a' + 10;
  else ret += hex[1] - '0';
  return ret;
}

int main(int ac, char **av, char **ae)
{
  const EVP_CIPHER *cipher;
  unsigned char key[32];
  int iv_len, len, i;
  unsigned char tag[16];

  if (ac != 3) {
    fprintf(stderr, "usage: %s KEY IV\n", av[0]);
    return 1;
  }

  char *key_txt = av[1];
  char *iv_txt = av[2];

  ERR_load_crypto_strings();

  if (strlen(key_txt) != 2 * sizeof key) {
    fprintf(stderr, "invalid key size\n");
    freeCrypto();
    return 1;
  }

  if (strlen(iv_txt) < 2 || strlen(iv_txt) % 2) {
    fprintf(stderr, "invalid IV size\n");
    freeCrypto();
    return 1;
  }
  iv_len = strlen(iv_txt) / 2;

  if (!(iv = malloc(iv_len))) {
    perror("malloc");
    freeCrypto();
    return 1;
  }

  if (!(buf_plain = malloc(iv_len))) {
    perror("malloc");
    freeCrypto();
    return 1;
  }

  if (!(buf_cipher = malloc(iv_len))) {
    perror("malloc");
    freeCrypto();
    return 1;
  }

  for (i = 0; i < sizeof key; i++) {
    if (!isValidHexChar(key_txt[2*i]) || !isValidHexChar(key_txt[2*i+1])) handleCryptoError();
    key[i] = hex2uchar(key_txt + 2*i);
  }

  for (i = 0; i < iv_len; i++) {
    if (!isValidHexChar(iv_txt[2*i]) || !isValidHexChar(iv_txt[2*i+1])) handleCryptoError();
    iv[i] = hex2uchar(iv_txt + 2*i);
  }

  if (!(ctx = EVP_CIPHER_CTX_new())) handleCryptoError();
  if (!(cipher = EVP_aes_256_gcm())) handleCryptoError();
  if (1 != EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL)) handleCryptoError();
  if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) handleCryptoError();
  if (1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) handleCryptoError();

  do {
    size_t ret = fread(buf_plain, 1, iv_len, stdin);
    if (!ret) {
      if (ferror(stdin)) {
    perror("fread");
        freeCrypto();
        return 1;
      }
      if (feof(stdin)) break;
    }

    if (1 != EVP_EncryptUpdate(ctx, buf_cipher, &len, buf_plain, ret)) handleCryptoError();

    if (len && !fwrite(buf_cipher, len, 1, stdout)) {
      if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
      else perror("fwrite");
      freeCrypto();
      return 1;
    }

  } while (1);

  if (1 != EVP_EncryptFinal_ex(ctx, buf_cipher, &len)) handleCryptoError();

  if (len && !fwrite(buf_cipher, len, 1, stdout)) {
    if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
    else perror("fwrite");
    freeCrypto();
    return 1;
  }

  if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag)) handleCryptoError();
  if (!fwrite(tag, sizeof tag, 1, stdout)) {
    if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
    else perror("fwrite");
    freeCrypto();
    return 1;
  }

  fflush(stdout);
  freeCrypto();
  return 0;
}

Y aquí es cómo descifrar lo que el programa anterior ha cifrado:

(compile de esta manera gcc -Wall -lcrypto -o aes256gcm-decrypt aes256gcm-decrypt.c )

// AES-256-GCM with libcrypto
// gcc -Wall -lcrypto -o aes256gcm-decrypt aes256gcm-decrypt.c

// tag is 16 bytes long
// no AAD (Additional Associated Data)
// input format: tag is read just after cipher text (see RFC-5116, sections 5.1 and 5.2)

// KEY=a6a7ee7abe681c9c4cede8e3366a9ded96b92668ea5e26a31a4b0856341ed224
// IV=87b7225d16ea2ae1f41d0b13fdce9bba
// cat ciphertext | ./aes256gcm-decrypt $KEY $IV

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>

EVP_CIPHER_CTX *ctx = NULL;
unsigned char *iv = NULL;
unsigned char *buf_plain = NULL;
unsigned char *buf_cipher = NULL;
unsigned char *input = NULL;

typedef enum { false, true } bool;

void freeCrypto() {
  if (input) free(input);

  if (ctx) {
    EVP_CIPHER_CTX_free(ctx);
    ctx = NULL;
  }
  CRYPTO_cleanup_all_ex_data();
  ERR_free_strings();

  if (iv) {
    free(iv);
    iv = NULL;
  }
  if (buf_plain) {
    free(buf_plain);
    buf_plain = NULL;
  }
  if (buf_cipher) {
    free(buf_cipher);
    buf_cipher = NULL;
  }
}

void handleCryptoError() {
  fprintf(stderr, "ERROR\n");
  ERR_print_errors_fp(stderr);
  freeCrypto();
  exit(1);
}

bool isValidHexChar(char c) {
  return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}

unsigned char hex2uchar(char *hex) {
  unsigned char ret;

  if (hex[0] >= 'a' && hex[0] <= 'f') ret = (hex[0] - 'a' + 10) * 16;
  else ret = (hex[0] - '0') * 16;
  if (hex[1] >= 'a' && hex[1] <= 'f') ret += hex[1] - 'a' + 10;
  else ret += hex[1] - '0';
  return ret;
}

unsigned char *loadInput(int *plen) {
  int len = 0;
  unsigned char *buf = NULL;
  unsigned char *old_buf;

  do {
    int c = fgetc(stdin);
    if (c == EOF) break;
    if (c < 0) {
      perror("fgetc");
      exit(1);
    }
    len++;
    old_buf = buf;
    buf = malloc(len);
    if (buf < 0) {
      perror("malloc");
      exit(1);
    }
    if (len > 1) bcopy(old_buf, buf, len - 1);
    buf[len - 1] = c;
    if (old_buf) free(old_buf);
  } while (1);

  *plen = len;
  return buf;
}

int main(int ac, char **av, char **ae)
{
  const EVP_CIPHER *cipher;
  unsigned char key[32];
  int iv_len, len, i;
  unsigned char *current;
  int input_len;

  if (ac != 3) {
    fprintf(stderr, "usage: %s KEY IV\n", av[0]);
    return 1;
  }

  char *key_txt = av[1];
  char *iv_txt = av[2];

  input = loadInput(&input_len);
  current = input;

  ERR_load_crypto_strings();

  if (strlen(key_txt) != 2 * sizeof key) {
    fprintf(stderr, "invalid key size\n");
    freeCrypto();
    return 1;
  }

  if (strlen(iv_txt) < 2 || strlen(iv_txt) % 2) {
    fprintf(stderr, "invalid IV size\n");
    freeCrypto();
    return 1;
  }
  iv_len = strlen(iv_txt) / 2;

  if (!(iv = malloc(iv_len))) {
    perror("malloc");
    freeCrypto();
    return 1;
  }

  if (!(buf_plain = malloc(iv_len))) {
    perror("malloc");
    freeCrypto();
    return 1;
  }

  if (!(buf_cipher = malloc(iv_len))) {
    perror("malloc");
    freeCrypto();
    return 1;
  }

  for (i = 0; i < sizeof key; i++) {
    if (!isValidHexChar(key_txt[2*i]) || !isValidHexChar(key_txt[2*i+1])) handleCryptoError();
    key[i] = hex2uchar(key_txt + 2*i);
  }

  for (i = 0; i < iv_len; i++) {
    if (!isValidHexChar(iv_txt[2*i]) || !isValidHexChar(iv_txt[2*i+1])) handleCryptoError();
    iv[i] = hex2uchar(iv_txt + 2*i);
  }

  if (!(ctx = EVP_CIPHER_CTX_new())) handleCryptoError();
  if (!(cipher = EVP_aes_256_gcm())) handleCryptoError();
  if (1 != EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL)) handleCryptoError();
  if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) handleCryptoError();
  if (1 != EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleCryptoError();
  if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, input + input_len - 16)) handleCryptoError();

  do {
    int nbytes = input + input_len - 16 - current;
    if (nbytes > iv_len) nbytes = iv_len;
    if (!nbytes) break;

    bcopy(current, buf_plain, nbytes);
    current += nbytes;

    if (1 != EVP_DecryptUpdate(ctx, buf_cipher, &len, buf_plain, nbytes)) handleCryptoError();

    if (len && !fwrite(buf_cipher, len, 1, stdout)) {
      if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
      else perror("fwrite");
      freeCrypto();
      return 1;
    }

  } while (1);

  // correct tag is checked here
  if (EVP_DecryptFinal_ex(ctx, buf_cipher, &len) <= 0) handleCryptoError();

  if (len && !fwrite(buf_cipher, len, 1, stdout)) {
    if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
    else perror("fwrite");
    freeCrypto();
    return 1;
  }

  fflush(stdout);
  freeCrypto();
  return 0;
}
    
respondido por el Alexandre Fenyo 07.08.2017 - 20:51
fuente

Lea otras preguntas en las etiquetas